<commit_before>package octokit

import (
	"io"
	"net/url"
	"time"

	"github.com/jingweno/go-sawyer/hypermedia"
)

// URLs for accessing specific endpoints on the gists API. The most general
// is GistsURL, used to list, get, create, edit and delete a gist. GistsUserURL,
// GistsPublicURL and GistsStarredURL are used for listing certain sets of gists.
// GistsRevisionURL can be used to access a specific revision of a gist.
// GistsCommitsURL can be used to access all the commits on a specific gist.
// GistsStarURL is used for starring and unstarring gists, and checking if a gist
// is starred. GistsForksURL is used to fork or delete a fork of a gist, and to
// list a gist's forks.
//
// https://developer.github.com/v3/gists
var (
	GistsUserURL     = Hyperlink("users/{username}/gists")
	GistsURL         = Hyperlink("gists{/gist_id}")
	GistsPublicURL   = Hyperlink("gists/public")
	GistsStarredURL  = Hyperlink("gists/starred")
	GistsRevisionURL = Hyperlink("gists/{gist_id}/{commit_sha}")
	GistsCommitsURL  = Hyperlink("gists/{gist_id}/commits")
	GistsStarURL     = Hyperlink("gists/{gist_id}/star")
	GistsForksURL    = Hyperlink("gists/{gist_id}/forks")
)

// Gists creates a GistsService to be used with any proper URL
//
// https://developer.github.com/v3/gists/
func (c *Client) Gists() (gists *GistsService) {
	gists = &GistsService{client: c}
	return
}

// GistsService is a service providing access to gists from a particular url
type GistsService struct {
	client *Client
}

// All gets a list of all gists associated with the url of the service
//
// https://developer.github.com/v3/gists/#list-gists
func (g *GistsService) All(uri *Hyperlink, uriParams M) (gists []Gist, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsURL, uriParams)
	if err != nil {
		return nil, &Result{Err: err}
	}

	result = g.client.get(url, &gists)
	return
}

// One gets a specific gist based on the url of the service
//
// https://developer.github.com/v3/gists/#get-a-single-gist
// https://developer.github.com/v3/gists/#get-a-specific-revision-of-a-gist
func (g *GistsService) One(uri *Hyperlink, uriParams M) (gist *Gist, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsURL, uriParams)
	if err != nil {
		return nil, &Result{Err: err}
	}

	result = g.client.get(url, &gist)
	return
}

// Raw gets the raw contents of first file in a specific gist
//
// https://developer.github.com/v3/gists/#truncation
func (g *GistsService) Raw(uri *Hyperlink, uriParams M) (body io.ReadCloser, result *Result) {
	var rawURL *url.URL

	gist, result := g.One(uri, uriParams)
	for _, file := range gist.Files {
		rawURL, _ = url.Parse(file.RawURL)
		break
	}

	body, result = g.client.getBody(rawURL, textMediaType)
	return
}

// Create posts a new gist based on parameters in a Gist struct to
// the specified URL
//
// https://developer.github.com/v3/gists/#create-a-gist
func (g *GistsService) Create(uri *Hyperlink, uriParams M, requestParams interface{}) (gist *Gist, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsURL, uriParams)
	if err != nil {
		return nil, &Result{Err: err}
	}

	result = g.client.post(url, requestParams, &gist)
	return
}

// Update modifies a specific gist based on the url of the service
//
// https://developer.github.com/v3/gists/#edit-a-gist
func (g *GistsService) Update(uri *Hyperlink, uriParams M, requestParams interface{}) (gist *Gist, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsURL, uriParams)
	if err != nil {
		return nil, &Result{Err: err}
	}

	result = g.client.patch(url, requestParams, &gist)
	return
}

// Commits gets a list of all commits to the given gist
//
// https://developer.github.com/v3/gists/#list-gists
func (g *GistsService) Commits(uri *Hyperlink, uriParams M) (gistCommits []GistCommit, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsCommitsURL, uriParams)
	if err != nil {
		return nil, &Result{Err: err}
	}

	result = g.client.get(url, &gistCommits)
	return
}

// Star stars a gist
//
// https://developer.github.com/v3/gists/#star-a-gist
func (g *GistsService) Star(uri *Hyperlink, uriParams M) (success bool, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsStarURL, uriParams)
	if err != nil {
		return false, &Result{Err: err}
	}

	result = g.client.put(url, nil, nil)
	success = (!result.HasError() && result.Response.StatusCode == 204)
	return
}

// Unstar unstars a gist
//
// https://developer.github.com/v3/gists/#unstar-a-gist
func (g *GistsService) Unstar(uri *Hyperlink, uriParams M) (success bool, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsStarURL, uriParams)
	if err != nil {
		return false, &Result{Err: err}
	}

	result = g.client.delete(url, nil, nil)
	success = (!result.HasError() && result.Response.StatusCode == 204)
	return
}

// CheckStar checks if a gist is starred
//
// https://developer.github.com/v3/gists/#check-if-a-gist-is-starred
func (g *GistsService) CheckStar(uri *Hyperlink, uriParams M) (success bool, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsStarURL, uriParams)
	if err != nil {
		return false, &Result{Err: err}
	}

	result = g.client.get(url, nil)
	success = (!result.HasError() && result.Response.StatusCode == 204)
	return
}

// Fork forks a gist
//
// https://developer.github.com/v3/gists/#fork-a-gist
func (g *GistsService) Fork(uri *Hyperlink, uriParams M) (gist *Gist, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsForksURL, uriParams)
	if err != nil {
		return nil, &Result{Err: err}
	}

	result = g.client.post(url, nil, &gist)
	return
}

// ListForks lists all the forks of a gist
//
// https://developer.github.com/v3/gists/#list-gist-forks
func (g *GistsService) ListForks(uri *Hyperlink, uriParams M) (gistForks []GistFork, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsForksURL, uriParams)
	if err != nil {
		return nil, &Result{Err: err}
	}

	result = g.client.get(url, &gistForks)
	return
}

// Delete deletes a gist by its id
//
// https://developer.github.com/v3/gists/#delete-a-gist
func (g *GistsService) Delete(uri *Hyperlink, uriParams M) (success bool, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsURL, uriParams)
	if err != nil {
		return false, &Result{Err: err}
	}

	result = g.client.delete(url, nil, nil)
	success = (!result.HasError() && result.Response.StatusCode == 204)
	return
}

// GistFile is a representation of the file stored in a gist
type GistFile struct {
	*hypermedia.HALResource

	FileName  string `json:"filename,omitempty"`
	Type      string `json:"type,omitempty"`
	Language  string `json:"language,omitempty"`
	RawURL    string `json:"raw_url,omitempty"`
	Size      int    `json:"size,omitempty"`
	Truncated bool   `json:"truncated,omitempty"`
	Content   string `json:"content,omitempty"`
}

// Gist is a representation of a gist on github, a standalone file that acts as a
// sole element of its own repository
type Gist struct {
	*hypermedia.HALResource

	ID          string               `json:"id,omitempty"`
	Comments    float64              `json:"comments,omitempty"`
	CommentsURL string               `json:"comments_url,omitempty"`
	CommitsURL  string               `json:"commits_url,omitempty"`
	CreatedAt   *time.Time           `json:"created_at,omitempty"`
	Description string               `json:"description,omitempty"`
	Files       map[string]*GistFile `json:"files,omitempty"`
	Forks       []GistFork           `json:"forks,omitempty"`
	ForksURL    Hyperlink            `json:"forks_url,omitempty"`
	GitPullURL  Hyperlink            `json:"git_pull_url,omitempty"`
	History     []GistCommit         `json:"history,omitempty"`
	GitPushURL  Hyperlink            `json:"git_push_url,omitempty"`
	HtmlURL     Hyperlink            `json:"html_url,omitempty"`
	Owner       *User                `json:"owner,omitempty"`
	Public      bool                 `json:"public,omitempty"`
	UpdatedAt   *time.Time           `json:"updated_at,omitempty"`
	URL         string               `json:"url,omitempty"`
	User        *User                `json:"user,omitempty"`
}

//GistFork represents information about a fork of a gist
type GistFork struct {
	*hypermedia.HALResource

	ID        string     `json:"id,omitempty"`
	CreatedAt *time.Time `json:"created_at,omitempty"`
	UpdatedAt *time.Time `json:"updated_at,omitempty"`
	URL       string     `json:"url,omitempty"`
	User      *User      `json:"user,omitempty"`
}

// GistCommit is a representation of one of the commits to a gist
type GistCommit struct {
	*hypermedia.HALResource

	ChangeStatus *GistChangeStatus `json:"change_status,omitempty"`
	CommittedAt  *time.Time        `json:"committed_at,omitempty"`
	URL          string            `json:"url,omitempty"`
	User         *User             `json:"user,omitempty"`
	Version      string            `json:"version,omitempty"`
}

// GistChangeStatus represents all changes on a given Gist
type GistChangeStatus struct {
	Additions int `json:"additions,omitempty"`
	Deletions int `json:"deletions,omitempty"`
	Total     int `json:"total,omitempty"`
}
<commit_msg>Fix doc urls gists<commit_after>package octokit

import (
	"io"
	"net/url"
	"time"

	"github.com/jingweno/go-sawyer/hypermedia"
)

// URLs for accessing specific endpoints on the gists API. The most general
// is GistsURL, used to list, get, create, edit and delete a gist. GistsUserURL,
// GistsPublicURL and GistsStarredURL are used for listing certain sets of gists.
// GistsRevisionURL can be used to access a specific revision of a gist.
// GistsCommitsURL can be used to access all the commits on a specific gist.
// GistsStarURL is used for starring and unstarring gists, and checking if a gist
// is starred. GistsForksURL is used to fork or delete a fork of a gist, and to
// list a gist's forks.
//
// https://developer.github.com/v3/gists
var (
	GistsUserURL     = Hyperlink("users/{username}/gists")
	GistsURL         = Hyperlink("gists{/gist_id}")
	GistsPublicURL   = Hyperlink("gists/public")
	GistsStarredURL  = Hyperlink("gists/starred")
	GistsRevisionURL = Hyperlink("gists/{gist_id}/{commit_sha}")
	GistsCommitsURL  = Hyperlink("gists/{gist_id}/commits")
	GistsStarURL     = Hyperlink("gists/{gist_id}/star")
	GistsForksURL    = Hyperlink("gists/{gist_id}/forks")
)

// Gists creates a GistsService to be used with any proper URL
//
// https://developer.github.com/v3/gists/
func (c *Client) Gists() (gists *GistsService) {
	gists = &GistsService{client: c}
	return
}

// GistsService is a service providing access to gists from a particular url
type GistsService struct {
	client *Client
}

// All gets a list of all gists associated with the url of the service
//
// https://developer.github.com/v3/gists/#list-gists
func (g *GistsService) All(uri *Hyperlink, uriParams M) (gists []Gist, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsURL, uriParams)
	if err != nil {
		return nil, &Result{Err: err}
	}

	result = g.client.get(url, &gists)
	return
}

// One gets a specific gist based on the url of the service
//
// https://developer.github.com/v3/gists/#get-a-single-gist
// https://developer.github.com/v3/gists/#get-a-specific-revision-of-a-gist
func (g *GistsService) One(uri *Hyperlink, uriParams M) (gist *Gist, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsURL, uriParams)
	if err != nil {
		return nil, &Result{Err: err}
	}

	result = g.client.get(url, &gist)
	return
}

// Raw gets the raw contents of first file in a specific gist
//
// https://developer.github.com/v3/gists/#truncation
func (g *GistsService) Raw(uri *Hyperlink, uriParams M) (body io.ReadCloser, result *Result) {
	var rawURL *url.URL

	gist, result := g.One(uri, uriParams)
	for _, file := range gist.Files {
		rawURL, _ = url.Parse(file.RawURL)
		break
	}

	body, result = g.client.getBody(rawURL, textMediaType)
	return
}

// Create posts a new gist based on parameters in a Gist struct to
// the specified URL
//
// https://developer.github.com/v3/gists/#create-a-gist
func (g *GistsService) Create(uri *Hyperlink, uriParams M, requestParams interface{}) (gist *Gist, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsURL, uriParams)
	if err != nil {
		return nil, &Result{Err: err}
	}

	result = g.client.post(url, requestParams, &gist)
	return
}

// Update modifies a specific gist based on the url of the service
//
// https://developer.github.com/v3/gists/#edit-a-gist
func (g *GistsService) Update(uri *Hyperlink, uriParams M, requestParams interface{}) (gist *Gist, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsURL, uriParams)
	if err != nil {
		return nil, &Result{Err: err}
	}

	result = g.client.patch(url, requestParams, &gist)
	return
}

// Commits gets a list of all commits to the given gist
//
// https://developer.github.com/v3/gists/#list-gist-commits
func (g *GistsService) Commits(uri *Hyperlink, uriParams M) (gistCommits []GistCommit, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsCommitsURL, uriParams)
	if err != nil {
		return nil, &Result{Err: err}
	}

	result = g.client.get(url, &gistCommits)
	return
}

// Star stars a gist
//
// https://developer.github.com/v3/gists/#star-a-gist
func (g *GistsService) Star(uri *Hyperlink, uriParams M) (success bool, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsStarURL, uriParams)
	if err != nil {
		return false, &Result{Err: err}
	}

	result = g.client.put(url, nil, nil)
	success = (!result.HasError() && result.Response.StatusCode == 204)
	return
}

// Unstar unstars a gist
//
// https://developer.github.com/v3/gists/#unstar-a-gist
func (g *GistsService) Unstar(uri *Hyperlink, uriParams M) (success bool, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsStarURL, uriParams)
	if err != nil {
		return false, &Result{Err: err}
	}

	result = g.client.delete(url, nil, nil)
	success = (!result.HasError() && result.Response.StatusCode == 204)
	return
}

// CheckStar checks if a gist is starred
//
// https://developer.github.com/v3/gists/#check-if-a-gist-is-starred
func (g *GistsService) CheckStar(uri *Hyperlink, uriParams M) (success bool, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsStarURL, uriParams)
	if err != nil {
		return false, &Result{Err: err}
	}

	result = g.client.get(url, nil)
	success = (!result.HasError() && result.Response.StatusCode == 204)
	return
}

// Fork forks a gist
//
// https://developer.github.com/v3/gists/#fork-a-gist
func (g *GistsService) Fork(uri *Hyperlink, uriParams M) (gist *Gist, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsForksURL, uriParams)
	if err != nil {
		return nil, &Result{Err: err}
	}

	result = g.client.post(url, nil, &gist)
	return
}

// ListForks lists all the forks of a gist
//
// https://developer.github.com/v3/gists/#list-gist-forks
func (g *GistsService) ListForks(uri *Hyperlink, uriParams M) (gistForks []GistFork, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsForksURL, uriParams)
	if err != nil {
		return nil, &Result{Err: err}
	}

	result = g.client.get(url, &gistForks)
	return
}

// Delete deletes a gist by its id
//
// https://developer.github.com/v3/gists/#delete-a-gist
func (g *GistsService) Delete(uri *Hyperlink, uriParams M) (success bool, result *Result) {
	url, err := ExpandWithDefault(uri, &GistsURL, uriParams)
	if err != nil {
		return false, &Result{Err: err}
	}

	result = g.client.delete(url, nil, nil)
	success = (!result.HasError() && result.Response.StatusCode == 204)
	return
}

// GistFile is a representation of the file stored in a gist
type GistFile struct {
	*hypermedia.HALResource

	FileName  string `json:"filename,omitempty"`
	Type      string `json:"type,omitempty"`
	Language  string `json:"language,omitempty"`
	RawURL    string `json:"raw_url,omitempty"`
	Size      int    `json:"size,omitempty"`
	Truncated bool   `json:"truncated,omitempty"`
	Content   string `json:"content,omitempty"`
}

// Gist is a representation of a gist on github, a standalone file that acts as a
// sole element of its own repository
type Gist struct {
	*hypermedia.HALResource

	ID          string               `json:"id,omitempty"`
	Comments    float64              `json:"comments,omitempty"`
	CommentsURL string               `json:"comments_url,omitempty"`
	CommitsURL  string               `json:"commits_url,omitempty"`
	CreatedAt   *time.Time           `json:"created_at,omitempty"`
	Description string               `json:"description,omitempty"`
	Files       map[string]*GistFile `json:"files,omitempty"`
	Forks       []GistFork           `json:"forks,omitempty"`
	ForksURL    Hyperlink            `json:"forks_url,omitempty"`
	GitPullURL  Hyperlink            `json:"git_pull_url,omitempty"`
	History     []GistCommit         `json:"history,omitempty"`
	GitPushURL  Hyperlink            `json:"git_push_url,omitempty"`
	HtmlURL     Hyperlink            `json:"html_url,omitempty"`
	Owner       *User                `json:"owner,omitempty"`
	Public      bool                 `json:"public,omitempty"`
	UpdatedAt   *time.Time           `json:"updated_at,omitempty"`
	URL         string               `json:"url,omitempty"`
	User        *User                `json:"user,omitempty"`
}

//GistFork represents information about a fork of a gist
type GistFork struct {
	*hypermedia.HALResource

	ID        string     `json:"id,omitempty"`
	CreatedAt *time.Time `json:"created_at,omitempty"`
	UpdatedAt *time.Time `json:"updated_at,omitempty"`
	URL       string     `json:"url,omitempty"`
	User      *User      `json:"user,omitempty"`
}

// GistCommit is a representation of one of the commits to a gist
type GistCommit struct {
	*hypermedia.HALResource

	ChangeStatus *GistChangeStatus `json:"change_status,omitempty"`
	CommittedAt  *time.Time        `json:"committed_at,omitempty"`
	URL          string            `json:"url,omitempty"`
	User         *User             `json:"user,omitempty"`
	Version      string            `json:"version,omitempty"`
}

// GistChangeStatus represents all changes on a given Gist
type GistChangeStatus struct {
	Additions int `json:"additions,omitempty"`
	Deletions int `json:"deletions,omitempty"`
	Total     int `json:"total,omitempty"`
}
<|endoftext|>


<commit_before>package simpleamqp

import (
	"log"
	"strings"
	"time"

	"github.com/streadway/amqp"
)

const (
	timeToReconnect = 5 * time.Second
)

// QueueOptions holds the flags when declaring a queue:
// durable, delete, exclusive
type QueueOptions struct {
	Durable   bool
	Delete    bool
	Exclusive bool
}

// DefaultQueueOptions define the default options when creating queues:
// durable, not autodelete and not exclusive
var DefaultQueueOptions = QueueOptions{
	Durable:   true,
	Delete:    false,
	Exclusive: false,
}

func normalizeURL(url string) string {
	return strings.Replace(url, "rabbitmq://", "amqp://", -1)
}

func setup(url string) (*amqp.Connection, *amqp.Channel, error) {
	conn, err := amqp.Dial(normalizeURL(url))
	if err != nil {
		log.Println("[simpleamqp] Error dial", err)
		return nil, nil, err
	}

	ch, err := conn.Channel()
	if err != nil {
		log.Println("[simpleamqp] Error channel", err)
		conn.Close()
		return nil, nil, err
	}
	return conn, ch, nil
}

func exchangeDeclare(ch *amqp.Channel, exchange string) error {
	log.Println("[simpleamqp] Exchange declare", exchange)
	err := ch.ExchangeDeclare(exchange, "topic", true, false, false, false, nil)
	if err != nil {
		log.Println("[simpleamqp] Error declaring exchange", err)
	}
	return err
}

func queueDeclare(ch *amqp.Channel, queue string, queueOptions QueueOptions) (amqp.Queue, error) {
	log.Println("[simpleamqp] Queue declare", queue)
	q, err := ch.QueueDeclare(
		queue, // name of the queue
		queueOptions.Durable,
		queueOptions.Delete,
		queueOptions.Exclusive,
		false, // noWait
		nil,   // arguments
	)
	if err != nil {
		log.Println("[simpleamqp] rror declaring queue", err)
	}
	return q, err
}
<commit_msg>[TYPO] Fix mistyped Error string<commit_after>package simpleamqp

import (
	"log"
	"strings"
	"time"

	"github.com/streadway/amqp"
)

const (
	timeToReconnect = 5 * time.Second
)

// QueueOptions holds the flags when declaring a queue:
// durable, delete, exclusive
type QueueOptions struct {
	Durable   bool
	Delete    bool
	Exclusive bool
}

// DefaultQueueOptions define the default options when creating queues:
// durable, not autodelete and not exclusive
var DefaultQueueOptions = QueueOptions{
	Durable:   true,
	Delete:    false,
	Exclusive: false,
}

func normalizeURL(url string) string {
	return strings.Replace(url, "rabbitmq://", "amqp://", -1)
}

func setup(url string) (*amqp.Connection, *amqp.Channel, error) {
	conn, err := amqp.Dial(normalizeURL(url))
	if err != nil {
		log.Println("[simpleamqp] Error dial", err)
		return nil, nil, err
	}

	ch, err := conn.Channel()
	if err != nil {
		log.Println("[simpleamqp] Error channel", err)
		conn.Close()
		return nil, nil, err
	}
	return conn, ch, nil
}

func exchangeDeclare(ch *amqp.Channel, exchange string) error {
	log.Println("[simpleamqp] Exchange declare", exchange)
	err := ch.ExchangeDeclare(exchange, "topic", true, false, false, false, nil)
	if err != nil {
		log.Println("[simpleamqp] Error declaring exchange", err)
	}
	return err
}

func queueDeclare(ch *amqp.Channel, queue string, queueOptions QueueOptions) (amqp.Queue, error) {
	log.Println("[simpleamqp] Queue declare", queue)
	q, err := ch.QueueDeclare(
		queue, // name of the queue
		queueOptions.Durable,
		queueOptions.Delete,
		queueOptions.Exclusive,
		false, // noWait
		nil,   // arguments
	)
	if err != nil {
		log.Println("[simpleamqp] Error declaring queue", err)
	}
	return q, err
}
<|endoftext|>


<commit_before>package main

import (
	// "bufio"
	"fmt"
	"github.com/dimfeld/gocache"
	"github.com/dimfeld/httppath"
	"github.com/dimfeld/httptreemux"
	"log"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"time"
)

var (
	logger      *log.Logger
	debugLogger *log.Logger
	debugMode   bool
)

func debugf(format string, args ...interface{}) {
	if debugMode {
		debugLogger.Printf(format, args...)
	}
}

func debug(args ...interface{}) {
	if debugMode {
		debugLogger.Println(args...)
	}
}
func catchSIGINT(f func(), quit bool) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for _ = range c {
			f()
			if quit {
				os.Exit(1)
			}
		}
	}()
}

type GlobalData struct {
	// Configuration Data
	indexPosts          int
	postsDir            string
	dataDir             http.Dir
	tagsPath            string
	tagsPageReverseSort bool

	// General cache
	cache    gocache.Cache
	memCache gocache.Cache
	archive  ArchiveSpecList
}

type simpleBlogHandler func(*GlobalData, http.ResponseWriter, *http.Request, map[string]string)

func handlerWrapper(handler simpleBlogHandler, globalData *GlobalData) httptreemux.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request, urlParams map[string]string) {
		logger.Printf("%s %s", r.Method, r.URL.Path)
		startTime := time.Now()
		handler(globalData, w, r, urlParams)
		endTime := time.Now()
		duration := endTime.Sub(startTime)
		logger.Printf("   Handled in %d us", duration/time.Microsecond)
	}
}

func fileWrapper(filename string, handler httptreemux.HandlerFunc) httptreemux.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request, urlParams map[string]string) {
		urlParams["file"] = filename
		handler(w, r, urlParams)
	}
}

func filePrefixWrapper(prefix string, handler httptreemux.HandlerFunc) httptreemux.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request, urlParams map[string]string) {
		urlParams["file"] = "/" + filepath.Join(prefix, httppath.Clean(urlParams["file"]))
		handler(w, r, urlParams)
	}
}

func isDirectory(dirPath string) bool {
	stat, err := os.Stat(dirPath)
	if err != nil || !stat.IsDir() {
		return false
	}
	return true
}

func main() {
	// TODO Load these from configuration
	cacheDir, _ := filepath.Abs("cache")
	dataDirStr, _ := filepath.Abs("./testdata")
	dataDir := http.Dir(dataDirStr)
	postsDir := filepath.Join(dataDirStr, "posts")
	logFilename, _ := filepath.Abs("simpleblog.log")
	logPrefix := ""
	tagsPageReverseSort := true
	indexPosts := 15
	debugMode = true

	logFile, err := os.OpenFile(logFilename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0640)
	if err != nil {
		fmt.Println("Could not open log file", logFilename)
		os.Exit(1)
	}

	logBuffer := bufio.NewWriter(logFile)

	closer := func() {
		logBuffer.Flush()
		logger.Println("Shutting down...")
		logFile.Sync()
		logFile.Close()
	}

	catchSIGINT(closer, true)
	defer closer()

	var logWriter io.Writer = logBuffer
	if debugMode {
		// In debug mode, use unbuffered logging so that they come out right away.
		logWriter = logFile
	}

	logger = log.New(logBuffer, logPrefix, log.LstdFlags)
	debugLogger = log.New(logBuffer, "DEBUG ", log.LstdFlags)
	logger.Println("Starting...")

	diskCache, err := gocache.NewDiskCache(cacheDir)
	if err != nil {
		logger.Fatal("Could not create disk cache in", cacheDir)
	}

	if !isDirectory(dataDirStr) {
		logger.Fatal("Could not find data directory", dataDirStr)
	}

	if !isDirectory(postsDir) {
		logger.Fatal("Could not find posts directory", postsDir)
	}

	if !isDirectory(filepath.Join(dataDirStr, "assets")) {
		logger.Fatal("Could not find assets directory", filepath.Join(dataDirStr, "assets"))
	}

	if !isDirectory(filepath.Join(dataDirStr, "images")) {
		logger.Fatal("Could not find assets directory", filepath.Join(dataDirStr, "images"))
	}

	// Large memory cache uses 64 MiB at most, with the largest object being 8 MiB.
	largeObjectLimit := 8 * 1024 * 1024
	largeMemCache := gocache.NewMemoryCache(64*1024*1024, largeObjectLimit)
	// Small memory cache uses 16 MiB at most, with the largest object being 16KiB.
	smallObjectLimit := 16 * 1024
	smallMemCache := gocache.NewMemoryCache(16*1024*1024, smallObjectLimit)

	// Create a split cache, putting all objects smaller than 16 KiB into the small cache.
	// This split cache prevents a few large objects from evicting all the smaller objects.
	memCache := gocache.NewSplitSize(
		gocache.SplitSizeChild{MaxSize: smallObjectLimit, Cache: smallMemCache},
		gocache.SplitSizeChild{MaxSize: largeObjectLimit, Cache: largeMemCache})

	multiLevelCache := gocache.MultiLevel{0: memCache, 1: diskCache}

	tagsPath := filepath.Join(cacheDir, "tags.json")
	os.Remove(tagsPath)
	globalData := &GlobalData{
		cache:               multiLevelCache,
		memCache:            memCache,
		dataDir:             dataDir,
		postsDir:            postsDir,
		tagsPath:            tagsPath,
		tagsPageReverseSort: tagsPageReverseSort,
		indexPosts:          indexPosts,
	}

	go watchFiles(globalData)

	router := httptreemux.New()
	router.PanicHandler = httptreemux.ShowErrorsPanicHandler

	router.GET("/", handlerWrapper(indexHandler, globalData))
	router.GET("/:year/:month/", handlerWrapper(archiveHandler, globalData))
	router.GET("/:year/:month/:post", handlerWrapper(postHandler, globalData))

	router.GET("/images/*file", filePrefixWrapper("images",
		handlerWrapper(staticNoCompressHandler, globalData)))
	router.GET("/assets/*file", filePrefixWrapper("assets",
		handlerWrapper(staticCompressHandler, globalData)))

	// No tags yet.
	router.GET("/tag/:tag", handlerWrapper(tagHandler, globalData))
	// No pagination yet.
	//router.GET("/tag/:tag/:page", handlerWrapper(tagHandler, globalData))

	router.GET("/:page", handlerWrapper(pageHandler, globalData))
	router.GET("/favicon.ico", fileWrapper("assets/favicon.ico",
		handlerWrapper(staticCompressHandler, globalData)))

	http.ListenAndServe(":8080", router)
}
<commit_msg>Oops, make last commit work.<commit_after>package main

import (
	// "bufio"
	"bufio"
	"fmt"
	"github.com/dimfeld/gocache"
	"github.com/dimfeld/httppath"
	"github.com/dimfeld/httptreemux"
	"io"
	"log"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"time"
)

var (
	logger      *log.Logger
	debugLogger *log.Logger
	debugMode   bool
)

func debugf(format string, args ...interface{}) {
	if debugMode {
		debugLogger.Printf(format, args...)
	}
}

func debug(args ...interface{}) {
	if debugMode {
		debugLogger.Println(args...)
	}
}
func catchSIGINT(f func(), quit bool) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for _ = range c {
			f()
			if quit {
				os.Exit(1)
			}
		}
	}()
}

type GlobalData struct {
	// Configuration Data
	indexPosts          int
	postsDir            string
	dataDir             http.Dir
	tagsPath            string
	tagsPageReverseSort bool

	// General cache
	cache    gocache.Cache
	memCache gocache.Cache
	archive  ArchiveSpecList
}

type simpleBlogHandler func(*GlobalData, http.ResponseWriter, *http.Request, map[string]string)

func handlerWrapper(handler simpleBlogHandler, globalData *GlobalData) httptreemux.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request, urlParams map[string]string) {
		logger.Printf("%s %s", r.Method, r.URL.Path)
		startTime := time.Now()
		handler(globalData, w, r, urlParams)
		endTime := time.Now()
		duration := endTime.Sub(startTime)
		logger.Printf("   Handled in %d us", duration/time.Microsecond)
	}
}

func fileWrapper(filename string, handler httptreemux.HandlerFunc) httptreemux.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request, urlParams map[string]string) {
		urlParams["file"] = filename
		handler(w, r, urlParams)
	}
}

func filePrefixWrapper(prefix string, handler httptreemux.HandlerFunc) httptreemux.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request, urlParams map[string]string) {
		urlParams["file"] = "/" + filepath.Join(prefix, httppath.Clean(urlParams["file"]))
		handler(w, r, urlParams)
	}
}

func isDirectory(dirPath string) bool {
	stat, err := os.Stat(dirPath)
	if err != nil || !stat.IsDir() {
		return false
	}
	return true
}

func main() {
	// TODO Load these from configuration
	cacheDir, _ := filepath.Abs("cache")
	dataDirStr, _ := filepath.Abs("./testdata")
	dataDir := http.Dir(dataDirStr)
	postsDir := filepath.Join(dataDirStr, "posts")
	logFilename, _ := filepath.Abs("simpleblog.log")
	logPrefix := ""
	tagsPageReverseSort := true
	indexPosts := 15
	debugMode = true

	logFile, err := os.OpenFile(logFilename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0640)
	if err != nil {
		fmt.Println("Could not open log file", logFilename)
		os.Exit(1)
	}

	logBuffer := bufio.NewWriter(logFile)

	closer := func() {
		logger.Println("Shutting down...")
		logBuffer.Flush()
		logFile.Sync()
		logFile.Close()
	}

	catchSIGINT(closer, true)
	defer closer()

	var logWriter io.Writer = logBuffer
	// if debugMode {
	// 	// In debug mode, use unbuffered logging so that they come out right away.
	// 	logWriter = logFile
	// }

	logger = log.New(logWriter, logPrefix, log.LstdFlags)
	debugLogger = log.New(logWriter, "DEBUG ", log.LstdFlags)
	logger.Println("Starting...")

	diskCache, err := gocache.NewDiskCache(cacheDir)
	if err != nil {
		logger.Fatal("Could not create disk cache in", cacheDir)
	}

	if !isDirectory(dataDirStr) {
		logger.Fatal("Could not find data directory", dataDirStr)
	}

	if !isDirectory(postsDir) {
		logger.Fatal("Could not find posts directory", postsDir)
	}

	if !isDirectory(filepath.Join(dataDirStr, "assets")) {
		logger.Fatal("Could not find assets directory", filepath.Join(dataDirStr, "assets"))
	}

	if !isDirectory(filepath.Join(dataDirStr, "images")) {
		logger.Fatal("Could not find assets directory", filepath.Join(dataDirStr, "images"))
	}

	// Large memory cache uses 64 MiB at most, with the largest object being 8 MiB.
	largeObjectLimit := 8 * 1024 * 1024
	largeMemCache := gocache.NewMemoryCache(64*1024*1024, largeObjectLimit)
	// Small memory cache uses 16 MiB at most, with the largest object being 16KiB.
	smallObjectLimit := 16 * 1024
	smallMemCache := gocache.NewMemoryCache(16*1024*1024, smallObjectLimit)

	// Create a split cache, putting all objects smaller than 16 KiB into the small cache.
	// This split cache prevents a few large objects from evicting all the smaller objects.
	memCache := gocache.NewSplitSize(
		gocache.SplitSizeChild{MaxSize: smallObjectLimit, Cache: smallMemCache},
		gocache.SplitSizeChild{MaxSize: largeObjectLimit, Cache: largeMemCache})

	multiLevelCache := gocache.MultiLevel{0: memCache, 1: diskCache}

	tagsPath := filepath.Join(cacheDir, "tags.json")
	os.Remove(tagsPath)
	globalData := &GlobalData{
		cache:               multiLevelCache,
		memCache:            memCache,
		dataDir:             dataDir,
		postsDir:            postsDir,
		tagsPath:            tagsPath,
		tagsPageReverseSort: tagsPageReverseSort,
		indexPosts:          indexPosts,
	}

	go watchFiles(globalData)

	router := httptreemux.New()
	router.PanicHandler = httptreemux.ShowErrorsPanicHandler

	router.GET("/", handlerWrapper(indexHandler, globalData))
	router.GET("/:year/:month/", handlerWrapper(archiveHandler, globalData))
	router.GET("/:year/:month/:post", handlerWrapper(postHandler, globalData))

	router.GET("/images/*file", filePrefixWrapper("images",
		handlerWrapper(staticNoCompressHandler, globalData)))
	router.GET("/assets/*file", filePrefixWrapper("assets",
		handlerWrapper(staticCompressHandler, globalData)))

	// No tags yet.
	router.GET("/tag/:tag", handlerWrapper(tagHandler, globalData))
	// No pagination yet.
	//router.GET("/tag/:tag/:page", handlerWrapper(tagHandler, globalData))

	router.GET("/:page", handlerWrapper(pageHandler, globalData))
	router.GET("/favicon.ico", fileWrapper("assets/favicon.ico",
		handlerWrapper(staticCompressHandler, globalData)))

	http.ListenAndServe(":8080", router)
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2016 Lawrence Woodman <lwoodman@vlifesystems.com>
 */
package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/lawrencewoodman/rulehunter"
	"github.com/lawrencewoodman/rulehunter/csvinput"
	"os"
	"path/filepath"
	"runtime"
)

func processExperiment(experimentFilename string, config *config) error {
	var experiment *rulehunter.Experiment
	var p *os.File
	var err error

	progressFullFilename := filepath.Join(
		config.ProgressDir,
		fmt.Sprintf("%s.progress", experimentFilename),
	)
	p, err = os.Create(progressFullFilename)
	if err != nil {
		msg := fmt.Sprintf("Couldn't create progress file: %s", err)
		err := errors.New(msg)
		return err
	}
	defer p.Close()

	experimentFullFilename :=
		filepath.Join(config.ExperimentsDir, experimentFilename)
	experiment, err = loadExperiment(experimentFullFilename)
	if err != nil {
		msg := fmt.Sprintf("Couldn't load experiment file: %s", err)
		reportProgress(p, msg)
		err := errors.New(msg)
		return err
	}
	defer experiment.Close()

	reportProgress(p, "Describing input")
	fieldDescriptions, err := rulehunter.DescribeInput(experiment.Input)
	if err != nil {
		msg := fmt.Sprintf("Couldn't describe input: %s", err)
		reportProgress(p, msg)
		err := errors.New(msg)
		return err
	}

	reportProgress(p, "Generating rules")
	rules, err :=
		rulehunter.GenerateRules(fieldDescriptions, experiment.ExcludeFieldNames)
	if err != nil {
		msg := fmt.Sprintf("Couldn't make report: %s", err)
		reportProgress(p, msg)
		err := errors.New(msg)
		return err
	}

	assessment, err := assessRules(rules, experiment, p)
	if err != nil {
		msg := fmt.Sprintf("Couldn't assess rules: %s", err)
		reportProgress(p, msg)
		err := errors.New(msg)
		return err
	}

	assessment.Sort(experiment.SortOrder)
	assessment.Refine(3)
	sortedRules := assessment.GetRules()

	reportProgress(p, "Tweaking rules")
	tweakableRules := rulehunter.TweakRules(sortedRules, fieldDescriptions)

	assessment2, err := assessRules(tweakableRules, experiment, p)
	if err != nil {
		msg := fmt.Sprintf("Couldn't assess rules: %s", err)
		reportProgress(p, msg)
		err := errors.New(msg)
		return err
	}

	assessment3, err := assessment.Merge(assessment2)
	if err != nil {
		msg := fmt.Sprintf("Couldn't merge assessments: %s", err)
		reportProgress(p, msg)
		err := errors.New(msg)
		return err
	}
	assessment3.Sort(experiment.SortOrder)
	assessment3.Refine(1)

	bestNonCombinedRules := assessment3.GetRules()
	numRulesToCombine := 50
	if len(bestNonCombinedRules) < numRulesToCombine {
		numRulesToCombine = len(bestNonCombinedRules)
	}
	combinedRules :=
		rulehunter.CombineRules(bestNonCombinedRules[:numRulesToCombine])

	assessment4, err := assessRules(combinedRules, experiment, p)
	if err != nil {
		msg := fmt.Sprintf("Couldn't assess rules: %s", err)
		reportProgress(p, msg)
		err := errors.New(msg)
		return err
	}

	assessment5, err := assessment3.Merge(assessment4)
	if err != nil {
		msg := fmt.Sprintf("Couldn't merge assessments: %s", err)
		reportProgress(p, msg)
		err := errors.New(msg)
		return err
	}

	err =
		writeReport(assessment5, experiment, experimentFilename, config.ReportsDir)
	if err != nil {
		reportProgress(p, err.Error())
		return err
	}
	return nil
}

type experimentFile struct {
	Title                 string
	InputFilename         string
	FieldNames            []string
	ExcludeFieldNames     []string
	IsFirstLineFieldNames bool
	Separator             string
	Aggregators           []*rulehunter.AggregatorDesc
	Goals                 []string
	SortOrder             []*rulehunter.SortDesc
}

func loadExperiment(filename string) (*rulehunter.Experiment, error) {
	var f *os.File
	var e experimentFile
	var err error

	f, err = os.Open(filename)
	if err != nil {
		return nil, err
	}

	dec := json.NewDecoder(f)
	if err = dec.Decode(&e); err != nil {
		return nil, err
	}

	input, err := csvinput.New(
		e.FieldNames,
		e.InputFilename,
		rune(e.Separator[0]),
		e.IsFirstLineFieldNames,
	)
	if err != nil {
		return nil, err
	}
	experimentDesc := &rulehunter.ExperimentDesc{
		Title:         e.Title,
		Input:         input,
		Fields:        e.FieldNames,
		ExcludeFields: e.ExcludeFieldNames,
		Aggregators:   e.Aggregators,
		Goals:         e.Goals,
		SortOrder:     e.SortOrder,
	}
	experiment, err := rulehunter.MakeExperiment(experimentDesc)
	return experiment, err
}

func reportProgress(f *os.File, msg string) {
	f.WriteString(fmt.Sprintf("%s\n", msg))
}

func prettyPrintFieldDescriptions(fds map[string]*rulehunter.FieldDescription) {
	fmt.Println("Input Description\n")
	for field, fd := range fds {
		fmt.Println("--------------------------")
		fmt.Printf("%s\n--------------------------\n", field)
		prettyPrintFieldDescription(fd)
	}
	fmt.Println("\n")
}

func prettyPrintFieldDescription(fd *rulehunter.FieldDescription) {
	fmt.Printf("Kind: %s\n", fd.Kind)
	fmt.Printf("Min: %s\n", fd.Min)
	fmt.Printf("Max: %s\n", fd.Max)
	fmt.Printf("MaxDP: %d\n", fd.MaxDP)
	fmt.Printf("Values: %s\n", fd.Values)
}

func assessRules(
	rules []*rulehunter.Rule,
	experiment *rulehunter.Experiment,
	progressFile *os.File,
) (*rulehunter.Assessment, error) {
	var assessment *rulehunter.Assessment
	// TODO: Make this part of the config
	maxProcesses := runtime.NumCPU()
	c := make(chan *rulehunter.AssessRulesMPOutcome)

	reportProgress(progressFile,
		fmt.Sprintf("Assessing rules using %d CPUs...\n", maxProcesses))
	go rulehunter.AssessRulesMP(
		rules,
		experiment.Aggregators,
		experiment.Goals,
		experiment.Input,
		maxProcesses,
		c,
	)
	for o := range c {
		if o.Err != nil {
			return nil, o.Err
		}
		reportProgress(progressFile, fmt.Sprintf("Progress: %.2f%%", o.Progress*100))
		assessment = o.Assessment
	}
	reportProgress(progressFile, "Progress: complete")
	return assessment, nil
}

func moveExperimentToSuccess(experimentFilename string, config *config) error {
	experimentFullFilename :=
		filepath.Join(config.ExperimentsDir, experimentFilename)
	experimentSuccessFullFilename :=
		filepath.Join(config.ExperimentsDir, "success", experimentFilename)
	return os.Rename(experimentFullFilename, experimentSuccessFullFilename)
}

func moveExperimentToFail(experimentFilename string, config *config) error {
	experimentFullFilename :=
		filepath.Join(config.ExperimentsDir, experimentFilename)
	experimentFailFullFilename :=
		filepath.Join(config.ExperimentsDir, "fail", experimentFilename)
	return os.Rename(experimentFullFilename, experimentFailFullFilename)
}
<commit_msg>Tidy rule truncation for CombineRules<commit_after>/*
 * Copyright (C) 2016 Lawrence Woodman <lwoodman@vlifesystems.com>
 */
package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/lawrencewoodman/rulehunter"
	"github.com/lawrencewoodman/rulehunter/csvinput"
	"os"
	"path/filepath"
	"runtime"
)

func processExperiment(experimentFilename string, config *config) error {
	var experiment *rulehunter.Experiment
	var p *os.File
	var err error

	progressFullFilename := filepath.Join(
		config.ProgressDir,
		fmt.Sprintf("%s.progress", experimentFilename),
	)
	p, err = os.Create(progressFullFilename)
	if err != nil {
		msg := fmt.Sprintf("Couldn't create progress file: %s", err)
		err := errors.New(msg)
		return err
	}
	defer p.Close()

	experimentFullFilename :=
		filepath.Join(config.ExperimentsDir, experimentFilename)
	experiment, err = loadExperiment(experimentFullFilename)
	if err != nil {
		msg := fmt.Sprintf("Couldn't load experiment file: %s", err)
		reportProgress(p, msg)
		err := errors.New(msg)
		return err
	}
	defer experiment.Close()

	reportProgress(p, "Describing input")
	fieldDescriptions, err := rulehunter.DescribeInput(experiment.Input)
	if err != nil {
		msg := fmt.Sprintf("Couldn't describe input: %s", err)
		reportProgress(p, msg)
		err := errors.New(msg)
		return err
	}

	reportProgress(p, "Generating rules")
	rules, err :=
		rulehunter.GenerateRules(fieldDescriptions, experiment.ExcludeFieldNames)
	if err != nil {
		msg := fmt.Sprintf("Couldn't make report: %s", err)
		reportProgress(p, msg)
		err := errors.New(msg)
		return err
	}

	assessment, err := assessRules(rules, experiment, p)
	if err != nil {
		msg := fmt.Sprintf("Couldn't assess rules: %s", err)
		reportProgress(p, msg)
		err := errors.New(msg)
		return err
	}

	assessment.Sort(experiment.SortOrder)
	assessment.Refine(3)
	sortedRules := assessment.GetRules()

	reportProgress(p, "Tweaking rules")
	tweakableRules := rulehunter.TweakRules(sortedRules, fieldDescriptions)

	assessment2, err := assessRules(tweakableRules, experiment, p)
	if err != nil {
		msg := fmt.Sprintf("Couldn't assess rules: %s", err)
		reportProgress(p, msg)
		err := errors.New(msg)
		return err
	}

	assessment3, err := assessment.Merge(assessment2)
	if err != nil {
		msg := fmt.Sprintf("Couldn't merge assessments: %s", err)
		reportProgress(p, msg)
		err := errors.New(msg)
		return err
	}
	assessment3.Sort(experiment.SortOrder)
	assessment3.Refine(1)

	bestNonCombinedRules := assessment3.GetRules()
	numRulesToCombine := 50
	combinedRules := rulehunter.CombineRules(
		truncateRules(bestNonCombinedRules, numRulesToCombine),
	)

	assessment4, err := assessRules(combinedRules, experiment, p)
	if err != nil {
		msg := fmt.Sprintf("Couldn't assess rules: %s", err)
		reportProgress(p, msg)
		err := errors.New(msg)
		return err
	}

	assessment5, err := assessment3.Merge(assessment4)
	if err != nil {
		msg := fmt.Sprintf("Couldn't merge assessments: %s", err)
		reportProgress(p, msg)
		err := errors.New(msg)
		return err
	}

	err =
		writeReport(assessment5, experiment, experimentFilename, config.ReportsDir)
	if err != nil {
		reportProgress(p, err.Error())
		return err
	}
	return nil
}

type experimentFile struct {
	Title                 string
	InputFilename         string
	FieldNames            []string
	ExcludeFieldNames     []string
	IsFirstLineFieldNames bool
	Separator             string
	Aggregators           []*rulehunter.AggregatorDesc
	Goals                 []string
	SortOrder             []*rulehunter.SortDesc
}

func loadExperiment(filename string) (*rulehunter.Experiment, error) {
	var f *os.File
	var e experimentFile
	var err error

	f, err = os.Open(filename)
	if err != nil {
		return nil, err
	}

	dec := json.NewDecoder(f)
	if err = dec.Decode(&e); err != nil {
		return nil, err
	}

	input, err := csvinput.New(
		e.FieldNames,
		e.InputFilename,
		rune(e.Separator[0]),
		e.IsFirstLineFieldNames,
	)
	if err != nil {
		return nil, err
	}
	experimentDesc := &rulehunter.ExperimentDesc{
		Title:         e.Title,
		Input:         input,
		Fields:        e.FieldNames,
		ExcludeFields: e.ExcludeFieldNames,
		Aggregators:   e.Aggregators,
		Goals:         e.Goals,
		SortOrder:     e.SortOrder,
	}
	experiment, err := rulehunter.MakeExperiment(experimentDesc)
	return experiment, err
}

func reportProgress(f *os.File, msg string) {
	f.WriteString(fmt.Sprintf("%s\n", msg))
}

func prettyPrintFieldDescriptions(fds map[string]*rulehunter.FieldDescription) {
	fmt.Println("Input Description\n")
	for field, fd := range fds {
		fmt.Println("--------------------------")
		fmt.Printf("%s\n--------------------------\n", field)
		prettyPrintFieldDescription(fd)
	}
	fmt.Println("\n")
}

func prettyPrintFieldDescription(fd *rulehunter.FieldDescription) {
	fmt.Printf("Kind: %s\n", fd.Kind)
	fmt.Printf("Min: %s\n", fd.Min)
	fmt.Printf("Max: %s\n", fd.Max)
	fmt.Printf("MaxDP: %d\n", fd.MaxDP)
	fmt.Printf("Values: %s\n", fd.Values)
}

func assessRules(
	rules []*rulehunter.Rule,
	experiment *rulehunter.Experiment,
	progressFile *os.File,
) (*rulehunter.Assessment, error) {
	var assessment *rulehunter.Assessment
	// TODO: Make this part of the config
	maxProcesses := runtime.NumCPU()
	c := make(chan *rulehunter.AssessRulesMPOutcome)

	reportProgress(progressFile,
		fmt.Sprintf("Assessing rules using %d CPUs...\n", maxProcesses))
	go rulehunter.AssessRulesMP(
		rules,
		experiment.Aggregators,
		experiment.Goals,
		experiment.Input,
		maxProcesses,
		c,
	)
	for o := range c {
		if o.Err != nil {
			return nil, o.Err
		}
		reportProgress(progressFile, fmt.Sprintf("Progress: %.2f%%", o.Progress*100))
		assessment = o.Assessment
	}
	reportProgress(progressFile, "Progress: complete")
	return assessment, nil
}

func moveExperimentToSuccess(experimentFilename string, config *config) error {
	experimentFullFilename :=
		filepath.Join(config.ExperimentsDir, experimentFilename)
	experimentSuccessFullFilename :=
		filepath.Join(config.ExperimentsDir, "success", experimentFilename)
	return os.Rename(experimentFullFilename, experimentSuccessFullFilename)
}

func moveExperimentToFail(experimentFilename string, config *config) error {
	experimentFullFilename :=
		filepath.Join(config.ExperimentsDir, experimentFilename)
	experimentFailFullFilename :=
		filepath.Join(config.ExperimentsDir, "fail", experimentFilename)
	return os.Rename(experimentFullFilename, experimentFailFullFilename)
}

func truncateRules(rules []*rulehunter.Rule, numRules int) []*rulehunter.Rule {
	if len(rules) < numRules {
		numRules = len(rules)
	}
	return rules[:numRules]
}
<|endoftext|>


<commit_before><commit_msg>Only show environment when changed<commit_after><|endoftext|>


<commit_before>package extra

import (
	"bufio"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"

	"github.com/jmoiron/sqlx"

	"github.com/elpinal/coco3/extra/ast"
	"github.com/elpinal/coco3/extra/parser" // Only for ParseError.
	"github.com/elpinal/coco3/extra/typed"
	"github.com/elpinal/coco3/extra/types"
)

type Env struct {
	cmds map[string]typed.Command
	Option
}

type Option struct {
	DB *sqlx.DB
}

func New(opt Option) Env {
	return Env{
		Option: opt,
		cmds: map[string]typed.Command{
			"exec":    execCommand,
			"execenv": execenvCommand, // exec with env
			"cd":      cdCommand,
			"exit":    exitCommand,
			"free":    freeCommand,
			"history": historyCommand,

			"remove": removeCommand,

			"ls":  lsCommand,
			"man": manCommand,

			"git":   gitCommand,
			"cargo": cargoCommand,
			"go":    goCommand,
			"stack": stackCommand,
			"lein":  leinCommand,

			"vim":    vimCommand,
			"emacs":  emacsCommand,
			"screen": screenCommand,

			"cnp":  cnpCommand,
			"gvmn": gvmnCommand,
			"vvmn": vvmnCommand,
		},
	}
}

func WithoutDefault() Env {
	return Env{cmds: make(map[string]typed.Command)}
}

func (e *Env) Bind(name string, c typed.Command) {
	e.cmds[name] = c
}

func (e *Env) Eval(command *ast.Command) (err error) {
	if command == nil {
		return nil
	}
	tc, found := e.cmds[command.Name.Lit]
	if !found {
		return &parser.ParseError{
			Msg:    fmt.Sprintf("no such typed command: %q", command.Name.Lit),
			Line:   command.Name.Line,
			Column: command.Name.Column,
		}
	}
	if len(command.Args) != len(tc.Params) {
		return &parser.ParseError{
			Msg:    fmt.Sprintf("the length of args (%d) != the one of params (%d)", len(command.Args), len(tc.Params)),
			Line:   command.Name.Line,
			Column: command.Name.Column,
		}
	}
	for i, arg := range command.Args {
		if arg.Type() != tc.Params[i] {
			return &parser.ParseError{
				Msg:    fmt.Sprintf("type mismatch: (%v) (type of %v) does not match with (%v) (expected type)", arg.Type(), arg, tc.Params[i]),
				Line:   command.Name.Line,
				Column: command.Name.Column,
			}
		}
	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	defer close(c)
	defer signal.Stop(c)

	defer func() {
		r := recover()
		if r == nil {
			return
		}
		var ok bool
		// may overwrite error of tc.Fn.
		err, ok = r.(error)
		if !ok {
			panic(r)
		}
	}()

	return tc.Fn(command.Args, e.DB)
}

func toSlice(list ast.List) ([]string, error) {
	ret := make([]string, 0, list.Length())
	for {
		switch x := list.(type) {
		case *ast.Cons:
			ret = append(ret, x.Head)
			list = x.Tail
		case *ast.Empty:
			return ret, nil
		default:
			return nil, fmt.Errorf("unexpected list type: %T", x)
		}
	}
}

var execCommand = typed.Command{
	Params: []types.Type{types.String, types.StringList},
	Fn: func(args []ast.Expr, _ *sqlx.DB) error {
		cmdArgs, err := toSlice(args[1].(ast.List))
		if err != nil {
			return errors.Wrap(err, "exec")
		}
		cmd := stdCmd(args[0].(*ast.String).Lit, cmdArgs...)
		return cmd.Run()
	},
}

var execenvCommand = typed.Command{
	Params: []types.Type{types.StringList, types.String, types.StringList},
	Fn: func(args []ast.Expr, _ *sqlx.DB) error {
		cmdArgs, err := toSlice(args[2].(ast.List))
		if err != nil {
			return errors.Wrap(err, "execenv")
		}
		cmd := stdCmd(args[1].(*ast.String).Lit, cmdArgs...)
		env, err := toSlice(args[0].(ast.List))
		if err != nil {
			return errors.Wrap(err, "execenv")
		}
		for _, e := range env {
			if !strings.Contains(e, "=") {
				return errors.New(`execenv: each item of the first argument must be the form "key=value"`)
			}
		}
		cmd.Env = append(env, os.Environ()...)
		return cmd.Run()
	},
}

var cdCommand = typed.Command{
	Params: []types.Type{types.String},
	Fn: func(args []ast.Expr, _ *sqlx.DB) error {
		return os.Chdir(args[0].(*ast.String).Lit)
	},
}

var exitCommand = typed.Command{
	Params: []types.Type{types.Int},
	Fn: func(args []ast.Expr, _ *sqlx.DB) error {
		n, err := strconv.Atoi(args[0].(*ast.Int).Lit)
		if err != nil {
			return err
		}
		os.Exit(n)
		return nil
	},
}

var freeCommand = typed.Command{
	Params: []types.Type{types.String, types.StringList},
	Fn: func(args []ast.Expr, _ *sqlx.DB) error {
		cmdArgs, err := toSlice(args[1].(ast.List))
		if err != nil {
			return errors.Wrap(err, "free")
		}
		name := args[0].(*ast.String).Lit
		cmd := exec.Cmd{Path: name, Args: append([]string{name}, cmdArgs...)}
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = os.Stdin
		return cmd.Run()
	},
}

func commandsInCommand(name string) func([]ast.Expr, *sqlx.DB) error {
	return func(args []ast.Expr, _ *sqlx.DB) error {
		cmdArgs, err := toSlice(args[1].(ast.List))
		if err != nil {
			return errors.Wrap(err, name)
		}
		var cmd *exec.Cmd
		switch lit := args[0].(*ast.Ident).Lit; lit {
		case "command":
			cmd = stdCmd(name, cmdArgs...)
		default:
			cmd = stdCmd(name, append([]string{lit}, cmdArgs...)...)
		}
		return cmd.Run()
	}
}

func goCommand1() func([]ast.Expr, *sqlx.DB) error {
	return func(args []ast.Expr, _ *sqlx.DB) error {
		name := "go"
		cmdArgs, err := toSlice(args[1].(ast.List))
		if err != nil {
			return errors.Wrap(err, name)
		}
		var cmd *exec.Cmd
		switch lit := args[0].(*ast.Ident).Lit; lit {
		case "command":
			cmd = stdCmd(name, cmdArgs...)
		case "testall":
			// I can't be confident in using such
			// a subcommand-specific way.  Another suggestion might
			// be like `go test all`, where 'all' is a postfix
			// operator of './...'.
			cmd = stdCmd(name, append([]string{"test"}, append(cmdArgs, "./...")...)...)
		default:
			cmd = stdCmd(name, append([]string{lit}, cmdArgs...)...)
		}
		return cmd.Run()
	}
}

var gitCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("git"),
}

var cargoCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("cargo"),
}

var goCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     goCommand1(),
}

var stackCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("stack"),
}

var leinCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("lein"),
}

func stdCmd(name string, args ...string) *exec.Cmd {
	cmd := exec.Command(name, args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin
	return cmd
}

func stdExec(name string, args ...string) func([]ast.Expr, *sqlx.DB) error {
	return func(_ []ast.Expr, _ *sqlx.DB) error {
		return stdCmd(name, args...).Run()
	}
}

var vimCommand = typed.Command{
	Params: []types.Type{},
	Fn:     stdExec("vim"),
}

var emacsCommand = typed.Command{
	Params: []types.Type{},
	Fn:     stdExec("emacs"),
}

func withEnv(s string, cmd *exec.Cmd) *exec.Cmd {
	if cmd.Env == nil {
		cmd.Env = os.Environ()
	}
	cmd.Env = append(cmd.Env, s)
	return cmd
}

var screenCommand = typed.Command{
	Params: []types.Type{},
	Fn: func(_ []ast.Expr, _ *sqlx.DB) error {
		return withEnv("LANG=en_US.UTF-8", stdCmd("screen")).Run()
	},
}

type execution struct {
	Time time.Time
	Line string
}

var historyCommand = typed.Command{
	Params: []types.Type{types.String},
	Fn: func(e []ast.Expr, db *sqlx.DB) error {
		var jsonFormat bool
		var enc *json.Encoder
		switch format := e[0].(*ast.String).Lit; format {
		case "json":
			jsonFormat = true
		case "lines":
		default:
			return fmt.Errorf("history: format %q is not supported", format)
		}

		buf := bufio.NewWriter(os.Stdout)
		if jsonFormat {
			enc = json.NewEncoder(buf)
		}
		rows, err := db.Queryx("select * from command_info")
		if err != nil {
			return err
		}
		data := execution{}
		for rows.Next() {
			err := rows.StructScan(&data)
			if err != nil {
				return err
			}
			if jsonFormat {
				err := enc.Encode(data)
				if err != nil {
					return err
				}
			} else {
				buf.WriteString(data.Time.Format("Mon, 02 Jan 2006 15:04:05"))
				buf.Write([]byte("  "))
				buf.WriteString(data.Line)
				buf.WriteByte('\n')
			}
		}
		return buf.Flush()
	},
}

var lsCommand = typed.Command{
	Params: []types.Type{},
	Fn:     stdExec("ls", "--show-control-chars", "--color=auto"),
}

var manCommand = typed.Command{
	Params: []types.Type{types.String},
	Fn: func(e []ast.Expr, _ *sqlx.DB) error {
		lit := e[0].(*ast.String).Lit
		return stdCmd("man", lit).Run()
	},
}

var removeCommand = typed.Command{
	Params: []types.Type{types.String},
	Fn: func(e []ast.Expr, _ *sqlx.DB) error {
		lit := e[0].(*ast.String).Lit
		return stdCmd("rm", "-i", lit).Run()
	},
}

var cnpCommand = typed.Command{
	Params: []types.Type{types.String},
	Fn: func(e []ast.Expr, _ *sqlx.DB) error {
		lit := e[0].(*ast.String).Lit
		return stdCmd("create-new-project", lit).Run()
	},
}

var gvmnCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("gvmn"),
}

var vvmnCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("vvmn"),
}
<commit_msg>Add 'make' typed command<commit_after>package extra

import (
	"bufio"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"strconv"
	"strings"
	"time"

	"github.com/pkg/errors"

	"github.com/jmoiron/sqlx"

	"github.com/elpinal/coco3/extra/ast"
	"github.com/elpinal/coco3/extra/parser" // Only for ParseError.
	"github.com/elpinal/coco3/extra/typed"
	"github.com/elpinal/coco3/extra/types"
)

type Env struct {
	cmds map[string]typed.Command
	Option
}

type Option struct {
	DB *sqlx.DB
}

func New(opt Option) Env {
	return Env{
		Option: opt,
		cmds: map[string]typed.Command{
			"exec":    execCommand,
			"execenv": execenvCommand, // exec with env
			"cd":      cdCommand,
			"exit":    exitCommand,
			"free":    freeCommand,
			"history": historyCommand,

			"remove": removeCommand,

			"ls":   lsCommand,
			"man":  manCommand,
			"make": makeCommand,

			"git":   gitCommand,
			"cargo": cargoCommand,
			"go":    goCommand,
			"stack": stackCommand,
			"lein":  leinCommand,

			"vim":    vimCommand,
			"emacs":  emacsCommand,
			"screen": screenCommand,

			"cnp":  cnpCommand,
			"gvmn": gvmnCommand,
			"vvmn": vvmnCommand,
		},
	}
}

func WithoutDefault() Env {
	return Env{cmds: make(map[string]typed.Command)}
}

func (e *Env) Bind(name string, c typed.Command) {
	e.cmds[name] = c
}

func (e *Env) Eval(command *ast.Command) (err error) {
	if command == nil {
		return nil
	}
	tc, found := e.cmds[command.Name.Lit]
	if !found {
		return &parser.ParseError{
			Msg:    fmt.Sprintf("no such typed command: %q", command.Name.Lit),
			Line:   command.Name.Line,
			Column: command.Name.Column,
		}
	}
	if len(command.Args) != len(tc.Params) {
		return &parser.ParseError{
			Msg:    fmt.Sprintf("the length of args (%d) != the one of params (%d)", len(command.Args), len(tc.Params)),
			Line:   command.Name.Line,
			Column: command.Name.Column,
		}
	}
	for i, arg := range command.Args {
		if arg.Type() != tc.Params[i] {
			return &parser.ParseError{
				Msg:    fmt.Sprintf("type mismatch: (%v) (type of %v) does not match with (%v) (expected type)", arg.Type(), arg, tc.Params[i]),
				Line:   command.Name.Line,
				Column: command.Name.Column,
			}
		}
	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	defer close(c)
	defer signal.Stop(c)

	defer func() {
		r := recover()
		if r == nil {
			return
		}
		var ok bool
		// may overwrite error of tc.Fn.
		err, ok = r.(error)
		if !ok {
			panic(r)
		}
	}()

	return tc.Fn(command.Args, e.DB)
}

func toSlice(list ast.List) ([]string, error) {
	ret := make([]string, 0, list.Length())
	for {
		switch x := list.(type) {
		case *ast.Cons:
			ret = append(ret, x.Head)
			list = x.Tail
		case *ast.Empty:
			return ret, nil
		default:
			return nil, fmt.Errorf("unexpected list type: %T", x)
		}
	}
}

var execCommand = typed.Command{
	Params: []types.Type{types.String, types.StringList},
	Fn: func(args []ast.Expr, _ *sqlx.DB) error {
		cmdArgs, err := toSlice(args[1].(ast.List))
		if err != nil {
			return errors.Wrap(err, "exec")
		}
		cmd := stdCmd(args[0].(*ast.String).Lit, cmdArgs...)
		return cmd.Run()
	},
}

var execenvCommand = typed.Command{
	Params: []types.Type{types.StringList, types.String, types.StringList},
	Fn: func(args []ast.Expr, _ *sqlx.DB) error {
		cmdArgs, err := toSlice(args[2].(ast.List))
		if err != nil {
			return errors.Wrap(err, "execenv")
		}
		cmd := stdCmd(args[1].(*ast.String).Lit, cmdArgs...)
		env, err := toSlice(args[0].(ast.List))
		if err != nil {
			return errors.Wrap(err, "execenv")
		}
		for _, e := range env {
			if !strings.Contains(e, "=") {
				return errors.New(`execenv: each item of the first argument must be the form "key=value"`)
			}
		}
		cmd.Env = append(env, os.Environ()...)
		return cmd.Run()
	},
}

var cdCommand = typed.Command{
	Params: []types.Type{types.String},
	Fn: func(args []ast.Expr, _ *sqlx.DB) error {
		return os.Chdir(args[0].(*ast.String).Lit)
	},
}

var exitCommand = typed.Command{
	Params: []types.Type{types.Int},
	Fn: func(args []ast.Expr, _ *sqlx.DB) error {
		n, err := strconv.Atoi(args[0].(*ast.Int).Lit)
		if err != nil {
			return err
		}
		os.Exit(n)
		return nil
	},
}

var freeCommand = typed.Command{
	Params: []types.Type{types.String, types.StringList},
	Fn: func(args []ast.Expr, _ *sqlx.DB) error {
		cmdArgs, err := toSlice(args[1].(ast.List))
		if err != nil {
			return errors.Wrap(err, "free")
		}
		name := args[0].(*ast.String).Lit
		cmd := exec.Cmd{Path: name, Args: append([]string{name}, cmdArgs...)}
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		cmd.Stdin = os.Stdin
		return cmd.Run()
	},
}

func commandsInCommand(name string) func([]ast.Expr, *sqlx.DB) error {
	return func(args []ast.Expr, _ *sqlx.DB) error {
		cmdArgs, err := toSlice(args[1].(ast.List))
		if err != nil {
			return errors.Wrap(err, name)
		}
		var cmd *exec.Cmd
		switch lit := args[0].(*ast.Ident).Lit; lit {
		case "command":
			cmd = stdCmd(name, cmdArgs...)
		default:
			cmd = stdCmd(name, append([]string{lit}, cmdArgs...)...)
		}
		return cmd.Run()
	}
}

func goCommand1() func([]ast.Expr, *sqlx.DB) error {
	return func(args []ast.Expr, _ *sqlx.DB) error {
		name := "go"
		cmdArgs, err := toSlice(args[1].(ast.List))
		if err != nil {
			return errors.Wrap(err, name)
		}
		var cmd *exec.Cmd
		switch lit := args[0].(*ast.Ident).Lit; lit {
		case "command":
			cmd = stdCmd(name, cmdArgs...)
		case "testall":
			// I can't be confident in using such
			// a subcommand-specific way.  Another suggestion might
			// be like `go test all`, where 'all' is a postfix
			// operator of './...'.
			cmd = stdCmd(name, append([]string{"test"}, append(cmdArgs, "./...")...)...)
		default:
			cmd = stdCmd(name, append([]string{lit}, cmdArgs...)...)
		}
		return cmd.Run()
	}
}

var gitCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("git"),
}

var cargoCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("cargo"),
}

var goCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     goCommand1(),
}

var stackCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("stack"),
}

var leinCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("lein"),
}

func stdCmd(name string, args ...string) *exec.Cmd {
	cmd := exec.Command(name, args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin
	return cmd
}

func stdExec(name string, args ...string) func([]ast.Expr, *sqlx.DB) error {
	return func(_ []ast.Expr, _ *sqlx.DB) error {
		return stdCmd(name, args...).Run()
	}
}

var vimCommand = typed.Command{
	Params: []types.Type{},
	Fn:     stdExec("vim"),
}

var emacsCommand = typed.Command{
	Params: []types.Type{},
	Fn:     stdExec("emacs"),
}

func withEnv(s string, cmd *exec.Cmd) *exec.Cmd {
	if cmd.Env == nil {
		cmd.Env = os.Environ()
	}
	cmd.Env = append(cmd.Env, s)
	return cmd
}

var screenCommand = typed.Command{
	Params: []types.Type{},
	Fn: func(_ []ast.Expr, _ *sqlx.DB) error {
		return withEnv("LANG=en_US.UTF-8", stdCmd("screen")).Run()
	},
}

type execution struct {
	Time time.Time
	Line string
}

var historyCommand = typed.Command{
	Params: []types.Type{types.String},
	Fn: func(e []ast.Expr, db *sqlx.DB) error {
		var jsonFormat bool
		var enc *json.Encoder
		switch format := e[0].(*ast.String).Lit; format {
		case "json":
			jsonFormat = true
		case "lines":
		default:
			return fmt.Errorf("history: format %q is not supported", format)
		}

		buf := bufio.NewWriter(os.Stdout)
		if jsonFormat {
			enc = json.NewEncoder(buf)
		}
		rows, err := db.Queryx("select * from command_info")
		if err != nil {
			return err
		}
		data := execution{}
		for rows.Next() {
			err := rows.StructScan(&data)
			if err != nil {
				return err
			}
			if jsonFormat {
				err := enc.Encode(data)
				if err != nil {
					return err
				}
			} else {
				buf.WriteString(data.Time.Format("Mon, 02 Jan 2006 15:04:05"))
				buf.Write([]byte("  "))
				buf.WriteString(data.Line)
				buf.WriteByte('\n')
			}
		}
		return buf.Flush()
	},
}

var lsCommand = typed.Command{
	Params: []types.Type{},
	Fn:     stdExec("ls", "--show-control-chars", "--color=auto"),
}

var manCommand = typed.Command{
	Params: []types.Type{types.String},
	Fn: func(e []ast.Expr, _ *sqlx.DB) error {
		lit := e[0].(*ast.String).Lit
		return stdCmd("man", lit).Run()
	},
}

var removeCommand = typed.Command{
	Params: []types.Type{types.String},
	Fn: func(e []ast.Expr, _ *sqlx.DB) error {
		lit := e[0].(*ast.String).Lit
		return stdCmd("rm", "-i", lit).Run()
	},
}

var cnpCommand = typed.Command{
	Params: []types.Type{types.String},
	Fn: func(e []ast.Expr, _ *sqlx.DB) error {
		lit := e[0].(*ast.String).Lit
		return stdCmd("create-new-project", lit).Run()
	},
}

var gvmnCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("gvmn"),
}

var vvmnCommand = typed.Command{
	Params: []types.Type{types.Ident, types.StringList},
	Fn:     commandsInCommand("vvmn"),
}

var makeCommand = typed.Command{
	Params: []types.Type{types.StringList},
	Fn: func(e []ast.Expr, _ *sqlx.DB) error {
		args, err := toSlice(e[0].(ast.List))
		if err != nil {
			return errors.Wrap(err, "make")
		}
		return stdCmd("make", args...).Run()
	},
}
<|endoftext|>


<commit_before>package main

import (
	"io"
	"net/http"
	"path"
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"
)

type DirEntry struct {
	pth      string
	fi       os.FileInfo
	checksum string
}

type DirEntries []DirEntry

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

type AppConfig struct {
	Name        string
	InputRoot   string
	OutputDir   string
	BuildCmd    string
	ArchiveLocal string
	ArchiveRemote string
	Include		[]string
	Exclude		[]string
}

func countFullChecksum(ents *DirEntries) {
	for i, v := range *ents {
		st, err := os.Stat(v.pth)
		if err != nil {
			fmt.Printf("SKIP bad file [%s]\n", v.pth)
			continue
		}
		v.fi = st

		if v.fi.IsDir() {
			continue
		}

		if v.fi.Size() == 0 {
			v.checksum = "0000"
			continue
		}

		dat, err := ioutil.ReadFile(v.pth)
		if err != nil {
			panic(err)
		}
		var csum = md5.Sum(dat)
		v.checksum = hex.EncodeToString(csum[:])
		//fmt.Printf("%s\n %x", v.checksum, csum)
		(*ents)[i] = v
	}
}

func shouldIgnore(config *AppConfig, pth string) bool {
	for _,v := range config.Exclude {
		if strings.HasPrefix(pth, v) {
			return true
		}
	}

	if len(config.Include) == 0 {
		return false
	}

	for _, v := range config.Include {
		if strings.HasPrefix(pth, v) {
			return false
		}
	}
	return true
}

func collectWithGit(config *AppConfig) DirEntries{
	cmd := exec.Command("git", "ls-files")

	cmd.Dir = config.InputRoot
	out, err := cmd.Output()
	if (err != nil) {
		panic(err)
	}
	asStr := string(out)
	lines := strings.Split(asStr, "\n")
	var all DirEntries

	for _, v := range lines {
		if shouldIgnore(config, v) {
			continue
		}
		all = append(all, DirEntry { pth: path.Join(config.InputRoot ,v)})
	}
	return all
}

func normalizePaths(ents *DirEntries, rootPath string) {
	for i, v := range *ents {
		oldpath := v.pth
		newpath := strings.Replace(strings.TrimPrefix(oldpath, rootPath+"/"), "\\", "/", -1)
		v.pth = newpath
		(*ents)[i] = v
	}
}

func collectByConfig(config *AppConfig) DirEntries {
	return collectWithGit(config)
}

func getCheckSumForFiles(config *AppConfig) (DirEntries, string) {
	all := collectByConfig(config)
	sort.Sort(all)
	countFullChecksum(&all)
	normalizePaths(&all, config.InputRoot)
	var manifest bytes.Buffer
	for _, v := range all {
		manifest.WriteString(v.pth)
		manifest.WriteString(v.checksum)
		//fmt.Printf("%s %s\n", v.pth, v.checksum)
	}
	manifestSum := md5.Sum(manifest.Bytes())
	return all, hex.EncodeToString(manifestSum[:])
}

func zipOutput(path string, zipfile string) {
	cmd := exec.Command("7za", "a", zipfile, path+"/*")
	err := cmd.Run()
	if err != nil {
		panic(err)
	}
}

func unzipOutput(path string, zipfile string) {
	// we will replace the old path completely
	os.RemoveAll(path)
	out, err := exec.Command("7za", "x", zipfile, "-o"+path).CombinedOutput()
	if err != nil {
		fmt.Printf("%s", string(out))
		panic(err)
	}
}

func runBuildCommand(config *AppConfig) {
	fmt.Printf("Running build command '%s' in %s\n", config.BuildCmd, config.InputRoot)
	parts := strings.Fields(config.BuildCmd)
	cmd := exec.Command(parts[0], parts[1:]...)
	cmd.Dir = config.InputRoot
	out, err := cmd.CombinedOutput()
	fmt.Println(string(out))
	if err != nil {
		fmt.Printf("Build failed with error!")
		panic(err)
	}
}

func fetchTo(url string, to string) bool {
	fmt.Printf("GET %s\n", url)
	resp, err := http.Get(url)
	if err != nil || resp.StatusCode != 200 {
		fmt.Printf("Not available: %s\n", url)
		return false
	}
	defer resp.Body.Close()
	out, err := os.Create(to)
	if err != nil {
		panic(err)
	}
	defer out.Close()
	_, err  = io.Copy(out, resp.Body)
	if err != nil {
		panic(err)
	}
	return true
}

func discoverArchive(config *AppConfig, checksum string) (string,bool) {
	archiveRoot := config.ArchiveLocal
	zipName := config.Name + "_" + checksum + ".zip"

	// 1. just try local
	localZipName := filepath.Join(archiveRoot, zipName)
	_, err := os.Stat(localZipName)
	if err == nil {
		return localZipName, true
	}

	// 2. try remote if applicable

	remoteArchive := config.ArchiveRemote

	if remoteArchive == "" {
		return localZipName, false
	}
	if strings.Index(remoteArchive, "[ZIP]") == -1 {
		fmt.Printf("Error: remote archive template %s does not contain [ZIP]\n", remoteArchive)
		return "", false
	}
	remoteUrl := strings.Replace(remoteArchive, "[ZIP]", zipName, -1)
	fetched := fetchTo(remoteUrl, localZipName)
	if !fetched {
		return localZipName, false
	}
	return localZipName, true
}

func buildWithConfig(config *AppConfig) {
	// check input checksum
	fmt.Printf("Config %s\n", config)
	archiveRoot := config.ArchiveLocal

	if archiveRoot == "" {
		fmt.Println("HASHIBUILD_ARCHIVE not set, building without artifact caching")
		runBuildCommand(config)
		return
	}

	_, inputChecksum := getCheckSumForFiles(config)
	// if finding archive found, unzip it and we are ready
	ensureDir(archiveRoot)

	zipName, found := discoverArchive(config, inputChecksum)

	if found {
		fmt.Printf("Unzip %s to %s\n", zipName, config.OutputDir)
		unzipOutput(config.OutputDir, zipName)
		return
	}

	// run build if mismatch

	runBuildCommand(config)

	// zip the results
	fmt.Printf("Zipping %s to %s\n", config.OutputDir, zipName)
	zipOutput(config.OutputDir, zipName)
}

func checkDir(pth string) {
	if _, err := os.Stat(pth); os.IsNotExist(err) {
		fmt.Printf("Path does not exist: %s", pth)
		panic(err)
	}
}

func ensureDir(pth string) {
	if _, err := os.Stat(pth); os.IsNotExist(err) {
		fmt.Printf("Creating dir: %s\n", pth)
		os.MkdirAll(pth, 0777)
	}
}

func parseConfig(configPath string) AppConfig {
	cont, err := ioutil.ReadFile(configPath)
	if err != nil {
		panic(err)
	}
	config := AppConfig{}
	err = json.Unmarshal(cont, &config)
	if (err != nil) {
		panic(err)
	}
	// fixup paths to be relative to config file
	configDir, _ := filepath.Abs(filepath.Dir(configPath))
	config.InputRoot = filepath.Join(configDir, config.InputRoot)
	config.OutputDir = filepath.Join(configDir, config.OutputDir)

	checkDir(config.InputRoot)

	return config
}

func dumpManifest(config *AppConfig) {

	all, csum := getCheckSumForFiles(config)
	for _, v := range all {
		fmt.Printf("%s %s\n", v.pth, v.checksum)
	}
	fmt.Printf("Total: %s\n", csum)
}

func main() {
	manifest := flag.Bool("manifest", false, "Show manifest (requires --config)")
	treeHash := flag.String("treehash", "", "Show manifest for specified path (no config needed)")
	toParse := flag.String("config", "", "Json config file")
	startBuild := flag.Bool("build", false, "Run build")
	archiveDir := flag.String("archive", "", "Archive root dir (needed if HASHIBUILD_ARCHIVE env var is not set)")
	fetch := flag.String("fetch", "", "Fetch remote archive file to local archive")
	if len(os.Args) < 2 {
		flag.Usage()
		return
	}

	flag.Parse()

	var config AppConfig
	if (*toParse) != "" {
		config = parseConfig(*toParse)
		if *archiveDir != "" {
			config.ArchiveLocal = *archiveDir
		}
		if config.ArchiveLocal == "" {
			config.ArchiveLocal = os.Getenv("HASHIBUILD_ARCHIVE")
		}
		if config.ArchiveRemote == "" {
			config.ArchiveRemote = os.Getenv("HASHIBUILD_ARCHIVE_REMOTE")
		}
	}

	if *fetch != "" {
		_, inputChecksum := getCheckSumForFiles(&config)
		// if finding archive found, unzip it and we are ready
		zipName, _ := discoverArchive(&config, inputChecksum)
		fmt.Printf("%s", zipName)
	}

	if *manifest {
		dumpManifest(&config)
	}

	if *startBuild {
		buildWithConfig(&config)
	}

	if len(*treeHash) > 0 {
		pth, _ := filepath.Abs(*treeHash)

		config := AppConfig{InputRoot: pth }
		dumpManifest(&config)
	}

}
<commit_msg>cleanup<commit_after>package main

import (
	"io"
	"net/http"
	"path"
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"
)

type DirEntry struct {
	pth      string
	fi       os.FileInfo
	checksum string
}

type DirEntries []DirEntry

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

type AppConfig struct {
	Name        string
	InputRoot   string
	OutputDir   string
	BuildCmd    string
	ArchiveLocal string
	ArchiveRemote string
	Include		[]string
	Exclude		[]string
}

func countFullChecksum(ents *DirEntries) {
	for i, v := range *ents {
		st, err := os.Stat(v.pth)
		if err != nil {
			fmt.Printf("SKIP bad file [%s]\n", v.pth)
			continue
		}
		v.fi = st

		if v.fi.IsDir() {
			continue
		}

		if v.fi.Size() == 0 {
			v.checksum = "0000"
			continue
		}

		dat, err := ioutil.ReadFile(v.pth)
		if err != nil {
			panic(err)
		}
		var csum = md5.Sum(dat)
		v.checksum = hex.EncodeToString(csum[:])
		//fmt.Printf("%s\n %x", v.checksum, csum)
		(*ents)[i] = v
	}
}

func shouldIgnore(config *AppConfig, pth string) bool {
	for _,v := range config.Exclude {
		if strings.HasPrefix(pth, v) {
			return true
		}
	}

	if len(config.Include) == 0 {
		return false
	}

	for _, v := range config.Include {
		if strings.HasPrefix(pth, v) {
			return false
		}
	}
	return true
}

func collectWithGit(config *AppConfig) DirEntries{
	cmd := exec.Command("git", "ls-files")

	cmd.Dir = config.InputRoot
	out, err := cmd.Output()
	if (err != nil) {
		panic(err)
	}
	asStr := string(out)
	lines := strings.Split(asStr, "\n")
	var all DirEntries

	for _, v := range lines {
		if shouldIgnore(config, v) {
			continue
		}
		all = append(all, DirEntry { pth: path.Join(config.InputRoot ,v)})
	}
	return all
}

func normalizePaths(ents *DirEntries, rootPath string) {
	for i, v := range *ents {
		oldpath := v.pth
		newpath := strings.Replace(strings.TrimPrefix(oldpath, rootPath+"/"), "\\", "/", -1)
		v.pth = newpath
		(*ents)[i] = v
	}
}

func collectByConfig(config *AppConfig) DirEntries {
	return collectWithGit(config)
}

func getCheckSumForFiles(config *AppConfig) (DirEntries, string) {
	all := collectByConfig(config)
	sort.Sort(all)
	countFullChecksum(&all)
	normalizePaths(&all, config.InputRoot)
	var manifest bytes.Buffer
	for _, v := range all {
		manifest.WriteString(v.pth)
		manifest.WriteString(v.checksum)
		//fmt.Printf("%s %s\n", v.pth, v.checksum)
	}
	manifestSum := md5.Sum(manifest.Bytes())
	return all, hex.EncodeToString(manifestSum[:])
}

func run(bin string, arg ...string) {
	fmt.Printf("> %s %s", bin, arg)

	cmd := exec.Command(bin, arg...)
	out, err := cmd.CombinedOutput()
	fmt.Printf("%s", string(out))
	if err != nil {
		panic(err)
	}
}
func zipOutput(path string, zipfile string) {
	run("zip", "-r", zipfile, path+"/*")
}

func unzipOutput(path string, zipfile string) {
	// we will replace the old path completely
	os.RemoveAll(path)
	run("unzip", zipfile, "-d"+path)
}

func runBuildCommand(config *AppConfig) {
	fmt.Printf("Running build command '%s' in %s\n", config.BuildCmd, config.InputRoot)
	parts := strings.Fields(config.BuildCmd)
	cmd := exec.Command(parts[0], parts[1:]...)
	cmd.Dir = config.InputRoot
	out, err := cmd.CombinedOutput()
	fmt.Println(string(out))
	if err != nil {
		fmt.Printf("Build failed with error!")
		panic(err)
	}
}

func fetchTo(url string, to string) bool {
	fmt.Printf("GET %s\n", url)
	resp, err := http.Get(url)
	if err != nil || resp.StatusCode != 200 {
		fmt.Printf("Not available: %s\n", url)
		return false
	}
	defer resp.Body.Close()
	out, err := os.Create(to)
	if err != nil {
		panic(err)
	}
	defer out.Close()
	_, err  = io.Copy(out, resp.Body)
	if err != nil {
		panic(err)
	}
	return true
}

func discoverArchive(config *AppConfig, checksum string) (string,bool) {
	archiveRoot := config.ArchiveLocal
	zipName := config.Name + "_" + checksum + ".zip"

	// 1. just try local
	localZipName := filepath.Join(archiveRoot, zipName)
	_, err := os.Stat(localZipName)
	if err == nil {
		return localZipName, true
	}

	// 2. try remote if applicable

	remoteArchive := config.ArchiveRemote

	if remoteArchive == "" {
		return localZipName, false
	}
	if strings.Index(remoteArchive, "[ZIP]") == -1 {
		fmt.Printf("Error: remote archive template %s does not contain [ZIP]\n", remoteArchive)
		return "", false
	}
	remoteUrl := strings.Replace(remoteArchive, "[ZIP]", zipName, -1)
	fetched := fetchTo(remoteUrl, localZipName)
	if !fetched {
		return localZipName, false
	}
	return localZipName, true
}

func buildWithConfig(config *AppConfig) {
	// check input checksum
	fmt.Printf("Config %s\n", config)
	archiveRoot := config.ArchiveLocal

	if archiveRoot == "" {
		fmt.Println("HASHIBUILD_ARCHIVE not set, building without artifact caching")
		runBuildCommand(config)
		return
	}

	_, inputChecksum := getCheckSumForFiles(config)
	// if finding archive found, unzip it and we are ready
	ensureDir(archiveRoot)

	zipName, found := discoverArchive(config, inputChecksum)

	if found {
		fmt.Printf("Unzip %s to %s\n", zipName, config.OutputDir)
		unzipOutput(config.OutputDir, zipName)
		return
	}

	// run build if mismatch

	runBuildCommand(config)

	// zip the results
	fmt.Printf("Zipping %s to %s\n", config.OutputDir, zipName)
	zipOutput(config.OutputDir, zipName)
}

func checkDir(pth string) {
	if _, err := os.Stat(pth); os.IsNotExist(err) {
		fmt.Printf("Path does not exist: %s", pth)
		panic(err)
	}
}

func ensureDir(pth string) {
	if _, err := os.Stat(pth); os.IsNotExist(err) {
		fmt.Printf("Creating dir: %s\n", pth)
		os.MkdirAll(pth, 0777)
	}
}

func parseConfig(configPath string) AppConfig {
	cont, err := ioutil.ReadFile(configPath)
	if err != nil {
		panic(err)
	}
	config := AppConfig{}
	err = json.Unmarshal(cont, &config)
	if (err != nil) {
		panic(err)
	}
	// fixup paths to be relative to config file
	configDir, _ := filepath.Abs(filepath.Dir(configPath))
	config.InputRoot = filepath.Join(configDir, config.InputRoot)
	config.OutputDir = filepath.Join(configDir, config.OutputDir)

	checkDir(config.InputRoot)

	return config
}

func dumpManifest(config *AppConfig) {

	all, csum := getCheckSumForFiles(config)
	for _, v := range all {
		fmt.Printf("%s %s\n", v.pth, v.checksum)
	}
	fmt.Printf("Total: %s\n", csum)
}

func main() {
	manifest := flag.Bool("manifest", false, "Show manifest (requires --config)")
	treeHash := flag.String("treehash", "", "Show manifest for specified path (no config needed)")
	toParse := flag.String("config", "", "Json config file")
	startBuild := flag.Bool("build", false, "Run build")
	archiveDir := flag.String("archive", "", "Archive root dir (needed if HASHIBUILD_ARCHIVE env var is not set)")
	fetch := flag.String("fetch", "", "Fetch remote archive file to local archive")
	if len(os.Args) < 2 {
		flag.Usage()
		return
	}

	flag.Parse()

	var config AppConfig
	if (*toParse) != "" {
		config = parseConfig(*toParse)
		if *archiveDir != "" {
			config.ArchiveLocal = *archiveDir
		}
		if config.ArchiveLocal == "" {
			config.ArchiveLocal = os.Getenv("HASHIBUILD_ARCHIVE")
		}
		if config.ArchiveRemote == "" {
			config.ArchiveRemote = os.Getenv("HASHIBUILD_ARCHIVE_REMOTE")
		}
	}

	if *fetch != "" {
		_, inputChecksum := getCheckSumForFiles(&config)
		// if finding archive found, unzip it and we are ready
		zipName, _ := discoverArchive(&config, inputChecksum)
		fmt.Printf("%s", zipName)
	}

	if *manifest {
		dumpManifest(&config)
	}

	if *startBuild {
		buildWithConfig(&config)
	}

	if len(*treeHash) > 0 {
		pth, _ := filepath.Abs(*treeHash)

		config := AppConfig{InputRoot: pth }
		dumpManifest(&config)
	}

}
<|endoftext|>


<commit_before>package main

// This module implements a gateway that bridges between IP based JSON events and the
// ASCII line delimited control messages intended for arduinos under the control of
// the gateway
//
import (
	"fmt"
	"strconv"
)

var (
	// Record the last known state of a portal in order that transitions can be discovered
	// and a diff can be sent to the arduino so that it does not have to track changes
	// in alignment etc
	//
	lastState = map[string]*portalStatus{}
)

func startGateway(homePortal string, tectC chan *portalStatus, quitC chan bool) {

	for {

		select {
		case state := <-tectC:
			// If there is not history add the fresh state as the previous state
			//
			if _, ok := lastState[state.Status.Title]; !ok {
				lastState[state.Status.Title] = state
			}

			factionChange := lastState[state.Status.Title].Status.ControllingFaction != state.Status.ControllingFaction

			// Process the state updates into arduino CMDs and then send these to
			// the arduinos that are listening and our associated with the home portal
			// in any functional capacity
			if homePortal == state.Status.Title {
				cmd := []byte{}
				switch state.Status.ControllingFaction {
				case "0":
					if factionChange {
						cmd = append(cmd, 'N')
					} else {
						cmd = append(cmd, 'n')
					}
				case "1":
					if factionChange {
						cmd = append(cmd, 'E')
					} else {
						cmd = append(cmd, 'e')
					}
				case "2":
					if factionChange {
						cmd = append(cmd, 'R')
					} else {
						cmd = append(cmd, 'r')
					}
				}

				// Now dump out resonator levels, one character for each, and record the health values
				resCmd := []byte{'0', '0', '0', '0', '0', '0', '0', '0'}
				resHealth := []string{"0", "0", "0", "0", "0", "0", "0", "0"}
				// Translate an ascii compass point to a position in the resonators array
				resPositionMap := map[string]int{"N": 2, "NE": 1, "E": 0, "SE": 7, "S": 6, "SW": 5, "W": 4, "NW": 3}

				for _, res := range state.Status.Resonators {
					if position, ok := resPositionMap[res.Position]; ok {
						// After we have the position set the character in the resCmd for that
						// position to the single ASCII digit the represents the level of the
						// resonator
						resCmd[position] = strconv.Itoa(res.Level)[0]
						resHealth[position] = strconv.Itoa(res.Health)
					}
				}

				cmd = append(cmd, resCmd...)
				cmd = append(cmd, ':')
				cmd = append(cmd, []byte(strconv.FormatInt(int64(state.Status.Health), 10))...)
				for _, health := range resHealth {
					cmd = append(cmd, ':')
					cmd = append(cmd, []byte(health)...)
				}

				cmd = append(cmd, ':')
				// Mods array handling
				mods := []byte{' ', ' ', ' ', ' '}
				modsMap := map[string]byte{"FA": '0', "HS-C": '1',
					"HS-R": '2', "HS-VR": '3', "LA-R ": '4', "LA-VR": '5',
					"SBUL": '6', "MH-C": '7', "MH-R": '8', "MH-VR": '9',
					"PS-C": 'A', "PS-R": 'B', "PS-VR": 'C', "AXA": 'D',
					"T": 'E'}
				for i, mod := range state.Status.Mods {
					if code, ok := modsMap[mod]; ok {
						mods[i] = code
					}
				}
				cmd = append(cmd, mods...)
				cmd = append(cmd, ':')

				// After printing the overall health output the per resonator health wih delimiters
				cmd = append(cmd, '\n')

				devices := getRunningDevices(homePortal)
				logW.Trace(fmt.Sprintf("sending data to %d devices", len(devices)))

				for _, device := range devices {
					func() {

						defer func() {
							if nil != recover() {
								stopRunningDevice(homePortal, device.devName)
							}
						}()

						if err := device.sendCmd(cmd); err != nil {
							logW.Warn(fmt.Sprintf("%q ➡  device %s role '%s' got an error %s, taking device offline", cmd, device.devName, device.role, err.Error()))
							stopRunningDevice(homePortal, device.devName)
							return
						}
						logW.Info(fmt.Sprintf("%q ➡ %40.40s\t%s", cmd, device.role, device.devName))
					}()
				}
			}

			// Save the new state as the last known state
			lastState[state.Status.Title] = state

		case <-quitC:
			return
		}
	}
}
<commit_msg>Change device logging to a single line for all devices present, reduce noise<commit_after>package main

// This module implements a gateway that bridges between IP based JSON events and the
// ASCII line delimited control messages intended for arduinos under the control of
// the gateway
//
import (
	"fmt"
	"strconv"
)

var (
	// Record the last known state of a portal in order that transitions can be discovered
	// and a diff can be sent to the arduino so that it does not have to track changes
	// in alignment etc
	//
	lastState = map[string]*portalStatus{}
)

func startGateway(homePortal string, tectC chan *portalStatus, quitC chan bool) {

	for {

		select {
		case state := <-tectC:
			// If there is not history add the fresh state as the previous state
			//
			if _, ok := lastState[state.Status.Title]; !ok {
				lastState[state.Status.Title] = state
			}

			factionChange := lastState[state.Status.Title].Status.ControllingFaction != state.Status.ControllingFaction

			// Process the state updates into arduino CMDs and then send these to
			// the arduinos that are listening and our associated with the home portal
			// in any functional capacity
			if homePortal == state.Status.Title {
				cmd := []byte{}
				switch state.Status.ControllingFaction {
				case "0":
					if factionChange {
						cmd = append(cmd, 'N')
					} else {
						cmd = append(cmd, 'n')
					}
				case "1":
					if factionChange {
						cmd = append(cmd, 'E')
					} else {
						cmd = append(cmd, 'e')
					}
				case "2":
					if factionChange {
						cmd = append(cmd, 'R')
					} else {
						cmd = append(cmd, 'r')
					}
				}

				// Now dump out resonator levels, one character for each, and record the health values
				resCmd := []byte{'0', '0', '0', '0', '0', '0', '0', '0'}
				resHealth := []string{"0", "0", "0", "0", "0", "0", "0", "0"}
				// Translate an ascii compass point to a position in the resonators array
				resPositionMap := map[string]int{"N": 2, "NE": 1, "E": 0, "SE": 7, "S": 6, "SW": 5, "W": 4, "NW": 3}

				for _, res := range state.Status.Resonators {
					if position, ok := resPositionMap[res.Position]; ok {
						// After we have the position set the character in the resCmd for that
						// position to the single ASCII digit the represents the level of the
						// resonator
						resCmd[position] = strconv.Itoa(res.Level)[0]
						resHealth[position] = strconv.Itoa(res.Health)
					}
				}

				cmd = append(cmd, resCmd...)
				cmd = append(cmd, ':')
				cmd = append(cmd, []byte(strconv.FormatInt(int64(state.Status.Health), 10))...)
				for _, health := range resHealth {
					cmd = append(cmd, ':')
					cmd = append(cmd, []byte(health)...)
				}

				cmd = append(cmd, ':')
				// Mods array handling
				mods := []byte{' ', ' ', ' ', ' '}
				modsMap := map[string]byte{"FA": '0', "HS-C": '1',
					"HS-R": '2', "HS-VR": '3', "LA-R ": '4', "LA-VR": '5',
					"SBUL": '6', "MH-C": '7', "MH-R": '8', "MH-VR": '9',
					"PS-C": 'A', "PS-R": 'B', "PS-VR": 'C', "AXA": 'D',
					"T": 'E'}
				for i, mod := range state.Status.Mods {
					if code, ok := modsMap[mod]; ok {
						mods[i] = code
					}
				}
				cmd = append(cmd, mods...)
				cmd = append(cmd, ':')

				// After printing the overall health output the per resonator health wih delimiters
				cmd = append(cmd, '\n')

				devices := getRunningDevices(homePortal)
				logW.Trace(fmt.Sprintf("sending data to %d devices", len(devices)))

				devicesSent := []string{}

				for _, device := range devices {
					func() {

						defer func() {
							if nil != recover() {
								stopRunningDevice(homePortal, device.devName)
							}
						}()

						if err := device.sendCmd(cmd); err != nil {
							logW.Warn(fmt.Sprintf("%q ➡  device %s role '%s' got an error %s, taking device offline", cmd, device.devName, device.role, err.Error()))
							stopRunningDevice(homePortal, device.devName)
							return
						}
						devicesSent = append(devicesSent, device.devName)
					}()
				}
				logW.Info(fmt.Sprintf("%q ➡ \t%v", cmd, devicesSent))
			}

			// Save the new state as the last known state
			lastState[state.Status.Title] = state

		case <-quitC:
			return
		}
	}
}
<|endoftext|>