<commit_before>// Package timestampexpanded implements the ruleset for making sure a variable
// that likely represents a timestamp is simply expanded so it doesn't change
// in between rule executions.
package timestampexpanded

import (
	"fmt"
	"strings"

	"github.com/mrtazz/checkmake/parser"
	"github.com/mrtazz/checkmake/rules"
)

func init() {
	rules.RegisterRule(&Timestampexpanded{})
}

// Timestampexpanded is an empty struct on which to call the rule functions
type Timestampexpanded struct {
}

var (
	vT = "Variable %q possibly contains and timestamp and should be simply exapnded."
)

// Name returns the name of the rule
func (r *Timestampexpanded) Name() string {
	return "timestampexpanded"
}

// Description returns the description of the rule
func (r *Timestampexpanded) Description() string {
	return "timestamp variables should be simply expanded"
}

// Run executes the rule logic
func (r *Timestampexpanded) Run(makefile parser.Makefile, config rules.RuleConfig) rules.RuleViolationList {
	ret := rules.RuleViolationList{}

	for _, variable := range makefile.Variables {
		if strings.Contains(variable.Assignment, "date") &&
			!variable.SimplyExpanded {
			ret = append(ret, rules.RuleViolation{
				Rule:       "timestampexpanded",
				Violation:  fmt.Sprintf(vT, variable.Name),
				LineNumber: variable.LineNumber,
			})
		}
	}

	return ret
}
<commit_msg>improve description of timestampexpanded package<commit_after>// Package timestampexpanded implements the ruleset for making sure a variable
// that likely represents a timestamp is simply expanded so it doesn't change
// in between rule executions. Ideally if you want to follow something like
// https://reproducible-builds.org/ timestamps in artefacts are frowned upon,
// however sometimes they are the best tool you have and at least they should
// be consistent across all build artefacts.
package timestampexpanded

import (
	"fmt"
	"strings"

	"github.com/mrtazz/checkmake/parser"
	"github.com/mrtazz/checkmake/rules"
)

func init() {
	rules.RegisterRule(&Timestampexpanded{})
}

// Timestampexpanded is an empty struct on which to call the rule functions
type Timestampexpanded struct {
}

var (
	vT = "Variable %q possibly contains and timestamp and should be simply exapnded."
)

// Name returns the name of the rule
func (r *Timestampexpanded) Name() string {
	return "timestampexpanded"
}

// Description returns the description of the rule
func (r *Timestampexpanded) Description() string {
	return "timestamp variables should be simply expanded"
}

// Run executes the rule logic
func (r *Timestampexpanded) Run(makefile parser.Makefile, config rules.RuleConfig) rules.RuleViolationList {
	ret := rules.RuleViolationList{}

	for _, variable := range makefile.Variables {
		if strings.Contains(variable.Assignment, "date") &&
			!variable.SimplyExpanded {
			ret = append(ret, rules.RuleViolation{
				Rule:       "timestampexpanded",
				Violation:  fmt.Sprintf(vT, variable.Name),
				LineNumber: variable.LineNumber,
			})
		}
	}

	return ret
}
<|endoftext|>


<commit_before>package remote

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"

	"github.com/hashicorp/terraform/helper/pathorcontents"
	"github.com/hashicorp/terraform/httpclient"
	"golang.org/x/net/context"
	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"golang.org/x/oauth2/jwt"
	"google.golang.org/api/googleapi"
	"google.golang.org/api/storage/v1"
)

// accountFile represents the structure of the credentials JSON
type accountFile struct {
	PrivateKeyId string `json:"private_key_id"`
	PrivateKey   string `json:"private_key"`
	ClientEmail  string `json:"client_email"`
	ClientId     string `json:"client_id"`
}

func parseJSON(result interface{}, contents string) error {
	r := strings.NewReader(contents)
	dec := json.NewDecoder(r)

	return dec.Decode(result)
}

type GCSClient struct {
	bucket        string
	path          string
	clientStorage *storage.Service
	context       context.Context
}

func gcsFactory(conf map[string]string) (Client, error) {
	var account accountFile
	var client *http.Client
	clientScopes := []string{
		"https://www.googleapis.com/auth/devstorage.full_control",
	}

	bucketName, ok := conf["bucket"]
	if !ok {
		return nil, fmt.Errorf("missing 'bucket' configuration")
	}

	pathName, ok := conf["path"]
	if !ok {
		return nil, fmt.Errorf("missing 'path' configuration")
	}

	credentials, ok := conf["credentials"]
	if !ok {
		credentials = os.Getenv("GOOGLE_CREDENTIALS")
	}

	if credentials != "" {
		contents, _, err := pathorcontents.Read(credentials)
		if err != nil {
			return nil, fmt.Errorf("Error loading credentials: %s", err)
		}

		// Assume account_file is a JSON string
		if err := parseJSON(&account, contents); err != nil {
			return nil, fmt.Errorf("Error parsing credentials '%s': %s", contents, err)
		}

		// Get the token for use in our requests
		log.Printf("[INFO] Requesting Google token...")
		log.Printf("[INFO]   -- Email: %s", account.ClientEmail)
		log.Printf("[INFO]   -- Scopes: %s", clientScopes)
		log.Printf("[INFO]   -- Private Key Length: %d", len(account.PrivateKey))

		conf := jwt.Config{
			Email:      account.ClientEmail,
			PrivateKey: []byte(account.PrivateKey),
			Scopes:     clientScopes,
			TokenURL:   "https://accounts.google.com/o/oauth2/token",
		}

		client = conf.Client(oauth2.NoContext)

	} else {
		log.Printf("[INFO] Authenticating using DefaultClient")
		err := error(nil)
		client, err = google.DefaultClient(oauth2.NoContext, clientScopes...)
		if err != nil {
			return nil, err
		}
	}

	log.Printf("[INFO] Instantiating Google Storage Client...")
	clientStorage, err := storage.New(client)
	if err != nil {
		return nil, err
	}
	clientStorage.UserAgent = httpclient.UserAgentString()

	return &GCSClient{
		clientStorage: clientStorage,
		bucket:        bucketName,
		path:          pathName,
	}, nil

}

func (c *GCSClient) Get() (*Payload, error) {
	// Read the object from bucket.
	log.Printf("[INFO] Reading %s/%s", c.bucket, c.path)

	resp, err := c.clientStorage.Objects.Get(c.bucket, c.path).Download()
	if err != nil {
		if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == 404 {
			log.Printf("[INFO] %s/%s not found", c.bucket, c.path)

			return nil, nil
		}

		return nil, fmt.Errorf("[WARN] Error retrieving object %s/%s: %s", c.bucket, c.path, err)
	}
	defer resp.Body.Close()

	var buf []byte
	w := bytes.NewBuffer(buf)
	n, err := io.Copy(w, resp.Body)
	if err != nil {
		log.Fatalf("[WARN] error buffering %q: %v", c.path, err)
	}
	log.Printf("[INFO] Downloaded %d bytes", n)

	payload := &Payload{
		Data: w.Bytes(),
	}

	// If there was no data, then return nil
	if len(payload.Data) == 0 {
		return nil, nil
	}

	return payload, nil
}

func (c *GCSClient) Put(data []byte) error {
	log.Printf("[INFO] Writing %s/%s", c.bucket, c.path)

	r := bytes.NewReader(data)
	_, err := c.clientStorage.Objects.Insert(c.bucket, &storage.Object{Name: c.path}).Media(r).Do()
	if err != nil {
		return err
	}

	return nil
}

func (c *GCSClient) Delete() error {
	log.Printf("[INFO] Deleting %s/%s", c.bucket, c.path)

	err := c.clientStorage.Objects.Delete(c.bucket, c.path).Do()
	return err

}
<commit_msg>remove dead legacy gcs code<commit_after><|endoftext|>


<commit_before>package reudp

import (
	"encoding/binary"
	"errors"
	"strconv"
	"sync"
	"net"
	"fmt"
	"time"

	"github.com/snuk182/go-multierror"
)

const (
	_MARKER = 0xdefcabbe
	_HEADER_SIZE = 24
	_PACKET_SIZE = 576
	_DATA_SIZE   = _PACKET_SIZE - _HEADER_SIZE

	_FLAG_ACK = 1

	MsgData = iota
	MsgAck
)

type Reudp struct {
	readCacheSync,writeCacheSync sync.Mutex

	conn   *net.UDPConn
	writebuffer,readbuffer [_PACKET_SIZE]byte

	writecache,readcache map[uint64][][]byte

	errfunc func(who *net.UDPAddr, err error)
	datafunc func(who *net.UDPAddr, data []byte)
	logfunc func(format string, args ...interface{})
}

func New(datafunc func(who *net.UDPAddr, data []byte), errfunc func(who *net.UDPAddr, err error), logfunc func(format string, args ...interface{})) *Reudp {
	return &Reudp{
		errfunc: errfunc,
		datafunc: datafunc,
		logfunc: logfunc,
	}
}

func (this *Reudp) IsRunning() bool {
	return this.conn != nil
}

func (this *Reudp) Close() error {
	if this.IsRunning() {
		err := this.conn.Close()
		this.conn = nil
		return err
	} else {
		return nil
	}
}

func (this *Reudp) LocalAddr() *net.UDPAddr {
	return this.conn.LocalAddr().(*net.UDPAddr)
}

func (this *Reudp) Listen(addr *net.UDPAddr) (err error) {
	this.conn, err = net.ListenUDP("udp", addr)

	this.readcache,this.writecache = make(map[uint64][][]byte),make(map[uint64][][]byte)

	if err == nil {
		if this.logfunc != nil {
			this.logfunc("reudp: listening to %v", this.LocalAddr())
		}

		go this.loop()
	} else {
		this.conn = nil
	}

	return err
}

func (this *Reudp) loop() {
	for this.IsRunning() {
		got,who,err := this.conn.ReadFromUDP(this.readbuffer[:])

		if this.logfunc != nil {
			this.logfunc("reudp: got %d bytes from %v, err %v", got, who, err)
			this.logfunc("reudp: data %v", this.readbuffer[:got])
		}

		if err != nil {
			this.errfunc(who,err)
		} else {
			this.process(got, who)
		}
	}
}

func (this *Reudp) process(length int, who *net.UDPAddr) {
	if length < 24 {
		this.errfunc(who, errors.New(fmt.Sprintf("Broken packet: %v", this.readbuffer[:length])))
		return
	}

	if binary.BigEndian.Uint32(this.readbuffer[:4]) != _MARKER {
		this.errfunc(who, errors.New(fmt.Sprintf("Not my packet: %v", this.readbuffer[:4])))
		return
	}

	clientId := binary.BigEndian.Uint32(this.readbuffer[4:])
	flags := binary.BigEndian.Uint16(this.readbuffer[8:])
	size := binary.BigEndian.Uint16(this.readbuffer[10:])
	packetId := binary.BigEndian.Uint32(this.readbuffer[12:])
	parts := binary.BigEndian.Uint32(this.readbuffer[16:])
	currentPart := binary.BigEndian.Uint32(this.readbuffer[20:])

	if this.logfunc != nil {
		this.logfunc("reudp: process: clientId %v / flags %v / %v bytes / packetId %v / part %v of %v / data %v", clientId, strconv.FormatUint(uint64(flags), 2), size, packetId, currentPart, parts, this.readbuffer[24:24+size])
	}

	if flags & _FLAG_ACK == 0 {
		this.send(who, clientId, packetId, flags | _FLAG_ACK, 0, parts, currentPart, []byte{})

		if parts < 2 {
			this.datafunc(who, this.readbuffer[24:24+size])
		} else {
			this.readCacheSync.Lock()
			defer this.readCacheSync.Unlock()

			key := makeCacheKey(clientId, packetId)

			cache,ok := this.readcache[key]
			if !ok {
				cache = make([][]byte, parts)
				this.readcache[key] = cache
			}

			cache[currentPart-1] = make([]byte, size)
			copy(cache[currentPart-1], this.readbuffer[24:24+size])

			cursor := 0
			for _,part := range cache {
				if part == nil {
					return
				} else {
					cursor += len(part)
				}
			}

			packet := make([]byte, cursor)
			cursor = 0
			for _,part := range cache {
				copy(packet[cursor:], part)
				cursor += len(part)
			}

			delete(this.readcache, key)

			this.datafunc(who, packet)
		}
	} else {
		this.writeCacheSync.Lock()
		defer this.writeCacheSync.Unlock()

		key := makeCacheKey(clientId, packetId)
		cache,ok := this.writecache[key]
		if ok && cache != nil && len(cache) > int(currentPart-1) {
			cache[currentPart-1] = nil

			for _,b := range cache {
				if b != nil {
					return
				}
			}

			delete(this.writecache, key)
		} else {

		}
	}
}

func (this *Reudp) Send(addr *net.UDPAddr, data []byte) error {
	parts := len(data) / _DATA_SIZE
	if len(data)%_DATA_SIZE > 0 {
		parts += 1
	}

	currentPart := 1
	cursor := 0
	packetId := uint32(time.Now().UnixNano())
	clientId := makeClientId(this.LocalAddr())

	var e *multierror.Error

	for currentPart <= parts {
		size := len(data) - cursor
		if size > _DATA_SIZE {
			size = _DATA_SIZE
		}

		err := this.send(addr, clientId, packetId, 0, uint16(size), uint32(parts), uint32(currentPart), data[cursor:cursor+size])

		multierror.Append(e, err)

		cursor += size
		currentPart++
	}

	return e.ErrorOrNil()
}

func (this *Reudp) send(where *net.UDPAddr, clientId, packetId uint32, flags,size uint16, parts,currentPart uint32, data []byte) error {
	if this.logfunc != nil {
		this.logfunc("reudp: send to %v / clientid %v / packetid %v / flags %v / %v bytes / %v of %v / data %v", where, clientId, packetId, flags, size, currentPart, parts, data)
	}

	binary.BigEndian.PutUint32(this.writebuffer[0:4], _MARKER)
	binary.BigEndian.PutUint32(this.writebuffer[4:], clientId)
	binary.BigEndian.PutUint16(this.writebuffer[8:], flags)
	binary.BigEndian.PutUint16(this.writebuffer[10:], uint16(size))
	binary.BigEndian.PutUint32(this.writebuffer[12:], packetId)
	binary.BigEndian.PutUint32(this.writebuffer[16:], uint32(parts))
	binary.BigEndian.PutUint32(this.writebuffer[20:], uint32(currentPart))

	copy(this.writebuffer[24:], data)

	if flags & _FLAG_ACK == 0 {
		this.writeCacheSync.Lock()

		key := makeCacheKey(clientId, packetId)
		cache,ok := this.writecache[key]
		if !ok {
			cache = make([][]byte, parts)
			this.writecache[key] = cache
		}

		cache[currentPart-1] = make([]byte, _HEADER_SIZE + size)
		copy(cache[currentPart-1], this.writebuffer[:_HEADER_SIZE + size])

		this.writeCacheSync.Unlock()

		go this.waitForAck(where, key, currentPart, 3)
	}

	_, err := this.conn.WriteToUDP(this.writebuffer[:size+_HEADER_SIZE], where)

	return err
}

func (this *Reudp) waitForAck(where *net.UDPAddr, writecacheKey uint64, part uint32, attempts int) {
	<- time.Tick(time.Second * 30)

	cache,ok := this.writecache[writecacheKey]
	if ok && cache != nil && len(cache) > int(part-1) && cache[part-1] != nil {
		this.conn.WriteToUDP(cache[part-1], where)

		if attempts > 0 {
			go this.waitForAck(where, writecacheKey, part, attempts-1)
		}
	}
}

func makeCacheKey(clientid, packetId uint32) uint64 {
	key := uint64(0)
	key += uint64(clientid)
	key = key << 32
	key += uint64(packetId)

	return key
}

func makeClientId(addr *net.UDPAddr) uint32 {
	return binary.BigEndian.Uint32(addr.IP)+uint32(addr.Port)
}
<commit_msg>Lock on inner socket<commit_after>package reudp

import (
	"encoding/binary"
	"errors"
	"fmt"
	"net"
	"strconv"
	"sync"
	"time"

	"github.com/snuk182/go-multierror"
)

const (
	_MARKER      = 0xdefcabbe
	_HEADER_SIZE = 24
	_PACKET_SIZE = 576
	_DATA_SIZE   = _PACKET_SIZE - _HEADER_SIZE

	_FLAG_ACK = 1

	MsgData = iota
	MsgAck
)

type Reudp struct {
	writeSync, readCacheSync, writeCacheSync sync.Mutex

	conn                    *net.UDPConn
	writebuffer, readbuffer [_PACKET_SIZE]byte

	writecache, readcache map[uint64][][]byte

	errfunc  func(who *net.UDPAddr, err error)
	datafunc func(who *net.UDPAddr, data []byte)
	logfunc  func(format string, args ...interface{})
}

func New(datafunc func(who *net.UDPAddr, data []byte), errfunc func(who *net.UDPAddr, err error), logfunc func(format string, args ...interface{})) *Reudp {
	return &Reudp{
		errfunc:  errfunc,
		datafunc: datafunc,
		logfunc:  logfunc,
	}
}

func (this *Reudp) IsRunning() bool {
	return this.conn != nil
}

func (this *Reudp) Close() error {
	if this.IsRunning() {
		err := this.conn.Close()
		this.conn = nil
		return err
	} else {
		return nil
	}
}

func (this *Reudp) LocalAddr() *net.UDPAddr {
	return this.conn.LocalAddr().(*net.UDPAddr)
}

func (this *Reudp) Listen(addr *net.UDPAddr) (err error) {
	this.conn, err = net.ListenUDP("udp", addr)

	this.readcache, this.writecache = make(map[uint64][][]byte), make(map[uint64][][]byte)

	if err == nil {
		if this.logfunc != nil {
			this.logfunc("reudp: listening to %v", this.LocalAddr())
		}

		go this.loop()
	} else {
		this.conn = nil
	}

	return err
}

func (this *Reudp) loop() {
	for this.IsRunning() {
		got, who, err := this.conn.ReadFromUDP(this.readbuffer[:])

		if this.logfunc != nil {
			this.logfunc("reudp: got %d bytes from %v, err %v", got, who, err)
			this.logfunc("reudp: data %v", this.readbuffer[:got])
		}

		if err != nil {
			this.errfunc(who, err)
		} else {
			this.process(got, who)
		}
	}
}

func (this *Reudp) process(length int, who *net.UDPAddr) {
	if length < 24 {
		this.errfunc(who, errors.New(fmt.Sprintf("Broken packet: %v", this.readbuffer[:length])))
		return
	}

	if binary.BigEndian.Uint32(this.readbuffer[:4]) != _MARKER {
		this.errfunc(who, errors.New(fmt.Sprintf("Not my packet: %v", this.readbuffer[:4])))
		return
	}

	clientId := binary.BigEndian.Uint32(this.readbuffer[4:])
	flags := binary.BigEndian.Uint16(this.readbuffer[8:])
	size := binary.BigEndian.Uint16(this.readbuffer[10:])
	packetId := binary.BigEndian.Uint32(this.readbuffer[12:])
	parts := binary.BigEndian.Uint32(this.readbuffer[16:])
	currentPart := binary.BigEndian.Uint32(this.readbuffer[20:])

	if this.logfunc != nil {
		this.logfunc("reudp: process: clientId %v / flags %v / %v bytes / packetId %v / part %v of %v / data %v", clientId, strconv.FormatUint(uint64(flags), 2), size, packetId, currentPart, parts, this.readbuffer[24:24+size])
	}

	if flags&_FLAG_ACK == 0 {
		this.send(who, clientId, packetId, flags|_FLAG_ACK, 0, parts, currentPart, []byte{})

		if parts < 2 {
			this.datafunc(who, this.readbuffer[24:24+size])
		} else {
			this.readCacheSync.Lock()
			defer this.readCacheSync.Unlock()

			key := makeCacheKey(clientId, packetId)

			cache, ok := this.readcache[key]
			if !ok {
				cache = make([][]byte, parts)
				this.readcache[key] = cache
			}

			cache[currentPart-1] = make([]byte, size)
			copy(cache[currentPart-1], this.readbuffer[24:24+size])

			cursor := 0
			for _, part := range cache {
				if part == nil {
					return
				} else {
					cursor += len(part)
				}
			}

			packet := make([]byte, cursor)
			cursor = 0
			for _, part := range cache {
				copy(packet[cursor:], part)
				cursor += len(part)
			}

			delete(this.readcache, key)

			this.datafunc(who, packet)
		}
	} else {
		this.writeCacheSync.Lock()
		defer this.writeCacheSync.Unlock()

		key := makeCacheKey(clientId, packetId)
		cache, ok := this.writecache[key]
		if ok && cache != nil && len(cache) > int(currentPart-1) {
			cache[currentPart-1] = nil

			for _, b := range cache {
				if b != nil {
					return
				}
			}

			delete(this.writecache, key)
		} else {

		}
	}
}

func (this *Reudp) Send(addr *net.UDPAddr, data []byte) error {
	parts := len(data) / _DATA_SIZE
	if len(data)%_DATA_SIZE > 0 {
		parts += 1
	}

	currentPart := 1
	cursor := 0
	packetId := uint32(time.Now().UnixNano())
	clientId := makeClientId(this.LocalAddr())

	var e *multierror.Error

	for currentPart <= parts {
		size := len(data) - cursor
		if size > _DATA_SIZE {
			size = _DATA_SIZE
		}

		this.writeSync.Lock()
		err := this.send(addr, clientId, packetId, 0, uint16(size), uint32(parts), uint32(currentPart), data[cursor:cursor+size])
		this.writeSync.Unlock()

		multierror.Append(e, err)

		cursor += size
		currentPart++
	}

	return e.ErrorOrNil()
}

func (this *Reudp) send(where *net.UDPAddr, clientId, packetId uint32, flags, size uint16, parts, currentPart uint32, data []byte) error {
	if this.logfunc != nil {
		this.logfunc("reudp: send to %v / clientid %v / packetid %v / flags %v / %v bytes / %v of %v / data %v", where, clientId, packetId, flags, size, currentPart, parts, data)
	}

	binary.BigEndian.PutUint32(this.writebuffer[0:4], _MARKER)
	binary.BigEndian.PutUint32(this.writebuffer[4:], clientId)
	binary.BigEndian.PutUint16(this.writebuffer[8:], flags)
	binary.BigEndian.PutUint16(this.writebuffer[10:], uint16(size))
	binary.BigEndian.PutUint32(this.writebuffer[12:], packetId)
	binary.BigEndian.PutUint32(this.writebuffer[16:], uint32(parts))
	binary.BigEndian.PutUint32(this.writebuffer[20:], uint32(currentPart))

	copy(this.writebuffer[24:], data)

	if flags&_FLAG_ACK == 0 {
		this.writeCacheSync.Lock()

		key := makeCacheKey(clientId, packetId)
		cache, ok := this.writecache[key]
		if !ok {
			cache = make([][]byte, parts)
			this.writecache[key] = cache
		}

		cache[currentPart-1] = make([]byte, _HEADER_SIZE+size)
		copy(cache[currentPart-1], this.writebuffer[:_HEADER_SIZE+size])

		this.writeCacheSync.Unlock()

		go this.waitForAck(where, key, currentPart, 3)
	}

	_, err := this.conn.WriteToUDP(this.writebuffer[:size+_HEADER_SIZE], where)

	return err
}

func (this *Reudp) waitForAck(where *net.UDPAddr, writecacheKey uint64, part uint32, attempts int) {
	<-time.Tick(time.Second * 30)

	cache, ok := this.writecache[writecacheKey]
	if ok && cache != nil && len(cache) > int(part-1) && cache[part-1] != nil {
		this.conn.WriteToUDP(cache[part-1], where)

		if attempts > 0 {
			go this.waitForAck(where, writecacheKey, part, attempts-1)
		}
	}
}

func makeCacheKey(clientid, packetId uint32) uint64 {
	key := uint64(0)
	key += uint64(clientid)
	key = key << 32
	key += uint64(packetId)

	return key
}

func makeClientId(addr *net.UDPAddr) uint32 {
	return binary.BigEndian.Uint32(addr.IP) + uint32(addr.Port)
}
<|endoftext|>


<commit_before>package down

import (
	"fmt"
)

// Noder is the interface used to describe a down Node that will be converted to html.
type Noder interface {
	String() string
}

// Node is a basic Node to show how Nodes should be constructed.
type Node struct{}

func (n Node) String() string {
	return ""
}

// GroupNode is a Node Primitive for nodes that contain n amounts of children.
type GroupNode struct {
	children []Noder
}

func (gn GroupNode) Content() string {
	var content string
	for _, n := range gn.children {
		content += n.String()
	}
	return content
}

// Add a node to a composite Node
func (gn *GroupNode) AddChild(n Noder) {
	gn.children = append(gn.children, n)
}

// LineNode describes a line of markdown.
type LineNode struct {
	Child Noder
}

func (ln LineNode) String() string {
	return ln.Child.String() + "\n"
}

// CompositeStringNode describes a string which can contain any number of RawTextNodes, ItalicNodes,
// BoldNodes and LinkNodes.
type CompositeStringNode struct {
	GroupNode
}

func (csn CompositeStringNode) String() string {
	return csn.Content()
}

// RawTextNode is a node that represents bare text.
type RawTextNode struct {
	Content string
}

func (rtn RawTextNode) String() string {
	return rtn.Content
}

// LinkNode is a node that represents a html link.
type LinkNode struct {
	Text Noder
	Addr Noder
}

func (ln LinkNode) String() string {
	return fmt.Sprintf("%v", ln.Addr.String(), ln.Text.String())
}

// BoldStringNode is a node to represent bold text.
type BoldStringNode struct {
	Child CompositeStringNode
}

func (bsn BoldStringNode) String() string {
	return "" + bsn.Child.String() + ""
}

// ItalicStringNode is a node to represent italic text.
type ItalicNode struct {
	Child CompositeStringNode
}

func (in ItalicNode) String() string {
	return "" + in.Child.String() + ""
}

// HeaderNode is a node that can represent any html header tag (h[1...6])
type HeaderNode struct {
	Child RawTextNode
	Level int
}

func (hn HeaderNode) String() string {
	return fmt.Sprintf("<h%[1]v>%[2]v</h%[1]v>", hn.Level, hn.Child.String())
	// return fmt.Sprintf("%[2] %[1]", ln., 22)
}

// ParahraphNode is a node to represent a paragraph of raw, links, bold and italic text.
type ParagraphNode struct {
	Child CompositeStringNode
}

func (pn ParagraphNode) String() string {
	return fmt.Sprintf("
%v
", pn.Child.String())
}

type UnorderedListNode struct {
	GroupNode
}

func (uln UnorderedListNode) String() string {
	return fmt.Sprintf("
%v
", uln.Content())
}
<commit_msg>nodes: add UnorderedListItemNode<commit_after>package down

import (
	"fmt"
)

// Noder is the interface used to describe a down Node that will be converted to html.
type Noder interface {
	String() string
}

// Node is a basic Node to show how Nodes should be constructed.
type Node struct{}

func (n Node) String() string {
	return ""
}

// GroupNode is a Node Primitive for nodes that contain n amounts of children.
type GroupNode struct {
	children []Noder
}

func (gn GroupNode) Content() string {
	var content string
	for _, n := range gn.children {
		content += n.String()
	}
	return content
}

// Add a node to a composite Node
func (gn *GroupNode) AddChild(n Noder) {
	gn.children = append(gn.children, n)
}

// LineNode describes a line of markdown.
type LineNode struct {
	Child Noder
}

func (ln LineNode) String() string {
	return ln.Child.String() + "\n"
}

// CompositeStringNode describes a string which can contain any number of RawTextNodes, ItalicNodes,
// BoldNodes and LinkNodes.
type CompositeStringNode struct {
	GroupNode
}

func (csn CompositeStringNode) String() string {
	return csn.Content()
}

// RawTextNode is a node that represents bare text.
type RawTextNode struct {
	Content string
}

func (rtn RawTextNode) String() string {
	return rtn.Content
}

// LinkNode is a node that represents a html link.
type LinkNode struct {
	Text Noder
	Addr Noder
}

func (ln LinkNode) String() string {
	return fmt.Sprintf("%v", ln.Addr.String(), ln.Text.String())
}

// BoldStringNode is a node to represent bold text.
type BoldStringNode struct {
	Child CompositeStringNode
}

func (bsn BoldStringNode) String() string {
	return "" + bsn.Child.String() + ""
}

// ItalicStringNode is a node to represent italic text.
type ItalicNode struct {
	Child CompositeStringNode
}

func (in ItalicNode) String() string {
	return "" + in.Child.String() + ""
}

// HeaderNode is a node that can represent any html header tag (h[1...6])
type HeaderNode struct {
	Child RawTextNode
	Level int
}

func (hn HeaderNode) String() string {
	return fmt.Sprintf("<h%[1]v>%[2]v</h%[1]v>", hn.Level, hn.Child.String())
	// return fmt.Sprintf("%[2] %[1]", ln., 22)
}

// ParahraphNode is a node to represent a paragraph of raw, links, bold and italic text.
type ParagraphNode struct {
	Child CompositeStringNode
}

func (pn ParagraphNode) String() string {
	return fmt.Sprintf("
%v
", pn.Child.String())
}

type UnorderedListNode struct {
	GroupNode
}

func (uln UnorderedListNode) String() string {
	return fmt.Sprintf("
%v
", uln.Content())
}

type UnorderedListItemNode struct {
	Child Noder
}

func (ulin UnorderedListItemNode) String() string {
	return fmt.Sprintf("
%v
", ulin.Child.String())
}
<|endoftext|>


<commit_before>package filefixture

import (
	"fmt"
	"io"
	"log"
	"reflect"
	"strconv"

	humanize "github.com/dustin/go-humanize"
	"github.com/google/jsonapi"
)

// List is a list of FileFixtures, used for index action
type List struct {
	Fixtures []*FileFixture
}

// FileFixture represents a single file fixture
type FileFixture struct {
	ID             string   `jsonapi:"primary,file_fixture_structureds"`
	Name           string   `jsonapi:"attr,name"`
	CurrentVersion *Version `jsonapi:"relation,current_version"`
	Versions       *Version `jsonapi:"relation,versions"`
	CreatedAt      string   `jsonapi:"attr,created_at"`
	UpdatedAt      string   `jsonapi:"attr,updated_at"`
}

// Version respresents a concrete version of a file fixture
type Version struct {
	ID               string `jsonapi:"primary,file_fixture_version_structureds"`
	OriginalHash     string `jsonapi:"attr,hash"`
	OriginalFileSize string `jsonapi:"attr,file_size"`
	FieldNames       string `jsonapi:"attr,field_names"`
	CreatedAt        string `jsonapi:"attr,created_at"`
	UpdatedAt        string `jsonapi:"attr,updated_at"`
}

// UnmarshalFileFixtures unmarshals a list of FileFixture records
func UnmarshalFileFixtures(input io.Reader) (List, error) {
	items, err := jsonapi.UnmarshalManyPayload(input, reflect.TypeOf(new(FileFixture)))
	if err != nil {
		return List{}, err
	}

	result := List{}

	for _, item := range items {
		fixture, ok := item.(*FileFixture)
		if !ok {
			return List{}, fmt.Errorf("Type assertion failed")
		}

		result.Fixtures = append(result.Fixtures, fixture)
	}

	return result, nil
}

// UnmarshalFileFixture unmarshals a single FileFixture record
func UnmarshalFileFixture(input io.Reader) (*FileFixture, error) {
	fixture := new(FileFixture)
	err := jsonapi.UnmarshalPayload(input, fixture)
	if err != nil {
		return new(FileFixture), err
	}

	return fixture, nil
}

// FindByName look up a FileFixture by name in List
func (fileFixtures List) FindByName(name string) FileFixture {
	for _, fileFixture := range fileFixtures.Fixtures {
		if fileFixture.Name == name {
			return *fileFixture
		}
	}

	return FileFixture{}
}

// ShowName displays the name, id and hash of a filefixture
func ShowName(input io.Reader) {
	items, err := jsonapi.UnmarshalManyPayload(input, reflect.TypeOf(new(FileFixture)))

	if err != nil {
		log.Fatal(err)
	}

	for _, item := range items {
		fixture, _ := item.(*FileFixture)

		fmt.Printf("* %s (ID: %s, Content-Hash: %s)\n", fixture.Name, fixture.ID, fixture.CurrentVersion.OriginalHash)
	}
}

// ShowDetails print out details of a file fixture, including its current version
func ShowDetails(fileFixture *FileFixture) {
	// TODO where to move this? shouldn't this be already done earlier?
	bytes, err := strconv.ParseUint(fileFixture.CurrentVersion.OriginalFileSize, 10, 64)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Name:            %s\n", fileFixture.Name)
	fmt.Printf("UID:             %s\n", fileFixture.ID)
	fmt.Printf("Current Version: %s\n", fileFixture.CurrentVersion.ID)
	fmt.Printf("  SHA256 Hash:   %s\n", fileFixture.CurrentVersion.OriginalHash)
	fmt.Printf("  Size:          %s\n", humanize.Bytes(bytes))
	fmt.Printf("  Created:       %s\n", fileFixture.CurrentVersion.CreatedAt)
}
<commit_msg>add some date formatting<commit_after>package filefixture

import (
	"fmt"
	"io"
	"log"
	"reflect"
	"strconv"
	"time"

	humanize "github.com/dustin/go-humanize"
	"github.com/google/jsonapi"
)

// List is a list of FileFixtures, used for index action
type List struct {
	Fixtures []*FileFixture
}

// FileFixture represents a single file fixture
type FileFixture struct {
	ID             string   `jsonapi:"primary,file_fixture_structureds"`
	Name           string   `jsonapi:"attr,name"`
	CurrentVersion *Version `jsonapi:"relation,current_version"`
	Versions       *Version `jsonapi:"relation,versions"`
	CreatedAt      string   `jsonapi:"attr,created_at"`
	UpdatedAt      string   `jsonapi:"attr,updated_at"`
}

// Version respresents a concrete version of a file fixture
type Version struct {
	ID               string `jsonapi:"primary,file_fixture_version_structureds"`
	OriginalHash     string `jsonapi:"attr,hash"`
	OriginalFileSize string `jsonapi:"attr,file_size"`
	FieldNames       string `jsonapi:"attr,field_names"`
	CreatedAt        string `jsonapi:"attr,created_at"`
	UpdatedAt        string `jsonapi:"attr,updated_at"`
}

// UnmarshalFileFixtures unmarshals a list of FileFixture records
func UnmarshalFileFixtures(input io.Reader) (List, error) {
	items, err := jsonapi.UnmarshalManyPayload(input, reflect.TypeOf(new(FileFixture)))
	if err != nil {
		return List{}, err
	}

	result := List{}

	for _, item := range items {
		fixture, ok := item.(*FileFixture)
		if !ok {
			return List{}, fmt.Errorf("Type assertion failed")
		}

		result.Fixtures = append(result.Fixtures, fixture)
	}

	return result, nil
}

// UnmarshalFileFixture unmarshals a single FileFixture record
func UnmarshalFileFixture(input io.Reader) (*FileFixture, error) {
	fixture := new(FileFixture)
	err := jsonapi.UnmarshalPayload(input, fixture)
	if err != nil {
		return new(FileFixture), err
	}

	return fixture, nil
}

// FindByName look up a FileFixture by name in List
func (fileFixtures List) FindByName(name string) FileFixture {
	for _, fileFixture := range fileFixtures.Fixtures {
		if fileFixture.Name == name {
			return *fileFixture
		}
	}

	return FileFixture{}
}

// ShowName displays the name, id and hash of a filefixture
func ShowName(input io.Reader) {
	items, err := jsonapi.UnmarshalManyPayload(input, reflect.TypeOf(new(FileFixture)))

	if err != nil {
		log.Fatal(err)
	}

	for _, item := range items {
		fixture, _ := item.(*FileFixture)

		fmt.Printf("* %s (ID: %s, Content-Hash: %s)\n", fixture.Name, fixture.ID, fixture.CurrentVersion.OriginalHash)
	}
}

// ShowDetails print out details of a file fixture, including its current version
func ShowDetails(fileFixture *FileFixture) {
	// TODO where to move this? shouldn't this be already done earlier?
	bytes, err := strconv.ParseUint(fileFixture.CurrentVersion.OriginalFileSize, 10, 64)
	if err != nil {
		log.Fatal(err)
	}
	fixtureCreatedAt := parseTime(fileFixture.CreatedAt)
	fixtureUpdatedAt := parseTime(fileFixture.UpdatedAt)
	fixtureCurrentVersionCreatedAt := parseTime(fileFixture.CurrentVersion.CreatedAt)

	fmt.Printf("Name:            %s\n", fileFixture.Name)
	fmt.Printf("UID:             %s\n", fileFixture.ID)
	fmt.Printf("Created:         %s (%s)\n", convertToLocalTZ(fixtureCreatedAt), humanize.Time(fixtureCreatedAt))
	fmt.Printf("Updated:         %s (%s)\n", convertToLocalTZ(fixtureUpdatedAt), humanize.Time(fixtureUpdatedAt))
	fmt.Printf("Current Version: %s\n", fileFixture.CurrentVersion.ID)
	fmt.Printf("  SHA256 Hash:   %s\n", fileFixture.CurrentVersion.OriginalHash)
	fmt.Printf("  Size:          %s\n", humanize.Bytes(bytes))
	fmt.Printf("  Created:       %s (%s)\n", convertToLocalTZ(fixtureCurrentVersionCreatedAt), humanize.Time(fixtureCurrentVersionCreatedAt))
}

func convertToLocalTZ(timeToConvert time.Time) time.Time {
	loc, err := time.LoadLocation("Local")
	if err != nil {
		log.Fatal(err)
	}

	return timeToConvert.In(loc)
}

func parseTime(subject string) time.Time {
	parsedTime, err := time.Parse(time.RFC3339Nano, subject)
	if err != nil {
		log.Fatal(err)
	}

	return parsedTime
}
<|endoftext|>


<commit_before>package server

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

	"github.com/gorilla/mux"
)

func home(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/doc", http.StatusFound)
}

func pizzasList(w http.ResponseWriter, r *http.Request) {
	list, _ := PizzaList()

	if err := serveResponse(w, 200, list); err != nil {
		panic(err)
	}
}

func findOrder(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, _ := strconv.Atoi(vars["id"])

	order, err := FindOrder(id)
	if err != nil {
		if err := serveResponse(w, 404, &errorResponse{"not_found", err.Error()}); err != nil {
			panic(err)
		}
		return
	}

	if err := serveResponse(w, 200, order); err != nil {
		panic(err)
	}
}

func findOrders(w http.ResponseWriter, r *http.Request) {
	orders, err := AllOrders()
	if err != nil {
		if err := serveResponse(w, 404, &errorResponse{"error", err.Error()}); err != nil {
			panic(err)
		}
		return
	}

	if err := serveResponse(w, 200, orders); err != nil {
		panic(err)
	}
}

func createOrder(w http.ResponseWriter, r *http.Request) {
	var orderContent struct {
		Pizza int `json:"id"`
	}
	body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
	if err != nil {
		panic(err)
	}
	if err := r.Body.Close(); err != nil {
		panic(err)
	}
	if err := json.Unmarshal(body, &orderContent); err != nil {
		if err := serveResponse(w, 422, &errorResponse{"invalid", fmt.Sprintf("Invalid data. Expected '{\"id\":1}'. Got %s", body)}); err != nil {
			panic(err)
		}

		return
	}

	pizza, err := FindPizza(orderContent.Pizza)
	if err != nil {
		if err := serveResponse(w, 404, &errorResponse{"not_found", err.Error()}); err != nil {
			panic(err)
		}
		return
	}

	order := CreateOrder(pizza)
	w.WriteHeader(http.StatusCreated)

	if err := serveResponse(w, 200, order); err != nil {
		panic(err)
	}
}

func upgrade(w http.ResponseWriter, r *http.Request) {
	var upgradeContent struct {
		Name  string `json:"name"`
		Token string `json:"token"`
	}
	body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
	if err != nil {
		panic(err)
	}
	if err := r.Body.Close(); err != nil {
		panic(err)
	}
	if err := json.Unmarshal(body, &upgradeContent); err != nil {
		if err := serveResponse(w, 422, err); err != nil {
			panic(err)
		}
		return
	}

	token := r.Header.Get("Authorization")
	if token != os.Getenv("UPGRADE_KEY") {
		if err := serveResponse(w, 401, &errorResponse{"unauthorized", "Not authorized"}); err != nil {
			panic(err)
		}
		return
	}

	buildConstraint(upgradeContent.Token, upgradeContent.Name)

	request_id := requestId(r)
	log.Printf("count#upgraded name=%s token=%s request_id=%s", upgradeContent.Name, upgradeContent.Token, request_id)
	if err := serveResponse(w, 200, upgradeContent); err != nil {
		panic(err)
	}
}
<commit_msg>don't call WriteHeader multiple times<commit_after>package server

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

	"github.com/gorilla/mux"
)

func home(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/doc", http.StatusFound)
}

func pizzasList(w http.ResponseWriter, r *http.Request) {
	list, _ := PizzaList()

	if err := serveResponse(w, 200, list); err != nil {
		panic(err)
	}
}

func findOrder(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	id, _ := strconv.Atoi(vars["id"])

	order, err := FindOrder(id)
	if err != nil {
		if err := serveResponse(w, 404, &errorResponse{"not_found", err.Error()}); err != nil {
			panic(err)
		}
		return
	}

	if err := serveResponse(w, 200, order); err != nil {
		panic(err)
	}
}

func findOrders(w http.ResponseWriter, r *http.Request) {
	orders, err := AllOrders()
	if err != nil {
		if err := serveResponse(w, 404, &errorResponse{"error", err.Error()}); err != nil {
			panic(err)
		}
		return
	}

	if err := serveResponse(w, 200, orders); err != nil {
		panic(err)
	}
}

func createOrder(w http.ResponseWriter, r *http.Request) {
	var orderContent struct {
		Pizza int `json:"id"`
	}
	body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
	if err != nil {
		panic(err)
	}
	if err := r.Body.Close(); err != nil {
		panic(err)
	}
	if err := json.Unmarshal(body, &orderContent); err != nil {
		if err := serveResponse(w, 422, &errorResponse{"invalid", fmt.Sprintf("Invalid data. Expected '{\"id\":1}'. Got %s", body)}); err != nil {
			panic(err)
		}

		return
	}

	pizza, err := FindPizza(orderContent.Pizza)
	if err != nil {
		if err := serveResponse(w, 404, &errorResponse{"not_found", err.Error()}); err != nil {
			panic(err)
		}
		return
	}

	order := CreateOrder(pizza)
	if err := serveResponse(w, http.StatusCreated, order); err != nil {
		panic(err)
	}
}

func upgrade(w http.ResponseWriter, r *http.Request) {
	var upgradeContent struct {
		Name  string `json:"name"`
		Token string `json:"token"`
	}
	body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))
	if err != nil {
		panic(err)
	}
	if err := r.Body.Close(); err != nil {
		panic(err)
	}
	if err := json.Unmarshal(body, &upgradeContent); err != nil {
		if err := serveResponse(w, 422, err); err != nil {
			panic(err)
		}
		return
	}

	token := r.Header.Get("Authorization")
	if token != os.Getenv("UPGRADE_KEY") {
		if err := serveResponse(w, 401, &errorResponse{"unauthorized", "Not authorized"}); err != nil {
			panic(err)
		}
		return
	}

	buildConstraint(upgradeContent.Token, upgradeContent.Name)

	request_id := requestId(r)
	log.Printf("count#upgraded name=%s token=%s request_id=%s", upgradeContent.Name, upgradeContent.Token, request_id)
	if err := serveResponse(w, 200, upgradeContent); err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>// +build !linux,!bsd
// TODO(bprosnitz) Should change for nacl?

package netconfig

// Code to signal a network change every 2 minutes.   We use
// this for systems where we don't yet have a good way to
// watch for network changes.

import (
	"time"
)

type timerNetConfigWatcher struct {
	c    chan struct{} // channel to signal confg changes
	stop chan struct{} // channel to tell the watcher to stop
}

// Stop any waiter
func (w *timerNetConfigWatcher) Stop() {
	w.stop <- struct{}{}
}

func (w *timerNetConfigWatcher) Channel() chan struct{} {
	return w.c
}

func (w *timerNetConfigWatcher) watcher() {
	for {
		select {
		case <-w.stop:
			close(w.c)
			return
		case <-time.NewTimer(2 * time.Minute).C:
			select {
			case w.c <- struct{}{}:
			default:
			}
		}
	}
}

func NewNetConfigWatcher() (NetConfigWatcher, error) {
	w := &timerNetConfigWatcher{}
	w.c = make(chan struct{})
	w.stop = make(chan struct{}, 1)
	go w.watcher()
	return w, nil
}

func GetIPRoutes(defaultOnly bool) []*IPRoute {
	panic("Not yet implemented")
}
<commit_msg>veyron/lib/netconfig: Fix build on Mac.<commit_after>// +build !linux,!darwin,!dragonfly,!freebsd,!netbsd,!openbsd
// TODO(bprosnitz) Should change for nacl?

package netconfig

// Code to signal a network change every 2 minutes.   We use
// this for systems where we don't yet have a good way to
// watch for network changes.

import (
	"time"
)

type timerNetConfigWatcher struct {
	c    chan struct{} // channel to signal confg changes
	stop chan struct{} // channel to tell the watcher to stop
}

// Stop any waiter
func (w *timerNetConfigWatcher) Stop() {
	w.stop <- struct{}{}
}

func (w *timerNetConfigWatcher) Channel() chan struct{} {
	return w.c
}

func (w *timerNetConfigWatcher) watcher() {
	for {
		select {
		case <-w.stop:
			close(w.c)
			return
		case <-time.NewTimer(2 * time.Minute).C:
			select {
			case w.c <- struct{}{}:
			default:
			}
		}
	}
}

func NewNetConfigWatcher() (NetConfigWatcher, error) {
	w := &timerNetConfigWatcher{}
	w.c = make(chan struct{})
	w.stop = make(chan struct{}, 1)
	go w.watcher()
	return w, nil
}

func GetIPRoutes(defaultOnly bool) []*IPRoute {
	panic("Not yet implemented")
}
<|endoftext|>


<commit_before>// Package ns provides name server methods for selected name server(s)
package ns

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"sort"
	"strings"
	"time"

	"github.com/mehrdadrad/mylg/cli"
	"github.com/mehrdadrad/mylg/data"
	"github.com/miekg/dns"
)

// A Host represents a name server host
type Host struct {
	ip      string
	alpha2  string
	country string
	city    string
}

// A Request represents a name server request
type Request struct {
	country string
	city    string
	host    string
	hosts   []Host
}

// NewRequest creates a new dns request object
func NewRequest() *Request {
	return &Request{host: ""}
}

// Init configure dns command and fetch name servers
func (d *Request) Init() {
	if !d.cache("validate") {
		d.hosts = fetchNSHosts()
		d.cache("write")
	} else {
		d.cache("read")
	}
}

// SetCountryList init the connect contry items
func (d *Request) SetCountryList(c *cli.Readline) {
	var countries []string
	for _, host := range d.hosts {
		countries = append(countries, host.country)
	}
	countries = uniqStrSlice(countries)
	sort.Strings(countries)
	c.UpdateCompleter("connect", countries)
}

// SetNodeList init the node city items
func (d *Request) SetNodeList(c *cli.Readline) {
	var node []string
	for _, host := range d.hosts {
		if host.country == d.country {
			node = append(node, host.city)
		}
	}
	sort.Strings(node)
	c.UpdateCompleter("node", node)
}

// ChkCountry set requested country
func (d *Request) ChkCountry(country string) bool {
	d.country = country
	return true
}

// ChkCountry set requested country
func (d *Request) ChkNode(city string) bool {
	for _, h := range d.hosts {
		if d.country == h.country && city == h.city {
			d.host = h.ip
		}
	}
	return true
}

// Dig look up name server
func (d *Request) Dig() {
	println(d.host)
	c := new(dns.Client)
	m := new(dns.Msg)

	m.SetQuestion(dns.Fqdn("yahoo.com"), dns.TypeA)
	m.RecursionDesired = true
	r, _, err := c.Exchange(m, "8.8.8.8:53")
	if err != nil {
		println(err.Error())
	}
	for _, a := range r.Answer {
		fmt.Println(a)
	}
}

// cache provides caching for name servers
func (d *Request) cache(r string) bool {
	switch r {
	case "read":
		b, err := ioutil.ReadFile("/tmp/mylg.ns")
		if err != nil {
			panic(err.Error())
		}
		d.hosts = d.hosts[:0]
		r := bytes.NewBuffer(b)
		s := bufio.NewScanner(r)
		for s.Scan() {
			csv := strings.Split(s.Text(), ";")
			if len(csv) != 4 {
				continue
			}
			d.hosts = append(d.hosts, Host{alpha2: csv[0], country: csv[1], city: csv[2], ip: csv[3]})
		}
	case "write":
		var data []string
		for _, h := range d.hosts {
			data = append(data, fmt.Sprintf("%s;%s;%s;%s", h.alpha2, h.country, h.city, h.ip))
		}
		err := ioutil.WriteFile("/tmp/mylg.ns", []byte(strings.Join(data, "\n")), 0644)
		if err != nil {
			panic(err.Error())
		}
		println("write done!")
	case "validate":
		f, err := os.Stat("/tmp/mylg.ns")
		if err != nil {
			return false
		}
		d := time.Since(f.ModTime())
		if d.Hours() > 48 {
			return false
		}
	}
	return true
}

// Fetch name servers from public-dns.info
func fetchNSHosts() []Host {
	var (
		hosts   []Host
		counter = make(map[string]int)
	)
	resp, err := http.Get("http://public-dns.info/nameservers.csv")
	if err != nil {
		println(err.Error())
	}
	defer resp.Body.Close()
	scanner := bufio.NewScanner(resp.Body)
	for scanner.Scan() {
		csv := strings.Split(scanner.Text(), ",")
		if csv[3] != "\"\"" {
			if name, ok := data.Country[csv[2]]; ok && counter[csv[2]] < 5 {
				hosts = append(hosts, Host{ip: csv[0], alpha2: csv[2], country: name, city: csv[3]})
				counter[csv[2]]++
			}
		}
	}
	return hosts
}

// uniqStrSlice return unique slice
func uniqStrSlice(src []string) []string {
	var rst []string
	tmp := make(map[string]struct{})
	for _, s := range src {
		tmp[s] = struct{}{}
	}
	for s := range tmp {
		rst = append(rst, s)
	}
	return rst
}
<commit_msg>added vars to dig<commit_after>// Package ns provides name server methods for selected name server(s)
package ns

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"sort"
	"strings"
	"time"

	"github.com/mehrdadrad/mylg/cli"
	"github.com/mehrdadrad/mylg/data"
	"github.com/miekg/dns"
)

// A Host represents a name server host
type Host struct {
	ip      string
	alpha2  string
	country string
	city    string
}

// A Request represents a name server request
type Request struct {
	country string
	city    string
	host    string
	hosts   []Host
}

// NewRequest creates a new dns request object
func NewRequest() *Request {
	return &Request{host: ""}
}

// Init configure dns command and fetch name servers
func (d *Request) Init() {
	if !d.cache("validate") {
		d.hosts = fetchNSHosts()
		d.cache("write")
	} else {
		d.cache("read")
	}
}

// SetCountryList init the connect contry items
func (d *Request) SetCountryList(c *cli.Readline) {
	var countries []string
	for _, host := range d.hosts {
		countries = append(countries, host.country)
	}
	countries = uniqStrSlice(countries)
	sort.Strings(countries)
	c.UpdateCompleter("connect", countries)
}

// SetNodeList init the node city items
func (d *Request) SetNodeList(c *cli.Readline) {
	var node []string
	for _, host := range d.hosts {
		if host.country == d.country {
			node = append(node, host.city)
		}
	}
	sort.Strings(node)
	c.UpdateCompleter("node", node)
}

// ChkCountry set requested country
func (d *Request) ChkCountry(country string) bool {
	d.country = country
	return true
}

// ChkNode set requested country
func (d *Request) ChkNode(city string) bool {
	for _, h := range d.hosts {
		if d.country == h.country && city == h.city {
			d.host = h.ip
		}
	}
	return true
}

// Dig look up name server
func (d *Request) Dig(args string) {
	c := new(dns.Client)
	m := new(dns.Msg)

	m.SetQuestion(dns.Fqdn(args), dns.TypeA)
	m.RecursionDesired = true
	r, _, err := c.Exchange(m, d.host+":53")
	if err != nil {
		println(err.Error())
	}
	for _, a := range r.Answer {
		fmt.Println(a)
	}
}

// cache provides caching for name servers
func (d *Request) cache(r string) bool {
	switch r {
	case "read":
		b, err := ioutil.ReadFile("/tmp/mylg.ns")
		if err != nil {
			panic(err.Error())
		}
		d.hosts = d.hosts[:0]
		r := bytes.NewBuffer(b)
		s := bufio.NewScanner(r)
		for s.Scan() {
			csv := strings.Split(s.Text(), ";")
			if len(csv) != 4 {
				continue
			}
			d.hosts = append(d.hosts, Host{alpha2: csv[0], country: csv[1], city: csv[2], ip: csv[3]})
		}
	case "write":
		var data []string
		for _, h := range d.hosts {
			data = append(data, fmt.Sprintf("%s;%s;%s;%s", h.alpha2, h.country, h.city, h.ip))
		}
		err := ioutil.WriteFile("/tmp/mylg.ns", []byte(strings.Join(data, "\n")), 0644)
		if err != nil {
			panic(err.Error())
		}
	case "validate":
		f, err := os.Stat("/tmp/mylg.ns")
		if err != nil {
			return false
		}
		d := time.Since(f.ModTime())
		if d.Hours() > 48 {
			return false
		}
	}
	return true
}

// Fetch name servers from public-dns.info
func fetchNSHosts() []Host {
	var (
		hosts   []Host
		counter = make(map[string]int)
	)
	resp, err := http.Get("http://public-dns.info/nameservers.csv")
	if err != nil {
		println(err.Error())
	}
	defer resp.Body.Close()
	scanner := bufio.NewScanner(resp.Body)
	for scanner.Scan() {
		csv := strings.Split(scanner.Text(), ",")
		if csv[3] != "\"\"" {
			if name, ok := data.Country[csv[2]]; ok && counter[csv[2]] < 5 {
				hosts = append(hosts, Host{ip: csv[0], alpha2: csv[2], country: name, city: csv[3]})
				counter[csv[2]]++
			}
		}
	}
	return hosts
}

// uniqStrSlice return unique slice
func uniqStrSlice(src []string) []string {
	var rst []string
	tmp := make(map[string]struct{})
	for _, s := range src {
		tmp[s] = struct{}{}
	}
	for s := range tmp {
		rst = append(rst, s)
	}
	return rst
}
<|endoftext|>


<commit_before>// Package pages provides some helpers for serving web pages.
//
// Example usage:
//   var page = Add("/uri", handler, "tmpl/base.tmpl", "tmpl/page.tmpl")
//
//   func handler(w http.ResponseWriter, r *http.Request) pages.Result {
//     return pages.OK("some data to page.tmpl")
//   }
//
//   pages.SetLogger(func(r *http.Request) pages.Logger {
//     return appengine.NewContext(r)
//   })
//   http.Handle(page.URI, page)
package pages

import (
	"fmt"
	"html/template"
	"net/http"
	"net/url"

	"log"
)

var (
	logger              LoggerFunc
	BaseTemplate        = "base"         // Name of top-level template to invoke for each page.
	BadRequestMsg       = "Bad request." // Message to display if ShowError is called.
	StatusBadRequest    = Result{responseCode: http.StatusBadRequest}
	StatusNotFound      = Result{responseCode: http.StatusNotFound}
	StatusInternalError = Result{responseCode: http.StatusInternalServerError}
)

// SetLogger registers a logger function.
func SetLogger(l LoggerFunc) {
	logger = l
}

// Renderer is a function to render a page result.
type Renderer func(w http.ResponseWriter, r *http.Request) Result

// A Page to be rendered.
type Page struct {
	URI    string             // URI path
	Render Renderer           // func to render the page
	tmpl   *template.Template // backing template
}

// Add creates a new page.
//
// Add panics if the page templates cannot be parsed.
func Add(uri string, render Renderer, tmpls ...string) Page {
	t := template.Must(template.ParseFiles(tmpls...))
	return Page{
		URI:    uri,
		tmpl:   t,
		Render: render,
	}
}

// Result is the result of rendering a page.
type Result struct {
	data         interface{} // Data to render the page.
	responseCode int         // HTTP response code.
	err          error       // Error, or nil.
}

// StatusOK returns http.StatusOK with given data passed to the template.
func StatusOK(data interface{}) Result {
	return Result{
		responseCode: http.StatusOK,
		data:         data,
	}
}

// BadRequestWith returns a Result indicating a bad request.
func BadRequestWith(err error) Result {
	return Result{
		responseCode: http.StatusBadRequest,
		err:          err,
	}
}

// ShowError redirects to the index page with the "error" param set to
// a static error message.
//
// Provided error is logged, but not displayed to the user.
func ShowError(w http.ResponseWriter, r *http.Request, err error) {
	if logger == nil {
		log.Fatalf("no logger specified; call SetLogger\n")
	}
	l := logger(r)
	q := url.Values{
		"error": []string{BadRequestMsg},
	}
	nextUrl := fmt.Sprintf("/?%s", q.Encode())
	l.Errorf("returning StatusBadRequest and redirecting to %q: %v\n", nextUrl, err)
	http.Redirect(w, r, nextUrl, http.StatusBadRequest)
}

// Values are simple URL params.
type Values map[string]string

// RedirectWith redirects to the index page with specified params.
func RedirectWith(w http.ResponseWriter, r *http.Request, params Values) {
	q := url.Values{}
	for k, v := range params {
		q[k] = []string{v}
	}
	http.Redirect(w, r, fmt.Sprintf("/?%s", q.Encode()), http.StatusFound)
}

// ServeHTTP serves HTTP for the page.
//
// ServeHTTP panics if no logger has been registered with SetLogger.
func (p Page) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if logger == nil {
		log.Fatalf("no logger specified; call SetLogger\n")
	}
	l := logger(r)
	l.Infof("Page %+v will ServeHTTP for URL: %v", p, r.URL)

	// Render the page, retrieving any data for the template.
	pr := p.Render(w, r)
	if pr.err != nil || pr.responseCode != http.StatusOK {
		if pr.err != nil {
			l.Errorf("Error while rendering %v: %v\n", r.URL, pr.err)
		}
		if pr.responseCode == http.StatusNotFound {
			http.NotFound(w, r)
		} else if pr.responseCode == http.StatusBadRequest {
			http.Error(w, "Bad request", http.StatusBadRequest)
		} else {
			http.Error(w, "Internal server error.", pr.responseCode)
		}
		return
	}

	err := p.tmpl.ExecuteTemplate(w, BaseTemplate, pr.data)
	if err != nil {
		// TODO: If this happens, partial template data is still written
		// to w by ExecuteTemplate, which isn't ideal; we'd like the 500
		// to be the only thing returned to viewing user.

		// Error rendering the template is a programming bug.
		l.Errorf("Failed to render template: %v", err)
		http.Error(w, "Internal server error.", http.StatusInternalServerError)
	}
}

// LoggerFunc returns a logger from a http request.
type LoggerFunc func(*http.Request) Logger

// Logger specifies logging functions.
type Logger interface {
	// Debugf formats its arguments according to the format, analogous to fmt.Printf,
	// and records the text as a log message at Debug level.
	Debugf(format string, args ...interface{})

	// Infof is like Debugf, but at Info level.
	Infof(format string, args ...interface{})

	// Warningf is like Debugf, but at Warning level.
	Warningf(format string, args ...interface{})

	// Errorf is like Debugf, but at Error level.
	Errorf(format string, args ...interface{})

	// Criticalf is like Debugf, but at Critical level.
	Criticalf(format string, args ...interface{})
}
<commit_msg>rejiggers redirect logic; Values.AddTo(uri) now makes     // TODO: replace pages.RedirectWith in computare with:<commit_after>// Package pages provides some helpers for serving web pages.
//
// Example usage:
//   var page = Add("/uri", handler, "tmpl/base.tmpl", "tmpl/page.tmpl")
//
//   func handler(w http.ResponseWriter, r *http.Request) pages.Result {
//     return pages.OK("some data to page.tmpl")
//   }
//
//   pages.SetLogger(func(r *http.Request) pages.Logger {
//     return appengine.NewContext(r)
//   })
//   http.Handle(page.URI, page)
package pages

import (
	"fmt"
	"html/template"
	"net/http"
	"net/url"

	"log"
)

var (
	logger              LoggerFunc
	BaseTemplate        = "base"         // Name of top-level template to invoke for each page.
	BadRequestMsg       = "Bad request." // Message to display if ShowError is called.
	StatusBadRequest    = Result{responseCode: http.StatusBadRequest}
	StatusNotFound      = Result{responseCode: http.StatusNotFound}
	StatusInternalError = Result{responseCode: http.StatusInternalServerError}
)

// SetLogger registers a logger function.
func SetLogger(l LoggerFunc) {
	logger = l
}

// Renderer is a function to render a page result.
type Renderer func(w http.ResponseWriter, r *http.Request) Result

// A Page to be rendered.
type Page struct {
	URI    string             // URI path
	Render Renderer           // func to render the page
	tmpl   *template.Template // backing template
}

// Add creates a new page.
//
// Add panics if the page templates cannot be parsed.
func Add(uri string, render Renderer, tmpls ...string) Page {
	t := template.Must(template.ParseFiles(tmpls...))
	return Page{
		URI:    uri,
		tmpl:   t,
		Render: render,
	}
}

// Result is the result of rendering a page.
type Result struct {
	data         interface{} // Data to render the page.
	responseCode int         // HTTP response code.
	err          error       // Error, or nil.
	next         string      // Next uri, if applicable.
}

// StatusOK returns http.StatusOK with given data passed to the template.
func StatusOK(data interface{}) Result {
	return Result{
		responseCode: http.StatusOK,
		data:         data,
	}
}

// BadRequestWith returns a Result indicating a bad request.
func BadRequestWith(err error) Result {
	return Result{
		responseCode: http.StatusBadRequest,
		err:          err,
	}
}

// RedirectWith returns a Result indicating to redirect to another URI.
func RedirectWith(uri string) Result {
	return Result{
		responseCode: http.StatusSeeOther,
		next:         uri,
	}
}

// ShowError redirects to the index page with the "error" param set to
// a static error message.
//
// Provided error is logged, but not displayed to the user.
func ShowError(w http.ResponseWriter, r *http.Request, err error) {
	if logger == nil {
		log.Fatalf("no logger specified; call SetLogger\n")
	}
	l := logger(r)
	q := url.Values{
		"error": []string{BadRequestMsg},
	}
	nextUrl := fmt.Sprintf("/?%s", q.Encode())
	l.Errorf("returning StatusBadRequest and redirecting to %q: %v\n", nextUrl, err)
	http.Redirect(w, r, nextUrl, http.StatusBadRequest)
}

// Values are simple URL params.
type Values map[string]string

// AddTo adds the Values to specified URI.
func (v Values) AddTo(uri string) string {
	q := url.Values{}
	for k, v := range v {
		q[k] = []string{v}
	}
	return fmt.Sprintf("%s?%s", uri, q.Encode())
}

// ServeHTTP serves HTTP for the page.
//
// ServeHTTP panics if no logger has been registered with SetLogger.
func (p Page) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if logger == nil {
		log.Fatalf("no logger specified; call SetLogger\n")
	}
	l := logger(r)
	l.Infof("Page %+v will ServeHTTP for URL: %v", p, r.URL)

	// Render the page, retrieving any data for the template.
	pr := p.Render(w, r)
	if pr.err != nil || pr.responseCode != http.StatusOK {
		if pr.err != nil {
			l.Errorf("Error while rendering %v: %v\n", r.URL, pr.err)
		}
		if pr.responseCode == http.StatusNotFound {
			http.NotFound(w, r)
		} else if pr.responseCode == http.StatusBadRequest {
			http.Error(w, "Bad request", http.StatusBadRequest)
		} else if pr.responseCode == http.StatusSeeOther {
			http.Redirect(w, r, pr.next, http.StatusSeeOther)
		} else {
			http.Error(w, "Internal server error.", pr.responseCode)
		}
		return
	}

	err := p.tmpl.ExecuteTemplate(w, BaseTemplate, pr.data)
	if err != nil {
		// TODO: If this happens, partial template data is still written
		// to w by ExecuteTemplate, which isn't ideal; we'd like the 500
		// to be the only thing returned to viewing user.

		// Error rendering the template is a programming bug.
		l.Errorf("Failed to render template: %v", err)
		http.Error(w, "Internal server error.", http.StatusInternalServerError)
	}
}

// LoggerFunc returns a logger from a http request.
type LoggerFunc func(*http.Request) Logger

// Logger specifies logging functions.
type Logger interface {
	// Debugf formats its arguments according to the format, analogous to fmt.Printf,
	// and records the text as a log message at Debug level.
	Debugf(format string, args ...interface{})

	// Infof is like Debugf, but at Info level.
	Infof(format string, args ...interface{})

	// Warningf is like Debugf, but at Warning level.
	Warningf(format string, args ...interface{})

	// Errorf is like Debugf, but at Error level.
	Errorf(format string, args ...interface{})

	// Criticalf is like Debugf, but at Critical level.
	Criticalf(format string, args ...interface{})
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"image"
	"image/color"
	"io"
	"strings"
	"unicode/utf8"

	_ "image/jpeg"
	_ "image/png"

	"github.com/nfnt/resize"
	termbox "github.com/nsf/termbox-go"
	"github.com/taylorskalyo/goreader/epub"

	"golang.org/x/net/html"
)

type parser struct {
	elStack   []string
	tokenizer *html.Tokenizer
	doc       cellbuf
	items     []epub.Item
}

type cellbuf struct {
	cells   []termbox.Cell
	width   int
	lmargin int
	col     int
	row     int
}

// setCell changes a cell's attributes in the cell buffer document at the given
// position.
func (c *cellbuf) setCell(x, y int, ch rune, fg, bg termbox.Attribute) {
	// Grow in steps of 1024 when out of space.
	for y*c.width+x >= len(c.cells) {
		c.cells = append(c.cells, make([]termbox.Cell, 1024)...)
	}
	c.cells[y*c.width+x] = termbox.Cell{Ch: ch, Fg: fg, Bg: bg}
}

// scanWords is a split function for a Scanner that returns space-separated
// words. Unlike bufio.ScanWords(), scanWords only splits on spaces (i.e. not
// newlines, tabs, or other whitespace).
func scanWords(data []byte, atEOF bool) (advance int, token []byte, err error) {
	// Skip leading spaces.
	start := 0
	for width := 0; start < len(data); start += width {
		var r rune
		r, width = utf8.DecodeRune(data[start:])
		if r != ' ' {
			break
		}
	}

	// Scan until space, marking end of word.
	for width, i := 0, start; i < len(data); i += width {
		var r rune
		r, width = utf8.DecodeRune(data[i:])
		if r == ' ' {
			return i + width, data[start:i], nil
		}
	}

	// If we're at EOF, we have a final, non-empty, non-terminated word. Return
	// it.
	if atEOF && len(data) > start {
		return len(data), data[start:], nil
	}

	// Request more data.
	return start, nil, nil
}

// appendStyledText appends styled text to the cell buffer document.
func (c *cellbuf) appendStyledText(str string, fg, bg termbox.Attribute) {
	if c.col < c.lmargin {
		c.col = c.lmargin
	}
	scanner := bufio.NewScanner(strings.NewReader(str))
	scanner.Split(scanWords)
	for scanner.Scan() {
		word := []rune(scanner.Text())
		if len(word) > c.width-c.col {
			c.row++
			c.col = c.lmargin
		}
		for _, r := range word {
			if r == '\n' {
				c.row++
				c.col = c.lmargin
				continue
			}
			c.setCell(c.col, c.row, r, fg, bg)
			c.col++
		}
		if c.col < c.width {
			c.col++
		}
	}
}

// appendText appends unstyled text to the cell buffer document.
func (c *cellbuf) appendText(str string) {
	c.appendStyledText(str, termbox.ColorDefault, termbox.ColorDefault)
}

// parseText takes in html content via an io.Reader and returns a buffer
// containing only plain text.
func parseText(r io.Reader, items []epub.Item) (cellbuf, error) {
	tokenizer := html.NewTokenizer(r)
	doc := cellbuf{width: 80}
	p := parser{tokenizer: tokenizer, doc: doc, items: items}
	err := p.parse(r)
	if err != nil {
		return p.doc, err
	}
	return p.doc, nil
}

// parse walks an html document and renders elements to a cell buffer document.
func (p *parser) parse(io.Reader) (err error) {
	for {
		switch p.tokenizer.Next() {
		case html.ErrorToken:
			err = p.tokenizer.Err()
		case html.StartTagToken:
			p.elStack = append(p.elStack, p.tokenizer.Token().Data) // push element
			fallthrough
		case html.SelfClosingTagToken:
			p.handleTags()
		case html.TextToken:
			p.handleText()
		case html.EndTagToken:
			p.elStack = p.elStack[:len(p.elStack)-1] // pop element
		}
		if err == io.EOF {
			return nil
		} else if err != nil {
			return err
		}
	}
}

// handleText appends text elements to the parser buffer. It filters elements
// that should not be displayed as text (e.g. style blocks).
func (p *parser) handleText() {
	// Skip style tags
	if len(p.elStack) > 0 && p.elStack[len(p.elStack)-1] == "style" {
		return
	}
	p.doc.appendText(string(p.tokenizer.Text()))
}

// handleTags appends text representations of non-text elements (e.g. image alt
// tags) to the parser buffer.
func (p *parser) handleTags() {
	token := p.tokenizer.Token()
	switch token.Data {
	case "img":
		// Display alt text in place of images.
		for _, a := range token.Attr {
			switch a.Key {
			case "alt":
				text := fmt.Sprintf("Alt text: %s\n", a.Val)
				p.doc.appendText(text)
			case "src":
				for _, item := range p.items {
					if item.HREF == a.Val {
						p.doc.appendText(imageToText(item))
						break
					}
				}
			}
		}
	case "br":
		p.doc.appendText("\n")
	}
}

func imageToText(item epub.Item) string {
	r, err := item.Open()
	if err != nil {
		return ""
	}

	img, _, err := image.Decode(r)
	if err != nil {
		return ""
	}
	bounds := img.Bounds()

	// Assume a character height to width ratio of 2:1.
	w := 80
	h := (bounds.Max.Y * w) / (bounds.Max.X * 2)
	img = resize.Resize(uint(w), uint(h), img, resize.Lanczos3)

	charGradient := []rune("MND8OZ$7I?+=~:,..")
	buf := new(bytes.Buffer)

	for y := 0; y < h; y++ {
		for x := 0; x < w; x++ {
			c := color.GrayModel.Convert(img.At(x, y))
			y := c.(color.Gray).Y
			pos := (len(charGradient) - 1) * int(y) / 255
			buf.WriteRune(charGradient[pos])
		}
		buf.WriteRune('\n')
	}

	return buf.String()
}
<commit_msg>Don't add a space if we're at the beginning of a line<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"image"
	"image/color"
	"io"
	"strings"
	"unicode/utf8"

	_ "image/jpeg"
	_ "image/png"

	"github.com/nfnt/resize"
	termbox "github.com/nsf/termbox-go"
	"github.com/taylorskalyo/goreader/epub"

	"golang.org/x/net/html"
)

type parser struct {
	elStack   []string
	tokenizer *html.Tokenizer
	doc       cellbuf
	items     []epub.Item
}

type cellbuf struct {
	cells   []termbox.Cell
	width   int
	lmargin int
	col     int
	row     int
}

// setCell changes a cell's attributes in the cell buffer document at the given
// position.
func (c *cellbuf) setCell(x, y int, ch rune, fg, bg termbox.Attribute) {
	// Grow in steps of 1024 when out of space.
	for y*c.width+x >= len(c.cells) {
		c.cells = append(c.cells, make([]termbox.Cell, 1024)...)
	}
	c.cells[y*c.width+x] = termbox.Cell{Ch: ch, Fg: fg, Bg: bg}
}

// scanWords is a split function for a Scanner that returns space-separated
// words. Unlike bufio.ScanWords(), scanWords only splits on spaces (i.e. not
// newlines, tabs, or other whitespace).
func scanWords(data []byte, atEOF bool) (advance int, token []byte, err error) {
	// Skip leading spaces.
	start := 0
	for width := 0; start < len(data); start += width {
		var r rune
		r, width = utf8.DecodeRune(data[start:])
		if r != ' ' {
			break
		}
	}

	// Scan until space, marking end of word.
	for width, i := 0, start; i < len(data); i += width {
		var r rune
		r, width = utf8.DecodeRune(data[i:])
		if r == ' ' {
			return i + width, data[start:i], nil
		}
	}

	// If we're at EOF, we have a final, non-empty, non-terminated word. Return
	// it.
	if atEOF && len(data) > start {
		return len(data), data[start:], nil
	}

	// Request more data.
	return start, nil, nil
}

// appendStyledText appends styled text to the cell buffer document.
func (c *cellbuf) appendStyledText(str string, fg, bg termbox.Attribute) {
	if c.col < c.lmargin {
		c.col = c.lmargin
	}
	scanner := bufio.NewScanner(strings.NewReader(str))
	scanner.Split(scanWords)
	for scanner.Scan() {
		word := []rune(scanner.Text())
		if len(word) > c.width-c.col {
			c.row++
			c.col = c.lmargin
		}
		for _, r := range word {
			if r == '\n' {
				c.row++
				c.col = c.lmargin
				continue
			}
			c.setCell(c.col, c.row, r, fg, bg)
			c.col++
		}
		if c.col != c.lmargin {
			c.col++
		}
	}
}

// appendText appends unstyled text to the cell buffer document.
func (c *cellbuf) appendText(str string) {
	c.appendStyledText(str, termbox.ColorDefault, termbox.ColorDefault)
}

// parseText takes in html content via an io.Reader and returns a buffer
// containing only plain text.
func parseText(r io.Reader, items []epub.Item) (cellbuf, error) {
	tokenizer := html.NewTokenizer(r)
	doc := cellbuf{width: 80}
	p := parser{tokenizer: tokenizer, doc: doc, items: items}
	err := p.parse(r)
	if err != nil {
		return p.doc, err
	}
	return p.doc, nil
}

// parse walks an html document and renders elements to a cell buffer document.
func (p *parser) parse(io.Reader) (err error) {
	for {
		switch p.tokenizer.Next() {
		case html.ErrorToken:
			err = p.tokenizer.Err()
		case html.StartTagToken:
			p.elStack = append(p.elStack, p.tokenizer.Token().Data) // push element
			fallthrough
		case html.SelfClosingTagToken:
			p.handleTags()
		case html.TextToken:
			p.handleText()
		case html.EndTagToken:
			p.elStack = p.elStack[:len(p.elStack)-1] // pop element
		}
		if err == io.EOF {
			return nil
		} else if err != nil {
			return err
		}
	}
}

// handleText appends text elements to the parser buffer. It filters elements
// that should not be displayed as text (e.g. style blocks).
func (p *parser) handleText() {
	// Skip style tags
	if len(p.elStack) > 0 && p.elStack[len(p.elStack)-1] == "style" {
		return
	}
	p.doc.appendText(string(p.tokenizer.Text()))
}

// handleTags appends text representations of non-text elements (e.g. image alt
// tags) to the parser buffer.
func (p *parser) handleTags() {
	token := p.tokenizer.Token()
	switch token.Data {
	case "img":
		// Display alt text in place of images.
		for _, a := range token.Attr {
			switch a.Key {
			case "alt":
				text := fmt.Sprintf("Alt text: %s\n", a.Val)
				p.doc.appendText(text)
			case "src":
				for _, item := range p.items {
					if item.HREF == a.Val {
						p.doc.appendText(imageToText(item))
						break
					}
				}
			}
		}
	case "br":
		p.doc.appendText("\n")
	}
}

func imageToText(item epub.Item) string {
	r, err := item.Open()
	if err != nil {
		return ""
	}

	img, _, err := image.Decode(r)
	if err != nil {
		return ""
	}
	bounds := img.Bounds()

	// Assume a character height to width ratio of 2:1.
	w := 80
	h := (bounds.Max.Y * w) / (bounds.Max.X * 2)
	img = resize.Resize(uint(w), uint(h), img, resize.Lanczos3)

	charGradient := []rune("MND8OZ$7I?+=~:,..")
	buf := new(bytes.Buffer)

	for y := 0; y < h; y++ {
		for x := 0; x < w; x++ {
			c := color.GrayModel.Convert(img.At(x, y))
			y := c.(color.Gray).Y
			pos := (len(charGradient) - 1) * int(y) / 255
			buf.WriteRune(charGradient[pos])
		}
		buf.WriteRune('\n')
	}

	return buf.String()
}
<|endoftext|>


<commit_before>package main

import (
  "os"
  "fmt"
  "io"
  "bufio"
  "strings"
  "strconv"
  "path"
  "path/filepath"
  "errors"
  "bytes"
  "go/token"
  "go/parser"
  "go/printer"
  "golang.org/x/tools/go/ast/astutil"
)

var verbose bool = false
var log *os.File = os.Stderr

type Section struct {
  Kind uint
  Text string
}
const (
  StaticSection uint = iota
  CodeSection
)

var sections []*Section

//--- Linear pattern matcher

type Pattern struct {
  Text []rune
  Length, Pos int
}

func newPattern(s string) Pattern {
  runes := []rune(s)
  return Pattern{ Text: runes, Length: len(runes) }
}

// Next returns true if Pos advances past the last character of Text.
func (pattern *Pattern) Next(ch rune) bool {
  // If Pos is past the end of Text, reset it to the beginning.
  if pattern.Pos == pattern.Length {
    pattern.Pos = 0
  }
  // Try to match the current rune in Text.
  if ch == pattern.Text[pattern.Pos] {
    pattern.Pos++
  }
  // Check for a complete match.
  return pattern.Pos == pattern.Length
}

//--- Template parsing and output generation

type TemplateEntry struct {
  SitePath, HardPath string
  FileInfo os.FileInfo
  InsertionLine int
}

func (entry TemplateEntry) String() string {
  if entry.InsertionLine == 0 {
    return entry.SitePath
  }
  return fmt.Sprintf("-> line %d: %s", entry.InsertionLine, entry.SitePath)
}

func makeTemplateEntry(siteRoot, startDir, sitePath string,
    insertionLine int) (*TemplateEntry, error) {
  hardPath := makeHardPath(siteRoot, startDir, sitePath)
  fileInfo, error := os.Stat(hardPath)
  if error != nil {
    return nil, error
  }
  entry := TemplateEntry{
      SitePath: sitePath,
      HardPath: hardPath,
      FileInfo: fileInfo,
      InsertionLine: insertionLine,
    }
  return &entry, nil
}

func makeHardPath(siteRoot, startDir, sitePath string) string {
  // A hard path names a location in the physical file system rather than
  //  in the website's directory structure. It is either an absolute path
  //  or a relative path with respect to the directory containing the
  //  top-level template that is being parsed.
  var dir string
  if filepath.IsAbs(sitePath) {
    dir = siteRoot
  } else {
    dir = startDir
  }
  // Note that filepath.Join automatically performs filepath.Clean, thus
  //  returning a lexically unique form of the path. However, the path
  //  does not uniquely identify a file if it includes a symbolic link.
  //  Therefore, we cannot rely on string comparison to prevent cycles.
  hardPath := filepath.Join(dir, sitePath)
  return hardPath
}

func parse(templatePath string) error {

  // We resolve absolute paths with the website root.
  siteRoot := "/var/www/dd1"  // Stub. We'll get the real value from Apache.
  // We resolve relative paths using the starting directory.
  startDir := filepath.Dir(templatePath)

  // Make an insertion stack with a top-level entry.
  entry, error := makeTemplateEntry(siteRoot, startDir, templatePath, 0)
  if error != nil {
    return error
  }
  stack := []*TemplateEntry{ entry }
  return doParse(siteRoot, startDir, stack)
}

func doParse(siteRoot, startDir string, stack []*TemplateEntry) error {
  current := stack[len(stack)-1]
  if verbose {
    fmt.Fprintf(log, "// start \"%s\"\n", current.SitePath)
  }
  var topLevel bool
  if len(stack) == 1 {
    topLevel = true
  }

  // Check for an insertion cycle.
  for i := len(stack)-2; i >= 0; i-- {
    ancestor := stack[i]
    if os.SameFile(ancestor.FileInfo, current.FileInfo) {
      lines := []string{ "doParse: insertion cycle" }
      for j := i; j < len(stack); j++ {
        lines = append(lines, stack[j].String())
      }
      message := fmt.Sprintf(strings.Join(lines, "\n  "))
      return errors.New(message)
    }
  }

  var error error
  var file *os.File

  file, error = os.Open(current.HardPath)
  if error != nil {
    return error
  }

  reader := bufio.NewReader(file)
  writer := bufio.NewWriter(os.Stdout)
  defer writer.Flush()

  codePattern := newPattern("<?code")
  insertPattern := newPattern("<?insert")
  openPatterns := []*Pattern{ &codePattern, &insertPattern }
  var open *Pattern
  close := newPattern("?>")

  var buffer []rune
  var ch rune
  var size int
  countBytes, countRunes := 0, 0
  lineIndex := 1
  prefix := true

  for {
    ch, size, error = reader.ReadRune()
    if error == nil {
      buffer = append(buffer, ch)
      countBytes += size
      countRunes += 1
      if ch == '\n' {
        lineIndex += 1
      }
    } else {
      content := string(buffer)
      if topLevel {
        content = strings.TrimSpace(content)
      }
      emitStatic(content)
      break
    }

    if open == nil {
      for _, pattern := range openPatterns {
        if pattern.Next(ch) {
          open = pattern
          content := string(buffer[0:len(buffer)-open.Length])
          if prefix {
            if topLevel {
              content = strings.TrimSpace(content)
            }
            prefix = false
          }
          emitStatic(content)
          buffer = []rune{}
        }
      }
    } else {
      if close.Next(ch) {
        content := buffer[0:len(buffer)-close.Length]
        if open == &codePattern {
          emitCode(string(content))
        } else if open == &insertPattern {
          childPath := strings.TrimSpace(string(content))
          entry, error := makeTemplateEntry(siteRoot, startDir, childPath,
              lineIndex)
          if error != nil {
            return error
          }
          stack = append(stack, entry)
          error = doParse(siteRoot, startDir, stack)
          if error != nil {
            return error
          }
          stack = stack[0:len(stack)-1]
        }
        open = nil
        buffer = []rune{}
      }
    }
  }
  if verbose {
    fmt.Fprintf(log, "// finish \"%s\"\n", current.SitePath)
    fmt.Fprintf(log, "// read %d bytes, %d runes\n", countBytes, countRunes)
    fmt.Fprintf(log, "// finished on line %d\n", lineIndex)
  }
  if error == io.EOF {
    return nil
  }
  return error
}

func emitCode(content string) {
  sections = append(sections, &Section{ Kind: CodeSection, Text: content })
}

func emitStatic(content string) {
  if len(content) == 0 {
    return
  }
  from := 0
  for pos, ch := range content {
    if ch == '`' {
      if pos != from {
        raw := fmt.Sprintf("`%s`", content[from:pos])
        emitStaticChunk(raw)
      }
      emitStaticChunk("'`'")
      from = pos+1
    }
  }
  if from != len(content) {
    raw := fmt.Sprintf("`%s`", content[from:len(content)])
    emitStaticChunk(raw)
  }
}
func emitStaticChunk(chunk string) {
  sections = append(sections, &Section{ Kind: StaticSection, Text: chunk })
}

func main() {
  writer := bufio.NewWriter(os.Stdout)
  defer writer.Flush()

  numFiles := len(os.Args)-1
  if numFiles == 0 {
    writer.WriteString("No files specified.\n")
    return
  }
  for argIx := 1; argIx <= numFiles; argIx++ {
    // Parse the top-level template. The resulting source code goes into
    //  the global variable sections.
    templatePath := os.Args[argIx]

    // We parse the template to obtain code sections and static sections.
    error := parse(templatePath)
    if error != nil {
      writer.WriteString(fmt.Sprintf("Template parsing error: %s\n", error))
      continue
    }

    // Concatenate only the code sections.
    output := bytes.Buffer{}
    for _, section := range sections {
      if section.Kind == CodeSection {
        fmt.Fprintf(&output, section.Text)
      }
    }
    fileSet := token.NewFileSet()
    fileNode, error := parser.ParseFile(fileSet, "output", output.Bytes(),
        parser.ParseComments)
    if error != nil {
      writer.Write(output.Bytes())
      writer.WriteString(fmt.Sprintf(
          "\n---\nError parsing code sections: %s\n", error))
      continue
    }

    seekPath := "fmt"
    seekName := path.Base(seekPath)
    printCall := "Print"

    //  Has the package been imported? Is the name available?
    isImported := false
    var importedAs string          // use this if the path has been imported
    seenName := map[string]bool{}  // consult this if we have to import

    for _, importSpec := range fileNode.Imports {
      importPath, _ := strconv.Unquote(importSpec.Path.Value)
      var importName string
      if importSpec.Name == nil {
        importName = path.Base(importPath)
      } else {
        importName = importSpec.Name.Name
      }
      seenName[importName] = true
      if !isImported && importPath == seekPath && importName != "_" {
        isImported = true
        importedAs = importName
      }
    }

    var importAs, printPrefix string  // NB: these are "" by default
    if isImported {
      if importedAs != "." {  // no prefix is needed with a dot import
        printPrefix = importedAs+"."
      }
    } else {
      if !seenName[seekName] {
        importAs = seekName
      } else {
        for i := 0; ; i++ {
          importAs = fmt.Sprintf("%s_%d", seekName, i)
          _, found := seenName[importAs]
          if !found {
            break
          }
        }
      }
      printPrefix = importAs+"."
    }

    // Concatenate the code sections and static sections.
    output.Reset()
    for _, section := range sections {
      if section.Kind == CodeSection {
        fmt.Fprintf(&output, section.Text)
      } else {
        s := fmt.Sprintf(";%s%s(%s);", printPrefix, printCall, section.Text)
        fmt.Fprintf(&output, s)
      }
    }
    // Have Go parse the entire template output.
    fileSet = token.NewFileSet()
    fileNode, error = parser.ParseFile(fileSet, "output", output.Bytes(),
        parser.ParseComments)
    if error != nil {
      writer.Write(output.Bytes())
      writer.WriteString(fmt.Sprintf(
          "\n---\nError parsing entire template output: %s\n", error))
      continue
    }
    // Finally, inject an import statement if necessary.
    if !isImported {
      if importAs == seekName {
        astutil.AddImport(fileSet, fileNode, seekPath)
      } else {
        astutil.AddNamedImport(fileSet, fileNode, importAs, seekPath)
      }
    }

    // Print with a custom configuration: soft tabs of two spaces each.
    config := printer.Config{ Mode: printer.UseSpaces, Tabwidth: 2 }
    (&config).Fprint(writer, fileSet, fileNode)
  }
}
<commit_msg>Factor out a template processing function<commit_after>/*
The executable "parse" takes one or more file names as arguments and
calls the template processing function, processTemplate, on each one.
*/

package main

import (
  "os"
  "fmt"
  "io"
  "bufio"
  "strings"
  "strconv"
  "path"
  "path/filepath"
  "errors"
  "bytes"
  "go/token"
  "go/parser"
  "go/printer"
  "golang.org/x/tools/go/ast/astutil"
)

var verbose bool = false
var log *os.File = os.Stderr

type Section struct {
  Kind uint
  Text string
}
const (
  StaticSection uint = iota
  CodeSection
)

var sections []*Section

//--- Linear pattern matcher

type Pattern struct {
  Text []rune
  Length, Pos int
}

func newPattern(s string) Pattern {
  runes := []rune(s)
  return Pattern{ Text: runes, Length: len(runes) }
}

// Next returns true if Pos advances past the last character of Text.
func (pattern *Pattern) Next(ch rune) bool {
  // If Pos is past the end of Text, reset it to the beginning.
  if pattern.Pos == pattern.Length {
    pattern.Pos = 0
  }
  // Try to match the current rune in Text.
  if ch == pattern.Text[pattern.Pos] {
    pattern.Pos++
  }
  // Check for a complete match.
  return pattern.Pos == pattern.Length
}

//--- Template parsing and output generation

type TemplateEntry struct {
  SitePath, HardPath string
  FileInfo os.FileInfo
  InsertionLine int
}

func (entry TemplateEntry) String() string {
  if entry.InsertionLine == 0 {
    return entry.SitePath
  }
  return fmt.Sprintf("-> line %d: %s", entry.InsertionLine, entry.SitePath)
}

func makeTemplateEntry(siteRoot, startDir, sitePath string,
    insertionLine int) (*TemplateEntry, error) {
  hardPath := makeHardPath(siteRoot, startDir, sitePath)
  fileInfo, error := os.Stat(hardPath)
  if error != nil {
    return nil, error
  }
  entry := TemplateEntry{
      SitePath: sitePath,
      HardPath: hardPath,
      FileInfo: fileInfo,
      InsertionLine: insertionLine,
    }
  return &entry, nil
}

func makeHardPath(siteRoot, startDir, sitePath string) string {
  // A hard path names a location in the physical file system rather than
  //  in the website's directory structure. It is either an absolute path
  //  or a relative path with respect to the directory containing the
  //  top-level template that is being parsed.
  var dir string
  if filepath.IsAbs(sitePath) {
    dir = siteRoot
  } else {
    dir = startDir
  }
  // Note that filepath.Join automatically performs filepath.Clean, thus
  //  returning a lexically unique form of the path. However, the path
  //  does not uniquely identify a file if it includes a symbolic link.
  //  Therefore, we cannot rely on string comparison to prevent cycles.
  hardPath := filepath.Join(dir, sitePath)
  return hardPath
}

func parse(templatePath string) error {
  sections = []*Section{}

  // We resolve absolute paths with the website root.
  siteRoot := "/var/www/dd1"  // Stub. We'll get the real value from Apache.
  // We resolve relative paths using the starting directory.
  startDir := filepath.Dir(templatePath)
  entryPoint := filepath.Base(templatePath)

  // Make an insertion stack with a top-level entry.
  entry, error := makeTemplateEntry(siteRoot, startDir, entryPoint, 0)
  if error != nil {
    return error
  }
  stack := []*TemplateEntry{ entry }
  return doParse(siteRoot, startDir, stack)
}

func doParse(siteRoot, startDir string, stack []*TemplateEntry) error {
  current := stack[len(stack)-1]
  if verbose {
    fmt.Fprintf(log, "// start \"%s\"\n", current.SitePath)
  }
  var topLevel bool
  if len(stack) == 1 {
    topLevel = true
  }

  // Check for an insertion cycle.
  for i := len(stack)-2; i >= 0; i-- {
    ancestor := stack[i]
    if os.SameFile(ancestor.FileInfo, current.FileInfo) {
      lines := []string{ "doParse: insertion cycle" }
      for j := i; j < len(stack); j++ {
        lines = append(lines, stack[j].String())
      }
      message := fmt.Sprintf(strings.Join(lines, "\n  "))
      return errors.New(message)
    }
  }

  var error error
  var file *os.File

  file, error = os.Open(current.HardPath)
  if error != nil {
    return error
  }

  reader := bufio.NewReader(file)
  writer := bufio.NewWriter(os.Stdout)
  defer writer.Flush()

  codePattern := newPattern("<?code")
  insertPattern := newPattern("<?insert")
  openPatterns := []*Pattern{ &codePattern, &insertPattern }
  var open *Pattern
  close := newPattern("?>")

  var buffer []rune
  var ch rune
  var size int
  countBytes, countRunes := 0, 0
  lineIndex := 1
  prefix := true

  for {
    ch, size, error = reader.ReadRune()
    if error == nil {
      buffer = append(buffer, ch)
      countBytes += size
      countRunes += 1
      if ch == '\n' {
        lineIndex += 1
      }
    } else {
      content := string(buffer)
      if topLevel {
        content = strings.TrimSpace(content)
      }
      emitStatic(content)
      break
    }

    if open == nil {
      for _, pattern := range openPatterns {
        if pattern.Next(ch) {
          open = pattern
          content := string(buffer[0:len(buffer)-open.Length])
          if prefix {
            if topLevel {
              content = strings.TrimSpace(content)
            }
            prefix = false
          }
          emitStatic(content)
          buffer = []rune{}
        }
      }
    } else {
      if close.Next(ch) {
        content := buffer[0:len(buffer)-close.Length]
        if open == &codePattern {
          emitCode(string(content))
        } else if open == &insertPattern {
          childPath := strings.TrimSpace(string(content))
          entry, error := makeTemplateEntry(siteRoot, startDir, childPath,
              lineIndex)
          if error != nil {
            return error
          }
          stack = append(stack, entry)
          error = doParse(siteRoot, startDir, stack)
          if error != nil {
            return error
          }
          stack = stack[0:len(stack)-1]
        }
        open = nil
        buffer = []rune{}
      }
    }
  }
  if verbose {
    fmt.Fprintf(log, "// finish \"%s\"\n", current.SitePath)
    fmt.Fprintf(log, "// read %d bytes, %d runes\n", countBytes, countRunes)
    fmt.Fprintf(log, "// finished on line %d\n", lineIndex)
  }
  if error == io.EOF {
    return nil
  }
  return error
}

func emitCode(content string) {
  sections = append(sections, &Section{ Kind: CodeSection, Text: content })
}

func emitStatic(content string) {
  if len(content) == 0 {
    return
  }
  from := 0
  for pos, ch := range content {
    if ch == '`' {
      if pos != from {
        raw := fmt.Sprintf("`%s`", content[from:pos])
        emitStaticChunk(raw)
      }
      emitStaticChunk("'`'")
      from = pos+1
    }
  }
  if from != len(content) {
    raw := fmt.Sprintf("`%s`", content[from:len(content)])
    emitStaticChunk(raw)
  }
}
func emitStaticChunk(chunk string) {
  sections = append(sections, &Section{ Kind: StaticSection, Text: chunk })
}

func processTemplate(templatePath string, writer *bufio.Writer) {
  // We parse the template to obtain code sections and static sections.
  error := parse(templatePath)
  if error != nil {
    writer.WriteString(fmt.Sprintf("Template parsing error: %s\n", error))
    return
  }

  // Concatenate only the code sections.
  output := bytes.Buffer{}
  for _, section := range sections {
    if section.Kind == CodeSection {
      fmt.Fprintf(&output, section.Text)
    }
  }
  fileSet := token.NewFileSet()
  fileNode, error := parser.ParseFile(fileSet, "output", output.Bytes(),
      parser.ParseComments)
  if error != nil {
    writer.Write(output.Bytes())
    writer.WriteString(fmt.Sprintf(
        "\n---\nError parsing code sections: %s\n", error))
    return
  }

  seekPath := "fmt"
  seekName := path.Base(seekPath)
  printCall := "Print"

  //  Has the package been imported? Is the name available?
  isImported := false
  var importedAs string          // use this if the path has been imported
  seenName := map[string]bool{}  // consult this if we have to import

  for _, importSpec := range fileNode.Imports {
    importPath, _ := strconv.Unquote(importSpec.Path.Value)
    var importName string
    if importSpec.Name == nil {
      importName = path.Base(importPath)
    } else {
      importName = importSpec.Name.Name
    }
    seenName[importName] = true
    if !isImported && importPath == seekPath && importName != "_" {
      isImported = true
      importedAs = importName
    }
  }

  var importAs, printPrefix string  // NB: these are "" by default
  if isImported {
    if importedAs != "." {  // no prefix is needed with a dot import
      printPrefix = importedAs+"."
    }
  } else {
    if !seenName[seekName] {
      importAs = seekName
    } else {
      for i := 0; ; i++ {
        importAs = fmt.Sprintf("%s_%d", seekName, i)
        _, found := seenName[importAs]
        if !found {
          break
        }
      }
    }
    printPrefix = importAs+"."
  }

  // Concatenate the code sections and static sections.
  output.Reset()
  for _, section := range sections {
    if section.Kind == CodeSection {
      fmt.Fprintf(&output, section.Text)
    } else {
      s := fmt.Sprintf(";%s%s(%s);", printPrefix, printCall, section.Text)
      fmt.Fprintf(&output, s)
    }
  }
  // Have Go parse the entire template output.
  fileSet = token.NewFileSet()
  fileNode, error = parser.ParseFile(fileSet, "output", output.Bytes(),
      parser.ParseComments)
  if error != nil {
    writer.Write(output.Bytes())
    writer.WriteString(fmt.Sprintf(
        "\n---\nError parsing entire template output: %s\n", error))
    return
  }
  // Finally, inject an import statement if necessary.
  if !isImported {
    if importAs == seekName {
      astutil.AddImport(fileSet, fileNode, seekPath)
    } else {
      astutil.AddNamedImport(fileSet, fileNode, importAs, seekPath)
    }
  }

  // Print with a custom configuration: soft tabs of two spaces each.
  config := printer.Config{ Mode: printer.UseSpaces, Tabwidth: 2 }
  (&config).Fprint(writer, fileSet, fileNode)
}

func main() {
  writer := bufio.NewWriter(os.Stdout)
  defer writer.Flush()

  numFiles := len(os.Args)-1
  if numFiles == 0 {
    writer.WriteString("No files specified.\n")
    return
  }
  for argIx := 1; argIx <= numFiles; argIx++ {
    // Parse a top-level template.
    processTemplate(os.Args[argIx], writer)
  }
}
<|endoftext|>