<commit_before>package vxsv

import (
	"fmt"

	"github.com/nsf/termbox-go"
)

const MaxCellWidth = 20
const CellSeparator = " │ "
const RowIndicator = '»'

const HiliteFg = termbox.ColorBlack | termbox.AttrBold
const HiliteBg = termbox.ColorWhite

const HelpText = `Key Bindings:

vxsv is a modal viewer, meaning that actions are only valid in certain
contexts.

DEFAULT MODE
============

  Ctrl l          refresh screen
  Ctrl a          pan to beginning of line
  Ctrl e          pan to end of line
  <arrows> / hjkl scroll / pan control
  Ctrl r, /       enter ** FILTER MODE **
  [SPACE]         scroll down one screen
  C               enter ** COLUMN SELECT MODE **
  R               enter ** ROW SELECT MODE **
  G               scroll to bottom
  g               scroll to top
  Z               toggle zebra stripes
  X               toggle expanding all columns
  ?               show this help dialog
  Ctrl c          exit

COLUMN SELECT MODE
==================

  <arrows> / hl   select column
  Ctrl a          select first column
  Ctrl e          select last column
  <               sort by column, ascending
  >               sort by column, descending
  w               toggle collapsing this column
  x               toggle expanding this column
  a               line up decimal points for floats in this column
  .               toggle pinning this column
  !               pipe column into shell, see ** SHELL COMMAND MODE **
  |               like '!', but replace column with output
  u               filter rows to unique values for this column
  s               show summary statistics for this column
  [ESC], Ctrl g   return to ** DEFAULT MODE **

FILTER MODE
===========

  Filter expressions can take two forms:

    1. Column filter: "column_name CMP value"
       * CMP is one of (==, !=, <, <=, >, >=, ~, !~)
       * Display rows where the given column's value for the
         row makes the comparison evaluate to true.
       * Read '~' and '!~' as "matches" and "doesn't match",
         respectively.

    2. Row filter: "filter_string"
       * Display rows where any column in the row matches the
         filter string.

  [ESC], Ctrl g   clear filter and return to previous mode
  Ctrl w, Ctrl u  clear entered filter expression
  [ENTER]         apply filter and return to previous mode

ROW SELECT MODE
===============

  <arrows> / jk   select row
  [ENTER]         pop open expanded row dialog.

SHELL COMMAND MODE
==================
  Pipe selected column's values into an external shell process.

  Each value is printed on a new line, which will work with most standard Unix
  pipe commands.

  Using '|' will replace the column with the output of the shell command, and
  '!' will show the output of the command as a popup.

  '|' Examples:
     jq -c '.foo.bar.baz' -       # extract json values from col with jq
     awk '{ println $1 * 100 }'   # multiply current col in each row by 100
     sed 's/1/true/g'             # simple remapping of values

  '!' Examples:
     sort | uniq -c | sort -r     # show unique values, ordered by frequency
     grep -v 'foo'                # filter out column values containing 'foo'

  [ESC], Ctrl g   exit shell command mode and revert to original values
  Ctrl w, Ctrl u  clear entered shell command
  [ENTER]         run shell command and return to previous mode
`

type UI struct {
	handlers         []ModeHandler
	rowIdx           int // Selection control
	offsetX, offsetY int // Pan control
	filter           Filter
	filterMatches    []int
	zebraStripe      bool
	allExpanded      bool
	columns          []Column
	rows             [][]string
}

type Column struct {
	Name string

	// Display options
	Display   ColumnDisplay
	Pinned    bool
	Highlight bool
	Width     int

	Modified        bool
	ModifiedValues  []string
	ModifiedCommand string
}

type TabularData struct {
	Columns []Column
	Rows    [][]string
}

type ColumnDisplay int

const (
	ColumnDefault = iota
	ColumnCollapsed
	ColumnExpanded

	// TODO: Move this to the Modified attribute
	ColumnAligned
)

func (c *Column) toggleDisplay(mode ColumnDisplay) {
	if c.Display == mode {
		c.Display = ColumnDefault
	} else {
		c.Display = mode
	}
}

func (c Column) displayWidth() int {
	switch c.Display {
	case ColumnAligned:
		return clamp(c.Width, 16, c.Width)
	case ColumnCollapsed:
		return 1
	case ColumnExpanded:
		return c.Width
	case ColumnDefault:
		return clamp(c.Width, 1, MaxCellWidth)
	}

	panic("TODO: this is a bug")
}

// It is so dumb that go doesn't have this
func clamp(val, lo, hi int) int {
	if val <= lo {
		return lo
	} else if val >= hi {
		return hi
	}

	return val
}
func NewUI(data *TabularData) *UI {
	filterMatches := make([]int, len(data.Rows))

	for i, col := range data.Columns {
		col.Display = ColumnDefault
		col.Pinned = false
		col.Highlight = false
		col.Modified = false

		// Last column should open expanded
		if i == len(data.Columns)-1 {
			col.Display = ColumnExpanded
		}
	}

	for i := range data.Rows {
		filterMatches[i] = i
	}

	ui := &UI{
		offsetX:       0,
		offsetY:       0,
		rows:          data.Rows,
		columns:       data.Columns,
		zebraStripe:   false,
		allExpanded:   false,
		filter:        EmptyFilter{},
		filterMatches: filterMatches,
	}

	ui.switchToDefault()

	return ui
}

func (ui *UI) Init() error {
	if err := termbox.Init(); err != nil {
		return err
	}

	termbox.SetInputMode(termbox.InputEsc)
	return nil
}

func (ui *UI) Loop() {
	defer termbox.Close()

	ui.repaint()

eventloop:
	for {
		switch ev := termbox.PollEvent(); ev.Type {
		case termbox.EventKey:
			if ev.Key == termbox.KeyCtrlC {
				break eventloop
			}

			ui.activeHandler().HandleKey(ev)
		}

		ui.repaint()
	}
}

// Return indices of rows to display
func (ui *UI) filterRows() {
	rows := make([]int, 0, 100)

	for i := 0; i < len(ui.rows); i++ {
		if ui.filter.Matches(ui.getRow(i)) {
			rows = append(rows, i)
		}
	}

	ui.filterMatches = rows
}

func (ui *UI) repaint() {
	termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
	termbox.HideCursor()
	_, vh := ui.viewSize()

	const coldef = termbox.ColorDefault

	ui.writeColumns(-ui.offsetX, 0)

	for i := 0; i < vh; i++ {
		if i+ui.offsetY < len(ui.filterMatches) {
			row := ui.getRow(ui.filterMatches[i+ui.offsetY])
			ui.writeRow(-ui.offsetX, i+1, row)
		} else {
			writeLine(0, i+1, termbox.ColorDefault|termbox.AttrBold, termbox.ColorDefault, "~")
		}
	}

	ui.activeHandler().Repaint()
	termbox.Flush()
}

func (ui *UI) viewSize() (int, int) {
	width, height := termbox.Size()
	pinnedWidth := ui.pinnedWidth()

	return width - pinnedWidth, height - 2
}

func (ui *UI) pinnedWidth() (width int) {
	for _, col := range ui.columns {
		if col.Pinned {
			width += col.displayWidth()
			width += len(CellSeparator)
		}
	}

	return width
}

func (ui *UI) columnOffset(colIdx int) (offset int, width int) {
	offset = 0

	for i, col := range ui.columns {
		// Pinned columns should always be visible
		if i == colIdx {
			if !col.Pinned {
				break
			}

			return 0, col.Width
		}

		if !col.Pinned {
			width = col.displayWidth()
			offset += width
			offset += len(CellSeparator)
		}
	}

	return offset, width
}

func (ui *UI) recomputeColumnWidth(colIdx int) {
	width := len(ui.columns[colIdx].Name)

	for _, idx := range ui.filterMatches {
		row := ui.getRow(idx)
		if len(row[colIdx]) > width {
			width = len(row[colIdx])
		}
	}

	ui.columns[colIdx].Width = width
}

// Find the first visually displayed column
func (ui *UI) findFirstColumn() int {
	for i, col := range ui.columns {
		if col.Pinned {
			return i
		}
	}

	// If there are no pinned columns, just return first
	return 0
}

func (ui *UI) findNextColumn(current, direction int) int {
	isPinned := ui.columns[current].Pinned

	// if pinned, find the next pinned col, or vice versa for unpinned
	for i := current + direction; i >= 0 && i < len(ui.columns); i += direction {
		if ui.columns[i].Pinned == isPinned {
			return i
		}
	}

	// Don't fall off the end (would cause confusing behavior)
	if isPinned && direction < 0 || !isPinned && direction > 0 {
		return current
	}

	// we're crossing the pinned/unpinned boundary
	i := 0
	if direction < 0 {
		i = len(ui.columns) - 1
	}
	for ; i >= 0 && i < len(ui.columns); i += direction {
		if (isPinned && !ui.columns[i].Pinned) || (!isPinned && ui.columns[i].Pinned) {
			return i
		}
	}

	return current
}

func (ui *UI) activeHandler() ModeHandler {
	if len(ui.handlers) == 0 {
		ui.switchToDefault()
	}
	return ui.handlers[len(ui.handlers)-1]
}

func (ui *UI) switchToDefault() {
	ui.handlers = []ModeHandler{&HandlerDefault{ui}}
}

func (ui *UI) pushHandler(h ModeHandler) {
	ui.handlers = append(ui.handlers, h)
}

func (ui *UI) popHandler() {
	if len(ui.handlers) > 1 {
		ui.handlers = ui.handlers[:len(ui.handlers)-1]
	} else {
		ui.switchToDefault()
	}
}

func (ui *UI) pushErrorPopup(msg string, err error) {
	errMsg := fmt.Sprintf("Error: %s\n\n%v", msg, err)
	ui.pushHandler(NewPopup(ui, errMsg))
}

func (ui *UI) getRow(idx int) []string {
	row := make([]string, len(ui.columns))

	if idx < 0 || idx >= len(ui.rows) {
		panic(fmt.Errorf("Overflowed row bounds: %d [0, %d]", idx, len(ui.rows)))
	}

	origRow := ui.rows[idx]

	for i, col := range ui.columns {
		if col.Modified {
			row[i] = col.ModifiedValues[idx]
		} else {
			row[i] = origRow[i]
		}
	}

	return row
}
<commit_msg>Enable zebra stripes by default<commit_after>package vxsv

import (
	"fmt"

	"github.com/nsf/termbox-go"
)

const MaxCellWidth = 20
const CellSeparator = " │ "
const RowIndicator = '»'

const HiliteFg = termbox.ColorBlack | termbox.AttrBold
const HiliteBg = termbox.ColorWhite

const HelpText = `Key Bindings:

vxsv is a modal viewer, meaning that actions are only valid in certain
contexts.

DEFAULT MODE
============

  Ctrl l          refresh screen
  Ctrl a          pan to beginning of line
  Ctrl e          pan to end of line
  <arrows> / hjkl scroll / pan control
  Ctrl r, /       enter ** FILTER MODE **
  [SPACE]         scroll down one screen
  C               enter ** COLUMN SELECT MODE **
  R               enter ** ROW SELECT MODE **
  G               scroll to bottom
  g               scroll to top
  Z               toggle zebra stripes
  X               toggle expanding all columns
  ?               show this help dialog
  Ctrl c          exit

COLUMN SELECT MODE
==================

  <arrows> / hl   select column
  Ctrl a          select first column
  Ctrl e          select last column
  <               sort by column, ascending
  >               sort by column, descending
  w               toggle collapsing this column
  x               toggle expanding this column
  a               line up decimal points for floats in this column
  .               toggle pinning this column
  !               pipe column into shell, see ** SHELL COMMAND MODE **
  |               like '!', but replace column with output
  u               filter rows to unique values for this column
  s               show summary statistics for this column
  [ESC], Ctrl g   return to ** DEFAULT MODE **

FILTER MODE
===========

  Filter expressions can take two forms:

    1. Column filter: "column_name CMP value"
       * CMP is one of (==, !=, <, <=, >, >=, ~, !~)
       * Display rows where the given column's value for the
         row makes the comparison evaluate to true.
       * Read '~' and '!~' as "matches" and "doesn't match",
         respectively.

    2. Row filter: "filter_string"
       * Display rows where any column in the row matches the
         filter string.

  [ESC], Ctrl g   clear filter and return to previous mode
  Ctrl w, Ctrl u  clear entered filter expression
  [ENTER]         apply filter and return to previous mode

ROW SELECT MODE
===============

  <arrows> / jk   select row
  [ENTER]         pop open expanded row dialog.

SHELL COMMAND MODE
==================
  Pipe selected column's values into an external shell process.

  Each value is printed on a new line, which will work with most standard Unix
  pipe commands.

  Using '|' will replace the column with the output of the shell command, and
  '!' will show the output of the command as a popup.

  '|' Examples:
     jq -c '.foo.bar.baz' -       # extract json values from col with jq
     awk '{ println $1 * 100 }'   # multiply current col in each row by 100
     sed 's/1/true/g'             # simple remapping of values

  '!' Examples:
     sort | uniq -c | sort -r     # show unique values, ordered by frequency
     grep -v 'foo'                # filter out column values containing 'foo'

  [ESC], Ctrl g   exit shell command mode and revert to original values
  Ctrl w, Ctrl u  clear entered shell command
  [ENTER]         run shell command and return to previous mode
`

type UI struct {
	handlers         []ModeHandler
	rowIdx           int // Selection control
	offsetX, offsetY int // Pan control
	filter           Filter
	filterMatches    []int
	zebraStripe      bool
	allExpanded      bool
	columns          []Column
	rows             [][]string
}

type Column struct {
	Name string

	// Display options
	Display   ColumnDisplay
	Pinned    bool
	Highlight bool
	Width     int

	Modified        bool
	ModifiedValues  []string
	ModifiedCommand string
}

type TabularData struct {
	Columns []Column
	Rows    [][]string
}

type ColumnDisplay int

const (
	ColumnDefault = iota
	ColumnCollapsed
	ColumnExpanded

	// TODO: Move this to the Modified attribute
	ColumnAligned
)

func (c *Column) toggleDisplay(mode ColumnDisplay) {
	if c.Display == mode {
		c.Display = ColumnDefault
	} else {
		c.Display = mode
	}
}

func (c Column) displayWidth() int {
	switch c.Display {
	case ColumnAligned:
		return clamp(c.Width, 16, c.Width)
	case ColumnCollapsed:
		return 1
	case ColumnExpanded:
		return c.Width
	case ColumnDefault:
		return clamp(c.Width, 1, MaxCellWidth)
	}

	panic("TODO: this is a bug")
}

// It is so dumb that go doesn't have this
func clamp(val, lo, hi int) int {
	if val <= lo {
		return lo
	} else if val >= hi {
		return hi
	}

	return val
}
func NewUI(data *TabularData) *UI {
	filterMatches := make([]int, len(data.Rows))

	for i, col := range data.Columns {
		col.Display = ColumnDefault
		col.Pinned = false
		col.Highlight = false
		col.Modified = false

		// Last column should open expanded
		if i == len(data.Columns)-1 {
			col.Display = ColumnExpanded
		}
	}

	for i := range data.Rows {
		filterMatches[i] = i
	}

	ui := &UI{
		offsetX:       0,
		offsetY:       0,
		rows:          data.Rows,
		columns:       data.Columns,
		zebraStripe:   true,
		allExpanded:   false,
		filter:        EmptyFilter{},
		filterMatches: filterMatches,
	}

	ui.switchToDefault()

	return ui
}

func (ui *UI) Init() error {
	if err := termbox.Init(); err != nil {
		return err
	}

	termbox.SetInputMode(termbox.InputEsc)
	return nil
}

func (ui *UI) Loop() {
	defer termbox.Close()

	ui.repaint()

eventloop:
	for {
		switch ev := termbox.PollEvent(); ev.Type {
		case termbox.EventKey:
			if ev.Key == termbox.KeyCtrlC {
				break eventloop
			}

			ui.activeHandler().HandleKey(ev)
		}

		ui.repaint()
	}
}

// Return indices of rows to display
func (ui *UI) filterRows() {
	rows := make([]int, 0, 100)

	for i := 0; i < len(ui.rows); i++ {
		if ui.filter.Matches(ui.getRow(i)) {
			rows = append(rows, i)
		}
	}

	ui.filterMatches = rows
}

func (ui *UI) repaint() {
	termbox.Clear(termbox.ColorDefault, termbox.ColorDefault)
	termbox.HideCursor()
	_, vh := ui.viewSize()

	const coldef = termbox.ColorDefault

	ui.writeColumns(-ui.offsetX, 0)

	for i := 0; i < vh; i++ {
		if i+ui.offsetY < len(ui.filterMatches) {
			row := ui.getRow(ui.filterMatches[i+ui.offsetY])
			ui.writeRow(-ui.offsetX, i+1, row)
		} else {
			writeLine(0, i+1, termbox.ColorDefault|termbox.AttrBold, termbox.ColorDefault, "~")
		}
	}

	ui.activeHandler().Repaint()
	termbox.Flush()
}

func (ui *UI) viewSize() (int, int) {
	width, height := termbox.Size()
	pinnedWidth := ui.pinnedWidth()

	return width - pinnedWidth, height - 2
}

func (ui *UI) pinnedWidth() (width int) {
	for _, col := range ui.columns {
		if col.Pinned {
			width += col.displayWidth()
			width += len(CellSeparator)
		}
	}

	return width
}

func (ui *UI) columnOffset(colIdx int) (offset int, width int) {
	offset = 0

	for i, col := range ui.columns {
		// Pinned columns should always be visible
		if i == colIdx {
			if !col.Pinned {
				break
			}

			return 0, col.Width
		}

		if !col.Pinned {
			width = col.displayWidth()
			offset += width
			offset += len(CellSeparator)
		}
	}

	return offset, width
}

func (ui *UI) recomputeColumnWidth(colIdx int) {
	width := len(ui.columns[colIdx].Name)

	for _, idx := range ui.filterMatches {
		row := ui.getRow(idx)
		if len(row[colIdx]) > width {
			width = len(row[colIdx])
		}
	}

	ui.columns[colIdx].Width = width
}

// Find the first visually displayed column
func (ui *UI) findFirstColumn() int {
	for i, col := range ui.columns {
		if col.Pinned {
			return i
		}
	}

	// If there are no pinned columns, just return first
	return 0
}

func (ui *UI) findNextColumn(current, direction int) int {
	isPinned := ui.columns[current].Pinned

	// if pinned, find the next pinned col, or vice versa for unpinned
	for i := current + direction; i >= 0 && i < len(ui.columns); i += direction {
		if ui.columns[i].Pinned == isPinned {
			return i
		}
	}

	// Don't fall off the end (would cause confusing behavior)
	if isPinned && direction < 0 || !isPinned && direction > 0 {
		return current
	}

	// we're crossing the pinned/unpinned boundary
	i := 0
	if direction < 0 {
		i = len(ui.columns) - 1
	}
	for ; i >= 0 && i < len(ui.columns); i += direction {
		if (isPinned && !ui.columns[i].Pinned) || (!isPinned && ui.columns[i].Pinned) {
			return i
		}
	}

	return current
}

func (ui *UI) activeHandler() ModeHandler {
	if len(ui.handlers) == 0 {
		ui.switchToDefault()
	}
	return ui.handlers[len(ui.handlers)-1]
}

func (ui *UI) switchToDefault() {
	ui.handlers = []ModeHandler{&HandlerDefault{ui}}
}

func (ui *UI) pushHandler(h ModeHandler) {
	ui.handlers = append(ui.handlers, h)
}

func (ui *UI) popHandler() {
	if len(ui.handlers) > 1 {
		ui.handlers = ui.handlers[:len(ui.handlers)-1]
	} else {
		ui.switchToDefault()
	}
}

func (ui *UI) pushErrorPopup(msg string, err error) {
	errMsg := fmt.Sprintf("Error: %s\n\n%v", msg, err)
	ui.pushHandler(NewPopup(ui, errMsg))
}

func (ui *UI) getRow(idx int) []string {
	row := make([]string, len(ui.columns))

	if idx < 0 || idx >= len(ui.rows) {
		panic(fmt.Errorf("Overflowed row bounds: %d [0, %d]", idx, len(ui.rows)))
	}

	origRow := ui.rows[idx]

	for i, col := range ui.columns {
		if col.Modified {
			row[i] = col.ModifiedValues[idx]
		} else {
			row[i] = origRow[i]
		}
	}

	return row
}
<|endoftext|>


<commit_before>// +build !windows,!darwin,!plan9

// 16 february 2014

package ui

import (
	"time"
)

// #cgo pkg-config: gtk+-3.0
// #include "gtk_unix.h"
import "C"

type sysData struct {
	cSysData

	widget		*C.GtkWidget
	container		*C.GtkWidget	// for moving
	pulse		chan bool		// for sysData.progressPulse()
}

type classData struct {
	make	func() *C.GtkWidget
	makeAlt	func() *C.GtkWidget
	setText	func(widget *C.GtkWidget, text string)
	text		func(widget *C.GtkWidget) string
	append	func(widget *C.GtkWidget, text string)
	insert	func(widget *C.GtkWidget, index int, text string)
	selected	func(widget *C.GtkWidget) int
	selMulti	func(widget *C.GtkWidget) []int
	smtexts	func(widget *C.GtkWidget) []string
	delete	func(widget *C.GtkWidget, index int)
	len		func(widget *C.GtkWidget) int
	// ...
	signals	callbackMap
	child		func(widget *C.GtkWidget) *C.GtkWidget
	childsigs	callbackMap
}

var classTypes = [nctypes]*classData{
	c_window:		&classData{
		make:		gtk_window_new,
		setText:		gtk_window_set_title,
		text:			gtk_window_get_title,
		signals:		callbackMap{
			"delete-event":		window_delete_event_callback,
			"configure-event":	window_configure_event_callback,
		},
	},
	c_button:			&classData{
		make:		gtk_button_new,
		setText:		gtk_button_set_label,
		text:			gtk_button_get_label,
		signals:		callbackMap{
			"clicked":		button_clicked_callback,
		},
	},
	c_checkbox:		&classData{
		make:		gtk_check_button_new,
		setText:		gtk_button_set_label,
		text:			gtk_button_get_label,
	},
	c_combobox:		&classData{
		make:		gtk_combo_box_text_new,
		makeAlt:		gtk_combo_box_text_new_with_entry,
		text:			gtk_combo_box_text_get_active_text,
		append:		gtk_combo_box_text_append_text,
		insert:		gtk_combo_box_text_insert_text,
		selected:		gtk_combo_box_get_active,
		delete:		gtk_combo_box_text_remove,
		len:			gtkComboBoxLen,
	},
	c_lineedit:		&classData{
		make:		gtk_entry_new,
		makeAlt:		gtkPasswordEntryNew,
		setText:		gtk_entry_set_text,
		text:			gtk_entry_get_text,
	},
	c_label:			&classData{
		make:		gtk_label_new,
		setText:		gtk_label_set_text,
		text:			gtk_label_get_text,
	},
	c_listbox:			&classData{
		make:		gListboxNewSingle,
		makeAlt:		gListboxNewMulti,
		text:			gListboxText,
		append:		gListboxAppend,
		insert:		gListboxInsert,
		selMulti:		gListboxSelectedMulti,
		smtexts:		gListboxSelMultiTexts,
		delete:		gListboxDelete,
		len:			gListboxLen,
	},
	c_progressbar:		&classData{
		make:		gtk_progress_bar_new,
	},
	c_area:			&classData{
		make:		gtkAreaNew,
		child:		gtkAreaGetControl,
		childsigs:		callbackMap{
			"draw":				area_draw_callback,
			"button-press-event":	area_button_press_event_callback,
			"button-release-event":	area_button_release_event_callback,
			"motion-notify-event":	area_motion_notify_event_callback,
			"key-press-event":		area_key_press_event_callback,
			"key-release-event":	area_key_release_event_callback,
		},
	},
}

func (s *sysData) make(window *sysData) error {
	ct := classTypes[s.ctype]
	ret := make(chan *C.GtkWidget)
	defer close(ret)
	uitask <- func() {
		if s.alternate {
			ret <- ct.makeAlt()
			return
		}
		ret <- ct.make()
	}
	s.widget = <-ret
	if window == nil {
		uitask <- func() {
			fixed := gtkNewWindowLayout()
			gtk_container_add(s.widget, fixed)
			// TODO return the container before assigning the signals?
			for signame, sigfunc := range ct.signals {
				g_signal_connect(s.widget, signame, sigfunc, s)
			}
			ret <- fixed
		}
		s.container = <-ret
	} else {
		s.container = window.container
		uitask <- func() {
			gtkAddWidgetToLayout(s.container, s.widget)
			for signame, sigfunc := range ct.signals {
				g_signal_connect(s.widget, signame, sigfunc, s)
			}
			if ct.child != nil {
				child := ct.child(s.widget)
				for signame, sigfunc := range ct.childsigs {
					g_signal_connect(child, signame, sigfunc, s)
				}
			}
			ret <- nil
		}
		<-ret
	}
	return nil
}

// used for Windows; nothing special needed elsewhere
func (s *sysData) firstShow() error {
	s.show()
	return nil
}

func (s *sysData) show() {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		gtk_widget_show(s.widget)
		ret <- struct{}{}
	}
	<-ret
}

func (s *sysData) hide() {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		gtk_widget_hide(s.widget)
		ret <- struct{}{}
	}
	<-ret
}

func (s *sysData) setText(text string) {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		classTypes[s.ctype].setText(s.widget, text)
		ret <- struct{}{}
	}
	<-ret
}

func (s *sysData) setRect(x int, y int, width int, height int, winheight int) error {
	gtkMoveWidgetInLayout(s.container, s.widget, x, y)
	gtk_widget_set_size_request(s.widget, width, height)
	return nil
}

func (s *sysData) isChecked() bool {
	ret := make(chan bool)
	defer close(ret)
	uitask <- func() {
		ret <- gtk_toggle_button_get_active(s.widget)
	}
	return <-ret
}

func (s *sysData) text() string {
	ret := make(chan string)
	defer close(ret)
	uitask <- func() {
		ret <- classTypes[s.ctype].text(s.widget)
	}
	return <-ret
}

func (s *sysData) append(what string) {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		classTypes[s.ctype].append(s.widget, what)
		ret <- struct{}{}
	}
	<-ret
}

func (s *sysData) insertBefore(what string, before int) {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		classTypes[s.ctype].insert(s.widget, before, what)
		ret <- struct{}{}
	}
	<-ret
}

func (s *sysData) selectedIndex() int {
	ret := make(chan int)
	defer close(ret)
	uitask <- func() {
		ret <- classTypes[s.ctype].selected(s.widget)
	}
	return <-ret
}

func (s *sysData) selectedIndices() []int {
	ret := make(chan []int)
	defer close(ret)
	uitask <- func() {
		ret <- classTypes[s.ctype].selMulti(s.widget)
	}
	return <-ret
}

func (s *sysData) selectedTexts() []string {
	ret := make(chan []string)
	defer close(ret)
	uitask <- func() {
		ret <- classTypes[s.ctype].smtexts(s.widget)
	}
	return <-ret
}

func (s *sysData) setWindowSize(width int, height int) error {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		// does not take window geometry into account (and cannot, since the window manager won't give that info away)
		// thanks to TingPing in irc.gimp.net/#gtk+
		gtk_window_resize(s.widget, width, height)
		ret <- struct{}{}
	}
	<-ret
	return nil
}

func (s *sysData) delete(index int) {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		classTypes[s.ctype].delete(s.widget, index)
		ret <- struct{}{}
	}
	<-ret
}

// With GTK+, we must manually pulse the indeterminate progressbar ourselves. This goroutine does that.
func (s *sysData) progressPulse() {
	pulse := func() {
		ret := make(chan struct{})
		defer close(ret)
		uitask <- func() {
			gtk_progress_bar_pulse(s.widget)
			ret <- struct{}{}
		}
		<-ret
	}

	var ticker *time.Ticker
	var tickchan <-chan time.Time

	// the pulse rate used by Zenity (https://git.gnome.org/browse/zenity/tree/src/progress.c#n69 for blob cbffe08e8337ba1375a0ac7210eff5a2e4313bb8)
	const pulseRate = 100 * time.Millisecond

	for {
		select {
		case start := <-s.pulse:
			if start {
				ticker = time.NewTicker(pulseRate)
				tickchan = ticker.C
				pulse()			// start the pulse animation now, not 100ms later
			} else {
				if ticker != nil {
					ticker.Stop()
				}
				ticker = nil
				tickchan = nil
				s.pulse <- true		// notify sysData.setProgress()
			}
		case <-tickchan:
			pulse()
		}
	}
}

func (s *sysData) setProgress(percent int) {
	if s.pulse == nil {
		s.pulse = make(chan bool)
		go s.progressPulse()
	}
	if percent == -1 {
		s.pulse <- true
		return
	}
	s.pulse <- false
	<-s.pulse			// wait for sysData.progressPulse() to register that
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		gtk_progress_bar_set_fraction(s.widget, percent)
		ret <- struct{}{}
	}
	<-ret
}

func (s *sysData) len() int {
	ret := make(chan int)
	defer close(ret)
	uitask <- func() {
		ret <- classTypes[s.ctype].len(s.widget)
	}
	return <-ret
}

func (s *sysData) setAreaSize(width int, height int) {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		c := gtkAreaGetControl(s.widget)
		gtk_widget_set_size_request(c, width, height)
		ret <- struct{}{}
	}
	<-ret
}

func (s *sysData) selectIndex(index int) {
	// TODO not yet implemented on Unix (added for Mac only right now)
}

func (s *sysData) selectIndices(indices []int) {
	// TODO not yet implemented on Windows (added for Mac only right now)
}
<commit_msg>Removed TODO about returning container before connecting signals in sysdata_unix.go; nothing will actually be sent until we show the widgets anyway.<commit_after>// +build !windows,!darwin,!plan9

// 16 february 2014

package ui

import (
	"time"
)

// #cgo pkg-config: gtk+-3.0
// #include "gtk_unix.h"
import "C"

type sysData struct {
	cSysData

	widget		*C.GtkWidget
	container		*C.GtkWidget	// for moving
	pulse		chan bool		// for sysData.progressPulse()
}

type classData struct {
	make	func() *C.GtkWidget
	makeAlt	func() *C.GtkWidget
	setText	func(widget *C.GtkWidget, text string)
	text		func(widget *C.GtkWidget) string
	append	func(widget *C.GtkWidget, text string)
	insert	func(widget *C.GtkWidget, index int, text string)
	selected	func(widget *C.GtkWidget) int
	selMulti	func(widget *C.GtkWidget) []int
	smtexts	func(widget *C.GtkWidget) []string
	delete	func(widget *C.GtkWidget, index int)
	len		func(widget *C.GtkWidget) int
	// ...
	signals	callbackMap
	child		func(widget *C.GtkWidget) *C.GtkWidget
	childsigs	callbackMap
}

var classTypes = [nctypes]*classData{
	c_window:		&classData{
		make:		gtk_window_new,
		setText:		gtk_window_set_title,
		text:			gtk_window_get_title,
		signals:		callbackMap{
			"delete-event":		window_delete_event_callback,
			"configure-event":	window_configure_event_callback,
		},
	},
	c_button:			&classData{
		make:		gtk_button_new,
		setText:		gtk_button_set_label,
		text:			gtk_button_get_label,
		signals:		callbackMap{
			"clicked":		button_clicked_callback,
		},
	},
	c_checkbox:		&classData{
		make:		gtk_check_button_new,
		setText:		gtk_button_set_label,
		text:			gtk_button_get_label,
	},
	c_combobox:		&classData{
		make:		gtk_combo_box_text_new,
		makeAlt:		gtk_combo_box_text_new_with_entry,
		text:			gtk_combo_box_text_get_active_text,
		append:		gtk_combo_box_text_append_text,
		insert:		gtk_combo_box_text_insert_text,
		selected:		gtk_combo_box_get_active,
		delete:		gtk_combo_box_text_remove,
		len:			gtkComboBoxLen,
	},
	c_lineedit:		&classData{
		make:		gtk_entry_new,
		makeAlt:		gtkPasswordEntryNew,
		setText:		gtk_entry_set_text,
		text:			gtk_entry_get_text,
	},
	c_label:			&classData{
		make:		gtk_label_new,
		setText:		gtk_label_set_text,
		text:			gtk_label_get_text,
	},
	c_listbox:			&classData{
		make:		gListboxNewSingle,
		makeAlt:		gListboxNewMulti,
		text:			gListboxText,
		append:		gListboxAppend,
		insert:		gListboxInsert,
		selMulti:		gListboxSelectedMulti,
		smtexts:		gListboxSelMultiTexts,
		delete:		gListboxDelete,
		len:			gListboxLen,
	},
	c_progressbar:		&classData{
		make:		gtk_progress_bar_new,
	},
	c_area:			&classData{
		make:		gtkAreaNew,
		child:		gtkAreaGetControl,
		childsigs:		callbackMap{
			"draw":				area_draw_callback,
			"button-press-event":	area_button_press_event_callback,
			"button-release-event":	area_button_release_event_callback,
			"motion-notify-event":	area_motion_notify_event_callback,
			"key-press-event":		area_key_press_event_callback,
			"key-release-event":	area_key_release_event_callback,
		},
	},
}

func (s *sysData) make(window *sysData) error {
	ct := classTypes[s.ctype]
	ret := make(chan *C.GtkWidget)
	defer close(ret)
	uitask <- func() {
		if s.alternate {
			ret <- ct.makeAlt()
			return
		}
		ret <- ct.make()
	}
	s.widget = <-ret
	if window == nil {
		uitask <- func() {
			fixed := gtkNewWindowLayout()
			gtk_container_add(s.widget, fixed)
			for signame, sigfunc := range ct.signals {
				g_signal_connect(s.widget, signame, sigfunc, s)
			}
			ret <- fixed
		}
		s.container = <-ret
	} else {
		s.container = window.container
		uitask <- func() {
			gtkAddWidgetToLayout(s.container, s.widget)
			for signame, sigfunc := range ct.signals {
				g_signal_connect(s.widget, signame, sigfunc, s)
			}
			if ct.child != nil {
				child := ct.child(s.widget)
				for signame, sigfunc := range ct.childsigs {
					g_signal_connect(child, signame, sigfunc, s)
				}
			}
			ret <- nil
		}
		<-ret
	}
	return nil
}

// used for Windows; nothing special needed elsewhere
func (s *sysData) firstShow() error {
	s.show()
	return nil
}

func (s *sysData) show() {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		gtk_widget_show(s.widget)
		ret <- struct{}{}
	}
	<-ret
}

func (s *sysData) hide() {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		gtk_widget_hide(s.widget)
		ret <- struct{}{}
	}
	<-ret
}

func (s *sysData) setText(text string) {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		classTypes[s.ctype].setText(s.widget, text)
		ret <- struct{}{}
	}
	<-ret
}

func (s *sysData) setRect(x int, y int, width int, height int, winheight int) error {
	gtkMoveWidgetInLayout(s.container, s.widget, x, y)
	gtk_widget_set_size_request(s.widget, width, height)
	return nil
}

func (s *sysData) isChecked() bool {
	ret := make(chan bool)
	defer close(ret)
	uitask <- func() {
		ret <- gtk_toggle_button_get_active(s.widget)
	}
	return <-ret
}

func (s *sysData) text() string {
	ret := make(chan string)
	defer close(ret)
	uitask <- func() {
		ret <- classTypes[s.ctype].text(s.widget)
	}
	return <-ret
}

func (s *sysData) append(what string) {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		classTypes[s.ctype].append(s.widget, what)
		ret <- struct{}{}
	}
	<-ret
}

func (s *sysData) insertBefore(what string, before int) {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		classTypes[s.ctype].insert(s.widget, before, what)
		ret <- struct{}{}
	}
	<-ret
}

func (s *sysData) selectedIndex() int {
	ret := make(chan int)
	defer close(ret)
	uitask <- func() {
		ret <- classTypes[s.ctype].selected(s.widget)
	}
	return <-ret
}

func (s *sysData) selectedIndices() []int {
	ret := make(chan []int)
	defer close(ret)
	uitask <- func() {
		ret <- classTypes[s.ctype].selMulti(s.widget)
	}
	return <-ret
}

func (s *sysData) selectedTexts() []string {
	ret := make(chan []string)
	defer close(ret)
	uitask <- func() {
		ret <- classTypes[s.ctype].smtexts(s.widget)
	}
	return <-ret
}

func (s *sysData) setWindowSize(width int, height int) error {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		// does not take window geometry into account (and cannot, since the window manager won't give that info away)
		// thanks to TingPing in irc.gimp.net/#gtk+
		gtk_window_resize(s.widget, width, height)
		ret <- struct{}{}
	}
	<-ret
	return nil
}

func (s *sysData) delete(index int) {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		classTypes[s.ctype].delete(s.widget, index)
		ret <- struct{}{}
	}
	<-ret
}

// With GTK+, we must manually pulse the indeterminate progressbar ourselves. This goroutine does that.
func (s *sysData) progressPulse() {
	pulse := func() {
		ret := make(chan struct{})
		defer close(ret)
		uitask <- func() {
			gtk_progress_bar_pulse(s.widget)
			ret <- struct{}{}
		}
		<-ret
	}

	var ticker *time.Ticker
	var tickchan <-chan time.Time

	// the pulse rate used by Zenity (https://git.gnome.org/browse/zenity/tree/src/progress.c#n69 for blob cbffe08e8337ba1375a0ac7210eff5a2e4313bb8)
	const pulseRate = 100 * time.Millisecond

	for {
		select {
		case start := <-s.pulse:
			if start {
				ticker = time.NewTicker(pulseRate)
				tickchan = ticker.C
				pulse()			// start the pulse animation now, not 100ms later
			} else {
				if ticker != nil {
					ticker.Stop()
				}
				ticker = nil
				tickchan = nil
				s.pulse <- true		// notify sysData.setProgress()
			}
		case <-tickchan:
			pulse()
		}
	}
}

func (s *sysData) setProgress(percent int) {
	if s.pulse == nil {
		s.pulse = make(chan bool)
		go s.progressPulse()
	}
	if percent == -1 {
		s.pulse <- true
		return
	}
	s.pulse <- false
	<-s.pulse			// wait for sysData.progressPulse() to register that
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		gtk_progress_bar_set_fraction(s.widget, percent)
		ret <- struct{}{}
	}
	<-ret
}

func (s *sysData) len() int {
	ret := make(chan int)
	defer close(ret)
	uitask <- func() {
		ret <- classTypes[s.ctype].len(s.widget)
	}
	return <-ret
}

func (s *sysData) setAreaSize(width int, height int) {
	ret := make(chan struct{})
	defer close(ret)
	uitask <- func() {
		c := gtkAreaGetControl(s.widget)
		gtk_widget_set_size_request(c, width, height)
		ret <- struct{}{}
	}
	<-ret
}

func (s *sysData) selectIndex(index int) {
	// TODO not yet implemented on Unix (added for Mac only right now)
}

func (s *sysData) selectIndices(indices []int) {
	// TODO not yet implemented on Windows (added for Mac only right now)
}
<|endoftext|>


<commit_before>package system

import (
	"errors"
	"io"
	"os"
	"path/filepath"
	"regexp"

	"github.com/b1101/systemgo/lib/handle"
	"github.com/b1101/systemgo/lib/state"
	"github.com/b1101/systemgo/unit/service"
	"github.com/b1101/systemgo/unit/target"
)

var (
	constructors = map[string]func(io.Reader) (Supervisable, error){
		"service": func(r io.Reader) (Supervisable, error) {
			return service.New(r)
		},
		"target": func(r io.Reader) (Supervisable, error) {
			return target.New(r)
		},
	}
)

// ParseAll searches for all specifications in given paths and returns a map of *Unit's parsed
func ParseAll(paths ...string) (map[string]*Unit, error) {
	units := map[string]*Unit{}
	for _, path := range paths {
		if err := filepath.Walk(path, func(fpath string, finfo os.FileInfo, _err error) error {
			switch {
			case _err != nil:
				return _err
			case fpath == path, finfo.IsDir() && !is("wants", fpath):
				return nil
			}

			u := NewUnit()
			u.name = finfo.Name()
			units[finfo.Name()] = u

			file, err := os.Open(fpath)
			defer file.Close()
			if err != nil {
				u.Log(err.Error())
				return nil
			}

			if sup, err := matchAndCreate(finfo.Name(), file); err != nil {
				u.Log(err.Error())
				u.stats.loaded = state.Error
			} else {
				u.Supervisable = sup
			}
			u.stats.path = fpath
			return nil

		}); err != nil {
			handle.Err(err)
			continue
		}
	}
	return units, nil
}

//
// TODO: Come back when reload() is ready
//

//// ParseOne searches for specification of unit name in given paths, parses and returns a Supervisable
//func ParseOne(name string, paths ...string) (Supervisable, error) {
//	for _, path := range paths {
//		file, err := os.Open(path + "/" + name)
//		defer file.Close()
//		if err != nil {
//			if err != os.ErrNotExist {
//				handle.Err(err)
//			}
//			continue
//		}
//
//		u, err := matchAndCreate(name, file)
//
//		return &Unit{}
//	}
//	return nil, ErrNotExist // TODO: replace by not found, when lib is ready
//}

// matchAndCreate determines the unit type by name, creates and returns a Supervisable of that type
func matchAndCreate(name string, definition io.Reader) (Supervisable, error) {
	for suffix, constructor := range constructors {
		if is(suffix, name) {
			return constructor(definition)
		}
	}
	return nil, errors.New(name + " does not match any known unit type")
}

// is checks if filename extension matches given type
func is(typ, name string) bool {
	switch match, err := regexp.MatchString(".*[.]"+typ, name); {
	case err != nil:
		handle.Err(err)
		fallthrough
	case !match:
		return false
	default:
		return true
	}
}
<commit_msg>parse: use strings.HasSuffix instead of is()<commit_after>package system

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

	"github.com/b1101/systemgo/lib/handle"
	"github.com/b1101/systemgo/lib/state"
	"github.com/b1101/systemgo/unit/service"
	"github.com/b1101/systemgo/unit/target"
)

var (
	constructors = map[string]func(io.Reader) (Supervisable, error){
		".service": func(r io.Reader) (Supervisable, error) {
			return service.New(r)
		},
		".target": func(r io.Reader) (Supervisable, error) {
			return target.New(r)
		},
	}
)

// ParseAll searches for all specifications in given paths and returns a map of *Unit's parsed
func ParseAll(paths ...string) (map[string]*Unit, error) {
	units := map[string]*Unit{}
	for _, path := range paths {
		if err := filepath.Walk(path, func(fpath string, finfo os.FileInfo, _err error) error {
			switch {
			case _err != nil:
				return _err
			case fpath == path, finfo.IsDir() && !strings.HasSuffix(fpath, ".wants"):
				return nil
			}

			u := NewUnit()
			u.name = finfo.Name()
			units[finfo.Name()] = u

			file, err := os.Open(fpath)
			defer file.Close()
			if err != nil {
				u.Log(err.Error())
				return nil
			}

			if sup, err := matchAndCreate(finfo.Name(), file); err != nil {
				u.Log(err.Error())
				u.stats.loaded = state.Error
			} else {
				u.Supervisable = sup
			}
			u.stats.path = fpath
			return nil

		}); err != nil {
			handle.Err(err)
			continue
		}
	}
	return units, nil
}

//
// TODO: Come back when reload() is ready
//

//// ParseOne searches for specification of unit name in given paths, parses and returns a Supervisable
//func ParseOne(name string, paths ...string) (Supervisable, error) {
//	for _, path := range paths {
//		file, err := os.Open(path + "/" + name)
//		defer file.Close()
//		if err != nil {
//			if err != os.ErrNotExist {
//				handle.Err(err)
//			}
//			continue
//		}
//
//		u, err := matchAndCreate(name, file)
//
//		return &Unit{}
//	}
//	return nil, ErrNotExist // TODO: replace by not found, when lib is ready
//}

// matchAndCreate determines the unit type by name, creates and returns a Supervisable of that type
func matchAndCreate(name string, definition io.Reader) (Supervisable, error) {
	for suffix, constructor := range constructors {
		if strings.HasSuffix(name, suffix) {
			return constructor(definition)
		}
	}
	return nil, errors.New(name + " does not match any known unit type")
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/bwmarrin/discordgo"
)

// for !en
func handleEnglish(s *discordgo.Session, m *discordgo.MessageCreate, tokens []string) error {

	channel, _ := s.Channel(m.ChannelID)
	guild, err := s.Guild(channel.GuildID)
	if err != nil {
		fmt.Println(err)
		return err
	}

	settings := getSetting(guild.ID)
	if !settings.Translation && !isOwner(m.Author.ID) {
		return errors.New("not allowed")
	}

	query := strings.Join(tokens, " ")
	query = strings.TrimSpace(query)

	resp, err := translate("en", query)
	if err != nil {
		return err
	}

	_, err = s.ChannelMessageSend(channel.ID, fmt.Sprintf("```%s```", resp))

	return err
}

// for !ja
func handleJapanese(s *discordgo.Session, m *discordgo.MessageCreate, tokens []string) error {

	channel, _ := s.Channel(m.ChannelID)
	guild, err := s.Guild(channel.GuildID)
	if err != nil {
		fmt.Println(err)
		return err
	}

	settings := getSetting(guild.ID)
	if !settings.Translation && !isOwner(m.Author.ID) {
		return errors.New("not allowed")
	}

	query := strings.Join(tokens, " ")
	query = strings.TrimSpace(query)

	resp, err := translate("ja", query)
	if err != nil {
		return err
	}

	_, err = s.ChannelMessageSend(channel.ID, fmt.Sprintf("```%s```", resp))

	return err
}

func translate(t, q string) (string, error) {
	url := fmt.Sprintf("https://tensei.moe/api/v1/translate?q=%s&t=%s", url.QueryEscape(q), t)

	resp, err := http.Get(url)
	if err != nil || resp.StatusCode == http.StatusNotFound {
		resp.Body.Close()
		return "", err
	}

	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", errors.New("error translating")
	}

	rc, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	ns := strings.TrimSpace(string(rc))

	if ns == "" {
		return "", errors.New("empty response")
	}
	return ns, nil
}
<commit_msg>replace newlines<commit_after>package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"

	"github.com/bwmarrin/discordgo"
)

// for !en
func handleEnglish(s *discordgo.Session, m *discordgo.MessageCreate, tokens []string) error {

	channel, _ := s.Channel(m.ChannelID)
	guild, err := s.Guild(channel.GuildID)
	if err != nil {
		fmt.Println(err)
		return err
	}

	settings := getSetting(guild.ID)
	if !settings.Translation && !isOwner(m.Author.ID) {
		return errors.New("not allowed")
	}

	query := strings.Join(tokens, " ")
	query = strings.TrimSpace(query)

	resp, err := translate("en", query)
	if err != nil {
		return err
	}

	_, err = s.ChannelMessageSend(channel.ID, fmt.Sprintf("```%s```", resp))

	return err
}

// for !ja
func handleJapanese(s *discordgo.Session, m *discordgo.MessageCreate, tokens []string) error {

	channel, _ := s.Channel(m.ChannelID)
	guild, err := s.Guild(channel.GuildID)
	if err != nil {
		fmt.Println(err)
		return err
	}

	settings := getSetting(guild.ID)
	if !settings.Translation && !isOwner(m.Author.ID) {
		return errors.New("not allowed")
	}

	query := strings.Join(tokens, " ")
	query = strings.TrimSpace(query)

	resp, err := translate("ja", query)
	if err != nil {
		return err
	}

	_, err = s.ChannelMessageSend(channel.ID, fmt.Sprintf("```%s```", resp))

	return err
}

func translate(t, q string) (string, error) {
	url := fmt.Sprintf("https://tensei.moe/api/v1/translate?q=%s&t=%s", url.QueryEscape(strings.Replace(q, "\n", " ", -1)), t)

	resp, err := http.Get(url)
	if err != nil || resp.StatusCode == http.StatusNotFound {
		resp.Body.Close()
		return "", err
	}

	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", errors.New("error translating")
	}

	rc, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	ns := strings.TrimSpace(string(rc))

	if ns == "" {
		return "", errors.New("empty response")
	}
	return ns, nil
}
<|endoftext|>


<commit_before>// Package treeprint provides a simple ASCII tree composing tool.
package treeprint

import (
	"bytes"
	"fmt"
	"io"
	"reflect"
)

type Value interface{}
type MetaValue interface{}

// Tree represents a tree structure with leaf-nodes and branch-nodes.
type Tree interface {
	// AddNode adds a new node to a branch.
	AddNode(v Value) Tree
	// AddMetaNode adds a new node with meta value provided to a branch.
	AddMetaNode(meta MetaValue, v Value) Tree
	// AddBranch adds a new branch node (a level deeper).
	AddBranch(v Value) Tree
	// AddMetaBranch adds a new branch node (a level deeper) with meta value provided.
	AddMetaBranch(meta MetaValue, v Value) Tree
	// Branch converts a leaf-node to a branch-node,
	// applying this on a branch-node does no effect.
	Branch() Tree
	// FindByMeta finds a node whose meta value matches the provided one by reflect.DeepEqual,
	// returns nil if not found.
	FindByMeta(meta MetaValue) Tree
	// FindByValue finds a node whose value matches the provided one by reflect.DeepEqual,
	// returns nil if not found.
	FindByValue(value Value) Tree
	// String renders the tree or subtree as a string.
	String() string
	// Bytes renders the tree or subtree as byteslice.
	Bytes() []byte
}

type node struct {
	Root  *node
	Meta  MetaValue
	Value Value
	Nodes []*node
}

func (n *node) AddNode(v Value) Tree {
	n.Nodes = append(n.Nodes, &node{
		Root:  n,
		Value: v,
	})
	if n.Root != nil {
		return n.Root
	}
	return n
}

func (n *node) AddMetaNode(meta MetaValue, v Value) Tree {
	n.Nodes = append(n.Nodes, &node{
		Root:  n,
		Meta:  meta,
		Value: v,
	})
	if n.Root != nil {
		return n.Root
	}
	return n
}

func (n *node) AddBranch(v Value) Tree {
	branch := &node{
		Value: v,
	}
	n.Nodes = append(n.Nodes, branch)
	return branch
}

func (n *node) AddMetaBranch(meta MetaValue, v Value) Tree {
	branch := &node{
		Meta:  meta,
		Value: v,
	}
	n.Nodes = append(n.Nodes, branch)
	return branch
}

func (n *node) Branch() Tree {
	n.Root = nil
	return n
}

func (n *node) FindByMeta(meta MetaValue) Tree {
	for _, node := range n.Nodes {
		if reflect.DeepEqual(node.Meta, meta) {
			return node
		}
		if v := node.FindByMeta(meta); v != nil {
			return v
		}
	}
	return nil
}

func (n *node) FindByValue(value Value) Tree {
	for _, node := range n.Nodes {
		if reflect.DeepEqual(node.Value, value) {
			return node
		}
		if v := node.FindByMeta(value); v != nil {
			return v
		}
	}
	return nil
}

func (n *node) Bytes() []byte {
	buf := new(bytes.Buffer)
	level := 0
	var levelsEnded []int
	if n.Root == nil {
		buf.WriteString(string(EdgeTypeStart))
		buf.WriteByte('\n')
	} else {
		edge := EdgeTypeMid
		if len(n.Nodes) == 0 {
			edge = EdgeTypeEnd
			levelsEnded = append(levelsEnded, level)
		}
		printValues(buf, 0, levelsEnded, edge, n.Meta, n.Value)
	}
	if len(n.Nodes) > 0 {
		printNodes(buf, level, levelsEnded, n.Nodes)
	}
	return buf.Bytes()
}

func (n *node) String() string {
	return string(n.Bytes())
}

func printNodes(wr io.Writer,
	level int, levelsEnded []int, nodes []*node) {

	for i, node := range nodes {
		edge := EdgeTypeMid
		if i == len(nodes)-1 {
			levelsEnded = append(levelsEnded, level)
			edge = EdgeTypeEnd
		}
		printValues(wr, level, levelsEnded, edge, node.Meta, node.Value)
		if len(node.Nodes) > 0 {
			printNodes(wr, level+1, levelsEnded, node.Nodes)
		}
	}
}

func printValues(wr io.Writer,
	level int, levelsEnded []int, edge EdgeType, meta MetaValue, val Value) {

	for i := 0; i < level; i++ {
		if isEnded(levelsEnded, i) {
			fmt.Fprint(wr, "    ")
			continue
		}
		fmt.Fprintf(wr, "%s   ", EdgeTypeLink)
	}
	if meta != nil {
		fmt.Fprintf(wr, "%s [%v]  %v\n", edge, meta, val)
		return
	}
	fmt.Fprintf(wr, "%s %v\n", edge, val)
}

func isEnded(levelsEnded []int, level int) bool {
	for _, l := range levelsEnded {
		if l == level {
			return true
		}
	}
	return false
}

type EdgeType string

const (
	EdgeTypeStart EdgeType = "."
	EdgeTypeLink  EdgeType = "│"
	EdgeTypeMid   EdgeType = "├──"
	EdgeTypeEnd   EdgeType = "└──"
)

func New() Tree {
	return &node{}
}
<commit_msg>added a small method to return the last node<commit_after>// Package treeprint provides a simple ASCII tree composing tool.
package treeprint

import (
	"bytes"
	"fmt"
	"io"
	"reflect"
)

type Value interface{}
type MetaValue interface{}

// Tree represents a tree structure with leaf-nodes and branch-nodes.
type Tree interface {
	// AddNode adds a new node to a branch.
	AddNode(v Value) Tree
	// AddMetaNode adds a new node with meta value provided to a branch.
	AddMetaNode(meta MetaValue, v Value) Tree
	// AddBranch adds a new branch node (a level deeper).
	AddBranch(v Value) Tree
	// AddMetaBranch adds a new branch node (a level deeper) with meta value provided.
	AddMetaBranch(meta MetaValue, v Value) Tree
	// Branch converts a leaf-node to a branch-node,
	// applying this on a branch-node does no effect.
	Branch() Tree
	// FindByMeta finds a node whose meta value matches the provided one by reflect.DeepEqual,
	// returns nil if not found.
	FindByMeta(meta MetaValue) Tree
	// FindByValue finds a node whose value matches the provided one by reflect.DeepEqual,
	// returns nil if not found.
	FindByValue(value Value) Tree
	//  returns the last node of a tree
	FindLastNode() Tree
	// String renders the tree or subtree as a string.
	String() string
	// Bytes renders the tree or subtree as byteslice.
	Bytes() []byte
}

type node struct {
	Root  *node
	Meta  MetaValue
	Value Value
	Nodes []*node
}

func (n *node) FindLastNode() Tree {
	ns := n.Nodes
	n = ns[len(ns)-1]
	return n
}

func (n *node) AddNode(v Value) Tree {
	n.Nodes = append(n.Nodes, &node{
		Root:  n,
		Value: v,
	})
	if n.Root != nil {
		return n.Root
	}
	return n
}

func (n *node) AddMetaNode(meta MetaValue, v Value) Tree {
	n.Nodes = append(n.Nodes, &node{
		Root:  n,
		Meta:  meta,
		Value: v,
	})
	if n.Root != nil {
		return n.Root
	}
	return n
}

func (n *node) AddBranch(v Value) Tree {
	branch := &node{
		Value: v,
	}
	n.Nodes = append(n.Nodes, branch)
	return branch
}

func (n *node) AddMetaBranch(meta MetaValue, v Value) Tree {
	branch := &node{
		Meta:  meta,
		Value: v,
	}
	n.Nodes = append(n.Nodes, branch)
	return branch
}

func (n *node) Branch() Tree {
	n.Root = nil
	return n
}

func (n *node) FindByMeta(meta MetaValue) Tree {
	for _, node := range n.Nodes {
		if reflect.DeepEqual(node.Meta, meta) {
			return node
		}
		if v := node.FindByMeta(meta); v != nil {
			return v
		}
	}
	return nil
}

func (n *node) FindByValue(value Value) Tree {
	for _, node := range n.Nodes {
		if reflect.DeepEqual(node.Value, value) {
			return node
		}
		if v := node.FindByMeta(value); v != nil {
			return v
		}
	}
	return nil
}

func (n *node) Bytes() []byte {
	buf := new(bytes.Buffer)
	level := 0
	var levelsEnded []int
	if n.Root == nil {
		buf.WriteString(string(EdgeTypeStart))
		buf.WriteByte('\n')
	} else {
		edge := EdgeTypeMid
		if len(n.Nodes) == 0 {
			edge = EdgeTypeEnd
			levelsEnded = append(levelsEnded, level)
		}
		printValues(buf, 0, levelsEnded, edge, n.Meta, n.Value)
	}
	if len(n.Nodes) > 0 {
		printNodes(buf, level, levelsEnded, n.Nodes)
	}
	return buf.Bytes()
}

func (n *node) String() string {
	return string(n.Bytes())
}

func printNodes(wr io.Writer,
	level int, levelsEnded []int, nodes []*node) {

	for i, node := range nodes {
		edge := EdgeTypeMid
		if i == len(nodes)-1 {
			levelsEnded = append(levelsEnded, level)
			edge = EdgeTypeEnd
		}
		printValues(wr, level, levelsEnded, edge, node.Meta, node.Value)
		if len(node.Nodes) > 0 {
			printNodes(wr, level+1, levelsEnded, node.Nodes)
		}
	}
}

func printValues(wr io.Writer,
	level int, levelsEnded []int, edge EdgeType, meta MetaValue, val Value) {

	for i := 0; i < level; i++ {
		if isEnded(levelsEnded, i) {
			fmt.Fprint(wr, "    ")
			continue
		}
		fmt.Fprintf(wr, "%s   ", EdgeTypeLink)
	}
	if meta != nil {
		fmt.Fprintf(wr, "%s [%v]  %v\n", edge, meta, val)
		return
	}
	fmt.Fprintf(wr, "%s %v\n", edge, val)
}

func isEnded(levelsEnded []int, level int) bool {
	for _, l := range levelsEnded {
		if l == level {
			return true
		}
	}
	return false
}

type EdgeType string

const (
	EdgeTypeStart EdgeType = "."
	EdgeTypeLink  EdgeType = "│"
	EdgeTypeMid   EdgeType = "├──"
	EdgeTypeEnd   EdgeType = "└──"
)

func New() Tree {
	return &node{}
}
<|endoftext|>


<commit_before>package trek

import (
	"database/sql"
	_ "github.com/go-sql-driver/mysql" // mysql driver
	_ "github.com/lib/pq"              // postgres driver
	"testing"
)

func connect(t *testing.T, kind string) (db *sql.DB) {
	var err error

	var options string
	switch kind {
	case POSTGRES:
		options = "user=postgres dbname=trek sslmode=disable"
		break
	case MYSQL:
		options = "root:@/trek"
		break
	default:
		t.Error("Unsupported database kind")
		return
	}

	db, err = sql.Open(kind, options)
	if err != nil {
		t.Errorf("Error connecting to %s", kind)
		return
	}

	err = db.Ping()
	if err != nil {
		t.Error("Error pinging the database")
		return
	}

	return
}

func dropTable(t *testing.T, db *sql.DB, table string) {
	_, err := db.Exec(`DROP TABLE IF EXISTS ` + table)
	if err != nil {
		t.Error(err.Error())
	}
}

func TestParseOptionsDefaultValues(t *testing.T) {
	options := []interface{}{}
	config := parseOptions(options)
	if config.Action != UP {
		t.Error("Action was not UP")
	}
	if config.Database != POSTGRES {
		t.Error("Database was not POSTGRES")
	}
}

func TestParseOptionsCustomValues1(t *testing.T) {
	options := []interface{}{POSTGRES, UP}
	config := parseOptions(options)
	if config.Action != UP {
		t.Error("Action was not UP")
	}
	if config.Database != POSTGRES {
		t.Error("Database was not POSTGRES")
	}
}

func TestParseOptionsCustomValues2(t *testing.T) {
	options := []interface{}{MYSQL, DOWN}
	config := parseOptions(options)
	if config.Action != DOWN {
		t.Error("Action was not DOWN")
	}
	if config.Database != MYSQL {
		t.Error("Database was not MYSQL")
	}
}

func TestCreateTableError(t *testing.T) {
	db := connect(t, POSTGRES)
	defer db.Close()
	config := &configuration{Database: "foo", Action: DOWN}
	dtbs := &database{db, config}
	err := createTable(dtbs)
	if err == nil {
		t.Error("Error expected")
	}
	if err.Error() != errUnrecognizedDatabase.Error() {
		t.Error("Unrecognized database was expected")
	}
}

func TestCreateTablePostgres(t *testing.T) {
	db := connect(t, POSTGRES)
	defer db.Close()
	config := &configuration{Database: POSTGRES, Action: UP}
	dtbs := &database{db, config}
	err := createTable(dtbs)
	if err != nil {
		t.Error(err.Error())
	}
	_, err = db.Query(`SELECT * FROM migrations`)
	if err != nil {
		t.Error(err.Error())
	}
	dropTable(t, db, "migrations")
}

func TestCreateTableMysql(t *testing.T) {
	db := connect(t, MYSQL)
	defer db.Close()
	config := &configuration{Database: MYSQL, Action: UP}
	dtbs := &database{db, config}
	err := createTable(dtbs)
	if err != nil {
		t.Error(err.Error())
	}
	_, err = db.Query(`SELECT * FROM migrations`)
	if err != nil {
		t.Error(err.Error())
	}
	dropTable(t, db, "migrations")
}

func TestGetVersion0(t *testing.T) {
	db := connect(t, POSTGRES)
	defer db.Close()
	config := &configuration{Database: POSTGRES, Action: UP}
	dtbs := &database{db, config}
	err := createTable(dtbs)
	if err != nil {
		t.Error(err.Error())
	}
	currentVersion, err := getVersion(dtbs)
	if err != nil {
		t.Error(err.Error())
	}
	if currentVersion != 0 {
		t.Error("Expected version different from 0")
	}
	dropTable(t, db, "migrations")
}

func TestGetVersion1(t *testing.T) {
	db := connect(t, POSTGRES)
	defer db.Close()
	config := &configuration{Database: POSTGRES, Action: UP}
	dtbs := &database{db, config}
	err := createTable(dtbs)
	if err != nil {
		t.Error(err.Error())
	}
	_, err = db.Exec(`INSERT INTO migrations (version) VALUES (1)`)
	if err != nil {
		t.Error(err.Error())
	}
	currentVersion, err := getVersion(dtbs)
	if err != nil {
		t.Error(err.Error())
	}
	if currentVersion != 1 {
		t.Error("Expected version different from 1")
	}
	dropTable(t, db, "migrations")
}

<commit_msg>Add migrations registration tests<commit_after>package trek

import (
	"database/sql"
	_ "github.com/go-sql-driver/mysql" // mysql driver
	_ "github.com/lib/pq"              // postgres driver
	"testing"
)

func connect(t *testing.T, kind string) (db *sql.DB) {
	var err error

	var options string
	switch kind {
	case POSTGRES:
		options = "user=postgres dbname=trek sslmode=disable"
		break
	case MYSQL:
		options = "root:@/trek"
		break
	default:
		t.Error("Unsupported database kind")
		return
	}

	db, err = sql.Open(kind, options)
	if err != nil {
		t.Errorf("Error connecting to %s", kind)
		return
	}

	err = db.Ping()
	if err != nil {
		t.Error("Error pinging the database")
		return
	}

	return
}

func dropTable(t *testing.T, db *sql.DB, table string) {
	_, err := db.Exec(`DROP TABLE IF EXISTS ` + table)
	if err != nil {
		t.Error(err.Error())
	}
}

func TestParseOptionsDefaultValues(t *testing.T) {
	options := []interface{}{}
	config := parseOptions(options)
	if config.Action != UP {
		t.Error("Action was not UP")
	}
	if config.Database != POSTGRES {
		t.Error("Database was not POSTGRES")
	}
}

func TestParseOptionsCustomValues1(t *testing.T) {
	options := []interface{}{POSTGRES, UP}
	config := parseOptions(options)
	if config.Action != UP {
		t.Error("Action was not UP")
	}
	if config.Database != POSTGRES {
		t.Error("Database was not POSTGRES")
	}
}

func TestParseOptionsCustomValues2(t *testing.T) {
	options := []interface{}{MYSQL, DOWN}
	config := parseOptions(options)
	if config.Action != DOWN {
		t.Error("Action was not DOWN")
	}
	if config.Database != MYSQL {
		t.Error("Database was not MYSQL")
	}
}

func TestCreateTableError(t *testing.T) {
	db := connect(t, POSTGRES)
	defer db.Close()
	config := &configuration{Database: "foo", Action: DOWN}
	dtbs := &database{db, config}
	err := createTable(dtbs)
	if err == nil {
		t.Error("Error expected")
	}
	if err.Error() != errUnrecognizedDatabase.Error() {
		t.Error("Unrecognized database was expected")
	}
}

func TestCreateTablePostgres(t *testing.T) {
	db := connect(t, POSTGRES)
	defer db.Close()
	config := &configuration{Database: POSTGRES, Action: UP}
	dtbs := &database{db, config}
	err := createTable(dtbs)
	if err != nil {
		t.Error(err.Error())
	}
	_, err = db.Query(`SELECT * FROM migrations`)
	if err != nil {
		t.Error(err.Error())
	}
	dropTable(t, db, "migrations")
}

func TestCreateTableMysql(t *testing.T) {
	db := connect(t, MYSQL)
	defer db.Close()
	config := &configuration{Database: MYSQL, Action: UP}
	dtbs := &database{db, config}
	err := createTable(dtbs)
	if err != nil {
		t.Error(err.Error())
	}
	_, err = db.Query(`SELECT * FROM migrations`)
	if err != nil {
		t.Error(err.Error())
	}
	dropTable(t, db, "migrations")
}

func TestGetVersion0(t *testing.T) {
	db := connect(t, POSTGRES)
	defer db.Close()
	config := &configuration{Database: POSTGRES, Action: UP}
	dtbs := &database{db, config}
	err := createTable(dtbs)
	if err != nil {
		t.Error(err.Error())
	}
	currentVersion, err := getVersion(dtbs)
	if err != nil {
		t.Error(err.Error())
	}
	if currentVersion != 0 {
		t.Error("Expected version different from 0")
	}
	dropTable(t, db, "migrations")
}

func TestGetVersion1(t *testing.T) {
	db := connect(t, POSTGRES)
	defer db.Close()
	config := &configuration{Database: POSTGRES, Action: UP}
	dtbs := &database{db, config}
	err := createTable(dtbs)
	if err != nil {
		t.Error(err.Error())
	}
	_, err = db.Exec(`INSERT INTO migrations (version) VALUES (1)`)
	if err != nil {
		t.Error(err.Error())
	}
	currentVersion, err := getVersion(dtbs)
	if err != nil {
		t.Error(err.Error())
	}
	if currentVersion != 1 {
		t.Error("Expected version different from 1")
	}
	dropTable(t, db, "migrations")
}

func TestRegister(t *testing.T) {
	var up = func(*sql.DB) error { return nil }
	var down = func(*sql.DB) error { return nil }
	err := Register(1, up, down)
	if err != nil {
		t.Error(err.Error())
	}
	if len(migrations) != 1 {
		t.Error("Expected migrations to have one migration")
	}
	if migrations[0].Version != 1 {
		t.Error("Expected migration to have version equal to 1")
	}
	migrations = []migration{}
}

func TestRegisterDuplicates(t *testing.T) {
	var up = func(*sql.DB) error { return nil }
	var down = func(*sql.DB) error { return nil }
	err := Register(1, up, down)
	if err != nil {
		t.Error(err.Error())
	}
	err = Register(1, up, down)
	if err == nil {
		t.Error("Expected duplicate version error")
	}
	if err.Error() != errVersionAlreadyRegistered.Error() {
		t.Error("Expected duplicate version error")
	}
	migrations = []migration{}
}

<|endoftext|>


<commit_before>package trie

type keyTranslator func(key string) []int
type indexTranslator func(b byte) int

func indexResolver(f indexTranslator) keyTranslator {
	cache := map[string][]int{}
	return func(key string) []int {
		retr, ok := cache[key]
		if ok {
			return retr
		}

		retr = make([]int, len(key))
		for i, ch := range key {
			retr[i] = f(byte(ch))
		}
		cache[key] = retr
		return retr
	}
}

type TrieNode struct {
	Data interface{}
	// Tag should help to annotate a specific feature e.g color, discovered, isDir etc
	Tag      interface{}
	Children *[]*TrieNode
	// Eos(End-Of-Sequence) is designated to terminate a sequence.
	Eos bool
}

type alphabet struct {
	max          int
	min          int
	alphabetizer keyTranslator
}

func asciiAlphabetizer(ch byte) int {
	return int(ch)
}

var AsciiAlphabet = &alphabet{
	min:          0,
	max:          255, // TODO: Perform a sizeof(byte) op
	alphabetizer: indexResolver(asciiAlphabetizer),
}

var (
	NumericStart = 0
	NumericEnd   = 9
	NumericOther = (NumericEnd - NumericStart) + 1
)

func numericAlphabetizer(b byte) int {
	if b >= '0' && b <= '9' {
		return int(b - '0')
	}
	return NumericOther
}

var NumericAlphabet = &alphabet{
	min:          NumericStart,
	max:          NumericOther + 1,
	alphabetizer: indexResolver(numericAlphabetizer),
}

type Trie struct {
	root       *TrieNode
	translator *alphabet
}

func newTrieNode(data interface{}) *TrieNode {
	return &TrieNode{
		Data:     data,
		Children: nil,
	}
}

func (tn *TrieNode) findNode(alphaIndices []int) (cur *TrieNode, ok bool) {
	cur = tn
	i, max := 0, len(alphaIndices)
	for {
		if cur == nil || i >= max {
			break
		}
		next := cur.Children
		if next == nil {
			return nil, false
		}
		children := *next
		first := alphaIndices[i] % len(children)
		cur = children[first]
		i += 1
	}
	if cur == nil || !cur.Eos {
		return nil, false
	}

	return cur, true
}

func (tn *TrieNode) pop(alphaIndices []int) (popd interface{}, ok bool) {
	location, ok := tn.findNode(alphaIndices)
	if !ok || location == nil {
		return nil, false
	}

	popd = location.Data
	location.Data = nil
	location.Eos = false
	// TODO: Perform a check on whether all the Children are set to
	// nil, if so, clean up the array memory, free and set it to nil.

	return popd, true
}

func (tn *TrieNode) tagOn(pass func(*TrieNode) bool, tag interface{}) (count int) {
	count = 0
	defer func() {
		if pass(tn) {
			tn.Tag = tag
			count += 1
		}
	}()

	if tn.Children == nil {
		return
	}

	Children := *tn.Children
	for _, child := range Children {
		if child == nil {
			continue
		}
		count += child.tagOn(pass, tag)
	}

	return count
}

func (tn *TrieNode) EOS() bool {
	return tn.Eos
}

func (tn *TrieNode) Match(pass func(*TrieNode) bool) (matches chan *TrieNode) {
	matches = make(chan *TrieNode)
	go func() {
		tn.match(pass, &matches)
		close(matches)
	}()
	return matches
}

func (tn *TrieNode) matchAndHarvest(pass func(*TrieNode) bool) (unravelled chan *TrieNode) {
	unravelled = make(chan *TrieNode)

	go func() {
		matches := tn.Match(pass)

		pops := uint(0)
		done := make(chan bool)

		for ch := range matches {
			unravelled <- ch
			expChan := ch.explore()
			pops += 1
			go func(ccChan *chan *TrieNode) {
				chChan := *ccChan
				for child := range chChan {
					unravelled <- child
				}
				done <- true
			}(&expChan)
		}

		for i := uint(0); i < pops; i += 1 {
			<-done
		}

		close(unravelled)
	}()

	return unravelled
}

func (tn *TrieNode) explore() (chChan chan *TrieNode) {
	chChan = make(chan *TrieNode)

	go func() {
		defer close(chChan)
		if tn == nil || tn.Children == nil {
			return
		}

		children := *(tn.Children)

		pops := uint(0)
		done := make(chan bool)

		for _, child := range children {
			pops += 1
			go func(ctnptr **TrieNode) {
				ctn := *ctnptr
				cchChan := ctn.explore()
				for ch := range cchChan {
					chChan <- ch
				}
				done <- true
			}(&child)
		}

		for i := uint(0); i < pops; i += 1 {
			<-done
		}
	}()

	return
}

func (tn *TrieNode) match(pass func(*TrieNode) bool, matches *chan *TrieNode) {
	defer func() {
		if pass(tn) {
			*matches <- tn
		}
	}()

	if tn.Children == nil {
		return
	}

	Children := *tn.Children
	for _, child := range Children {
		if child == nil {
			continue
		}
		child.match(pass, &*matches)
	}
	return
}

func (tn *TrieNode) applyOnEos(f func(*TrieNode)) {
	defer func() {
		if tn.Eos {
			f(tn)
		}
	}()

	if tn.Children == nil {
		return
	}

	Children := *tn.Children
	for _, child := range Children {
		if child == nil {
			continue
		}
		child.applyOnEos(f)
	}
	return
}

func (tn *TrieNode) walk() chan interface{} {
	results := make(chan interface{})
	go func() {
		defer func() {
			close(results)
		}()

		if tn.Children == nil {
			return
		}

		Children := *tn.Children
		for _, child := range Children {
			if child == nil {
				continue
			}
			if child.Eos {
				results <- child.Data
			}
			childChan := child.walk()
			for res := range childChan {
				results <- res
			}
		}
	}()
	return results
}

func (tn *TrieNode) get(alphaIndices []int) (value interface{}, ok bool) {
	location, ok := tn.findNode(alphaIndices)
	if !ok || location == nil {
		return nil, false
	}
	return location.Data, true
}

func (tn *TrieNode) set(alphaIndices []int, data interface{}, maxLen int) (prev interface{}, inserted *TrieNode) {
	cur := tn

	for _, curIndex := range alphaIndices {
		var children []*TrieNode
		if cur.Children == nil {
			children = make([]*TrieNode, maxLen)
			cur.Children = &children
		}

		children = *cur.Children
		mod := curIndex % maxLen

		if children[mod] == nil {
			children[mod] = newTrieNode(nil)
		}

		cur = children[mod]
	}

	prev = cur.Data
	cur.Data = data
	cur.Eos = true
	return prev, tn
}

func (t *Trie) Set(key string, value interface{}) (prev interface{}) {
	indices := t.translator.alphabetizer(key)
	prev, _ = t.root.set(indices, value, t.translator.max)
	return prev
}

func (t *Trie) Get(key string) (value interface{}, ok bool) {
	indices := t.translator.alphabetizer(key)
	return t.root.get(indices)
}

func (t *Trie) Pop(key string) (popd interface{}, ok bool) {
	indices := t.translator.alphabetizer(key)
	return t.root.pop(indices)
}

func (t *Trie) Walk() chan interface{} {
	return t.root.walk()
}

func (t *Trie) Apply(f func(*TrieNode)) {
	t.root.applyOnEos(f)
}

func (t *Trie) Tag(pass func(*TrieNode) bool, tag interface{}) int {
	return t.root.tagOn(pass, tag)
}

func (t *Trie) Match(pass func(*TrieNode) bool) (matches chan *TrieNode) {
	matches = make(chan *TrieNode)
	go func() {
		t.root.match(pass, &matches)
		close(matches)
	}()
	return matches
}

func (t *Trie) MatchAndHarvest(pass func(*TrieNode) bool) (matches chan *TrieNode) {
	return t.root.matchAndHarvest(pass)
}

func (tn *TrieNode) breadthFirstApply(apply func(interface{})) {
	if tn == nil || tn.Children == nil {
		return
	}

	children := *(tn.Children)

	for _, child := range children {
		if child == nil {
			continue
		}

		if child.Eos {
			apply(child.Data)
		}
	}

	clog := make(chan bool)
	clogCount := uint64(0)

	for _, child := range children {
		if child == nil {
			continue
		}

		clogCount += 1
		go func(cc *TrieNode) {
			cc.breadthFirstApply(apply)
			clog <- true
		}(child)
	}

	for i := uint64(0); i < clogCount; i += 1 {
		<-clog
	}
}

func (t *Trie) BreadthFirstApply(apply func(interface{})) {
	t.root.breadthFirstApply(apply)
}

func potentialDir(t *TrieNode, onTerminal bool) bool {
	if t == nil || t.Children == nil || len(*t.Children) < 1 {
		return false
	}
	if onTerminal && !t.Eos {
		return false
	}
	nonNilCount := 0
	children := *(t.Children)
	for _, child := range children {
		if child != nil {
			nonNilCount += 1
		}
	}
	return nonNilCount >= 2

}

func PotentialTerminalDir(t *TrieNode) bool {
	return potentialDir(t, true)
}

func PotentialDir(t *TrieNode) bool {
	return potentialDir(t, false)
}

func HasEOS(t *TrieNode) bool {
	return t != nil && t.Eos
}

func New(alphabetizer *alphabet) *Trie {
	t := &Trie{
		root:       newTrieNode(""),
		translator: alphabetizer,
	}
	return t
}
<commit_msg>mutex protect index resolver<commit_after>package trie

import (
	"sync"
)

type keyTranslator func(key string) []int
type indexTranslator func(b byte) int

func indexResolver(f indexTranslator) keyTranslator {
	cache := map[string][]int{}
	var mu sync.Mutex
	return func(key string) []int {
		mu.Lock()
		defer mu.Unlock()
		retr, ok := cache[key]
		if ok {
			return retr
		}

		retr = make([]int, len(key))
		for i, ch := range key {
			retr[i] = f(byte(ch))
		}
		cache[key] = retr
		return retr
	}
}

type TrieNode struct {
	Data interface{}
	// Tag should help to annotate a specific feature e.g color, discovered, isDir etc
	Tag      interface{}
	Children *[]*TrieNode
	// Eos(End-Of-Sequence) is designated to terminate a sequence.
	Eos bool
}

type alphabet struct {
	max          int
	min          int
	alphabetizer keyTranslator
}

func asciiAlphabetizer(ch byte) int {
	return int(ch)
}

var AsciiAlphabet = &alphabet{
	min:          0,
	max:          255, // TODO: Perform a sizeof(byte) op
	alphabetizer: indexResolver(asciiAlphabetizer),
}

var (
	NumericStart = 0
	NumericEnd   = 9
	NumericOther = (NumericEnd - NumericStart) + 1
)

func numericAlphabetizer(b byte) int {
	if b >= '0' && b <= '9' {
		return int(b - '0')
	}
	return NumericOther
}

var NumericAlphabet = &alphabet{
	min:          NumericStart,
	max:          NumericOther + 1,
	alphabetizer: indexResolver(numericAlphabetizer),
}

type Trie struct {
	root       *TrieNode
	translator *alphabet
}

func newTrieNode(data interface{}) *TrieNode {
	return &TrieNode{
		Data:     data,
		Children: nil,
	}
}

func (tn *TrieNode) findNode(alphaIndices []int) (cur *TrieNode, ok bool) {
	cur = tn
	i, max := 0, len(alphaIndices)
	for {
		if cur == nil || i >= max {
			break
		}
		next := cur.Children
		if next == nil {
			return nil, false
		}
		children := *next
		first := alphaIndices[i] % len(children)
		cur = children[first]
		i += 1
	}
	if cur == nil || !cur.Eos {
		return nil, false
	}

	return cur, true
}

func (tn *TrieNode) pop(alphaIndices []int) (popd interface{}, ok bool) {
	location, ok := tn.findNode(alphaIndices)
	if !ok || location == nil {
		return nil, false
	}

	popd = location.Data
	location.Data = nil
	location.Eos = false
	// TODO: Perform a check on whether all the Children are set to
	// nil, if so, clean up the array memory, free and set it to nil.

	return popd, true
}

func (tn *TrieNode) tagOn(pass func(*TrieNode) bool, tag interface{}) (count int) {
	count = 0
	defer func() {
		if pass(tn) {
			tn.Tag = tag
			count += 1
		}
	}()

	if tn.Children == nil {
		return
	}

	Children := *tn.Children
	for _, child := range Children {
		if child == nil {
			continue
		}
		count += child.tagOn(pass, tag)
	}

	return count
}

func (tn *TrieNode) EOS() bool {
	return tn.Eos
}

func (tn *TrieNode) Match(pass func(*TrieNode) bool) (matches chan *TrieNode) {
	matches = make(chan *TrieNode)
	go func() {
		tn.match(pass, &matches)
		close(matches)
	}()
	return matches
}

func (tn *TrieNode) matchAndHarvest(pass func(*TrieNode) bool) (unravelled chan *TrieNode) {
	unravelled = make(chan *TrieNode)

	go func() {
		matches := tn.Match(pass)

		pops := uint(0)
		done := make(chan bool)

		for ch := range matches {
			unravelled <- ch
			expChan := ch.explore()
			pops += 1
			go func(ccChan *chan *TrieNode) {
				chChan := *ccChan
				for child := range chChan {
					unravelled <- child
				}
				done <- true
			}(&expChan)
		}

		for i := uint(0); i < pops; i += 1 {
			<-done
		}

		close(unravelled)
	}()

	return unravelled
}

func (tn *TrieNode) explore() (chChan chan *TrieNode) {
	chChan = make(chan *TrieNode)

	go func() {
		defer close(chChan)
		if tn == nil || tn.Children == nil {
			return
		}

		children := *(tn.Children)

		pops := uint(0)
		done := make(chan bool)

		for _, child := range children {
			pops += 1
			go func(ctnptr **TrieNode) {
				ctn := *ctnptr
				cchChan := ctn.explore()
				for ch := range cchChan {
					chChan <- ch
				}
				done <- true
			}(&child)
		}

		for i := uint(0); i < pops; i += 1 {
			<-done
		}
	}()

	return
}

func (tn *TrieNode) match(pass func(*TrieNode) bool, matches *chan *TrieNode) {
	defer func() {
		if pass(tn) {
			*matches <- tn
		}
	}()

	if tn.Children == nil {
		return
	}

	Children := *tn.Children
	for _, child := range Children {
		if child == nil {
			continue
		}
		child.match(pass, &*matches)
	}
	return
}

func (tn *TrieNode) applyOnEos(f func(*TrieNode)) {
	defer func() {
		if tn.Eos {
			f(tn)
		}
	}()

	if tn.Children == nil {
		return
	}

	Children := *tn.Children
	for _, child := range Children {
		if child == nil {
			continue
		}
		child.applyOnEos(f)
	}
	return
}

func (tn *TrieNode) walk() chan interface{} {
	results := make(chan interface{})
	go func() {
		defer func() {
			close(results)
		}()

		if tn.Children == nil {
			return
		}

		Children := *tn.Children
		for _, child := range Children {
			if child == nil {
				continue
			}
			if child.Eos {
				results <- child.Data
			}
			childChan := child.walk()
			for res := range childChan {
				results <- res
			}
		}
	}()
	return results
}

func (tn *TrieNode) get(alphaIndices []int) (value interface{}, ok bool) {
	location, ok := tn.findNode(alphaIndices)
	if !ok || location == nil {
		return nil, false
	}
	return location.Data, true
}

func (tn *TrieNode) set(alphaIndices []int, data interface{}, maxLen int) (prev interface{}, inserted *TrieNode) {
	cur := tn

	for _, curIndex := range alphaIndices {
		var children []*TrieNode
		if cur.Children == nil {
			children = make([]*TrieNode, maxLen)
			cur.Children = &children
		}

		children = *cur.Children
		mod := curIndex % maxLen

		if children[mod] == nil {
			children[mod] = newTrieNode(nil)
		}

		cur = children[mod]
	}

	prev = cur.Data
	cur.Data = data
	cur.Eos = true
	return prev, tn
}

func (t *Trie) Set(key string, value interface{}) (prev interface{}) {
	indices := t.translator.alphabetizer(key)
	prev, _ = t.root.set(indices, value, t.translator.max)
	return prev
}

func (t *Trie) Get(key string) (value interface{}, ok bool) {
	indices := t.translator.alphabetizer(key)
	return t.root.get(indices)
}

func (t *Trie) Pop(key string) (popd interface{}, ok bool) {
	indices := t.translator.alphabetizer(key)
	return t.root.pop(indices)
}

func (t *Trie) Walk() chan interface{} {
	return t.root.walk()
}

func (t *Trie) Apply(f func(*TrieNode)) {
	t.root.applyOnEos(f)
}

func (t *Trie) Tag(pass func(*TrieNode) bool, tag interface{}) int {
	return t.root.tagOn(pass, tag)
}

func (t *Trie) Match(pass func(*TrieNode) bool) (matches chan *TrieNode) {
	matches = make(chan *TrieNode)
	go func() {
		t.root.match(pass, &matches)
		close(matches)
	}()
	return matches
}

func (t *Trie) MatchAndHarvest(pass func(*TrieNode) bool) (matches chan *TrieNode) {
	return t.root.matchAndHarvest(pass)
}

func (tn *TrieNode) breadthFirstApply(apply func(interface{})) {
	if tn == nil || tn.Children == nil {
		return
	}

	children := *(tn.Children)

	for _, child := range children {
		if child == nil {
			continue
		}

		if child.Eos {
			apply(child.Data)
		}
	}

	clog := make(chan bool)
	clogCount := uint64(0)

	for _, child := range children {
		if child == nil {
			continue
		}

		clogCount += 1

		go func(cc *TrieNode) {
			cc.breadthFirstApply(apply)
			clog <- true
		}(child)
	}

	for i := uint64(0); i < clogCount; i += 1 {
		<-clog
	}
}

func (t *Trie) BreadthFirstApply(apply func(interface{})) {
	t.root.breadthFirstApply(apply)
}

func potentialDir(t *TrieNode, onTerminal bool) bool {
	if t == nil || t.Children == nil || len(*t.Children) < 1 {
		return false
	}
	if onTerminal && !t.Eos {
		return false
	}
	nonNilCount := 0
	children := *(t.Children)
	for _, child := range children {
		if child != nil {
			nonNilCount += 1
		}
	}
	return nonNilCount >= 2

}

func PotentialTerminalDir(t *TrieNode) bool {
	return potentialDir(t, true)
}

func PotentialDir(t *TrieNode) bool {
	return potentialDir(t, false)
}

func HasEOS(t *TrieNode) bool {
	return t != nil && t.Eos
}

func New(alphabetizer *alphabet) *Trie {
	t := &Trie{
		root:       newTrieNode(""),
		translator: alphabetizer,
	}
	return t
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"text/template"
	"time"

	"github.com/eknkc/amber"
	"github.com/russross/blackfriday"
	"github.com/yosssi/gcss"
	"gopkg.in/yaml.v2"
)

const (
	ZSDIR  = ".zs"
	PUBDIR = ".pub"
)

type Vars map[string]string

// renameExt renames extension (if any) from oldext to newext
// If oldext is an empty string - extension is extracted automatically.
// If path has no extension - new extension is appended
func renameExt(path, oldext, newext string) string {
	if oldext == "" {
		oldext = filepath.Ext(path)
	}
	if oldext == "" || strings.HasSuffix(path, oldext) {
		return strings.TrimSuffix(path, oldext) + newext
	} else {
		return path
	}
}

// globals returns list of global OS environment variables that start
// with ZS_ prefix as Vars, so the values can be used inside templates
func globals() Vars {
	vars := Vars{}
	for _, e := range os.Environ() {
		pair := strings.Split(e, "=")
		if strings.HasPrefix(pair[0], "ZS_") {
			vars[strings.ToLower(pair[0][3:])] = pair[1]
		}
	}
	return vars
}

// run executes a command or a script. Vars define the command environment,
// each zs var is converted into OS environemnt variable with ZS_ prefix
// prepended.  Additional variable $ZS contains path to the zs binary. Command
// stderr is printed to zs stderr, command output is returned as a string.
func run(vars Vars, cmd string, args ...string) (string, error) {
	// First check if partial exists (.amber or .html)
	if b, err := ioutil.ReadFile(filepath.Join(ZSDIR, cmd+".amber")); err == nil {
		return string(b), nil
	}
	if b, err := ioutil.ReadFile(filepath.Join(ZSDIR, cmd+".html")); err == nil {
		return string(b), nil
	}

	var errbuf, outbuf bytes.Buffer
	c := exec.Command(cmd, args...)
	env := []string{"ZS=" + os.Args[0], "ZS_OUTDIR=" + PUBDIR}
	env = append(env, os.Environ()...)
	for k, v := range vars {
		env = append(env, "ZS_"+strings.ToUpper(k)+"="+v)
	}
	c.Env = env
	c.Stdout = &outbuf
	c.Stderr = &errbuf

	err := c.Run()

	if errbuf.Len() > 0 {
		log.Println("ERROR:", errbuf.String())
	}
	if err != nil {
		return "", err
	}
	return string(outbuf.Bytes()), nil
}

// getVars returns list of variables defined in a text file and actual file
// content following the variables declaration. Header is separated from
// content by an empty line. Header can be either YAML or JSON.
// If no empty newline is found - file is treated as content-only.
func getVars(path string, globals Vars) (Vars, string, error) {
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, "", err
	}
	s := string(b)

	// Copy globals first
	v := Vars{}
	for name, value := range globals {
		v[name] = value
	}

	// Override them by default values extracted from file name/path
	if _, err := os.Stat(filepath.Join(ZSDIR, "layout.amber")); err == nil {
		v["layout"] = "layout.amber"
	} else {
		v["layout"] = "layout.html"
	}
	title := strings.Replace(strings.Replace(path, "_", " ", -1), "-", " ", -1)
	v["title"] = strings.ToTitle(title)
	v["description"] = ""
	v["file"] = path
	v["url"] = path[:len(path)-len(filepath.Ext(path))] + ".html"
	v["output"] = filepath.Join(PUBDIR, v["url"])

	delim := "\n---\n"
	if sep := strings.Index(s, delim); sep == -1 {
		return v, s, nil
	} else {
		header := s[:sep]
		body := s[sep+len(delim):]

		vars := Vars{}
		if err := yaml.Unmarshal([]byte(header), &vars); err != nil {
			fmt.Println("ERROR: failed to parse header", err)
			return nil, "", err
		} else {
			for key, value := range vars {
				v[key] = value
			}
		}
		if strings.HasPrefix(v["url"], "./") {
			v["url"] = v["url"][2:]
		}
		return v, body, nil
	}
}

// Render expanding zs plugins and variables
func render(s string, vars Vars) (string, error) {
	delim_open := "{{"
	delim_close := "}}"

	out := &bytes.Buffer{}
	for {
		if from := strings.Index(s, delim_open); from == -1 {
			out.WriteString(s)
			return out.String(), nil
		} else {
			if to := strings.Index(s, delim_close); to == -1 {
				return "", fmt.Errorf("Close delim not found")
			} else {
				out.WriteString(s[:from])
				cmd := s[from+len(delim_open) : to]
				s = s[to+len(delim_close):]
				m := strings.Fields(cmd)
				if len(m) == 1 {
					if v, ok := vars[m[0]]; ok {
						out.WriteString(v)
						continue
					}
				}
				if res, err := run(vars, m[0], m[1:]...); err == nil {
					out.WriteString(res)
				} else {
					fmt.Println(err)
				}
			}
		}
	}
	return s, nil
}

// Renders markdown with the given layout into html expanding all the macros
func buildMarkdown(path string, w io.Writer, vars Vars) error {
	v, body, err := getVars(path, vars)
	if err != nil {
		return err
	}
	content, err := render(body, v)
	if err != nil {
		return err
	}
	v["content"] = string(blackfriday.MarkdownCommon([]byte(content)))
	if w == nil {
		out, err := os.Create(filepath.Join(PUBDIR, renameExt(path, "", ".html")))
		if err != nil {
			return err
		}
		defer out.Close()
		w = out
	}
	if strings.HasSuffix(v["layout"], ".amber") {
		return buildAmber(filepath.Join(ZSDIR, v["layout"]), w, v)
	} else {
		return buildHTML(filepath.Join(ZSDIR, v["layout"]), w, v)
	}
}

// Renders text file expanding all variable macros inside it
func buildHTML(path string, w io.Writer, vars Vars) error {
	v, body, err := getVars(path, vars)
	if err != nil {
		return err
	}
	if body, err = render(body, v); err != nil {
		return err
	}
	tmpl, err := template.New("").Delims("<%", "%>").Parse(body)
	if err != nil {
		return err
	}
	if w == nil {
		f, err := os.Create(filepath.Join(PUBDIR, path))
		if err != nil {
			return err
		}
		defer f.Close()
		w = f
	}
	return tmpl.Execute(w, vars)
}

// Renders .amber file into .html
func buildAmber(path string, w io.Writer, vars Vars) error {
	v, body, err := getVars(path, vars)
	if err != nil {
		return err
	}
	a := amber.New()
	if err := a.Parse(body); err != nil {
		fmt.Println(body)
		return err
	}

	t, err := a.Compile()
	if err != nil {
		return err
	}

	htmlBuf := &bytes.Buffer{}
	if err := t.Execute(htmlBuf, v); err != nil {
		return err
	}

	if body, err = render(string(htmlBuf.Bytes()), v); err != nil {
		return err
	}

	if w == nil {
		f, err := os.Create(filepath.Join(PUBDIR, renameExt(path, ".amber", ".html")))
		if err != nil {
			return err
		}
		defer f.Close()
		w = f
	}
	_, err = io.WriteString(w, body)
	return err
}

// Compiles .gcss into .css
func buildGCSS(path string, w io.Writer) error {
	f, err := os.Open(path)
	if err != nil {
		return err
	}
	defer f.Close()

	if w == nil {
		s := strings.TrimSuffix(path, ".gcss") + ".css"
		css, err := os.Create(filepath.Join(PUBDIR, s))
		if err != nil {
			return err
		}
		defer css.Close()
		w = css
	}
	_, err = gcss.Compile(w, f)
	return err
}

// Copies file as is from path to writer
func buildRaw(path string, w io.Writer) error {
	in, err := os.Open(path)
	if err != nil {
		return err
	}
	defer in.Close()
	if w == nil {
		if out, err := os.Create(filepath.Join(PUBDIR, path)); err != nil {
			return err
		} else {
			defer out.Close()
			w = out
		}
	}
	_, err = io.Copy(w, in)
	return err
}

func build(path string, w io.Writer, vars Vars) error {
	ext := filepath.Ext(path)
	if ext == ".md" || ext == ".mkd" {
		return buildMarkdown(path, w, vars)
	} else if ext == ".html" || ext == ".xml" {
		return buildHTML(path, w, vars)
	} else if ext == ".amber" {
		return buildAmber(path, w, vars)
	} else if ext == ".gcss" {
		return buildGCSS(path, w)
	} else {
		return buildRaw(path, w)
	}
}

func buildAll(watch bool) {
	lastModified := time.Unix(0, 0)
	modified := false

	vars := globals()
	for {
		os.Mkdir(PUBDIR, 0755)
		filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
			// ignore hidden files and directories
			if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") {
				return nil
			}
			// inform user about fs walk errors, but continue iteration
			if err != nil {
				fmt.Println("error:", err)
				return nil
			}

			if info.IsDir() {
				os.Mkdir(filepath.Join(PUBDIR, path), 0755)
				return nil
			} else if info.ModTime().After(lastModified) {
				if !modified {
					// First file in this build cycle is about to be modified
					run(vars, "prehook")
					modified = true
				}
				log.Println("build:", path)
				return build(path, nil, vars)
			}
			return nil
		})
		if modified {
			// At least one file in this build cycle has been modified
			run(vars, "posthook")
			modified = false
		}
		if !watch {
			break
		}
		lastModified = time.Now()
		time.Sleep(1 * time.Second)
	}
}

func init() {
	// prepend .zs to $PATH, so plugins will be found before OS commands
	p := os.Getenv("PATH")
	p = ZSDIR + ":" + p
	os.Setenv("PATH", p)
}

func main() {
	if len(os.Args) == 1 {
		fmt.Println(os.Args[0], "<command> [args]")
		return
	}
	cmd := os.Args[1]
	args := os.Args[2:]
	switch cmd {
	case "build":
		if len(args) == 0 {
			buildAll(false)
		} else if len(args) == 1 {
			if err := build(args[0], os.Stdout, globals()); err != nil {
				fmt.Println("ERROR: " + err.Error())
			}
		} else {
			fmt.Println("ERROR: too many arguments")
		}
	case "watch":
		buildAll(true)
	case "var":
		if len(args) == 0 {
			fmt.Println("var: filename expected")
		} else {
			s := ""
			if vars, _, err := getVars(args[0], globals()); err != nil {
				fmt.Println("var: " + err.Error())
			} else {
				if len(args) > 1 {
					for _, a := range args[1:] {
						s = s + vars[a] + "\n"
					}
				} else {
					for k, v := range vars {
						s = s + k + ":" + v + "\n"
					}
				}
			}
			fmt.Println(strings.TrimSpace(s))
		}
	default:
		if s, err := run(globals(), cmd, args...); err != nil {
			fmt.Println(err)
		} else {
			fmt.Println(s)
		}
	}
}
<commit_msg>fixed variable override order<commit_after>package main

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"text/template"
	"time"

	"github.com/eknkc/amber"
	"github.com/russross/blackfriday"
	"github.com/yosssi/gcss"
	"gopkg.in/yaml.v2"
)

const (
	ZSDIR  = ".zs"
	PUBDIR = ".pub"
)

type Vars map[string]string

// renameExt renames extension (if any) from oldext to newext
// If oldext is an empty string - extension is extracted automatically.
// If path has no extension - new extension is appended
func renameExt(path, oldext, newext string) string {
	if oldext == "" {
		oldext = filepath.Ext(path)
	}
	if oldext == "" || strings.HasSuffix(path, oldext) {
		return strings.TrimSuffix(path, oldext) + newext
	} else {
		return path
	}
}

// globals returns list of global OS environment variables that start
// with ZS_ prefix as Vars, so the values can be used inside templates
func globals() Vars {
	vars := Vars{}
	for _, e := range os.Environ() {
		pair := strings.Split(e, "=")
		if strings.HasPrefix(pair[0], "ZS_") {
			vars[strings.ToLower(pair[0][3:])] = pair[1]
		}
	}
	return vars
}

// run executes a command or a script. Vars define the command environment,
// each zs var is converted into OS environemnt variable with ZS_ prefix
// prepended.  Additional variable $ZS contains path to the zs binary. Command
// stderr is printed to zs stderr, command output is returned as a string.
func run(vars Vars, cmd string, args ...string) (string, error) {
	// First check if partial exists (.amber or .html)
	if b, err := ioutil.ReadFile(filepath.Join(ZSDIR, cmd+".amber")); err == nil {
		return string(b), nil
	}
	if b, err := ioutil.ReadFile(filepath.Join(ZSDIR, cmd+".html")); err == nil {
		return string(b), nil
	}

	var errbuf, outbuf bytes.Buffer
	c := exec.Command(cmd, args...)
	env := []string{"ZS=" + os.Args[0], "ZS_OUTDIR=" + PUBDIR}
	env = append(env, os.Environ()...)
	for k, v := range vars {
		env = append(env, "ZS_"+strings.ToUpper(k)+"="+v)
	}
	c.Env = env
	c.Stdout = &outbuf
	c.Stderr = &errbuf

	err := c.Run()

	if errbuf.Len() > 0 {
		log.Println("ERROR:", errbuf.String())
	}
	if err != nil {
		return "", err
	}
	return string(outbuf.Bytes()), nil
}

// getVars returns list of variables defined in a text file and actual file
// content following the variables declaration. Header is separated from
// content by an empty line. Header can be either YAML or JSON.
// If no empty newline is found - file is treated as content-only.
func getVars(path string, globals Vars) (Vars, string, error) {
	b, err := ioutil.ReadFile(path)
	if err != nil {
		return nil, "", err
	}
	s := string(b)

	// Pick some default values for content-dependent variables
	v := Vars{}
	title := strings.Replace(strings.Replace(path, "_", " ", -1), "-", " ", -1)
	v["title"] = strings.ToTitle(title)
	v["description"] = ""

	// Copy globals (will override title and description for markdown layouts
	for name, value := range globals {
		v[name] = value
	}

	// Add default values extracted from file name/path
	if _, err := os.Stat(filepath.Join(ZSDIR, "layout.amber")); err == nil {
		v["layout"] = "layout.amber"
	} else {
		v["layout"] = "layout.html"
	}
	v["file"] = path
	v["url"] = path[:len(path)-len(filepath.Ext(path))] + ".html"
	v["output"] = filepath.Join(PUBDIR, v["url"])

	delim := "\n---\n"
	if sep := strings.Index(s, delim); sep == -1 {
		return v, s, nil
	} else {
		header := s[:sep]
		body := s[sep+len(delim):]

		vars := Vars{}
		if err := yaml.Unmarshal([]byte(header), &vars); err != nil {
			fmt.Println("ERROR: failed to parse header", err)
			return nil, "", err
		} else {
			for key, value := range vars {
				v[key] = value
			}
		}
		if strings.HasPrefix(v["url"], "./") {
			v["url"] = v["url"][2:]
		}
		return v, body, nil
	}
}

// Render expanding zs plugins and variables
func render(s string, vars Vars) (string, error) {
	delim_open := "{{"
	delim_close := "}}"

	out := &bytes.Buffer{}
	for {
		if from := strings.Index(s, delim_open); from == -1 {
			out.WriteString(s)
			return out.String(), nil
		} else {
			if to := strings.Index(s, delim_close); to == -1 {
				return "", fmt.Errorf("Close delim not found")
			} else {
				out.WriteString(s[:from])
				cmd := s[from+len(delim_open) : to]
				s = s[to+len(delim_close):]
				m := strings.Fields(cmd)
				if len(m) == 1 {
					if v, ok := vars[m[0]]; ok {
						out.WriteString(v)
						continue
					}
				}
				if res, err := run(vars, m[0], m[1:]...); err == nil {
					out.WriteString(res)
				} else {
					fmt.Println(err)
				}
			}
		}
	}
	return s, nil
}

// Renders markdown with the given layout into html expanding all the macros
func buildMarkdown(path string, w io.Writer, vars Vars) error {
	v, body, err := getVars(path, vars)
	if err != nil {
		return err
	}
	content, err := render(body, v)
	if err != nil {
		return err
	}
	v["content"] = string(blackfriday.MarkdownCommon([]byte(content)))
	if w == nil {
		out, err := os.Create(filepath.Join(PUBDIR, renameExt(path, "", ".html")))
		if err != nil {
			return err
		}
		defer out.Close()
		w = out
	}
	if strings.HasSuffix(v["layout"], ".amber") {
		return buildAmber(filepath.Join(ZSDIR, v["layout"]), w, v)
	} else {
		return buildHTML(filepath.Join(ZSDIR, v["layout"]), w, v)
	}
}

// Renders text file expanding all variable macros inside it
func buildHTML(path string, w io.Writer, vars Vars) error {
	v, body, err := getVars(path, vars)
	if err != nil {
		return err
	}
	if body, err = render(body, v); err != nil {
		return err
	}
	tmpl, err := template.New("").Delims("<%", "%>").Parse(body)
	if err != nil {
		return err
	}
	if w == nil {
		f, err := os.Create(filepath.Join(PUBDIR, path))
		if err != nil {
			return err
		}
		defer f.Close()
		w = f
	}
	return tmpl.Execute(w, vars)
}

// Renders .amber file into .html
func buildAmber(path string, w io.Writer, vars Vars) error {
	v, body, err := getVars(path, vars)
	if err != nil {
		return err
	}
	a := amber.New()
	if err := a.Parse(body); err != nil {
		fmt.Println(body)
		return err
	}

	t, err := a.Compile()
	if err != nil {
		return err
	}

	htmlBuf := &bytes.Buffer{}
	if err := t.Execute(htmlBuf, v); err != nil {
		return err
	}

	if body, err = render(string(htmlBuf.Bytes()), v); err != nil {
		return err
	}

	if w == nil {
		f, err := os.Create(filepath.Join(PUBDIR, renameExt(path, ".amber", ".html")))
		if err != nil {
			return err
		}
		defer f.Close()
		w = f
	}
	_, err = io.WriteString(w, body)
	return err
}

// Compiles .gcss into .css
func buildGCSS(path string, w io.Writer) error {
	f, err := os.Open(path)
	if err != nil {
		return err
	}
	defer f.Close()

	if w == nil {
		s := strings.TrimSuffix(path, ".gcss") + ".css"
		css, err := os.Create(filepath.Join(PUBDIR, s))
		if err != nil {
			return err
		}
		defer css.Close()
		w = css
	}
	_, err = gcss.Compile(w, f)
	return err
}

// Copies file as is from path to writer
func buildRaw(path string, w io.Writer) error {
	in, err := os.Open(path)
	if err != nil {
		return err
	}
	defer in.Close()
	if w == nil {
		if out, err := os.Create(filepath.Join(PUBDIR, path)); err != nil {
			return err
		} else {
			defer out.Close()
			w = out
		}
	}
	_, err = io.Copy(w, in)
	return err
}

func build(path string, w io.Writer, vars Vars) error {
	ext := filepath.Ext(path)
	if ext == ".md" || ext == ".mkd" {
		return buildMarkdown(path, w, vars)
	} else if ext == ".html" || ext == ".xml" {
		return buildHTML(path, w, vars)
	} else if ext == ".amber" {
		return buildAmber(path, w, vars)
	} else if ext == ".gcss" {
		return buildGCSS(path, w)
	} else {
		return buildRaw(path, w)
	}
}

func buildAll(watch bool) {
	lastModified := time.Unix(0, 0)
	modified := false

	vars := globals()
	for {
		os.Mkdir(PUBDIR, 0755)
		filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
			// ignore hidden files and directories
			if filepath.Base(path)[0] == '.' || strings.HasPrefix(path, ".") {
				return nil
			}
			// inform user about fs walk errors, but continue iteration
			if err != nil {
				fmt.Println("error:", err)
				return nil
			}

			if info.IsDir() {
				os.Mkdir(filepath.Join(PUBDIR, path), 0755)
				return nil
			} else if info.ModTime().After(lastModified) {
				if !modified {
					// First file in this build cycle is about to be modified
					run(vars, "prehook")
					modified = true
				}
				log.Println("build:", path)
				return build(path, nil, vars)
			}
			return nil
		})
		if modified {
			// At least one file in this build cycle has been modified
			run(vars, "posthook")
			modified = false
		}
		if !watch {
			break
		}
		lastModified = time.Now()
		time.Sleep(1 * time.Second)
	}
}

func init() {
	// prepend .zs to $PATH, so plugins will be found before OS commands
	p := os.Getenv("PATH")
	p = ZSDIR + ":" + p
	os.Setenv("PATH", p)
}

func main() {
	if len(os.Args) == 1 {
		fmt.Println(os.Args[0], "<command> [args]")
		return
	}
	cmd := os.Args[1]
	args := os.Args[2:]
	switch cmd {
	case "build":
		if len(args) == 0 {
			buildAll(false)
		} else if len(args) == 1 {
			if err := build(args[0], os.Stdout, globals()); err != nil {
				fmt.Println("ERROR: " + err.Error())
			}
		} else {
			fmt.Println("ERROR: too many arguments")
		}
	case "watch":
		buildAll(true)
	case "var":
		if len(args) == 0 {
			fmt.Println("var: filename expected")
		} else {
			s := ""
			if vars, _, err := getVars(args[0], globals()); err != nil {
				fmt.Println("var: " + err.Error())
			} else {
				if len(args) > 1 {
					for _, a := range args[1:] {
						s = s + vars[a] + "\n"
					}
				} else {
					for k, v := range vars {
						s = s + k + ":" + v + "\n"
					}
				}
			}
			fmt.Println(strings.TrimSpace(s))
		}
	default:
		if s, err := run(globals(), cmd, args...); err != nil {
			fmt.Println(err)
		} else {
			fmt.Println(s)
		}
	}
}
<|endoftext|>