<commit_before>package message

import (
	"fmt"
	"time"
)

const timestampLayout = "2006-01-02 15:04:05 MST"

const (
	// Reset resets the color
	Reset = "\033[0m"

	// Bold makes the following text bold
	Bold = "\033[1m"

	// Dim dims the following text
	Dim = "\033[2m"

	// Italic makes the following text italic
	Italic = "\033[3m"

	// Underline underlines the following text
	Underline = "\033[4m"

	// Blink blinks the following text
	Blink = "\033[5m"

	// Invert inverts the following text
	Invert = "\033[7m"

	// Newline
	Newline = "\r\n"

	// BEL
	Bel = "\007"
)

// Interface for Styles
type Style interface {
	String() string
	Format(string) string
}

// General hardcoded style, mostly used as a crutch until we flesh out the
// framework to support backgrounds etc.
type style string

func (c style) String() string {
	return string(c)
}

func (c style) Format(s string) string {
	return c.String() + s + Reset
}

// 256 color type, for terminals who support it
type Color256 uint8

// String version of this color
func (c Color256) String() string {
	return fmt.Sprintf("38;05;%d", c)
}

// Return formatted string with this color
func (c Color256) Format(s string) string {
	return "\033[" + c.String() + "m" + s + Reset
}

func Color256Palette(colors ...uint8) *Palette {
	size := len(colors)
	p := make([]Style, 0, size)
	for _, color := range colors {
		p = append(p, Color256(color))
	}
	return &Palette{
		colors: p,
		size:   size,
	}
}

// No color, used for mono theme
type Color0 struct{}

// No-op for Color0
func (c Color0) String() string {
	return ""
}

// No-op for Color0
func (c Color0) Format(s string) string {
	return s
}

// Container for a collection of colors
type Palette struct {
	colors []Style
	size   int
}

// Get a color by index, overflows are looped around.
func (p Palette) Get(i int) Style {
	if p.size == 1 {
		return p.colors[0]
	}
	return p.colors[i%(p.size-1)]
}

func (p Palette) Len() int {
	return p.size
}

func (p Palette) String() string {
	r := ""
	for _, c := range p.colors {
		r += c.Format("X")
	}
	return r
}

// Collection of settings for chat
type Theme struct {
	id        string
	sys       Style
	pm        Style
	highlight Style
	names     *Palette
}

func (theme Theme) ID() string {
	return theme.id
}

// Colorize name string given some index
func (theme Theme) ColorName(u *User) string {
	if theme.names == nil {
		return u.Name()
	}

	return theme.names.Get(u.colorIdx).Format(u.Name())
}

// Colorize the PM string
func (theme Theme) ColorPM(s string) string {
	if theme.pm == nil {
		return s
	}

	return theme.pm.Format(s)
}

// Colorize the Sys message
func (theme Theme) ColorSys(s string) string {
	if theme.sys == nil {
		return s
	}

	return theme.sys.Format(s)
}

// Highlight a matched string, usually name
func (theme Theme) Highlight(s string) string {
	if theme.highlight == nil {
		return s
	}
	return theme.highlight.Format(s)
}

// Timestamp formats and colorizes the timestamp.
func (theme Theme) Timestamp(t time.Time) string {
	// TODO: Change this per-theme? Or config?
	return theme.sys.Format(t.Format(timestampLayout))
}

// List of initialzied themes
var Themes []Theme

// Default theme to use
var DefaultTheme *Theme

func allColors256() *Palette {
	colors := []uint8{}
	var i uint8
	for i = 0; i < 255; i++ {
		colors = append(colors, i)
	}
	return Color256Palette(colors...)
}

func readableColors256() *Palette {
	colors := []uint8{}
	var i uint8
	for i = 0; i < 255; i++ {
		if i == 0 || i == 7 || i == 8 || i == 15 || i == 16 || i == 17 || i > 230 {
			// Skip 31 Shades of Grey, and one hyperintelligent shade of blue.
			continue
		}
		colors = append(colors, i)
	}
	return Color256Palette(colors...)
}

func init() {
	Themes = []Theme{
		{
			id:        "colors",
			names:     readableColors256(),
			sys:       Color256(245),                              // Grey
			pm:        Color256(7),                                // White
			highlight: style(Bold + "\033[48;5;11m\033[38;5;16m"), // Yellow highlight
		},
		{
			id:        "solarized",
			names:     Color256Palette(1, 2, 3, 4, 5, 6, 7, 9, 13),
			sys:       Color256(11),                              // Yellow
			pm:        Color256(15),                              // White
			highlight: style(Bold + "\033[48;5;3m\033[38;5;94m"), // Orange highlight
		},
		{
			id:        "hacker",
			names:     Color256Palette(82),                        // Green
			sys:       Color256(22),                               // Another green
			pm:        Color256(28),                               // More green, slightly lighter
			highlight: style(Bold + "\033[48;5;22m\033[38;5;46m"), // Green on dark green
		},
		{
			id: "mono",
		},
	}

	DefaultTheme = &Themes[0]

	/* Some debug helpers for your convenience:

	// Debug for palettes
	printPalette(allColors256())

	// Debug for themes
	for _, t := range Themes {
		printTheme(t)
	}

	*/
}

func printTheme(t Theme) {
	fmt.Println("Printing theme:", t.ID())
	if t.names != nil {
		for i, color := range t.names.colors {
			fmt.Printf("%s ", color.Format(fmt.Sprintf("name%d", i)))
		}
		fmt.Println("")
	}
	fmt.Println(t.ColorSys("SystemMsg"))
	fmt.Println(t.ColorPM("PrivateMsg"))
	fmt.Println(t.Highlight("Highlight"))
	fmt.Println("")
}

func printPalette(p *Palette) {
	for i, color := range p.colors {
		fmt.Printf("%d\t%s\n", i, color.Format(color.String()+" "))
	}
}
<commit_msg>/timestamp: Remove timezone by default for now<commit_after>package message

import (
	"fmt"
	"time"
)

const timestampLayout = "2006-01-02 15:04:05"

const (
	// Reset resets the color
	Reset = "\033[0m"

	// Bold makes the following text bold
	Bold = "\033[1m"

	// Dim dims the following text
	Dim = "\033[2m"

	// Italic makes the following text italic
	Italic = "\033[3m"

	// Underline underlines the following text
	Underline = "\033[4m"

	// Blink blinks the following text
	Blink = "\033[5m"

	// Invert inverts the following text
	Invert = "\033[7m"

	// Newline
	Newline = "\r\n"

	// BEL
	Bel = "\007"
)

// Interface for Styles
type Style interface {
	String() string
	Format(string) string
}

// General hardcoded style, mostly used as a crutch until we flesh out the
// framework to support backgrounds etc.
type style string

func (c style) String() string {
	return string(c)
}

func (c style) Format(s string) string {
	return c.String() + s + Reset
}

// 256 color type, for terminals who support it
type Color256 uint8

// String version of this color
func (c Color256) String() string {
	return fmt.Sprintf("38;05;%d", c)
}

// Return formatted string with this color
func (c Color256) Format(s string) string {
	return "\033[" + c.String() + "m" + s + Reset
}

func Color256Palette(colors ...uint8) *Palette {
	size := len(colors)
	p := make([]Style, 0, size)
	for _, color := range colors {
		p = append(p, Color256(color))
	}
	return &Palette{
		colors: p,
		size:   size,
	}
}

// No color, used for mono theme
type Color0 struct{}

// No-op for Color0
func (c Color0) String() string {
	return ""
}

// No-op for Color0
func (c Color0) Format(s string) string {
	return s
}

// Container for a collection of colors
type Palette struct {
	colors []Style
	size   int
}

// Get a color by index, overflows are looped around.
func (p Palette) Get(i int) Style {
	if p.size == 1 {
		return p.colors[0]
	}
	return p.colors[i%(p.size-1)]
}

func (p Palette) Len() int {
	return p.size
}

func (p Palette) String() string {
	r := ""
	for _, c := range p.colors {
		r += c.Format("X")
	}
	return r
}

// Collection of settings for chat
type Theme struct {
	id        string
	sys       Style
	pm        Style
	highlight Style
	names     *Palette
}

func (theme Theme) ID() string {
	return theme.id
}

// Colorize name string given some index
func (theme Theme) ColorName(u *User) string {
	if theme.names == nil {
		return u.Name()
	}

	return theme.names.Get(u.colorIdx).Format(u.Name())
}

// Colorize the PM string
func (theme Theme) ColorPM(s string) string {
	if theme.pm == nil {
		return s
	}

	return theme.pm.Format(s)
}

// Colorize the Sys message
func (theme Theme) ColorSys(s string) string {
	if theme.sys == nil {
		return s
	}

	return theme.sys.Format(s)
}

// Highlight a matched string, usually name
func (theme Theme) Highlight(s string) string {
	if theme.highlight == nil {
		return s
	}
	return theme.highlight.Format(s)
}

// Timestamp formats and colorizes the timestamp.
func (theme Theme) Timestamp(t time.Time) string {
	// TODO: Change this per-theme? Or config?
	return theme.sys.Format(t.Format(timestampLayout))
}

// List of initialzied themes
var Themes []Theme

// Default theme to use
var DefaultTheme *Theme

func allColors256() *Palette {
	colors := []uint8{}
	var i uint8
	for i = 0; i < 255; i++ {
		colors = append(colors, i)
	}
	return Color256Palette(colors...)
}

func readableColors256() *Palette {
	colors := []uint8{}
	var i uint8
	for i = 0; i < 255; i++ {
		if i == 0 || i == 7 || i == 8 || i == 15 || i == 16 || i == 17 || i > 230 {
			// Skip 31 Shades of Grey, and one hyperintelligent shade of blue.
			continue
		}
		colors = append(colors, i)
	}
	return Color256Palette(colors...)
}

func init() {
	Themes = []Theme{
		{
			id:        "colors",
			names:     readableColors256(),
			sys:       Color256(245),                              // Grey
			pm:        Color256(7),                                // White
			highlight: style(Bold + "\033[48;5;11m\033[38;5;16m"), // Yellow highlight
		},
		{
			id:        "solarized",
			names:     Color256Palette(1, 2, 3, 4, 5, 6, 7, 9, 13),
			sys:       Color256(11),                              // Yellow
			pm:        Color256(15),                              // White
			highlight: style(Bold + "\033[48;5;3m\033[38;5;94m"), // Orange highlight
		},
		{
			id:        "hacker",
			names:     Color256Palette(82),                        // Green
			sys:       Color256(22),                               // Another green
			pm:        Color256(28),                               // More green, slightly lighter
			highlight: style(Bold + "\033[48;5;22m\033[38;5;46m"), // Green on dark green
		},
		{
			id: "mono",
		},
	}

	DefaultTheme = &Themes[0]

	/* Some debug helpers for your convenience:

	// Debug for palettes
	printPalette(allColors256())

	// Debug for themes
	for _, t := range Themes {
		printTheme(t)
	}

	*/
}

func printTheme(t Theme) {
	fmt.Println("Printing theme:", t.ID())
	if t.names != nil {
		for i, color := range t.names.colors {
			fmt.Printf("%s ", color.Format(fmt.Sprintf("name%d", i)))
		}
		fmt.Println("")
	}
	fmt.Println(t.ColorSys("SystemMsg"))
	fmt.Println(t.ColorPM("PrivateMsg"))
	fmt.Println(t.Highlight("Highlight"))
	fmt.Println("")
}

func printPalette(p *Palette) {
	for i, color := range p.colors {
		fmt.Printf("%d\t%s\n", i, color.Format(color.String()+" "))
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"regexp"
	"strings"

	"gopkg.in/yaml.v2"
)

const (
	Single InputType = iota
	Array
)

var (
	comment       = regexp.MustCompile(`^\s*(?:#|//|--)\s?(.*)$`)
	metadataStart = regexp.MustCompile(`^\s*(?:#|//|--)\s?(\s*-{3}\s*)$`)
	metadataEnd   = regexp.MustCompile(`^\s*(?:#|//|--)\s?(\s*\.{3}\s*)$`)
)

type RightScriptMetadata struct {
	Name        string                   `yaml:"RightScript Name"`
	Description string                   `yaml:"Description,omitempty"`
	Inputs      map[string]InputMetadata `yaml:"Inputs,omitempty"`
	Attachments []string                 `yaml:"Attachments,omitempty"`
}

type InputMetadata struct {
	Category       string       `yaml:"Category,omitempty"`
	Description    string       `yaml:"Description,omitempty"`
	InputType      InputType    `yaml:"Input Type,omitempty"`
	Required       bool         `yaml:"Required,omitempty"`
	Advanced       bool         `yaml:"Advanced,omitempty"`
	Default        *InputValue  `yaml:"Default,omitempty"`
	PossibleValues []InputValue `yaml:"Possible Values,omitempty"`
}

type InputType int

type InputValue struct {
	Type  string
	Value string
}

func ParseRightScriptMetadata(script *os.File) (*RightScriptMetadata, error) {
	defer script.Seek(0, os.SEEK_SET)
	scanner := bufio.NewScanner(script)
	var buffer bytes.Buffer
	inMetadata := false
	for scanner.Scan() {
		line := scanner.Text()
		switch {
		case inMetadata:
			submatches := metadataEnd.FindStringSubmatch(line)
			if submatches != nil {
				buffer.WriteString(submatches[1] + "\n")
				inMetadata = false
				break
			}
			submatches = comment.FindStringSubmatch(line)
			if submatches != nil {
				buffer.WriteString(submatches[1] + "\n")
			}
		case metadataStart.MatchString(line):
			submatches := metadataStart.FindStringSubmatch(line)
			buffer.WriteString(submatches[1] + "\n")
			inMetadata = true
		}
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	if inMetadata {
		return nil, fmt.Errorf("Unterminated RightScript metadata comment")
	}
	var metadata RightScriptMetadata
	// TODO: https://github.com/go-yaml/yaml/issues/136
	err := yaml.Unmarshal(buffer.Bytes(), &metadata)
	if err != nil {
		// TODO: adjust line numbers, etc.
		return &metadata, err
	}
	return &metadata, nil
}

func (i *InputType) MarshalYAML() (interface{}, error) {
	switch *i {
	case Single:
		return "single", nil
	case Array:
		return "array", nil
	default:
		return "", fmt.Errorf("Invalid input type value: %d", *i)
	}
}

func (i *InputType) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var value string
	err := unmarshal(&value)
	if err != nil {
		return err
	}
	switch value {
	case "single":
		*i = Single
	case "array":
		*i = Array
	default:
		return fmt.Errorf("Invalid input type value: %s", value)
	}
	return nil
}

func (i *InputValue) MarshalYAML() (interface{}, error) {
	return i.Type + ":" + i.Value, nil
}

func (i *InputValue) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var value string
	err := unmarshal(&value)
	if err != nil {
		return err
	}
	values := strings.SplitN(value, ":", 2)
	if len(values) < 2 {
		return fmt.Errorf("Invalid input value: %s", value)
	}
	*i = InputValue{Type: values[0], Value: values[1]}
	return nil
}
<commit_msg>Add some String methods<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"regexp"
	"strings"

	"gopkg.in/yaml.v2"
)

const (
	Single InputType = iota
	Array
)

var (
	comment       = regexp.MustCompile(`^\s*(?:#|//|--)\s?(.*)$`)
	metadataStart = regexp.MustCompile(`^\s*(?:#|//|--)\s?(\s*-{3}\s*)$`)
	metadataEnd   = regexp.MustCompile(`^\s*(?:#|//|--)\s?(\s*\.{3}\s*)$`)
)

type RightScriptMetadata struct {
	Name        string                   `yaml:"RightScript Name"`
	Description string                   `yaml:"Description,omitempty"`
	Inputs      map[string]InputMetadata `yaml:"Inputs,omitempty"`
	Attachments []string                 `yaml:"Attachments,omitempty"`
}

type InputMetadata struct {
	Category       string       `yaml:"Category,omitempty"`
	Description    string       `yaml:"Description,omitempty"`
	InputType      InputType    `yaml:"Input Type,omitempty"`
	Required       bool         `yaml:"Required,omitempty"`
	Advanced       bool         `yaml:"Advanced,omitempty"`
	Default        *InputValue  `yaml:"Default,omitempty"`
	PossibleValues []InputValue `yaml:"Possible Values,omitempty"`
}

type InputType int

type InputValue struct {
	Type  string
	Value string
}

func ParseRightScriptMetadata(script *os.File) (*RightScriptMetadata, error) {
	defer script.Seek(0, os.SEEK_SET)
	scanner := bufio.NewScanner(script)
	var buffer bytes.Buffer
	inMetadata := false
	for scanner.Scan() {
		line := scanner.Text()
		switch {
		case inMetadata:
			submatches := metadataEnd.FindStringSubmatch(line)
			if submatches != nil {
				buffer.WriteString(submatches[1] + "\n")
				inMetadata = false
				break
			}
			submatches = comment.FindStringSubmatch(line)
			if submatches != nil {
				buffer.WriteString(submatches[1] + "\n")
			}
		case metadataStart.MatchString(line):
			submatches := metadataStart.FindStringSubmatch(line)
			buffer.WriteString(submatches[1] + "\n")
			inMetadata = true
		}
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	if inMetadata {
		return nil, fmt.Errorf("Unterminated RightScript metadata comment")
	}
	var metadata RightScriptMetadata
	// TODO: https://github.com/go-yaml/yaml/issues/136
	err := yaml.Unmarshal(buffer.Bytes(), &metadata)
	if err != nil {
		// TODO: adjust line numbers, etc.
		return &metadata, err
	}
	return &metadata, nil
}

func (i InputType) String() string {
	switch i {
	case Single:
		return "single"
	case Array:
		return "array"
	default:
		return fmt.Sprintf("%d", i)
	}
}

func (i *InputType) MarshalYAML() (interface{}, error) {
	switch *i {
	case Single, Array:
		return i.String(), nil
	default:
		return "", fmt.Errorf("Invalid input type value: %d", *i)
	}
}

func (i *InputType) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var value string
	err := unmarshal(&value)
	if err != nil {
		return err
	}
	switch value {
	case "single":
		*i = Single
	case "array":
		*i = Array
	default:
		return fmt.Errorf("Invalid input type value: %s", value)
	}
	return nil
}

func (i InputValue) String() string {
	return i.Type + ":" + i.Value
}

func (i *InputValue) MarshalYAML() (interface{}, error) {
	return i.String(), nil
}

func (i *InputValue) UnmarshalYAML(unmarshal func(interface{}) error) error {
	var value string
	err := unmarshal(&value)
	if err != nil {
		return err
	}
	values := strings.SplitN(value, ":", 2)
	if len(values) < 2 {
		return fmt.Errorf("Invalid input value: %s", value)
	}
	*i = InputValue{Type: values[0], Value: values[1]}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"
)

type Metadata struct {
	TVShows  []TVShowMetadata
	Movies   []MovieMetadata
	Capacity string
}

type MovieMetadata struct {
	// TODO
}

type TVShowMetadata struct {
	TMDBId       int
	Name         string
	Overview     string
	Image        string
	Backdrop     string
	FirstAirDate string
	LastAirDate  string
	Seasons      []TVSeasonMetadata
}

type TVSeasonMetadata struct {
	TMDBId   int
	Season   int
	Name     string
	Overview string
	Image    string
	AirDate  string
	Episodes []TVEpisodeMetadata
}

type TVEpisodeMetadata struct {
	TMDBId         int
	Episode        int
	Name           string
	Overview       string
	Image          string
	Media          string
	AirDate        string
	ProductionCode string
	Vote           float32
}

// Generates metadata for everything.
func generateMetadata(paths Paths) {
	log.Println("Generating metadata")

	shows := make([]TVShowMetadata, 0)
	for _, showFolder := range directoriesIn(paths.TV) {

		// Load the metadata.
		var showDetails *TmdbTvShowDetails
		if err := readAndUnmarshal(showFolder, metadataFilename, &showDetails); err != nil {
			continue
		}

		// Create the model.
		image, _ := filepath.Rel(paths.Root, filepath.Join(showFolder, imageFilename))
		backdrop, _ := filepath.Rel(paths.Root, filepath.Join(showFolder, imageBackdropFilename))
		show := TVShowMetadata{
			TMDBId:       showDetails.Id,
			Name:         showDetails.Name,
			Overview:     showDetails.Overview,
			Image:        image,
			Backdrop:     backdrop,
			FirstAirDate: showDetails.FirstAirDate,
			LastAirDate:  showDetails.LastAirDate,
		}

		// Find the seasons.
		for _, seasonFolder := range directoriesIn(showFolder) {
			var seasonDetails *TmdbTvSeasonDetails
			if err := readAndUnmarshal(seasonFolder, metadataFilename, &seasonDetails); err != nil {
				continue
			}

			image, _ := filepath.Rel(paths.Root, filepath.Join(seasonFolder, imageFilename))
			season := TVSeasonMetadata{
				TMDBId:   seasonDetails.Id,
				Season:   seasonDetails.SeasonNumber,
				Name:     seasonDetails.Name,
				Overview: seasonDetails.Overview,
				Image:    image,
				AirDate:  seasonDetails.AirDate,
			}

			// Find the episodes.
			for _, epFolder := range directoriesIn(seasonFolder) {
				var epDetails *TmdbTvEpisodeDetails
				if err := readAndUnmarshal(epFolder, metadataFilename, &epDetails); err != nil {
					continue
				}

				image, _ := filepath.Rel(paths.Root, filepath.Join(epFolder, imageFilename))
				media, _ := filepath.Rel(paths.Root, filepath.Join(epFolder, hlsFilename))
				episode := TVEpisodeMetadata{
					TMDBId:         epDetails.Id,
					Episode:        epDetails.EpisodeNumber,
					Name:           epDetails.Name,
					Overview:       epDetails.Overview,
					Image:          image,
					Media:          media,
					AirDate:        epDetails.AirDate,
					ProductionCode: epDetails.ProductionCode,
					Vote:           epDetails.VoteAverage,
				}
				season.Episodes = append(season.Episodes, episode)
			}
			sort.Sort(ByEpisode(season.Episodes))

			show.Seasons = append(show.Seasons, season)
			generateSeasonHTML(seasonFolder, season, paths)
		}
		sort.Sort(BySeason(show.Seasons))

		shows = append(shows, show)
		generateShowHTML(showFolder, show, paths)
	}

	// Make the root metadata.
	capacity := capacity(paths)
	metadata := Metadata{
		TVShows:  shows,
		Movies:   nil, // TODO
		Capacity: capacity,
	}

	// Save.
	data, _ := json.MarshalIndent(metadata, "", "    ")
	ioutil.WriteFile(filepath.Join(paths.Root, metadataFilename), data, os.ModePerm)

	generateRootHTML(capacity, paths)
}

// List of full directories (eg path + name).
func directoriesIn(path string) []string {
	directories := make([]string, 0)
	infos, _ := ioutil.ReadDir(path)
	for _, info := range infos {
		if info.IsDir() {
			directory := filepath.Join(path, info.Name())
			directories = append(directories, directory)
		}
	}
	return directories
}

func readAndUnmarshal(folder string, file string, v interface{}) error {
	path := filepath.Join(folder, file)
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}
	return json.Unmarshal(data, v)
}

// Sorting.

type ByEpisode []TVEpisodeMetadata

func (a ByEpisode) Len() int      { return len(a) }
func (a ByEpisode) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByEpisode) Less(i, j int) bool {
	return a[i].Episode < a[j].Episode
}

type BySeason []TVSeasonMetadata

func (a BySeason) Len() int      { return len(a) }
func (a BySeason) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a BySeason) Less(i, j int) bool {
	return a[i].Season < a[j].Season
}

/// Generate a season's html (which is the list of episodes in it).
func generateSeasonHTML(seasonPath string, season TVSeasonMetadata, paths Paths) {
	html := htmlStart
	isLeft := true
	trOpen := false

	for _, episode := range season.Episodes {
		if isLeft {
			html += "
"
			trOpen = true
		}

		linkPath, _ := filepath.Rel(seasonPath, filepath.Join(paths.Root, episode.Media))
		imagePath, _ := filepath.Rel(seasonPath, filepath.Join(paths.Root, episode.Image))
		name := fmt.Sprintf("S%02d E%02d %s", season.Season, episode.Episode, episode.Name)

		h := htmlTd
		h = strings.Replace(h, "LINK", linkPath, -1)
		h = strings.Replace(h, "IMAGE", imagePath, -1)
		h = strings.Replace(h, "NAME", name, -1)
		html += h

		if !isLeft {
			html += "
"
			trOpen = false
		}

		isLeft = !isLeft
	}

	if trOpen {
		html += "
"
	}
	html += htmlEnd

	// Save.
	outPath := filepath.Join(seasonPath, indexHtml)
	ioutil.WriteFile(outPath, []byte(html), os.ModePerm)
}

/// Generates the root index.html
func generateRootHTML(capacity string, paths Paths) {
	html := htmlStart
	isLeft := true
	trOpen := false

	// Movies.
	movieFiles, _ := ioutil.ReadDir(paths.Movies)
	for _, fileInfo := range movieFiles {
		if fileInfo.IsDir() {
			if isLeft {
				html += "
"
				trOpen = true
			}

			linkPath := paths.MoviesRelativeToRoot + "/" + fileInfo.Name() + "/" + hlsFilename
			imagePath := paths.MoviesRelativeToRoot + "/" + fileInfo.Name() + "/" + imageFilename

			h := htmlTd
			h = strings.Replace(h, "LINK", linkPath, -1)
			h = strings.Replace(h, "IMAGE", imagePath, -1)
			h = strings.Replace(h, "NAME", fileInfo.Name(), -1)
			html += h

			if !isLeft {
				html += "
"
				trOpen = false
			}

			isLeft = !isLeft
		}
	}

	// TV Shows.
	tvFiles, _ := ioutil.ReadDir(paths.TV)
	for _, fileInfo := range tvFiles {
		if fileInfo.IsDir() {
			if isLeft {
				html += "
"
				trOpen = true
			}

			linkPath := paths.TVRelativeToRoot + "/" + fileInfo.Name()
			imagePath := paths.TVRelativeToRoot + "/" + fileInfo.Name() + "/" + imageFilename

			h := htmlTd
			h = strings.Replace(h, "LINK", linkPath, -1)
			h = strings.Replace(h, "IMAGE", imagePath, -1)
			h = strings.Replace(h, "NAME", fileInfo.Name(), -1)
			html += h

			if !isLeft {
				html += "
"
				trOpen = false
			}

			isLeft = !isLeft
		}
	}

	if trOpen {
		html += "
"
	}

	// Add the html trailer.
	end := strings.Replace(htmlEnd, "CAPACITY", capacity, -1)
	html += end

	// Save.
	outPath := filepath.Join(paths.Root, indexHtml)
	ioutil.WriteFile(outPath, []byte(html), os.ModePerm)
}

/// Generate a show's html (which is the list of seasons in it).
func generateShowHTML(showPath string, show TVShowMetadata, paths Paths) {
	html := htmlStart
	isLeft := true
	trOpen := false

	for _, season := range show.Seasons {
		if isLeft {
			html += "
"
			trOpen = true
		}

		linkPath := tvSeasonFolderNameFor(season.Season)
		imagePath := linkPath + "/" + imageFilename
		name := season.Name

		h := htmlTd
		h = strings.Replace(h, "LINK", linkPath, -1)
		h = strings.Replace(h, "IMAGE", imagePath, -1)
		h = strings.Replace(h, "NAME", name, -1)
		html += h

		if !isLeft {
			html += "
"
			trOpen = false
		}

		isLeft = !isLeft
	}

	if trOpen {
		html += "
"
	}
	html += htmlEnd

	// Save.
	outPath := filepath.Join(showPath, indexHtml)
	ioutil.WriteFile(outPath, []byte(html), os.ModePerm)
}

/// Figure out how much disk space is left.
func capacity(paths Paths) string {
	cmd := exec.Command("df", "-h", paths.Root)
	var out bytes.Buffer
	cmd.Stdout = &out
	cmd.Run()
	// TODO split and transpose
	return strings.TrimSpace(out.String())
}
<commit_msg>List movies metadata<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"
)

type Metadata struct {
	TVShows  []TVShowMetadata
	Movies   []MovieMetadata
	Capacity string
}

type MovieMetadata struct {
	TMDBId      int
	Name        string
	Overview    string
	Image       string
	Backdrop    string
	ReleaseDate string
	Vote        float32
}

type TVShowMetadata struct {
	TMDBId       int
	Name         string
	Overview     string
	Image        string
	Backdrop     string
	FirstAirDate string
	LastAirDate  string
	Seasons      []TVSeasonMetadata
}

type TVSeasonMetadata struct {
	TMDBId   int
	Season   int
	Name     string
	Overview string
	Image    string
	AirDate  string
	Episodes []TVEpisodeMetadata
}

type TVEpisodeMetadata struct {
	TMDBId         int
	Episode        int
	Name           string
	Overview       string
	Image          string
	Media          string
	AirDate        string
	ProductionCode string
	Vote           float32
}

// Generates metadata for everything.
func generateMetadata(paths Paths) {
	log.Println("Generating metadata")

	shows := make([]TVShowMetadata, 0)
	for _, showFolder := range directoriesIn(paths.TV) {

		// Load the metadata.
		var showDetails *TmdbTvShowDetails
		if err := readAndUnmarshal(showFolder, metadataFilename, &showDetails); err != nil {
			continue
		}

		// Create the model.
		image, _ := filepath.Rel(paths.Root, filepath.Join(showFolder, imageFilename))
		backdrop, _ := filepath.Rel(paths.Root, filepath.Join(showFolder, imageBackdropFilename))
		show := TVShowMetadata{
			TMDBId:       showDetails.Id,
			Name:         showDetails.Name,
			Overview:     showDetails.Overview,
			Image:        image,
			Backdrop:     backdrop,
			FirstAirDate: showDetails.FirstAirDate,
			LastAirDate:  showDetails.LastAirDate,
		}

		// Find the seasons.
		for _, seasonFolder := range directoriesIn(showFolder) {
			var seasonDetails *TmdbTvSeasonDetails
			if err := readAndUnmarshal(seasonFolder, metadataFilename, &seasonDetails); err != nil {
				continue
			}

			image, _ := filepath.Rel(paths.Root, filepath.Join(seasonFolder, imageFilename))
			season := TVSeasonMetadata{
				TMDBId:   seasonDetails.Id,
				Season:   seasonDetails.SeasonNumber,
				Name:     seasonDetails.Name,
				Overview: seasonDetails.Overview,
				Image:    image,
				AirDate:  seasonDetails.AirDate,
			}

			// Find the episodes.
			for _, epFolder := range directoriesIn(seasonFolder) {
				var epDetails *TmdbTvEpisodeDetails
				if err := readAndUnmarshal(epFolder, metadataFilename, &epDetails); err != nil {
					continue
				}

				image, _ := filepath.Rel(paths.Root, filepath.Join(epFolder, imageFilename))
				media, _ := filepath.Rel(paths.Root, filepath.Join(epFolder, hlsFilename))
				episode := TVEpisodeMetadata{
					TMDBId:         epDetails.Id,
					Episode:        epDetails.EpisodeNumber,
					Name:           epDetails.Name,
					Overview:       epDetails.Overview,
					Image:          image,
					Media:          media,
					AirDate:        epDetails.AirDate,
					ProductionCode: epDetails.ProductionCode,
					Vote:           epDetails.VoteAverage,
				}
				season.Episodes = append(season.Episodes, episode)
			}
			sort.Sort(ByEpisode(season.Episodes))

			show.Seasons = append(show.Seasons, season)
			generateSeasonHTML(seasonFolder, season, paths)
		}
		sort.Sort(BySeason(show.Seasons))

		shows = append(shows, show)
		generateShowHTML(showFolder, show, paths)
	}

	// Find the movies.
	movies := make([]MovieMetadata, 0)
	for _, folder := range directoriesIn(paths.Movies) {
		// Load the metadata.
		var details *TmdbMovieSearchResult
		if err := readAndUnmarshal(folder, metadataFilename, &details); err != nil {
			continue
		}

		// Create the model.
		image, _ := filepath.Rel(paths.Root, filepath.Join(folder, imageFilename))
		backdrop, _ := filepath.Rel(paths.Root, filepath.Join(folder, imageBackdropFilename))
		movie := MovieMetadata{
			TMDBId:      details.Id,
			Name:        details.Title,
			Overview:    details.Overview,
			Image:       image,
			Backdrop:    backdrop,
			ReleaseDate: details.ReleaseDate,
			Vote:        details.VoteAverage,
		}
		movies = append(movies, movie)
	}

	// Make the root metadata.
	capacity := capacity(paths)
	metadata := Metadata{
		TVShows:  shows,
		Movies:   movies,
		Capacity: capacity,
	}

	// Save.
	data, _ := json.MarshalIndent(metadata, "", "    ")
	ioutil.WriteFile(filepath.Join(paths.Root, metadataFilename), data, os.ModePerm)

	generateRootHTML(capacity, paths)
}

// List of full directories (eg path + name).
func directoriesIn(path string) []string {
	directories := make([]string, 0)
	infos, _ := ioutil.ReadDir(path)
	for _, info := range infos {
		if info.IsDir() {
			directory := filepath.Join(path, info.Name())
			directories = append(directories, directory)
		}
	}
	return directories
}

func readAndUnmarshal(folder string, file string, v interface{}) error {
	path := filepath.Join(folder, file)
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return err
	}
	return json.Unmarshal(data, v)
}

// Sorting.

type ByEpisode []TVEpisodeMetadata

func (a ByEpisode) Len() int      { return len(a) }
func (a ByEpisode) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByEpisode) Less(i, j int) bool {
	return a[i].Episode < a[j].Episode
}

type BySeason []TVSeasonMetadata

func (a BySeason) Len() int      { return len(a) }
func (a BySeason) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a BySeason) Less(i, j int) bool {
	return a[i].Season < a[j].Season
}

/// Generate a season's html (which is the list of episodes in it).
func generateSeasonHTML(seasonPath string, season TVSeasonMetadata, paths Paths) {
	html := htmlStart
	isLeft := true
	trOpen := false

	for _, episode := range season.Episodes {
		if isLeft {
			html += "
"
			trOpen = true
		}

		linkPath, _ := filepath.Rel(seasonPath, filepath.Join(paths.Root, episode.Media))
		imagePath, _ := filepath.Rel(seasonPath, filepath.Join(paths.Root, episode.Image))
		name := fmt.Sprintf("S%02d E%02d %s", season.Season, episode.Episode, episode.Name)

		h := htmlTd
		h = strings.Replace(h, "LINK", linkPath, -1)
		h = strings.Replace(h, "IMAGE", imagePath, -1)
		h = strings.Replace(h, "NAME", name, -1)
		html += h

		if !isLeft {
			html += "
"
			trOpen = false
		}

		isLeft = !isLeft
	}

	if trOpen {
		html += "
"
	}
	html += htmlEnd

	// Save.
	outPath := filepath.Join(seasonPath, indexHtml)
	ioutil.WriteFile(outPath, []byte(html), os.ModePerm)
}

/// Generates the root index.html
func generateRootHTML(capacity string, paths Paths) {
	html := htmlStart
	isLeft := true
	trOpen := false

	// Movies.
	movieFiles, _ := ioutil.ReadDir(paths.Movies)
	for _, fileInfo := range movieFiles {
		if fileInfo.IsDir() {
			if isLeft {
				html += "
"
				trOpen = true
			}

			linkPath := paths.MoviesRelativeToRoot + "/" + fileInfo.Name() + "/" + hlsFilename
			imagePath := paths.MoviesRelativeToRoot + "/" + fileInfo.Name() + "/" + imageFilename

			h := htmlTd
			h = strings.Replace(h, "LINK", linkPath, -1)
			h = strings.Replace(h, "IMAGE", imagePath, -1)
			h = strings.Replace(h, "NAME", fileInfo.Name(), -1)
			html += h

			if !isLeft {
				html += "
"
				trOpen = false
			}

			isLeft = !isLeft
		}
	}

	// TV Shows.
	tvFiles, _ := ioutil.ReadDir(paths.TV)
	for _, fileInfo := range tvFiles {
		if fileInfo.IsDir() {
			if isLeft {
				html += "
"
				trOpen = true
			}

			linkPath := paths.TVRelativeToRoot + "/" + fileInfo.Name()
			imagePath := paths.TVRelativeToRoot + "/" + fileInfo.Name() + "/" + imageFilename

			h := htmlTd
			h = strings.Replace(h, "LINK", linkPath, -1)
			h = strings.Replace(h, "IMAGE", imagePath, -1)
			h = strings.Replace(h, "NAME", fileInfo.Name(), -1)
			html += h

			if !isLeft {
				html += "
"
				trOpen = false
			}

			isLeft = !isLeft
		}
	}

	if trOpen {
		html += "
"
	}

	// Add the html trailer.
	end := strings.Replace(htmlEnd, "CAPACITY", capacity, -1)
	html += end

	// Save.
	outPath := filepath.Join(paths.Root, indexHtml)
	ioutil.WriteFile(outPath, []byte(html), os.ModePerm)
}

/// Generate a show's html (which is the list of seasons in it).
func generateShowHTML(showPath string, show TVShowMetadata, paths Paths) {
	html := htmlStart
	isLeft := true
	trOpen := false

	for _, season := range show.Seasons {
		if isLeft {
			html += "
"
			trOpen = true
		}

		linkPath := tvSeasonFolderNameFor(season.Season)
		imagePath := linkPath + "/" + imageFilename
		name := season.Name

		h := htmlTd
		h = strings.Replace(h, "LINK", linkPath, -1)
		h = strings.Replace(h, "IMAGE", imagePath, -1)
		h = strings.Replace(h, "NAME", name, -1)
		html += h

		if !isLeft {
			html += "
"
			trOpen = false
		}

		isLeft = !isLeft
	}

	if trOpen {
		html += "
"
	}
	html += htmlEnd

	// Save.
	outPath := filepath.Join(showPath, indexHtml)
	ioutil.WriteFile(outPath, []byte(html), os.ModePerm)
}

/// Figure out how much disk space is left.
func capacity(paths Paths) string {
	cmd := exec.Command("df", "-h", paths.Root)
	var out bytes.Buffer
	cmd.Stdout = &out
	cmd.Run()
	// TODO split and transpose
	return strings.TrimSpace(out.String())
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"net"
	"net/http"
	"os"
	"os/exec"
	"text/template"
)

type BindDnsInstance struct {
	dns_backend_point
	Templates  *template.Template
	ServerName string
}

func buildZone(name string, zoneData *ZoneData) Zone {

	records := make([]Record, 0, 100)

	if zoneData != nil {
		for name, entry := range zoneData.Entries {
			for t, contents := range entry.Types {
				for _, content := range contents {
					record := Record{
						Name:    name,
						Content: content.Content,
						Type:    t,
					}
					records = append(records, record)
				}
			}
		}
	}

	zone := Zone{
		Name:    name,
		Records: records,
	}

	return zone
}

func NewBindDnsInstance(serverName string) *BindDnsInstance {
	return &BindDnsInstance{
		Templates:  template.Must(template.ParseGlob("/etc/dns_mgmt.d/*.tmpl")),
		ServerName: serverName,
	}
}

// List function
func (di *BindDnsInstance) GetAllZones(zones *ZoneTracker) ([]Zone, *backendError) {
	answer := make([]Zone, 0, 10)
	for k, v := range zones.Zones {
		answer = append(answer, buildZone(k, v))
	}

	return answer, nil
}

// Get function
func (di *BindDnsInstance) GetZone(zones *ZoneTracker, id string) (Zone, *backendError) {
	zdata := zones.Zones[id]
	if zdata == nil {
		return Zone{}, &backendError{"Not Found", 404}
	}

	return buildZone(id, zdata), nil
}

type bindZoneData struct {
	Domain           string
	ServerName       string
	Data             *ZoneData
	ReverseZoneNames []string
}

type bindRZoneData struct {
	Domain     string
	ServerName string
	Name       string
}

func makeRevName(t string, address string) string {
	var buffer bytes.Buffer
	var count, end int

	if t == "A" {
		count = 15
		end = 12
	} else {
		count = 15
		end = 0
	}

	ip := net.ParseIP(address)
	for i := count; i >= end; i-- {
		if t == "A" {
			buffer.WriteString(fmt.Sprintf("%d.", ip[i]))
		} else {
			buffer.WriteString(fmt.Sprintf("%x.", ip[i]&0xf))
			buffer.WriteString(fmt.Sprintf("%x.", ((ip[i] >> 4) & 0xf)))
		}
	}

	if t == "A" {
		buffer.WriteString("in-addr.arpa")
	} else {
		buffer.WriteString("ip6.arpa")
	}

	return buffer.String()
}

// Patch function
func (di *BindDnsInstance) PatchZone(zones *ZoneTracker, zoneName string, rec Record) (Zone, *backendError) {

	// Rebuild include list
	file, err := os.Create("/etc/bind/named.conf.crowbar")
	defer file.Close()
	if err != nil {
		return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
	}
	err = di.Templates.ExecuteTemplate(file, "zones_list.tmpl", zones)
	if err != nil {
		return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
	}

	err = os.RemoveAll("/etc/bind/" + zoneName)
	if err != nil {
		return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
	}

	if zones.Zones[zoneName] != nil {
		revz := make([]string, 0, 100)
		zone := zones.Zones[zoneName]

		err = os.Mkdir("/etc/bind/"+zoneName, 0755)
		if err != nil {
			return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
		}

		// Build reverse maps
		for name, entry := range zone.Entries {
			for t, contents := range entry.Types {
				for _, content := range contents {
					rvName := makeRevName(t, content.Content)
					revz = append(revz, rvName)

					rdata := bindRZoneData{
						Domain:     rvName,
						ServerName: di.ServerName,
						Name:       name + "." + zoneName,
					}

					rfile, err := os.Create("/etc/bind/" + zoneName + "/rdb." + rvName)
					defer rfile.Close()
					if err != nil {
						return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
					}
					err = di.Templates.ExecuteTemplate(rfile, "rdb.tmpl", rdata)
					if err != nil {
						return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
					}

				}
			}
		}

		// Make a database file.
		zdata := bindZoneData{
			Domain:           zoneName,
			ServerName:       di.ServerName,
			Data:             zone,
			ReverseZoneNames: revz,
		}

		zfile, err := os.Create("/etc/bind/" + zoneName + "/zone." + zoneName)
		defer zfile.Close()
		if err != nil {
			return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
		}
		err = di.Templates.ExecuteTemplate(zfile, "zone.tmpl", zdata)
		if err != nil {
			return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
		}

		dfile, err := os.Create("/etc/bind/" + zoneName + "/db." + zoneName)
		defer dfile.Close()
		if err != nil {
			return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
		}
		err = di.Templates.ExecuteTemplate(dfile, "db.tmpl", zdata)
		if err != nil {
			return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
		}
	}

	// Restart bind
	cmd := exec.Command("service", "named", "restart")
	err = cmd.Run()
	if err != nil {
		return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
	}

	return buildZone(zoneName, zones.Zones[zoneName]), nil
}
<commit_msg>Get the name right<commit_after>package main

import (
	"bytes"
	"fmt"
	"net"
	"net/http"
	"os"
	"os/exec"
	"text/template"
)

type BindDnsInstance struct {
	dns_backend_point
	Templates  *template.Template
	ServerName string
}

func buildZone(name string, zoneData *ZoneData) Zone {

	records := make([]Record, 0, 100)

	if zoneData != nil {
		for name, entry := range zoneData.Entries {
			for t, contents := range entry.Types {
				for _, content := range contents {
					record := Record{
						Name:    name,
						Content: content.Content,
						Type:    t,
					}
					records = append(records, record)
				}
			}
		}
	}

	zone := Zone{
		Name:    name,
		Records: records,
	}

	return zone
}

func NewBindDnsInstance(serverName string) *BindDnsInstance {
	return &BindDnsInstance{
		Templates:  template.Must(template.ParseGlob("/etc/dns-mgmt.d/*.tmpl")),
		ServerName: serverName,
	}
}

// List function
func (di *BindDnsInstance) GetAllZones(zones *ZoneTracker) ([]Zone, *backendError) {
	answer := make([]Zone, 0, 10)
	for k, v := range zones.Zones {
		answer = append(answer, buildZone(k, v))
	}

	return answer, nil
}

// Get function
func (di *BindDnsInstance) GetZone(zones *ZoneTracker, id string) (Zone, *backendError) {
	zdata := zones.Zones[id]
	if zdata == nil {
		return Zone{}, &backendError{"Not Found", 404}
	}

	return buildZone(id, zdata), nil
}

type bindZoneData struct {
	Domain           string
	ServerName       string
	Data             *ZoneData
	ReverseZoneNames []string
}

type bindRZoneData struct {
	Domain     string
	ServerName string
	Name       string
}

func makeRevName(t string, address string) string {
	var buffer bytes.Buffer
	var count, end int

	if t == "A" {
		count = 15
		end = 12
	} else {
		count = 15
		end = 0
	}

	ip := net.ParseIP(address)
	for i := count; i >= end; i-- {
		if t == "A" {
			buffer.WriteString(fmt.Sprintf("%d.", ip[i]))
		} else {
			buffer.WriteString(fmt.Sprintf("%x.", ip[i]&0xf))
			buffer.WriteString(fmt.Sprintf("%x.", ((ip[i] >> 4) & 0xf)))
		}
	}

	if t == "A" {
		buffer.WriteString("in-addr.arpa")
	} else {
		buffer.WriteString("ip6.arpa")
	}

	return buffer.String()
}

// Patch function
func (di *BindDnsInstance) PatchZone(zones *ZoneTracker, zoneName string, rec Record) (Zone, *backendError) {

	// Rebuild include list
	file, err := os.Create("/etc/bind/named.conf.crowbar")
	defer file.Close()
	if err != nil {
		return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
	}
	err = di.Templates.ExecuteTemplate(file, "zones_list.tmpl", zones)
	if err != nil {
		return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
	}

	err = os.RemoveAll("/etc/bind/" + zoneName)
	if err != nil {
		return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
	}

	if zones.Zones[zoneName] != nil {
		revz := make([]string, 0, 100)
		zone := zones.Zones[zoneName]

		err = os.Mkdir("/etc/bind/"+zoneName, 0755)
		if err != nil {
			return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
		}

		// Build reverse maps
		for name, entry := range zone.Entries {
			for t, contents := range entry.Types {
				for _, content := range contents {
					rvName := makeRevName(t, content.Content)
					revz = append(revz, rvName)

					rdata := bindRZoneData{
						Domain:     rvName,
						ServerName: di.ServerName,
						Name:       name + "." + zoneName,
					}

					rfile, err := os.Create("/etc/bind/" + zoneName + "/rdb." + rvName)
					defer rfile.Close()
					if err != nil {
						return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
					}
					err = di.Templates.ExecuteTemplate(rfile, "rdb.tmpl", rdata)
					if err != nil {
						return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
					}

				}
			}
		}

		// Make a database file.
		zdata := bindZoneData{
			Domain:           zoneName,
			ServerName:       di.ServerName,
			Data:             zone,
			ReverseZoneNames: revz,
		}

		zfile, err := os.Create("/etc/bind/" + zoneName + "/zone." + zoneName)
		defer zfile.Close()
		if err != nil {
			return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
		}
		err = di.Templates.ExecuteTemplate(zfile, "zone.tmpl", zdata)
		if err != nil {
			return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
		}

		dfile, err := os.Create("/etc/bind/" + zoneName + "/db." + zoneName)
		defer dfile.Close()
		if err != nil {
			return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
		}
		err = di.Templates.ExecuteTemplate(dfile, "db.tmpl", zdata)
		if err != nil {
			return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
		}
	}

	// Restart bind
	cmd := exec.Command("service", "named", "restart")
	err = cmd.Run()
	if err != nil {
		return Zone{}, &backendError{err.Error(), http.StatusInternalServerError}
	}

	return buildZone(zoneName, zones.Zones[zoneName]), nil
}
<|endoftext|>


<commit_before>package blinkstick

import (
	"fmt"
	"image/color"

	"github.com/yesnault/hid"
)

// Version of Blinkstick
// One Line for this, used by release.sh script
// Keep "const Version on one line"
const Version = "0.1.6"

// vendorID blinkstick
const vendorID = 0x20a0

// productID blinkstick
const productID = 0x41e5

// usbDevice ...
type usbDevice struct {
	DeviceInfo *hid.DeviceInfo
	Device     *hid.Device
}

// Blinkstick represents a blinkstick device
type Blinkstick interface {
	List() []Blinkstick
	SetColor(color.Color) error
	Blink(color.Color, int, int) error
	GetDeviceInfo() *hid.DeviceInfo
	ListFilter(hid *hid.DeviceInfo) (bool, Blinkstick)
	getUSBDevice() *usbDevice
}

// SetColor set color
func (usbDevice *usbDevice) setColor(c color.Color, index byte) error {
	if usbDevice.Device == nil {
		if err := usbDevice.Open(); err != nil {
			return err
		}
	}
	r, g, b, _ := c.RGBA()
	d := *usbDevice.Device
	return d.WriteFeature([]byte{0x05, 0x00, index, byte(r >> 8), byte(g >> 8), byte(b >> 8)})
}

// Open open a device
func (usbDevice *usbDevice) Open() error {
	device, err := usbDevice.DeviceInfo.Open()
	if err != nil {
		return fmt.Errorf("Error while opening device: %s", err)
	}
	usbDevice.Device = &device
	return nil
}

// ListFilter is used to filter device on List
type ListFilter func(*hid.DeviceInfo) (bool, Blinkstick)

// List gets all blinkstick device
func List(opts ...ListFilter) []Blinkstick {
	out := []Blinkstick{}

	if len(opts) == 0 {
		opts = append(opts, Nano{}.ListFilter)
		opts = append(opts, Flex{}.ListFilter)
		opts = append(opts, Strip{}.ListFilter)
	}

	for di := range hid.Devices() {
		if di.VendorId == vendorID && di.ProductId == productID {
			//			fmt.Printf("di: %+v", di)
			for _, o := range opts {
				if toKeep, blinkstick := o(di); toKeep {
					out = append(out, blinkstick)
				}
			}

		}
	}
	return out
}
<commit_msg>[auto] bump version to v0.1.7<commit_after>package blinkstick

import (
	"fmt"
	"image/color"

	"github.com/yesnault/hid"
)

// Version of Blinkstick
// One Line for this, used by release.sh script
// Keep "const Version on one line"
const Version = "0.1.7"

// vendorID blinkstick
const vendorID = 0x20a0

// productID blinkstick
const productID = 0x41e5

// usbDevice ...
type usbDevice struct {
	DeviceInfo *hid.DeviceInfo
	Device     *hid.Device
}

// Blinkstick represents a blinkstick device
type Blinkstick interface {
	List() []Blinkstick
	SetColor(color.Color) error
	Blink(color.Color, int, int) error
	GetDeviceInfo() *hid.DeviceInfo
	ListFilter(hid *hid.DeviceInfo) (bool, Blinkstick)
	getUSBDevice() *usbDevice
}

// SetColor set color
func (usbDevice *usbDevice) setColor(c color.Color, index byte) error {
	if usbDevice.Device == nil {
		if err := usbDevice.Open(); err != nil {
			return err
		}
	}
	r, g, b, _ := c.RGBA()
	d := *usbDevice.Device
	return d.WriteFeature([]byte{0x05, 0x00, index, byte(r >> 8), byte(g >> 8), byte(b >> 8)})
}

// Open open a device
func (usbDevice *usbDevice) Open() error {
	device, err := usbDevice.DeviceInfo.Open()
	if err != nil {
		return fmt.Errorf("Error while opening device: %s", err)
	}
	usbDevice.Device = &device
	return nil
}

// ListFilter is used to filter device on List
type ListFilter func(*hid.DeviceInfo) (bool, Blinkstick)

// List gets all blinkstick device
func List(opts ...ListFilter) []Blinkstick {
	out := []Blinkstick{}

	if len(opts) == 0 {
		opts = append(opts, Nano{}.ListFilter)
		opts = append(opts, Flex{}.ListFilter)
		opts = append(opts, Strip{}.ListFilter)
	}

	for di := range hid.Devices() {
		if di.VendorId == vendorID && di.ProductId == productID {
			//			fmt.Printf("di: %+v", di)
			for _, o := range opts {
				if toKeep, blinkstick := o(di); toKeep {
					out = append(out, blinkstick)
				}
			}

		}
	}
	return out
}
<|endoftext|>


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

package main

import (
	"log"
	"sync"
	"time"

	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/util"
)

const (
	blobObjectNamePrefix = "blobs/"
)

var g_blobStoreOnce sync.Once
var g_blobStore blob.Store

// A generous lower bound for where the OS starts to tell us to fuck off if we
// have too many files. This may also cover the case where we get "no such
// host" errors, apparently because we do too many lookups all at once.
const osFileLimit = 128

func minInt(a, b int) int {
	if a < b {
		return a
	}

	return b
}

func initBlobStore() {
	bucket := getBucket()
	crypter := getCrypter()
	stateStruct := getState()

	// Store blobs in GCS.
	gcsStore := blob.NewGCSStore(bucket, blobObjectNamePrefix)
	g_blobStore = gcsStore

	// If we don't know the set of hex scores in the store, or the set of scores
	// is stale, re-list.
	age := time.Now().Sub(stateStruct.RelistTime)
	const maxAge = 30 * 24 * time.Hour

	if stateStruct.ExistingScores == nil || age > maxAge {
		log.Println("Listing existing scores...")

		stateStruct.RelistTime = time.Now()
		allScores, err := gcsStore.List()
		if err != nil {
			log.Fatalln("g_blobStore.List:", err)
		}

		log.Printf(
			"Listed %d scores in %v.",
			len(allScores),
			time.Since(stateStruct.RelistTime))

		stateStruct.ExistingScores = util.NewStringSet()
		for _, score := range allScores {
			stateStruct.ExistingScores.Add(score.Hex())
		}
	}

	// Respond efficiently to Contains requests.
	g_blobStore = blob.NewExistingScoresStore(
		stateStruct.ExistingScores,
		g_blobStore)

	// Buffer around GCS with bounded parallelism, allowing file system
	// scanning to proceed independent of waiting for GCS to ack writes.
	const latencySecs = 2
	const bandwidthBytesPerSec = 50e6
	const bandwidthHz = 512

	g_blobStore = blob.NewBufferingStore(
		3*bandwidthBytesPerSec*latencySecs,
		minInt(osFileLimit, 3*bandwidthHz*latencySecs),
		g_blobStore)

	// Make paranoid checks on the results.
	g_blobStore = blob.NewCheckingStore(g_blobStore)

	// Encrypt blob data before sending it off to GCS.
	g_blobStore = blob.NewEncryptingStore(crypter, g_blobStore)
}

func getBlobStore() blob.Store {
	g_blobStoreOnce.Do(initBlobStore)
	return g_blobStore
}
<commit_msg>Deleted blob_store.go.<commit_after><|endoftext|>


<commit_before>// Package application contains the server-side application code.
package application

/*
 * The MIT License (MIT)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
<commit_msg>Added some more tests which still require improvements<commit_after>// Package application contains the server-side application code.
package application

/*
 * The MIT License (MIT)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
import (
    "testing"
    "github.com/miekg/dns"
)

const badDomain = "goooolang.org"
const domain = "golang.org"
const record = "a"
var server = Server{ Server: "8.8.4.4" }

// TODO Mock the DNS requests!
// TODO Test for the content of the DNS answer
func TestDnsQuery_Query(t *testing.T) {
    instance := DnsQuery{ Domain: domain, Record: record, Server: server }
    records, _, err := instance.Query()

    if err != nil {
        t.Error("There should be no errors in this request (unless of course the server is down)")
    }

    if (len(records) != 1) {
        t.Errorf("Response should have returned a single record. It returned %d", len(records))
    }
}

func TestDnsQuery_QueryBadDomain(t *testing.T) {
    instance := DnsQuery{ Domain: badDomain, Record: record, Server: server }
    records, _, err := instance.Query()

    if err == nil {
        t.Error("There should be an errors in this request")
    }

    if err != nil && err != ErrNoRecords {
        t.Errorf("The function should have returned -- %s -- but it returned -- %s --", ErrNoRecords.Error(), err.Error())
    }

    if (len(records) != 0) {
        t.Errorf("Response should have returned a single record. It returned %d", len(records))
    }
}

func TestDnsQuery_GetResponse(t *testing.T) {
    query := DnsQuery{ Domain: domain, Record: record, Server: server }
    response := query.GetResponse()

    if len(response.Message) != 0 {
        t.Error("There should be no errors in this valid request")
    }

    if response.Server != server {
        t.Error("The server returned in the response shouldn't be different from the original")
    }

    if response.Records.Type != record {
        t.Errorf("The record returned in the response -- %s -- is different from the one that should have been sent -- %s --", response.Records.Type, record)
    }

    if len(response.Records.Data) != 1 {
        t.Errorf("There are %d record in the response when there should only be 1 -- %s", len(response.Records.Data), response.Records)
    }
}

func TestDnsQuery_GetResponseWithMessage(t *testing.T) {
    query := DnsQuery{ Domain: domain, Record: "PTR", Server: server }
    response := query.GetResponse()

    if len(response.Message) == 0 {
        t.Error("There should be a message in this request but nothing was returned")
    }
}

func TestDnsQuery_RecordValidation(t *testing.T) {
    if IsRecordValid("A") != true {
        t.Error("Uppercase 'A' is a valid record type")
    }

    if IsRecordValid("a") != true {
        t.Error("Lowercase 'a' is a valid record type")
    }

    if IsRecordValid("AAAAA") == true {
        t.Error("'AAAAA' is not a valid record")
    }

    if GetRecordType("A") != dns.TypeA {
        t.Error("Uppercase Record 'A' should return dns.TypeA")
    }

    if GetRecordType("a") != dns.TypeA {
        t.Error("Lowercase record 'a' should return dns.TypeA")
    }

    if GetRecordType("AAAAA") != 0 {
        t.Error("Record 'AAAAA' does not exist and the return type should be zero")
    }
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/hex"
	"testing"
)

func TestNewBlockFromBytes(t *testing.T) {
	blockBytes, err := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000000000000000000000000917c5457000000006d792067656e6573697320626c6f636b2121")
	if err != nil {
		t.Fatalf("should not be fail: %v", err)
	}

	block, err := newBlockFromBytes(blockBytes)
	if err != nil {
		t.Fatalf("should not be fail: %v", err)
	}
	if block.Index != genesisBlock.Index {
		t.Errorf("want %d but %d", genesisBlock.Index, block.Index)
	}
	if block.PreviousHash != genesisBlock.PreviousHash {
		t.Errorf("want %q but %q", genesisBlock.PreviousHash, block.PreviousHash)
	}
	if block.Timestamp != genesisBlock.Timestamp {
		t.Errorf("want %d but %d", genesisBlock.Timestamp, block.Timestamp)
	}
	if block.Data != genesisBlock.Data {
		t.Errorf("want %q but %q", genesisBlock.Data, block.Data)
	}
}

func TestBlockHex(t *testing.T) {
	blockHex, err := genesisBlock.hex()
	if err != nil {
		t.Fatalf("should not be fail: %v", err)
	}
	if blockHex != "00000000000000000000000000000000000000000000000000000000000000000000000000000000917c5457000000006d792067656e6573697320626c6f636b2121" {
		t.Errorf("want \"00000000000000000000000000000000000000000000000000000000000000000000000000000000917c5457000000006d792067656e6573697320626c6f636b2121\" but %q", blockHex)
	}
}
<commit_msg>add test for isValidBlock()<commit_after>package main

import (
	"encoding/hex"
	"testing"
)

func TestNewBlockFromBytes(t *testing.T) {
	blockBytes, err := hex.DecodeString("00000000000000000000000000000000000000000000000000000000000000000000000000000000917c5457000000006d792067656e6573697320626c6f636b2121")
	if err != nil {
		t.Fatalf("should not be fail: %v", err)
	}

	block, err := newBlockFromBytes(blockBytes)
	if err != nil {
		t.Fatalf("should not be fail: %v", err)
	}
	if block.Index != genesisBlock.Index {
		t.Errorf("want %d but %d", genesisBlock.Index, block.Index)
	}
	if block.PreviousHash != genesisBlock.PreviousHash {
		t.Errorf("want %q but %q", genesisBlock.PreviousHash, block.PreviousHash)
	}
	if block.Timestamp != genesisBlock.Timestamp {
		t.Errorf("want %d but %d", genesisBlock.Timestamp, block.Timestamp)
	}
	if block.Data != genesisBlock.Data {
		t.Errorf("want %q but %q", genesisBlock.Data, block.Data)
	}
}

func TestBlockHex(t *testing.T) {
	blockHex, err := genesisBlock.hex()
	if err != nil {
		t.Fatalf("should not be fail: %v", err)
	}
	if blockHex != "00000000000000000000000000000000000000000000000000000000000000000000000000000000917c5457000000006d792067656e6573697320626c6f636b2121" {
		t.Errorf("want \"00000000000000000000000000000000000000000000000000000000000000000000000000000000917c5457000000006d792067656e6573697320626c6f636b2121\" but %q", blockHex)
	}
}

type isValidBlockTestCase struct {
	block     *Block
	prevBlock *Block
	ok        bool
}

var isValidBlockTestCases = []isValidBlockTestCase{
	isValidBlockTestCase{
		&Block{
			Index:        2,
			PreviousHash: "17aacbe244debc3869a4f604c8136da450283cba3e0467681f398af16871cc3f",
			Timestamp:    1494177351,
			Data:         "white noise",
		},
		genesisBlock,
		false,
	},
	isValidBlockTestCase{
		&Block{
			Index:        1,
			PreviousHash: "27aacbe244debc3869a4f604c8136da450283cba3e0467681f398af16871cc3f",
			Timestamp:    1494177351,
			Data:         "white noise",
		},
		genesisBlock,
		false,
	},
	isValidBlockTestCase{
		&Block{
			Index:        1,
			PreviousHash: "17aacbe244debc3869a4f604c8136da450283cba3e0467681f398af16871cc3f",
			Timestamp:    1494177351,
			Data:         "white noise",
		},
		genesisBlock,
		true,
	},
}

func TestIsValidBlock(t *testing.T) {
	for _, testCase := range isValidBlockTestCases {
		ok, err := isValidBlock(testCase.block, testCase.prevBlock)
		if err != nil {
			t.Fatalf("should not be fail: %v", err)
		}
		if ok != testCase.ok {
			t.Errorf("want %t but %t", testCase.ok, ok)
		}
	}
}
<|endoftext|>


<commit_before>package blockchain

import (
	"reflect"
	"errors"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
)

const (
	rootURL = "https://blockchain.info"
)

type BlockChain struct {
	client *http.Client
	GUID string
	Password string
	SecondPassword string
	APICode string
}

type Item interface {
	load(bc *BlockChain) error
}

func New(c *http.Client) *BlockChain {
	return &BlockChain{client: c}
}

func checkHTTPResponse(r *http.Response) error {
	if r.StatusCode == 200 {
		return nil
	}

	bodyErr, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	return fmt.Errorf("%s: %.30q...", r.Status, bodyErr)
}

func (bc *BlockChain) Request(item Item) error {
	return item.load(bc)
}

func (bc *BlockChain) httpGetJSON(url string, v interface{}) error {
	resp, err := bc.client.Get(url)
	if err != nil {
		return err
	}

	if err := checkHTTPResponse(resp); err != nil {
		return err
	}

	defer resp.Body.Close()
	if err := decodeJSON(resp.Body, v); err != nil {
		return err
	}
	return nil
}

func decodeJSON(r io.Reader, v interface{}) error {
	data, err := ioutil.ReadAll(r)
	if err != nil {
		return err
	}

	fmt.Printf("Data: %s\n", data)
	if err := json.Unmarshal(data, v); err != nil {
		return err
	}

	// Check for errors.
	fmt.Printf("Struct: %+v\n", v)
	errVal := reflect.ValueOf(v).Elem().FieldByName("Error")
	if errVal.IsValid() && errVal.String() != "" {
		return errors.New(errVal.String())
	}

	return nil
}
<commit_msg>Deleted test printouts.<commit_after>package blockchain

import (
	"reflect"
	"errors"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
)

const (
	rootURL = "https://blockchain.info"
)

type BlockChain struct {
	client *http.Client
	GUID string
	Password string
	SecondPassword string
	APICode string
}

type Item interface {
	load(bc *BlockChain) error
}

func New(c *http.Client) *BlockChain {
	return &BlockChain{client: c}
}

func checkHTTPResponse(r *http.Response) error {
	if r.StatusCode == 200 {
		return nil
	}

	bodyErr, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return err
	}
	return fmt.Errorf("%s: %.30q...", r.Status, bodyErr)
}

func (bc *BlockChain) Request(item Item) error {
	return item.load(bc)
}

func (bc *BlockChain) httpGetJSON(url string, v interface{}) error {
	resp, err := bc.client.Get(url)
	if err != nil {
		return err
	}

	if err := checkHTTPResponse(resp); err != nil {
		return err
	}

	defer resp.Body.Close()
	if err := decodeJSON(resp.Body, v); err != nil {
		return err
	}
	return nil
}

func decodeJSON(r io.Reader, v interface{}) error {
	data, err := ioutil.ReadAll(r)
	if err != nil {
		return err
	}

	if err := json.Unmarshal(data, v); err != nil {
		return err
	}

	// Check for errors.
	errVal := reflect.ValueOf(v).Elem().FieldByName("Error")
	if errVal.IsValid() && errVal.String() != "" {
		return errors.New(errVal.String())
	}

	return nil
}
<|endoftext|>


<commit_before>package cmd

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"github.com/fatih/color"
	"github.com/spf13/cobra"
	"github.com/wataru0225/sreq/config"
	"github.com/wataru0225/sreq/snippet"
)

var editor string
var browse bool

var searchCmd = &cobra.Command{
	Use:     "search",
	Aliases: []string{"s"},
	Short:   "Search on Qiita (short-cut alias: \"s\")",
	Long:    "Search on Qiita (short-cut alias: \"s\")",
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) == 0 {
			fmt.Println("Failed to not argument of search keyword.")
			os.Exit(2)
		}

		argument := strings.Join(args, ",")
		pagenation := 1

		for {
			end := execute(argument, pagenation)
			if end {
				break
			}
			pagenation++
		}
	},
}

// Content is structure that scraping content from Qiita
type Content struct {
	ID    string
	Title string
	Desc  string
}

func init() {
	RootCmd.AddCommand(searchCmd)
}

func execute(argument string, pagenation int) bool {
	doc, err := goquery.NewDocument(config.PageURL(argument, "rel", strconv.Itoa(pagenation)))
	if err != nil {
		fmt.Printf("Scraping failed -> err: %v", err)
		return true
	}

	var contents []*Content

	doc.Find(".searchResult").Each(func(_ int, s *goquery.Selection) {
		itemID, _ := s.Attr("data-uuid")
		title := s.Find(".searchResult_itemTitle a").Text()
		desc := s.Find(".searchResult_snippet").Text()

		content := &Content{
			ID:    itemID,
			Title: title,
			Desc:  desc,
		}

		contents = append(contents, content)
	})

	rendering(contents)
	end := scan(contents, argument)

	return end
}

func rendering(contents []*Content) {
	for num, content := range contents {
		fmt.Print(color.YellowString(strconv.Itoa(num) + " -> "))
		fmt.Println(content.Title)
		fmt.Println(color.GreenString(content.Desc))
		fmt.Print("\n")
	}
	if len(contents) == 10 {
		fmt.Println(color.YellowString("n -> ") + "next page")
	}
	fmt.Print("SELECT > ")
}

func scan(contents []*Content, argument string) bool {
	var num string
	if _, err := fmt.Scanf("%s", &num); err != nil {
		fmt.Println(err)
	}

	if num == "n" {
		return false
	}

	index, _ := strconv.Atoi(num)
	target := contents[index]

	resp, err := http.Get(config.APIURL(target.ID))
	if err != nil {
		fmt.Println(err)
	}
	defer resp.Body.Close()

	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
	}
	var qiita *config.Qiita
	json.Unmarshal(b, &qiita)

	OpenEditor(qiita.Markdown, "less")

	writeHistory(qiita, argument)

	return true
}

func writeHistory(content *config.Qiita, argument string) {
	var snippets snippet.Snippets
	snippets.Load()
	url := content.URL
	newSnippet := snippet.Snippet{
		SearchKeyword: argument,
		URL:           url,
		Title:         content.Title,
	}
	snippets.Snippets = append(snippets.Snippets, newSnippet)
	if err := snippets.Save(); err != nil {
		fmt.Printf("Failed. %v", err)
		os.Exit(2)
	}
}
<commit_msg>Add sort option for search cmd<commit_after>package cmd

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"strings"

	"github.com/PuerkitoBio/goquery"
	"github.com/fatih/color"
	"github.com/spf13/cobra"
	"github.com/wataru0225/sreq/config"
	"github.com/wataru0225/sreq/snippet"
)

var editor string
var browse bool

var searchCmd = &cobra.Command{
	Use:     "search",
	Aliases: []string{"s"},
	Short:   "Search on Qiita (short-cut alias: \"s\")",
	Long:    "Search on Qiita (short-cut alias: \"s\")",
	Run: func(cmd *cobra.Command, args []string) {
		if len(args) == 0 {
			fmt.Println("Failed to not argument of search keyword.")
			os.Exit(2)
		}

		argument := strings.Join(args, ",")
		pagenation := 1

		for {
			end := execute(argument, pagenation)
			if end {
				break
			}
			pagenation++
		}
	},
}

// Content is structure that scraping content from Qiita
type Content struct {
	ID    string
	Title string
	Desc  string
}

var sort string

func init() {
	RootCmd.AddCommand(searchCmd)
	searchCmd.Flags().StringVar(&sort, "sort", "rel", "Select rel or created or stock for sort")
}

func execute(argument string, pagenation int) bool {
	if sort != "rel" && sort != "created" && sort != "stock" {
		fmt.Println("Please select 'rel' or 'created' or 'stock'")
		return true
	}
	doc, err := goquery.NewDocument(config.PageURL(argument, sort, strconv.Itoa(pagenation)))
	if err != nil {
		fmt.Printf("Scraping failed -> err: %v", err)
		return true
	}

	var contents []*Content

	doc.Find(".searchResult").Each(func(_ int, s *goquery.Selection) {
		itemID, _ := s.Attr("data-uuid")
		title := s.Find(".searchResult_itemTitle a").Text()
		desc := s.Find(".searchResult_snippet").Text()

		content := &Content{
			ID:    itemID,
			Title: title,
			Desc:  desc,
		}

		contents = append(contents, content)
	})

	rendering(contents)
	end := scan(contents, argument)

	return end
}

func rendering(contents []*Content) {
	for num, content := range contents {
		fmt.Print(color.YellowString(strconv.Itoa(num) + " -> "))
		fmt.Println(content.Title)
		fmt.Println(color.GreenString(content.Desc))
		fmt.Print("\n")
	}
	if len(contents) == 10 {
		fmt.Println(color.YellowString("n -> ") + "next page")
	}
	fmt.Print("SELECT > ")
}

func scan(contents []*Content, argument string) bool {
	var num string
	if _, err := fmt.Scanf("%s", &num); err != nil {
		fmt.Println(err)
	}

	if num == "n" {
		return false
	}

	index, _ := strconv.Atoi(num)
	target := contents[index]

	resp, err := http.Get(config.APIURL(target.ID))
	if err != nil {
		fmt.Println(err)
	}
	defer resp.Body.Close()

	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Println(err)
	}
	var qiita *config.Qiita
	json.Unmarshal(b, &qiita)

	OpenEditor(qiita.Markdown, "less")

	writeHistory(qiita, argument)

	return true
}

func writeHistory(content *config.Qiita, argument string) {
	var snippets snippet.Snippets
	snippets.Load()
	url := content.URL
	newSnippet := snippet.Snippet{
		SearchKeyword: argument,
		URL:           url,
		Title:         content.Title,
	}
	snippets.Snippets = append(snippets.Snippets, newSnippet)
	if err := snippets.Save(); err != nil {
		fmt.Printf("Failed. %v", err)
		os.Exit(2)
	}
}
<|endoftext|>


<commit_before>// Copyright 2017 Tomas Machalek <tomas.machalek@gmail.com>
// Copyright 2017 Charles University, Faculty of Arts,
//                Institute of the Czech National Corpus
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cnf

import (
	"encoding/json"
	"io/ioutil"

	"github.com/czcorpus/vert-tagextract/v2/db"
)

// FilterConf specifies a plug-in containing
// a compatible filter (see LineFilter interface).
type FilterConf struct {
	Lib string `json:"lib"`
	Fn  string `json:"fn"`
}

// NgramConf configures positional attributes (referred by their
// column position) we want to store and count as n-grams. This can
// be used to extract all the unique PoS tags or frequency information
// about words/lemmas.
type NgramConf struct {
	AttrColumns    []int    `json:"attrColumns"`
	ColumnMods     []string `json:"columnMods"`
	NgramSize      int      `json:"ngramSize"`
	UniqKeyColumns []int    `json:"uniqKeyColumns"`
	CalcARF        bool     `json:"calcARF"`
}

// VTEConf holds configuration for a concrete
// data extraction task.
type VTEConf struct {
	Corpus              string `json:"corpus"`
	ParallelCorpus      string `json:"parallelCorpus,omitempty"`
	AtomStructure       string `json:"atomStructure"`
	AtomParentStructure string `json:"atomParentStructure"`
	StackStructEval     bool   `json:"stackStructEval"`

	// MaxNumErrors if reached then the process stops
	MaxNumErrors int                 `json:"maxNumErrors"`
	Structures   map[string][]string `json:"structures"`

	// Ngrams - see NgramConf
	// If omitted then the function is disabled.
	Ngrams NgramConf `json:"ngrams"`

	// VerticalFile can be either a path to a single file
	// or a path to a directory containing multiple vertical
	// files (then we assume all the vertical files are of the
	// same structure)
	VerticalFile string `json:"verticalFile"`

	// VerticalFiles is an alternative to VerticalFile allowing
	// explicit selection of one or more files to be processed
	// as one.
	VerticalFiles []string `json:"verticalFiles"`

	DB db.Conf `json:"db"`

	Encoding    string          `json:"encoding"`
	SelfJoin    db.SelfJoinConf `json:"selfJoin"`
	IndexedCols []string        `json:"indexedCols"`
	BibView     db.BibViewConf  `json:"bibView"`

	Filter FilterConf `json:"filter"`
}

func (c *VTEConf) HasConfiguredFilter() bool {
	return c.Filter.Lib != "" && c.Filter.Fn != ""
}

func LoadConf(confPath string) (*VTEConf, error) {
	rawData, err := ioutil.ReadFile(confPath)
	if err != nil {
		return nil, err
	}
	var conf VTEConf
	err2 := json.Unmarshal(rawData, &conf)
	if err2 != nil {
		return nil, err2
	}
	return &conf, nil
}
<commit_msg>omit empty vert conf<commit_after>// Copyright 2017 Tomas Machalek <tomas.machalek@gmail.com>
// Copyright 2017 Charles University, Faculty of Arts,
//                Institute of the Czech National Corpus
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cnf

import (
	"encoding/json"
	"io/ioutil"

	"github.com/czcorpus/vert-tagextract/v2/db"
)

// FilterConf specifies a plug-in containing
// a compatible filter (see LineFilter interface).
type FilterConf struct {
	Lib string `json:"lib"`
	Fn  string `json:"fn"`
}

// NgramConf configures positional attributes (referred by their
// column position) we want to store and count as n-grams. This can
// be used to extract all the unique PoS tags or frequency information
// about words/lemmas.
type NgramConf struct {
	AttrColumns    []int    `json:"attrColumns"`
	ColumnMods     []string `json:"columnMods"`
	NgramSize      int      `json:"ngramSize"`
	UniqKeyColumns []int    `json:"uniqKeyColumns"`
	CalcARF        bool     `json:"calcARF"`
}

// VTEConf holds configuration for a concrete
// data extraction task.
type VTEConf struct {
	Corpus              string `json:"corpus"`
	ParallelCorpus      string `json:"parallelCorpus,omitempty"`
	AtomStructure       string `json:"atomStructure"`
	AtomParentStructure string `json:"atomParentStructure"`
	StackStructEval     bool   `json:"stackStructEval"`

	// MaxNumErrors if reached then the process stops
	MaxNumErrors int                 `json:"maxNumErrors"`
	Structures   map[string][]string `json:"structures"`

	// Ngrams - see NgramConf
	// If omitted then the function is disabled.
	Ngrams NgramConf `json:"ngrams"`

	// VerticalFile can be either a path to a single file
	// or a path to a directory containing multiple vertical
	// files (then we assume all the vertical files are of the
	// same structure)
	VerticalFile string `json:"verticalFile,omitempty"`

	// VerticalFiles is an alternative to VerticalFile allowing
	// explicit selection of one or more files to be processed
	// as one.
	VerticalFiles []string `json:"verticalFiles,omitempty"`

	DB db.Conf `json:"db"`

	Encoding    string          `json:"encoding"`
	SelfJoin    db.SelfJoinConf `json:"selfJoin"`
	IndexedCols []string        `json:"indexedCols"`
	BibView     db.BibViewConf  `json:"bibView"`

	Filter FilterConf `json:"filter"`
}

func (c *VTEConf) HasConfiguredFilter() bool {
	return c.Filter.Lib != "" && c.Filter.Fn != ""
}

func LoadConf(confPath string) (*VTEConf, error) {
	rawData, err := ioutil.ReadFile(confPath)
	if err != nil {
		return nil, err
	}
	var conf VTEConf
	err2 := json.Unmarshal(rawData, &conf)
	if err2 != nil {
		return nil, err2
	}
	return &conf, nil
}
<|endoftext|>


<commit_before>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Tests for client.go

package http_test

import (
	"fmt"
	. "http"
	"http/httptest"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
	"testing"
)

var robotsTxtHandler = HandlerFunc(func(w ResponseWriter, r *Request) {
	w.Header().Set("Last-Modified", "sometime")
	fmt.Fprintf(w, "User-agent: go\nDisallow: /something/")
})

func TestClient(t *testing.T) {
	ts := httptest.NewServer(robotsTxtHandler)
	defer ts.Close()

	r, err := Get(ts.URL)
	var b []byte
	if err == nil {
		b, err = ioutil.ReadAll(r.Body)
		r.Body.Close()
	}
	if err != nil {
		t.Error(err)
	} else if s := string(b); !strings.HasPrefix(s, "User-agent:") {
		t.Errorf("Incorrect page body (did not begin with User-agent): %q", s)
	}
}

func TestClientHead(t *testing.T) {
	ts := httptest.NewServer(robotsTxtHandler)
	defer ts.Close()

	r, err := Head(ts.URL)
	if err != nil {
		t.Fatal(err)
	}
	if _, ok := r.Header["Last-Modified"]; !ok {
		t.Error("Last-Modified header not found.")
	}
}

type recordingTransport struct {
	req *Request
}

func (t *recordingTransport) RoundTrip(req *Request) (resp *Response, err os.Error) {
	t.req = req
	return nil, os.NewError("dummy impl")
}

func TestGetRequestFormat(t *testing.T) {
	tr := &recordingTransport{}
	client := &Client{Transport: tr}
	url := "http://dummy.faketld/"
	client.Get(url) // Note: doesn't hit network
	if tr.req.Method != "GET" {
		t.Errorf("expected method %q; got %q", "GET", tr.req.Method)
	}
	if tr.req.URL.String() != url {
		t.Errorf("expected URL %q; got %q", url, tr.req.URL.String())
	}
	if tr.req.Header == nil {
		t.Errorf("expected non-nil request Header")
	}
}

func TestRedirects(t *testing.T) {
	var ts *httptest.Server
	ts = httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
		n, _ := strconv.Atoi(r.FormValue("n"))
		// Test Referer header. (7 is arbitrary position to test at)
		if n == 7 {
			if g, e := r.Referer, ts.URL+"/?n=6"; e != g {
				t.Errorf("on request ?n=7, expected referer of %q; got %q", e, g)
			}
		}
		if n < 15 {
			Redirect(w, r, fmt.Sprintf("/?n=%d", n+1), StatusFound)
			return
		}
		fmt.Fprintf(w, "n=%d", n)
	}))
	defer ts.Close()

	c := &Client{}
	_, err := c.Get(ts.URL)
	if e, g := "Get /?n=10: stopped after 10 redirects", fmt.Sprintf("%v", err); e != g {
		t.Errorf("with default client Get, expected error %q, got %q", e, g)
	}

	// HEAD request should also have the ability to follow redirects.
	_, err = c.Head(ts.URL)
	if e, g := "Head /?n=10: stopped after 10 redirects", fmt.Sprintf("%v", err); e != g {
		t.Errorf("with default client Head, expected error %q, got %q", e, g)
	}

	// Do should also follow redirects.
	greq, _ := NewRequest("GET", ts.URL, nil)
	_, err = c.Do(greq)
	if e, g := "Get /?n=10: stopped after 10 redirects", fmt.Sprintf("%v", err); e != g {
		t.Errorf("with default client Do, expected error %q, got %q", e, g)
	}

	var checkErr os.Error
	var lastVia []*Request
	c = &Client{CheckRedirect: func(_ *Request, via []*Request) os.Error {
		lastVia = via
		return checkErr
	}}
	res, err := c.Get(ts.URL)
	finalUrl := res.Request.URL.String()
	if e, g := "<nil>", fmt.Sprintf("%v", err); e != g {
		t.Errorf("with custom client, expected error %q, got %q", e, g)
	}
	if !strings.HasSuffix(finalUrl, "/?n=15") {
		t.Errorf("expected final url to end in /?n=15; got url %q", finalUrl)
	}
	if e, g := 15, len(lastVia); e != g {
		t.Errorf("expected lastVia to have contained %d elements; got %d", e, g)
	}

	checkErr = os.NewError("no redirects allowed")
	res, err = c.Get(ts.URL)
	finalUrl = res.Request.URL.String()
	if e, g := "Get /?n=1: no redirects allowed", fmt.Sprintf("%v", err); e != g {
		t.Errorf("with redirects forbidden, expected error %q, got %q", e, g)
	}
}
<commit_msg>http: Client test for streaming responses (no code changes)<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Tests for client.go

package http_test

import (
	"fmt"
	. "http"
	"http/httptest"
	"io"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
	"testing"
)

var robotsTxtHandler = HandlerFunc(func(w ResponseWriter, r *Request) {
	w.Header().Set("Last-Modified", "sometime")
	fmt.Fprintf(w, "User-agent: go\nDisallow: /something/")
})

func TestClient(t *testing.T) {
	ts := httptest.NewServer(robotsTxtHandler)
	defer ts.Close()

	r, err := Get(ts.URL)
	var b []byte
	if err == nil {
		b, err = ioutil.ReadAll(r.Body)
		r.Body.Close()
	}
	if err != nil {
		t.Error(err)
	} else if s := string(b); !strings.HasPrefix(s, "User-agent:") {
		t.Errorf("Incorrect page body (did not begin with User-agent): %q", s)
	}
}

func TestClientHead(t *testing.T) {
	ts := httptest.NewServer(robotsTxtHandler)
	defer ts.Close()

	r, err := Head(ts.URL)
	if err != nil {
		t.Fatal(err)
	}
	if _, ok := r.Header["Last-Modified"]; !ok {
		t.Error("Last-Modified header not found.")
	}
}

type recordingTransport struct {
	req *Request
}

func (t *recordingTransport) RoundTrip(req *Request) (resp *Response, err os.Error) {
	t.req = req
	return nil, os.NewError("dummy impl")
}

func TestGetRequestFormat(t *testing.T) {
	tr := &recordingTransport{}
	client := &Client{Transport: tr}
	url := "http://dummy.faketld/"
	client.Get(url) // Note: doesn't hit network
	if tr.req.Method != "GET" {
		t.Errorf("expected method %q; got %q", "GET", tr.req.Method)
	}
	if tr.req.URL.String() != url {
		t.Errorf("expected URL %q; got %q", url, tr.req.URL.String())
	}
	if tr.req.Header == nil {
		t.Errorf("expected non-nil request Header")
	}
}

func TestRedirects(t *testing.T) {
	var ts *httptest.Server
	ts = httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
		n, _ := strconv.Atoi(r.FormValue("n"))
		// Test Referer header. (7 is arbitrary position to test at)
		if n == 7 {
			if g, e := r.Referer, ts.URL+"/?n=6"; e != g {
				t.Errorf("on request ?n=7, expected referer of %q; got %q", e, g)
			}
		}
		if n < 15 {
			Redirect(w, r, fmt.Sprintf("/?n=%d", n+1), StatusFound)
			return
		}
		fmt.Fprintf(w, "n=%d", n)
	}))
	defer ts.Close()

	c := &Client{}
	_, err := c.Get(ts.URL)
	if e, g := "Get /?n=10: stopped after 10 redirects", fmt.Sprintf("%v", err); e != g {
		t.Errorf("with default client Get, expected error %q, got %q", e, g)
	}

	// HEAD request should also have the ability to follow redirects.
	_, err = c.Head(ts.URL)
	if e, g := "Head /?n=10: stopped after 10 redirects", fmt.Sprintf("%v", err); e != g {
		t.Errorf("with default client Head, expected error %q, got %q", e, g)
	}

	// Do should also follow redirects.
	greq, _ := NewRequest("GET", ts.URL, nil)
	_, err = c.Do(greq)
	if e, g := "Get /?n=10: stopped after 10 redirects", fmt.Sprintf("%v", err); e != g {
		t.Errorf("with default client Do, expected error %q, got %q", e, g)
	}

	var checkErr os.Error
	var lastVia []*Request
	c = &Client{CheckRedirect: func(_ *Request, via []*Request) os.Error {
		lastVia = via
		return checkErr
	}}
	res, err := c.Get(ts.URL)
	finalUrl := res.Request.URL.String()
	if e, g := "<nil>", fmt.Sprintf("%v", err); e != g {
		t.Errorf("with custom client, expected error %q, got %q", e, g)
	}
	if !strings.HasSuffix(finalUrl, "/?n=15") {
		t.Errorf("expected final url to end in /?n=15; got url %q", finalUrl)
	}
	if e, g := 15, len(lastVia); e != g {
		t.Errorf("expected lastVia to have contained %d elements; got %d", e, g)
	}

	checkErr = os.NewError("no redirects allowed")
	res, err = c.Get(ts.URL)
	finalUrl = res.Request.URL.String()
	if e, g := "Get /?n=1: no redirects allowed", fmt.Sprintf("%v", err); e != g {
		t.Errorf("with redirects forbidden, expected error %q, got %q", e, g)
	}
}

func TestStreamingGet(t *testing.T) {
	say := make(chan string)
	ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) {
		w.(Flusher).Flush()
		for str := range say {
			w.Write([]byte(str))
			w.(Flusher).Flush()
		}
	}))
	defer ts.Close()

	c := &Client{}
	res, err := c.Get(ts.URL)
	if err != nil {
		t.Fatal(err)
	}
	var buf [10]byte
	for _, str := range []string{"i", "am", "also", "known", "as", "comet"} {
		say <- str
		n, err := io.ReadFull(res.Body, buf[0:len(str)])
		if err != nil {
			t.Fatalf("ReadFull on %q: %v", str, err)
		}
		if n != len(str) {
			t.Fatalf("Receiving %q, only read %d bytes", str, n)
		}
		got := string(buf[0:n])
		if got != str {
			t.Fatalf("Expected %q, got %q", str, got)
		}
	}
	close(say)
	_, err = io.ReadFull(res.Body, buf[0:1])
	if err != os.EOF {
		t.Fatalf("at end expected EOF, got %v", err)
	}
}
<|endoftext|>


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

package userhtml

import (
	"context"
	"fmt"
	"html/template"
	"net/http"
	"strings"
	"time"

	"github.com/dustin/go-humanize"

	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"go.chromium.org/luci/auth/identity"
	"go.chromium.org/luci/common/clock"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/server"
	"go.chromium.org/luci/server/analytics"
	"go.chromium.org/luci/server/auth"
	"go.chromium.org/luci/server/auth/xsrf"
	"go.chromium.org/luci/server/router"
	"go.chromium.org/luci/server/templates"

	"go.chromium.org/luci/cv/internal/changelist"
)

type startTimeContextKey int

// InstallHandlers adds HTTP handlers that render HTML pages.
func InstallHandlers(srv *server.Server) {
	m := router.NewMiddlewareChain(
		func(c *router.Context, next router.Handler) {
			c.Context = context.WithValue(c.Context, startTimeContextKey(0), clock.Now(c.Context))
			next(c)
		},
		templates.WithTemplates(prepareTemplates(&srv.Options, "templates")),
		auth.Authenticate(srv.CookieAuth),
	)

	srv.Routes.GET("/ui/recents", m, recentsPage)
	srv.Routes.GET("/ui/recents/:Project", m, recentsPage)
	srv.Routes.GET("/ui/run/:Project/:Run", m, runDetails)
}

// prepareTemplates configures templates.Bundle used by all UI handlers.
//
// In particular it includes a set of default arguments passed to all templates.
func prepareTemplates(opts *server.Options, templatesPath string) *templates.Bundle {
	versionID := "unknown"
	if idx := strings.LastIndex(opts.ContainerImageID, ":"); idx != -1 {
		versionID = opts.ContainerImageID[idx+1:]
	}
	return &templates.Bundle{
		Loader:          templates.FileSystemLoader(templatesPath),
		DebugMode:       func(context.Context) bool { return !opts.Prod },
		DefaultTemplate: "base",
		FuncMap: template.FuncMap{
			"FmTime": func(ts time.Time) string {
				return ts.UTC().Format("2006-01-02 15:04:05 (MST)")
			},
			"RelTime": func(ts, now time.Time) string {
				return humanize.RelTime(ts, now, "ago", "from now")
			},
			"Split": func(s string) []string {
				return strings.Split(s, "\n")
			},
			"SplitSlash": func(s string) []string {
				return strings.Split(s, "/")
			},
			"Title": func(s string) string {
				return strings.Title(strings.ToLower(strings.Replace(s, "_", " ", -1)))
			},
			"LinkifyExternalID": func(eid string) string {
				if eid == "" {
					// Very old RunCL entities don't have ExternalID set.
					return ""
				}
				return changelist.ExternalID(eid).MustURL()
			},
			// Shortens a cl id for display purposes.
			// Accepts string or changelist.ExternalID.
			"DisplayExternalID": func(arg interface{}) string {
				var eid string
				switch v := arg.(type) {
				case string:
					eid = v
				case changelist.ExternalID:
					eid = string(v)
				default:
					panic(fmt.Sprintf("DisplayExternalID called with unsupported type %t", v))
				}
				if eid == "" {
					// Very old RunCL entities don't have ExternalID set.
					return ""
				}
				return displayCLExternalID(changelist.ExternalID(eid))
			},
			// Runlog specific, see run_details.go.
			"LogTypeString": logTypeString,

			"UITryjob":       makeUITryjob,
			"ByTryjobStatus": groupTryjobsByStatus,
		},
		DefaultArgs: func(ctx context.Context, e *templates.Extra) (templates.Args, error) {
			loginURL, err := auth.LoginURL(ctx, e.Request.URL.RequestURI())
			if err != nil {
				return nil, err
			}
			logoutURL, err := auth.LogoutURL(ctx, e.Request.URL.RequestURI())
			if err != nil {
				return nil, err
			}
			token, err := xsrf.Token(ctx)
			if err != nil {
				return nil, err
			}
			return templates.Args{
				"AppVersion":  versionID,
				"IsAnonymous": auth.CurrentIdentity(ctx) == identity.AnonymousIdentity,
				"User":        auth.CurrentUser(ctx),
				"LoginURL":    loginURL,
				"LogoutURL":   logoutURL,
				"XsrfToken":   token,
				"Now":         startTime(ctx),
				"HandlerDuration": func() time.Duration {
					return clock.Now(ctx).Sub(startTime(ctx))
				},
				"AnalyticsSnippet": analytics.GTagSnippet(ctx),
			}, nil
		},
	}
}

func startTime(c context.Context) time.Time {
	ts, ok := c.Value(startTimeContextKey(0)).(time.Time)
	if !ok {
		panic("impossible, startTimeContextKey is not set")
	}
	return ts
}

func displayCLExternalID(eid changelist.ExternalID) string {
	host, change, err := eid.ParseGobID()
	if err != nil {
		panic(err)
	}
	host = strings.Replace(host, "-review.googlesource.com", "", 1)
	return fmt.Sprintf("%s/%d", host, change)
}

func errPage(c *router.Context, err error) {
	logging.Errorf(c.Context, "Error: %s", err)
	code := http.StatusInternalServerError
	if status.Code(errors.Unwrap(err)) == codes.PermissionDenied {
		code = http.StatusForbidden
	}
	c.Writer.WriteHeader(code)
	templates.MustRender(c.Context, c.Writer, "pages/error.html", map[string]interface{}{
		"Error": err,
		"Code":  code,
	})
}
<commit_msg>[cv] [ui] Get correct code for error page.<commit_after>// Copyright 2021 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package userhtml

import (
	"context"
	"fmt"
	"html/template"
	"strings"
	"time"

	"github.com/dustin/go-humanize"

	"google.golang.org/grpc/status"

	"go.chromium.org/luci/auth/identity"
	"go.chromium.org/luci/common/clock"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/grpc/grpcutil"
	"go.chromium.org/luci/server"
	"go.chromium.org/luci/server/analytics"
	"go.chromium.org/luci/server/auth"
	"go.chromium.org/luci/server/auth/xsrf"
	"go.chromium.org/luci/server/router"
	"go.chromium.org/luci/server/templates"

	"go.chromium.org/luci/cv/internal/changelist"
)

type startTimeContextKey int

// InstallHandlers adds HTTP handlers that render HTML pages.
func InstallHandlers(srv *server.Server) {
	m := router.NewMiddlewareChain(
		func(c *router.Context, next router.Handler) {
			c.Context = context.WithValue(c.Context, startTimeContextKey(0), clock.Now(c.Context))
			next(c)
		},
		templates.WithTemplates(prepareTemplates(&srv.Options, "templates")),
		auth.Authenticate(srv.CookieAuth),
	)

	srv.Routes.GET("/ui/recents", m, recentsPage)
	srv.Routes.GET("/ui/recents/:Project", m, recentsPage)
	srv.Routes.GET("/ui/run/:Project/:Run", m, runDetails)
}

// prepareTemplates configures templates.Bundle used by all UI handlers.
//
// In particular it includes a set of default arguments passed to all templates.
func prepareTemplates(opts *server.Options, templatesPath string) *templates.Bundle {
	versionID := "unknown"
	if idx := strings.LastIndex(opts.ContainerImageID, ":"); idx != -1 {
		versionID = opts.ContainerImageID[idx+1:]
	}
	return &templates.Bundle{
		Loader:          templates.FileSystemLoader(templatesPath),
		DebugMode:       func(context.Context) bool { return !opts.Prod },
		DefaultTemplate: "base",
		FuncMap: template.FuncMap{
			"FmTime": func(ts time.Time) string {
				return ts.UTC().Format("2006-01-02 15:04:05 (MST)")
			},
			"RelTime": func(ts, now time.Time) string {
				return humanize.RelTime(ts, now, "ago", "from now")
			},
			"Split": func(s string) []string {
				return strings.Split(s, "\n")
			},
			"SplitSlash": func(s string) []string {
				return strings.Split(s, "/")
			},
			"Title": func(s string) string {
				return strings.Title(strings.ToLower(strings.Replace(s, "_", " ", -1)))
			},
			"LinkifyExternalID": func(eid string) string {
				if eid == "" {
					// Very old RunCL entities don't have ExternalID set.
					return ""
				}
				return changelist.ExternalID(eid).MustURL()
			},
			// Shortens a cl id for display purposes.
			// Accepts string or changelist.ExternalID.
			"DisplayExternalID": func(arg interface{}) string {
				var eid string
				switch v := arg.(type) {
				case string:
					eid = v
				case changelist.ExternalID:
					eid = string(v)
				default:
					panic(fmt.Sprintf("DisplayExternalID called with unsupported type %t", v))
				}
				if eid == "" {
					// Very old RunCL entities don't have ExternalID set.
					return ""
				}
				return displayCLExternalID(changelist.ExternalID(eid))
			},
			// Runlog specific, see run_details.go.
			"LogTypeString": logTypeString,

			"UITryjob":       makeUITryjob,
			"ByTryjobStatus": groupTryjobsByStatus,
		},
		DefaultArgs: func(ctx context.Context, e *templates.Extra) (templates.Args, error) {
			loginURL, err := auth.LoginURL(ctx, e.Request.URL.RequestURI())
			if err != nil {
				return nil, err
			}
			logoutURL, err := auth.LogoutURL(ctx, e.Request.URL.RequestURI())
			if err != nil {
				return nil, err
			}
			token, err := xsrf.Token(ctx)
			if err != nil {
				return nil, err
			}
			return templates.Args{
				"AppVersion":  versionID,
				"IsAnonymous": auth.CurrentIdentity(ctx) == identity.AnonymousIdentity,
				"User":        auth.CurrentUser(ctx),
				"LoginURL":    loginURL,
				"LogoutURL":   logoutURL,
				"XsrfToken":   token,
				"Now":         startTime(ctx),
				"HandlerDuration": func() time.Duration {
					return clock.Now(ctx).Sub(startTime(ctx))
				},
				"AnalyticsSnippet": analytics.GTagSnippet(ctx),
			}, nil
		},
	}
}

func startTime(c context.Context) time.Time {
	ts, ok := c.Value(startTimeContextKey(0)).(time.Time)
	if !ok {
		panic("impossible, startTimeContextKey is not set")
	}
	return ts
}

func displayCLExternalID(eid changelist.ExternalID) string {
	host, change, err := eid.ParseGobID()
	if err != nil {
		panic(err)
	}
	host = strings.Replace(host, "-review.googlesource.com", "", 1)
	return fmt.Sprintf("%s/%d", host, change)
}

func errPage(c *router.Context, err error) {
	logging.Errorf(c.Context, "Error: %s", err)
	err = errors.Unwrap(err)
	code := grpcutil.CodeStatus(status.Code(err))
	c.Writer.WriteHeader(code)
	templates.MustRender(c.Context, c.Writer, "pages/error.html", map[string]interface{}{
		"Error": err,
		"Code":  code,
	})
}
<|endoftext|>