<commit_before>package main

import (
	"fmt"
	"os"
	"os/exec"
	"log"
	"strings"
	"errors"
	"io/ioutil"
	"time"
	"math"
	"strconv"
	"flag"
	"github.com/nsf/termbox-go"
)

var columnNames = statusLine{"Job name","Queued","Running","Workers"}

type statusLine struct {
	name string
	queued string
	running string
	workers string
}

type fieldWidths struct {
	name int
	queued int
	running int
	workers int
}

type gearmanStatus struct {
	statusLines []statusLine
	statusLineDims fieldWidths
}

func fieldWidthsFactory(line statusLine) fieldWidths{
	return fieldWidths{
		len(line.name),
		len(line.queued),
		len(line.running),
		len(line.workers)}
}

func statusLineFromString(line string) (statusLine, error) {
	parts := strings.Fields(line)
	if len(parts) != 4 {
		return statusLine{}, errors.New("Wrong number of fields")
	}
	return statusLine{parts[0], parts[1], parts[2], parts[3]}, nil
}

func getStatus(c chan gearmanStatus) {
	const waitTime = 1000 * time.Millisecond
	for {
		log.Println("Getting status")
		start := time.Now()
		widths := fieldWidthsFactory(columnNames)
	    data, err := exec.Command(gearadmin,"--host="+gearmanHost,"--port="+gearmanPort,"--status").Output()
	    if err != nil {
	    	log.Fatal("Couldn't get status from gearadmin: "+err.Error())
	    }
	    strData := string(data)
	    statusLines := make([]statusLine, 0)
	    for _, line := range strings.Split(strData, "\n") {
	    	statusLine, ok := statusLineFromString(line)
	    	if ok != nil {
	    		continue
	    	}
	    	widths.name = int(math.Max(float64(len(statusLine.name)), float64(widths.name)))
	    	widths.queued = int(math.Max(float64(len(statusLine.queued)), float64(widths.queued)))
	    	widths.running = int(math.Max(float64(len(statusLine.running)), float64(widths.running)))
	    	widths.workers = int(math.Max(float64(len(statusLine.workers)), float64(widths.workers)))
	    	statusLines = append(statusLines, statusLine)
	    }
	    c <- gearmanStatus{statusLines, widths}
	    duration := time.Since(start)
	    time.Sleep(waitTime - duration)
    }
}

func print_tb(x, y int, fg, bg termbox.Attribute, msg string) {
	for _, c := range msg {
		termbox.SetCell(x, y, c, fg, bg)
		x++
	}
}

func printf_tb(x, y int, fg, bg termbox.Attribute, format string, args ...interface{}) {
	s := fmt.Sprintf(format, args...)
	print_tb(x, y, fg, bg, s)
}

func printField(x, y, fieldWidth int, value string, bold bool) int{
	intValue, ok := strconv.Atoi(value)
	if ok == nil {
		value = fmt.Sprintf("%"+strconv.Itoa(fieldWidth)+"d",intValue) + " "
	}
	fg := termbox.ColorDefault
	if bold {
		fg |= termbox.AttrBold
	}
	print_tb(x, y, fg, termbox.ColorDefault, value)
	return x + fieldWidth
}

func printLine(y int, widths fieldWidths, line statusLine, bold bool) {
	x := 0
	if len(line.name) > widths.name {
		line.name = line.name[:widths.name]
	}
	x = printField(x, y, widths.name + 1, line.name, bold)
	x = printField(x, y, widths.queued + 1, line.queued, bold)
	x = printField(x, y, widths.running + 1, line.running, bold)
	x = printField(x, y, widths.workers, line.workers, bold)
}

func drawStatus(gearmanStatus gearmanStatus, position int, height int, width int) {
	termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
	y := 0
	printY := 0

	widths := gearmanStatus.statusLineDims
	totalWidth := widths.name + widths.queued + widths.running + widths.workers + 3;
	if totalWidth > width {
		widths.name += width - totalWidth
	}
	printLine(0, widths, columnNames, true)
	for _, line := range gearmanStatus.statusLines {
		printY = y - position
		if printY > height {
			break
		}
		if printY < 1 {
			y++
			continue
		}
		printLine(printY, widths, line, false)
		y++
	}
	termbox.Flush()
}

func handleEvents(direction chan int, resized chan termbox.Event, quit chan bool) {
	for {
		event := termbox.PollEvent()
		log.Println("Recieved event: ",event)
		switch event.Type {
			case termbox.EventKey:
				if event.Ch == 'q' {
					quit<-true
				}
				switch event.Key {
					case termbox.KeyArrowUp:
						direction <- -1
					case termbox.KeyArrowDown:
						direction <- 1
				}
			case termbox.EventResize:
				resized<-event
		}
	}
}

func calculatePosition (currentPosition int, direction int, gearmanStatus gearmanStatus) (int, bool) {
	_, height := termbox.Size()
	scrolledToBottom := len(gearmanStatus.statusLines) <= (currentPosition + height);
	scrolledToTop := currentPosition == 0
	if (direction < 0 && !scrolledToTop) || (direction > 0 && !scrolledToBottom) {
		log.Println("Moving")
		return currentPosition + direction, true
	}
	return currentPosition, false
}

func scrollOutput(direction int, scroll chan int, position int, currentGearmanStatus gearmanStatus) int{
	positionUpdated := false
	log.Println("Scrolling")
	for {
		width, height := termbox.Size()
		position, positionUpdated = calculatePosition(position, direction, currentGearmanStatus)
		if positionUpdated {
			drawStatus(currentGearmanStatus, position, height, width)
		}
		select {
			case direction = <-scroll:
				log.Println("Collating scrolling")
			default:
				return position
		}
	}
	panic("unreachable")
}

func initLogging() *os.File{
	f, err := os.OpenFile("/tmp/gearman_gtop.log", os.O_RDWR | os.O_CREATE | os.O_APPEND, 0666)
	if err != nil {
	    panic(err)
	}
	log.SetOutput(f)
	return f
}

var doLogging bool
var gearadmin string
var gearmanHost string
var gearmanPort string

func init() {
	logDefault := false
	logUsage := "Log debug to /tmp/gearman_gtop.log"
	flag.BoolVar(&doLogging, "log", logDefault, logUsage)
	flag.BoolVar(&doLogging, "l", logDefault, logUsage+" (shorthand)")
	gearadminDefault := "/usr/bin/gearadmin"
	gearadminUsage := "Path to gearadmin, e.g. `which gearadmin`"
	flag.StringVar(&gearadmin, "gearadmin", gearadminDefault, gearadminUsage)
	hostDefault := "localhost"
	hostUsage := "Gearmand host to connect to"
	flag.StringVar(&gearmanHost, "host", hostDefault, hostUsage)
	flag.StringVar(&gearmanHost, "h", hostDefault, hostUsage+" (shorthand)")
	portDefault := "4730"
	portUsage := "Gearmand port to connect to"
	flag.StringVar(&gearmanPort, "port", portDefault, portUsage)
	flag.StringVar(&gearmanPort, "p", portDefault, portUsage+" (shorthand)")
}

func main() {

	flag.Parse()

	if doLogging {
		defer (initLogging()).Close()
	} else {
		log.SetOutput(ioutil.Discard)
	}

	var currentGearmanStatus gearmanStatus
	position := 0
	status := make(chan gearmanStatus)
	quit := make(chan bool)
	resized := make(chan termbox.Event)
	scroll := make(chan int, 3)

	err := termbox.Init()
	if err != nil {
		panic(err)
	}
	defer termbox.Close()
	termbox.SetInputMode(termbox.InputEsc)

	go getStatus(status)
	go handleEvents(scroll, resized, quit)
	for {
		select {
			case currentGearmanStatus = <-status:
				log.Println("Redrawing for updated status")
				width, height := termbox.Size()
				drawStatus(currentGearmanStatus, position, height, width)
			case ev := <-resized:
				log.Println("Redrawing for resize")
				drawStatus(currentGearmanStatus, position, ev.Height, ev.Width)
			case direction := <-scroll:
				position = scrollOutput(direction, scroll, position, currentGearmanStatus)
			case <-quit:
				log.Println("Exiting")
				return
		}
	}
}
<commit_msg>go fmt'ed<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"github.com/nsf/termbox-go"
	"io/ioutil"
	"log"
	"math"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

var columnNames = statusLine{"Job name", "Queued", "Running", "Workers"}

type statusLine struct {
	name    string
	queued  string
	running string
	workers string
}

type fieldWidths struct {
	name    int
	queued  int
	running int
	workers int
}

type gearmanStatus struct {
	statusLines    []statusLine
	statusLineDims fieldWidths
}

func fieldWidthsFactory(line statusLine) fieldWidths {
	return fieldWidths{
		len(line.name),
		len(line.queued),
		len(line.running),
		len(line.workers)}
}

func statusLineFromString(line string) (statusLine, error) {
	parts := strings.Fields(line)
	if len(parts) != 4 {
		return statusLine{}, errors.New("Wrong number of fields")
	}
	return statusLine{parts[0], parts[1], parts[2], parts[3]}, nil
}

func getStatus(c chan gearmanStatus) {
	const waitTime = 1000 * time.Millisecond
	for {
		log.Println("Getting status")
		start := time.Now()
		widths := fieldWidthsFactory(columnNames)
		data, err := exec.Command(gearadmin, "--host="+gearmanHost, "--port="+gearmanPort, "--status").Output()
		if err != nil {
			log.Fatal("Couldn't get status from gearadmin: " + err.Error())
		}
		strData := string(data)
		statusLines := make([]statusLine, 0)
		for _, line := range strings.Split(strData, "\n") {
			statusLine, ok := statusLineFromString(line)
			if ok != nil {
				continue
			}
			widths.name = int(math.Max(float64(len(statusLine.name)), float64(widths.name)))
			widths.queued = int(math.Max(float64(len(statusLine.queued)), float64(widths.queued)))
			widths.running = int(math.Max(float64(len(statusLine.running)), float64(widths.running)))
			widths.workers = int(math.Max(float64(len(statusLine.workers)), float64(widths.workers)))
			statusLines = append(statusLines, statusLine)
		}
		c <- gearmanStatus{statusLines, widths}
		duration := time.Since(start)
		time.Sleep(waitTime - duration)
	}
}

func print_tb(x, y int, fg, bg termbox.Attribute, msg string) {
	for _, c := range msg {
		termbox.SetCell(x, y, c, fg, bg)
		x++
	}
}

func printf_tb(x, y int, fg, bg termbox.Attribute, format string, args ...interface{}) {
	s := fmt.Sprintf(format, args...)
	print_tb(x, y, fg, bg, s)
}

func printField(x, y, fieldWidth int, value string, bold bool) int {
	intValue, ok := strconv.Atoi(value)
	if ok == nil {
		value = fmt.Sprintf("%"+strconv.Itoa(fieldWidth)+"d", intValue) + " "
	}
	fg := termbox.ColorDefault
	if bold {
		fg |= termbox.AttrBold
	}
	print_tb(x, y, fg, termbox.ColorDefault, value)
	return x + fieldWidth
}

func printLine(y int, widths fieldWidths, line statusLine, bold bool) {
	x := 0
	if len(line.name) > widths.name {
		line.name = line.name[:widths.name]
	}
	x = printField(x, y, widths.name+1, line.name, bold)
	x = printField(x, y, widths.queued+1, line.queued, bold)
	x = printField(x, y, widths.running+1, line.running, bold)
	x = printField(x, y, widths.workers, line.workers, bold)
}

func drawStatus(gearmanStatus gearmanStatus, position int, height int, width int) {
	termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
	y := 0
	printY := 0

	widths := gearmanStatus.statusLineDims
	totalWidth := widths.name + widths.queued + widths.running + widths.workers + 3
	if totalWidth > width {
		widths.name += width - totalWidth
	}
	printLine(0, widths, columnNames, true)
	for _, line := range gearmanStatus.statusLines {
		printY = y - position
		if printY > height {
			break
		}
		if printY < 1 {
			y++
			continue
		}
		printLine(printY, widths, line, false)
		y++
	}
	termbox.Flush()
}

func handleEvents(direction chan int, resized chan termbox.Event, quit chan bool) {
	for {
		event := termbox.PollEvent()
		log.Println("Recieved event: ", event)
		switch event.Type {
		case termbox.EventKey:
			if event.Ch == 'q' {
				quit <- true
			}
			switch event.Key {
			case termbox.KeyArrowUp:
				direction <- -1
			case termbox.KeyArrowDown:
				direction <- 1
			}
		case termbox.EventResize:
			resized <- event
		}
	}
}

func calculatePosition(currentPosition int, direction int, gearmanStatus gearmanStatus) (int, bool) {
	_, height := termbox.Size()
	scrolledToBottom := len(gearmanStatus.statusLines) <= (currentPosition + height)
	scrolledToTop := currentPosition == 0
	if (direction < 0 && !scrolledToTop) || (direction > 0 && !scrolledToBottom) {
		log.Println("Moving")
		return currentPosition + direction, true
	}
	return currentPosition, false
}

func scrollOutput(direction int, scroll chan int, position int, currentGearmanStatus gearmanStatus) int {
	positionUpdated := false
	log.Println("Scrolling")
	for {
		width, height := termbox.Size()
		position, positionUpdated = calculatePosition(position, direction, currentGearmanStatus)
		if positionUpdated {
			drawStatus(currentGearmanStatus, position, height, width)
		}
		select {
		case direction = <-scroll:
			log.Println("Collating scrolling")
		default:
			return position
		}
	}
	panic("unreachable")
}

func initLogging() *os.File {
	f, err := os.OpenFile("/tmp/gearman_gtop.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		panic(err)
	}
	log.SetOutput(f)
	return f
}

var doLogging bool
var gearadmin string
var gearmanHost string
var gearmanPort string

func init() {
	logDefault := false
	logUsage := "Log debug to /tmp/gearman_gtop.log"
	flag.BoolVar(&doLogging, "log", logDefault, logUsage)
	flag.BoolVar(&doLogging, "l", logDefault, logUsage+" (shorthand)")
	gearadminDefault := "/usr/bin/gearadmin"
	gearadminUsage := "Path to gearadmin, e.g. `which gearadmin`"
	flag.StringVar(&gearadmin, "gearadmin", gearadminDefault, gearadminUsage)
	hostDefault := "localhost"
	hostUsage := "Gearmand host to connect to"
	flag.StringVar(&gearmanHost, "host", hostDefault, hostUsage)
	flag.StringVar(&gearmanHost, "h", hostDefault, hostUsage+" (shorthand)")
	portDefault := "4730"
	portUsage := "Gearmand port to connect to"
	flag.StringVar(&gearmanPort, "port", portDefault, portUsage)
	flag.StringVar(&gearmanPort, "p", portDefault, portUsage+" (shorthand)")
}

func main() {

	flag.Parse()

	if doLogging {
		defer (initLogging()).Close()
	} else {
		log.SetOutput(ioutil.Discard)
	}

	var currentGearmanStatus gearmanStatus
	position := 0
	status := make(chan gearmanStatus)
	quit := make(chan bool)
	resized := make(chan termbox.Event)
	scroll := make(chan int, 3)

	err := termbox.Init()
	if err != nil {
		panic(err)
	}
	defer termbox.Close()
	termbox.SetInputMode(termbox.InputEsc)

	go getStatus(status)
	go handleEvents(scroll, resized, quit)
	for {
		select {
		case currentGearmanStatus = <-status:
			log.Println("Redrawing for updated status")
			width, height := termbox.Size()
			drawStatus(currentGearmanStatus, position, height, width)
		case ev := <-resized:
			log.Println("Redrawing for resize")
			drawStatus(currentGearmanStatus, position, ev.Height, ev.Width)
		case direction := <-scroll:
			position = scrollOutput(direction, scroll, position, currentGearmanStatus)
		case <-quit:
			log.Println("Exiting")
			return
		}
	}
}
<|endoftext|>


<commit_before>package gen

import (
	"crypto/md5"
	"crypto/sha1"
	"encoding/base64"
	"fmt"
	"path"
	"strconv"
	"strings"
	"text/template"
)

var (
	defaultFuncs = template.FuncMap{
		"greaterThanEqualF": func(b, a float64) bool {
			return b >= a
		},
		"lessThanEqualF": func(b, a float64) bool {
			return b <= a
		},
		"greaterThanEqual": func(b, a int) bool {
			return b >= a
		},
		"capitalize": func(b string) string {
			if len(b) == 0 {
				return b
			}

			return strings.ToUpper(b[:1]) + b[1:]
		},
		"notempty": func(b string) bool {
			return strings.TrimSpace(b) != ""
		},
		"empty": func(b string) bool {
			return strings.TrimSpace(b) == ""
		},
		"title": func(b string) string {
			return strings.ToTitle(b)
		},
		"trim": func(b, suff string) string {
			return strings.Trim(b, suff)
		},
		"trimSuffix": func(b, suff string) string {
			return strings.TrimSuffix(b, suff)
		},
		"trimPrefix": func(b, pre string) string {
			return strings.TrimPrefix(b, pre)
		},
		"hasSuffix": func(b, suff string) bool {
			return strings.HasSuffix(b, suff)
		},
		"hasPrefix": func(b, pre string) bool {
			return strings.HasPrefix(b, pre)
		},
		"replaceOnce": func(b, target, sub string) string {
			return strings.Replace(b, target, sub, 1)
		},
		"replaceAll": func(b, target, sub string) string {
			return strings.Replace(b, target, sub, -1)
		},
		"lower": func(b string) string {
			return strings.ToLower(b)
		},
		"upper": func(b string) string {
			return strings.ToUpper(b)
		},
		"joinPath": func(b ...string) string {
			return path.Join(b...)
		},
		"basePathName": func(b string) string {
			return path.Base(b)
		},
		"join": func(vals []string, jn string) string {
			return strings.Join(vals, jn)
		},
		"joinInterface": func(vals []interface{}, jn string) string {
			var items []string
			for _, val := range vals {
				items = append(items, fmt.Sprintf("%+s", val))
			}
			return strings.Join(items, jn)
		},
		"joinSlice": func(vals []string, jn string) string {
			return strings.Join(vals, jn)
		},
		"joinVariadic": func(jn string, vals ...string) string {
			return strings.Join(vals, jn)
		},
		"splitAfter": func(b string, sp string, n int) []string {
			return strings.SplitAfterN(b, sp, n)
		},
		"split": func(b string, sp string) []string {
			return strings.Split(b, sp)
		},
		"indent": func(b string) string {
			return strings.Join(strings.Split(b, "\n"), "\n\t")
		},
		"lessThanEqual": func(b, a int) bool {
			return b <= a
		},
		"greaterThanF": func(b, a float64) bool {
			return b > a
		},
		"lessThanF": func(b, a float64) bool {
			return b < a
		},
		"greaterThan": func(b, a int) bool {
			return b > a
		},
		"lessThan": func(b, a int) bool {
			return b < a
		},
		"trimspace": func(b string) string {
			return strings.TrimSpace(b)
		},
		"equal": func(b, a interface{}) bool {
			return b == a
		},
		"not": func(b bool) bool {
			return !b
		},
		"notequal": func(b, a interface{}) bool {
			return b != a
		},
		"quote": quote,
		"prefixInt": func(prefix string, b int) string {
			return fmt.Sprintf("%s%d", prefix, b)
		},
		"stub": func(count int) string {
			var vals []string

			for i := count; i > 0; i-- {
				vals = append(vals, "_")
			}

			return strings.Join(vals, ",")
		},
		"subs": func(word string, b int) string {
			return word[:b]
		},
		"add": func(a, b int) int {
			return a + b
		},
		"multiply": func(a, b int) int {
			return a * b
		},
		"subtract": func(a, b int) int {
			return a - b
		},
		"divide": func(a, b int) int {
			return a / b
		},
		"lenNotEqual": func(b interface{}, target int) bool {
			return lenOff(b) != target
		},
		"lenEqual": func(b interface{}, target int) bool {
			return lenOff(b) == target
		},
		"percentage": func(a, b float64) float64 {
			return (a / b) * 100
		},
		"base64": func(data string) string {
			return base64.StdEncoding.EncodeToString([]byte(data))
		},
		"sha1": func(data string) string {
			mo := sha1.New()
			mo.Write([]byte(data))
			return fmt.Sprintf("%x", md5.Sum(nil))
		},
		"md5": func(data string) string {
			mo := md5.New()
			mo.Write([]byte(data))
			return fmt.Sprintf("%x", md5.Sum(nil))
		},
		"lenOf":         lenOff,
		"nthOf":         nthOf,
		"doCut":         cutList,
		"doTimesPrefix": doTimePrefix,
		"doTimesSuffix": doTimeSuffix,
		"doPrefix":      doPrefix,
		"doSuffix":      doSuffix,
		"doCutSplit":    doCutSplit,
		"doPrefixCut":   cutListPrefix,
		"doSuffixCut":   cutListSuffix,
		"intsToString":  doStringConvert,
	}
)

func doStringConvert(vals []interface{}) []string {
	var items []string
	for _, val := range vals {
		items = append(items, fmt.Sprintf("%+s", val))
	}
	return items
}

func cutListSuffix(sets []string, cutsuffix string) []string {
	var do []string

	for _, set := range sets {
		do = append(do, strings.TrimSuffix(set, cutsuffix))
	}

	return do
}

func cutListPrefix(sets []string, cutprefix string) []string {
	var do []string

	for _, set := range sets {
		do = append(do, strings.TrimPrefix(set, cutprefix))
	}

	return do
}

func doCutSplit(sets []string, sp string, index int) []string {
	var do []string

	for _, set := range sets {
		parts := strings.Split(set, sp)
		if index >= len(parts) {
			continue
		}

		do = append(do, parts[index])
	}

	return do
}

func cutList(sets []string, cut string) []string {
	var do []string

	for _, set := range sets {
		do = append(do, strings.Trim(set, cut))
	}

	return do
}

func doSuffix(elems []string, suffix string) []string {
	var do []string

	for _, item := range elems {
		do = append(do, fmt.Sprintf("%s%s", item, suffix))
	}

	return do
}

func doPrefix(elems []string, prefix string) []string {
	var do []string

	for _, item := range elems {
		do = append(do, fmt.Sprintf("%s%s", prefix, item))
	}

	return do
}

func doTimeSuffix(times int, suffix string) []string {
	var do []string

	for i := 0; i < times; i++ {
		do = append(do, fmt.Sprintf("%d%s", i, suffix))
	}

	return do
}

func doTimePrefix(times int, prefix string) []string {
	var do []string

	for i := 0; i < times; i++ {
		do = append(do, fmt.Sprintf("%s%d", prefix, i))
	}

	return do
}

func quote(b interface{}) string {
	switch bo := b.(type) {
	case string:
		return strconv.Quote(bo)
	case int:
		return strconv.Quote(strconv.Itoa(bo))
	case bool:
		return strconv.Quote(strconv.FormatBool(bo))
	case int64:
		return strconv.Quote(strconv.Itoa(int(bo)))
	case float32:
		mo := strconv.FormatFloat(float64(bo), 'f', 4, 32)
		return strconv.Quote(mo)
	case float64:
		mo := strconv.FormatFloat(bo, 'f', 4, 32)
		return strconv.Quote(mo)
	case byte:
		return strconv.QuoteRune(rune(bo))
	case rune:
		return strconv.QuoteRune(bo)
	default:
		return "Unconvertible Type"
	}
}

func nthOf(b interface{}, index int) (val interface{}) {
	switch bo := b.(type) {
	case []interface{}:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []string:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case string:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []int:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []bool:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []int64:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []float32:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []float64:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []byte:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	}

	return
}

func lenOff(b interface{}) int {
	switch bo := b.(type) {
	case []interface{}:
		return len(bo)
	case []string:
		return len(bo)
	case string:
		return len(bo)
	case []int:
		return len(bo)
	case []bool:
		return len(bo)
	case []int64:
		return len(bo)
	case []float32:
		return len(bo)
	case []float64:
		return len(bo)
	case []byte:
		return len(bo)
	default:
		return 0
	}
}

// ToTemplateFuncs returns a template.FuncMap which is a union of all key and values
// from the provided map. It does not check for function type and will override any previos
// key values.
func ToTemplateFuncs(funcs ...map[string]interface{}) template.FuncMap {
	tfuncs := make(map[string]interface{})

	for _, item := range funcs {
		for k, v := range item {
			tfuncs[k] = v
		}
	}

	return template.FuncMap(tfuncs)
}

// ToTemplate returns a template instance with the giving templ string and functions.
func ToTemplate(name string, templ string, mx template.FuncMap) (*template.Template, error) {
	tml, err := template.New(name).Funcs(defaultFuncs).Funcs(mx).Parse(templ)
	if err != nil {
		return nil, err
	}

	return tml, nil
}
<commit_msg>Adding md5, sha1 and base64 functions to templates<commit_after>package gen

import (
	"crypto/md5"
	"crypto/sha1"
	"encoding/base64"
	"fmt"
	"path"
	"strconv"
	"strings"
	"text/template"
)

var (
	defaultFuncs = template.FuncMap{
		"greaterThanEqualF": func(b, a float64) bool {
			return b >= a
		},
		"lessThanEqualF": func(b, a float64) bool {
			return b <= a
		},
		"greaterThanEqual": func(b, a int) bool {
			return b >= a
		},
		"capitalize": func(b string) string {
			if len(b) == 0 {
				return b
			}

			return strings.ToUpper(b[:1]) + b[1:]
		},
		"notempty": func(b string) bool {
			return strings.TrimSpace(b) != ""
		},
		"empty": func(b string) bool {
			return strings.TrimSpace(b) == ""
		},
		"title": func(b string) string {
			return strings.ToTitle(b)
		},
		"trim": func(b, suff string) string {
			return strings.Trim(b, suff)
		},
		"trimSuffix": func(b, suff string) string {
			return strings.TrimSuffix(b, suff)
		},
		"trimPrefix": func(b, pre string) string {
			return strings.TrimPrefix(b, pre)
		},
		"hasSuffix": func(b, suff string) bool {
			return strings.HasSuffix(b, suff)
		},
		"hasPrefix": func(b, pre string) bool {
			return strings.HasPrefix(b, pre)
		},
		"replaceOnce": func(b, target, sub string) string {
			return strings.Replace(b, target, sub, 1)
		},
		"replaceAll": func(b, target, sub string) string {
			return strings.Replace(b, target, sub, -1)
		},
		"lower": func(b string) string {
			return strings.ToLower(b)
		},
		"upper": func(b string) string {
			return strings.ToUpper(b)
		},
		"joinPath": func(b ...string) string {
			return path.Join(b...)
		},
		"basePathName": func(b string) string {
			return path.Base(b)
		},
		"join": func(vals []string, jn string) string {
			return strings.Join(vals, jn)
		},
		"joinInterface": func(vals []interface{}, jn string) string {
			var items []string
			for _, val := range vals {
				items = append(items, fmt.Sprintf("%+s", val))
			}
			return strings.Join(items, jn)
		},
		"joinSlice": func(vals []string, jn string) string {
			return strings.Join(vals, jn)
		},
		"joinVariadic": func(jn string, vals ...string) string {
			return strings.Join(vals, jn)
		},
		"splitAfter": func(b string, sp string, n int) []string {
			return strings.SplitAfterN(b, sp, n)
		},
		"split": func(b string, sp string) []string {
			return strings.Split(b, sp)
		},
		"indent": func(b string) string {
			return strings.Join(strings.Split(b, "\n"), "\n\t")
		},
		"lessThanEqual": func(b, a int) bool {
			return b <= a
		},
		"greaterThanF": func(b, a float64) bool {
			return b > a
		},
		"lessThanF": func(b, a float64) bool {
			return b < a
		},
		"greaterThan": func(b, a int) bool {
			return b > a
		},
		"lessThan": func(b, a int) bool {
			return b < a
		},
		"trimspace": func(b string) string {
			return strings.TrimSpace(b)
		},
		"equal": func(b, a interface{}) bool {
			return b == a
		},
		"not": func(b bool) bool {
			return !b
		},
		"notequal": func(b, a interface{}) bool {
			return b != a
		},
		"quote": quote,
		"prefixInt": func(prefix string, b int) string {
			return fmt.Sprintf("%s%d", prefix, b)
		},
		"stub": func(count int) string {
			var vals []string

			for i := count; i > 0; i-- {
				vals = append(vals, "_")
			}

			return strings.Join(vals, ",")
		},
		"subs": func(word string, b int) string {
			return word[:b]
		},
		"add": func(a, b int) int {
			return a + b
		},
		"multiply": func(a, b int) int {
			return a * b
		},
		"subtract": func(a, b int) int {
			return a - b
		},
		"divide": func(a, b int) int {
			return a / b
		},
		"lenNotEqual": func(b interface{}, target int) bool {
			return lenOff(b) != target
		},
		"lenEqual": func(b interface{}, target int) bool {
			return lenOff(b) == target
		},
		"percentage": func(a, b float64) float64 {
			return (a / b) * 100
		},
		"base64": func(data string) string {
			return base64.StdEncoding.EncodeToString([]byte(data))
		},
		"sha1": func(data string) string {
			mo := sha1.New()
			mo.Write([]byte(data))
			return fmt.Sprintf("%x", mo.Sum(nil))
		},
		"md5": func(data string) string {
			mo := md5.New()
			mo.Write([]byte(data))
			return fmt.Sprintf("%x", mo.Sum(nil))
		},
		"lenOf":         lenOff,
		"nthOf":         nthOf,
		"doCut":         cutList,
		"doTimesPrefix": doTimePrefix,
		"doTimesSuffix": doTimeSuffix,
		"doPrefix":      doPrefix,
		"doSuffix":      doSuffix,
		"doCutSplit":    doCutSplit,
		"doPrefixCut":   cutListPrefix,
		"doSuffixCut":   cutListSuffix,
		"intsToString":  doStringConvert,
	}
)

func doStringConvert(vals []interface{}) []string {
	var items []string
	for _, val := range vals {
		items = append(items, fmt.Sprintf("%+s", val))
	}
	return items
}

func cutListSuffix(sets []string, cutsuffix string) []string {
	var do []string

	for _, set := range sets {
		do = append(do, strings.TrimSuffix(set, cutsuffix))
	}

	return do
}

func cutListPrefix(sets []string, cutprefix string) []string {
	var do []string

	for _, set := range sets {
		do = append(do, strings.TrimPrefix(set, cutprefix))
	}

	return do
}

func doCutSplit(sets []string, sp string, index int) []string {
	var do []string

	for _, set := range sets {
		parts := strings.Split(set, sp)
		if index >= len(parts) {
			continue
		}

		do = append(do, parts[index])
	}

	return do
}

func cutList(sets []string, cut string) []string {
	var do []string

	for _, set := range sets {
		do = append(do, strings.Trim(set, cut))
	}

	return do
}

func doSuffix(elems []string, suffix string) []string {
	var do []string

	for _, item := range elems {
		do = append(do, fmt.Sprintf("%s%s", item, suffix))
	}

	return do
}

func doPrefix(elems []string, prefix string) []string {
	var do []string

	for _, item := range elems {
		do = append(do, fmt.Sprintf("%s%s", prefix, item))
	}

	return do
}

func doTimeSuffix(times int, suffix string) []string {
	var do []string

	for i := 0; i < times; i++ {
		do = append(do, fmt.Sprintf("%d%s", i, suffix))
	}

	return do
}

func doTimePrefix(times int, prefix string) []string {
	var do []string

	for i := 0; i < times; i++ {
		do = append(do, fmt.Sprintf("%s%d", prefix, i))
	}

	return do
}

func quote(b interface{}) string {
	switch bo := b.(type) {
	case string:
		return strconv.Quote(bo)
	case int:
		return strconv.Quote(strconv.Itoa(bo))
	case bool:
		return strconv.Quote(strconv.FormatBool(bo))
	case int64:
		return strconv.Quote(strconv.Itoa(int(bo)))
	case float32:
		mo := strconv.FormatFloat(float64(bo), 'f', 4, 32)
		return strconv.Quote(mo)
	case float64:
		mo := strconv.FormatFloat(bo, 'f', 4, 32)
		return strconv.Quote(mo)
	case byte:
		return strconv.QuoteRune(rune(bo))
	case rune:
		return strconv.QuoteRune(bo)
	default:
		return "Unconvertible Type"
	}
}

func nthOf(b interface{}, index int) (val interface{}) {
	switch bo := b.(type) {
	case []interface{}:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []string:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case string:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []int:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []bool:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []int64:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []float32:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []float64:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	case []byte:
		if index >= len(bo) {
			return
		}
		val = bo[index]
	}

	return
}

func lenOff(b interface{}) int {
	switch bo := b.(type) {
	case []interface{}:
		return len(bo)
	case []string:
		return len(bo)
	case string:
		return len(bo)
	case []int:
		return len(bo)
	case []bool:
		return len(bo)
	case []int64:
		return len(bo)
	case []float32:
		return len(bo)
	case []float64:
		return len(bo)
	case []byte:
		return len(bo)
	default:
		return 0
	}
}

// ToTemplateFuncs returns a template.FuncMap which is a union of all key and values
// from the provided map. It does not check for function type and will override any previos
// key values.
func ToTemplateFuncs(funcs ...map[string]interface{}) template.FuncMap {
	tfuncs := make(map[string]interface{})

	for _, item := range funcs {
		for k, v := range item {
			tfuncs[k] = v
		}
	}

	return template.FuncMap(tfuncs)
}

// ToTemplate returns a template instance with the giving templ string and functions.
func ToTemplate(name string, templ string, mx template.FuncMap) (*template.Template, error) {
	tml, err := template.New(name).Funcs(defaultFuncs).Funcs(mx).Parse(templ)
	if err != nil {
		return nil, err
	}

	return tml, nil
}
<|endoftext|>


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

package main

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/minio/cli"
	"github.com/minio/mc/pkg/console"
	"github.com/minio/minio/pkg/iodine"
)

// Help message.
var sessionCmd = cli.Command{
	Name:   "session",
	Usage:  "Start sessions for cp and sync",
	Action: runSessionCmd,
	CustomHelpTemplate: `NAME:
   mc {{.Name}} - {{.Usage}}

USAGE:
   mc {{.Name}}{{if .Flags}} [ARGS...]{{end}} SOURCE TARGET [TARGET...] {{if .Description}}

DESCRIPTION:
   {{.Description}}{{end}}{{if .Flags}}

FLAGS:
   {{range .Flags}}{{.}}
   {{end}}{{ end }}

EXAMPLES:
   1. List sessions
      $ mc {{.Name}} list

   2. Resume session
      $ mc {{.Name}} resume [SESSION]

   3. Clear session
      $ mc {{.Name}} clear [SESSION]|[*]

`,
}

func listSessions(sdir string) error {
	sdir, err := getSessionDir()
	if err != nil {
		return iodine.New(err, nil)
	}
	sessions, err := ioutil.ReadDir(sdir)
	if err != nil {
		return iodine.New(err, nil)
	}
	for _, session := range sessions {
		if session.Mode().IsRegular() {
			s, err := loadSession(session.Name())
			if err != nil {
				return iodine.New(err, nil)
			}
			console.Prints(s)
		}
	}
	return nil
}

func resumeSession(sid string) (*sessionV1, error) {
	sfile, err := getSessionFile(sid)
	if err != nil {
		return nil, iodine.New(err, nil)
	}
	_, err = os.Stat(sfile)
	if err != nil {
		return nil, iodine.New(errInvalidSessionID{id: sid}, nil)
	}
	s, err := loadSession(sid)
	if err != nil {
		return nil, iodine.New(err, nil)
	}
	return s, nil
}

func clearSession(sid string) error {
	if sid == "*" {
		sdir, err := getSessionDir()
		if err != nil {
			return iodine.New(err, nil)
		}
		sessions, err := ioutil.ReadDir(sdir)
		if err != nil {
			return iodine.New(err, nil)
		}
		for _, session := range sessions {
			if session.Mode().IsRegular() {
				err := os.Remove(filepath.Join(sdir, session.Name()))
				if err != nil {
					return iodine.New(err, nil)
				}
			}
		}
		return nil
	}
	sfile, err := getSessionFile(sid)
	if err != nil {
		return iodine.New(err, nil)
	}
	err = os.Remove(sfile)
	if err != nil {
		return iodine.New(errInvalidSessionID{id: sid}, nil)
	}
	return nil
}

func sessionExecute(bar barSend, s *sessionV1) {
	switch s.CommandType {
	case "cp":
		for cps := range doCopyCmdSession(bar, s) {
			if cps.Error != nil {
				console.Errors(ErrorMessage{
					Message: "Failed with",
					Error:   iodine.New(cps.Error, nil),
				})
			}
			if cps.Done {
				if err := saveSession(s); err != nil {
					console.Fatals(ErrorMessage{
						Message: "Failed with",
						Error:   iodine.New(err, nil),
					})
				}
				console.Infos(InfoMessage{
					Message: "\nSession terminated. To resume session type ‘mc session resume " + s.SessionID + "’",
				})
				// this os.Exit is needed really to exit in-case of "os.Interrupt"
				os.Exit(0)
			}
		}
	case "sync":
		for ss := range doSyncCmdSession(bar, s) {
			if ss.Error != nil {
				console.Errors(ErrorMessage{
					Message: "Failed with",
					Error:   iodine.New(ss.Error, nil),
				})
			}
			if ss.Done {
				if err := saveSession(s); err != nil {
					console.Fatals(ErrorMessage{
						Message: "Failed with",
						Error:   iodine.New(err, nil),
					})
				}
				console.Infos(InfoMessage{
					Message: "\nSession terminated. To resume session type ‘mc session resume " + s.SessionID + "’",
				})
				// this os.Exit is needed really to exit in-case of "os.Interrupt"
				os.Exit(0)
			}
		}
	}
}

func runSessionCmd(ctx *cli.Context) {
	if len(ctx.Args()) < 1 || ctx.Args().First() == "help" {
		cli.ShowCommandHelpAndExit(ctx, "session", 1) // last argument is exit code
	}
	if strings.TrimSpace(ctx.Args().First()) == "" {
		cli.ShowCommandHelpAndExit(ctx, "session", 1) // last argument is exit code
	}
	if !isSessionDirExists() {
		if err := createSessionDir(); err != nil {
			console.Fatals(ErrorMessage{
				Message: "Failed with",
				Error:   iodine.ToError(err),
			})
		}
	}
	switch strings.TrimSpace(ctx.Args().First()) {
	// list resumable sessions
	case "list":
		err := listSessions(sessionDir)
		if err != nil {
			console.Fatals(ErrorMessage{
				Message: "Failed with",
				Error:   iodine.ToError(err),
			})
		}
	case "resume":
		if len(ctx.Args().Tail()) != 1 {
			cli.ShowCommandHelpAndExit(ctx, "session", 1) // last argument is exit code
		}
		if strings.TrimSpace(ctx.Args().Tail().First()) == "" {
			cli.ShowCommandHelpAndExit(ctx, "session", 1) // last argument is exit code
		}
		sid := strings.TrimSpace(ctx.Args().Tail().First())
		s, err := resumeSession(sid)
		if err != nil {
			console.Fatals(ErrorMessage{
				Message: "Failed with",
				Error:   iodine.ToError(err),
			})
		}
		var bar barSend
		// set up progress bar
		if !globalQuietFlag {
			bar = newCpBar()
		}
		sessionExecute(bar, s)
		if !globalQuietFlag {
			bar.Finish()
			if err := clearSession(sid); err != nil {
				console.Fatals(ErrorMessage{
					Message: "Failed with",
					Error:   iodine.ToError(err),
				})
			}
		}
	// purge a requested pending session, if "*" purge everything
	case "clear":
		if len(ctx.Args().Tail()) != 1 {
			cli.ShowCommandHelpAndExit(ctx, "session", 1) // last argument is exit code
		}
		if strings.TrimSpace(ctx.Args().Tail().First()) == "" {
			cli.ShowCommandHelpAndExit(ctx, "session", 1) // last argument is exit code
		}
		if err := clearSession(strings.TrimSpace(ctx.Args().Tail().First())); err != nil {
			console.Fatals(ErrorMessage{
				Message: "Failed with",
				Error:   iodine.ToError(err),
			})
		}
	}
}
<commit_msg>Show help when the user enters an incorrect session subcommand<commit_after>/*
 * Minio Client, (C) 2015 Minio, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package main

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/minio/cli"
	"github.com/minio/mc/pkg/console"
	"github.com/minio/minio/pkg/iodine"
)

// Help message.
var sessionCmd = cli.Command{
	Name:   "session",
	Usage:  "Start sessions for cp and sync",
	Action: runSessionCmd,
	CustomHelpTemplate: `NAME:
   mc {{.Name}} - {{.Usage}}

USAGE:
   mc {{.Name}}{{if .Flags}} [ARGS...]{{end}} SOURCE TARGET [TARGET...] {{if .Description}}

DESCRIPTION:
   {{.Description}}{{end}}{{if .Flags}}

FLAGS:
   {{range .Flags}}{{.}}
   {{end}}{{ end }}

EXAMPLES:
   1. List sessions
      $ mc {{.Name}} list

   2. Resume session
      $ mc {{.Name}} resume [SESSION]

   3. Clear session
      $ mc {{.Name}} clear [SESSION]|[*]

`,
}

func listSessions(sdir string) error {
	sdir, err := getSessionDir()
	if err != nil {
		return iodine.New(err, nil)
	}
	sessions, err := ioutil.ReadDir(sdir)
	if err != nil {
		return iodine.New(err, nil)
	}
	for _, session := range sessions {
		if session.Mode().IsRegular() {
			s, err := loadSession(session.Name())
			if err != nil {
				return iodine.New(err, nil)
			}
			console.Prints(s)
		}
	}
	return nil
}

func resumeSession(sid string) (*sessionV1, error) {
	sfile, err := getSessionFile(sid)
	if err != nil {
		return nil, iodine.New(err, nil)
	}
	_, err = os.Stat(sfile)
	if err != nil {
		return nil, iodine.New(errInvalidSessionID{id: sid}, nil)
	}
	s, err := loadSession(sid)
	if err != nil {
		return nil, iodine.New(err, nil)
	}
	return s, nil
}

func clearSession(sid string) error {
	if sid == "*" {
		sdir, err := getSessionDir()
		if err != nil {
			return iodine.New(err, nil)
		}
		sessions, err := ioutil.ReadDir(sdir)
		if err != nil {
			return iodine.New(err, nil)
		}
		for _, session := range sessions {
			if session.Mode().IsRegular() {
				err := os.Remove(filepath.Join(sdir, session.Name()))
				if err != nil {
					return iodine.New(err, nil)
				}
			}
		}
		return nil
	}
	sfile, err := getSessionFile(sid)
	if err != nil {
		return iodine.New(err, nil)
	}
	err = os.Remove(sfile)
	if err != nil {
		return iodine.New(errInvalidSessionID{id: sid}, nil)
	}
	return nil
}

func sessionExecute(bar barSend, s *sessionV1) {
	switch s.CommandType {
	case "cp":
		for cps := range doCopyCmdSession(bar, s) {
			if cps.Error != nil {
				console.Errors(ErrorMessage{
					Message: "Failed with",
					Error:   iodine.New(cps.Error, nil),
				})
			}
			if cps.Done {
				if err := saveSession(s); err != nil {
					console.Fatals(ErrorMessage{
						Message: "Failed with",
						Error:   iodine.New(err, nil),
					})
				}
				console.Infos(InfoMessage{
					Message: "\nSession terminated. To resume session type ‘mc session resume " + s.SessionID + "’",
				})
				// this os.Exit is needed really to exit in-case of "os.Interrupt"
				os.Exit(0)
			}
		}
	case "sync":
		for ss := range doSyncCmdSession(bar, s) {
			if ss.Error != nil {
				console.Errors(ErrorMessage{
					Message: "Failed with",
					Error:   iodine.New(ss.Error, nil),
				})
			}
			if ss.Done {
				if err := saveSession(s); err != nil {
					console.Fatals(ErrorMessage{
						Message: "Failed with",
						Error:   iodine.New(err, nil),
					})
				}
				console.Infos(InfoMessage{
					Message: "\nSession terminated. To resume session type ‘mc session resume " + s.SessionID + "’",
				})
				// this os.Exit is needed really to exit in-case of "os.Interrupt"
				os.Exit(0)
			}
		}
	}
}

func runSessionCmd(ctx *cli.Context) {
	if len(ctx.Args()) < 1 || ctx.Args().First() == "help" {
		cli.ShowCommandHelpAndExit(ctx, "session", 1) // last argument is exit code
	}
	if strings.TrimSpace(ctx.Args().First()) == "" {
		cli.ShowCommandHelpAndExit(ctx, "session", 1) // last argument is exit code
	}
	if !isSessionDirExists() {
		if err := createSessionDir(); err != nil {
			console.Fatals(ErrorMessage{
				Message: "Failed with",
				Error:   iodine.ToError(err),
			})
		}
	}
	switch strings.TrimSpace(ctx.Args().First()) {
	// list resumable sessions
	case "list":
		err := listSessions(sessionDir)
		if err != nil {
			console.Fatals(ErrorMessage{
				Message: "Failed with",
				Error:   iodine.ToError(err),
			})
		}
	case "resume":
		if len(ctx.Args().Tail()) != 1 {
			cli.ShowCommandHelpAndExit(ctx, "session", 1) // last argument is exit code
		}
		if strings.TrimSpace(ctx.Args().Tail().First()) == "" {
			cli.ShowCommandHelpAndExit(ctx, "session", 1) // last argument is exit code
		}
		sid := strings.TrimSpace(ctx.Args().Tail().First())
		s, err := resumeSession(sid)
		if err != nil {
			console.Fatals(ErrorMessage{
				Message: "Failed with",
				Error:   iodine.ToError(err),
			})
		}
		var bar barSend
		// set up progress bar
		if !globalQuietFlag {
			bar = newCpBar()
		}
		sessionExecute(bar, s)
		if !globalQuietFlag {
			bar.Finish()
			if err := clearSession(sid); err != nil {
				console.Fatals(ErrorMessage{
					Message: "Failed with",
					Error:   iodine.ToError(err),
				})
			}
		}
	// purge a requested pending session, if "*" purge everything
	case "clear":
		if len(ctx.Args().Tail()) != 1 {
			cli.ShowCommandHelpAndExit(ctx, "session", 1) // last argument is exit code
		}
		if strings.TrimSpace(ctx.Args().Tail().First()) == "" {
			cli.ShowCommandHelpAndExit(ctx, "session", 1) // last argument is exit code
		}
		if err := clearSession(strings.TrimSpace(ctx.Args().Tail().First())); err != nil {
			console.Fatals(ErrorMessage{
				Message: "Failed with",
				Error:   iodine.ToError(err),
			})
		}
	default:
		cli.ShowCommandHelpAndExit(ctx, "session", 1) // last argument is exit code
	}
}
<|endoftext|>


<commit_before>// Copyright © 2017 The VirusTotal CLI authors. 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 cmd

import (
	"bufio"
	"errors"
	"fmt"
	"os"
	"regexp"
	"strconv"
	"sync"

	vt "github.com/VirusTotal/vt-go"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var notificationsDeleteCmdHelp = `Delete hunting notifications.

This command deletes the malware hunting notifications associated to the
currently configured API key.`

// NewHuntingNotificationDeleteCmd returns a command for deleting all hunting
// notifications for the current user.
func NewHuntingNotificationDeleteCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "delete [notification id]...",
		Short: "Delete hunting notifications",
		Long:  notificationsDeleteCmdHelp,
		RunE: func(cmd *cobra.Command, args []string) error {
			deleteAll := viper.GetBool("all")
			deleteTag := viper.GetString("with-tag")
			if len(args) == 0 && !deleteAll && deleteTag == "" {
				return errors.New("Specify notification id or use --all or --with-tag")
			}
			client, err := NewAPIClient()
			if err != nil {
				return err
			}
			if deleteAll {
				_, err = client.Delete(vt.URL("intelligence/hunting_notifications"))
			} else if deleteTag != "" {
				_, err = client.Delete(vt.URL("intelligence/hunting_notifications?tag=%s", deleteTag))
			} else {
				var wg sync.WaitGroup
				for _, arg := range args {
					wg.Add(1)
					go func(notificationID string) {
						url := vt.URL("intelligence/hunting_notifications/%s", notificationID)
						if _, err := client.Delete(url); err != nil {
							fmt.Fprintf(os.Stderr, "%v\n", err)
						}
						wg.Done()
					}(arg)
				}
				wg.Wait()
			}
			return err
		},
	}

	cmd.Flags().BoolP("all", "a", false, "delete all notifications")
	cmd.Flags().StringP("with-tag", "t", "", "delete notifications with a given tag")

	return cmd
}

var notificationsListCmdHelp = `List malware hunting notifications.

This command list the malware hunting notifications associated to the currently
configured API key.`

// NewHuntingNotificationListCmd returns a new instance of the 'notifications list' command.
func NewHuntingNotificationListCmd() *cobra.Command {
	cmd := &cobra.Command{
		Aliases: []string{"ls"},
		Use:     "list",
		Short:   "List notifications",
		Long:    notificationsListCmdHelp,

		RunE: func(cmd *cobra.Command, args []string) error {
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			return p.PrintCollection(vt.URL("intelligence/hunting_notifications?relationships=file"))
		},
	}

	addIncludeExcludeFlags(cmd.Flags())
	addFilterFlag(cmd.Flags())
	addLimitFlag(cmd.Flags())
	addCursorFlag(cmd.Flags())

	cmd.AddCommand(NewHuntingNotificationDeleteCmd())

	return cmd
}

// NewHuntingNotificationCmd returns a new instance of the 'notifications' command.
func NewHuntingNotificationCmd() *cobra.Command {
	cmd := &cobra.Command{
		Aliases: []string{"nt"},
		Use:     "notification [id]...",
		Short:   "Manage malware hunting notifications",
		Args:    cobra.MinimumNArgs(1),

		RunE: func(cmd *cobra.Command, args []string) error {
			re, _ := regexp.Compile("\\d+")
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			return p.GetAndPrintObjects("intelligence/hunting_notifications/%s", args, re)
		},
	}

	addThreadsFlag(cmd.Flags())

	cmd.AddCommand(NewHuntingNotificationListCmd())
	cmd.AddCommand(NewHuntingNotificationDeleteCmd())

	return cmd
}

var rulesetsListCmdHelp = `List malware hunting rulesets.

This command list the malware hunting rulesets associated to the currently
configured API key.`

// NewHuntingRulesetListCmd returns a new instance of the 'rulesets list' command.
func NewHuntingRulesetListCmd() *cobra.Command {
	cmd := &cobra.Command{
		Aliases: []string{"ls"},
		Use:     "list",
		Short:   "List rulesets",
		Long:    rulesetsListCmdHelp,

		RunE: func(cmd *cobra.Command, args []string) error {
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			return p.PrintCollection(vt.URL("intelligence/hunting_rulesets"))
		},
	}

	addIncludeExcludeFlags(cmd.Flags())
	addFilterFlag(cmd.Flags())
	addLimitFlag(cmd.Flags())
	addCursorFlag(cmd.Flags())
	addIDOnlyFlag(cmd.Flags())

	return cmd
}

func patchRuleset(id, attr string, value interface{}) error {
	client, err := NewAPIClient()
	if err != nil {
		return err
	}
	obj := vt.NewObjectWithID("hunting_ruleset", id)
	obj.Set(attr, value)
	return client.PatchObject(vt.URL("intelligence/hunting_rulesets/%s", id), obj)
}

// NewHuntingRulesetDisableCmd returns a command for disabling a given ruleset.
func NewHuntingRulesetDisableCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "disable [ruleset id]",
		Short: "Disable ruleset",
		Args:  cobra.ExactArgs(1),

		RunE: func(cmd *cobra.Command, args []string) error {
			return patchRuleset(args[0], "enabled", false)
		},
	}
}

// NewHuntingRulesetEnableCmd returns a command for enabling a given ruleset.
func NewHuntingRulesetEnableCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "enable [ruleset id]",
		Short: "Enable ruleset",
		Args:  cobra.ExactArgs(1),

		RunE: func(cmd *cobra.Command, args []string) error {
			return patchRuleset(args[0], "enabled", true)
		},
	}
}

// NewHuntingRulesetRenameCmd returns a command for renaming a given ruleset.
func NewHuntingRulesetRenameCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "rename [ruleset id] [name]",
		Short: "Rename ruleset",
		Args:  cobra.ExactArgs(2),

		RunE: func(cmd *cobra.Command, args []string) error {
			return patchRuleset(args[0], "name", args[1])
		},
	}
}

// NewHuntingRulesetSetLimitCmd returns a command for changing a ruleset's limit.
func NewHuntingRulesetSetLimitCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "setlimit [ruleset id] [limit]",
		Short: "Set ruleset limit",
		Args:  cobra.MinimumNArgs(2),

		RunE: func(cmd *cobra.Command, args []string) error {
			limit, err := strconv.Atoi(args[1])
			if err != nil {
				return fmt.Errorf("invalid limit: %s", args[1])
			}
			return patchRuleset(args[0], "limit", limit)
		},
	}
}

// NewHuntingRulesetUpdateCmd returns a command for updating ruleset's rules.
func NewHuntingRulesetUpdateCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "update [ruleset id] [rules file]",
		Short: "Change the rules for a ruleset.",
		Args:  cobra.MinimumNArgs(2),

		RunE: func(cmd *cobra.Command, args []string) error {
			rules, err := ReadFile(args[1])
			if err != nil {
				return err
			}
			return patchRuleset(args[0], "rules", string(rules))
		},
	}
}

// NewHuntingRulesetDeleteCmd returns a command for deleting a given ruleset.
func NewHuntingRulesetDeleteCmd() *cobra.Command {
	cmd := &cobra.Command{
		Aliases: []string{"del", "rm"},
		Use:     "delete [ruleset id]...",
		Short:   "Delete rulesets",

		RunE: func(cmd *cobra.Command, args []string) error {
			client, err := NewAPIClient()
			if err != nil {
				return err
			}
			deleteAll := viper.GetBool("all")
			if len(args) == 0 && !deleteAll {
				return errors.New("Specify ruleset id or use --all")
			}
			if deleteAll {
				fmt.Print("Enter your VirusTotal username to confirm: ")
				scanner := bufio.NewScanner(os.Stdin)
				scanner.Scan()
				username := scanner.Text()
				_, err = client.Delete(
					vt.URL("intelligence/hunting_rulesets"),
					vt.WithHeader("x-confirm-delete", username))
			} else {
				var wg sync.WaitGroup
				for _, arg := range args {
					wg.Add(1)
					go func(rulesetID string) {
						url := vt.URL("intelligence/hunting_rulesets/%s", rulesetID)
						if _, err := client.Delete(url); err != nil {
							fmt.Fprintf(os.Stderr, "%v\n", err)
						}
						wg.Done()
					}(arg)
				}
				wg.Wait()
			}
			return err
		},
	}

	cmd.Flags().BoolP("all", "a", false, "delete all rulesets")

	return cmd
}

// NewHuntingRulesetAddCmd returns a command for adding a new ruleset.
func NewHuntingRulesetAddCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "add [ruleset name] [rules file]",
		Short: "Add a new ruleset",
		Args:  cobra.ExactArgs(2),

		RunE: func(cmd *cobra.Command, args []string) error {
			client, err := NewAPIClient()
			if err != nil {
				return err
			}
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			rules, err := ReadFile(args[1])
			if err != nil {
				return err
			}
			obj := vt.NewObject("hunting_ruleset")
			obj.SetString("name", args[0])
			obj.SetString("rules", string(rules))

			err = client.PostObject(vt.URL("intelligence/hunting_rulesets"), obj)
			if err != nil {
				return err
			}
			return p.PrintObject(obj)
		},
	}
}

// NewHuntingRulesetCmd returns a new instance of the 'rulesets' command.
func NewHuntingRulesetCmd() *cobra.Command {

	cmd := &cobra.Command{
		Aliases: []string{"rs"},
		Use:     "ruleset [id]...",
		Short:   "Manage hunting rulesets",
		Args:    cobra.MinimumNArgs(1),

		RunE: func(cmd *cobra.Command, args []string) error {
			re, _ := regexp.Compile("\\d+")
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			return p.GetAndPrintObjects("intelligence/hunting_rulesets/%s?relationships=owner,editors", args, re)
		},
	}

	addThreadsFlag(cmd.Flags())

	cmd.AddCommand(NewHuntingRulesetAddCmd())
	cmd.AddCommand(NewHuntingRulesetDeleteCmd())
	cmd.AddCommand(NewHuntingRulesetDisableCmd())
	cmd.AddCommand(NewHuntingRulesetEnableCmd())
	cmd.AddCommand(NewHuntingRulesetListCmd())
	cmd.AddCommand(NewHuntingRulesetRenameCmd())
	cmd.AddCommand(NewHuntingRulesetSetLimitCmd())
	cmd.AddCommand(NewHuntingRulesetUpdateCmd())

	return cmd
}

// NewHuntingCmd returns a new instance of the 'hunting' command.
func NewHuntingCmd() *cobra.Command {

	cmd := &cobra.Command{
		Aliases: []string{"ht"},
		Use:     "hunting [subcommand]",
		Short:   "Manage malware hunting rules and notifications",
	}

	cmd.AddCommand(NewHuntingNotificationCmd())
	cmd.AddCommand(NewHuntingRulesetCmd())

	return cmd
}
<commit_msg>Show owner and editors while listing hunting rulesets.<commit_after>// Copyright © 2017 The VirusTotal CLI authors. 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 cmd

import (
	"bufio"
	"errors"
	"fmt"
	"os"
	"regexp"
	"strconv"
	"sync"

	vt "github.com/VirusTotal/vt-go"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var notificationsDeleteCmdHelp = `Delete hunting notifications.

This command deletes the malware hunting notifications associated to the
currently configured API key.`

// NewHuntingNotificationDeleteCmd returns a command for deleting all hunting
// notifications for the current user.
func NewHuntingNotificationDeleteCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "delete [notification id]...",
		Short: "Delete hunting notifications",
		Long:  notificationsDeleteCmdHelp,
		RunE: func(cmd *cobra.Command, args []string) error {
			deleteAll := viper.GetBool("all")
			deleteTag := viper.GetString("with-tag")
			if len(args) == 0 && !deleteAll && deleteTag == "" {
				return errors.New("Specify notification id or use --all or --with-tag")
			}
			client, err := NewAPIClient()
			if err != nil {
				return err
			}
			if deleteAll {
				_, err = client.Delete(vt.URL("intelligence/hunting_notifications"))
			} else if deleteTag != "" {
				_, err = client.Delete(vt.URL("intelligence/hunting_notifications?tag=%s", deleteTag))
			} else {
				var wg sync.WaitGroup
				for _, arg := range args {
					wg.Add(1)
					go func(notificationID string) {
						url := vt.URL("intelligence/hunting_notifications/%s", notificationID)
						if _, err := client.Delete(url); err != nil {
							fmt.Fprintf(os.Stderr, "%v\n", err)
						}
						wg.Done()
					}(arg)
				}
				wg.Wait()
			}
			return err
		},
	}

	cmd.Flags().BoolP("all", "a", false, "delete all notifications")
	cmd.Flags().StringP("with-tag", "t", "", "delete notifications with a given tag")

	return cmd
}

var notificationsListCmdHelp = `List malware hunting notifications.

This command list the malware hunting notifications associated to the currently
configured API key.`

// NewHuntingNotificationListCmd returns a new instance of the 'notifications list' command.
func NewHuntingNotificationListCmd() *cobra.Command {
	cmd := &cobra.Command{
		Aliases: []string{"ls"},
		Use:     "list",
		Short:   "List notifications",
		Long:    notificationsListCmdHelp,

		RunE: func(cmd *cobra.Command, args []string) error {
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			return p.PrintCollection(vt.URL("intelligence/hunting_notifications?relationships=file"))
		},
	}

	addIncludeExcludeFlags(cmd.Flags())
	addFilterFlag(cmd.Flags())
	addLimitFlag(cmd.Flags())
	addCursorFlag(cmd.Flags())

	cmd.AddCommand(NewHuntingNotificationDeleteCmd())

	return cmd
}

// NewHuntingNotificationCmd returns a new instance of the 'notifications' command.
func NewHuntingNotificationCmd() *cobra.Command {
	cmd := &cobra.Command{
		Aliases: []string{"nt"},
		Use:     "notification [id]...",
		Short:   "Manage malware hunting notifications",
		Args:    cobra.MinimumNArgs(1),

		RunE: func(cmd *cobra.Command, args []string) error {
			re, _ := regexp.Compile("\\d+")
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			return p.GetAndPrintObjects("intelligence/hunting_notifications/%s", args, re)
		},
	}

	addThreadsFlag(cmd.Flags())

	cmd.AddCommand(NewHuntingNotificationListCmd())
	cmd.AddCommand(NewHuntingNotificationDeleteCmd())

	return cmd
}

var rulesetsListCmdHelp = `List malware hunting rulesets.

This command list the malware hunting rulesets associated to the currently
configured API key.`

// NewHuntingRulesetListCmd returns a new instance of the 'rulesets list' command.
func NewHuntingRulesetListCmd() *cobra.Command {
	cmd := &cobra.Command{
		Aliases: []string{"ls"},
		Use:     "list",
		Short:   "List rulesets",
		Long:    rulesetsListCmdHelp,

		RunE: func(cmd *cobra.Command, args []string) error {
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			return p.PrintCollection(vt.URL("intelligence/hunting_rulesets?relationships=owner,editors"))
		},
	}

	addIncludeExcludeFlags(cmd.Flags())
	addFilterFlag(cmd.Flags())
	addLimitFlag(cmd.Flags())
	addCursorFlag(cmd.Flags())
	addIDOnlyFlag(cmd.Flags())

	return cmd
}

func patchRuleset(id, attr string, value interface{}) error {
	client, err := NewAPIClient()
	if err != nil {
		return err
	}
	obj := vt.NewObjectWithID("hunting_ruleset", id)
	obj.Set(attr, value)
	return client.PatchObject(vt.URL("intelligence/hunting_rulesets/%s", id), obj)
}

// NewHuntingRulesetDisableCmd returns a command for disabling a given ruleset.
func NewHuntingRulesetDisableCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "disable [ruleset id]",
		Short: "Disable ruleset",
		Args:  cobra.ExactArgs(1),

		RunE: func(cmd *cobra.Command, args []string) error {
			return patchRuleset(args[0], "enabled", false)
		},
	}
}

// NewHuntingRulesetEnableCmd returns a command for enabling a given ruleset.
func NewHuntingRulesetEnableCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "enable [ruleset id]",
		Short: "Enable ruleset",
		Args:  cobra.ExactArgs(1),

		RunE: func(cmd *cobra.Command, args []string) error {
			return patchRuleset(args[0], "enabled", true)
		},
	}
}

// NewHuntingRulesetRenameCmd returns a command for renaming a given ruleset.
func NewHuntingRulesetRenameCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "rename [ruleset id] [name]",
		Short: "Rename ruleset",
		Args:  cobra.ExactArgs(2),

		RunE: func(cmd *cobra.Command, args []string) error {
			return patchRuleset(args[0], "name", args[1])
		},
	}
}

// NewHuntingRulesetSetLimitCmd returns a command for changing a ruleset's limit.
func NewHuntingRulesetSetLimitCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "setlimit [ruleset id] [limit]",
		Short: "Set ruleset limit",
		Args:  cobra.MinimumNArgs(2),

		RunE: func(cmd *cobra.Command, args []string) error {
			limit, err := strconv.Atoi(args[1])
			if err != nil {
				return fmt.Errorf("invalid limit: %s", args[1])
			}
			return patchRuleset(args[0], "limit", limit)
		},
	}
}

// NewHuntingRulesetUpdateCmd returns a command for updating ruleset's rules.
func NewHuntingRulesetUpdateCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "update [ruleset id] [rules file]",
		Short: "Change the rules for a ruleset.",
		Args:  cobra.MinimumNArgs(2),

		RunE: func(cmd *cobra.Command, args []string) error {
			rules, err := ReadFile(args[1])
			if err != nil {
				return err
			}
			return patchRuleset(args[0], "rules", string(rules))
		},
	}
}

// NewHuntingRulesetDeleteCmd returns a command for deleting a given ruleset.
func NewHuntingRulesetDeleteCmd() *cobra.Command {
	cmd := &cobra.Command{
		Aliases: []string{"del", "rm"},
		Use:     "delete [ruleset id]...",
		Short:   "Delete rulesets",

		RunE: func(cmd *cobra.Command, args []string) error {
			client, err := NewAPIClient()
			if err != nil {
				return err
			}
			deleteAll := viper.GetBool("all")
			if len(args) == 0 && !deleteAll {
				return errors.New("Specify ruleset id or use --all")
			}
			if deleteAll {
				fmt.Print("Enter your VirusTotal username to confirm: ")
				scanner := bufio.NewScanner(os.Stdin)
				scanner.Scan()
				username := scanner.Text()
				_, err = client.Delete(
					vt.URL("intelligence/hunting_rulesets"),
					vt.WithHeader("x-confirm-delete", username))
			} else {
				var wg sync.WaitGroup
				for _, arg := range args {
					wg.Add(1)
					go func(rulesetID string) {
						url := vt.URL("intelligence/hunting_rulesets/%s", rulesetID)
						if _, err := client.Delete(url); err != nil {
							fmt.Fprintf(os.Stderr, "%v\n", err)
						}
						wg.Done()
					}(arg)
				}
				wg.Wait()
			}
			return err
		},
	}

	cmd.Flags().BoolP("all", "a", false, "delete all rulesets")

	return cmd
}

// NewHuntingRulesetAddCmd returns a command for adding a new ruleset.
func NewHuntingRulesetAddCmd() *cobra.Command {
	return &cobra.Command{
		Use:   "add [ruleset name] [rules file]",
		Short: "Add a new ruleset",
		Args:  cobra.ExactArgs(2),

		RunE: func(cmd *cobra.Command, args []string) error {
			client, err := NewAPIClient()
			if err != nil {
				return err
			}
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			rules, err := ReadFile(args[1])
			if err != nil {
				return err
			}
			obj := vt.NewObject("hunting_ruleset")
			obj.SetString("name", args[0])
			obj.SetString("rules", string(rules))

			err = client.PostObject(vt.URL("intelligence/hunting_rulesets"), obj)
			if err != nil {
				return err
			}
			return p.PrintObject(obj)
		},
	}
}

// NewHuntingRulesetCmd returns a new instance of the 'rulesets' command.
func NewHuntingRulesetCmd() *cobra.Command {

	cmd := &cobra.Command{
		Aliases: []string{"rs"},
		Use:     "ruleset [id]...",
		Short:   "Manage hunting rulesets",
		Args:    cobra.MinimumNArgs(1),

		RunE: func(cmd *cobra.Command, args []string) error {
			re, _ := regexp.Compile("\\d+")
			p, err := NewPrinter(cmd)
			if err != nil {
				return err
			}
			return p.GetAndPrintObjects("intelligence/hunting_rulesets/%s?relationships=owner,editors", args, re)
		},
	}

	addThreadsFlag(cmd.Flags())

	cmd.AddCommand(NewHuntingRulesetAddCmd())
	cmd.AddCommand(NewHuntingRulesetDeleteCmd())
	cmd.AddCommand(NewHuntingRulesetDisableCmd())
	cmd.AddCommand(NewHuntingRulesetEnableCmd())
	cmd.AddCommand(NewHuntingRulesetListCmd())
	cmd.AddCommand(NewHuntingRulesetRenameCmd())
	cmd.AddCommand(NewHuntingRulesetSetLimitCmd())
	cmd.AddCommand(NewHuntingRulesetUpdateCmd())

	return cmd
}

// NewHuntingCmd returns a new instance of the 'hunting' command.
func NewHuntingCmd() *cobra.Command {

	cmd := &cobra.Command{
		Aliases: []string{"ht"},
		Use:     "hunting [subcommand]",
		Short:   "Manage malware hunting rules and notifications",
	}

	cmd.AddCommand(NewHuntingNotificationCmd())
	cmd.AddCommand(NewHuntingRulesetCmd())

	return cmd
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strings"

	"github.com/ianremmler/dgrl"
	"github.com/ianremmler/lit"
)

const usage = `usage:

spec: . | all | <id(s)> | (with|without) <field> [<val>]
	'.' indicates the currently open issue

lit [help | usage]            Show usage
lit list <spec>               Show list of issues in spec
lit init                      Initialize new issue tracker
lit new                       Create new issue
lit id <spec>                 List ids in spec
lit show <spec>               Show issues in spec
lit set <field> <val> <spec>  Set issue field
lit edit <spec>               Edit issues in spec
lit close <spec>              Close issues in spec
lit reopen <spec>             Reopen closed issues in spec`

const (
	// id, stat, priority, assigned, tags, summary
	listFmt = "%-8.8s %-1.1s %-1.1s %-6.6s %-16.16s %s"
)

var (
	args    = os.Args[1:]
	it      = lit.New()
	listHdr = fmt.Sprintf(listFmt, "id", "c", "p", "assign", "tags", "summary")
)

func main() {
	log.SetFlags(0)
	log.SetPrefix("lit: ")

	cmd := ""
	if len(args) > 0 {
		cmd = args[0]
		args = args[1:]
	}
	switch cmd {
	case "", "-h", "-help", "--help", "help", "-u", "-usage", "--usage", "usage":
		usageCmd()
	case "list":
		listCmd()
	case "init":
		initCmd()
	case "new":
		newCmd()
	case "id":
		idCmd()
	case "show":
		showCmd()
	case "set":
		setCmd()
	case "edit":
		editCmd()
	case "close", "reopen":
		closeCmd(cmd)
	default:
		log.Fatalln(cmd + " is not a valid command\n\n" + usage)
	}
}

func usageCmd() {
	fmt.Println(usage)
}

func initCmd() {
	if it.InitFile() != nil {
		log.Fatalln("init: Error initializing issue tracker")
	}
}

func newCmd() {
	id, err := it.NewIssue()
	checkErr("new", err)
	err = it.AppendIssues()
	checkErr("new", err)
	fmt.Println(id)
}

func listCmd() {
	loadIssues("list")
	fmt.Println(listHdr)
	for _, id := range specIds(args) {
		issue := it.Issue(id)
		if issue != nil {
			fmt.Println(listInfo(id, issue))
		}
	}
}

func idCmd() {
	loadIssues("id")
	for _, id := range specIds(args) {
		if it.Issue(id) != nil {
			fmt.Println(id)
		}
	}
}

func showCmd() {
	loadIssues("show")
	for _, id := range specIds(args) {
		fmt.Println(it.Issue(id))
	}
}

func setCmd() {
	if len(args) < 3 {
		log.Fatalln("set: You must specify a field, and value, and spec")
	}
	key, val, spec := args[0], args[1], args[2:]
	loadIssues("set")
	stamp := lit.Stamp()
	for _, id := range specIds(spec) {
		issue := it.Issue(id)
		if issue == nil {
			log.Printf("set: Error finding issue %s\n", id)
			continue
		}
		ok := lit.Set(issue, key, val)
		ok = ok && lit.Set(issue, "updated", stamp)
		if !ok {
			log.Printf("set: Error updating fields in %s\n", id)
			continue
		}
	}
	storeIssues("set")
}

func editCmd() {
	editor := os.Getenv("VISUAL")
	if editor == "" {
		editor = os.Getenv("EDITOR")
	}
	if editor == "" {
		log.Fatalln("VISUAL or EDITOR environment variable must be set")
	}
	if len(args) < 1 {
		log.Fatalln("edit: You must specify a spec to edit")
	}

	loadIssues("edit")

	// create temp file
	tempFile, err := ioutil.TempFile("", "lit-")
	checkErr("edit", err)
	filename := tempFile.Name()

	// load issue content into temp file
	ids := specIds(args)
	for _, id := range ids {
		issue := it.Issue(id)
		if issue == nil {
			log.Printf("edit: Error finding issue %s\n")
			continue
		}
		if !lit.Set(issue, "updated", lit.Stamp()) {
			log.Printf("edit: Error setting update time for %s\n")
			continue
		}
		fmt.Fprintln(tempFile, issue)
	}
	tempFile.Close()

	// get original file state
	origStat, err := os.Stat(filename)
	checkErr("edit", err)

	// launch editor
	cmd := exec.Command(editor, filename)
	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
	err = cmd.Run()
	checkErr("edit", err)

	// get updated file state, compare to original
	newStat, err := os.Stat(filename)
	checkErr("edit", err)
	if newStat.ModTime() == origStat.ModTime() {
		log.Fatalln("edit: File unchanged")
	}

	// parse issue from temp file
	tempFile, err = os.Open(filename)
	checkErr("edit", err)
	edited := dgrl.NewParser().Parse(tempFile)
	tempFile.Close()
	if edited == nil {
		log.Fatalln("edit: Error parsing file")
	}

	// update issues if we find a match
	didUpdate := false
	for _, id := range ids {
		issue := it.Issue(id)
		if issue == nil {
			// already printed error, so don't repeat here
			continue
		}
		for _, node := range edited.Kids() {
			if node.Type() == dgrl.BranchType && strings.HasPrefix(node.Key(), id) {
				if editedIssue, ok := node.(*dgrl.Branch); ok {
					*issue = *editedIssue
					didUpdate = true
					break
				}
			}
		}
	}
	if !didUpdate {
		log.Fatalln("edit: Did not update anything")
	}

	storeIssues("edit")
}

func closeCmd(cmd string) {
	if len(args) < 1 {
		log.Fatalf("%s: You must specify a spec\n", cmd)
	}
	loadIssues(cmd)
	stamp := lit.Stamp()
	for _, id := range specIds(args) {
		issue := it.Issue(id)
		if issue == nil {
			log.Printf("%s: Error finding issue %s\n", cmd, id)
			continue
		}
		closedStamp := ""
		if cmd == "close" {
			closedStamp = stamp
		}
		ok := lit.Set(issue, "closed", closedStamp)
		ok = ok && lit.Set(issue, "updated", stamp)
		if !ok {
			log.Printf("%s: Error updating fields for %s\n", cmd, id)
			continue
		}
	}
	storeIssues(cmd)
}

func listInfo(id string, issue *dgrl.Branch) string {
	status := " "
	closed, _ := lit.Get(issue, "closed")
	if len(closed) > 0 {
		status = "*"
	}
	tags, _ := lit.Get(issue, "tags")
	if len(tags) > 13 {
		tags = tags[:10] + "..."
	}
	priority, _ := lit.Get(issue, "priority")
	assigned, _ := lit.Get(issue, "assigned")
	summary, _ := lit.Get(issue, "summary")
	return fmt.Sprintf(listFmt, id, status, priority, assigned, tags, summary)
}

func matchIds(kv []string, doesMatch bool) []string {
	key, val := "", ""
	if len(kv) > 0 {
		key = kv[0]
	}
	if len(kv) > 1 {
		val = kv[1]
	}
	return it.Match(key, val, doesMatch)
}

func compareIds(kv []string, isLess bool) []string {
	key, val := "", ""
	if len(kv) > 0 {
		key = kv[0]
	}
	if len(kv) > 1 {
		val = kv[1]
	}
	return it.Compare(key, val, isLess)
}

func specIds(args []string) []string {
	ids := []string{}
	switch {
	case len(args) == 0:
		return ids
	case args[0] == "with":
		ids = matchIds(args[1:], true)
	case args[0] == "without":
		ids = matchIds(args[1:], false)
	case args[0] == "less":
		ids = compareIds(args[1:], true)
	case args[0] == "greater":
		ids = compareIds(args[1:], false)
	case args[0] == "all":
		ids = it.IssueIds()
	default:
		ids = args
	}
	return ids
}

func loadIssues(cmd string) {
	err := it.Load()
	checkErr(cmd, err)
}

func storeIssues(cmd string) {
	err := it.Store()
	checkErr(cmd, err)
}

func checkErr(cmd string, err error) {
	if err != nil {
		log.Fatalf("%s: %s\n", cmd, err)
	}
}
<commit_msg>Added comment command.<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strings"

	"github.com/ianremmler/dgrl"
	"github.com/ianremmler/lit"
)

const usage = `usage:

spec: . | all | <id(s)> | (with|without) <field> [<val>]
	'.' indicates the currently open issue

lit [help | usage]            Show usage
lit list <spec>               Show list of issues in spec
lit init                      Initialize new issue tracker
lit new                       Create new issue
lit id <spec>                 List ids in spec
lit show <spec>               Show issues in spec
lit set <field> <val> <spec>  Set issue field
lit comment <id> [<comment>]  Add issue comment (launches editor if no comment given)
lit edit <spec>               Edit issues in spec
lit close <spec>              Close issues in spec
lit reopen <spec>             Reopen closed issues in spec`

const (
	// id, stat, priority, assigned, tags, summary
	listFmt = "%-8.8s %-1.1s %-1.1s %-6.6s %-16.16s %s"
)

var (
	args    = os.Args[1:]
	it      = lit.New()
	listHdr = fmt.Sprintf(listFmt, "id", "c", "p", "assign", "tags", "summary")
)

func main() {
	log.SetFlags(0)
	log.SetPrefix("lit: ")

	cmd := ""
	if len(args) > 0 {
		cmd = args[0]
		args = args[1:]
	}
	switch cmd {
	case "", "-h", "-help", "--help", "help", "-u", "-usage", "--usage", "usage":
		usageCmd()
	case "list":
		listCmd()
	case "init":
		initCmd()
	case "new":
		newCmd()
	case "id":
		idCmd()
	case "show":
		showCmd()
	case "set":
		setCmd()
	case "comment":
		commentCmd()
	case "edit":
		editCmd()
	case "close", "reopen":
		closeCmd(cmd)
	default:
		log.Fatalln(cmd + " is not a valid command\n\n" + usage)
	}
}

func usageCmd() {
	fmt.Println(usage)
}

func initCmd() {
	if it.InitFile() != nil {
		log.Fatalln("init: Error initializing issue tracker")
	}
}

func newCmd() {
	id, err := it.NewIssue()
	checkErr("new", err)
	err = it.AppendIssues()
	checkErr("new", err)
	fmt.Println(id)
}

func listCmd() {
	loadIssues("list")
	fmt.Println(listHdr)
	for _, id := range specIds(args) {
		issue := it.Issue(id)
		if issue != nil {
			fmt.Println(listInfo(id, issue))
		}
	}
}

func idCmd() {
	loadIssues("id")
	for _, id := range specIds(args) {
		if it.Issue(id) != nil {
			fmt.Println(id)
		}
	}
}

func showCmd() {
	loadIssues("show")
	for _, id := range specIds(args) {
		fmt.Println(it.Issue(id))
	}
}

func setCmd() {
	if len(args) < 3 {
		log.Fatalln("set: You must specify a field, and value, and spec")
	}
	key, val, spec := args[0], args[1], args[2:]
	loadIssues("set")
	stamp := lit.Stamp()
	for _, id := range specIds(spec) {
		issue := it.Issue(id)
		if issue == nil {
			log.Printf("set: Error finding issue %s\n", id)
			continue
		}
		ok := lit.Set(issue, key, val)
		ok = ok && lit.Set(issue, "updated", stamp)
		if !ok {
			log.Printf("set: Error updating fields in %s\n", id)
			continue
		}
	}
	storeIssues("set")
}

func editCmd() {
	editor := getEditor()
	if editor == "" {
		log.Fatalln("edit: VISUAL or EDITOR environment variable must be set\n")
	}
	if len(args) < 1 {
		log.Fatalln("edit: You must specify a spec to edit")
	}

	loadIssues("edit")

	// create temp file
	tempFile, err := ioutil.TempFile("", "lit-")
	checkErr("edit", err)
	filename := tempFile.Name()

	// load issue content into temp file
	ids := specIds(args)
	for _, id := range ids {
		issue := it.Issue(id)
		if issue == nil {
			log.Printf("edit: Error finding issue %s\n")
			continue
		}
		if !lit.Set(issue, "updated", lit.Stamp()) {
			log.Printf("edit: Error setting update time for %s\n")
			continue
		}
		fmt.Fprintln(tempFile, issue)
	}
	tempFile.Close()

	// get original file state
	origStat, err := os.Stat(filename)
	checkErr("edit", err)

	// launch editor
	cmd := exec.Command(editor, filename)
	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
	err = cmd.Run()
	checkErr("edit", err)

	// get updated file state, compare to original
	newStat, err := os.Stat(filename)
	checkErr("edit", err)
	if newStat.ModTime() == origStat.ModTime() {
		log.Fatalln("edit: File unchanged")
	}

	// parse issue from temp file
	tempFile, err = os.Open(filename)
	checkErr("edit", err)
	edited := dgrl.NewParser().Parse(tempFile)
	tempFile.Close()
	if edited == nil {
		log.Fatalln("edit: Error parsing file")
	}

	// update issues if we find a match
	didUpdate := false
	for _, id := range ids {
		issue := it.Issue(id)
		if issue == nil {
			// already printed error, so don't repeat here
			continue
		}
		for _, node := range edited.Kids() {
			if node.Type() == dgrl.BranchType && strings.HasPrefix(node.Key(), id) {
				if editedIssue, ok := node.(*dgrl.Branch); ok {
					*issue = *editedIssue
					didUpdate = true
					break
				}
			}
		}
	}
	if !didUpdate {
		log.Fatalln("edit: Did not update anything")
	}

	storeIssues("edit")
}

func closeCmd(cmd string) {
	if len(args) < 1 {
		log.Fatalf("%s: You must specify a spec\n", cmd)
	}
	loadIssues(cmd)
	stamp := lit.Stamp()
	for _, id := range specIds(args) {
		issue := it.Issue(id)
		if issue == nil {
			log.Printf("%s: Error finding issue %s\n", cmd, id)
			continue
		}
		closedStamp := ""
		if cmd == "close" {
			closedStamp = stamp
		}
		ok := lit.Set(issue, "closed", closedStamp)
		ok = ok && lit.Set(issue, "updated", stamp)
		if !ok {
			log.Printf("%s: Error updating fields for %s\n", cmd, id)
			continue
		}
	}
	storeIssues(cmd)
}

func commentCmd() {
	if len(args) < 1 {
		log.Fatalln("comment: You must specify an issue to comment on")
	}
	id := args[0]
	loadIssues("comment")
	issue := it.Issue(id)
	if issue == nil {
		log.Fatalf("comment: Error finding issue %s\n", id)
	}
	comment := ""
	if len(args) > 1 {
		comment = args[1]
	} else {
		comment = editComment()
	}
	commentBranch := dgrl.NewBranch(lit.Stamp())
	commentBranch.Append(dgrl.NewLongLeaf("", comment))
	issue.Append(commentBranch)
	storeIssues("comment")
}

func editComment() string {
	editor := getEditor()
	if editor == "" {
		log.Fatalln("comment: VISUAL or EDITOR environment variable must be set\n")
	}
	// create temp file
	tempFile, err := ioutil.TempFile("", "lit-")
	checkErr("comment", err)
	filename := tempFile.Name()

	// get original file state
	origStat, err := os.Stat(filename)
	checkErr("comment", err)

	// launch editor
	cmd := exec.Command(editor, filename)
	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
	err = cmd.Run()
	checkErr("comment", err)

	// get updated file state, compare to original
	newStat, err := os.Stat(filename)
	checkErr("comment", err)
	if newStat.ModTime() == origStat.ModTime() {
		log.Fatalln("comment: File unchanged")
	}

	// read comment from file
	commentData, err := ioutil.ReadFile(filename)
	checkErr("comment", err)
	return string(commentData)
}

func listInfo(id string, issue *dgrl.Branch) string {
	status := " "
	closed, _ := lit.Get(issue, "closed")
	if len(closed) > 0 {
		status = "*"
	}
	tags, _ := lit.Get(issue, "tags")
	if len(tags) > 13 {
		tags = tags[:10] + "..."
	}
	priority, _ := lit.Get(issue, "priority")
	assigned, _ := lit.Get(issue, "assigned")
	summary, _ := lit.Get(issue, "summary")
	return fmt.Sprintf(listFmt, id, status, priority, assigned, tags, summary)
}

func matchIds(kv []string, doesMatch bool) []string {
	key, val := "", ""
	if len(kv) > 0 {
		key = kv[0]
	}
	if len(kv) > 1 {
		val = kv[1]
	}
	return it.Match(key, val, doesMatch)
}

func compareIds(kv []string, isLess bool) []string {
	key, val := "", ""
	if len(kv) > 0 {
		key = kv[0]
	}
	if len(kv) > 1 {
		val = kv[1]
	}
	return it.Compare(key, val, isLess)
}

func specIds(args []string) []string {
	ids := []string{}
	switch {
	case len(args) == 0:
		return ids
	case args[0] == "with":
		ids = matchIds(args[1:], true)
	case args[0] == "without":
		ids = matchIds(args[1:], false)
	case args[0] == "less":
		ids = compareIds(args[1:], true)
	case args[0] == "greater":
		ids = compareIds(args[1:], false)
	case args[0] == "all":
		ids = it.IssueIds()
	default:
		ids = args
	}
	return ids
}

func loadIssues(cmd string) {
	err := it.Load()
	checkErr(cmd, err)
}

func storeIssues(cmd string) {
	err := it.Store()
	checkErr(cmd, err)
}

func checkErr(cmd string, err error) {
	if err != nil {
		log.Fatalf("%s: %s\n", cmd, err)
	}
}

func getEditor() string {
	editor := os.Getenv("VISUAL")
	if editor == "" {
		editor = os.Getenv("EDITOR")
	}
	return editor
}
<|endoftext|>


<commit_before>package pipelines

import (
	"io"
	"io/ioutil"
	"path/filepath"

	"github.com/osteele/gojekyll/config"
	"github.com/osteele/gojekyll/helpers"
	"github.com/osteele/gojekyll/liquid"
	"github.com/osteele/gojekyll/templates"
	"github.com/russross/blackfriday"
)

// PipelineInterface applies transformations to a document.
type PipelineInterface interface {
	ApplyLayout(string, []byte, templates.VariableMap) ([]byte, error)
	OutputExt(pathname string) string
	Render(io.Writer, []byte, string, templates.VariableMap) ([]byte, error)
}

// Pipeline applies a rendering transformation to a file.
type Pipeline struct {
	config       config.Config
	liquidEngine liquid.Engine
	pageSupplier PageSupplier
	sassTempDir  string
	sourceDir    string
}

// PipelineOptions configures a pipeline.
type PipelineOptions struct {
	UseRemoteLiquidEngine bool
}

// PageSupplier tells a pipeline how to resolve link tags.
type PageSupplier interface {
	FilenameURLs() map[string]string
	RelativeFilenameToURL(string) (string, bool)
}

// NewPipeline makes a rendering pipeline.
func NewPipeline(sourceDir string, c config.Config, pageSupplier PageSupplier, o PipelineOptions) (*Pipeline, error) {
	p := Pipeline{config: c, pageSupplier: pageSupplier, sourceDir: sourceDir}
	engine, err := p.makeLiquidEngine(o)
	if err != nil {
		return nil, err
	}
	p.liquidEngine = engine
	if err := p.CopySassFileIncludes(); err != nil {
		return nil, err
	}
	return &p, nil
}

// OutputExt returns the output extension.
func (p *Pipeline) OutputExt(pathname string) string {
	return p.config.OutputExt(pathname)
}

// Render returns nil iff it wrote to the writer
func (p *Pipeline) Render(w io.Writer, b []byte, filename string, e templates.VariableMap) ([]byte, error) {
	if p.config.IsSassPath(filename) {
		return nil, p.WriteSass(w, b)
	}
	b, err := p.renderTemplate(b, e, filename)
	if err != nil {
		return nil, err
	}
	if p.config.IsMarkdown(filename) {
		b = blackfriday.MarkdownCommon(b)
	}
	return b, nil
}

func (p *Pipeline) renderTemplate(b []byte, e templates.VariableMap, filename string) ([]byte, error) {
	b, err := p.liquidEngine.ParseAndRender(b, e)
	if err != nil {
		switch err := err.(type) {
		case *liquid.RenderError:
			if err.Filename == "" {
				err.Filename = filename
			}
			return nil, err
		default:
			return nil, helpers.PathError(err, "Liquid Error", filename)
		}
	}
	return b, err
}

// ApplyLayout applies the named layout to bytes.
func (p *Pipeline) ApplyLayout(name string, b []byte, e templates.VariableMap) ([]byte, error) {
	for name != "" {
		var lfm templates.VariableMap
		t, err := p.FindLayout(name, &lfm)
		if err != nil {
			return nil, err
		}
		le := templates.MergeVariableMaps(e, templates.VariableMap{
			"content": string(b),
			"layout":  lfm,
		})
		b, err = t.Render(le)
		if err != nil {
			return nil, err
		}
		name = lfm.String("layout", "")
	}
	return b, nil
}

func (p *Pipeline) makeLocalLiquidEngine() liquid.Engine {
	engine := liquid.NewLocalWrapperEngine()
	engine.LinkTagHandler(p.pageSupplier.RelativeFilenameToURL)
	includeHandler := func(name string, w io.Writer, scope map[string]interface{}) error {
		filename := filepath.Join(p.sourceDir, p.config.IncludesDir, name)
		template, err := ioutil.ReadFile(filename)
		if err != nil {
			return err
		}
		text, err := engine.ParseAndRender(template, scope)
		if err != nil {
			return err
		}
		_, err = w.Write(text)
		return err
	}
	engine.IncludeHandler(includeHandler)
	return engine
}

func (p *Pipeline) makeLiquidClient() (engine liquid.RemoteEngine, err error) {
	engine, err = liquid.NewRPCClientEngine(liquid.DefaultServer)
	if err != nil {
		return
	}
	err = engine.FileURLMap(p.pageSupplier.FilenameURLs())
	if err != nil {
		return
	}
	err = engine.IncludeDirs([]string{filepath.Join(p.sourceDir, p.config.IncludesDir)})
	return
}

func (p *Pipeline) makeLiquidEngine(o PipelineOptions) (liquid.Engine, error) {
	if o.UseRemoteLiquidEngine {
		return p.makeLiquidClient()
	}
	return p.makeLocalLiquidEngine(), nil
}
<commit_msg>Display filename for liquid render errors<commit_after>package pipelines

import (
	"io"
	"io/ioutil"
	"path/filepath"

	"github.com/osteele/gojekyll/config"
	"github.com/osteele/gojekyll/helpers"
	"github.com/osteele/gojekyll/liquid"
	"github.com/osteele/gojekyll/templates"
	"github.com/russross/blackfriday"
)

// PipelineInterface applies transformations to a document.
type PipelineInterface interface {
	ApplyLayout(string, []byte, templates.VariableMap) ([]byte, error)
	OutputExt(pathname string) string
	Render(io.Writer, []byte, string, templates.VariableMap) ([]byte, error)
}

// Pipeline applies a rendering transformation to a file.
type Pipeline struct {
	config       config.Config
	liquidEngine liquid.Engine
	pageSupplier PageSupplier
	sassTempDir  string
	sourceDir    string
}

// PipelineOptions configures a pipeline.
type PipelineOptions struct {
	UseRemoteLiquidEngine bool
}

// PageSupplier tells a pipeline how to resolve link tags.
type PageSupplier interface {
	FilenameURLs() map[string]string
	RelativeFilenameToURL(string) (string, bool)
}

// NewPipeline makes a rendering pipeline.
func NewPipeline(sourceDir string, c config.Config, pageSupplier PageSupplier, o PipelineOptions) (*Pipeline, error) {
	p := Pipeline{config: c, pageSupplier: pageSupplier, sourceDir: sourceDir}
	engine, err := p.makeLiquidEngine(o)
	if err != nil {
		return nil, err
	}
	p.liquidEngine = engine
	if err := p.CopySassFileIncludes(); err != nil {
		return nil, err
	}
	return &p, nil
}

// OutputExt returns the output extension.
func (p *Pipeline) OutputExt(pathname string) string {
	return p.config.OutputExt(pathname)
}

// Render returns nil iff it wrote to the writer
func (p *Pipeline) Render(w io.Writer, b []byte, filename string, e templates.VariableMap) ([]byte, error) {
	if p.config.IsSassPath(filename) {
		return nil, p.WriteSass(w, b)
	}
	b, err := p.renderTemplate(b, e, filename)
	if err != nil {
		return nil, err
	}
	if p.config.IsMarkdown(filename) {
		b = blackfriday.MarkdownCommon(b)
	}
	return b, nil
}

func (p *Pipeline) renderTemplate(b []byte, e templates.VariableMap, filename string) ([]byte, error) {
	b, err := p.liquidEngine.ParseAndRender(b, e)
	if err != nil {
		switch err := err.(type) {
		case *liquid.RenderError:
			if err.Filename == "" {
				err.Filename = filename
			}
			return nil, err
		default:
			return nil, helpers.PathError(err, "Liquid Error", filename)
		}
	}
	return b, err
}

// ApplyLayout applies the named layout to the data.
func (p *Pipeline) ApplyLayout(name string, data []byte, e templates.VariableMap) ([]byte, error) {
	for name != "" {
		var lfm templates.VariableMap
		t, err := p.FindLayout(name, &lfm)
		if err != nil {
			return nil, err
		}
		le := templates.MergeVariableMaps(e, templates.VariableMap{
			"content": string(data),
			"layout":  lfm,
		})
		data, err = t.Render(le)
		if err != nil {
			return nil, helpers.PathError(err, "render template", name)
		}
		name = lfm.String("layout", "")
	}
	return data, nil
}

func (p *Pipeline) makeLocalLiquidEngine() liquid.Engine {
	engine := liquid.NewLocalWrapperEngine()
	engine.LinkTagHandler(p.pageSupplier.RelativeFilenameToURL)
	includeHandler := func(name string, w io.Writer, scope map[string]interface{}) error {
		filename := filepath.Join(p.sourceDir, p.config.IncludesDir, name)
		template, err := ioutil.ReadFile(filename)
		if err != nil {
			return err
		}
		text, err := engine.ParseAndRender(template, scope)
		if err != nil {
			return err
		}
		_, err = w.Write(text)
		return err
	}
	engine.IncludeHandler(includeHandler)
	return engine
}

func (p *Pipeline) makeLiquidClient() (engine liquid.RemoteEngine, err error) {
	engine, err = liquid.NewRPCClientEngine(liquid.DefaultServer)
	if err != nil {
		return
	}
	err = engine.FileURLMap(p.pageSupplier.FilenameURLs())
	if err != nil {
		return
	}
	err = engine.IncludeDirs([]string{filepath.Join(p.sourceDir, p.config.IncludesDir)})
	return
}

func (p *Pipeline) makeLiquidEngine(o PipelineOptions) (liquid.Engine, error) {
	if o.UseRemoteLiquidEngine {
		return p.makeLiquidClient()
	}
	return p.makeLocalLiquidEngine(), nil
}
<|endoftext|>