<commit_before>package gonf
// # Gonf!!!
// Loads a configuration from a file into a map[string]string.

// Currently no support for loading the configuration options into
// their respective types, but that should all come in good time.
//
// Original consideration was to let you specify if you wanted Gonf to
// panic, print, or fatal if the file could not be read/loaded etc but
// that seemed excessive for this iddy biddy package. You're a big coder,
// I'm sure you can manage some proper error handling. :)
import (
  "io/ioutil"
  "regexp"
  "strings"
)
// ### Parameters
// - fname _string_ - the file name with absolute path to the config file in question
func GetGonf(fname string) (map[string]string, error) {
  // Default separator for now...
  sep := "\n"

  // Get the file.
  conf, err := ioutil.ReadFile(fname)

  // ### _Be responsible and check for errors!_
  if err != nil {
    return nil, err
  }

  // ### Create somewhere to keep out results
  config := make(map[string]string)

  // ### Parse the file.
  lines := strings.Split(string(conf[:]), sep)

  // ### Analyse what we have for some config...
  // This is a relatively naive process at the moment.
  for _, v := range lines {
    // Find the lines that start with valid characters and contain a value.
    // It's only one value per line at the moment. :(
    matched, _ := regexp.MatchString("^[a-z, A-Z, 0-9]+(=| = )+", strings.TrimSpace(v))
    if matched && len(v) > 0 {
      // Break out our respective values.
      // __TODO__ Fix this so we can comment inline.
      line := strings.Split(v, "=")
      // Trim our final values so we don't waste our time with gritty whitespace
      // That stuff gets in your teeth, it's horrible...
      config[strings.TrimSpace(line[0])] = strings.TrimSpace(line[1])
      // A moose might also have bit my sister. She didn't whine about it on
      // film though like some cry baby projectionist I know.
    }
  }
  // Oh yeah, actually give the values back.. Heh. That was close.
  return config, err
}
<commit_msg>Updated Func Desc<commit_after>package gonf
// # Gonf!!!
// Loads a configuration from a file into a map[string]string.

// Currently no support for loading the configuration options into
// their respective types, but that should all come in good time.
//
// Original consideration was to let you specify if you wanted Gonf to
// panic, print, or fatal if the file could not be read/loaded etc but
// that seemed excessive for this iddy biddy package. You're a big coder,
// I'm sure you can manage some proper error handling. :)
import (
  "io/ioutil"
  "regexp"
  "strings"
)
// Loads a configuration from a file into a map[string]string.
func GetGonf(fname string) (map[string]string, error) {
  // Default separator for now...
  sep := "\n"

  // Get the file.
  conf, err := ioutil.ReadFile(fname)

  // ### _Be responsible and check for errors!_
  if err != nil {
    return nil, err
  }

  // ### Create somewhere to keep out results
  config := make(map[string]string)

  // ### Parse the file.
  lines := strings.Split(string(conf[:]), sep)

  // ### Analyse what we have for some config...
  // This is a relatively naive process at the moment.
  for _, v := range lines {
    // Find the lines that start with valid characters and contain a value.
    // It's only one value per line at the moment. :(
    matched, _ := regexp.MatchString("^[a-z, A-Z, 0-9]+(=| = )+", strings.TrimSpace(v))
    if matched && len(v) > 0 {
      // Break out our respective values.
      // __TODO__ Fix this so we can comment inline.
      line := strings.Split(v, "=")
      // Trim our final values so we don't waste our time with gritty whitespace
      // That stuff gets in your teeth, it's horrible...
      config[strings.TrimSpace(line[0])] = strings.TrimSpace(line[1])
      // A moose might also have bit my sister. She didn't whine about it on
      // film though like some cry baby projectionist I know.
    }
  }
  // Oh yeah, actually give the values back.. Heh. That was close.
  return config, err
}
<|endoftext|>


<commit_before>// The algorithm implemented here is based on "An O(NP) Sequence Comparison Algorithm"
// by described by Sun Wu, Udi Manber and Gene Myers

package gonp

import (
	"container/list"
	"fmt"
	"math"
	"unicode/utf8"
)

type SesType int

const (
	Delete SesType = iota
	Common
	Add
)

type Point struct {
	x, y, k int
}

type SesElem struct {
	c rune
	t SesType
}

type Diff struct {
	a    []rune
	b    []rune
	m, n int
	ed   int
	ctl  *Ctl
	lcs  *list.List
	ses  *list.List
}

type Ctl struct {
	reverse  bool
	path     []int
	onlyEd   bool
	pathposi map[int]Point
}

func max(x, y int) int {
	return int(math.Max(float64(x), float64(y)))
}

func New(a string, b string) *Diff {
	m, n := utf8.RuneCountInString(a), utf8.RuneCountInString(b)
	diff := new(Diff)
	ctl := new(Ctl)
	if m >= n {
		diff.a, diff.b = []rune(b), []rune(a)
		diff.m, diff.n = n, m
		ctl.reverse = true
	} else {
		diff.a, diff.b = []rune(a), []rune(b)
		diff.m, diff.n = m, n
		ctl.reverse = false
	}
	ctl.onlyEd = false
	diff.ctl = ctl
	return diff
}

func (diff *Diff) OnlyEd() {
	diff.ctl.onlyEd = true
}

func (diff *Diff) Editdistance() int {
	return diff.ed
}

func (diff *Diff) Lcs() string {
	var b = make([]rune, diff.lcs.Len())
	for i, e := 0, diff.lcs.Front(); e != nil; i, e = i+1, e.Next() {
		b[i] = e.Value.(rune)
	}
	return string(b)
}

func (diff *Diff) Ses() []SesElem {
	seq := make([]SesElem, diff.ses.Len())
	for i, e := 0, diff.ses.Front(); e != nil; i, e = i+1, e.Next() {
		seq[i].c = e.Value.(SesElem).c
		seq[i].t = e.Value.(SesElem).t
	}
	return seq
}

func (diff *Diff) PrintSes() {
	for _, e := 0, diff.ses.Front(); e != nil; e = e.Next() {
		ee := e.Value.(SesElem)
		switch ee.t {
		case Delete:
			fmt.Println("- " + string(ee.c))
		case Add:
			fmt.Println("+ " + string(ee.c))
		case Common:
			fmt.Println("  " + string(ee.c))
		}
	}
}

func (diff *Diff) Compose() {
	offset := diff.m + 1
	delta := diff.n - diff.m
	size := diff.m + diff.n + 3
	fp := make([]int, size)
	diff.ctl.path = make([]int, size)
	diff.ctl.pathposi = make(map[int]Point)
	diff.lcs = list.New()
	diff.ses = list.New()

	for i := range fp {
		fp[i] = -1
		diff.ctl.path[i] = -1
	}

	for p := 0; ; p++ {

		for k := -p; k <= delta-1; k++ {
			fp[k+offset] = diff.snake(k, fp[k-1+offset]+1, fp[k+1+offset], offset)
		}

		for k := delta + p; k >= delta+1; k-- {
			fp[k+offset] = diff.snake(k, fp[k-1+offset]+1, fp[k+1+offset], offset)
		}

		fp[delta+offset] = diff.snake(delta, fp[delta-1+offset]+1, fp[delta+1+offset], offset)

		if fp[delta+offset] >= diff.n {
			diff.ed = delta + 2*p
			break
		}
	}

	if diff.ctl.onlyEd {
		return
	}

	r := diff.ctl.path[delta+offset]
	epc := make(map[int]Point)
	for r != -1 {
		epc[len(epc)] = Point{x: diff.ctl.pathposi[r].x, y: diff.ctl.pathposi[r].y, k: -1}
		r = diff.ctl.pathposi[r].k
	}
	diff.recordSeq(epc)
}

func (diff *Diff) recordSeq(epc map[int]Point) {
	x_idx, y_idx := 1, 1
	px_idx, py_idx := 0, 0
	ctl := diff.ctl
	for i := len(epc) - 1; i >= 0; i-- {
		for (px_idx < epc[i].x) || (py_idx < epc[i].y) {
			var t SesType
			if (epc[i].y - epc[i].x) > (py_idx - px_idx) {
				elem := diff.b[py_idx]
				if ctl.reverse {
					t = Delete
				} else {
					t = Add
				}
				diff.ses.PushBack(SesElem{c: elem, t: t})
				y_idx++
				py_idx++
			} else if epc[i].y-epc[i].x < py_idx-px_idx {
				elem := diff.a[px_idx]
				if ctl.reverse {
					t = Add
				} else {
					t = Delete
				}
				diff.ses.PushBack(SesElem{c: elem, t: t})
				x_idx++
				px_idx++
			} else {
				elem := diff.a[px_idx]
				t = Common
				diff.lcs.PushBack(elem)
				diff.ses.PushBack(SesElem{c: elem, t: t})
				x_idx++
				y_idx++
				px_idx++
				py_idx++
			}
		}
	}
}

func (diff *Diff) snake(k, p, pp, offset int) int {
	r := 0
	if p > pp {
		r = diff.ctl.path[k-1+offset]
	} else {
		r = diff.ctl.path[k+1+offset]
	}

	y := max(p, pp)
	x := y - k

	for x < diff.m && y < diff.n && diff.a[x] == diff.b[y] {
		x++
		y++
	}

	if !diff.ctl.onlyEd {
		diff.ctl.path[k+offset] = len(diff.ctl.pathposi)
		diff.ctl.pathposi[len(diff.ctl.pathposi)] = Point{x: x, y: y, k: r}
	}

	return y
}
<commit_msg>renamed ctl to ctx.<commit_after>// The algorithm implemented here is based on "An O(NP) Sequence Comparison Algorithm"
// by described by Sun Wu, Udi Manber and Gene Myers

package gonp

import (
	"container/list"
	"fmt"
	"math"
	"unicode/utf8"
)

const (
	Delete SesType = iota
	Common
	Add
)

type SesType int

type Point struct {
	x, y, k int
}

type SesElem struct {
	c rune
	t SesType
}

type Diff struct {
	a    []rune
	b    []rune
	m, n int
	ed   int
	ctx  *Ctx
	lcs  *list.List
	ses  *list.List
}

type Ctx struct {
	reverse  bool
	path     []int
	onlyEd   bool
	pathposi map[int]Point
}

func max(x, y int) int {
	return int(math.Max(float64(x), float64(y)))
}

func New(a string, b string) *Diff {
	m, n := utf8.RuneCountInString(a), utf8.RuneCountInString(b)
	diff := new(Diff)
	ctx := new(Ctx)
	if m >= n {
		diff.a, diff.b = []rune(b), []rune(a)
		diff.m, diff.n = n, m
		ctx.reverse = true
	} else {
		diff.a, diff.b = []rune(a), []rune(b)
		diff.m, diff.n = m, n
		ctx.reverse = false
	}
	ctx.onlyEd = false
	diff.ctx = ctx
	return diff
}

func (diff *Diff) OnlyEd() {
	diff.ctx.onlyEd = true
}

func (diff *Diff) Editdistance() int {
	return diff.ed
}

func (diff *Diff) Lcs() string {
	var b = make([]rune, diff.lcs.Len())
	for i, e := 0, diff.lcs.Front(); e != nil; i, e = i+1, e.Next() {
		b[i] = e.Value.(rune)
	}
	return string(b)
}

func (diff *Diff) Ses() []SesElem {
	seq := make([]SesElem, diff.ses.Len())
	for i, e := 0, diff.ses.Front(); e != nil; i, e = i+1, e.Next() {
		seq[i].c = e.Value.(SesElem).c
		seq[i].t = e.Value.(SesElem).t
	}
	return seq
}

func (diff *Diff) PrintSes() {
	for _, e := 0, diff.ses.Front(); e != nil; e = e.Next() {
		ee := e.Value.(SesElem)
		switch ee.t {
		case Delete:
			fmt.Println("- " + string(ee.c))
		case Add:
			fmt.Println("+ " + string(ee.c))
		case Common:
			fmt.Println("  " + string(ee.c))
		}
	}
}

func (diff *Diff) Compose() {
	offset := diff.m + 1
	delta := diff.n - diff.m
	size := diff.m + diff.n + 3
	fp := make([]int, size)
	diff.ctx.path = make([]int, size)
	diff.ctx.pathposi = make(map[int]Point)
	diff.lcs = list.New()
	diff.ses = list.New()

	for i := range fp {
		fp[i] = -1
		diff.ctx.path[i] = -1
	}

	for p := 0; ; p++ {

		for k := -p; k <= delta-1; k++ {
			fp[k+offset] = diff.snake(k, fp[k-1+offset]+1, fp[k+1+offset], offset)
		}

		for k := delta + p; k >= delta+1; k-- {
			fp[k+offset] = diff.snake(k, fp[k-1+offset]+1, fp[k+1+offset], offset)
		}

		fp[delta+offset] = diff.snake(delta, fp[delta-1+offset]+1, fp[delta+1+offset], offset)

		if fp[delta+offset] >= diff.n {
			diff.ed = delta + 2*p
			break
		}
	}

	if diff.ctx.onlyEd {
		return
	}

	r := diff.ctx.path[delta+offset]
	epc := make(map[int]Point)
	for r != -1 {
		epc[len(epc)] = Point{x: diff.ctx.pathposi[r].x, y: diff.ctx.pathposi[r].y, k: -1}
		r = diff.ctx.pathposi[r].k
	}
	diff.recordSeq(epc)
}

func (diff *Diff) recordSeq(epc map[int]Point) {
	x_idx, y_idx := 1, 1
	px_idx, py_idx := 0, 0
	ctx := diff.ctx
	for i := len(epc) - 1; i >= 0; i-- {
		for (px_idx < epc[i].x) || (py_idx < epc[i].y) {
			var t SesType
			if (epc[i].y - epc[i].x) > (py_idx - px_idx) {
				elem := diff.b[py_idx]
				if ctx.reverse {
					t = Delete
				} else {
					t = Add
				}
				diff.ses.PushBack(SesElem{c: elem, t: t})
				y_idx++
				py_idx++
			} else if epc[i].y-epc[i].x < py_idx-px_idx {
				elem := diff.a[px_idx]
				if ctx.reverse {
					t = Add
				} else {
					t = Delete
				}
				diff.ses.PushBack(SesElem{c: elem, t: t})
				x_idx++
				px_idx++
			} else {
				elem := diff.a[px_idx]
				t = Common
				diff.lcs.PushBack(elem)
				diff.ses.PushBack(SesElem{c: elem, t: t})
				x_idx++
				y_idx++
				px_idx++
				py_idx++
			}
		}
	}
}

func (diff *Diff) snake(k, p, pp, offset int) int {
	r := 0
	if p > pp {
		r = diff.ctx.path[k-1+offset]
	} else {
		r = diff.ctx.path[k+1+offset]
	}

	y := max(p, pp)
	x := y - k

	for x < diff.m && y < diff.n && diff.a[x] == diff.b[y] {
		x++
		y++
	}

	if !diff.ctx.onlyEd {
		diff.ctx.path[k+offset] = len(diff.ctx.pathposi)
		diff.ctx.pathposi[len(diff.ctx.pathposi)] = Point{x: x, y: y, k: r}
	}

	return y
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/ugorji/go-msgpack"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

type Cache struct {
	Paths   map[string]Path
	History []string
}

type Path struct {
	Dir   string
	Execs []string
	Mtime int64
}

func main() {
	// Where's the cache?
	cacheDir := os.Getenv("XDG_CACHE_HOME")
	if cacheDir == "" {
		cacheDir = filepath.Join(os.Getenv("HOME"), ".cache")
	}

	// Per the freedesktop spec, non-existent directories should be created 0700
	os.MkdirAll(cacheDir, 0700)
	cacheName := filepath.Join(cacheDir, "gorn.msgpack")

	// Read the cache
	in, _ := os.Open(cacheName)
	dec := msgpack.NewDecoder(in, nil)
	var cache Cache
	dec.Decode(&cache)

	candidates := make(map[string]string)
	// Populate history map
	historyMap := make(map[string]int)
	for i, exec := range cache.History {
		historyMap[exec] = i
	}

	// Check timestamps of everything on $PATH. If the timestamp is newer,
	// regenerate that path
	pathEnv := os.Getenv("PATH")
	paths := strings.Split(pathEnv, ":")
	for _, path := range paths {
		if path == "." {
			continue
		}
		fi, e := os.Stat(path)
		if e != nil {
			continue
		}
		mtime := fi.ModTime().Unix()
		if cache.Paths[path].Mtime != mtime {
			// Regenerate path
			if len(cache.Paths) == 0 {
				cache.Paths = make(map[string]Path, 64)
			}
			cache.Paths[path] = regenerate(path)
		}

		// now that the cache is up-to-date, read it and add to candidates
		for _, exec := range cache.Paths[path].Execs {
			// if it's not in previous input
			if _, ok := historyMap[exec]; !ok {
				// add it to candidates
				candidates[exec] = exec
			}
		}

	}

	var input []string
	// print previous input in order ...
	for _, exec := range cache.History {
		input = append(input, exec)
	}
	// print candidates in any order
	for exec := range candidates {
		input = append(input, exec)
	}
	inputJoined := strings.Join(input, "\n")
	reader := strings.NewReader(inputJoined)

	// get dmenu output
	dmenu := exec.Command("dmenu", os.Args[1:]...)
	dmenu.Stdin = reader
	dmenuBytes, _ := dmenu.Output()
	dmenuOut := strings.TrimSpace(string(dmenuBytes))

	// run it, without a shell
	progParts := strings.Split(dmenuOut, " ")
	path, err := exec.LookPath(progParts[0])
	if err != nil {
		log.Fatal("executable not found in path")
	}
	prog := exec.Command(path, progParts[1:]...)
	prog.Start()

	// add to beginning of list
	newHistory := []string{dmenuOut}
	// if dmenu output in previous input
	if i, ok := historyMap[dmenuOut]; ok {
		// remove it
		before := cache.History[:i]
		after := cache.History[i+1:]
		cache.History = append(before, after...)
	}
	cache.History = append(newHistory, cache.History...)
	cache.History = cleanHistory(cache.History)

	// serialize previous input list and write
	// serialize paths and write
	out, _ := os.Create(cacheName)
	enc := msgpack.NewEncoder(out)
	enc.Encode(&cache)
}

func cleanHistory(oldHistory []string) []string {
	// remove dead entries before serialization
	var cleanHistory []string
	for _, command := range oldHistory {
		executable := strings.Split(command, " ")[0]
		_, err := exec.LookPath(executable)
		if err != nil {
			log.Printf("Pruning lost command: %s\n", command)
			continue
		}
		cleanHistory = append(cleanHistory, command)
	}
	return cleanHistory
}

func regenerate(pathname string) Path {
	var p Path
	p.Dir = pathname
	fi, _ := os.Stat(pathname)
	p.Mtime = fi.ModTime().Unix()

	fileinfos, _ := ioutil.ReadDir(pathname)
	for _, fi := range fileinfos {
		// Is it an executable?
		if fi.IsDir() == false && fi.Mode()&0111 != 0 {
			p.Execs = append(p.Execs, fi.Name())
		}
	}
	return p
}
<commit_msg>These seem like methods. Let's make them methods.<commit_after>package main

import (
	"github.com/ugorji/go-msgpack"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

type Cache struct {
	Paths   map[string]Path
	History []string
}

type Path struct {
	Dir   string
	Execs []string
	Mtime int64
}

func (cache *Cache) where() string {
	// Where's the cache?
	cacheDir := os.Getenv("XDG_CACHE_HOME")
	if cacheDir == "" {
		cacheDir = filepath.Join(os.Getenv("HOME"), ".cache")
	}

	// Per the freedesktop spec, non-existent directories should be created 0700
	os.MkdirAll(cacheDir, 0700)
	cacheName := filepath.Join(cacheDir, "gorn.msgpack")

	return cacheName
}

func (cache *Cache) Write() {
	cacheName := cache.where()

	// serialize previous input list and write
	// serialize paths and write
	out, _ := os.Create(cacheName)
	enc := msgpack.NewEncoder(out)
	enc.Encode(&cache)
}

func (cache *Cache) Read() {
	cacheName := cache.where()

	// Read the cache
	in, _ := os.Open(cacheName)
	dec := msgpack.NewDecoder(in, nil)
	dec.Decode(&cache)
}

func main() {

	var cache Cache
	cache.Read()

	candidates := make(map[string]string)
	// Populate history map
	historyMap := make(map[string]int)
	for i, exec := range cache.History {
		historyMap[exec] = i
	}

	// Check timestamps of everything on $PATH. If the timestamp is newer,
	// regenerate that path
	pathEnv := os.Getenv("PATH")
	paths := strings.Split(pathEnv, ":")
	for _, path := range paths {
		if path == "." {
			continue
		}
		fi, e := os.Stat(path)
		if e != nil {
			continue
		}
		mtime := fi.ModTime().Unix()
		if cache.Paths[path].Mtime != mtime {
			// Regenerate path
			if len(cache.Paths) == 0 {
				cache.Paths = make(map[string]Path, 64)
			}
			cache.Paths[path] = regenerate(path)
		}

		// now that the cache is up-to-date, read it and add to candidates
		for _, exec := range cache.Paths[path].Execs {
			// if it's not in previous input
			if _, ok := historyMap[exec]; !ok {
				// add it to candidates
				candidates[exec] = exec
			}
		}

	}

	var input []string
	// print previous input in order ...
	for _, exec := range cache.History {
		input = append(input, exec)
	}
	// print candidates in any order
	for exec := range candidates {
		input = append(input, exec)
	}
	inputJoined := strings.Join(input, "\n")
	reader := strings.NewReader(inputJoined)

	// get dmenu output
	dmenu := exec.Command("dmenu", os.Args[1:]...)
	dmenu.Stdin = reader
	dmenuBytes, _ := dmenu.Output()
	dmenuOut := strings.TrimSpace(string(dmenuBytes))

	// run it, without a shell
	progParts := strings.Split(dmenuOut, " ")
	path, err := exec.LookPath(progParts[0])
	if err != nil {
		log.Fatal("executable not found in path")
	}
	prog := exec.Command(path, progParts[1:]...)
	prog.Start()

	// add to beginning of list
	newHistory := []string{dmenuOut}
	// if dmenu output in previous input
	if i, ok := historyMap[dmenuOut]; ok {
		// remove it
		before := cache.History[:i]
		after := cache.History[i+1:]
		cache.History = append(before, after...)
	}
	cache.History = append(newHistory, cache.History...)
	cache.History = cleanHistory(cache.History)

	cache.Write()
}

func cleanHistory(oldHistory []string) []string {
	// remove dead entries before serialization
	var cleanHistory []string
	for _, command := range oldHistory {
		executable := strings.Split(command, " ")[0]
		_, err := exec.LookPath(executable)
		if err != nil {
			log.Printf("Pruning lost command: %s\n", command)
			continue
		}
		cleanHistory = append(cleanHistory, command)
	}
	return cleanHistory
}

func regenerate(pathname string) Path {
	var p Path
	p.Dir = pathname
	fi, _ := os.Stat(pathname)
	p.Mtime = fi.ModTime().Unix()

	fileinfos, _ := ioutil.ReadDir(pathname)
	for _, fi := range fileinfos {
		// Is it an executable?
		if fi.IsDir() == false && fi.Mode()&0111 != 0 {
			p.Execs = append(p.Execs, fi.Name())
		}
	}
	return p
}
<|endoftext|>


<commit_before>package internal

import "net/http"

const (
	DeleteIsolationSegmentRequest                        = "DeleteIsolationSegment"
	GetAppsRequest                                       = "Apps"
	GetAppTasksRequest                                   = "AppTasks"
	GetIsolationSegmentsRequest                          = "GetIsolationSegments"
	GetOrgsRequest                                       = "Orgs"
	PostAppTasksRequest                                  = "PostAppTasks"
	PostIsolationSegmentsRequest                         = "PostIsolationSegment"
	PostIsolationSegmentRelationshipOrganizationsRequest = "NewIsolationSegmentOrganizationRelationship"
)

const (
	AppsResource              = "apps"
	IsolationSegmentsResource = "isolation_segments"
	OrgsResource              = "organizations"
	TasksResource             = "tasks"
)

// APIRoutes is a list of routes used by the router to construct request URLs.
var APIRoutes = []Route{
	{Path: "/:guid", Method: http.MethodDelete, Name: DeleteIsolationSegmentRequest, Resource: IsolationSegmentsResource},
	{Path: "/:guid/relationships/organizations", Method: http.MethodPost, Name: PostIsolationSegmentRelationshipOrganizationsRequest, Resource: IsolationSegmentsResource},
	{Path: "/:guid/tasks", Method: http.MethodGet, Name: GetAppTasksRequest, Resource: AppsResource},
	{Path: "/:guid/tasks", Method: http.MethodPost, Name: PostAppTasksRequest, Resource: AppsResource},
	{Path: "/", Method: http.MethodGet, Name: GetAppsRequest, Resource: AppsResource},
	{Path: "/", Method: http.MethodGet, Name: GetIsolationSegmentsRequest, Resource: IsolationSegmentsResource},
	{Path: "/", Method: http.MethodGet, Name: GetOrgsRequest, Resource: OrgsResource},
	{Path: "/", Method: http.MethodPost, Name: PostIsolationSegmentsRequest, Resource: IsolationSegmentsResource},
}
<commit_msg>standardize and document  v3 API request constant naming convention<commit_after>package internal

import "net/http"

// Naming convention:
//
// Method + non-parameter parts of the path
//
// If the request returns a single entity by GUID, use the singular (for example
// /v2/organizations/:organization_guid is GetOrganization).
//
// The const name should always be the const value + Request.
const (
	DeleteIsolationSegmentRequest                        = "DeleteIsolationSegment"
	GetAppsRequest                                       = "GetApps"
	GetAppTasksRequest                                   = "GetAppTasks"
	GetIsolationSegmentsRequest                          = "GetIsolationSegments"
	GetOrgsRequest                                       = "GetOrgs"
	PostAppTasksRequest                                  = "PostAppTasks"
	PostIsolationSegmentsRequest                         = "PostIsolationSegments"
	PostIsolationSegmentRelationshipOrganizationsRequest = "PostIsolationSegmentRelationshipOrganizations"
)

const (
	AppsResource              = "apps"
	IsolationSegmentsResource = "isolation_segments"
	OrgsResource              = "organizations"
	TasksResource             = "tasks"
)

// APIRoutes is a list of routes used by the router to construct request URLs.
var APIRoutes = []Route{
	{Path: "/:guid", Method: http.MethodDelete, Name: DeleteIsolationSegmentRequest, Resource: IsolationSegmentsResource},
	{Path: "/:guid/relationships/organizations", Method: http.MethodPost, Name: PostIsolationSegmentRelationshipOrganizationsRequest, Resource: IsolationSegmentsResource},
	{Path: "/:guid/tasks", Method: http.MethodGet, Name: GetAppTasksRequest, Resource: AppsResource},
	{Path: "/:guid/tasks", Method: http.MethodPost, Name: PostAppTasksRequest, Resource: AppsResource},
	{Path: "/", Method: http.MethodGet, Name: GetAppsRequest, Resource: AppsResource},
	{Path: "/", Method: http.MethodGet, Name: GetIsolationSegmentsRequest, Resource: IsolationSegmentsResource},
	{Path: "/", Method: http.MethodGet, Name: GetOrgsRequest, Resource: OrgsResource},
	{Path: "/", Method: http.MethodPost, Name: PostIsolationSegmentsRequest, Resource: IsolationSegmentsResource},
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/ugorji/go-msgpack"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strings"
)

type Cache struct {
	Paths   map[string]Path
	History []string
}

type Path struct {
	Dir   string
	Execs []string
	Mtime int64
}

func main() {
	// Where's the cache?
	home := os.Getenv("HOME")
	cacheName := home + "/.cache/gorn.msgpack"
	// Read the cache
	in, _ := os.Open(cacheName)
	dec := msgpack.NewDecoder(in, nil)
	var cache Cache
	dec.Decode(&cache)

	// Check timestamps of everything on $PATH. If the timestamp is newer,
	// regenerate that path
	pathEnv := os.Getenv("PATH")
	paths := strings.Split(pathEnv, ":")
	for _, path := range paths {
		// TODO: compensate for missing paths
		fi, _ := os.Stat(path)
		mtime := fi.ModTime().Unix()
		if cache.Paths[path].Mtime != mtime {
			// Regenerate path
			if len(cache.Paths) == 0 {
				cache.Paths = make(map[string]Path, 64)
			}
			cache.Paths[path] = regenerate(path)
		}
	}

	candidates := make(map[string]string)
	// Populate history map
	historyMap := make(map[string]int)
	for i, exec := range cache.History {
		historyMap[exec] = i
	}
	// For executables in the paths dictionary
	for _, path := range cache.Paths {
		for _, exec := range path.Execs {
			// if it's not in previous input
			if _, ok := historyMap[exec]; !ok {
				// add it to candidates
				candidates[exec] = exec
			}
		}
	}

	var input []string
	// print previous input in order ...
	for _, exec := range cache.History {
		input = append(input, exec)
	}
	// print candidates in any order
	for exec := range candidates {
		input = append(input, exec)
	}
	inputJoined := strings.Join(input, "\n")
	reader := strings.NewReader(inputJoined)

	// get dmenu output
	dmenu := exec.Command("dmenu", os.Args[1:]...)
	dmenu.Stdin = reader
	dmenuBytes, _ := dmenu.Output()
	dmenuOut := strings.TrimSpace(string(dmenuBytes))

	// run it, without a shell
	progParts := strings.Split(dmenuOut, " ")
	path, err := exec.LookPath(progParts[0])
	if err != nil {
		log.Fatal("executable not found in path")
	}
  	prog := exec.Command(path, progParts[1:]...)
	prog.Start()

	// add to beginning of list
	newHistory := []string{dmenuOut}
	// if dmenu output in previous input
	if i, ok := historyMap[dmenuOut]; ok {
		// remove it
		before := cache.History[:i]
		after := cache.History[i+1:]
		cache.History = append(before, after...)
	}
	cache.History = append(newHistory, cache.History...)

	// serialize previous input list and write
	// serialize paths and write
	out, _ := os.Create(cacheName)
	enc := msgpack.NewEncoder(out)
	enc.Encode(&cache)
}

func regenerate(pathname string) Path {
	var p Path
	p.Dir = pathname
	fi, _ := os.Stat(pathname)
	p.Mtime = fi.ModTime().Unix()

	fileinfos, _ := ioutil.ReadDir(pathname)
	for _, fi := range fileinfos {
		// Is it an executable?
		if fi.IsDir() == false && fi.Mode()&0111 != 0 {
			p.Execs = append(p.Execs, fi.Name())
		}
	}
	return p
}
<commit_msg>gofmt<commit_after>package main

import (
	"github.com/ugorji/go-msgpack"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strings"
)

type Cache struct {
	Paths   map[string]Path
	History []string
}

type Path struct {
	Dir   string
	Execs []string
	Mtime int64
}

func main() {
	// Where's the cache?
	home := os.Getenv("HOME")
	cacheName := home + "/.cache/gorn.msgpack"
	// Read the cache
	in, _ := os.Open(cacheName)
	dec := msgpack.NewDecoder(in, nil)
	var cache Cache
	dec.Decode(&cache)

	// Check timestamps of everything on $PATH. If the timestamp is newer,
	// regenerate that path
	pathEnv := os.Getenv("PATH")
	paths := strings.Split(pathEnv, ":")
	for _, path := range paths {
		// TODO: compensate for missing paths
		fi, _ := os.Stat(path)
		mtime := fi.ModTime().Unix()
		if cache.Paths[path].Mtime != mtime {
			// Regenerate path
			if len(cache.Paths) == 0 {
				cache.Paths = make(map[string]Path, 64)
			}
			cache.Paths[path] = regenerate(path)
		}
	}

	candidates := make(map[string]string)
	// Populate history map
	historyMap := make(map[string]int)
	for i, exec := range cache.History {
		historyMap[exec] = i
	}
	// For executables in the paths dictionary
	for _, path := range cache.Paths {
		for _, exec := range path.Execs {
			// if it's not in previous input
			if _, ok := historyMap[exec]; !ok {
				// add it to candidates
				candidates[exec] = exec
			}
		}
	}

	var input []string
	// print previous input in order ...
	for _, exec := range cache.History {
		input = append(input, exec)
	}
	// print candidates in any order
	for exec := range candidates {
		input = append(input, exec)
	}
	inputJoined := strings.Join(input, "\n")
	reader := strings.NewReader(inputJoined)

	// get dmenu output
	dmenu := exec.Command("dmenu", os.Args[1:]...)
	dmenu.Stdin = reader
	dmenuBytes, _ := dmenu.Output()
	dmenuOut := strings.TrimSpace(string(dmenuBytes))

	// run it, without a shell
	progParts := strings.Split(dmenuOut, " ")
	path, err := exec.LookPath(progParts[0])
	if err != nil {
		log.Fatal("executable not found in path")
	}
	prog := exec.Command(path, progParts[1:]...)
	prog.Start()

	// add to beginning of list
	newHistory := []string{dmenuOut}
	// if dmenu output in previous input
	if i, ok := historyMap[dmenuOut]; ok {
		// remove it
		before := cache.History[:i]
		after := cache.History[i+1:]
		cache.History = append(before, after...)
	}
	cache.History = append(newHistory, cache.History...)

	// serialize previous input list and write
	// serialize paths and write
	out, _ := os.Create(cacheName)
	enc := msgpack.NewEncoder(out)
	enc.Encode(&cache)
}

func regenerate(pathname string) Path {
	var p Path
	p.Dir = pathname
	fi, _ := os.Stat(pathname)
	p.Mtime = fi.ModTime().Unix()

	fileinfos, _ := ioutil.ReadDir(pathname)
	for _, fi := range fileinfos {
		// Is it an executable?
		if fi.IsDir() == false && fi.Mode()&0111 != 0 {
			p.Execs = append(p.Execs, fi.Name())
		}
	}
	return p
}
<|endoftext|>


<commit_before>package main

import (
	plug "./plugins"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/jcline/goty"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)

var user = ""

type UriFunc func(*string) (*string, error)
type WriteFunc func(*plug.IRCMessage, *string) (*plug.IRCMessage, error)

// Commands
var matchHelp = regexp.MustCompile(`^help`)
var matchHelpTerms = regexp.MustCompile(`^help (.+)`)
var matchSpoilers = regexp.MustCompile(`(?i)(.*spoil.*)`)

func auth(con *goty.IRCConn, writeMessage chan plug.IRCMessage, user string) {
	var pswd string
	fmt.Printf("Password for NickServ:\n")
	_, err := fmt.Scanf("%s", &pswd)
	if err != nil {
		return
	}

	msg := plug.IRCMessage{Channel: "NickServ", Msg: "IDENTIFY " + user + " " + pswd}
	writeMessage <- msg
}

func exists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, ErrConfNotFound
	}
	return false, err
}

type Settings struct {
	Server   string   `json:"server"`
	UserName string   `json:"userName"`
	RealName string   `json:"realName"`
	Channels []string `json:"channels"`
}

var ErrConfNotFound = errors.New("Conf does not exist")

func readConfig() (conf Settings, path string, err error) {
	args := os.Args
	path = ""
	if len(args) == 2 {
		path = filepath.Clean(args[1])
	} else {
		path = os.Getenv("XDG_CONFIG_HOME")
		if path == "" {
			path = filepath.Join("$HOME", ".config", "goto", "conf")
		} else {
			path = filepath.Join(path, "goto", "conf")
		}
	}

	path, err = filepath.Abs(os.ExpandEnv(path))
	if err != nil {
		return
	}

	log.Println(path)

	_, err = exists(path)
	if err != nil {
		return
	}

	file, err := ioutil.ReadFile(path)
	if err != nil {
		return
	}

	err = json.Unmarshal(file, &conf)
	if err != nil {
		return
	}

	return
}

func createConfig(path string) (conf Settings, err error) {

	_, err = exists(path)
	log.Println(exists, err)
	if err == ErrConfNotFound {
		err = os.MkdirAll(filepath.Dir(path), 0644)
		log.Println(path, ":", filepath.Dir(path))
		if err != nil && !os.IsPermission(err) {
			return
		}
	}

	for {
		log.Println("Server (e.g. irc.freenode.net:6666):")
		_, err = fmt.Scanf("%s", &conf.Server)
		if err != nil {
			return
		}
		if !strings.Contains(conf.Server, ":") {
			log.Println("You must include a port.")
		} else {
			break
		}
	}

	for {
		log.Println("User name:")
		_, err = fmt.Scanf("%s", &conf.UserName)
		if err != nil {
			return
		}
		if conf.UserName == "" {
			log.Println("User name must not be empty")
		} else {
			break
		}
	}

	for {
		log.Println("Real name:")
		_, err = fmt.Scanf("%s", &conf.RealName)
		if err != nil {
			return
		}
		if conf.RealName == "" {
			log.Println("Real name must not be empty")
		} else {
			break
		}
	}

	for {
		log.Println("Channels to join (e.g. #chan1,#chan2 or #chan1):")
		var channels string
		_, err = fmt.Scanf("%s", &channels)
		if err != nil {
			return
		}
		if channels == "" || !strings.Contains(channels, "#") {
			log.Println("You must provide at least one channel")
		} else {
			conf.Channels = strings.Split(channels, ",")
			break
		}
	}

	js, err := json.Marshal(conf)
	if err != nil {
		return
	}

	log.Println("Writing to: ", path)
	err = ioutil.WriteFile(path, js, 0644)
	if err != nil {
		return
	}

	return
}

func main() {
	conf, path, err := readConfig()
	if err != nil {
		if err == ErrConfNotFound {
			log.Println("Could not read config, would you like to create one? [y/n]")
			var response string
			_, err := fmt.Scanf("%s", &response)
			if err != nil {
				log.Fatal(err)
			}
			if response == "y" || response == "Y" {
				conf, err = createConfig(path)
				if err != nil {
					log.Fatal(err)
				}
			} else {
				log.Fatal("I can't do anything without config.")
			}
		} else {
			log.Fatal(err)
		}
	}

	user = conf.UserName
	con, err := goty.Dial(conf.Server, conf.UserName, conf.RealName)
	if err != nil {
		log.Fatal(err)
	}

	rand.Seed(time.Now().UnixNano())

	writeMessage := make(chan plug.IRCMessage, 1000)
	go messageHandler(con, writeMessage, conf.Channels, 10, 2)

	var plugins []plug.Plugin
	plugins = append(plugins, new(plug.Youtube))
	plugins = append(plugins, new(plug.AmiAmi))
	plugins = append(plugins, new(plug.Reddit))
	plugins = append(plugins, new(plug.Mal))

	for _, plugin := range plugins {
		plugin.Setup()
		go scrapeAndSend(plugin.Event(), plugin.FindUri, plugin.Write, writeMessage)
	}

	auth(con, writeMessage, conf.UserName)
	for _, channel := range conf.Channels {
		con.Write <- "JOIN " + channel
	}

	for msg := range con.Read {
		log.Printf("%s\n", msg)
		prepared, err := getMsgInfo(msg)
		if err != nil {
			continue
		}
		prepared.When = time.Now()

		// half assed filtering
		_, notFound := getFirstMatch(matchSpoilers, &prepared.Msg)
		if notFound != nil {
			for _, plugin := range plugins {
				if plugin.Match().MatchString(prepared.Msg) {
					plugin.Event() <- *prepared
				}
			}
		}
	}
	con.Close()
}

type unparsedMessage struct {
	msg  string
	when time.Time
}

func message(con *goty.IRCConn, msg plug.IRCMessage) {
	privmsg := "PRIVMSG " + msg.Channel + " :" + msg.Msg + "\r\n"
	log.Println(privmsg)
	con.Write <- privmsg
}

func messageHandler(con *goty.IRCConn, event chan plug.IRCMessage, channels []string, chanDelay, pmDelay int) {
	allBooks := map[string]time.Time{}
	//chanBooks := map[string]time.Time{}
	for msg := range event {
		now := time.Now()
		key := msg.Channel + ":" + msg.User
		delay := pmDelay
		for _, channel := range channels {
			if msg.Channel == channel {
				delay = chanDelay
				break
			}
		}
		if now.Sub(allBooks[key]) < time.Duration(delay)*time.Second { //|| now.Sub(chanBooks[key]) < time.Second*2 {
			continue
		}
		allBooks[key] = now
		//chanBooks[key] = now
		message(con, msg)
	}
}

var PRIVMSG = regexp.MustCompile(`:(.+)![^ ]+ PRIVMSG ([^ ]+) :(.*)`)

func getMsgInfo(msg string) (*plug.IRCMessage, error) {
	// :nick!~realname@0.0.0.0 PRIVMSG #chan :msg
	imsg := new(plug.IRCMessage)
	match := PRIVMSG.FindAllStringSubmatch(msg, -1)
	if len(match) < 1 {
		return imsg, errors.New("could not parse message")
	}
	if len(match[0]) < 3 {
		return imsg, errors.New("could not parse message")
	}
	imsg.User = user
	imsg.Channel = match[0][2]
	if imsg.Channel == user {
		imsg.Channel = match[0][1]
	}
	imsg.Msg = match[0][3]
	return imsg, nil
}

func bastille(event chan plug.IRCMessage, writeMessage chan plug.IRCMessage) {
	msgs := []string{
		"Bastille, yo brodudedudebro!!!!1",
		"Bastille, wat up homie",
		"Bastille, word",
		"Bastille, duuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuude",
		"'sup Bastille?",
	}

	for msg := range event {
		writeMessage <- plug.IRCMessage{msg.Channel, msgs[rand.Intn(len(msgs))-1], msg.User, msg.When}
	}
}

func scrapeAndSend(event chan plug.IRCMessage, findUri UriFunc, write WriteFunc, writeMessage chan plug.IRCMessage) {
	var f = func(msg plug.IRCMessage) {
		uri, err := findUri(&msg.Msg)
		if err != nil {
			log.Println(err)
			return
		}

		resp, err := http.Get(*uri)
		if err != nil {
			log.Println(err)
			return
		}

		bodyBytes, err := ioutil.ReadAll(resp.Body)
		defer resp.Body.Close()
		if err != nil {
			log.Println(err)
			return
		}
		body := string(bodyBytes)

		outMsg, err := write(&msg, &body)
		if err != nil {
			log.Println(err)
			return
		}

		writeMessage <- *outMsg
	}

	for msg := range event {
		go f(msg)
	}
}

func getFirstMatch(re *regexp.Regexp, matchee *string) (*string, error) {
	match := re.FindAllStringSubmatch(*matchee, -1)
	if len(match) < 1 {
		return nil, errors.New("Could not match")
	}
	if len(match[0]) < 2 {
		return nil, errors.New("Could not match")
	}
	return &match[0][1], nil
}
<commit_msg>Delete bastille<commit_after>package main

import (
	plug "./plugins"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/jcline/goty"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"
)

var user = ""

type UriFunc func(*string) (*string, error)
type WriteFunc func(*plug.IRCMessage, *string) (*plug.IRCMessage, error)

// Commands
var matchHelp = regexp.MustCompile(`^help`)
var matchHelpTerms = regexp.MustCompile(`^help (.+)`)
var matchSpoilers = regexp.MustCompile(`(?i)(.*spoil.*)`)

func auth(con *goty.IRCConn, writeMessage chan plug.IRCMessage, user string) {
	var pswd string
	fmt.Printf("Password for NickServ:\n")
	_, err := fmt.Scanf("%s", &pswd)
	if err != nil {
		return
	}

	msg := plug.IRCMessage{Channel: "NickServ", Msg: "IDENTIFY " + user + " " + pswd}
	writeMessage <- msg
}

func exists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, ErrConfNotFound
	}
	return false, err
}

type Settings struct {
	Server   string   `json:"server"`
	UserName string   `json:"userName"`
	RealName string   `json:"realName"`
	Channels []string `json:"channels"`
}

var ErrConfNotFound = errors.New("Conf does not exist")

func readConfig() (conf Settings, path string, err error) {
	args := os.Args
	path = ""
	if len(args) == 2 {
		path = filepath.Clean(args[1])
	} else {
		path = os.Getenv("XDG_CONFIG_HOME")
		if path == "" {
			path = filepath.Join("$HOME", ".config", "goto", "conf")
		} else {
			path = filepath.Join(path, "goto", "conf")
		}
	}

	path, err = filepath.Abs(os.ExpandEnv(path))
	if err != nil {
		return
	}

	log.Println(path)

	_, err = exists(path)
	if err != nil {
		return
	}

	file, err := ioutil.ReadFile(path)
	if err != nil {
		return
	}

	err = json.Unmarshal(file, &conf)
	if err != nil {
		return
	}

	return
}

func createConfig(path string) (conf Settings, err error) {

	_, err = exists(path)
	log.Println(exists, err)
	if err == ErrConfNotFound {
		err = os.MkdirAll(filepath.Dir(path), 0644)
		log.Println(path, ":", filepath.Dir(path))
		if err != nil && !os.IsPermission(err) {
			return
		}
	}

	for {
		log.Println("Server (e.g. irc.freenode.net:6666):")
		_, err = fmt.Scanf("%s", &conf.Server)
		if err != nil {
			return
		}
		if !strings.Contains(conf.Server, ":") {
			log.Println("You must include a port.")
		} else {
			break
		}
	}

	for {
		log.Println("User name:")
		_, err = fmt.Scanf("%s", &conf.UserName)
		if err != nil {
			return
		}
		if conf.UserName == "" {
			log.Println("User name must not be empty")
		} else {
			break
		}
	}

	for {
		log.Println("Real name:")
		_, err = fmt.Scanf("%s", &conf.RealName)
		if err != nil {
			return
		}
		if conf.RealName == "" {
			log.Println("Real name must not be empty")
		} else {
			break
		}
	}

	for {
		log.Println("Channels to join (e.g. #chan1,#chan2 or #chan1):")
		var channels string
		_, err = fmt.Scanf("%s", &channels)
		if err != nil {
			return
		}
		if channels == "" || !strings.Contains(channels, "#") {
			log.Println("You must provide at least one channel")
		} else {
			conf.Channels = strings.Split(channels, ",")
			break
		}
	}

	js, err := json.Marshal(conf)
	if err != nil {
		return
	}

	log.Println("Writing to: ", path)
	err = ioutil.WriteFile(path, js, 0644)
	if err != nil {
		return
	}

	return
}

func main() {
	conf, path, err := readConfig()
	if err != nil {
		if err == ErrConfNotFound {
			log.Println("Could not read config, would you like to create one? [y/n]")
			var response string
			_, err := fmt.Scanf("%s", &response)
			if err != nil {
				log.Fatal(err)
			}
			if response == "y" || response == "Y" {
				conf, err = createConfig(path)
				if err != nil {
					log.Fatal(err)
				}
			} else {
				log.Fatal("I can't do anything without config.")
			}
		} else {
			log.Fatal(err)
		}
	}

	user = conf.UserName
	con, err := goty.Dial(conf.Server, conf.UserName, conf.RealName)
	if err != nil {
		log.Fatal(err)
	}

	rand.Seed(time.Now().UnixNano())

	writeMessage := make(chan plug.IRCMessage, 1000)
	go messageHandler(con, writeMessage, conf.Channels, 10, 2)

	var plugins []plug.Plugin
	plugins = append(plugins, new(plug.Youtube))
	plugins = append(plugins, new(plug.AmiAmi))
	plugins = append(plugins, new(plug.Reddit))
	plugins = append(plugins, new(plug.Mal))

	for _, plugin := range plugins {
		plugin.Setup()
		go scrapeAndSend(plugin.Event(), plugin.FindUri, plugin.Write, writeMessage)
	}

	auth(con, writeMessage, conf.UserName)
	for _, channel := range conf.Channels {
		con.Write <- "JOIN " + channel
	}

	for msg := range con.Read {
		log.Printf("%s\n", msg)
		prepared, err := getMsgInfo(msg)
		if err != nil {
			continue
		}
		prepared.When = time.Now()

		// half assed filtering
		_, notFound := getFirstMatch(matchSpoilers, &prepared.Msg)
		if notFound != nil {
			for _, plugin := range plugins {
				if plugin.Match().MatchString(prepared.Msg) {
					plugin.Event() <- *prepared
				}
			}
		}
	}
	con.Close()
}

type unparsedMessage struct {
	msg  string
	when time.Time
}

func message(con *goty.IRCConn, msg plug.IRCMessage) {
	privmsg := "PRIVMSG " + msg.Channel + " :" + msg.Msg + "\r\n"
	log.Println(privmsg)
	con.Write <- privmsg
}

func messageHandler(con *goty.IRCConn, event chan plug.IRCMessage, channels []string, chanDelay, pmDelay int) {
	allBooks := map[string]time.Time{}
	//chanBooks := map[string]time.Time{}
	for msg := range event {
		now := time.Now()
		key := msg.Channel + ":" + msg.User
		delay := pmDelay
		for _, channel := range channels {
			if msg.Channel == channel {
				delay = chanDelay
				break
			}
		}
		if now.Sub(allBooks[key]) < time.Duration(delay)*time.Second { //|| now.Sub(chanBooks[key]) < time.Second*2 {
			continue
		}
		allBooks[key] = now
		//chanBooks[key] = now
		message(con, msg)
	}
}

var PRIVMSG = regexp.MustCompile(`:(.+)![^ ]+ PRIVMSG ([^ ]+) :(.*)`)

func getMsgInfo(msg string) (*plug.IRCMessage, error) {
	// :nick!~realname@0.0.0.0 PRIVMSG #chan :msg
	imsg := new(plug.IRCMessage)
	match := PRIVMSG.FindAllStringSubmatch(msg, -1)
	if len(match) < 1 {
		return imsg, errors.New("could not parse message")
	}
	if len(match[0]) < 3 {
		return imsg, errors.New("could not parse message")
	}
	imsg.User = user
	imsg.Channel = match[0][2]
	if imsg.Channel == user {
		imsg.Channel = match[0][1]
	}
	imsg.Msg = match[0][3]
	return imsg, nil
}

func scrapeAndSend(event chan plug.IRCMessage, findUri UriFunc, write WriteFunc, writeMessage chan plug.IRCMessage) {
	var f = func(msg plug.IRCMessage) {
		uri, err := findUri(&msg.Msg)
		if err != nil {
			log.Println(err)
			return
		}

		resp, err := http.Get(*uri)
		if err != nil {
			log.Println(err)
			return
		}

		bodyBytes, err := ioutil.ReadAll(resp.Body)
		defer resp.Body.Close()
		if err != nil {
			log.Println(err)
			return
		}
		body := string(bodyBytes)

		outMsg, err := write(&msg, &body)
		if err != nil {
			log.Println(err)
			return
		}

		writeMessage <- *outMsg
	}

	for msg := range event {
		go f(msg)
	}
}

func getFirstMatch(re *regexp.Regexp, matchee *string) (*string, error) {
	match := re.FindAllStringSubmatch(*matchee, -1)
	if len(match) < 1 {
		return nil, errors.New("Could not match")
	}
	if len(match[0]) < 2 {
		return nil, errors.New("Could not match")
	}
	return &match[0][1], nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/xml" // gelbooru parsing
	"errors"
	"fmt"
	"github.com/jcline/goty"
	"html"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"regexp"
	"time"
)

var user = ""

var matchGelbooru = regexp.MustCompile(`.*\Qhttp://gelbooru.com/index.php?page=post&s=view&id=\E([\d]+).*`)
var matchYouTube = regexp.MustCompile(`.*(https?://(?:www\.|)youtu(?:\.be|be\.com)/[^ ]+).*`)
var matchAmiAmi = regexp.MustCompile(`(https?://(?:www\.|)amiami.com/[^/]+/detail/.*)`)

func auth(con *goty.IRCConn, writeMessage chan IRCMessage) {
	var pswd string
	_, err := fmt.Scanf("%s", &pswd)
	if err != nil {
		os.Exit(1)
	}

	msg := IRCMessage{channel: "NickServ", msg: "IDENTIFY Laala " + pswd}
	writeMessage <- msg
}

func main() {
	args := os.Args
	if len(args) < 4 {
		os.Exit(1)
	}

	con, err := goty.Dial(args[1], args[2], args[3])
	user = args[2]
	if err != nil {
		fmt.Fprintf(os.Stderr, "err: %s\n", err.Error())
	}

	gelbooruEvent := make(chan unparsedMessage, 1000)
	youtubeEvent := make(chan unparsedMessage, 1000)
	amiAmiEvent := make(chan unparsedMessage, 1000)
	bastilleEvent := make(chan unparsedMessage, 1000)
	writeMessage := make(chan IRCMessage, 1000)

	go messageHandler(con, writeMessage)
	go gelbooru(gelbooruEvent, writeMessage)
	go youtube(youtubeEvent, writeMessage)
	go amiami(amiAmiEvent, writeMessage)
	go bastille(bastilleEvent, writeMessage)

	auth(con, writeMessage)
	con.Write <- "JOIN " + args[4]

	for msg := range con.Read {
		prepared := unparsedMessage{msg, time.Now()}
		fmt.Printf("%s||%s\n", prepared.when, prepared.msg)

		switch {
		case matchGelbooru.MatchString(msg):
			//gelbooruEvent <- matchGelbooru.FindAllStringSubmatch(msg, -1)[0][1]
		case matchYouTube.MatchString(msg):
			youtubeEvent <- prepared
		case matchAmiAmi.MatchString(msg):
			amiAmiEvent <- prepared
		default:
		}
	}
	con.Close()
}

type unparsedMessage struct {
	msg  string
	when time.Time
}

type IRCMessage struct {
	channel string
	msg     string
	user    string
}

func message(con *goty.IRCConn, msg IRCMessage) {
	con.Write <- "PRIVMSG " + msg.channel + " :" + msg.msg + "\r\n"
}

func messageHandler(con *goty.IRCConn, event chan IRCMessage) {
	allBooks := map[string]time.Time{}
	chanBooks := map[string]time.Time{}
	for msg := range event {
		now := time.Now()
		key := msg.channel + ":" + msg.user
		if now.Sub(allBooks[key]) < time.Second*10 || now.Sub(chanBooks[key]) < time.Second*2 {
			continue
		}
		allBooks[key] = now
		chanBooks[key] = now
		message(con, msg)
	}
}

var PRIVMSG = regexp.MustCompile(`:(.+)![^ ]+ PRIVMSG ([^ ]+) :(.*)`)

func getMsgInfo(msg string) (*IRCMessage, error) {
	// :nick!~realname@0.0.0.0 PRIVMSG #chan :msg
	imsg := new(IRCMessage)
	match := PRIVMSG.FindAllStringSubmatch(msg, -1)
	if len(match) < 1 {
		return imsg, errors.New("could not parse message")
	}
	if len(match[0]) < 3 {
		return imsg, errors.New("could not parse message")
	}
	imsg.user = user
	imsg.channel = match[0][2]
	if imsg.channel == user {
		imsg.channel = match[0][1]
	}
	imsg.msg = match[0][3]
	return imsg, nil
}

func bastille(event chan unparsedMessage, writeMessage chan IRCMessage) {
	msgs := []string{
		"Bastille, yo brodudedudebro!!!!1",
		"Bastille, wat up homie",
		"Bastille, word",
		"Bastille, duuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuude",
		"'sup Bastille?",
	}

	for msg := range event {
		parsed, err := getMsgInfo(msg.msg)
		if err != nil {
			continue
		}
		writeMessage <- IRCMessage{parsed.channel, msgs[rand.Intn(len(msgs))-1], parsed.user}
	}
}

type uriFunc func(*string) (*string, error)
type writeFunc func(*IRCMessage, *string) error

func scrapeAndSend(event chan unparsedMessage, findUri uriFunc, write writeFunc) {
	for msg := range event {
		parsed, err := getMsgInfo(msg.msg)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		uri, err := findUri(&parsed.msg)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		resp, err := http.Get(*uri)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		bodyBytes, err := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}
		body := string(bodyBytes)

		if write(parsed, &body) != nil {
			fmt.Printf("%v\n", err)
			continue
		}
	}
}

func getFirstMatch(re *regexp.Regexp, matchee *string) (*string, error) {
	match := re.FindAllStringSubmatch(*matchee, -1)
	if len(match) < 1 {
		return nil, errors.New("Could not match")
	}
	if len(match[0]) < 2 {
		return nil, errors.New("Could not match")
	}
	return &match[0][1], nil
}

func amiami(event chan unparsedMessage, writeMessage chan IRCMessage) {
	matchTitle := regexp.MustCompile(`.*<meta property="og:title" content="(.+)" />.*`)
	matchDiscount := regexp.MustCompile(`[0-9]+\%OFF `)
	scrapeAndSend(event, func(msg *string) (*string, error) { return getFirstMatch(matchAmiAmi, msg) },
		func(msg *IRCMessage, body *string) error {
			title, err := getFirstMatch(matchTitle, body)
			if err != nil {
				return err
			}

			writeMessage <- IRCMessage{msg.channel, "[AmiAmi]: " + matchDiscount.ReplaceAllLiteralString(*title, ""), msg.user}
			return nil
		})
}

func youtube(event chan unparsedMessage, writeMessage chan IRCMessage) {
	matchTitle := regexp.MustCompile(`.*<title>(.+)(?: - YouTube){1}</title>.*`)
	matchUser := regexp.MustCompile(`.*<a[^>]+class="[^"]+yt-user-name[^>]+>([^<]+)</a>.*`)

	scrapeAndSend(event, func(msg *string) (*string, error) { return getFirstMatch(matchYouTube, msg) },
		func(msg *IRCMessage, body *string) error {
			title, err := getFirstMatch(matchTitle, body)
			if err != nil {
				return err
			}
			user, err := getFirstMatch(matchUser, body)
			if err != nil {
				return err
			}
			writeMessage <- IRCMessage{msg.channel, "[YouTube]: " + html.UnescapeString(*title+" uploaded by "+*user), msg.user}
			return nil
		})
}

func gelbooru(event chan unparsedMessage, writeMessage chan IRCMessage) {
	type Post struct {
		post string
		tags string `xml:",attr"`
	}

	for msg := range event {
		parsed, err := getMsgInfo(msg.msg)
		if err != nil {
			continue
		}

		resp, err := http.Get("http://gelbooru.com/index.php?page=dapi&s=post&q=index&tags&id=" + msg.msg)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		body, err := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		fmt.Printf("%s\n", body)

		var result Post

		err = xml.Unmarshal(body, &result)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		fmt.Printf("%s\n", result.tags)
		writeMessage <- IRCMessage{parsed.channel, "tobedone", parsed.user}
	}
}
<commit_msg>Cleanup + add reddit title support<commit_after>package main

import (
	"encoding/xml" // gelbooru parsing
	"errors"
	"fmt"
	"github.com/jcline/goty"
	"html"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"regexp"
	"time"
)

var user = ""

var matchGelbooru = regexp.MustCompile(`\Qhttp://gelbooru.com/index.php?page=post&s=view&id=\E([\d]+)`)
var matchYouTube = regexp.MustCompile(`(https?://(?:www\.|)youtu(?:\.be|be\.com)/[^ ]+)`)
var matchAmiAmi = regexp.MustCompile(`(https?://(?:www\.|)amiami.com/[^/ ]+/detail/[^ ]+)`)
var matchReddit = regexp.MustCompile(`(https?://(?:www\.|)redd(?:\.it|it\.com)/r/[^/ ]+/comments/[^/ ]+/?)(?: .*|\z)`)

func auth(con *goty.IRCConn, writeMessage chan IRCMessage) {
	var pswd string
	_, err := fmt.Scanf("%s", &pswd)
	if err != nil {
		os.Exit(1)
	}

	msg := IRCMessage{channel: "NickServ", msg: "IDENTIFY Laala " + pswd}
	writeMessage <- msg
}

func main() {
	args := os.Args
	if len(args) < 4 {
		os.Exit(1)
	}

	con, err := goty.Dial(args[1], args[2], args[3])
	user = args[2]
	if err != nil {
		fmt.Fprintf(os.Stderr, "err: %s\n", err.Error())
	}

	writeMessage := make(chan IRCMessage, 1000)
	go messageHandler(con, writeMessage)

	amiAmiEvent := make(chan unparsedMessage, 1000)
	bastilleEvent := make(chan unparsedMessage, 1000)
	gelbooruEvent := make(chan unparsedMessage, 1000)
	redditEvent := make(chan unparsedMessage, 1000)
	youtubeEvent := make(chan unparsedMessage, 1000)

	go amiami(amiAmiEvent, writeMessage)
	go bastille(bastilleEvent, writeMessage)
	go gelbooru(gelbooruEvent, writeMessage)
	go reddit(redditEvent, writeMessage)
	go youtube(youtubeEvent, writeMessage)

	//auth(con, writeMessage)
	con.Write <- "JOIN " + args[4]

	for msg := range con.Read {
		prepared := unparsedMessage{msg, time.Now()}
		fmt.Printf("%s||%s\n", prepared.when, prepared.msg)

		switch {
		case matchAmiAmi.MatchString(msg):
			amiAmiEvent <- prepared
		case matchGelbooru.MatchString(msg):
			//gelbooruEvent <- matchGelbooru.FindAllStringSubmatch(msg, -1)[0][1]
		case matchReddit.MatchString(msg):
			redditEvent <- prepared
		case matchYouTube.MatchString(msg):
			youtubeEvent <- prepared
		default:
		}
	}
	con.Close()
}

type unparsedMessage struct {
	msg  string
	when time.Time
}

type IRCMessage struct {
	channel string
	msg     string
	user    string
}

func message(con *goty.IRCConn, msg IRCMessage) {
	con.Write <- "PRIVMSG " + msg.channel + " :" + msg.msg + "\r\n"
}

func messageHandler(con *goty.IRCConn, event chan IRCMessage) {
	allBooks := map[string]time.Time{}
	chanBooks := map[string]time.Time{}
	for msg := range event {
		now := time.Now()
		key := msg.channel + ":" + msg.user
		if now.Sub(allBooks[key]) < time.Second*10 || now.Sub(chanBooks[key]) < time.Second*2 {
			continue
		}
		allBooks[key] = now
		chanBooks[key] = now
		message(con, msg)
	}
}

var PRIVMSG = regexp.MustCompile(`:(.+)![^ ]+ PRIVMSG ([^ ]+) :(.*)`)

func getMsgInfo(msg string) (*IRCMessage, error) {
	// :nick!~realname@0.0.0.0 PRIVMSG #chan :msg
	imsg := new(IRCMessage)
	match := PRIVMSG.FindAllStringSubmatch(msg, -1)
	if len(match) < 1 {
		return imsg, errors.New("could not parse message")
	}
	if len(match[0]) < 3 {
		return imsg, errors.New("could not parse message")
	}
	imsg.user = user
	imsg.channel = match[0][2]
	if imsg.channel == user {
		imsg.channel = match[0][1]
	}
	imsg.msg = match[0][3]
	return imsg, nil
}

func bastille(event chan unparsedMessage, writeMessage chan IRCMessage) {
	msgs := []string{
		"Bastille, yo brodudedudebro!!!!1",
		"Bastille, wat up homie",
		"Bastille, word",
		"Bastille, duuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuude",
		"'sup Bastille?",
	}

	for msg := range event {
		parsed, err := getMsgInfo(msg.msg)
		if err != nil {
			continue
		}
		writeMessage <- IRCMessage{parsed.channel, msgs[rand.Intn(len(msgs))-1], parsed.user}
	}
}

type uriFunc func(*string) (*string, error)
type writeFunc func(*IRCMessage, *string) error

func scrapeAndSend(event chan unparsedMessage, findUri uriFunc, write writeFunc) {
	for msg := range event {
		parsed, err := getMsgInfo(msg.msg)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		uri, err := findUri(&parsed.msg)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		resp, err := http.Get(*uri)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		bodyBytes, err := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}
		body := string(bodyBytes)

		if write(parsed, &body) != nil {
			fmt.Printf("%v\n", err)
			continue
		}
	}
}

func getFirstMatch(re *regexp.Regexp, matchee *string) (*string, error) {
	match := re.FindAllStringSubmatch(*matchee, -1)
	if len(match) < 1 {
		return nil, errors.New("Could not match")
	}
	if len(match[0]) < 2 {
		return nil, errors.New("Could not match")
	}
	return &match[0][1], nil
}

func amiami(event chan unparsedMessage, writeMessage chan IRCMessage) {
	matchTitle := regexp.MustCompile(`.*<meta property="og:title" content="(.+)" />.*`)
	matchDiscount := regexp.MustCompile(`[0-9]+\%OFF `)
	scrapeAndSend(event, func(msg *string) (*string, error) { return getFirstMatch(matchAmiAmi, msg) },
		func(msg *IRCMessage, body *string) error {
			title, err := getFirstMatch(matchTitle, body)
			if err != nil {
				return err
			}

			writeMessage <- IRCMessage{msg.channel, "[AmiAmi]: " + matchDiscount.ReplaceAllLiteralString(*title, ""), msg.user}
			return nil
		})
}

func reddit(event chan unparsedMessage, writeMessage chan IRCMessage) {
	matchTitle := regexp.MustCompile(`.*<title>(.+)</title>.*`)

	scrapeAndSend(event, func(msg *string) (*string, error) { return getFirstMatch(matchReddit, msg) },
		func(msg *IRCMessage, body *string) error {
			title, err := getFirstMatch(matchTitle, body)
			if err != nil {
				return err
			}

			writeMessage <- IRCMessage{msg.channel, "[Reddit]: " + html.UnescapeString(*title), msg.user}
			return nil
		})
}

func youtube(event chan unparsedMessage, writeMessage chan IRCMessage) {
	matchTitle := regexp.MustCompile(`.*<title>(.+)(?: - YouTube){1}</title>.*`)
	matchUser := regexp.MustCompile(`.*<a[^>]+class="[^"]+yt-user-name[^>]+>([^<]+)</a>.*`)

	scrapeAndSend(event, func(msg *string) (*string, error) { return getFirstMatch(matchYouTube, msg) },
		func(msg *IRCMessage, body *string) error {
			title, err := getFirstMatch(matchTitle, body)
			if err != nil {
				return err
			}
			user, err := getFirstMatch(matchUser, body)
			if err != nil {
				return err
			}
			writeMessage <- IRCMessage{msg.channel, "[YouTube]: " + html.UnescapeString(*title+" uploaded by "+*user), msg.user}
			return nil
		})
}

func gelbooru(event chan unparsedMessage, writeMessage chan IRCMessage) {
	type Post struct {
		post string
		tags string `xml:",attr"`
	}

	for msg := range event {
		parsed, err := getMsgInfo(msg.msg)
		if err != nil {
			continue
		}

		resp, err := http.Get("http://gelbooru.com/index.php?page=dapi&s=post&q=index&tags&id=" + msg.msg)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		body, err := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		fmt.Printf("%s\n", body)

		var result Post

		err = xml.Unmarshal(body, &result)
		if err != nil {
			fmt.Printf("%v\n", err)
			continue
		}

		fmt.Printf("%s\n", result.tags)
		writeMessage <- IRCMessage{parsed.channel, "tobedone", parsed.user}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/bcicen/ctop/config"
	"github.com/bcicen/ctop/cwidgets/single"
	ui "github.com/gizak/termui"
)

func RedrawRows(clr bool) {
	// reinit body rows
	cGrid.Clear()

	// build layout
	y := 1
	if config.GetSwitchVal("enableHeader") {
		header.SetCount(cursor.Len())
		header.SetFilter(config.GetVal("filterStr"))
		y += header.Height()
	}

	cGrid.SetY(y)

	for _, c := range cursor.filtered {
		cGrid.AddRows(c.Widgets)
	}

	if clr {
		ui.Clear()
		log.Debugf("screen cleared")
	}
	if config.GetSwitchVal("enableHeader") {
		ui.Render(header)
	}
	cGrid.Align()
	ui.Render(cGrid)

}

func SingleView() MenuFn {
	c := cursor.Selected()
	if c == nil {
		return nil
	}

	ui.Clear()
	ui.DefaultEvtStream.ResetHandlers()
	defer ui.DefaultEvtStream.ResetHandlers()

	ex := single.NewSingle(c.Id)
	c.SetUpdater(ex)

	ex.Align()
	ui.Render(ex)

	HandleKeys("up", ex.Up)
	HandleKeys("down", ex.Down)
	ui.Handle("/sys/kbd/", func(ui.Event) { ui.StopLoop() })

	ui.Handle("/timer/1s", func(ui.Event) { ui.Render(ex) })
	ui.Handle("/sys/wnd/resize", func(e ui.Event) {
		ex.SetWidth(ui.TermWidth())
		ex.Align()
		log.Infof("resize: width=%v max-rows=%v", ex.Width, cGrid.MaxRows())
	})

	ui.Loop()
	c.SetUpdater(c.Widgets)
	return nil
}

func RefreshDisplay() {
	// skip display refresh during scroll
	if !cursor.isScrolling {
		needsClear := cursor.RefreshContainers()
		RedrawRows(needsClear)
	}
}

func Display() bool {
	var menu MenuFn

	cGrid.SetWidth(ui.TermWidth())
	ui.DefaultEvtStream.Hook(logEvent)

	// initial draw
	header.Align()
	status.Align()
	cursor.RefreshContainers()
	RedrawRows(true)

	HandleKeys("up", cursor.Up)
	HandleKeys("down", cursor.Down)

	HandleKeys("pgup", cursor.PgUp)
	HandleKeys("pgdown", cursor.PgDown)

	HandleKeys("exit", ui.StopLoop)
	HandleKeys("help", func() {
		menu = HelpMenu
		ui.StopLoop()
	})

	ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
		menu = ContainerMenu
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/l", func(ui.Event) {
		menu = LogMenu
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/o", func(ui.Event) {
		menu = SingleView
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/a", func(ui.Event) {
		config.Toggle("allContainers")
		RefreshDisplay()
	})
	ui.Handle("/sys/kbd/D", func(ui.Event) {
		dumpContainer(cursor.Selected())
	})
	ui.Handle("/sys/kbd/f", func(ui.Event) {
		menu = FilterMenu
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/H", func(ui.Event) {
		config.Toggle("enableHeader")
		RedrawRows(true)
	})
	ui.Handle("/sys/kbd/r", func(e ui.Event) {
		config.Toggle("sortReversed")
	})
	ui.Handle("/sys/kbd/s", func(ui.Event) {
		menu = SortMenu
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/S", func(ui.Event) {
		path, err := config.Write()
		if err == nil {
			log.Statusf("wrote config to %s", path)
		} else {
			log.StatusErr(err)
		}
		ui.StopLoop()
	})

	ui.Handle("/timer/1s", func(e ui.Event) {
		if log.StatusQueued() {
			ui.StopLoop()
		}
		RefreshDisplay()
	})

	ui.Handle("/sys/wnd/resize", func(e ui.Event) {
		header.Align()
		status.Align()
		cursor.ScrollPage()
		cGrid.SetWidth(ui.TermWidth())
		log.Infof("resize: width=%v max-rows=%v", cGrid.Width, cGrid.MaxRows())
		RedrawRows(true)
	})

	ui.Loop()

	if log.StatusQueued() {
		for sm := range log.FlushStatus() {
			if sm.IsError {
				status.ShowErr(sm.Text)
			} else {
				status.Show(sm.Text)
			}
		}
		return false
	}

	if menu != nil {
		for menu != nil {
			menu = menu()
		}
		return false
	}

	return true
}
<commit_msg>add support for alternative navigation<commit_after>package main

import (
	"github.com/bcicen/ctop/config"
	"github.com/bcicen/ctop/cwidgets/single"
	ui "github.com/gizak/termui"
)

func RedrawRows(clr bool) {
	// reinit body rows
	cGrid.Clear()

	// build layout
	y := 1
	if config.GetSwitchVal("enableHeader") {
		header.SetCount(cursor.Len())
		header.SetFilter(config.GetVal("filterStr"))
		y += header.Height()
	}

	cGrid.SetY(y)

	for _, c := range cursor.filtered {
		cGrid.AddRows(c.Widgets)
	}

	if clr {
		ui.Clear()
		log.Debugf("screen cleared")
	}
	if config.GetSwitchVal("enableHeader") {
		ui.Render(header)
	}
	cGrid.Align()
	ui.Render(cGrid)

}

func SingleView() MenuFn {
	c := cursor.Selected()
	if c == nil {
		return nil
	}

	ui.Clear()
	ui.DefaultEvtStream.ResetHandlers()
	defer ui.DefaultEvtStream.ResetHandlers()

	ex := single.NewSingle(c.Id)
	c.SetUpdater(ex)

	ex.Align()
	ui.Render(ex)

	HandleKeys("up", ex.Up)
	HandleKeys("down", ex.Down)
	ui.Handle("/sys/kbd/", func(ui.Event) { ui.StopLoop() })

	ui.Handle("/timer/1s", func(ui.Event) { ui.Render(ex) })
	ui.Handle("/sys/wnd/resize", func(e ui.Event) {
		ex.SetWidth(ui.TermWidth())
		ex.Align()
		log.Infof("resize: width=%v max-rows=%v", ex.Width, cGrid.MaxRows())
	})

	ui.Loop()
	c.SetUpdater(c.Widgets)
	return nil
}

func RefreshDisplay() {
	// skip display refresh during scroll
	if !cursor.isScrolling {
		needsClear := cursor.RefreshContainers()
		RedrawRows(needsClear)
	}
}

func Display() bool {
	var menu MenuFn

	cGrid.SetWidth(ui.TermWidth())
	ui.DefaultEvtStream.Hook(logEvent)

	// initial draw
	header.Align()
	status.Align()
	cursor.RefreshContainers()
	RedrawRows(true)

	HandleKeys("up", cursor.Up)
	HandleKeys("down", cursor.Down)

	HandleKeys("pgup", cursor.PgUp)
	HandleKeys("pgdown", cursor.PgDown)

	HandleKeys("exit", ui.StopLoop)
	HandleKeys("help", func() {
		menu = HelpMenu
		ui.StopLoop()
	})

	ui.Handle("/sys/kbd/<enter>", func(ui.Event) {
		menu = ContainerMenu
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/<left>", func(ui.Event) {
		menu = LogMenu
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/<right>", func(ui.Event) {
		menu = SingleView
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/l", func(ui.Event) {
		menu = LogMenu
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/o", func(ui.Event) {
		menu = SingleView
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/a", func(ui.Event) {
		config.Toggle("allContainers")
		RefreshDisplay()
	})
	ui.Handle("/sys/kbd/D", func(ui.Event) {
		dumpContainer(cursor.Selected())
	})
	ui.Handle("/sys/kbd/f", func(ui.Event) {
		menu = FilterMenu
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/H", func(ui.Event) {
		config.Toggle("enableHeader")
		RedrawRows(true)
	})
	ui.Handle("/sys/kbd/r", func(e ui.Event) {
		config.Toggle("sortReversed")
	})
	ui.Handle("/sys/kbd/s", func(ui.Event) {
		menu = SortMenu
		ui.StopLoop()
	})
	ui.Handle("/sys/kbd/S", func(ui.Event) {
		path, err := config.Write()
		if err == nil {
			log.Statusf("wrote config to %s", path)
		} else {
			log.StatusErr(err)
		}
		ui.StopLoop()
	})

	ui.Handle("/timer/1s", func(e ui.Event) {
		if log.StatusQueued() {
			ui.StopLoop()
		}
		RefreshDisplay()
	})

	ui.Handle("/sys/wnd/resize", func(e ui.Event) {
		header.Align()
		status.Align()
		cursor.ScrollPage()
		cGrid.SetWidth(ui.TermWidth())
		log.Infof("resize: width=%v max-rows=%v", cGrid.Width, cGrid.MaxRows())
		RedrawRows(true)
	})

	ui.Loop()

	if log.StatusQueued() {
		for sm := range log.FlushStatus() {
			if sm.IsError {
				status.ShowErr(sm.Text)
			} else {
				status.Show(sm.Text)
			}
		}
		return false
	}

	if menu != nil {
		for menu != nil {
			menu = menu()
		}
		return false
	}

	return true
}
<|endoftext|>


<commit_before>// Copyright 2016 Google Inc. All rights reserved.
//
// 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 gitiles is a client library for the Gitiles source viewer.
package gitiles

// The gitiles command set is defined here:
//
// https://gerrit.googlesource.com/gitiles/+/7c07a4a68ece6009909206482e0728dbbf0be77d/java/com/google/gitiles/ViewFilter.java#47

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"path"
	"strings"

	"github.com/google/slothfs/cookie"
	"golang.org/x/net/context"
	"golang.org/x/time/rate"
)

// Service is a client for the Gitiles JSON interface.
type Service struct {
	limiter *rate.Limiter
	addr    url.URL
	client  http.Client
	agent   string
	jar     http.CookieJar
	debug   bool
}

// Addr returns the address of the gitiles service.
func (s *Service) Addr() string {
	return s.addr.String()
}

// Options configures the the Gitiles service.
type Options struct {
	// A URL for the Gitiles service.
	Address string

	BurstQPS     int
	SustainedQPS float64

	// Path to a Netscape/Mozilla style cookie file.
	CookieJar string

	// UserAgent defines how we present ourself to the server.
	UserAgent string

	// HTTPClient allows callers to present their own http.Client instead of the default.
	HTTPClient http.Client

	Debug bool
}

var defaultOptions Options

// DefineFlags sets up standard command line flags, and returns the
// options struct in which the values are put.
func DefineFlags() *Options {
	flag.StringVar(&defaultOptions.Address, "gitiles_url", "https://android.googlesource.com", "Set the URL of the Gitiles service.")
	flag.StringVar(&defaultOptions.CookieJar, "gitiles_cookies", "", "Set path to cURL-style cookie jar file.")
	flag.StringVar(&defaultOptions.UserAgent, "gitiles_agent", "slothfs", "Set the User-Agent string to report to Gitiles.")
	flag.Float64Var(&defaultOptions.SustainedQPS, "gitiles_qps", 4, "Set the maximum QPS to send to Gitiles.")
	flag.BoolVar(&defaultOptions.Debug, "gitiles_debug", false, "Print URLs as they are fetched.")
	return &defaultOptions
}

// NewService returns a new Gitiles JSON client.
func NewService(opts Options) (*Service, error) {
	var jar http.CookieJar
	if nm := opts.CookieJar; nm != "" {
		var err error
		jar, err = cookie.NewJar(nm)
		if err != nil {
			return nil, err
		}
		if err := cookie.WatchJar(jar, nm); err != nil {
			return nil, err
		}
	}

	if opts.SustainedQPS == 0.0 {
		opts.SustainedQPS = 4
	}
	if opts.BurstQPS == 0 {
		opts.BurstQPS = int(10.0 * opts.SustainedQPS)
	} else if float64(opts.BurstQPS) < opts.SustainedQPS {
		opts.BurstQPS = int(opts.SustainedQPS) + 1
	}

	url, err := url.Parse(opts.Address)
	if err != nil {
		return nil, err
	}
	s := &Service{
		limiter: rate.NewLimiter(rate.Limit(opts.SustainedQPS), opts.BurstQPS),
		addr:    *url,
		agent:   opts.UserAgent,
		client:  opts.HTTPClient,
	}

	s.client.Jar = jar
	s.client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
		req.Header.Set("User-Agent", s.agent)
		return nil
	}
	s.debug = opts.Debug
	return s, nil
}

func (s *Service) stream(u *url.URL) (*http.Response, error) {
	ctx := context.Background()

	if err := s.limiter.Wait(ctx); err != nil {
		return nil, err
	}
	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("User-Agent", s.agent)
	resp, err := s.client.Do(req)

	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		resp.Body.Close()
		return nil, fmt.Errorf("%s: %s", u.String(), resp.Status)
	}

	if s.debug {
		log.Printf("%s %s: %d", req.Method, req.URL, resp.StatusCode)
	}
	if got := resp.Request.URL.String(); got != u.String() {
		resp.Body.Close()
		// We accept redirects, but only for authentication.
		// If we get a 200 from a different page than we
		// requested, it's probably some sort of login page.
		return nil, fmt.Errorf("got URL %s, want %s", got, u.String())
	}

	return resp, nil
}

func (s *Service) get(u *url.URL) ([]byte, error) {
	resp, err := s.stream(u)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	c, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.Header.Get("Content-Type") == "text/plain; charset=UTF-8" {
		out := make([]byte, base64.StdEncoding.DecodedLen(len(c)))
		n, err := base64.StdEncoding.Decode(out, c)
		return out[:n], err
	}
	return c, nil
}

var xssTag = []byte(")]}'\n")

func (s *Service) getJSON(u *url.URL, dest interface{}) error {
	c, err := s.get(u)
	if err != nil {
		return err
	}

	if !bytes.HasPrefix(c, xssTag) {
		return fmt.Errorf("Gitiles JSON %s missing XSS tag: %q", u, c)
	}
	c = c[len(xssTag):]

	err = json.Unmarshal(c, dest)
	if err != nil {
		err = fmt.Errorf("Unmarshal(%s): %v", u, err)
	}
	return err
}

// List retrieves the list of projects.
func (s *Service) List(branches []string) (map[string]*Project, error) {
	listURL := s.addr
	listURL.RawQuery = "format=JSON"
	for _, b := range branches {
		listURL.RawQuery += "&b=" + b
	}

	projects := map[string]*Project{}
	err := s.getJSON(&listURL, &projects)
	for k, v := range projects {
		if k != v.Name {
			return nil, fmt.Errorf("gitiles: key %q had project name %q", k, v.Name)
		}
	}

	return projects, err
}

// NewRepoService creates a service for a specific repository on a Gitiles server.
func (s *Service) NewRepoService(name string) *RepoService {
	return &RepoService{
		Name:    name,
		service: s,
	}
}

// RepoService is a JSON client for the functionality of a specific
// respository.
type RepoService struct {
	Name    string
	service *Service
}

// Get retrieves a single project.
func (s *RepoService) Get() (*Project, error) {
	jsonURL := s.service.addr
	jsonURL.Path = path.Join(jsonURL.Path, s.Name)
	jsonURL.RawQuery = "format=JSON"

	var p Project
	err := s.service.getJSON(&jsonURL, &p)
	return &p, err
}

// GetBlob fetches a blob.
func (s *RepoService) GetBlob(branch, filename string) ([]byte, error) {
	blobURL := s.service.addr

	blobURL.Path = path.Join(blobURL.Path, s.Name, "+show", branch, filename)
	blobURL.RawQuery = "format=TEXT"

	// TODO(hanwen): invent a more structured mechanism for logging.
	log.Println(blobURL.String())
	return s.service.get(&blobURL)
}

// Archive formats for +archive. JGit also supports some shorthands.
const (
	ArchiveTbz = "tar.bz2"
	ArchiveTgz = "tar.gz"
	ArchiveTar = "tar"
	ArchiveTxz = "tar.xz"

	// the Gitiles source code claims .tar.xz and .tar are
	// supported, but googlesource.com doesn't support it,
	// apparently. In addition, JGit provides ZipFormat, but
	// gitiles doesn't support it.
)

// GetArchive downloads an archive of the project. Format is one
// ArchivXxx formats. dirPrefix, if given, restricts to the given
// subpath, and strips the path prefix from the files in the resulting
// tar archive. revision is a git revision, either a branch/tag name
// ("master") or a hex commit SHA1.
func (s *RepoService) GetArchive(revision, dirPrefix, format string) (io.ReadCloser, error) {
	u := s.service.addr
	u.Path = path.Join(u.Path, s.Name, "+archive", revision)
	if dirPrefix != "" {
		u.Path = path.Join(u.Path, dirPrefix)
	}
	u.Path += "." + format
	resp, err := s.service.stream(&u)
	if err != nil {
		return nil, err
	}
	return resp.Body, err
}

// GetTree fetches a tree. The dir argument may not point to a
// blob. If recursive is given, the server recursively expands the
// tree.
func (s *RepoService) GetTree(branch, dir string, recursive bool) (*Tree, error) {
	jsonURL := s.service.addr
	jsonURL.Path = path.Join(jsonURL.Path, s.Name, "+", branch, dir)
	if !strings.HasSuffix(jsonURL.Path, "/") {
		jsonURL.Path += "/"
	}
	jsonURL.RawQuery = "format=JSON&long=1"

	if recursive {
		jsonURL.RawQuery += "&recursive=1"
	}

	var tree Tree
	err := s.service.getJSON(&jsonURL, &tree)
	return &tree, err
}

// GetCommit gets the data of a commit in a branch.
func (s *RepoService) GetCommit(branch string) (*Commit, error) {
	jsonURL := s.service.addr
	jsonURL.Path = path.Join(jsonURL.Path, s.Name, "+", branch)
	jsonURL.RawQuery = "format=JSON"

	var c Commit
	err := s.service.getJSON(&jsonURL, &c)
	return &c, err
}

// Options for Describe.
const (
	// Return a ref that contains said commmit
	DescribeContains = "contains"

	// Return any type of ref
	DescribeAll = "all"

	// Only return a tag ref
	DescribeTags = "tags"

	// The default for 'contains': return annotated tags
	DescribeAnnotatedTags = ""
)

// Describe describes a possibly shortened commit hash as a ref that
// is visible to the caller. Currently, only the 'contains' flavor is
// implemented, so options must always include 'contains'.
func (s *RepoService) Describe(revision string, options ...string) (string, error) {
	jsonURL := s.service.addr
	jsonURL.Path = path.Join(jsonURL.Path, s.Name, "+describe", revision)
	jsonURL.RawQuery = "format=JSON&" + strings.Join(options, "&")

	result := map[string]string{}
	err := s.service.getJSON(&jsonURL, &result)
	if err != nil {
		return "", err
	}

	if len(result) != 1 {
		return "", fmt.Errorf("gitiles: got map %v, want just one entry", result)
	}

	for _, v := range result {
		return v, nil
	}

	panic("unreachable.")
}

// Refs returns the refs of a repository, optionally filtered by prefix.
func (s *RepoService) Refs(prefix string) (map[string]*RefData, error) {

	jsonURL := s.service.addr
	jsonURL.Path = path.Join(jsonURL.Path, s.Name, "+refs")
	if prefix != "" {
		jsonURL.Path = path.Join(jsonURL.Path, prefix)
	}
	jsonURL.RawQuery = "format=JSON"

	result := map[string]*RefData{}
	err := s.service.getJSON(&jsonURL, &result)
	if err != nil {
		return nil, err
	}

	return result, err
}
<commit_msg>gitiles: remove unused var<commit_after>// Copyright 2016 Google Inc. All rights reserved.
//
// 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 gitiles is a client library for the Gitiles source viewer.
package gitiles

// The gitiles command set is defined here:
//
// https://gerrit.googlesource.com/gitiles/+/7c07a4a68ece6009909206482e0728dbbf0be77d/java/com/google/gitiles/ViewFilter.java#47

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"path"
	"strings"

	"github.com/google/slothfs/cookie"
	"golang.org/x/net/context"
	"golang.org/x/time/rate"
)

// Service is a client for the Gitiles JSON interface.
type Service struct {
	limiter *rate.Limiter
	addr    url.URL
	client  http.Client
	agent   string
	debug   bool
}

// Addr returns the address of the gitiles service.
func (s *Service) Addr() string {
	return s.addr.String()
}

// Options configures the the Gitiles service.
type Options struct {
	// A URL for the Gitiles service.
	Address string

	BurstQPS     int
	SustainedQPS float64

	// Path to a Netscape/Mozilla style cookie file.
	CookieJar string

	// UserAgent defines how we present ourself to the server.
	UserAgent string

	// HTTPClient allows callers to present their own http.Client instead of the default.
	HTTPClient http.Client

	Debug bool
}

var defaultOptions Options

// DefineFlags sets up standard command line flags, and returns the
// options struct in which the values are put.
func DefineFlags() *Options {
	flag.StringVar(&defaultOptions.Address, "gitiles_url", "https://android.googlesource.com", "Set the URL of the Gitiles service.")
	flag.StringVar(&defaultOptions.CookieJar, "gitiles_cookies", "", "Set path to cURL-style cookie jar file.")
	flag.StringVar(&defaultOptions.UserAgent, "gitiles_agent", "slothfs", "Set the User-Agent string to report to Gitiles.")
	flag.Float64Var(&defaultOptions.SustainedQPS, "gitiles_qps", 4, "Set the maximum QPS to send to Gitiles.")
	flag.BoolVar(&defaultOptions.Debug, "gitiles_debug", false, "Print URLs as they are fetched.")
	return &defaultOptions
}

// NewService returns a new Gitiles JSON client.
func NewService(opts Options) (*Service, error) {
	var jar http.CookieJar
	if nm := opts.CookieJar; nm != "" {
		var err error
		jar, err = cookie.NewJar(nm)
		if err != nil {
			return nil, err
		}
		if err := cookie.WatchJar(jar, nm); err != nil {
			return nil, err
		}
	}

	if opts.SustainedQPS == 0.0 {
		opts.SustainedQPS = 4
	}
	if opts.BurstQPS == 0 {
		opts.BurstQPS = int(10.0 * opts.SustainedQPS)
	} else if float64(opts.BurstQPS) < opts.SustainedQPS {
		opts.BurstQPS = int(opts.SustainedQPS) + 1
	}

	url, err := url.Parse(opts.Address)
	if err != nil {
		return nil, err
	}
	s := &Service{
		limiter: rate.NewLimiter(rate.Limit(opts.SustainedQPS), opts.BurstQPS),
		addr:    *url,
		agent:   opts.UserAgent,
		client:  opts.HTTPClient,
	}

	s.client.Jar = jar
	s.client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
		req.Header.Set("User-Agent", s.agent)
		return nil
	}
	s.debug = opts.Debug
	return s, nil
}

func (s *Service) stream(u *url.URL) (*http.Response, error) {
	ctx := context.Background()

	if err := s.limiter.Wait(ctx); err != nil {
		return nil, err
	}
	req, err := http.NewRequest("GET", u.String(), nil)
	if err != nil {
		return nil, err
	}
	req.Header.Add("User-Agent", s.agent)
	resp, err := s.client.Do(req)

	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		resp.Body.Close()
		return nil, fmt.Errorf("%s: %s", u.String(), resp.Status)
	}

	if s.debug {
		log.Printf("%s %s: %d", req.Method, req.URL, resp.StatusCode)
	}
	if got := resp.Request.URL.String(); got != u.String() {
		resp.Body.Close()
		// We accept redirects, but only for authentication.
		// If we get a 200 from a different page than we
		// requested, it's probably some sort of login page.
		return nil, fmt.Errorf("got URL %s, want %s", got, u.String())
	}

	return resp, nil
}

func (s *Service) get(u *url.URL) ([]byte, error) {
	resp, err := s.stream(u)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	c, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.Header.Get("Content-Type") == "text/plain; charset=UTF-8" {
		out := make([]byte, base64.StdEncoding.DecodedLen(len(c)))
		n, err := base64.StdEncoding.Decode(out, c)
		return out[:n], err
	}
	return c, nil
}

var xssTag = []byte(")]}'\n")

func (s *Service) getJSON(u *url.URL, dest interface{}) error {
	c, err := s.get(u)
	if err != nil {
		return err
	}

	if !bytes.HasPrefix(c, xssTag) {
		return fmt.Errorf("Gitiles JSON %s missing XSS tag: %q", u, c)
	}
	c = c[len(xssTag):]

	err = json.Unmarshal(c, dest)
	if err != nil {
		err = fmt.Errorf("Unmarshal(%s): %v", u, err)
	}
	return err
}

// List retrieves the list of projects.
func (s *Service) List(branches []string) (map[string]*Project, error) {
	listURL := s.addr
	listURL.RawQuery = "format=JSON"
	for _, b := range branches {
		listURL.RawQuery += "&b=" + b
	}

	projects := map[string]*Project{}
	err := s.getJSON(&listURL, &projects)
	for k, v := range projects {
		if k != v.Name {
			return nil, fmt.Errorf("gitiles: key %q had project name %q", k, v.Name)
		}
	}

	return projects, err
}

// NewRepoService creates a service for a specific repository on a Gitiles server.
func (s *Service) NewRepoService(name string) *RepoService {
	return &RepoService{
		Name:    name,
		service: s,
	}
}

// RepoService is a JSON client for the functionality of a specific
// respository.
type RepoService struct {
	Name    string
	service *Service
}

// Get retrieves a single project.
func (s *RepoService) Get() (*Project, error) {
	jsonURL := s.service.addr
	jsonURL.Path = path.Join(jsonURL.Path, s.Name)
	jsonURL.RawQuery = "format=JSON"

	var p Project
	err := s.service.getJSON(&jsonURL, &p)
	return &p, err
}

// GetBlob fetches a blob.
func (s *RepoService) GetBlob(branch, filename string) ([]byte, error) {
	blobURL := s.service.addr

	blobURL.Path = path.Join(blobURL.Path, s.Name, "+show", branch, filename)
	blobURL.RawQuery = "format=TEXT"

	// TODO(hanwen): invent a more structured mechanism for logging.
	log.Println(blobURL.String())
	return s.service.get(&blobURL)
}

// Archive formats for +archive. JGit also supports some shorthands.
const (
	ArchiveTbz = "tar.bz2"
	ArchiveTgz = "tar.gz"
	ArchiveTar = "tar"
	ArchiveTxz = "tar.xz"

	// the Gitiles source code claims .tar.xz and .tar are
	// supported, but googlesource.com doesn't support it,
	// apparently. In addition, JGit provides ZipFormat, but
	// gitiles doesn't support it.
)

// GetArchive downloads an archive of the project. Format is one
// ArchivXxx formats. dirPrefix, if given, restricts to the given
// subpath, and strips the path prefix from the files in the resulting
// tar archive. revision is a git revision, either a branch/tag name
// ("master") or a hex commit SHA1.
func (s *RepoService) GetArchive(revision, dirPrefix, format string) (io.ReadCloser, error) {
	u := s.service.addr
	u.Path = path.Join(u.Path, s.Name, "+archive", revision)
	if dirPrefix != "" {
		u.Path = path.Join(u.Path, dirPrefix)
	}
	u.Path += "." + format
	resp, err := s.service.stream(&u)
	if err != nil {
		return nil, err
	}
	return resp.Body, err
}

// GetTree fetches a tree. The dir argument may not point to a
// blob. If recursive is given, the server recursively expands the
// tree.
func (s *RepoService) GetTree(branch, dir string, recursive bool) (*Tree, error) {
	jsonURL := s.service.addr
	jsonURL.Path = path.Join(jsonURL.Path, s.Name, "+", branch, dir)
	if !strings.HasSuffix(jsonURL.Path, "/") {
		jsonURL.Path += "/"
	}
	jsonURL.RawQuery = "format=JSON&long=1"

	if recursive {
		jsonURL.RawQuery += "&recursive=1"
	}

	var tree Tree
	err := s.service.getJSON(&jsonURL, &tree)
	return &tree, err
}

// GetCommit gets the data of a commit in a branch.
func (s *RepoService) GetCommit(branch string) (*Commit, error) {
	jsonURL := s.service.addr
	jsonURL.Path = path.Join(jsonURL.Path, s.Name, "+", branch)
	jsonURL.RawQuery = "format=JSON"

	var c Commit
	err := s.service.getJSON(&jsonURL, &c)
	return &c, err
}

// Options for Describe.
const (
	// Return a ref that contains said commmit
	DescribeContains = "contains"

	// Return any type of ref
	DescribeAll = "all"

	// Only return a tag ref
	DescribeTags = "tags"

	// The default for 'contains': return annotated tags
	DescribeAnnotatedTags = ""
)

// Describe describes a possibly shortened commit hash as a ref that
// is visible to the caller. Currently, only the 'contains' flavor is
// implemented, so options must always include 'contains'.
func (s *RepoService) Describe(revision string, options ...string) (string, error) {
	jsonURL := s.service.addr
	jsonURL.Path = path.Join(jsonURL.Path, s.Name, "+describe", revision)
	jsonURL.RawQuery = "format=JSON&" + strings.Join(options, "&")

	result := map[string]string{}
	err := s.service.getJSON(&jsonURL, &result)
	if err != nil {
		return "", err
	}

	if len(result) != 1 {
		return "", fmt.Errorf("gitiles: got map %v, want just one entry", result)
	}

	for _, v := range result {
		return v, nil
	}

	panic("unreachable.")
}

// Refs returns the refs of a repository, optionally filtered by prefix.
func (s *RepoService) Refs(prefix string) (map[string]*RefData, error) {

	jsonURL := s.service.addr
	jsonURL.Path = path.Join(jsonURL.Path, s.Name, "+refs")
	if prefix != "" {
		jsonURL.Path = path.Join(jsonURL.Path, prefix)
	}
	jsonURL.RawQuery = "format=JSON"

	result := map[string]*RefData{}
	err := s.service.getJSON(&jsonURL, &result)
	if err != nil {
		return nil, err
	}

	return result, err
}
<|endoftext|>


<commit_before>package logparser

import (
	"testing"
	"time"

	assert "github.com/stretchr/testify/require"
	"go.skia.org/infra/go/sklog"
	"go.skia.org/infra/go/testutils"
)

func TestSyslogParsing(t *testing.T) {
	testutils.SmallTest(t)
	contents := testutils.MustReadFile("basicsyslog")
	lp := ParseSyslog(contents)
	assert.Equal(t, 2, lp.Len(), "Wrong number of log lines")

	lp.Start(0)
	if line := lp.CurrLine(); line != 0 {
		t.Errorf("Line counter should start at 0: Was %d", line)
	}
	payload := lp.ReadAndNext()
	expected := sklog.LogPayload{
		Payload:  "kernel: [ 5932.706546] usb 1-1.5.2: SerialNumber: 015d210a13480604",
		Time:     time.Date(time.Now().Year(), 5, 27, 15, 20, 15, 0, time.Local),
		Severity: sklog.INFO,
	}
	assert.NotNil(t, payload)
	assert.Equal(t, expected, *payload)

	if line := lp.CurrLine(); line != 1 {
		t.Errorf("Line counter should advance: Was %d", line)
	}

	payload = lp.ReadAndNext()
	expected = sklog.LogPayload{
		Payload:  "rsyslogd-2007: action 'action 17' suspended, next retry is Fri May 27 15:22:59 2016 [try http://www.rsyslog.com/e/2007 ]",
		Time:     time.Date(time.Now().Year(), 5, 27, 15, 21, 59, 0, time.Local),
		Severity: sklog.INFO,
	}
	assert.NotNil(t, payload)
	assert.Equal(t, expected, *payload)

	if line := lp.CurrLine(); line != 2 {
		t.Errorf("Line counter should advance: Was %d", line)
	}

	payload = lp.ReadAndNext()
	assert.Nil(t, payload, "Should have reached end of input")

	if line := lp.CurrLine(); line != 2 {
		t.Errorf("Line counter should not advance: Was %d", line)
	}

	// Test ReadLine
	payload = lp.ReadLine(1)
	assert.NotNil(t, payload)
	assert.Equal(t, expected, *payload)
}

func TestPythonLogParsing(t *testing.T) {
	testutils.SmallTest(t)
	contents := testutils.MustReadFile("pythonlog1")
	lp := ParsePythonLog(contents)
	assert.Equal(t, 5, lp.Len(), "Wrong number of log lines")

	// Spot check a few lines

	payload := lp.ReadLine(0)
	expected := sklog.LogPayload{
		Payload:  "GCE metadata not available: <urlopen error [Errno -2] Name or service not known>",
		Time:     time.Date(time.Now().Year(), 5, 10, 20, 01, 12, 305000000, time.UTC),
		Severity: sklog.ERROR,
	}
	assert.NotNil(t, payload)
	assert.Equal(t, expected, *payload)
	assert.Equal(t, 0, lp.CurrLine())

	payload = lp.ReadLine(2)
	expected = sklog.LogPayload{
		Payload:  "Writing in /home/chrome-bot/.config/autostart/swarming.desktop:\n[Desktop Entry]\nType=Application\nName=swarming\nExec=/usr/bin/python /b/s/swarming_bot.zip start_bot\nHidden=false\nNoDisplay=false\nComment=Created by os_utilities.py in swarming_bot.zip\nX-GNOME-Autostart-enabled=true",
		Time:     time.Date(time.Now().Year(), 5, 10, 20, 01, 12, 573000000, time.UTC),
		Severity: sklog.INFO,
	}
	assert.NotNil(t, payload)
	assert.Equal(t, expected, *payload)
	assert.Equal(t, 2, lp.CurrLine())

	payload = lp.ReadLine(3)
	expected = sklog.LogPayload{
		Payload:  "Starting new HTTPS connection (1): chromium-swarm.appspot.com",
		Time:     time.Date(time.Now().Year(), 5, 10, 20, 01, 12, 617000000, time.UTC),
		Severity: sklog.INFO,
	}
	assert.NotNil(t, payload)
	assert.Equal(t, expected, *payload)
	assert.Equal(t, 3, lp.CurrLine())
}
<commit_msg>Python log file contains year.<commit_after>package logparser

import (
	"testing"
	"time"

	assert "github.com/stretchr/testify/require"
	"go.skia.org/infra/go/sklog"
	"go.skia.org/infra/go/testutils"
)

func TestSyslogParsing(t *testing.T) {
	testutils.SmallTest(t)
	contents := testutils.MustReadFile("basicsyslog")
	lp := ParseSyslog(contents)
	assert.Equal(t, 2, lp.Len(), "Wrong number of log lines")

	lp.Start(0)
	if line := lp.CurrLine(); line != 0 {
		t.Errorf("Line counter should start at 0: Was %d", line)
	}
	payload := lp.ReadAndNext()
	expected := sklog.LogPayload{
		Payload:  "kernel: [ 5932.706546] usb 1-1.5.2: SerialNumber: 015d210a13480604",
		Time:     time.Date(time.Now().Year(), 5, 27, 15, 20, 15, 0, time.Local),
		Severity: sklog.INFO,
	}
	assert.NotNil(t, payload)
	assert.Equal(t, expected, *payload)

	if line := lp.CurrLine(); line != 1 {
		t.Errorf("Line counter should advance: Was %d", line)
	}

	payload = lp.ReadAndNext()
	expected = sklog.LogPayload{
		Payload:  "rsyslogd-2007: action 'action 17' suspended, next retry is Fri May 27 15:22:59 2016 [try http://www.rsyslog.com/e/2007 ]",
		Time:     time.Date(time.Now().Year(), 5, 27, 15, 21, 59, 0, time.Local),
		Severity: sklog.INFO,
	}
	assert.NotNil(t, payload)
	assert.Equal(t, expected, *payload)

	if line := lp.CurrLine(); line != 2 {
		t.Errorf("Line counter should advance: Was %d", line)
	}

	payload = lp.ReadAndNext()
	assert.Nil(t, payload, "Should have reached end of input")

	if line := lp.CurrLine(); line != 2 {
		t.Errorf("Line counter should not advance: Was %d", line)
	}

	// Test ReadLine
	payload = lp.ReadLine(1)
	assert.NotNil(t, payload)
	assert.Equal(t, expected, *payload)
}

func TestPythonLogParsing(t *testing.T) {
	testutils.SmallTest(t)
	contents := testutils.MustReadFile("pythonlog1")
	lp := ParsePythonLog(contents)
	assert.Equal(t, 5, lp.Len(), "Wrong number of log lines")

	// Spot check a few lines

	payload := lp.ReadLine(0)
	expected := sklog.LogPayload{
		Payload:  "GCE metadata not available: <urlopen error [Errno -2] Name or service not known>",
		Time:     time.Date(2016, 5, 10, 20, 01, 12, 305000000, time.UTC),
		Severity: sklog.ERROR,
	}
	assert.NotNil(t, payload)
	assert.Equal(t, expected, *payload)
	assert.Equal(t, 0, lp.CurrLine())

	payload = lp.ReadLine(2)
	expected = sklog.LogPayload{
		Payload:  "Writing in /home/chrome-bot/.config/autostart/swarming.desktop:\n[Desktop Entry]\nType=Application\nName=swarming\nExec=/usr/bin/python /b/s/swarming_bot.zip start_bot\nHidden=false\nNoDisplay=false\nComment=Created by os_utilities.py in swarming_bot.zip\nX-GNOME-Autostart-enabled=true",
		Time:     time.Date(2016, 5, 10, 20, 01, 12, 573000000, time.UTC),
		Severity: sklog.INFO,
	}
	assert.NotNil(t, payload)
	assert.Equal(t, expected, *payload)
	assert.Equal(t, 2, lp.CurrLine())

	payload = lp.ReadLine(3)
	expected = sklog.LogPayload{
		Payload:  "Starting new HTTPS connection (1): chromium-swarm.appspot.com",
		Time:     time.Date(2016, 5, 10, 20, 01, 12, 617000000, time.UTC),
		Severity: sklog.INFO,
	}
	assert.NotNil(t, payload)
	assert.Equal(t, expected, *payload)
	assert.Equal(t, 3, lp.CurrLine())
}
<|endoftext|>