<commit_before>package lint

import (
	"bytes"
	"os/exec"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/ValeLint/vale/core"
	"github.com/russross/blackfriday"
	"golang.org/x/net/html"
	"matloob.io/regexp"
)

// reStructuredText configuration.
//
// reCodeBlock is used to convert Sphinx-style code directives to the regular
// `::` for rst2html.
var reCodeBlock = regexp.MustCompile(`.. (?:raw|code(?:-block)?):: (\w+)`)
var rstArgs = []string{
	"--quiet",
	"--halt=5",
	"--report=5",
	"--link-stylesheet",
	"--no-file-insertion",
	"--no-toc-backlinks",
	"--no-footnote-backlinks",
	"--no-section-numbering",
}

// AsciiDoc configuration.
var adocArgs = []string{
	"-s",
	"--quiet",
	"--safe-mode",
	"secure",
	"-",
}

// Blackfriday configuration.
var commonHTMLFlags = 0 | blackfriday.HTML_USE_XHTML
var commonExtensions = 0 |
	blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
	blackfriday.EXTENSION_TABLES |
	blackfriday.EXTENSION_FENCED_CODE
var renderer = blackfriday.HtmlRenderer(commonHTMLFlags, "", "")
var options = blackfriday.Options{Extensions: commonExtensions}

// HTML configuration.
var heading = regexp.MustCompile(`^h\d$`)

// skipTags are tags that we don't want to lint.
var skipTags = []string{"script", "style", "pre", "figure", "code", "tt"}
var inlineTags = []string{
	"b", "big", "i", "small", "abbr", "acronym", "cite", "dfn", "em", "kbd",
	"strong", "a", "br", "img", "span", "sub", "sup"}
var tagToScope = map[string]string{
	"th": "text.table.heading",
	"tr": "text.table.cell",
	"li": "text.list",
}

func (l Linter) lintHTMLTokens(f *core.File, ctx string, fsrc []byte, offset int) {
	var txt string
	var tokt html.TokenType
	var tok html.Token
	var inBlock, skip, inline bool

	lines := len(f.Lines) + offset
	buf := bytes.NewBufferString("")
	queue := []string{}

	tokens := html.NewTokenizer(bytes.NewReader(fsrc))
	for {
		tokt = tokens.Next()
		tok = tokens.Token()
		txt = html.UnescapeString(strings.TrimSpace(tok.Data))
		skip = core.StringInSlice(txt, skipTags)

		if tokt == html.ErrorToken {
			break
		} else if tokt == html.StartTagToken && skip {
			inBlock = true
		} else if skip && inBlock {
			inBlock = false
		} else if tokt == html.StartTagToken {
			inline = core.StringInSlice(txt, inlineTags)
		} else if tokt == html.CommentToken {
			f.UpdateComments(txt)
		} else if tokt == html.TextToken {
			queue = append(queue, txt)
			if !inBlock {
				first, _ := utf8.DecodeRuneInString(txt)
				// Only add a space for non-punctuation. NOTE: we may want to
				// use JaroWinkler here (instead of later), but we'll need to
				// store position information to avoid duplicating work.
				if inline && !unicode.IsPunct(first) {
					txt = " " + txt
				}
				buf.WriteString(txt)
			}
		}

		if tokt == html.EndTagToken && !core.StringInSlice(txt, inlineTags) {
			content := buf.String()
			if content != "" {
				l.lintScope(f, ctx, content, txt, lines)
			}
			for _, s := range queue {
				ctx = updateCtx(ctx, s, html.TextToken)
			}
			queue = []string{}
			buf.Reset()
		}

		ctx = clearElements(ctx, tok)
	}
}

func (l Linter) lintScope(f *core.File, ctx, txt, tag string, lines int) {
	if heading.MatchString(tag) {
		l.lintText(f, NewBlock(ctx, txt, "text.heading"+f.RealExt), lines, 0)
	} else if scope, match := tagToScope[tag]; match {
		l.lintText(f, NewBlock(ctx, txt, scope+f.RealExt), lines, 0)
	} else {
		l.lintProse(f, ctx, txt, lines, 0)
	}
}

func clearElements(ctx string, tok html.Token) string {
	if tok.Data == "img" || tok.Data == "a" {
		for _, a := range tok.Attr {
			if a.Key == "alt" || a.Key == "href" {
				ctx = updateCtx(ctx, a.Val, html.TextToken)
			}
		}
	}
	return ctx
}

func updateCtx(ctx string, txt string, tokt html.TokenType) string {
	var found bool
	if (tokt == html.TextToken || tokt == html.CommentToken) && txt != "" {
		for _, s := range strings.Split(txt, "\n") {
			ctx, found = core.Substitute(ctx, s)
			if !found {
				for _, w := range strings.Fields(s) {
					ctx, _ = core.Substitute(ctx, w)
				}
			}
		}
	}
	return ctx
}

func (l Linter) lintHTML(f *core.File) {
	l.lintHTMLTokens(f, f.Content, []byte(f.Content), 0)
}

func (l Linter) lintMarkdown(f *core.File) {
	html := blackfriday.MarkdownOptions([]byte(f.Content), renderer, options)
	l.lintHTMLTokens(f, f.Content, html, 0)
}

func (l Linter) lintRST(f *core.File, python string, rst2html string) {
	var out bytes.Buffer
	cmd := exec.Command(python, append([]string{rst2html}, rstArgs...)...)
	cmd.Stdin = strings.NewReader(reCodeBlock.ReplaceAllString(f.Content, "::"))
	cmd.Stdout = &out
	if core.CheckError(cmd.Run()) {
		l.lintHTMLTokens(f, f.Content, out.Bytes(), 0)
	}
}

func (l Linter) lintADoc(f *core.File, asciidoctor string) {
	var out bytes.Buffer
	cmd := exec.Command(asciidoctor, adocArgs...)
	cmd.Stdin = strings.NewReader(f.Content)
	cmd.Stdout = &out
	if core.CheckError(cmd.Run()) {
		l.lintHTMLTokens(f, f.Content, out.Bytes(), 0)
	}
}
<commit_msg>refactor: don't check skipTags on every iteration<commit_after>package lint

import (
	"bytes"
	"os/exec"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/ValeLint/vale/core"
	"github.com/russross/blackfriday"
	"golang.org/x/net/html"
	"matloob.io/regexp"
)

// reStructuredText configuration.
//
// reCodeBlock is used to convert Sphinx-style code directives to the regular
// `::` for rst2html.
var reCodeBlock = regexp.MustCompile(`.. (?:raw|code(?:-block)?):: (\w+)`)
var rstArgs = []string{
	"--quiet",
	"--halt=5",
	"--report=5",
	"--link-stylesheet",
	"--no-file-insertion",
	"--no-toc-backlinks",
	"--no-footnote-backlinks",
	"--no-section-numbering",
}

// AsciiDoc configuration.
var adocArgs = []string{
	"-s",
	"--quiet",
	"--safe-mode",
	"secure",
	"-",
}

// Blackfriday configuration.
var commonHTMLFlags = 0 | blackfriday.HTML_USE_XHTML
var commonExtensions = 0 |
	blackfriday.EXTENSION_NO_INTRA_EMPHASIS |
	blackfriday.EXTENSION_TABLES |
	blackfriday.EXTENSION_FENCED_CODE
var renderer = blackfriday.HtmlRenderer(commonHTMLFlags, "", "")
var options = blackfriday.Options{Extensions: commonExtensions}

// HTML configuration.
var heading = regexp.MustCompile(`^h\d$`)

// skipTags are tags that we don't want to lint.
var skipTags = []string{"script", "style", "pre", "figure", "code", "tt"}
var inlineTags = []string{
	"b", "big", "i", "small", "abbr", "acronym", "cite", "dfn", "em", "kbd",
	"strong", "a", "br", "img", "span", "sub", "sup"}
var tagToScope = map[string]string{
	"th": "text.table.heading",
	"tr": "text.table.cell",
	"li": "text.list",
}

func (l Linter) lintHTMLTokens(f *core.File, ctx string, fsrc []byte, offset int) {
	var txt string
	var tokt html.TokenType
	var tok html.Token
	var inBlock, inline bool

	lines := len(f.Lines) + offset
	buf := bytes.NewBufferString("")
	queue := []string{}

	tokens := html.NewTokenizer(bytes.NewReader(fsrc))
	for {
		tokt = tokens.Next()
		tok = tokens.Token()
		txt = html.UnescapeString(strings.TrimSpace(tok.Data))

		if tokt == html.ErrorToken {
			break
		} else if tokt == html.StartTagToken && core.StringInSlice(txt, skipTags) {
			inBlock = true
		} else if inBlock && core.StringInSlice(txt, skipTags) {
			inBlock = false
		} else if tokt == html.StartTagToken {
			inline = core.StringInSlice(txt, inlineTags)
		} else if tokt == html.CommentToken {
			f.UpdateComments(txt)
		} else if tokt == html.TextToken {
			queue = append(queue, txt)
			if !inBlock {
				first, _ := utf8.DecodeRuneInString(txt)
				// Only add a space for non-punctuation. NOTE: we may want to
				// use JaroWinkler here (instead of later), but we'll need to
				// store position information to avoid duplicating work.
				if inline && !unicode.IsPunct(first) {
					txt = " " + txt
				}
				buf.WriteString(txt)
			}
		}

		if tokt == html.EndTagToken && !core.StringInSlice(txt, inlineTags) {
			content := buf.String()
			if content != "" {
				l.lintScope(f, ctx, content, txt, lines)
			}
			for _, s := range queue {
				ctx = updateCtx(ctx, s, html.TextToken)
			}
			queue = []string{}
			buf.Reset()
		}

		ctx = clearElements(ctx, tok)
	}
}

func (l Linter) lintScope(f *core.File, ctx, txt, tag string, lines int) {
	if heading.MatchString(tag) {
		l.lintText(f, NewBlock(ctx, txt, "text.heading"+f.RealExt), lines, 0)
	} else if scope, match := tagToScope[tag]; match {
		l.lintText(f, NewBlock(ctx, txt, scope+f.RealExt), lines, 0)
	} else {
		l.lintProse(f, ctx, txt, lines, 0)
	}
}

func clearElements(ctx string, tok html.Token) string {
	if tok.Data == "img" || tok.Data == "a" {
		for _, a := range tok.Attr {
			if a.Key == "alt" || a.Key == "href" {
				ctx = updateCtx(ctx, a.Val, html.TextToken)
			}
		}
	}
	return ctx
}

func updateCtx(ctx string, txt string, tokt html.TokenType) string {
	var found bool
	if (tokt == html.TextToken || tokt == html.CommentToken) && txt != "" {
		for _, s := range strings.Split(txt, "\n") {
			ctx, found = core.Substitute(ctx, s)
			if !found {
				for _, w := range strings.Fields(s) {
					ctx, _ = core.Substitute(ctx, w)
				}
			}
		}
	}
	return ctx
}

func (l Linter) lintHTML(f *core.File) {
	l.lintHTMLTokens(f, f.Content, []byte(f.Content), 0)
}

func (l Linter) lintMarkdown(f *core.File) {
	html := blackfriday.MarkdownOptions([]byte(f.Content), renderer, options)
	l.lintHTMLTokens(f, f.Content, html, 0)
}

func (l Linter) lintRST(f *core.File, python string, rst2html string) {
	var out bytes.Buffer
	cmd := exec.Command(python, append([]string{rst2html}, rstArgs...)...)
	cmd.Stdin = strings.NewReader(reCodeBlock.ReplaceAllString(f.Content, "::"))
	cmd.Stdout = &out
	if core.CheckError(cmd.Run()) {
		l.lintHTMLTokens(f, f.Content, out.Bytes(), 0)
	}
}

func (l Linter) lintADoc(f *core.File, asciidoctor string) {
	var out bytes.Buffer
	cmd := exec.Command(asciidoctor, adocArgs...)
	cmd.Stdin = strings.NewReader(f.Content)
	cmd.Stdout = &out
	if core.CheckError(cmd.Run()) {
		l.lintHTMLTokens(f, f.Content, out.Bytes(), 0)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"thezombie.net/libgojira"

	"github.com/hoisie/mustache"
)

func init() {
	parser.AddCommand("list",
		"List Jira stories",
		"The list command list stories and their subtasks on stdout",
		&listCommand)

}

//Command made to list
type ListCommand struct {
	CurrentSprint bool   `short:"c" long:"current-sprint" description:"Show stories for current sprint"`
	Open          bool   `short:"o" long:"open"`
	Issue         string `short:"i" long:"issue"`
	JQL           string `short:"q" long:"jql" description:"Custom JQL query"`
	Print         bool   `long:"print" description:"Print stories to file"`
	PrintTmpl     string `long:"tmpl" description:"Custom mustache template."`
}

var listCommand ListCommand

//Implements go-flags's Command interface
func (lc *ListCommand) Execute(args []string) error { //ListTasks(){//
	if options.Verbose {
		fmt.Println("In List Command")
	}
	jc := libgojira.NewJiraClient(options)
	if len(args) == 1 && (!lc.Open && !lc.CurrentSprint && lc.JQL == "") {
		lc.JQL = fmt.Sprintf("key = %s or parent = %s order by rank", args[0], args[0])
	}
	issues, err := jc.Search(&libgojira.SearchOptions{options.Project, lc.CurrentSprint, lc.Open, lc.Issue, lc.JQL})
	if err != nil {
		return err
	}
	if lc.Print {
		var tmpl *mustache.Template
		if lc.PrintTmpl != "" {
			tmpl, err = mustache.ParseFile(lc.PrintTmpl)
		} else {
			tmpl, err = mustache.ParseString(defaultTemplate)
		}
		if err != nil {
			return err
		}
		fmt.Fprintln(out, tmpl.Render(map[string]interface{}{"Issues": issues}))
	} else {
		for _, v := range issues {
			fmt.Fprintln(out, v)
		}
	}

	return nil
}
<commit_msg>Added some filter flags for the list command<commit_after>package main

import (
	"fmt"
	"thezombie.net/libgojira"

	"github.com/hoisie/mustache"
)

func init() {
	parser.AddCommand("list",
		"List Jira stories",
		"The list command list stories and their subtasks on stdout",
		&listCommand)

}

//Command made to list
type ListCommand struct {
	CurrentSprint bool     `short:"c" long:"current-sprint" description:"Show stories for current sprint"`
	Open          bool     `short:"o" long:"open"`
	Issue         string   `short:"i" long:"issue"`
	JQL           string   `short:"q" long:"jql" description:"Custom JQL query"`
	Print         bool     `long:"print" description:"Print stories to file"`
	PrintTmpl     string   `long:"tmpl" description:"Custom mustache template."`
	Type          []string `long:"type" short:"t" description:"Inclusive 'OR' cumulative task type flag. (jql: type in (a, b,c))"`
	NotType       []string `long:"nottype" short:"n" description:"'AND' cumulative task type flag. (jql: type not in (d,e,f))"`
	Status        []string `long:"status" description:"Inclusive 'OR' cumulative task status flag. (jql: status in (a, b,c))"`
	NotStatus     []string `long:"notstatus" description:"'AND' cumulative task status flag. (jql: status not in (d,e,f))"`
}

var listCommand ListCommand

//Implements go-flags's Command interface
func (lc *ListCommand) Execute(args []string) error { //ListTasks(){//
	if options.Verbose {
		fmt.Println("In List Command")
	}
	jc := libgojira.NewJiraClient(options)
	if len(args) == 1 && (!lc.Open && !lc.CurrentSprint && lc.JQL == "") {
		lc.JQL = fmt.Sprintf("key = %s or parent = %s order by rank", args[0], args[0])
	}
	issues, err := jc.Search(&libgojira.SearchOptions{options.Project, lc.CurrentSprint, lc.Open, lc.Issue, lc.JQL, lc.Type, lc.NotType, lc.Status, lc.NotStatus})
	if err != nil {
		return err
	}
	if lc.Print {
		var tmpl *mustache.Template
		if lc.PrintTmpl != "" {
			tmpl, err = mustache.ParseFile(lc.PrintTmpl)
		} else {
			tmpl, err = mustache.ParseString(defaultTemplate)
		}
		if err != nil {
			return err
		}
		fmt.Fprintln(out, tmpl.Render(map[string]interface{}{"Issues": issues}))
	} else {
		for _, v := range issues {
			fmt.Fprintln(out, v)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package tuf

import (
	"bytes"
	"encoding/json"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/flynn/go-tuf/data"
	"github.com/flynn/go-tuf/encrypted"
	"github.com/flynn/go-tuf/sign"
	"github.com/flynn/go-tuf/util"
)

func MemoryStore(meta map[string]json.RawMessage, files map[string][]byte) LocalStore {
	if meta == nil {
		meta = make(map[string]json.RawMessage)
	}
	return &memoryStore{
		meta:    meta,
		files:   files,
		signers: make(map[string][]sign.Signer),
	}
}

type memoryStore struct {
	meta    map[string]json.RawMessage
	files   map[string][]byte
	signers map[string][]sign.Signer
}

func (m *memoryStore) GetMeta() (map[string]json.RawMessage, error) {
	return m.meta, nil
}

func (m *memoryStore) SetMeta(name string, meta json.RawMessage) error {
	m.meta[name] = meta
	return nil
}

func (m *memoryStore) WalkStagedTargets(paths []string, targetsFn targetsWalkFunc) error {
	if len(paths) == 0 {
		for path, data := range m.files {
			if err := targetsFn(path, bytes.NewReader(data)); err != nil {
				return err
			}
		}
		return nil
	}

	for _, path := range paths {
		data, ok := m.files[path]
		if !ok {
			return ErrFileNotFound{path}
		}
		if err := targetsFn(path, bytes.NewReader(data)); err != nil {
			return err
		}
	}
	return nil
}

func (m *memoryStore) Commit(map[string]json.RawMessage, bool, map[string]data.Hashes) error {
	return nil
}

func (m *memoryStore) GetSigningKeys(role string) ([]sign.Signer, error) {
	return m.signers[role], nil
}

func (m *memoryStore) SavePrivateKey(role string, key *sign.PrivateKey) error {
	m.signers[role] = append(m.signers[role], key.Signer())
	return nil
}

func (m *memoryStore) Clean() error {
	return nil
}

type persistedKeys struct {
	Encrypted bool            `json:"encrypted"`
	Data      json.RawMessage `json:"data"`
}

func FileSystemStore(dir string, p util.PassphraseFunc) LocalStore {
	return &fileSystemStore{
		dir:            dir,
		passphraseFunc: p,
		signers:        make(map[string][]sign.Signer),
	}
}

type fileSystemStore struct {
	dir            string
	passphraseFunc util.PassphraseFunc

	// signers is a cache of persisted keys to avoid decrypting multiple times
	signers map[string][]sign.Signer
}

func (f *fileSystemStore) repoDir() string {
	return filepath.Join(f.dir, "repository")
}

func (f *fileSystemStore) stagedDir() string {
	return filepath.Join(f.dir, "staged")
}

func (f *fileSystemStore) GetMeta() (map[string]json.RawMessage, error) {
	meta := make(map[string]json.RawMessage)
	var err error
	notExists := func(path string) bool {
		_, err := os.Stat(path)
		return os.IsNotExist(err)
	}
	for _, name := range topLevelManifests {
		path := filepath.Join(f.stagedDir(), name)
		if notExists(path) {
			path = filepath.Join(f.repoDir(), name)
			if notExists(path) {
				continue
			}
		}
		meta[name], err = ioutil.ReadFile(path)
		if err != nil {
			return nil, err
		}
	}
	return meta, nil
}

func (f *fileSystemStore) SetMeta(name string, meta json.RawMessage) error {
	if err := f.createDirs(); err != nil {
		return err
	}
	if err := ioutil.WriteFile(filepath.Join(f.stagedDir(), name), meta, 0644); err != nil {
		return err
	}
	return nil
}

func (f *fileSystemStore) createDirs() error {
	for _, dir := range []string{"keys", "repository", "staged/targets"} {
		if err := os.MkdirAll(filepath.Join(f.dir, dir), 0755); err != nil {
			return err
		}
	}
	return nil
}

func (f *fileSystemStore) WalkStagedTargets(paths []string, targetsFn targetsWalkFunc) error {
	if len(paths) == 0 {
		walkFunc := func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if info.IsDir() || !info.Mode().IsRegular() {
				return nil
			}
			rel, err := filepath.Rel(filepath.Join(f.stagedDir(), "targets"), path)
			if err != nil {
				return err
			}
			file, err := os.Open(path)
			if err != nil {
				return err
			}
			defer file.Close()
			return targetsFn(rel, file)
		}
		return filepath.Walk(filepath.Join(f.stagedDir(), "targets"), walkFunc)
	}

	// check all the files exist before processing any files
	for _, path := range paths {
		realPath := filepath.Join(f.stagedDir(), "targets", path)
		if _, err := os.Stat(realPath); err != nil {
			if os.IsNotExist(err) {
				return ErrFileNotFound{realPath}
			}
			return err
		}
	}

	for _, path := range paths {
		realPath := filepath.Join(f.stagedDir(), "targets", path)
		file, err := os.Open(realPath)
		if err != nil {
			if os.IsNotExist(err) {
				return ErrFileNotFound{realPath}
			}
			return err
		}
		err = targetsFn(path, file)
		file.Close()
		if err != nil {
			return err
		}
	}
	return nil
}

func (f *fileSystemStore) createRepoFile(path string) (*os.File, error) {
	dst := filepath.Join(f.repoDir(), path)
	if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
		return nil, err
	}
	return os.Create(dst)
}

func (f *fileSystemStore) Commit(meta map[string]json.RawMessage, consistentSnapshot bool, hashes map[string]data.Hashes) error {
	isTarget := func(path string) bool {
		return strings.HasPrefix(path, "targets/")
	}
	shouldCopyHashed := func(path string) bool {
		return consistentSnapshot && path != "timestamp.json"
	}
	shouldCopyUnhashed := func(path string) bool {
		return !consistentSnapshot || !isTarget(path)
	}
	copyToRepo := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() || !info.Mode().IsRegular() {
			return nil
		}
		rel, err := filepath.Rel(f.stagedDir(), path)
		if err != nil {
			return err
		}
		var paths []string
		if shouldCopyHashed(rel) {
			paths = append(paths, util.HashedPaths(rel, hashes[rel])...)
		}
		if shouldCopyUnhashed(rel) {
			paths = append(paths, rel)
		}
		var files []io.Writer
		for _, path := range paths {
			file, err := f.createRepoFile(path)
			if err != nil {
				return err
			}
			defer file.Close()
			files = append(files, file)
		}
		staged, err := os.Open(path)
		if err != nil {
			return err
		}
		defer staged.Close()
		if _, err = io.Copy(io.MultiWriter(files...), staged); err != nil {
			return err
		}
		return nil
	}
	needsRemoval := func(path string) bool {
		if consistentSnapshot {
			// strip out the hash
			name := strings.SplitN(filepath.Base(path), ".", 2)
			if name[1] == "" {
				return false
			}
			path = filepath.Join(filepath.Dir(path), name[1])
		}
		_, ok := hashes[path]
		return !ok
	}
	removeFile := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		rel, err := filepath.Rel(f.repoDir(), path)
		if err != nil {
			return err
		}
		if !info.IsDir() && isTarget(rel) && needsRemoval(rel) {
			if err := os.Remove(path); err != nil {
				// TODO: log / handle error
			}
			// TODO: remove empty directory
		}
		return nil
	}
	if err := filepath.Walk(f.stagedDir(), copyToRepo); err != nil {
		return err
	}
	if err := filepath.Walk(f.repoDir(), removeFile); err != nil {
		return err
	}
	return f.Clean()
}

func (f *fileSystemStore) GetSigningKeys(role string) ([]sign.Signer, error) {
	if keys, ok := f.signers[role]; ok {
		return keys, nil
	}
	keys, _, err := f.loadKeys(role)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}
	f.signers[role] = f.privateKeySigners(keys)
	return f.signers[role], nil
}

func (f *fileSystemStore) SavePrivateKey(role string, key *sign.PrivateKey) error {
	if err := f.createDirs(); err != nil {
		return err
	}

	// add the key to the existing keys (if any)
	keys, pass, err := f.loadKeys(role)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	keys = append(keys, key)

	// if loadKeys didn't return a passphrase (because no keys yet exist)
	// and passphraseFunc is set, get the passphrase so the keys file can
	// be encrypted later (passphraseFunc being nil indicates the keys file
	// should not be encrypted)
	if pass == nil && f.passphraseFunc != nil {
		pass, err = f.passphraseFunc(role, true)
		if err != nil {
			return err
		}
	}

	pk := &persistedKeys{}
	if pass != nil {
		pk.Data, err = encrypted.Marshal(keys, pass)
		if err != nil {
			return err
		}
		pk.Encrypted = true
	} else {
		pk.Data, err = json.MarshalIndent(keys, "", "\t")
		if err != nil {
			return err
		}
	}
	data, err := json.MarshalIndent(pk, "", "\t")
	if err != nil {
		return err
	}
	if err := ioutil.WriteFile(f.keysPath(role), append(data, '\n'), 0600); err != nil {
		return err
	}
	f.signers[role] = f.privateKeySigners(keys)
	return nil
}

func (f *fileSystemStore) privateKeySigners(keys []*sign.PrivateKey) []sign.Signer {
	res := make([]sign.Signer, len(keys))
	for i, k := range keys {
		res[i] = k.Signer()
	}
	return res
}

// loadKeys loads keys for the given role and returns them along with the
// passphrase (if read) so that callers don't need to re-read it.
func (f *fileSystemStore) loadKeys(role string) ([]*sign.PrivateKey, []byte, error) {
	file, err := os.Open(f.keysPath(role))
	if err != nil {
		return nil, nil, err
	}
	defer file.Close()

	pk := &persistedKeys{}
	if err := json.NewDecoder(file).Decode(pk); err != nil {
		return nil, nil, err
	}

	var keys []*sign.PrivateKey
	if !pk.Encrypted {
		if err := json.Unmarshal(pk.Data, &keys); err != nil {
			return nil, nil, err
		}
		return keys, nil, nil
	}

	// the keys are encrypted so cannot be loaded if passphraseFunc is not set
	if f.passphraseFunc == nil {
		return nil, nil, ErrPassphraseRequired{role}
	}

	pass, err := f.passphraseFunc(role, false)
	if err != nil {
		return nil, nil, err
	}
	if err := encrypted.Unmarshal(pk.Data, &keys, pass); err != nil {
		return nil, nil, err
	}
	return keys, pass, nil
}

func (f *fileSystemStore) keysPath(role string) string {
	return filepath.Join(f.dir, "keys", role+".json")
}

func (f *fileSystemStore) Clean() error {
	_, err := os.Stat(filepath.Join(f.repoDir(), "root.json"))
	if os.IsNotExist(err) {
		return ErrNewRepository
	} else if err != nil {
		return err
	}
	if err := os.RemoveAll(f.stagedDir()); err != nil {
		return err
	}
	return os.MkdirAll(filepath.Join(f.stagedDir(), "targets"), 0755)
}
<commit_msg>Fix panic when checking consistent snapshot paths<commit_after>package tuf

import (
	"bytes"
	"encoding/json"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/flynn/go-tuf/data"
	"github.com/flynn/go-tuf/encrypted"
	"github.com/flynn/go-tuf/sign"
	"github.com/flynn/go-tuf/util"
)

func MemoryStore(meta map[string]json.RawMessage, files map[string][]byte) LocalStore {
	if meta == nil {
		meta = make(map[string]json.RawMessage)
	}
	return &memoryStore{
		meta:    meta,
		files:   files,
		signers: make(map[string][]sign.Signer),
	}
}

type memoryStore struct {
	meta    map[string]json.RawMessage
	files   map[string][]byte
	signers map[string][]sign.Signer
}

func (m *memoryStore) GetMeta() (map[string]json.RawMessage, error) {
	return m.meta, nil
}

func (m *memoryStore) SetMeta(name string, meta json.RawMessage) error {
	m.meta[name] = meta
	return nil
}

func (m *memoryStore) WalkStagedTargets(paths []string, targetsFn targetsWalkFunc) error {
	if len(paths) == 0 {
		for path, data := range m.files {
			if err := targetsFn(path, bytes.NewReader(data)); err != nil {
				return err
			}
		}
		return nil
	}

	for _, path := range paths {
		data, ok := m.files[path]
		if !ok {
			return ErrFileNotFound{path}
		}
		if err := targetsFn(path, bytes.NewReader(data)); err != nil {
			return err
		}
	}
	return nil
}

func (m *memoryStore) Commit(map[string]json.RawMessage, bool, map[string]data.Hashes) error {
	return nil
}

func (m *memoryStore) GetSigningKeys(role string) ([]sign.Signer, error) {
	return m.signers[role], nil
}

func (m *memoryStore) SavePrivateKey(role string, key *sign.PrivateKey) error {
	m.signers[role] = append(m.signers[role], key.Signer())
	return nil
}

func (m *memoryStore) Clean() error {
	return nil
}

type persistedKeys struct {
	Encrypted bool            `json:"encrypted"`
	Data      json.RawMessage `json:"data"`
}

func FileSystemStore(dir string, p util.PassphraseFunc) LocalStore {
	return &fileSystemStore{
		dir:            dir,
		passphraseFunc: p,
		signers:        make(map[string][]sign.Signer),
	}
}

type fileSystemStore struct {
	dir            string
	passphraseFunc util.PassphraseFunc

	// signers is a cache of persisted keys to avoid decrypting multiple times
	signers map[string][]sign.Signer
}

func (f *fileSystemStore) repoDir() string {
	return filepath.Join(f.dir, "repository")
}

func (f *fileSystemStore) stagedDir() string {
	return filepath.Join(f.dir, "staged")
}

func (f *fileSystemStore) GetMeta() (map[string]json.RawMessage, error) {
	meta := make(map[string]json.RawMessage)
	var err error
	notExists := func(path string) bool {
		_, err := os.Stat(path)
		return os.IsNotExist(err)
	}
	for _, name := range topLevelManifests {
		path := filepath.Join(f.stagedDir(), name)
		if notExists(path) {
			path = filepath.Join(f.repoDir(), name)
			if notExists(path) {
				continue
			}
		}
		meta[name], err = ioutil.ReadFile(path)
		if err != nil {
			return nil, err
		}
	}
	return meta, nil
}

func (f *fileSystemStore) SetMeta(name string, meta json.RawMessage) error {
	if err := f.createDirs(); err != nil {
		return err
	}
	if err := ioutil.WriteFile(filepath.Join(f.stagedDir(), name), meta, 0644); err != nil {
		return err
	}
	return nil
}

func (f *fileSystemStore) createDirs() error {
	for _, dir := range []string{"keys", "repository", "staged/targets"} {
		if err := os.MkdirAll(filepath.Join(f.dir, dir), 0755); err != nil {
			return err
		}
	}
	return nil
}

func (f *fileSystemStore) WalkStagedTargets(paths []string, targetsFn targetsWalkFunc) error {
	if len(paths) == 0 {
		walkFunc := func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}
			if info.IsDir() || !info.Mode().IsRegular() {
				return nil
			}
			rel, err := filepath.Rel(filepath.Join(f.stagedDir(), "targets"), path)
			if err != nil {
				return err
			}
			file, err := os.Open(path)
			if err != nil {
				return err
			}
			defer file.Close()
			return targetsFn(rel, file)
		}
		return filepath.Walk(filepath.Join(f.stagedDir(), "targets"), walkFunc)
	}

	// check all the files exist before processing any files
	for _, path := range paths {
		realPath := filepath.Join(f.stagedDir(), "targets", path)
		if _, err := os.Stat(realPath); err != nil {
			if os.IsNotExist(err) {
				return ErrFileNotFound{realPath}
			}
			return err
		}
	}

	for _, path := range paths {
		realPath := filepath.Join(f.stagedDir(), "targets", path)
		file, err := os.Open(realPath)
		if err != nil {
			if os.IsNotExist(err) {
				return ErrFileNotFound{realPath}
			}
			return err
		}
		err = targetsFn(path, file)
		file.Close()
		if err != nil {
			return err
		}
	}
	return nil
}

func (f *fileSystemStore) createRepoFile(path string) (*os.File, error) {
	dst := filepath.Join(f.repoDir(), path)
	if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
		return nil, err
	}
	return os.Create(dst)
}

func (f *fileSystemStore) Commit(meta map[string]json.RawMessage, consistentSnapshot bool, hashes map[string]data.Hashes) error {
	isTarget := func(path string) bool {
		return strings.HasPrefix(path, "targets/")
	}
	shouldCopyHashed := func(path string) bool {
		return consistentSnapshot && path != "timestamp.json"
	}
	shouldCopyUnhashed := func(path string) bool {
		return !consistentSnapshot || !isTarget(path)
	}
	copyToRepo := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() || !info.Mode().IsRegular() {
			return nil
		}
		rel, err := filepath.Rel(f.stagedDir(), path)
		if err != nil {
			return err
		}
		var paths []string
		if shouldCopyHashed(rel) {
			paths = append(paths, util.HashedPaths(rel, hashes[rel])...)
		}
		if shouldCopyUnhashed(rel) {
			paths = append(paths, rel)
		}
		var files []io.Writer
		for _, path := range paths {
			file, err := f.createRepoFile(path)
			if err != nil {
				return err
			}
			defer file.Close()
			files = append(files, file)
		}
		staged, err := os.Open(path)
		if err != nil {
			return err
		}
		defer staged.Close()
		if _, err = io.Copy(io.MultiWriter(files...), staged); err != nil {
			return err
		}
		return nil
	}
	needsRemoval := func(path string) bool {
		if consistentSnapshot {
			// strip out the hash
			name := strings.SplitN(filepath.Base(path), ".", 2)
			if len(name) != 2 || name[1] == "" {
				return false
			}
			path = filepath.Join(filepath.Dir(path), name[1])
		}
		_, ok := hashes[path]
		return !ok
	}
	removeFile := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		rel, err := filepath.Rel(f.repoDir(), path)
		if err != nil {
			return err
		}
		if !info.IsDir() && isTarget(rel) && needsRemoval(rel) {
			if err := os.Remove(path); err != nil {
				// TODO: log / handle error
			}
			// TODO: remove empty directory
		}
		return nil
	}
	if err := filepath.Walk(f.stagedDir(), copyToRepo); err != nil {
		return err
	}
	if err := filepath.Walk(f.repoDir(), removeFile); err != nil {
		return err
	}
	return f.Clean()
}

func (f *fileSystemStore) GetSigningKeys(role string) ([]sign.Signer, error) {
	if keys, ok := f.signers[role]; ok {
		return keys, nil
	}
	keys, _, err := f.loadKeys(role)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}
	f.signers[role] = f.privateKeySigners(keys)
	return f.signers[role], nil
}

func (f *fileSystemStore) SavePrivateKey(role string, key *sign.PrivateKey) error {
	if err := f.createDirs(); err != nil {
		return err
	}

	// add the key to the existing keys (if any)
	keys, pass, err := f.loadKeys(role)
	if err != nil && !os.IsNotExist(err) {
		return err
	}
	keys = append(keys, key)

	// if loadKeys didn't return a passphrase (because no keys yet exist)
	// and passphraseFunc is set, get the passphrase so the keys file can
	// be encrypted later (passphraseFunc being nil indicates the keys file
	// should not be encrypted)
	if pass == nil && f.passphraseFunc != nil {
		pass, err = f.passphraseFunc(role, true)
		if err != nil {
			return err
		}
	}

	pk := &persistedKeys{}
	if pass != nil {
		pk.Data, err = encrypted.Marshal(keys, pass)
		if err != nil {
			return err
		}
		pk.Encrypted = true
	} else {
		pk.Data, err = json.MarshalIndent(keys, "", "\t")
		if err != nil {
			return err
		}
	}
	data, err := json.MarshalIndent(pk, "", "\t")
	if err != nil {
		return err
	}
	if err := ioutil.WriteFile(f.keysPath(role), append(data, '\n'), 0600); err != nil {
		return err
	}
	f.signers[role] = f.privateKeySigners(keys)
	return nil
}

func (f *fileSystemStore) privateKeySigners(keys []*sign.PrivateKey) []sign.Signer {
	res := make([]sign.Signer, len(keys))
	for i, k := range keys {
		res[i] = k.Signer()
	}
	return res
}

// loadKeys loads keys for the given role and returns them along with the
// passphrase (if read) so that callers don't need to re-read it.
func (f *fileSystemStore) loadKeys(role string) ([]*sign.PrivateKey, []byte, error) {
	file, err := os.Open(f.keysPath(role))
	if err != nil {
		return nil, nil, err
	}
	defer file.Close()

	pk := &persistedKeys{}
	if err := json.NewDecoder(file).Decode(pk); err != nil {
		return nil, nil, err
	}

	var keys []*sign.PrivateKey
	if !pk.Encrypted {
		if err := json.Unmarshal(pk.Data, &keys); err != nil {
			return nil, nil, err
		}
		return keys, nil, nil
	}

	// the keys are encrypted so cannot be loaded if passphraseFunc is not set
	if f.passphraseFunc == nil {
		return nil, nil, ErrPassphraseRequired{role}
	}

	pass, err := f.passphraseFunc(role, false)
	if err != nil {
		return nil, nil, err
	}
	if err := encrypted.Unmarshal(pk.Data, &keys, pass); err != nil {
		return nil, nil, err
	}
	return keys, pass, nil
}

func (f *fileSystemStore) keysPath(role string) string {
	return filepath.Join(f.dir, "keys", role+".json")
}

func (f *fileSystemStore) Clean() error {
	_, err := os.Stat(filepath.Join(f.repoDir(), "root.json"))
	if os.IsNotExist(err) {
		return ErrNewRepository
	} else if err != nil {
		return err
	}
	if err := os.RemoveAll(f.stagedDir()); err != nil {
		return err
	}
	return os.MkdirAll(filepath.Join(f.stagedDir(), "targets"), 0755)
}
<|endoftext|>


<commit_before><commit_msg>more tests<commit_after><|endoftext|>


<commit_before>// Copyright (c) 2014, Rob Thornton
// All rights reserved.
// This source code is governed by a Simplied BSD-License. Please see the
// LICENSE included in this distribution for a copy of the full license
// or, if one is not included, you may also find a copy at
// http://opensource.org/licenses/BSD-2-Clause

package ast

import (
	"github.com/rthornton128/calc/token"
)

type Node interface {
	Pos() token.Pos
	End() token.Pos
}

type Expr interface {
	Node
	exprNode()
}

type AssignExpr struct {
	Expression
	Equal token.Pos
	Name  *Ident
	Value Expr
}

type BasicLit struct {
	LitPos token.Pos
	Kind   token.Token
	Lit    string
}

type BinaryExpr struct {
	Expression
	Op    token.Token
	OpPos token.Pos
	List  []Expr
}

type CallExpr struct {
	Expression
	Name *Ident
	Args []Expr
}

type DeclExpr struct {
	Expression
	Decl   token.Pos
	Name   *Ident
	Type   *Ident
	Params []*Ident
	Body   Expr
}

type Expression struct {
	Opening token.Pos
	Closing token.Pos
}

type ExprList struct {
	Expression
	List []Expr
}

type File struct {
	Scope *Scope
}

type Ident struct {
	NamePos token.Pos
	Name    string
	Type    *Ident // TODO: object; to hold type (once set), value ptr, etc
}

type IfExpr struct {
	Expression
	If   token.Pos
	Type *Ident
	Cond Expr
	Then Expr
	Else Expr
}

type Package struct {
	Scope *Scope
	//Files map[string]*File
}

type Scope struct {
	parent *Scope
	table  map[string]Expr
}

type VarExpr struct {
	Expression
	Var   token.Pos
	Name  *Ident
	Type  *Ident
	Value Expr
}

func (b *BasicLit) Pos() token.Pos   { return b.LitPos }
func (e *Expression) Pos() token.Pos { return e.Opening }
func (f *File) Pos() token.Pos       { return token.NoPos }
func (i *Ident) Pos() token.Pos      { return i.NamePos }
func (p *Package) Pos() token.Pos    { return token.NoPos }

func (b *BasicLit) End() token.Pos   { return b.LitPos + token.Pos(len(b.Lit)) }
func (e *Expression) End() token.Pos { return e.Closing }
func (f *File) End() token.Pos       { return token.NoPos }
func (i *Ident) End() token.Pos      { return i.NamePos + token.Pos(len(i.Name)) }
func (p *Package) End() token.Pos    { return token.NoPos }

func (b *BasicLit) exprNode()   {}
func (e *Expression) exprNode() {}
func (e *ExprList) exprNode()   {}
func (i *Ident) exprNode()      {}

func NewScope(parent *Scope) *Scope {
	return &Scope{parent: parent, table: make(map[string]Expr)}
}

func (s *Scope) Insert(ident *Ident, exp Expr) {
	s.table[ident.Name] = exp
}

func (s *Scope) Lookup(ident string) Expr {
	var exp Expr
	var ok bool
	if exp, ok = s.table[ident]; !ok {
		if s.parent == nil {
			return nil
		}
		return s.parent.Lookup(ident)
	}
	return exp
}

func (s *Scope) Parent() *Scope {
	return s.parent
}
<commit_msg>scoping should use an object<commit_after>// Copyright (c) 2014, Rob Thornton
// All rights reserved.
// This source code is governed by a Simplied BSD-License. Please see the
// LICENSE included in this distribution for a copy of the full license
// or, if one is not included, you may also find a copy at
// http://opensource.org/licenses/BSD-2-Clause

package ast

import (
	"github.com/rthornton128/calc/token"
)

type Node interface {
	Pos() token.Pos
	End() token.Pos
}

type Expr interface {
	Node
	exprNode()
}

type AssignExpr struct {
	Expression
	Equal token.Pos
	Name  *Ident
	Value Expr
}

type BasicLit struct {
	LitPos token.Pos
	Kind   token.Token
	Lit    string
}

type BinaryExpr struct {
	Expression
	Op    token.Token
	OpPos token.Pos
	List  []Expr
}

type CallExpr struct {
	Expression
	Name *Ident
	Args []Expr
}

type DeclExpr struct {
	Expression
	Decl   token.Pos
	Name   *Ident
	Type   *Ident
	Params []*Ident
	Body   Expr
}

type Expression struct {
	Opening token.Pos
	Closing token.Pos
}

type ExprList struct {
	Expression
	List []Expr
}

type File struct {
	Scope *Scope
}

type Ident struct {
	NamePos token.Pos
	Name    string
	Object  *Object // may be nil (ie. Name is a type keyword)
}

type IfExpr struct {
	Expression
	If   token.Pos
	Type *Ident
	Cond Expr
	Then Expr
	Else Expr
}

type Object struct {
	NamePos token.Pos
	Name    string
	Kind    ObKind
	Type    *Ident // variable type, function return type, etc
	Value   Expr
}

type ObKind int

type Package struct {
	Scope *Scope
	//Files map[string]*File
}

type Scope struct {
	parent *Scope
	table  map[string]*Object
}

type VarExpr struct {
	Expression
	Var  token.Pos
	Name *Ident
}

func (b *BasicLit) Pos() token.Pos   { return b.LitPos }
func (e *Expression) Pos() token.Pos { return e.Opening }
func (f *File) Pos() token.Pos       { return token.NoPos }
func (i *Ident) Pos() token.Pos      { return i.NamePos }
func (p *Package) Pos() token.Pos    { return token.NoPos }

func (b *BasicLit) End() token.Pos   { return b.LitPos + token.Pos(len(b.Lit)) }
func (e *Expression) End() token.Pos { return e.Closing }
func (f *File) End() token.Pos       { return token.NoPos }
func (i *Ident) End() token.Pos      { return i.NamePos + token.Pos(len(i.Name)) }
func (p *Package) End() token.Pos    { return token.NoPos }

func (b *BasicLit) exprNode()   {}
func (e *Expression) exprNode() {}
func (e *ExprList) exprNode()   {}
func (i *Ident) exprNode()      {}

const (
	Decl ObKind = iota
	Var
)

func NewScope(parent *Scope) *Scope {
	return &Scope{parent: parent, table: make(map[string]*Object)}
}

func (s *Scope) Insert(ob *Object) *Object {
	if old, ok := s.table[ob.Name]; ok {
		return old
	}
	s.table[ob.Name] = ob
	return nil
}

func (s *Scope) Lookup(ident string) *Object {
	ob, ok := s.table[ident]
	if !ok {
		if s.parent == nil {
			return nil
		}
		return s.parent.Lookup(ident)
	}
	return ob
}

func (s *Scope) Parent() *Scope {
	return s.parent
}
<|endoftext|>


<commit_before>package ast

import (
	"fmt"
	"math"
	"strconv"
	"strings"

	"github.com/goccy/go-yaml/token"
)

type Document struct {
	Nodes []Node
}

func (d *Document) String() string {
	values := []string{}
	for _, node := range d.Nodes {
		values = append(values, strings.TrimLeft(node.String(), " "))
	}
	return strings.Join(values, "\n")
}

type Node interface {
	String() string
	GetToken() *token.Token
}

type ScalarNode interface {
	Node
	GetValue() interface{}
}

var (
	Null = func(tk *token.Token) Node {
		return &NullNode{
			Token: tk,
		}
	}
	Bool = func(tk *token.Token) Node {
		b, _ := strconv.ParseBool(tk.Value)
		return &BoolNode{
			Token: tk,
			Value: b,
		}
	}
	Integer = func(tk *token.Token) Node {
		i, _ := strconv.ParseInt(tk.Value, 10, 64)
		return &IntegerNode{
			Token: tk,
			Value: i,
		}
	}
	Float = func(tk *token.Token) Node {
		f, _ := strconv.ParseFloat(tk.Value, 64)
		return &FloatNode{
			Token: tk,
			Value: f,
		}
	}
	Infinity = func(tk *token.Token) Node {
		node := &InfinityNode{
			Token: tk,
		}
		switch tk.Value {
		case ".inf":
			node.Value = math.Inf(0)
		case "-.inf":
			node.Value = math.Inf(-1)
		}
		return node
	}
	Nan = func(tk *token.Token) Node {
		return &NanNode{Token: tk}
	}
	String = func(tk *token.Token) Node {
		return &StringNode{
			Token: tk,
			Value: tk.Value,
		}
	}
	MergeKey = func(tk *token.Token) Node {
		return &MergeKeyNode{
			Token: tk,
		}
	}
)

type NullNode struct {
	ScalarNode
	Token *token.Token
}

func (n *NullNode) GetToken() *token.Token {
	return n.Token
}

func (n *NullNode) GetValue() interface{} {
	return nil
}

func (n *NullNode) String() string {
	return "null"
}

type IntegerNode struct {
	ScalarNode
	Token *token.Token
	Value int64
}

func (n *IntegerNode) GetToken() *token.Token {
	return n.Token
}

func (n *IntegerNode) GetValue() interface{} {
	return n.Value
}

func (n *IntegerNode) String() string {
	return n.Token.Value
}

type FloatNode struct {
	ScalarNode
	Token     *token.Token
	Precision int
	Value     float64
}

func (n *FloatNode) GetToken() *token.Token {
	return n.Token
}

func (n *FloatNode) GetValue() interface{} {
	return n.Value
}

func (n *FloatNode) String() string {
	return n.Token.Value
}

type StringNode struct {
	ScalarNode
	Token *token.Token
	Value string
}

func (n *StringNode) GetToken() *token.Token {
	return n.Token
}

func (n *StringNode) GetValue() interface{} {
	return n.Value
}

func (n *StringNode) String() string {
	switch n.Token.Type {
	case token.SingleQuoteType:
		return fmt.Sprintf(`'%s'`, n.Value)
	case token.DoubleQuoteType:
		return fmt.Sprintf(`"%s"`, n.Value)
	}
	return n.Value
}

type LiteralNode struct {
	ScalarNode
	Start *token.Token
	Value *StringNode
}

func (n *LiteralNode) GetToken() *token.Token {
	return n.Start
}

func (n *LiteralNode) GetValue() interface{} {
	return n.Value.GetValue()
}

func (n *LiteralNode) String() string {
	return n.Value.String()
}

type MergeKeyNode struct {
	ScalarNode
	Token *token.Token
}

func (n *MergeKeyNode) GetToken() *token.Token {
	return n.Token
}

func (n *MergeKeyNode) GetValue() interface{} {
	return n.Token.Value
}

func (n *MergeKeyNode) String() string {
	return n.Token.Value
}

type BoolNode struct {
	ScalarNode
	Token *token.Token
	Value bool
}

func (n *BoolNode) GetToken() *token.Token {
	return n.Token
}

func (n *BoolNode) GetValue() interface{} {
	return n.Value
}

func (n *BoolNode) String() string {
	return n.Token.Value
}

type InfinityNode struct {
	ScalarNode
	Token *token.Token
	Value float64
}

func (n *InfinityNode) GetToken() *token.Token {
	return n.Token
}

func (n *InfinityNode) GetValue() interface{} {
	return n.Value
}

func (n *InfinityNode) String() string {
	return n.Token.Value
}

type NanNode struct {
	ScalarNode
	Token *token.Token
}

func (n *NanNode) GetToken() *token.Token {
	return n.Token
}

func (n *NanNode) GetValue() interface{} {
	return math.NaN()
}

func (n *NanNode) String() string {
	return n.Token.Value
}

type FlowMappingNode struct {
	Start  *token.Token
	End    *token.Token
	Values []*MappingValueNode
}

func (n *FlowMappingNode) GetToken() *token.Token {
	return n.Start
}

func (n *FlowMappingNode) String() string {
	values := []string{}
	for _, value := range n.Values {
		values = append(values, strings.TrimLeft(value.String(), " "))
	}
	return fmt.Sprintf("{%s}", strings.Join(values, ", "))
}

type MappingCollectionNode struct {
	Start  *token.Token
	Values []Node
}

func (n *MappingCollectionNode) GetToken() *token.Token {
	return n.Start
}

func (n *MappingCollectionNode) String() string {
	values := []string{}
	for _, value := range n.Values {
		values = append(values, value.String())
	}
	return strings.Join(values, "\n")
}

type MappingValueNode struct {
	Start *token.Token
	Key   Node
	Value Node
}

func (n *MappingValueNode) GetToken() *token.Token {
	return n.Start
}

func (n *MappingValueNode) String() string {
	space := strings.Repeat(" ", n.Key.GetToken().Position.Column-1)
	keyIndentLevel := n.Key.GetToken().Position.IndentLevel
	valueIndentLevel := n.Value.GetToken().Position.IndentLevel
	if _, ok := n.Value.(ScalarNode); ok {
		return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
	} else if keyIndentLevel < valueIndentLevel {
		return fmt.Sprintf("%s%s:\n%s", space, n.Key.String(), n.Value.String())
	} else if _, ok := n.Value.(*FlowSequenceNode); ok {
		return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
	} else if _, ok := n.Value.(*AnchorNode); ok {
		return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
	} else if _, ok := n.Value.(*AliasNode); ok {
		return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
	}
	return fmt.Sprintf("%s%s:\n%s", space, n.Key.String(), n.Value.String())
}

type FlowSequenceNode struct {
	Start  *token.Token
	End    *token.Token
	Values []Node
}

func (n *FlowSequenceNode) GetToken() *token.Token {
	return n.Start
}

func (n *FlowSequenceNode) String() string {
	values := []string{}
	for _, value := range n.Values {
		values = append(values, value.String())
	}
	return fmt.Sprintf("[%s]", strings.Join(values, ", "))
}

type SequenceNode struct {
	Start  *token.Token
	Values []Node
}

func (n *SequenceNode) GetToken() *token.Token {
	return n.Start
}

func (n *SequenceNode) String() string {
	space := strings.Repeat(" ", n.Start.Position.Column-1)
	values := []string{}
	for _, value := range n.Values {
		valueStr := value.String()
		splittedValues := strings.Split(valueStr, "\n")
		trimmedFirstValue := strings.TrimLeft(splittedValues[0], " ")
		diffLength := len(splittedValues[0]) - len(trimmedFirstValue)
		newValues := []string{trimmedFirstValue}
		for i := 1; i < len(splittedValues); i++ {
			trimmed := splittedValues[i][diffLength:]
			newValues = append(newValues, fmt.Sprintf("%s  %s", space, trimmed))
		}
		newValue := strings.Join(newValues, "\n")
		values = append(values, fmt.Sprintf("%s- %s", space, newValue))
	}
	return strings.Join(values, "\n")
}

type AnchorNode struct {
	Start *token.Token
	Name  Node
	Value Node
}

func (n *AnchorNode) GetToken() *token.Token {
	return n.Start
}

func (n *AnchorNode) String() string {
	value := n.Value.String()
	if len(strings.Split(value, "\n")) > 1 {
		return fmt.Sprintf("&%s\n%s", n.Name.String(), value)
	}
	return fmt.Sprintf("&%s %s", n.Name.String(), value)
}

type AliasNode struct {
	Start *token.Token
	Value Node
}

func (n *AliasNode) GetToken() *token.Token {
	return n.Start
}

func (n *AliasNode) String() string {
	return fmt.Sprintf("*%s", n.Value.String())
}

type DirectiveNode struct {
	Start *token.Token
	Value Node
}

func (n *DirectiveNode) GetToken() *token.Token {
	return n.Start
}

func (n *DirectiveNode) String() string {
	return fmt.Sprintf("%s%s", n.Start.Value, n.Value.String())
}

type TagNode struct {
	Start *token.Token
	Value Node
}

func (n *TagNode) GetToken() *token.Token {
	return n.Start
}

func (n *TagNode) String() string {
	return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String())
}

type Visitor interface {
	Visit(Node) Visitor
}

func Walk(v Visitor, node Node) {
	if v = v.Visit(node); v == nil {
		return
	}

	switch n := node.(type) {
	case *NullNode:
	case *IntegerNode:
	case *FloatNode:
	case *StringNode:
	case *MergeKeyNode:
	case *BoolNode:
	case *InfinityNode:
	case *NanNode:
	case *FlowMappingNode:
		for _, value := range n.Values {
			Walk(v, value)
		}
	case *MappingCollectionNode:
		for _, value := range n.Values {
			Walk(v, value)
		}
	case *MappingValueNode:
		Walk(v, n.Key)
		Walk(v, n.Value)
	case *FlowSequenceNode:
		for _, value := range n.Values {
			Walk(v, value)
		}
	case *SequenceNode:
		for _, value := range n.Values {
			Walk(v, value)
		}
	case *AnchorNode:
		Walk(v, n.Name)
		Walk(v, n.Value)
	case *AliasNode:
		Walk(v, n.Value)
	}
}
<commit_msg>Add document for ast package<commit_after>package ast

import (
	"fmt"
	"math"
	"strconv"
	"strings"

	"github.com/goccy/go-yaml/token"
)

// NodeType type identifier of node
type NodeType int

const (
	// UnknownNodeType
	UnknownNodeType NodeType = iota
	// DocumentType
	DocumentType
	// NullType
	NullType
	// BoolType
	BoolType
	// IntegerType
	IntegerType
	// FloatType
	FloatType
	// InfinityType
	InfinityType
	// NanType
	NanType
	// StringType
	StringType
	// MergeKeyType
	MergeKeyType
	// LiteralType
	LiteralType
	// FlowMappingType
	FlowMappingType
	// MappingCollectionType
	MappingCollectionType
	// MappingValueType
	MappingValueType
	// FlowSequenceType
	FlowSequenceType
	// SequenceType
	SequenceType
	// AnchorType
	AnchorType
	// AliasType
	AliasType
	// DirectiveType
	DirectiveType
	// TagType
	TagType
)

// Node type of node
type Node interface {
	// String node to text
	String() string
	// GetToken returns token instance
	GetToken() *token.Token
	// Type returns type of node
	Type() NodeType
}

// Document type of Document
type Document struct {
	// Nodes all nodes in document
	Nodes []Node
}

// Type returns DocumentType
func (d *Document) Type() NodeType { return DocumentType }

// String document to text
func (d *Document) String() string {
	values := []string{}
	for _, node := range d.Nodes {
		values = append(values, strings.TrimLeft(node.String(), " "))
	}
	return strings.Join(values, "\n")
}

// ScalarNode type for scalar node
type ScalarNode interface {
	Node
	GetValue() interface{}
}

// Null create node for null value
func Null(tk *token.Token) Node {
	return &NullNode{
		Token: tk,
	}
}

// Bool create node for boolean value
func Bool(tk *token.Token) Node {
	b, _ := strconv.ParseBool(tk.Value)
	return &BoolNode{
		Token: tk,
		Value: b,
	}
}

// Integer create node for integer value
func Integer(tk *token.Token) Node {
	i, _ := strconv.ParseInt(tk.Value, 10, 64)
	return &IntegerNode{
		Token: tk,
		Value: i,
	}
}

// Float create node for float value
func Float(tk *token.Token) Node {
	f, _ := strconv.ParseFloat(tk.Value, 64)
	return &FloatNode{
		Token: tk,
		Value: f,
	}
}

// Infinity create node for .inf or -.inf value
func Infinity(tk *token.Token) Node {
	node := &InfinityNode{
		Token: tk,
	}
	switch tk.Value {
	case ".inf":
		node.Value = math.Inf(0)
	case "-.inf":
		node.Value = math.Inf(-1)
	}
	return node
}

// Nan create node for .nan value
func Nan(tk *token.Token) Node {
	return &NanNode{Token: tk}
}

// String create node for string value
func String(tk *token.Token) Node {
	return &StringNode{
		Token: tk,
		Value: tk.Value,
	}
}

// MergeKey create node for merge key ( << )
func MergeKey(tk *token.Token) Node {
	return &MergeKeyNode{
		Token: tk,
	}
}

// NullNode type of null node
type NullNode struct {
	ScalarNode
	Token *token.Token
}

// Type returns NullType
func (n *NullNode) Type() NodeType { return NullType }

// GetToken returns token instance
func (n *NullNode) GetToken() *token.Token {
	return n.Token
}

// GetValue returns nil value
func (n *NullNode) GetValue() interface{} {
	return nil
}

// String returns `null` text
func (n *NullNode) String() string {
	return "null"
}

// IntegerNode type of integer node
type IntegerNode struct {
	ScalarNode
	Token *token.Token
	Value int64
}

// Type returns IntegerType
func (n *IntegerNode) Type() NodeType { return IntegerType }

// GetToken returns token instance
func (n *IntegerNode) GetToken() *token.Token {
	return n.Token
}

// GetValue returns int64 value
func (n *IntegerNode) GetValue() interface{} {
	return n.Value
}

// String int64 to text
func (n *IntegerNode) String() string {
	return n.Token.Value
}

// FloatNode type of float node
type FloatNode struct {
	ScalarNode
	Token     *token.Token
	Precision int
	Value     float64
}

// Type returns FloatType
func (n *FloatNode) Type() NodeType { return FloatType }

// GetToken returns token instance
func (n *FloatNode) GetToken() *token.Token {
	return n.Token
}

// GetValue returns float64 value
func (n *FloatNode) GetValue() interface{} {
	return n.Value
}

// String float64 to text
func (n *FloatNode) String() string {
	return n.Token.Value
}

// StringNode type of string node
type StringNode struct {
	ScalarNode
	Token *token.Token
	Value string
}

// Type returns StringType
func (n *StringNode) Type() NodeType { return StringType }

// GetToken returns token instance
func (n *StringNode) GetToken() *token.Token {
	return n.Token
}

// GetValue returns string value
func (n *StringNode) GetValue() interface{} {
	return n.Value
}

// String string value to text with quote if required
func (n *StringNode) String() string {
	switch n.Token.Type {
	case token.SingleQuoteType:
		return fmt.Sprintf(`'%s'`, n.Value)
	case token.DoubleQuoteType:
		return fmt.Sprintf(`"%s"`, n.Value)
	}
	return n.Value
}

// LiteralNode type of literal node
type LiteralNode struct {
	ScalarNode
	Start *token.Token
	Value *StringNode
}

// Type returns LiteralType
func (n *LiteralNode) Type() NodeType { return LiteralType }

// GetToken returns token instance
func (n *LiteralNode) GetToken() *token.Token {
	return n.Start
}

// GetValue returns string value
func (n *LiteralNode) GetValue() interface{} {
	return n.Value.GetValue()
}

// String literal to text
func (n *LiteralNode) String() string {
	return n.Value.String()
}

// MergeKeyNode type of merge key node
type MergeKeyNode struct {
	ScalarNode
	Token *token.Token
}

// Type returns MergeKeyType
func (n *MergeKeyNode) Type() NodeType { return MergeKeyType }

// GetToken returns token instance
func (n *MergeKeyNode) GetToken() *token.Token {
	return n.Token
}

// GetValue returns '<<' value
func (n *MergeKeyNode) GetValue() interface{} {
	return n.Token.Value
}

// String returns '<<' value
func (n *MergeKeyNode) String() string {
	return n.Token.Value
}

// BoolNode type of boolean node
type BoolNode struct {
	ScalarNode
	Token *token.Token
	Value bool
}

// Type returns BoolType
func (n *BoolNode) Type() NodeType { return BoolType }

// GetToken returns token instance
func (n *BoolNode) GetToken() *token.Token {
	return n.Token
}

// GetValue returns boolean value
func (n *BoolNode) GetValue() interface{} {
	return n.Value
}

// String boolean to text
func (n *BoolNode) String() string {
	return n.Token.Value
}

// InfinityNode type of infinity node
type InfinityNode struct {
	ScalarNode
	Token *token.Token
	Value float64
}

// Type returns InfinityType
func (n *InfinityNode) Type() NodeType { return InfinityType }

// GetToken returns token instance
func (n *InfinityNode) GetToken() *token.Token {
	return n.Token
}

// GetValue returns math.Inf(0) or math.Inf(-1)
func (n *InfinityNode) GetValue() interface{} {
	return n.Value
}

// String infinity to text
func (n *InfinityNode) String() string {
	return n.Token.Value
}

// NanNode type of nan node
type NanNode struct {
	ScalarNode
	Token *token.Token
}

// Type returns NanType
func (n *NanNode) Type() NodeType { return NanType }

// GetToken returns token instance
func (n *NanNode) GetToken() *token.Token {
	return n.Token
}

// GetValue returns math.NaN()
func (n *NanNode) GetValue() interface{} {
	return math.NaN()
}

// String returns .nan
func (n *NanNode) String() string {
	return n.Token.Value
}

// FlowMappingNode type of flow mapping node
type FlowMappingNode struct {
	Start  *token.Token
	End    *token.Token
	Values []*MappingValueNode
}

// Type returns FlowMappingType
func (n *FlowMappingNode) Type() NodeType { return FlowMappingType }

// GetToken returns token instance
func (n *FlowMappingNode) GetToken() *token.Token {
	return n.Start
}

// String flow mapping to text
func (n *FlowMappingNode) String() string {
	values := []string{}
	for _, value := range n.Values {
		values = append(values, strings.TrimLeft(value.String(), " "))
	}
	return fmt.Sprintf("{%s}", strings.Join(values, ", "))
}

// MappingCollectionNode type of mapping collection node
type MappingCollectionNode struct {
	Start  *token.Token
	Values []Node
}

// Type returns MappingCollectionType
func (n *MappingCollectionNode) Type() NodeType { return MappingCollectionType }

// GetToken returns token instance
func (n *MappingCollectionNode) GetToken() *token.Token {
	return n.Start
}

// String mapping collection to text
func (n *MappingCollectionNode) String() string {
	values := []string{}
	for _, value := range n.Values {
		values = append(values, value.String())
	}
	return strings.Join(values, "\n")
}

// MappingValueNode type of mapping value
type MappingValueNode struct {
	Start *token.Token
	Key   Node
	Value Node
}

// Type returns MappingValueType
func (n *MappingValueNode) Type() NodeType { return MappingValueType }

// GetToken returns token instance
func (n *MappingValueNode) GetToken() *token.Token {
	return n.Start
}

// String mapping value to text
func (n *MappingValueNode) String() string {
	space := strings.Repeat(" ", n.Key.GetToken().Position.Column-1)
	keyIndentLevel := n.Key.GetToken().Position.IndentLevel
	valueIndentLevel := n.Value.GetToken().Position.IndentLevel
	if _, ok := n.Value.(ScalarNode); ok {
		return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
	} else if keyIndentLevel < valueIndentLevel {
		return fmt.Sprintf("%s%s:\n%s", space, n.Key.String(), n.Value.String())
	} else if _, ok := n.Value.(*FlowSequenceNode); ok {
		return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
	} else if _, ok := n.Value.(*AnchorNode); ok {
		return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
	} else if _, ok := n.Value.(*AliasNode); ok {
		return fmt.Sprintf("%s%s: %s", space, n.Key.String(), n.Value.String())
	}
	return fmt.Sprintf("%s%s:\n%s", space, n.Key.String(), n.Value.String())
}

// FlowSequenceNode type of sequence node
type FlowSequenceNode struct {
	Start  *token.Token
	End    *token.Token
	Values []Node
}

// Type returns FlowSequenceType
func (n *FlowSequenceNode) Type() NodeType { return FlowSequenceType }

// GetToken returns token instance
func (n *FlowSequenceNode) GetToken() *token.Token {
	return n.Start
}

// String flow sequence to text
func (n *FlowSequenceNode) String() string {
	values := []string{}
	for _, value := range n.Values {
		values = append(values, value.String())
	}
	return fmt.Sprintf("[%s]", strings.Join(values, ", "))
}

// SequenceNode type of sequence node
type SequenceNode struct {
	Start  *token.Token
	Values []Node
}

// Type returns SequenceType
func (n *SequenceNode) Type() NodeType { return SequenceType }

// GetToken returns token instance
func (n *SequenceNode) GetToken() *token.Token {
	return n.Start
}

// String sequence to text
func (n *SequenceNode) String() string {
	space := strings.Repeat(" ", n.Start.Position.Column-1)
	values := []string{}
	for _, value := range n.Values {
		valueStr := value.String()
		splittedValues := strings.Split(valueStr, "\n")
		trimmedFirstValue := strings.TrimLeft(splittedValues[0], " ")
		diffLength := len(splittedValues[0]) - len(trimmedFirstValue)
		newValues := []string{trimmedFirstValue}
		for i := 1; i < len(splittedValues); i++ {
			trimmed := splittedValues[i][diffLength:]
			newValues = append(newValues, fmt.Sprintf("%s  %s", space, trimmed))
		}
		newValue := strings.Join(newValues, "\n")
		values = append(values, fmt.Sprintf("%s- %s", space, newValue))
	}
	return strings.Join(values, "\n")
}

// AnchorNode type of anchor node
type AnchorNode struct {
	Start *token.Token
	Name  Node
	Value Node
}

// Type returns AnchorType
func (n *AnchorNode) Type() NodeType { return AnchorType }

// GetToken returns token instance
func (n *AnchorNode) GetToken() *token.Token {
	return n.Start
}

// String anchor to text
func (n *AnchorNode) String() string {
	value := n.Value.String()
	if len(strings.Split(value, "\n")) > 1 {
		return fmt.Sprintf("&%s\n%s", n.Name.String(), value)
	}
	return fmt.Sprintf("&%s %s", n.Name.String(), value)
}

// AliasNode type of alias node
type AliasNode struct {
	Start *token.Token
	Value Node
}

// Type returns AliasType
func (n *AliasNode) Type() NodeType { return AliasType }

// GetToken returns token instance
func (n *AliasNode) GetToken() *token.Token {
	return n.Start
}

// String alias to text
func (n *AliasNode) String() string {
	return fmt.Sprintf("*%s", n.Value.String())
}

// DirectiveNode type of directive node
type DirectiveNode struct {
	Start *token.Token
	Value Node
}

// Type returns DirectiveType
func (n *DirectiveNode) Type() NodeType { return DirectiveType }

// GetToken returns token instance
func (n *DirectiveNode) GetToken() *token.Token {
	return n.Start
}

// String directive to text
func (n *DirectiveNode) String() string {
	return fmt.Sprintf("%s%s", n.Start.Value, n.Value.String())
}

// TagNode type of tag node
type TagNode struct {
	Start *token.Token
	Value Node
}

// Type returns TagType
func (n *TagNode) Type() NodeType { return TagType }

// GetToken returns token instance
func (n *TagNode) GetToken() *token.Token {
	return n.Start
}

// String tag to text
func (n *TagNode) String() string {
	return fmt.Sprintf("%s %s", n.Start.Value, n.Value.String())
}

// A Visitor's Visit method is invoked for each node encountered by Walk.
// If the result visitor w is not nil, Walk visits each of the children of node with the visitor w,
// followed by a call of w.Visit(nil).
type Visitor interface {
	Visit(Node) Visitor
}

// Walk traverses an AST in depth-first order: It starts by calling v.Visit(node); node must not be nil.
// If the visitor w returned by v.Visit(node) is not nil,
// Walk is invoked recursively with visitor w for each of the non-nil children of node,
// followed by a call of w.Visit(nil).
func Walk(v Visitor, node Node) {
	if v = v.Visit(node); v == nil {
		return
	}

	switch n := node.(type) {
	case *NullNode:
	case *IntegerNode:
	case *FloatNode:
	case *StringNode:
	case *MergeKeyNode:
	case *BoolNode:
	case *InfinityNode:
	case *NanNode:
	case *FlowMappingNode:
		for _, value := range n.Values {
			Walk(v, value)
		}
	case *MappingCollectionNode:
		for _, value := range n.Values {
			Walk(v, value)
		}
	case *MappingValueNode:
		Walk(v, n.Key)
		Walk(v, n.Value)
	case *FlowSequenceNode:
		for _, value := range n.Values {
			Walk(v, value)
		}
	case *SequenceNode:
		for _, value := range n.Values {
			Walk(v, value)
		}
	case *AnchorNode:
		Walk(v, n.Name)
		Walk(v, n.Value)
	case *AliasNode:
		Walk(v, n.Value)
	}
}
<|endoftext|>


<commit_before><commit_msg>hash: Add UIntPtr.<commit_after><|endoftext|>


<commit_before>// Copyright (c) 2015, Ben Morgan. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

// Package aur lets you query the Arch Linux User Repository (AUR).
package aur

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/goulash/pacman"
	"github.com/goulash/pacman/alpm"
)

type Packages []*Package

func (pkgs Packages) Len() int      { return len(pkgs) }
func (pkgs Packages) Swap(i, j int) { pkgs[i], pkgs[j] = pkgs[j], pkgs[i] }
func (pkgs Packages) Less(i, j int) bool {
	if pkgs[i].Name != pkgs[j].Name {
		return pkgs[i].Name < pkgs[j].Name
	}
	return alpm.VerCmp(pkgs[i].Version, pkgs[j].Version) == -1
}

func (pkgs Packages) Pkgs() pacman.Packages {
	results := make(pacman.Packages, len(pkgs))
	for i, p := range pkgs {
		results[i] = p.Pkg()
	}
	return results
}
func (pkgs Packages) Iterate(f func(pacman.AnyPackage)) {
	for _, p := range pkgs {
		f(p)
	}
}

// NotFoundError is returned when a package could not be found on AUR.
//
// The error message returned is different dependent on the number of packages
// that could not be found.
type NotFoundError struct {
	Names []string
}

func (e NotFoundError) Error() string {
	n := len(e.Names)
	if n == 1 {
		return fmt.Sprintf("package %q could not be found on AUR", e.Names[0])
	} else if n == 2 {
		return fmt.Sprintf("packages %q and %q could not be found on AUR", e.Names[0], e.Names[1])
	}

	// We have three or more packages that we could not find.
	var buf bytes.Buffer
	buf.WriteString("packages ")
	for _, nam := range e.Names[:n-1] {
		buf.WriteRune('"')
		buf.WriteString(nam)
		buf.WriteString(`", `)
	}
	buf.WriteString(`and "`)
	buf.WriteString(e.Names[n-1])
	buf.WriteString(`" could not be found on AUR`)
	return buf.String()
}

func IsNotFound(err error) bool {
	_, ok := err.(*NotFoundError)
	return ok
}

// response is what the AUR api returns:
//
//  {
//      "version":5,
//      "type":"multiinfo",
//      "resultcount":1,
//      "results": [
//          {
//              "ID":279276,
//              "Name":"telegram-desktop",
//              "PackageBaseID":95783,
//              "PackageBase":"telegram-desktop",
//              "Version":"0.9.28-1",
//              "Description":"Official desktop version of Telegram messaging app.",
//              "URL":"https:\/\/desktop.telegram.org\/",
//              "NumVotes":43,
//              "Popularity":8.94582,
//              "OutOfDate":null,
//              "Maintainer":"eduardosm",
//              "FirstSubmitted":1436478182,
//              "LastModified":1456515415,
//              "URLPath":"\/cgit\/aur.git\/snapshot\/telegram-desktop.tar.gz",
//              "Depends":["ffmpeg","icu","jasper","libexif","libmng","libwebp",
//                         "libxkbcommon-x11","libinput","libproxy","mtdev",
//                         "openal","libva","desktop-file-utils","gtk-update-icon-cache"],
//              "MakeDepends":["git","patch","libunity","libappindicator-gtk2"]
//              "License":["GPL3"]
//              "Keywords":[]
//          }
//      ]
//  }
type response struct {
	Version     int        `json:"version"`
	Type        string     `json:"type"`
	ResultCount int        `json:"resultcount"`
	Results     []*Package `json:"results"`
}

// Package is the information that we can retrieve about a package that is
// hosted on the Arch Linux User Repository (AUR), version 4.
//
// JSON Example:
//
//	{
//		"ID": 213309,
//		"Name": "repoctl",
//		"PackageBaseID": 96153,
//		"PackageBase": "repoctl",
//		"Version": "0.13-2",
//		"Description": "A supplement to repo-add and repo-remove which simplifies managing local repositories",
//		"URL": "https:\/\/github.com\/cassava\/repoctl",
//		"NumVotes": 1,
//		"OutOfDate": 0,
//		"Maintainer": "cassava",
//		"FirstSubmitted": 1437296687,
//		"LastModified": 1437298275,
//		"License": "MIT",
//		"URLPath": "\/cgit\/aur.git\/snapshot\/repoctl.tar.gz",
//		"CategoryID": 1,
//		"Popularity": 0
//	}
type Package struct {
	ID             uint64
	Name           string
	PackageBaseID  uint64
	PackageBase    string
	Version        string
	Description    string
	URL            string
	NumVotes       int
	Popularity     float64
	OutOfDate      int
	Maintainer     string
	FirstSubmitted uint64
	LastModified   uint64
	URLPath        string
	Depends        []string
	MakeDepends    []string
	License        []string
	Keywords       []string
}

// PacmanPkg converts an aur.Package into a pacman.Package.
//
// Note that only a few fields in the resulting Package are actually filled in,
// namely Origin, Name, Version, Description, URL, and License. This is all the
// information that we are able to retrieve.
func (p *Package) Pkg() *pacman.Package {
	return &pacman.Package{
		Origin:      pacman.AUROrigin,
		Name:        p.Name,
		Base:        p.PackageBase,
		Version:     p.Version,
		Description: p.Description,
		URL:         p.URL,
		// TODO: License is string, but p.License is []string
		// Has there been a format change?
		//License:     p.License,
		Depends:     p.Depends,
		MakeDepends: p.MakeDepends,
	}
}
func (p *Package) PkgName() string    { return p.Name }
func (p *Package) PkgVersion() string { return p.Version }

// DownloadURL returns the URL for downloading the PKGBUILD tarball.
func (p *Package) DownloadURL() string {
	return fmt.Sprintf("https://aur.archlinux.org%s", p.URLPath)
}

const (
	apiURL = "https://aur.archlinux.org/rpc.php?v=5&type=multiinfo&arg[]=%s"
	apiArg = "&arg[]="
)

// generateURL creates a URL that gets the package information from AUR.
func generateURL(args []string) string {
	na := make([]string, len(args))
	for i, s := range args {
		na[i] = url.QueryEscape(s)
	}
	return fmt.Sprintf(apiURL, strings.Join(na, apiArg))
}

// Read reads package information from the Arch Linux User Repository (AUR)
// online.
//
// If a package cannot be found, (nil, *NotFoundError) is returned.
func Read(pkgname string) (*Package, error) {
	q := generateURL([]string{pkgname})
	resp, err := http.Get(q)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var msg response
	dec := json.NewDecoder(resp.Body)
	err = dec.Decode(&msg)
	if err != nil {
		return nil, err
	}
	if msg.ResultCount == 0 {
		return nil, &NotFoundError{Names: []string{pkgname}}
	}
	return msg.Results[0], nil
}

// ReadAll reads multiple packages from the Arch Linux User Repository (AUR)
// at once.
//
// If any packages cannot be found, (Packages, *NotFoundError) is returned.
// That is, all successfully read packages are returned.
func ReadAll(pkgnames []string) (Packages, error) {
	// We can only query at most 63 packages at a time.
	max := 63
	if len(pkgnames) <= max {
		return readAll(pkgnames)
	}

	// len(pkgnames) must be greater or equal to 64, so let's split it up.
	var pkgs Packages
	var err *NotFoundError
	for len(pkgnames) > 0 {
		// Select next slice of messages
		var slice []string
		if len(pkgnames) > max {
			slice = pkgnames[:max]
			pkgnames = pkgnames[max:]
		} else {
			slice = pkgnames
			pkgnames = []string{}
		}

		// Query selected slice of messages
		p, e := readAll(slice)
		if e != nil {
			nfe, ok := e.(*NotFoundError)
			if !ok {
				return nil, e
			}

			if err == nil {
				err = nfe
			} else {
				err.Names = append(err.Names, nfe.Names...)
			}
		}
		pkgs = append(pkgs, p...)
	}
	return pkgs, err
}

func readAll(pkgnames []string) (Packages, error) {
	q := generateURL(pkgnames)
	resp, err := http.Get(q)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var msg response
	dec := json.NewDecoder(resp.Body)
	err = dec.Decode(&msg)
	if err != nil {
		return nil, err
	}
	if msg.ResultCount != len(pkgnames) {
		m := make(map[string]bool)
		nfe := &NotFoundError{
			Names: make([]string, 0, len(pkgnames)-msg.ResultCount),
		}
		for _, i := range msg.Results {
			m[i.Name] = true
		}
		for _, s := range pkgnames {
			if !m[s] {
				nfe.Names = append(nfe.Names, s)
			}
		}
		return msg.Results, nfe
	}
	return msg.Results, nil
}
<commit_msg>Raising limit for aur.readAll from 63 to 200<commit_after>// Copyright (c) 2015, Ben Morgan. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

// Package aur lets you query the Arch Linux User Repository (AUR).
package aur

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/goulash/pacman"
	"github.com/goulash/pacman/alpm"
)

// Packages is a slice of Package with several methods facilitating
// sorting, iterating, and converting to pacman.Packages.
type Packages []*Package

func (pkgs Packages) Len() int      { return len(pkgs) }
func (pkgs Packages) Swap(i, j int) { pkgs[i], pkgs[j] = pkgs[j], pkgs[i] }
func (pkgs Packages) Less(i, j int) bool {
	if pkgs[i].Name != pkgs[j].Name {
		return pkgs[i].Name < pkgs[j].Name
	}
	return alpm.VerCmp(pkgs[i].Version, pkgs[j].Version) == -1
}

// Pkgs returns the entire slice as pacman.Packages.
func (pkgs Packages) Pkgs() pacman.Packages {
	results := make(pacman.Packages, len(pkgs))
	for i, p := range pkgs {
		results[i] = p.Pkg()
	}
	return results
}

// Iterate calls f for each package in the list of packages.
func (pkgs Packages) Iterate(f func(pacman.AnyPackage)) {
	for _, p := range pkgs {
		f(p)
	}
}

// NotFoundError is returned when a package could not be found on AUR.
//
// The error message returned is different dependent on the number of packages
// that could not be found.
type NotFoundError struct {
	Names []string
}

func (e NotFoundError) Error() string {
	n := len(e.Names)
	if n == 1 {
		return fmt.Sprintf("package %q could not be found on AUR", e.Names[0])
	} else if n == 2 {
		return fmt.Sprintf("packages %q and %q could not be found on AUR", e.Names[0], e.Names[1])
	}

	// We have three or more packages that we could not find.
	var buf bytes.Buffer
	buf.WriteString("packages ")
	for _, nam := range e.Names[:n-1] {
		buf.WriteRune('"')
		buf.WriteString(nam)
		buf.WriteString(`", `)
	}
	buf.WriteString(`and "`)
	buf.WriteString(e.Names[n-1])
	buf.WriteString(`" could not be found on AUR`)
	return buf.String()
}

// IsNotFound returns true if the error is an instance of NotFoundError.
func IsNotFound(err error) bool {
	_, ok := err.(*NotFoundError)
	return ok
}

// response is what the AUR api returns:
//
//  {
//      "version":5,
//      "type":"multiinfo",
//      "resultcount":1,
//      "results": [
//          {
//              "ID":279276,
//              "Name":"telegram-desktop",
//              "PackageBaseID":95783,
//              "PackageBase":"telegram-desktop",
//              "Version":"0.9.28-1",
//              "Description":"Official desktop version of Telegram messaging app.",
//              "URL":"https:\/\/desktop.telegram.org\/",
//              "NumVotes":43,
//              "Popularity":8.94582,
//              "OutOfDate":null,
//              "Maintainer":"eduardosm",
//              "FirstSubmitted":1436478182,
//              "LastModified":1456515415,
//              "URLPath":"\/cgit\/aur.git\/snapshot\/telegram-desktop.tar.gz",
//              "Depends":["ffmpeg","icu","jasper","libexif","libmng","libwebp",
//                         "libxkbcommon-x11","libinput","libproxy","mtdev",
//                         "openal","libva","desktop-file-utils","gtk-update-icon-cache"],
//              "MakeDepends":["git","patch","libunity","libappindicator-gtk2"]
//              "License":["GPL3"]
//              "Keywords":[]
//          }
//      ]
//  }
type response struct {
	Version     int        `json:"version"`
	Type        string     `json:"type"`
	ResultCount int        `json:"resultcount"`
	Results     []*Package `json:"results"`
}

// Package is the information that we can retrieve about a package that is
// hosted on the Arch Linux User Repository (AUR), version 4.
//
// JSON Example:
//
//	{
//		"ID": 213309,
//		"Name": "repoctl",
//		"PackageBaseID": 96153,
//		"PackageBase": "repoctl",
//		"Version": "0.13-2",
//		"Description": "A supplement to repo-add and repo-remove which simplifies managing local repositories",
//		"URL": "https:\/\/github.com\/cassava\/repoctl",
//		"NumVotes": 1,
//		"OutOfDate": 0,
//		"Maintainer": "cassava",
//		"FirstSubmitted": 1437296687,
//		"LastModified": 1437298275,
//		"License": "MIT",
//		"URLPath": "\/cgit\/aur.git\/snapshot\/repoctl.tar.gz",
//		"CategoryID": 1,
//		"Popularity": 0
//	}
type Package struct {
	ID             uint64
	Name           string
	PackageBaseID  uint64
	PackageBase    string
	Version        string
	Description    string
	URL            string
	NumVotes       int
	Popularity     float64
	OutOfDate      int
	Maintainer     string
	FirstSubmitted uint64
	LastModified   uint64
	URLPath        string
	Depends        []string
	MakeDepends    []string
	License        []string
	Keywords       []string
}

// Pkg converts an aur.Package into a pacman.Package.
//
// Note that only a few fields in the resulting Package are actually filled in,
// namely Origin, Name, Version, Description, URL, and License. This is all the
// information that we are able to retrieve.
func (p *Package) Pkg() *pacman.Package {
	return &pacman.Package{
		Origin:      pacman.AUROrigin,
		Name:        p.Name,
		Base:        p.PackageBase,
		Version:     p.Version,
		Description: p.Description,
		URL:         p.URL,
		// TODO: License is string, but p.License is []string
		// Has there been a format change?
		//License:     p.License,
		Depends:     p.Depends,
		MakeDepends: p.MakeDepends,
	}
}

// PkgName returns the unique name of the package.
func (p *Package) PkgName() string { return p.Name }

// PkgVersion returns the version string of the package.
func (p *Package) PkgVersion() string { return p.Version }

// DownloadURL returns the URL for downloading the PKGBUILD tarball.
func (p *Package) DownloadURL() string {
	return fmt.Sprintf("https://aur.archlinux.org%s", p.URLPath)
}

const (
	apiURL = "https://aur.archlinux.org/rpc.php?v=5&type=multiinfo&arg[]=%s"
	apiArg = "&arg[]="
)

// generateURL creates a URL that gets the package information from AUR.
func generateURL(args []string) string {
	na := make([]string, len(args))
	for i, s := range args {
		na[i] = url.QueryEscape(s)
	}
	return fmt.Sprintf(apiURL, strings.Join(na, apiArg))
}

// Read reads package information from the Arch Linux User Repository (AUR)
// online.
//
// If a package cannot be found, (nil, *NotFoundError) is returned.
func Read(pkgname string) (*Package, error) {
	q := generateURL([]string{pkgname})
	resp, err := http.Get(q)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var msg response
	dec := json.NewDecoder(resp.Body)
	err = dec.Decode(&msg)
	if err != nil {
		return nil, err
	}
	if msg.ResultCount == 0 {
		return nil, &NotFoundError{Names: []string{pkgname}}
	}
	return msg.Results[0], nil
}

// ReadAll reads multiple packages from the Arch Linux User Repository (AUR)
// at once.
//
// If any packages cannot be found, (Packages, *NotFoundError) is returned.
// That is, all successfully read packages are returned.
func ReadAll(pkgnames []string) (Packages, error) {
	// We only query at most 200 packages at a time, the limit currently
	// appears to be 250, but we'll stay well beneath that for now.
	const limit = 200
	if len(pkgnames) <= limit {
		return readAll(pkgnames)
	}

	var pkgs Packages
	var err *NotFoundError
	for len(pkgnames) > 0 {
		// Select next slice of messages
		var slice []string
		if len(pkgnames) > limit {
			slice = pkgnames[:limit]
			pkgnames = pkgnames[limit:]
		} else {
			slice = pkgnames
			pkgnames = []string{}
		}

		// Query selected slice of messages
		p, e := readAll(slice)
		if e != nil {
			nfe, ok := e.(*NotFoundError)
			if !ok {
				return nil, e
			}

			if err == nil {
				err = nfe
			} else {
				err.Names = append(err.Names, nfe.Names...)
			}
		}
		pkgs = append(pkgs, p...)
	}
	return pkgs, err
}

func readAll(pkgnames []string) (Packages, error) {
	q := generateURL(pkgnames)
	resp, err := http.Get(q)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var msg response
	dec := json.NewDecoder(resp.Body)
	err = dec.Decode(&msg)
	if err != nil {
		return nil, err
	}
	if msg.ResultCount != len(pkgnames) {
		m := make(map[string]bool)
		nfe := &NotFoundError{
			Names: make([]string, 0, len(pkgnames)-msg.ResultCount),
		}
		for _, i := range msg.Results {
			m[i.Name] = true
		}
		for _, s := range pkgnames {
			if !m[s] {
				nfe.Names = append(nfe.Names, s)
			}
		}
		return msg.Results, nfe
	}
	return msg.Results, nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"sync"

	"github.com/zephyyrr/autobot/config"
)

var (
	configuration config.Config
	processes     sync.WaitGroup
	configfile    = flag.String("f", "autobot.toml", "Config filename")
)

func init() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	flag.Parse()
	f, err := os.Open(*configfile)
	if err != nil {
		log.Fatalf("Unable to read open config file %s. %s", *configfile, err)
	}
	configuration, err = config.LoadConfig(f)
	if err != nil {
		log.Fatalln("Unable to parse config file.", err)
	}
}

func main() {
	interrupt := make(chan os.Signal)
	signal.Notify(interrupt, os.Interrupt)

	port := "8080"
	if p := os.Getenv("PORT"); p != "" {
		port = p
	}

	conn, err := net.Listen("tcp", ":"+port)
	if err != nil {
		log.Fatalln("Error listening to port.", err)
	}

	go http.Serve(conn, http.DefaultServeMux)

	select {
	case <-interrupt:
	}

	if err := conn.Close(); err != nil {

	}

	shutdown()
}

func rollOut() {
	// Pull from git
	// Issue build command (read from config)
	// ???
	// Profit!
}

func shutdown() {
	// Allows clean shutdowns.
	processes.Wait()
}
<commit_msg>Added comment outline of rollOut process.<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"net/http"
	"os"
	"os/signal"
	"sync"

	"github.com/zephyyrr/autobot/config"
)

var (
	configuration config.Config
	processes     sync.WaitGroup
	configfile    = flag.String("f", "autobot.toml", "Config filename")
)

func init() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	flag.Parse()
	f, err := os.Open(*configfile)
	if err != nil {
		log.Fatalf("Unable to read open config file %s. %s", *configfile, err)
	}
	configuration, err = config.LoadConfig(f)
	if err != nil {
		log.Fatalln("Unable to parse config file.", err)
	}
}

func main() {
	interrupt := make(chan os.Signal)
	signal.Notify(interrupt, os.Interrupt)

	port := "8080"
	if p := os.Getenv("PORT"); p != "" {
		port = p
	}

	conn, err := net.Listen("tcp", ":"+port)
	if err != nil {
		log.Fatalln("Error listening to port.", err)
	}

	go http.Serve(conn, http.DefaultServeMux)

	select {
	case <-interrupt:
	}

	if err := conn.Close(); err != nil {

	}

	shutdown()
}

func rollOut() {
	// Pull from git
	// Issue build command (read from config)
	// ???
	// Profit!
}

func shutdown() {
	// Allows clean shutdowns.
	processes.Wait()
}

func hookAddress(req *http.Request) string {
	return fmt.Sprintf("%s%s", req.Host, "/hook")
}
<|endoftext|>


<commit_before>package agent

import (
	"flag"
	"fmt"
	"github.com/hashicorp/logutils"
	"github.com/hashicorp/serf/cli"
	"github.com/hashicorp/serf/serf"
	"os"
	"strings"
	"sync"
)

// Command is a Command implementation that runs a Serf agent.
// The command will not end unless a shutdown message is sent on the
// ShutdownCh. If two messages are sent on the ShutdownCh it will forcibly
// exit.
type Command struct {
	ShutdownCh <-chan struct{}

	lock         sync.Mutex
	shuttingDown bool
}

func (c *Command) Help() string {
	helpText := `
Usage: serf agent [options]

  Starts the Serf agent and runs until an interrupt is received. The
  agent represents a single node in a cluster.

Options:

  -bind=0.0.0.0            Address to bind network listeners to
  -event-script=foo        Script to execute when events occur. This can
                           be specified multiple times. See the event scripts
                           section below for more info.
  -log-level=info          Log level of the agent.
  -node=hostname           Name of this node. Must be unique in the cluster
  -role=foo                The role of this node, if any. This can be used
                           by event scripts to differentiate different types
						   of nodes that may be part of the same cluster.
  -rpc-addr=127.0.0.1:7373 Address to bind the RPC listener.

Event scripts:

  For more information on what event scripts are, please read the
  Serf documentation. This section will document how to configure them
  on the command-line. There are three methods of specifying an event
  script:

  - The value can be a plain script, such as "event.sh". In this case,
    Serf will send all events to this script, and you'll be responsible
    for differentiating between them based on the SERF_EVENT.

  - The value can be in the format of "TYPE=SCRIPT", such as
    "member-join=join.sh". With this format, Serf will only send events
    of that type to that script.

  - The value can be in the format of "user:EVENT=SCRIPT", such as
    "user:deploy=deploy.sh". This means that Serf will only invoke this
    script in the case of user events named "deploy".
`
	return strings.TrimSpace(helpText)
}

func (c *Command) Run(args []string, rawUi cli.Ui) int {
	ui := &cli.PrefixedUi{
		OutputPrefix: "==> ",
		InfoPrefix:   "    ",
		ErrorPrefix:  "==> ",
		Ui:           rawUi,
	}

	var bindAddr string
	var logLevel string
	var eventScripts []EventScript
	var nodeName string
	var nodeRole string
	var rpcAddr string

	cmdFlags := flag.NewFlagSet("agent", flag.ContinueOnError)
	cmdFlags.Usage = func() { ui.Output(c.Help()) }
	cmdFlags.StringVar(&bindAddr, "bind", "0.0.0.0", "address to bind listeners to")
	cmdFlags.Var((*FlagEventScripts)(&eventScripts), "event-script",
		"script to execute when events occur")
	cmdFlags.StringVar(&logLevel, "log-level", "INFO", "log level")
	cmdFlags.StringVar(&nodeName, "node", "", "node name")
	cmdFlags.StringVar(&nodeRole, "role", "", "role name")
	cmdFlags.StringVar(&rpcAddr, "rpc-addr", "127.0.0.1:7373",
		"address to bind RPC listener to")
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	for _, script := range eventScripts {
		if !script.Valid() {
			rawUi.Error(fmt.Sprintf("Invalid event script: %s", script.String()))
			return 1
		}
	}

	if nodeName == "" {
		hostname, err := os.Hostname()
		if err != nil {
			rawUi.Error(fmt.Sprintf("Error determining hostname: %s", err))
			return 1
		}

		nodeName = hostname
	}

	// Setup logging. First create the gated log writer, which will
	// store logs until we're ready to show them. Then create the level
	// filter, filtering logs of the specified level.
	logGate := &GatedWriter{
		Writer: &cli.UiWriter{Ui: rawUi},
	}

	logLevelFilter := LevelFilter()
	logLevelFilter.MinLevel = logutils.LogLevel(strings.ToUpper(logLevel))
	logLevelFilter.Writer = logGate
	if !ValidateLevelFilter(logLevelFilter) {
		ui.Error(fmt.Sprintf(
			"Invalid log level: %s. Valid log levels are: %v",
			logLevelFilter.MinLevel, logLevelFilter.Levels))
		return 1
	}

	config := serf.DefaultConfig()
	config.MemberlistConfig.BindAddr = bindAddr
	config.NodeName = nodeName
	config.Role = nodeRole

	agent := &Agent{
		EventHandler: &ScriptEventHandler{
			Self: serf.Member{
				Name: config.NodeName,
				Role: config.Role,
			},
			Scripts: eventScripts,
		},
		LogOutput:  logLevelFilter,
		RPCAddr:    rpcAddr,
		SerfConfig: config,
	}

	ui.Output("Starting Serf agent...")
	if err := agent.Start(); err != nil {
		ui.Error(err.Error())
		return 1
	}

	ui.Output("Serf agent running!")
	ui.Info(fmt.Sprintf("Node name: '%s'", config.NodeName))
	ui.Info(fmt.Sprintf("Bind addr: '%s'", config.MemberlistConfig.BindAddr))
	ui.Info(fmt.Sprintf(" RPC addr: '%s'", rpcAddr))
	ui.Info("")
	ui.Output("Log data will now stream in as it occurs:\n")
	logGate.Flush()

	graceful, forceful := c.startShutdownWatcher(agent, ui)
	select {
	case <-graceful:
	case <-forceful:
		// Forcefully shut down, return a bad exit status.
		return 1
	}

	return 0
}

func (c *Command) Synopsis() string {
	return "Runs a Serf agent"
}

func (c *Command) startShutdownWatcher(agent *Agent, ui cli.Ui) (graceful <-chan struct{}, forceful <-chan struct{}) {
	g := make(chan struct{})
	f := make(chan struct{})
	graceful = g
	forceful = f

	go func() {
		<-c.ShutdownCh

		c.lock.Lock()
		c.shuttingDown = true
		c.lock.Unlock()

		ui.Output("Gracefully shutting down agent...")
		go func() {
			if err := agent.Shutdown(); err != nil {
				ui.Error(fmt.Sprintf("Error: %s", err))
				return
			}
			close(g)
		}()

		select {
		case <-g:
			// Gracefully shut down properly
		case <-c.ShutdownCh:
			close(f)
		}
	}()

	return
}
<commit_msg>command/agent: fix odd spacing in help message<commit_after>package agent

import (
	"flag"
	"fmt"
	"github.com/hashicorp/logutils"
	"github.com/hashicorp/serf/cli"
	"github.com/hashicorp/serf/serf"
	"os"
	"strings"
	"sync"
)

// Command is a Command implementation that runs a Serf agent.
// The command will not end unless a shutdown message is sent on the
// ShutdownCh. If two messages are sent on the ShutdownCh it will forcibly
// exit.
type Command struct {
	ShutdownCh <-chan struct{}

	lock         sync.Mutex
	shuttingDown bool
}

func (c *Command) Help() string {
	helpText := `
Usage: serf agent [options]

  Starts the Serf agent and runs until an interrupt is received. The
  agent represents a single node in a cluster.

Options:

  -bind=0.0.0.0            Address to bind network listeners to
  -event-script=foo        Script to execute when events occur. This can
                           be specified multiple times. See the event scripts
                           section below for more info.
  -log-level=info          Log level of the agent.
  -node=hostname           Name of this node. Must be unique in the cluster
  -role=foo                The role of this node, if any. This can be used
                           by event scripts to differentiate different types
                           of nodes that may be part of the same cluster.
  -rpc-addr=127.0.0.1:7373 Address to bind the RPC listener.

Event scripts:

  For more information on what event scripts are, please read the
  Serf documentation. This section will document how to configure them
  on the command-line. There are three methods of specifying an event
  script:

  - The value can be a plain script, such as "event.sh". In this case,
    Serf will send all events to this script, and you'll be responsible
    for differentiating between them based on the SERF_EVENT.

  - The value can be in the format of "TYPE=SCRIPT", such as
    "member-join=join.sh". With this format, Serf will only send events
    of that type to that script.

  - The value can be in the format of "user:EVENT=SCRIPT", such as
    "user:deploy=deploy.sh". This means that Serf will only invoke this
    script in the case of user events named "deploy".
`
	return strings.TrimSpace(helpText)
}

func (c *Command) Run(args []string, rawUi cli.Ui) int {
	ui := &cli.PrefixedUi{
		OutputPrefix: "==> ",
		InfoPrefix:   "    ",
		ErrorPrefix:  "==> ",
		Ui:           rawUi,
	}

	var bindAddr string
	var logLevel string
	var eventScripts []EventScript
	var nodeName string
	var nodeRole string
	var rpcAddr string

	cmdFlags := flag.NewFlagSet("agent", flag.ContinueOnError)
	cmdFlags.Usage = func() { ui.Output(c.Help()) }
	cmdFlags.StringVar(&bindAddr, "bind", "0.0.0.0", "address to bind listeners to")
	cmdFlags.Var((*FlagEventScripts)(&eventScripts), "event-script",
		"script to execute when events occur")
	cmdFlags.StringVar(&logLevel, "log-level", "INFO", "log level")
	cmdFlags.StringVar(&nodeName, "node", "", "node name")
	cmdFlags.StringVar(&nodeRole, "role", "", "role name")
	cmdFlags.StringVar(&rpcAddr, "rpc-addr", "127.0.0.1:7373",
		"address to bind RPC listener to")
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	for _, script := range eventScripts {
		if !script.Valid() {
			rawUi.Error(fmt.Sprintf("Invalid event script: %s", script.String()))
			return 1
		}
	}

	if nodeName == "" {
		hostname, err := os.Hostname()
		if err != nil {
			rawUi.Error(fmt.Sprintf("Error determining hostname: %s", err))
			return 1
		}

		nodeName = hostname
	}

	// Setup logging. First create the gated log writer, which will
	// store logs until we're ready to show them. Then create the level
	// filter, filtering logs of the specified level.
	logGate := &GatedWriter{
		Writer: &cli.UiWriter{Ui: rawUi},
	}

	logLevelFilter := LevelFilter()
	logLevelFilter.MinLevel = logutils.LogLevel(strings.ToUpper(logLevel))
	logLevelFilter.Writer = logGate
	if !ValidateLevelFilter(logLevelFilter) {
		ui.Error(fmt.Sprintf(
			"Invalid log level: %s. Valid log levels are: %v",
			logLevelFilter.MinLevel, logLevelFilter.Levels))
		return 1
	}

	config := serf.DefaultConfig()
	config.MemberlistConfig.BindAddr = bindAddr
	config.NodeName = nodeName
	config.Role = nodeRole

	agent := &Agent{
		EventHandler: &ScriptEventHandler{
			Self: serf.Member{
				Name: config.NodeName,
				Role: config.Role,
			},
			Scripts: eventScripts,
		},
		LogOutput:  logLevelFilter,
		RPCAddr:    rpcAddr,
		SerfConfig: config,
	}

	ui.Output("Starting Serf agent...")
	if err := agent.Start(); err != nil {
		ui.Error(err.Error())
		return 1
	}

	ui.Output("Serf agent running!")
	ui.Info(fmt.Sprintf("Node name: '%s'", config.NodeName))
	ui.Info(fmt.Sprintf("Bind addr: '%s'", config.MemberlistConfig.BindAddr))
	ui.Info(fmt.Sprintf(" RPC addr: '%s'", rpcAddr))
	ui.Info("")
	ui.Output("Log data will now stream in as it occurs:\n")
	logGate.Flush()

	graceful, forceful := c.startShutdownWatcher(agent, ui)
	select {
	case <-graceful:
	case <-forceful:
		// Forcefully shut down, return a bad exit status.
		return 1
	}

	return 0
}

func (c *Command) Synopsis() string {
	return "Runs a Serf agent"
}

func (c *Command) startShutdownWatcher(agent *Agent, ui cli.Ui) (graceful <-chan struct{}, forceful <-chan struct{}) {
	g := make(chan struct{})
	f := make(chan struct{})
	graceful = g
	forceful = f

	go func() {
		<-c.ShutdownCh

		c.lock.Lock()
		c.shuttingDown = true
		c.lock.Unlock()

		ui.Output("Gracefully shutting down agent...")
		go func() {
			if err := agent.Shutdown(); err != nil {
				ui.Error(fmt.Sprintf("Error: %s", err))
				return
			}
			close(g)
		}()

		select {
		case <-g:
			// Gracefully shut down properly
		case <-c.ShutdownCh:
			close(f)
		}
	}()

	return
}
<|endoftext|>