<commit_before>package main

import (
	"code.google.com/p/go.net/html"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/mediocregopher/growler/config"
	"github.com/mediocregopher/growler/tracker"
)

var parseSearch = map[string]string{
	"a":   "href",
	"img": "src",
}

var rootURL *url.URL
var rootPath string
var wg sync.WaitGroup

func init() {
	log.Printf("Setting GOMAXPRROCS to %d", config.NumProcs)
	runtime.GOMAXPROCS(config.NumProcs)

	var err error
	rootReq, err := http.NewRequest("GET", config.Src, nil)
	if err != nil {
		log.Fatalf("parsing rootReq: %s", err)
	}
	rootURL = rootReq.URL
	rootPath = path.Clean(rootURL.Path)
}

func extractLinks(body io.Reader) ([]string, error) {
	t := html.NewTokenizer(body)
	ret := make([]string, 0, 16)

tokenLoop:
	for {
		tt := t.Next()
		switch tt {

		case html.ErrorToken:
			if err := t.Err(); err == io.EOF {
				break tokenLoop
			} else {
				return nil, err
			}

		case html.StartTagToken:
			tagNameB, hasAttr := t.TagName()
			if !hasAttr {
				continue tokenLoop
			}
			searchAttr, ok := parseSearch[string(tagNameB)]
			if !ok {
				continue tokenLoop
			}
			for {
				attr, val, moreAttr := t.TagAttr()
				if string(attr) == searchAttr {
					ret = append(ret, string(val))
				} else if !moreAttr {
					continue tokenLoop
				}
			}
		}
	}

	return ret, nil
}

func lastChar(s string) byte {
	return s[len(s)-1]
}

func getFilePath(u *url.URL) (string, error) {
	relPath, err := filepath.Rel(rootPath, u.Path)
	if err != nil {
		return "", err
	}

	filePath := path.Join(config.Dst, relPath)
	filePathLast := lastChar(filePath)
	if lastChar(u.Path) == '/' && filePathLast != '/' {
		filePath += "/"
		filePathLast = '/'
	}

	if filePathLast == '/' {
		filePath += "index.html"
	}
	return filePath, nil
}

type downloader struct {
	i int
}

func (d *downloader) Println(args ...interface{}) {
	fargs := make([]interface{}, 0, len(args) + 2)
	fargs = append(fargs, d.i, "-")
	fargs = append(fargs, args...)
	log.Print(fargs...)
}

func (d *downloader) Printf(format string, args ...interface{}) {
	fargs := make([]interface{}, 0, len(args) + 1)
	fargs = append(fargs, d.i)
	fargs = append(fargs, args...)
	log.Printf("%d - " + format, fargs...)
}

func (d *downloader) Fatal(args ...interface{}) {
	fargs := make([]interface{}, 0, len(args) + 2)
	fargs = append(fargs, d.i, "-")
	fargs = append(fargs, args...)
	log.Fatal(fargs...)
}

func (d *downloader) Fatalf(format string, args ...interface{}) {
	fargs := make([]interface{}, 0, len(args) + 1)
	fargs = append(fargs, d.i)
	fargs = append(fargs, args...)
	log.Fatalf("%d - " + format, fargs...)
}

// Does a GET to retrieve the file from disk and returns the io.ReadCloser for
// the body. Also returns whether or not the body should be written to disk
// (always true).
func (d *downloader) getPage(
	client *http.Client, page *url.URL,
) (
	*http.Response, io.ReadCloser, string, bool, error,
) {
	d.Printf("GET %s", page)
	r, err := client.Get(page.String())
	if err != nil {
		return nil, nil, "", true, err
	}
	filePath, err := getFilePath(r.Request.URL)
	if err != nil {
		return nil, nil, "", true, err
	}
	return r, r.Body, filePath, true, nil
}

// Does the processing necessary to know if a file should be actually downloaded
// or not. May include a HEAD call to the server if the file already exists in
// some form on disk. Returns:
// * The http.Response from the last HEAD or GET performed
// * an io.ReadCloser which must be closed once read from
// * the filePath to use
// * whether the contents of the io.ReadCloser should be written to the file for
//   the page.
func (d *downloader) maybeGetPage(
	client *http.Client, page *url.URL,
) (
	*http.Response, io.ReadCloser, string, bool, error,
) {

	var filestat os.FileInfo
	var filePath string
	var err error

	filePath, err = getFilePath(page)
	if err != nil {
		return nil, nil, "", false, err
	}

	if config.ForceDownload {
		return d.getPage(client, page)
	}

	filestat, err = os.Stat(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			return d.getPage(client, page)
		} else {
			return nil, nil, "", false, err
		}
	}

	d.Printf("HEAD %s", page)
	r, err := client.Head(page.String())
	if err != nil {
		return nil, nil, "", false, err
	}
	drainAndClose(r.Body) // HEAD response has no body, but we Close anyway

	// We recompute the filePath and filestat in case the HEAD got redirected
	// and we're actually looking at a different file now
	filePath, err = getFilePath(page)
	if err != nil {
		return nil, nil, "", false, err
	}

	filestat, err = os.Stat(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			return d.getPage(client, page)
		} else {
			return nil, nil, "", false, err
		}
	}

	lmStr := r.Header.Get("Last-Modified")
	// We don't want to make any assumptions if we don't know the last modified
	// time
	if lmStr == "" {
		return d.getPage(client, page)
	}

	lm, err := time.Parse(time.RFC1123, lmStr)
	if err != nil {
		d.Printf("error parsing last modified (%s); %s", lmStr, err)
		return d.getPage(client, page)
	}

	if lm.After(filestat.ModTime()) {
		return d.getPage(client, page)
	}

	if r.ContentLength != filestat.Size() {
		return d.getPage(client, page)
	}

	f, err := os.Open(filePath)
	if err != nil {
		return nil, nil, "", false, err
	}
	return r, f, filePath, false, nil
}

func drainAndClose(b io.ReadCloser) {
	io.Copy(ioutil.Discard, b)
	b.Close()
}

func (d *downloader) processPage(
	client *http.Client, page *url.URL,
) (
	retURLs []*url.URL,
) {
	retURLs = make([]*url.URL, 0, 0)
	page.Path = path.Clean(page.Path)

	if !tracker.CanFetch(page.Path) {
		return
	}

	d.Printf("processesing %s", page)

	r, body, filePath, store, err := d.maybeGetPage(client, page)
	if err != nil {
		d.Printf("getPage: %s err: %s", page, err)
		return
	}
	defer drainAndClose(body)

	// page might have changed inside of maybeGetPage due to redirects
	page = r.Request.URL

	var bodyReader io.Reader
	if store {
		if _, ok := config.ExcludeFiles[path.Base(filePath)]; ok {
			d.Printf("skipping %s because exclude", filePath)
			bodyReader = body
		} else {
			d.Printf("storing as %s", filePath)
			fileDir := path.Dir(filePath)
			if err := os.MkdirAll(fileDir, 0755); err != nil {
				d.Fatalf("MkdirAll(%s) err: %s", fileDir, err)
				return
			}

			f, err := os.Create(filePath)
			if err != nil {
				d.Fatalf("Create(%s) err: %s", filePath, err)
			}
			defer f.Close()
			bodyReader = io.TeeReader(body, f)
		}
	} else {
		bodyReader = body
	}

	// At this point reading from bodyReader will also write to the
	// corresponding file for this page on the filesystem, if necessary. In the
	// next part we only extractLinks from a page which is html, otherwise we
	// io.Copy into an ioutil.Discard to "read" the whole page, so that we write
	// to the page's file if necessary.

	if r.Header.Get("Content-Type") != "text/html" {
		if _, err := io.Copy(ioutil.Discard, bodyReader); err != nil {
			d.Printf("Copy page: %s err: %s", page, err)
		}
		return
	}

	links, err := extractLinks(bodyReader)
	if err != nil {
		d.Printf("extractLinks page: %s err: %s", page, err)
		return
	}

	retURLs = make([]*url.URL, 0, len(links))
	for _, link := range links {
		linkURL, err := url.Parse(link)
		if err != nil {
			d.Printf("url.Parse link: %s page: %s err: %s", link, page, err)
			return
		}

		absURL := page.ResolveReference(linkURL)
		if strings.HasPrefix(path.Clean(absURL.Path), rootPath) {
			retURLs = append(retURLs, absURL)
		}
	}

	return retURLs
}

func (d *downloader) crawl() {
	client := new(http.Client)
	client.Transport = &http.Transport{
		MaxIdleConnsPerHost: 100,
	}

	for i := 0; i < 3; {
		page := tracker.FreeLink()
		if page == nil {
			i++
			d.Printf("downloader waiting (%d)", i)
			time.Sleep(30 * time.Second)
			continue
		}
		i = 0

		retURLs := d.processPage(client, page)
		if len(retURLs) == 0 {
			continue
		}
		tracker.AddFreeLinks(retURLs)
	}

	d.Println("downloader routine done")
	wg.Done()
}

func main() {
	srcURL, err := url.ParseRequestURI(config.Src)
	if err != nil {
		log.Fatalf("parsing src: %s", err)
	}

	tracker.AddFreeLinks([]*url.URL{srcURL})

	log.Printf("Spawning %d downloaders", config.NumDownloaders)
	for i := 0; i < config.NumDownloaders; i++ {
		d := downloader{i}
		go d.crawl()
		wg.Add(1)
	}

	wg.Wait()
}
<commit_msg>fix some small bugs<commit_after>package main

import (
	"code.google.com/p/go.net/html"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"time"

	"github.com/mediocregopher/growler/config"
	"github.com/mediocregopher/growler/tracker"
)

var parseSearch = map[string]string{
	"a":   "href",
	"img": "src",
}

var rootURL *url.URL
var rootPath string
var wg sync.WaitGroup

func init() {
	log.Printf("Setting GOMAXPRROCS to %d", config.NumProcs)
	runtime.GOMAXPROCS(config.NumProcs)

	var err error
	rootReq, err := http.NewRequest("GET", config.Src, nil)
	if err != nil {
		log.Fatalf("parsing rootReq: %s", err)
	}
	rootURL = rootReq.URL
	rootPath = path.Clean(rootURL.Path)
}

func extractLinks(body io.Reader) ([]string, error) {
	t := html.NewTokenizer(body)
	ret := make([]string, 0, 16)

tokenLoop:
	for {
		tt := t.Next()
		switch tt {

		case html.ErrorToken:
			if err := t.Err(); err == io.EOF {
				break tokenLoop
			} else {
				return nil, err
			}

		case html.StartTagToken:
			tagNameB, hasAttr := t.TagName()
			if !hasAttr {
				continue tokenLoop
			}
			searchAttr, ok := parseSearch[string(tagNameB)]
			if !ok {
				continue tokenLoop
			}
			for {
				attr, val, moreAttr := t.TagAttr()
				if string(attr) == searchAttr {
					ret = append(ret, string(val))
				} else if !moreAttr {
					continue tokenLoop
				}
			}
		}
	}

	return ret, nil
}

func lastChar(s string) byte {
	return s[len(s)-1]
}

func getFilePath(u *url.URL) (string, error) {
	relPath, err := filepath.Rel(rootPath, u.Path)
	if err != nil {
		return "", err
	}

	filePath := path.Join(config.Dst, relPath)
	filePathLast := lastChar(filePath)
	if lastChar(u.Path) == '/' && filePathLast != '/' {
		filePath += "/"
		filePathLast = '/'
	}

	if filePathLast == '/' {
		filePath += "index.html"
	}
	return filePath, nil
}

type downloader struct {
	i int
}

func (d *downloader) Println(args ...interface{}) {
	fargs := make([]interface{}, 0, len(args) + 2)
	fargs = append(fargs, d.i, " - ")
	fargs = append(fargs, args...)
	log.Print(fargs...)
}

func (d *downloader) Printf(format string, args ...interface{}) {
	fargs := make([]interface{}, 0, len(args) + 1)
	fargs = append(fargs, d.i)
	fargs = append(fargs, args...)
	log.Printf("%d - " + format, fargs...)
}

func (d *downloader) Fatal(args ...interface{}) {
	fargs := make([]interface{}, 0, len(args) + 2)
	fargs = append(fargs, d.i, "-")
	fargs = append(fargs, args...)
	log.Fatal(fargs...)
}

func (d *downloader) Fatalf(format string, args ...interface{}) {
	fargs := make([]interface{}, 0, len(args) + 1)
	fargs = append(fargs, d.i)
	fargs = append(fargs, args...)
	log.Fatalf("%d - " + format, fargs...)
}

// Does a GET to retrieve the file from disk and returns the io.ReadCloser for
// the body. Also returns whether or not the body should be written to disk
// (always true).
func (d *downloader) getPage(
	client *http.Client, page *url.URL,
) (
	*http.Response, io.ReadCloser, string, bool, error,
) {
	d.Printf("GET %s", page)
	r, err := client.Get(page.String())
	if err != nil {
		return nil, nil, "", true, err
	}
	filePath, err := getFilePath(r.Request.URL)
	if err != nil {
		return nil, nil, "", true, err
	}
	return r, r.Body, filePath, true, nil
}

// Does the processing necessary to know if a file should be actually downloaded
// or not. May include a HEAD call to the server if the file already exists in
// some form on disk. Returns:
// * The http.Response from the last HEAD or GET performed
// * an io.ReadCloser which must be closed once read from
// * the filePath to use
// * whether the contents of the io.ReadCloser should be written to the file for
//   the page.
func (d *downloader) maybeGetPage(
	client *http.Client, page *url.URL,
) (
	*http.Response, io.ReadCloser, string, bool, error,
) {

	var filestat os.FileInfo
	var filePath string
	var err error

	filePath, err = getFilePath(page)
	if err != nil {
		return nil, nil, "", false, err
	}

	if config.ForceDownload {
		return d.getPage(client, page)
	}

	filestat, err = os.Stat(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			return d.getPage(client, page)
		} else {
			return nil, nil, "", false, err
		}
	}

	d.Printf("HEAD %s", page)
	r, err := client.Head(page.String())
	if err != nil {
		return nil, nil, "", false, err
	}
	drainAndClose(r.Body) // HEAD response has no body, but we Close anyway

	// We recompute the filePath and filestat in case the HEAD got redirected
	// and we're actually looking at a different file now
	filePath, err = getFilePath(page)
	if err != nil {
		return nil, nil, "", false, err
	}

	filestat, err = os.Stat(filePath)
	if err != nil {
		if os.IsNotExist(err) {
			return d.getPage(client, page)
		} else {
			return nil, nil, "", false, err
		}
	}

	lmStr := r.Header.Get("Last-Modified")
	// We don't want to make any assumptions if we don't know the last modified
	// time
	if lmStr == "" {
		return d.getPage(client, page)
	}

	lm, err := time.Parse(time.RFC1123, lmStr)
	if err != nil {
		d.Printf("error parsing last modified (%s); %s", lmStr, err)
		return d.getPage(client, page)
	}

	if lm.After(filestat.ModTime()) {
		return d.getPage(client, page)
	}

	if r.ContentLength != filestat.Size() {
		return d.getPage(client, page)
	}

	f, err := os.Open(filePath)
	if err != nil {
		return nil, nil, "", false, err
	}
	return r, f, filePath, false, nil
}

func drainAndClose(b io.ReadCloser) {
	io.Copy(ioutil.Discard, b)
	b.Close()
}

func (d *downloader) processPage(
	client *http.Client, page *url.URL,
) (
	retURLs []*url.URL,
) {
	retURLs = make([]*url.URL, 0, 0)
	page.Path = path.Clean(page.Path)

	if !tracker.CanFetch(page.Path) {
		return
	}

	d.Printf("processesing %s", page)

	r, body, filePath, store, err := d.maybeGetPage(client, page)
	if err != nil {
		d.Printf("getPage: %s err: %s", page, err)
		return
	}
	defer drainAndClose(body)

	// page might have changed inside of maybeGetPage due to redirects
	page = r.Request.URL

	var bodyReader io.Reader
	if store {
		if _, ok := config.ExcludeFiles[path.Base(filePath)]; ok {
			d.Printf("skipping %s because exclude", filePath)
			bodyReader = body
		} else {
			d.Printf("storing as %s", filePath)
			fileDir := path.Dir(filePath)
			if err := os.MkdirAll(fileDir, 0755); err != nil {
				d.Fatalf("MkdirAll(%s) err: %s", fileDir, err)
				return
			}

			f, err := os.Create(filePath)
			if err != nil {
				d.Fatalf("Create(%s) err: %s", filePath, err)
			}
			defer f.Close()
			bodyReader = io.TeeReader(body, f)
		}
	} else {
		bodyReader = body
	}

	// At this point reading from bodyReader will also write to the
	// corresponding file for this page on the filesystem, if necessary. In the
	// next part we only extractLinks from a page which is html, otherwise we
	// io.Copy into an ioutil.Discard to "read" the whole page, so that we write
	// to the page's file if necessary.

	if strings.Index(r.Header.Get("Content-Type"), "text/html") != 0 {
		if _, err := io.Copy(ioutil.Discard, bodyReader); err != nil {
			d.Printf("Copy page: %s err: %s", page, err)
		}
		return
	}

	links, err := extractLinks(bodyReader)
	if err != nil {
		d.Printf("extractLinks page: %s err: %s", page, err)
		return
	}

	retURLs = make([]*url.URL, 0, len(links))
	for _, link := range links {
		linkURL, err := url.Parse(link)
		if err != nil {
			d.Printf("url.Parse link: %s page: %s err: %s", link, page, err)
			return
		}

		absURL := page.ResolveReference(linkURL)
		if strings.HasPrefix(path.Clean(absURL.Path), rootPath) {
			retURLs = append(retURLs, absURL)
		}
	}

	return retURLs
}

func (d *downloader) crawl() {
	client := new(http.Client)
	client.Transport = &http.Transport{
		MaxIdleConnsPerHost: 100,
	}

	for i := 0; i < 3; {
		page := tracker.FreeLink()
		if page == nil {
			i++
			d.Printf("downloader waiting (%d)", i)
			time.Sleep(30 * time.Second)
			continue
		}
		i = 0

		retURLs := d.processPage(client, page)
		if len(retURLs) == 0 {
			continue
		}
		tracker.AddFreeLinks(retURLs)
	}

	d.Println("downloader routine done")
	wg.Done()
}

func main() {
	srcURL, err := url.ParseRequestURI(config.Src)
	if err != nil {
		log.Fatalf("parsing src: %s", err)
	}

	tracker.AddFreeLinks([]*url.URL{srcURL})

	log.Printf("Spawning %d downloaders", config.NumDownloaders)
	for i := 0; i < config.NumDownloaders; i++ {
		d := downloader{i}
		go d.crawl()
		wg.Add(1)
	}

	wg.Wait()
}
<|endoftext|>


<commit_before>package main

import (
	"os"

	"github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	"github.com/opencontainers/specs"
)

const (
	version       = "0.3"
	specConfig    = "config.json"
	runtimeConfig = "runtime.json"
	usage         = `Open Container Initiative runtime

runc is a command line client for running applications packaged according to
the Open Container Format (OCF) and is a compliant implementation of the
Open Container Initiative specification.

runc integrates well with existing process supervisors to provide a production
container runtime environment for applications. It can be used with your
existing process monitoring tools and the container will be spawned as a
direct child of the process supervisor.

After creating config files for your root filesystem with runc, you can execute
a container in your shell by running:

    # cd /mycontainer
    # runc start [ -b bundle ]

If not specified, the default value for the 'bundle' is the current directory.
'Bundle' is the directory where '` + specConfig + `' and '` + runtimeConfig + `' must be located.`
)

func main() {
	app := cli.NewApp()
	app.Name = "runc"
	app.Usage = usage
	app.Version = version
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "id",
			Value: getDefaultID(),
			Usage: "specify the ID to be used for the container",
		},
		cli.BoolFlag{
			Name:  "debug",
			Usage: "enable debug output for logging",
		},
		cli.StringFlag{
			Name:  "log",
			Usage: "set the log file path where internal debug information is written",
		},
		cli.StringFlag{
			Name:  "log-format",
			Value: "text",
			Usage: "set the format used by logs ('text' (default), or 'json')",
		},
		cli.StringFlag{
			Name:  "root",
			Value: specs.LinuxStateDirectory,
			Usage: "root directory for storage of container state (this should be located in tmpfs)",
		},
		cli.StringFlag{
			Name:  "criu",
			Value: "criu",
			Usage: "path to the criu binary used for checkpoint and restore",
		},
	}
	app.Commands = []cli.Command{
		startCommand,
		checkpointCommand,
		eventsCommand,
		restoreCommand,
		killCommand,
		specCommand,
		pauseCommand,
		resumeCommand,
		execCommand,
	}
	app.Before = func(context *cli.Context) error {
		if context.GlobalBool("debug") {
			logrus.SetLevel(logrus.DebugLevel)
		}
		if path := context.GlobalString("log"); path != "" {
			f, err := os.Create(path)
			if err != nil {
				return err
			}
			logrus.SetOutput(f)
		}
		switch context.GlobalString("log-format") {
		case "text":
			// retain logrus's default.
		case "json":
			logrus.SetFormatter(new(logrus.JSONFormatter))
		default:
			logrus.Fatalf("unknown log-format %q", context.GlobalString("log-format"))
		}
		return nil
	}
	if err := app.Run(os.Args); err != nil {
		logrus.Fatal(err)
	}
}
<commit_msg>Add spec version to runC version cli<commit_after>package main

import (
	"fmt"
	"os"

	"github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	"github.com/opencontainers/specs"
)

const (
	version       = "0.3"
	specConfig    = "config.json"
	runtimeConfig = "runtime.json"
	usage         = `Open Container Initiative runtime

runc is a command line client for running applications packaged according to
the Open Container Format (OCF) and is a compliant implementation of the
Open Container Initiative specification.

runc integrates well with existing process supervisors to provide a production
container runtime environment for applications. It can be used with your
existing process monitoring tools and the container will be spawned as a
direct child of the process supervisor.

After creating config files for your root filesystem with runc, you can execute
a container in your shell by running:

    # cd /mycontainer
    # runc start [ -b bundle ]

If not specified, the default value for the 'bundle' is the current directory.
'Bundle' is the directory where '` + specConfig + `' and '` + runtimeConfig + `' must be located.`
)

func main() {
	app := cli.NewApp()
	app.Name = "runc"
	app.Usage = usage
	app.Version = fmt.Sprintf("%s\nspec version %s", version, specs.Version)
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "id",
			Value: getDefaultID(),
			Usage: "specify the ID to be used for the container",
		},
		cli.BoolFlag{
			Name:  "debug",
			Usage: "enable debug output for logging",
		},
		cli.StringFlag{
			Name:  "log",
			Usage: "set the log file path where internal debug information is written",
		},
		cli.StringFlag{
			Name:  "log-format",
			Value: "text",
			Usage: "set the format used by logs ('text' (default), or 'json')",
		},
		cli.StringFlag{
			Name:  "root",
			Value: specs.LinuxStateDirectory,
			Usage: "root directory for storage of container state (this should be located in tmpfs)",
		},
		cli.StringFlag{
			Name:  "criu",
			Value: "criu",
			Usage: "path to the criu binary used for checkpoint and restore",
		},
	}
	app.Commands = []cli.Command{
		startCommand,
		checkpointCommand,
		eventsCommand,
		restoreCommand,
		killCommand,
		specCommand,
		pauseCommand,
		resumeCommand,
		execCommand,
	}
	app.Before = func(context *cli.Context) error {
		if context.GlobalBool("debug") {
			logrus.SetLevel(logrus.DebugLevel)
		}
		if path := context.GlobalString("log"); path != "" {
			f, err := os.Create(path)
			if err != nil {
				return err
			}
			logrus.SetOutput(f)
		}
		switch context.GlobalString("log-format") {
		case "text":
			// retain logrus's default.
		case "json":
			logrus.SetFormatter(new(logrus.JSONFormatter))
		default:
			logrus.Fatalf("unknown log-format %q", context.GlobalString("log-format"))
		}
		return nil
	}
	if err := app.Run(os.Args); err != nil {
		logrus.Fatal(err)
	}
}
<|endoftext|>


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

package main

import (
	"flag"
	"os"
	"os/signal"
	"sync"
	"syscall"
	"time"

	"github.com/golang/glog"
	"github.com/prometheus/client_golang/extraction"

	clientmodel "github.com/prometheus/client_golang/model"

	"github.com/prometheus/prometheus/config"
	"github.com/prometheus/prometheus/notification"
	"github.com/prometheus/prometheus/retrieval"
	"github.com/prometheus/prometheus/rules"
	"github.com/prometheus/prometheus/storage/metric"
	"github.com/prometheus/prometheus/web"
	"github.com/prometheus/prometheus/web/api"
)

const deletionBatchSize = 100

// Commandline flags.
var (
	configFile         = flag.String("configFile", "prometheus.conf", "Prometheus configuration file name.")
	metricsStoragePath = flag.String("metricsStoragePath", "/tmp/metrics", "Base path for metrics storage.")

	alertmanagerUrl = flag.String("alertmanager.url", "", "The URL of the alert manager to send notifications to.")

	samplesQueueCapacity      = flag.Int("storage.queue.samplesCapacity", 4096, "The size of the unwritten samples queue.")
	diskAppendQueueCapacity   = flag.Int("storage.queue.diskAppendCapacity", 1000000, "The size of the queue for items that are pending writing to disk.")
	memoryAppendQueueCapacity = flag.Int("storage.queue.memoryAppendCapacity", 10000, "The size of the queue for items that are pending writing to memory.")

	headCompactInterval = flag.Duration("compact.headInterval", 10*3*time.Minute, "The amount of time between head compactions.")
	bodyCompactInterval = flag.Duration("compact.bodyInterval", 10*5*time.Minute, "The amount of time between body compactions.")
	tailCompactInterval = flag.Duration("compact.tailInterval", 10*7*time.Minute, "The amount of time between tail compactions.")

	headGroupSize = flag.Int("compact.headGroupSize", 50, "The minimum group size for head samples.")
	bodyGroupSize = flag.Int("compact.bodyGroupSize", 250, "The minimum group size for body samples.")
	tailGroupSize = flag.Int("compact.tailGroupSize", 5000, "The minimum group size for tail samples.")

	headAge = flag.Duration("compact.headAgeInclusiveness", 5*time.Minute, "The relative inclusiveness of head samples.")
	bodyAge = flag.Duration("compact.bodyAgeInclusiveness", time.Hour, "The relative inclusiveness of body samples.")
	tailAge = flag.Duration("compact.tailAgeInclusiveness", 24*time.Hour, "The relative inclusiveness of tail samples.")

	deleteInterval = flag.Duration("delete.interval", 10*11*time.Minute, "The amount of time between deletion of old values.")

	deleteAge = flag.Duration("delete.ageMaximum", 10*24*time.Hour, "The relative maximum age for values before they are deleted.")

	arenaFlushInterval = flag.Duration("arena.flushInterval", 15*time.Minute, "The period at which the in-memory arena is flushed to disk.")
	arenaTTL           = flag.Duration("arena.ttl", 10*time.Minute, "The relative age of values to purge to disk from memory.")

	notificationQueueCapacity = flag.Int("alertmanager.notificationQueueCapacity", 100, "The size of the queue for pending alert manager notifications.")

	concurrentRetrievalAllowance = flag.Int("concurrentRetrievalAllowance", 15, "The number of concurrent metrics retrieval requests allowed.")

	printVersion = flag.Bool("version", false, "print version information")
)

type prometheus struct {
	headCompactionTimer *time.Ticker
	bodyCompactionTimer *time.Ticker
	tailCompactionTimer *time.Ticker
	deletionTimer       *time.Ticker

	curationMutex            sync.Mutex
	stopBackgroundOperations chan bool

	unwrittenSamples chan *extraction.Result

	ruleManager   rules.RuleManager
	notifications chan notification.NotificationReqs
	storage       *metric.TieredStorage

	curationState metric.CurationStateUpdater
}

func (p *prometheus) interruptHandler() {
	notifier := make(chan os.Signal)
	signal.Notify(notifier, os.Interrupt, syscall.SIGTERM)

	<-notifier

	glog.Warning("Received SIGINT/SIGTERM; Exiting gracefully...")
	p.close()
	os.Exit(0)
}

func (p *prometheus) compact(olderThan time.Duration, groupSize int) error {
	p.curationMutex.Lock()
	defer p.curationMutex.Unlock()

	processor := &metric.CompactionProcessor{
		MaximumMutationPoolBatch: groupSize * 3,
		MinimumGroupSize:         groupSize,
	}

	curator := metric.Curator{
		Stop: p.stopBackgroundOperations,
	}

	return curator.Run(olderThan, time.Now(), processor, p.storage.DiskStorage.CurationRemarks, p.storage.DiskStorage.MetricSamples, p.storage.DiskStorage.MetricHighWatermarks, p.curationState)
}

func (p *prometheus) delete(olderThan time.Duration, batchSize int) error {
	p.curationMutex.Lock()
	defer p.curationMutex.Unlock()

	processor := &metric.DeletionProcessor{
		MaximumMutationPoolBatch: batchSize,
	}

	curator := metric.Curator{
		Stop: p.stopBackgroundOperations,
	}

	return curator.Run(olderThan, time.Now(), processor, p.storage.DiskStorage.CurationRemarks, p.storage.DiskStorage.MetricSamples, p.storage.DiskStorage.MetricHighWatermarks, p.curationState)
}

func (p *prometheus) close() {
	if p.headCompactionTimer != nil {
		p.headCompactionTimer.Stop()
	}
	if p.bodyCompactionTimer != nil {
		p.bodyCompactionTimer.Stop()
	}
	if p.tailCompactionTimer != nil {
		p.tailCompactionTimer.Stop()
	}
	if p.deletionTimer != nil {
		p.deletionTimer.Stop()
	}

	if len(p.stopBackgroundOperations) == 0 {
		p.stopBackgroundOperations <- true
	}

	p.curationMutex.Lock()

	p.ruleManager.Stop()
	p.storage.Close()

	close(p.notifications)
	close(p.stopBackgroundOperations)
}

func main() {
	// TODO(all): Future additions to main should be, where applicable, glumped
	// into the prometheus struct above---at least where the scoping of the entire
	// server is concerned.
	flag.Parse()

	versionInfoTmpl.Execute(os.Stdout, BuildInfo)

	if *printVersion {
		os.Exit(0)
	}

	conf, err := config.LoadFromFile(*configFile)
	if err != nil {
		glog.Fatalf("Error loading configuration from %s: %v", *configFile, err)
	}

	ts, err := metric.NewTieredStorage(uint(*diskAppendQueueCapacity), 100, *arenaFlushInterval, *arenaTTL, *metricsStoragePath)
	if err != nil {
		glog.Fatal("Error opening storage: ", err)
	}

	unwrittenSamples := make(chan *extraction.Result, *samplesQueueCapacity)
	ingester := &retrieval.MergeLabelsIngester{
		Labels:          conf.GlobalLabels(),
		CollisionPrefix: clientmodel.ExporterLabelPrefix,

		Ingester: retrieval.ChannelIngester(unwrittenSamples),
	}
	// Coprime numbers, fool!
	headCompactionTimer := time.NewTicker(*headCompactInterval)
	bodyCompactionTimer := time.NewTicker(*bodyCompactInterval)
	tailCompactionTimer := time.NewTicker(*tailCompactInterval)
	deletionTimer := time.NewTicker(*deleteInterval)

	// Queue depth will need to be exposed
	targetManager := retrieval.NewTargetManager(ingester, *concurrentRetrievalAllowance)
	targetManager.AddTargetsFromConfig(conf)

	notifications := make(chan notification.NotificationReqs, *notificationQueueCapacity)

	// Queue depth will need to be exposed
	ruleManager := rules.NewRuleManager(unwrittenSamples, notifications, conf.EvaluationInterval(), ts)
	if err := ruleManager.AddRulesFromConfig(conf); err != nil {
		glog.Fatal("Error loading rule files: ", err)
	}
	go ruleManager.Run()

	prometheusUrl := web.MustBuildServerUrl()
	notificationHandler := notification.NewNotificationHandler(*alertmanagerUrl, prometheusUrl, notifications)
	go notificationHandler.Run()

	flags := map[string]string{}

	flag.VisitAll(func(f *flag.Flag) {
		flags[f.Name] = f.Value.String()
	})

	prometheusStatus := &web.PrometheusStatusHandler{
		BuildInfo:   BuildInfo,
		Config:      conf.String(),
		RuleManager: ruleManager,
		TargetPools: targetManager.Pools(),
		Flags:       flags,
		Birth:       time.Now(),
	}

	alertsHandler := &web.AlertsHandler{
		RuleManager: ruleManager,
	}

	databasesHandler := &web.DatabasesHandler{
		Provider:        ts.DiskStorage,
		RefreshInterval: 5 * time.Minute,
	}

	metricsService := &api.MetricsService{
		Config:        &conf,
		TargetManager: targetManager,
		Storage:       ts,
	}

	webService := &web.WebService{
		StatusHandler:    prometheusStatus,
		MetricsHandler:   metricsService,
		DatabasesHandler: databasesHandler,
		AlertsHandler:    alertsHandler,
	}

	prometheus := &prometheus{
		bodyCompactionTimer: bodyCompactionTimer,
		headCompactionTimer: headCompactionTimer,
		tailCompactionTimer: tailCompactionTimer,

		deletionTimer: deletionTimer,

		curationState: prometheusStatus,

		unwrittenSamples: unwrittenSamples,

		stopBackgroundOperations: make(chan bool, 1),

		ruleManager:   ruleManager,
		notifications: notifications,
		storage:       ts,
	}
	defer prometheus.close()

	storageStarted := make(chan bool)
	go ts.Serve(storageStarted)
	<-storageStarted

	go prometheus.interruptHandler()

	go func() {
		for _ = range prometheus.headCompactionTimer.C {
			glog.Info("Starting head compaction...")
			err := prometheus.compact(*headAge, *headGroupSize)

			if err != nil {
				glog.Error("could not compact: ", err)
			}
			glog.Info("Done")
		}
	}()

	go func() {
		for _ = range prometheus.bodyCompactionTimer.C {
			glog.Info("Starting body compaction...")
			err := prometheus.compact(*bodyAge, *bodyGroupSize)

			if err != nil {
				glog.Error("could not compact: ", err)
			}
			glog.Info("Done")
		}
	}()

	go func() {
		for _ = range prometheus.tailCompactionTimer.C {
			glog.Info("Starting tail compaction...")
			err := prometheus.compact(*tailAge, *tailGroupSize)

			if err != nil {
				glog.Error("could not compact: ", err)
			}
			glog.Info("Done")
		}
	}()

	go func() {
		for _ = range prometheus.deletionTimer.C {
			glog.Info("Starting deletion of stale values...")
			err := prometheus.delete(*deleteAge, deletionBatchSize)

			if err != nil {
				glog.Error("could not delete: ", err)
			}
			glog.Info("Done")
		}
	}()

	go func() {
		err := webService.ServeForever()
		if err != nil {
			glog.Fatal(err)
		}
	}()

	// TODO(all): Migrate this into prometheus.serve().
	for block := range unwrittenSamples {
		if block.Err == nil {
			ts.AppendSamples(block.Samples)
		}
	}
}
<commit_msg>Adjust curation thresholds and intervals.<commit_after>// Copyright 2013 Prometheus Team
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"flag"
	"os"
	"os/signal"
	"sync"
	"syscall"
	"time"

	"github.com/golang/glog"
	"github.com/prometheus/client_golang/extraction"

	clientmodel "github.com/prometheus/client_golang/model"

	"github.com/prometheus/prometheus/config"
	"github.com/prometheus/prometheus/notification"
	"github.com/prometheus/prometheus/retrieval"
	"github.com/prometheus/prometheus/rules"
	"github.com/prometheus/prometheus/storage/metric"
	"github.com/prometheus/prometheus/web"
	"github.com/prometheus/prometheus/web/api"
)

const deletionBatchSize = 100

// Commandline flags.
var (
	configFile         = flag.String("configFile", "prometheus.conf", "Prometheus configuration file name.")
	metricsStoragePath = flag.String("metricsStoragePath", "/tmp/metrics", "Base path for metrics storage.")

	alertmanagerUrl = flag.String("alertmanager.url", "", "The URL of the alert manager to send notifications to.")

	samplesQueueCapacity      = flag.Int("storage.queue.samplesCapacity", 4096, "The size of the unwritten samples queue.")
	diskAppendQueueCapacity   = flag.Int("storage.queue.diskAppendCapacity", 1000000, "The size of the queue for items that are pending writing to disk.")
	memoryAppendQueueCapacity = flag.Int("storage.queue.memoryAppendCapacity", 10000, "The size of the queue for items that are pending writing to memory.")

	headCompactInterval = flag.Duration("compact.headInterval", 3*time.Hour, "The amount of time between head compactions.")
	bodyCompactInterval = flag.Duration("compact.bodyInterval", 5*time.Hour, "The amount of time between body compactions.")
	tailCompactInterval = flag.Duration("compact.tailInterval", 7*time.Hour, "The amount of time between tail compactions.")

	headGroupSize = flag.Int("compact.headGroupSize", 500, "The minimum group size for head samples.")
	bodyGroupSize = flag.Int("compact.bodyGroupSize", 5000, "The minimum group size for body samples.")
	tailGroupSize = flag.Int("compact.tailGroupSize", 10000, "The minimum group size for tail samples.")

	headAge = flag.Duration("compact.headAgeInclusiveness", 5*time.Minute, "The relative inclusiveness of head samples.")
	bodyAge = flag.Duration("compact.bodyAgeInclusiveness", time.Hour, "The relative inclusiveness of body samples.")
	tailAge = flag.Duration("compact.tailAgeInclusiveness", 24*time.Hour, "The relative inclusiveness of tail samples.")

	deleteInterval = flag.Duration("delete.interval", 11*time.Hour, "The amount of time between deletion of old values.")

	deleteAge = flag.Duration("delete.ageMaximum", 15*24*time.Hour, "The relative maximum age for values before they are deleted.")

	arenaFlushInterval = flag.Duration("arena.flushInterval", 15*time.Minute, "The period at which the in-memory arena is flushed to disk.")
	arenaTTL           = flag.Duration("arena.ttl", 10*time.Minute, "The relative age of values to purge to disk from memory.")

	notificationQueueCapacity = flag.Int("alertmanager.notificationQueueCapacity", 100, "The size of the queue for pending alert manager notifications.")

	concurrentRetrievalAllowance = flag.Int("concurrentRetrievalAllowance", 15, "The number of concurrent metrics retrieval requests allowed.")

	printVersion = flag.Bool("version", false, "print version information")
)

type prometheus struct {
	headCompactionTimer *time.Ticker
	bodyCompactionTimer *time.Ticker
	tailCompactionTimer *time.Ticker
	deletionTimer       *time.Ticker

	curationMutex            sync.Mutex
	stopBackgroundOperations chan bool

	unwrittenSamples chan *extraction.Result

	ruleManager   rules.RuleManager
	notifications chan notification.NotificationReqs
	storage       *metric.TieredStorage

	curationState metric.CurationStateUpdater
}

func (p *prometheus) interruptHandler() {
	notifier := make(chan os.Signal)
	signal.Notify(notifier, os.Interrupt, syscall.SIGTERM)

	<-notifier

	glog.Warning("Received SIGINT/SIGTERM; Exiting gracefully...")
	p.close()
	os.Exit(0)
}

func (p *prometheus) compact(olderThan time.Duration, groupSize int) error {
	p.curationMutex.Lock()
	defer p.curationMutex.Unlock()

	processor := &metric.CompactionProcessor{
		MaximumMutationPoolBatch: groupSize * 3,
		MinimumGroupSize:         groupSize,
	}

	curator := metric.Curator{
		Stop: p.stopBackgroundOperations,
	}

	return curator.Run(olderThan, time.Now(), processor, p.storage.DiskStorage.CurationRemarks, p.storage.DiskStorage.MetricSamples, p.storage.DiskStorage.MetricHighWatermarks, p.curationState)
}

func (p *prometheus) delete(olderThan time.Duration, batchSize int) error {
	p.curationMutex.Lock()
	defer p.curationMutex.Unlock()

	processor := &metric.DeletionProcessor{
		MaximumMutationPoolBatch: batchSize,
	}

	curator := metric.Curator{
		Stop: p.stopBackgroundOperations,
	}

	return curator.Run(olderThan, time.Now(), processor, p.storage.DiskStorage.CurationRemarks, p.storage.DiskStorage.MetricSamples, p.storage.DiskStorage.MetricHighWatermarks, p.curationState)
}

func (p *prometheus) close() {
	if p.headCompactionTimer != nil {
		p.headCompactionTimer.Stop()
	}
	if p.bodyCompactionTimer != nil {
		p.bodyCompactionTimer.Stop()
	}
	if p.tailCompactionTimer != nil {
		p.tailCompactionTimer.Stop()
	}
	if p.deletionTimer != nil {
		p.deletionTimer.Stop()
	}

	if len(p.stopBackgroundOperations) == 0 {
		p.stopBackgroundOperations <- true
	}

	p.curationMutex.Lock()

	p.ruleManager.Stop()
	p.storage.Close()

	close(p.notifications)
	close(p.stopBackgroundOperations)
}

func main() {
	// TODO(all): Future additions to main should be, where applicable, glumped
	// into the prometheus struct above---at least where the scoping of the entire
	// server is concerned.
	flag.Parse()

	versionInfoTmpl.Execute(os.Stdout, BuildInfo)

	if *printVersion {
		os.Exit(0)
	}

	conf, err := config.LoadFromFile(*configFile)
	if err != nil {
		glog.Fatalf("Error loading configuration from %s: %v", *configFile, err)
	}

	ts, err := metric.NewTieredStorage(uint(*diskAppendQueueCapacity), 100, *arenaFlushInterval, *arenaTTL, *metricsStoragePath)
	if err != nil {
		glog.Fatal("Error opening storage: ", err)
	}

	unwrittenSamples := make(chan *extraction.Result, *samplesQueueCapacity)
	ingester := &retrieval.MergeLabelsIngester{
		Labels:          conf.GlobalLabels(),
		CollisionPrefix: clientmodel.ExporterLabelPrefix,

		Ingester: retrieval.ChannelIngester(unwrittenSamples),
	}
	// Coprime numbers, fool!
	headCompactionTimer := time.NewTicker(*headCompactInterval)
	bodyCompactionTimer := time.NewTicker(*bodyCompactInterval)
	tailCompactionTimer := time.NewTicker(*tailCompactInterval)
	deletionTimer := time.NewTicker(*deleteInterval)

	// Queue depth will need to be exposed
	targetManager := retrieval.NewTargetManager(ingester, *concurrentRetrievalAllowance)
	targetManager.AddTargetsFromConfig(conf)

	notifications := make(chan notification.NotificationReqs, *notificationQueueCapacity)

	// Queue depth will need to be exposed
	ruleManager := rules.NewRuleManager(unwrittenSamples, notifications, conf.EvaluationInterval(), ts)
	if err := ruleManager.AddRulesFromConfig(conf); err != nil {
		glog.Fatal("Error loading rule files: ", err)
	}
	go ruleManager.Run()

	prometheusUrl := web.MustBuildServerUrl()
	notificationHandler := notification.NewNotificationHandler(*alertmanagerUrl, prometheusUrl, notifications)
	go notificationHandler.Run()

	flags := map[string]string{}

	flag.VisitAll(func(f *flag.Flag) {
		flags[f.Name] = f.Value.String()
	})

	prometheusStatus := &web.PrometheusStatusHandler{
		BuildInfo:   BuildInfo,
		Config:      conf.String(),
		RuleManager: ruleManager,
		TargetPools: targetManager.Pools(),
		Flags:       flags,
		Birth:       time.Now(),
	}

	alertsHandler := &web.AlertsHandler{
		RuleManager: ruleManager,
	}

	databasesHandler := &web.DatabasesHandler{
		Provider:        ts.DiskStorage,
		RefreshInterval: 5 * time.Minute,
	}

	metricsService := &api.MetricsService{
		Config:        &conf,
		TargetManager: targetManager,
		Storage:       ts,
	}

	webService := &web.WebService{
		StatusHandler:    prometheusStatus,
		MetricsHandler:   metricsService,
		DatabasesHandler: databasesHandler,
		AlertsHandler:    alertsHandler,
	}

	prometheus := &prometheus{
		bodyCompactionTimer: bodyCompactionTimer,
		headCompactionTimer: headCompactionTimer,
		tailCompactionTimer: tailCompactionTimer,

		deletionTimer: deletionTimer,

		curationState: prometheusStatus,

		unwrittenSamples: unwrittenSamples,

		stopBackgroundOperations: make(chan bool, 1),

		ruleManager:   ruleManager,
		notifications: notifications,
		storage:       ts,
	}
	defer prometheus.close()

	storageStarted := make(chan bool)
	go ts.Serve(storageStarted)
	<-storageStarted

	go prometheus.interruptHandler()

	go func() {
		for _ = range prometheus.headCompactionTimer.C {
			glog.Info("Starting head compaction...")
			err := prometheus.compact(*headAge, *headGroupSize)

			if err != nil {
				glog.Error("could not compact: ", err)
			}
			glog.Info("Done")
		}
	}()

	go func() {
		for _ = range prometheus.bodyCompactionTimer.C {
			glog.Info("Starting body compaction...")
			err := prometheus.compact(*bodyAge, *bodyGroupSize)

			if err != nil {
				glog.Error("could not compact: ", err)
			}
			glog.Info("Done")
		}
	}()

	go func() {
		for _ = range prometheus.tailCompactionTimer.C {
			glog.Info("Starting tail compaction...")
			err := prometheus.compact(*tailAge, *tailGroupSize)

			if err != nil {
				glog.Error("could not compact: ", err)
			}
			glog.Info("Done")
		}
	}()

	go func() {
		for _ = range prometheus.deletionTimer.C {
			glog.Info("Starting deletion of stale values...")
			err := prometheus.delete(*deleteAge, deletionBatchSize)

			if err != nil {
				glog.Error("could not delete: ", err)
			}
			glog.Info("Done")
		}
	}()

	go func() {
		err := webService.ServeForever()
		if err != nil {
			glog.Fatal(err)
		}
	}()

	// TODO(all): Migrate this into prometheus.serve().
	for block := range unwrittenSamples {
		if block.Err == nil {
			ts.AppendSamples(block.Samples)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"errors"
	"fmt"
	"log"
	"math/rand"
	"os"
	"time"
)

type Entrada int

// Possiveis entradas do usuário
const (
	Nada = iota
	ParaCima
	ParaBaixo
	ParaEsquerda
	ParaDireita
	SairDoJogo // Tecla ESC
)

type PacGo struct {
	posicao    Posicao
	posInicial Posicao
	figura     string
	pontos     int
	vidas      int
	pilula     bool
}

type Fantasma struct {
	posicao Posicao
	figura  string
}

type Labirinto struct {
	largura      int
	altura       int
	mapa         []string
	numPastilhas int
}

var pacgo PacGo
var fantasmas []*Fantasma
var labirinto Labirinto

func criarPacGo(pos Posicao, fig string) {
	pacgo = PacGo{
		posicao:    pos,
		posInicial: pos,
		figura:     fig,
		vidas:      2,
	}
}

func criarFantasma(posicao Posicao, figura string) {
	fantasma := &Fantasma{posicao: posicao, figura: figura}
	fantasmas = append(fantasmas, fantasma)
}

func leEntradaDoUsuario(canal chan<- Entrada) {
	array := make([]byte, 10)

	for {
		lido, _ := os.Stdin.Read(array)

		if lido == 1 && array[0] == 0x1b {
			canal <- SairDoJogo
		} else if lido == 3 {
			if array[0] == 0x1b && array[1] == '[' {
				switch array[2] {
				case 'A':
					canal <- ParaCima
				case 'B':
					canal <- ParaBaixo
				case 'C':
					canal <- ParaDireita
				case 'D':
					canal <- ParaEsquerda
				}
			}
		}
	}
}

func inicializarLabirinto(arquivo string) error {
	var ErrMapNotFound = errors.New("Não conseguiu ler o arquivo do mapa")

	// aplica o valor default caso não seja passado um arquivo
	var tmpArquivo string
	tmpArquivo = arquivo
	if tmpArquivo == "" {
		tmpArquivo = "./mapas/mapa01.txt"
	}

	// abre arquivo
	file, err := os.Open(tmpArquivo)
	if err != nil {
		log.Fatal(err)
		return ErrMapNotFound
	}
	// fecha depois de ler o arquivo
	defer file.Close()

	// inicializa o mapa vazio
	mapa := []string{}

	// cria um leitor para ler linha a linha o arquivo
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		linha := scanner.Text()
		mapa = append(mapa, linha)
	}

	// determina o tamanho do mapa baseado no arquivo lido
	largura := len(mapa[0]) - 2 // -2 para não contar o \x0D\x0A do final
	altura := len(mapa)

	labirinto = Labirinto{
		largura: largura,
		altura:  altura,
		mapa:    mapa,
	}

	// Processa caracteres especiais
	for linha, linhaMapa := range labirinto.mapa {
		for coluna, caractere := range linhaMapa {
			switch caractere {
			case 'G':
				criarPacGo(Posicao{linha, coluna}, "\xF0\x9F\x98\x83")
			case 'F':
				criarFantasma(Posicao{linha, coluna}, "\xF0\x9F\x91\xBB")
			case '.':
				labirinto.numPastilhas++
			}
		}
	}

	return nil
}

func desenhaTela() {
	LimpaTela()

	// Imprime placar
	MoveCursor(Posicao{0, 0})
	placar := fmt.Sprintf("Pontos: %d Vidas: %d", pacgo.pontos, pacgo.vidas)
	fmt.Println(Vermelho(Intenso(placar)))

	// Ajuste para desenhar o mapa embaixo do placar
	deslocamento := Posicao{2, 0}
	MoveCursor(deslocamento)

	// Imprime mapa
	for _, linha := range labirinto.mapa {
		for _, char := range linha {
			switch char {
			case '#':
				if pacgo.pilula {
					fmt.Print(FundoVermelho(" "))
				} else {
					fmt.Print(FundoAzul(" "))
				}
			case '.':
				fmt.Print(".")
			case 'P':
				fmt.Print("\xF0\x9F\x8D\x84")
			default:
				fmt.Print(" ")
			}
		}
		fmt.Println("")
	}

	// Imprime PacGo
	MoveCursor(pacgo.posicao.Soma(deslocamento))
	fmt.Printf("%s", pacgo.figura)

	// Imprime fantasmas
	for _, fantasma := range fantasmas {
		MoveCursor(fantasma.posicao.Soma(deslocamento))
		fmt.Printf("%s", fantasma.figura)
	}

	// Move cursor para fora do labirinto
	MoveCursor(deslocamento.Soma(Posicao{labirinto.altura + 2, 0}))
}

func moverPacGo(m Entrada) {
	var novaLinha = pacgo.posicao.linha
	var novaColuna = pacgo.posicao.coluna

	switch m {
	case ParaCima:
		novaLinha--
		if novaLinha < 0 {
			novaLinha = labirinto.altura - 1
		}
	case ParaBaixo:
		novaLinha++
		if novaLinha >= labirinto.altura {
			novaLinha = 0
		}
	case ParaDireita:
		novaColuna++
		if novaColuna >= labirinto.largura {
			novaColuna = 0
		}
	case ParaEsquerda:
		novaColuna--
		if novaColuna < 0 {
			novaColuna = labirinto.largura - 1
		}
	}

	conteudoDoMapa := labirinto.mapa[novaLinha][novaColuna]
	if conteudoDoMapa != '#' {
		pacgo.posicao.linha = novaLinha
		pacgo.posicao.coluna = novaColuna

		if conteudoDoMapa == '.' || conteudoDoMapa == 'P' {
			switch conteudoDoMapa {
			case '.':
				pacgo.pontos += 10
				labirinto.numPastilhas--
			case 'P':
				pacgo.pontos += 100
				ativarPilula()
			}

			// Remove item do mapa
			linha := labirinto.mapa[novaLinha]
			linha = linha[:novaColuna] + " " + linha[novaColuna+1:]
			labirinto.mapa[novaLinha] = linha
		}
	}
}

func moverFantasmas() {
	for _, fantasma := range fantasmas {
		// gera um número aleatório entre 0 e 4 (Direita = 3)
		var direcao = rand.Intn(ParaDireita + 1)

		var novaPosicao = fantasma.posicao

		// Atualiza posição testando os limites do mapa
		switch direcao {
		case ParaCima:
			novaPosicao.linha--
			if novaPosicao.linha < 0 {
				novaPosicao.linha = labirinto.altura - 1
			}
		case ParaBaixo:
			novaPosicao.linha++
			if novaPosicao.linha > labirinto.altura-1 {
				novaPosicao.linha = 0
			}
		case ParaEsquerda:
			novaPosicao.coluna--
			if novaPosicao.coluna < 0 {
				novaPosicao.coluna = labirinto.largura - 1
			}
		case ParaDireita:
			novaPosicao.coluna++
			if novaPosicao.coluna > labirinto.largura-1 {
				novaPosicao.coluna = 0
			}
		}

		// Verifica se a posição nova do mapa é válida
		conteudoMapa := labirinto.mapa[novaPosicao.linha][novaPosicao.coluna]
		if conteudoMapa != '#' {
			fantasma.posicao = novaPosicao
		}
	}
}

func dorme(milisegundos time.Duration) {
	time.Sleep(time.Millisecond * milisegundos)
}

func detectarColisao() *Fantasma {
	for _, fantasma := range fantasmas {
		if fantasma.posicao == pacgo.posicao {
			return fantasma
		}
	}
	return nil
}

func main() {
	// Inicializar terminal
	Inicializa()
	defer Finaliza()

	// Lê parâmetros do sistema operacional
	args := os.Args[1:]
	var arquivo string
	if len(args) >= 1 {
		arquivo = args[0]
	} else {
		arquivo = ""
	}

	// Inicializar labirinto
	inicializarLabirinto(arquivo)

	// Cria rotina para ler entradas
	canal := make(chan Entrada, 10)
	go leEntradaDoUsuario(canal)

	// Loop principal
	for {
		// Desenha tela
		desenhaTela()

		// Fim de jogo
		if labirinto.numPastilhas == 0 {
			MoveCursor(Posicao{labirinto.altura + 3, 0})
			fmt.Print("Fim de jogo! Você venceu! \xF0\x9F\x98\x84\n\n")
			break
		}

		// Processa entrada do jogador
		var tecla Entrada
		select {
		case tecla = <-canal:
		default:
		}

		if tecla == SairDoJogo {
			break
		} else {
			moverPacGo(tecla)
		}

		// Processa movimento dos fantasmas
		moverFantasmas()

		// Processa colisões
		if fantasma := detectarColisao(); fantasma != nil {
			if pacgo.pilula {
				go ressucitarFantasma(fantasma, 10000) // 10 segundos
				matarFantasma(fantasma)
				pacgo.pontos += 500
			} else {
				pacgo.vidas--

				// Reseta posição do PacGopher para a posição inicial
				pacgo.posicao = pacgo.posInicial
			}

			if pacgo.vidas < 0 {
				MoveCursor(Posicao{labirinto.altura + 3, 0})
				fmt.Print("Fim de jogo! Os fantasmas venceram... \xF0\x9F\x98\xAD\n\n")
				break
			}
		}

		// Dorme
		dorme(100)
	}
}

func ativarPilula() {
	pacgo.pilula = true
	go desativarPilula(10000) // 10 segundos
}

func desativarPilula(milisegundos time.Duration) {
	dorme(milisegundos)
	pacgo.pilula = false
}

func ressucitarFantasma(fantasma *Fantasma, milisegundos time.Duration) {
	dorme(milisegundos)
	criarFantasma(fantasma.posicao, fantasma.figura)
}

func matarFantasma(fantasma *Fantasma) {
	var indice int
	for idx, f := range fantasmas {
		if f.posicao == fantasma.posicao {
			indice = idx
			break
		}
	}
	fantasmas = append(fantasmas[:indice], fantasmas[indice+1:]...)
}
<commit_msg>reset do main.go<commit_after>package main

import "fmt"

func main() {
  // Inicializar terminal
  Inicializa()
  defer Finaliza()

  // Inicializar labirinto

  // Loop principal
  for {
    // Desenha tela

    // Processa entrada do jogador

    // Processa movimento dos fantasmas

    // Processa colisões

    // Dorme

    fmt.Println("Olá Go!")
    break // Temporário: quebra o loop infinito
  }
}
<|endoftext|>


<commit_before>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package main

import (
	log "github.com/Sirupsen/logrus"
	"gopkg.in/urfave/cli.v1"
	"os"
)

func main() {
	// This won't be needed in cli v2
	cli.VersionFlag.Name = "version"
	cli.HelpFlag.Name = "help"
	cli.HelpFlag.Hidden = true

	app := cli.NewApp()
	app.Name = "k6"
	app.Usage = "a next generation load generator"
	app.Version = "0.4.4"
	app.Commands = []cli.Command{
		commandRun,
		commandInspect,
		commandStatus,
		commandStats,
		commandScale,
		commandStart,
		commandPause,
	}
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "verbose, v",
			Usage: "show debug messages",
		},
		cli.StringFlag{
			Name:  "address, a",
			Usage: "address for the API",
			Value: "127.0.0.1:6565",
		},
	}
	app.Before = func(cc *cli.Context) error {
		if cc.Bool("verbose") {
			log.SetLevel(log.DebugLevel)
		}

		return nil
	}
	if err := app.Run(os.Args); err != nil {
		os.Exit(1)
	}
}
<commit_msg>v0.4.5<commit_after>/*
 *
 * k6 - a next-generation load testing tool
 * Copyright (C) 2016 Load Impact
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package main

import (
	log "github.com/Sirupsen/logrus"
	"gopkg.in/urfave/cli.v1"
	"os"
)

func main() {
	// This won't be needed in cli v2
	cli.VersionFlag.Name = "version"
	cli.HelpFlag.Name = "help"
	cli.HelpFlag.Hidden = true

	app := cli.NewApp()
	app.Name = "k6"
	app.Usage = "a next generation load generator"
	app.Version = "0.4.5"
	app.Commands = []cli.Command{
		commandRun,
		commandInspect,
		commandStatus,
		commandStats,
		commandScale,
		commandStart,
		commandPause,
	}
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "verbose, v",
			Usage: "show debug messages",
		},
		cli.StringFlag{
			Name:  "address, a",
			Usage: "address for the API",
			Value: "127.0.0.1:6565",
		},
	}
	app.Before = func(cc *cli.Context) error {
		if cc.Bool("verbose") {
			log.SetLevel(log.DebugLevel)
		}

		return nil
	}
	if err := app.Run(os.Args); err != nil {
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/dfordsoft/golib/fsutil"

	"github.com/dfordsoft/golib/ebook"
	flags "github.com/jessevdk/go-flags"
)

// Options for all command line options
type Options struct {
	ListenAndServe  string  `long:"httpServe" description:"set http listen and serve address, example: :8080"`
	Format          string  `short:"f" long:"format" description:"set generated file format, candidate values: mobi, epub, pdf"`
	List            bool    `short:"l" long:"list" description:"list supported novel websites"`
	LeftMargin      float64 `long:"leftMargin" description:"set left margin for PDF format"`
	TopMargin       float64 `long:"topMargin" description:"set top margin for PDF format"`
	PageWidth       float64 `long:"pageWidth" description:"set page width for PDF format(unit: mm)"`
	PageHeight      float64 `long:"pageHeight" description:"set page height for PDF format(unit: mm)"`
	PageType        string  `short:"p" long:"pageType" description:"set page type for PDF format, add suffix to output file name"`
	TitleFontSize   int     `long:"titleFontSize" description:"set title font point size for PDF format"`
	ContentFontSize int     `long:"contentFontSize" description:"set content font point size for PDF format"`
	LineSpacing     float64 `long:"lineSpacing" description:"set line spacing rate for PDF format"`
	PagesPerFile    int     `long:"pagesPerFile" description:"split the big single PDF file to several smaller PDF files, how many pages should be included in a file, 0 means don't split"`
	ChaptersPerFile int     `long:"chaptersPerFile" description:"split the big signle PDF file to several smaller PDF files, how many chapters should be included in a file, 0 means don't split"`
	FontFile        string  `long:"fontFile" description:"set TTF font file path"`
	RetryCount      int     `short:"r" long:"retries" description:"download retry count"`
	Timeout         int     `short:"t" long:"timeout" description:"download timeout seconds"`
	ParallelCount   int64   `long:"parallel" description:"parallel count for downloading"`
	ConfigFile      string  `short:"c" long:"config" description:"read configurations from local file"`
	OutputFile      string  `short:"o" long:"output" description:"output file path"`
	FromChapter     int     `long:"fromChapter" description:"from chapter"`
	FromTitle       string  `long:"fromTitle" description:"from title"`
	ToChapter       int     `long:"toChapter" description:"to chapter"`
	ToTitle         string  `long:"toTitle" description:"to title"`
}

type tocPattern struct {
	host            string
	bookTitle       string
	bookTitlePos    int
	item            string
	articleTitlePos int
	articleURLPos   int
	isAbsoluteURL   bool
}

type pageContentMarker struct {
	host  string
	start []byte
	end   []byte
}

type novelSiteHandler struct {
	Title         string
	MatchPatterns []string
	Download      func(string, ebook.IBook)
}

var (
	novelSiteHandlers []*novelSiteHandler
	opts              Options
	sha1ver           string // sha1 revision used to build the program
	buildTime         string // when the executable was built
)

func registerNovelSiteHandler(h *novelSiteHandler) {
	novelSiteHandlers = append(novelSiteHandlers, h)
}

func listCommandHandler() {
	fmt.Println("支持小说网站：")
	for _, h := range novelSiteHandlers {
		urlMap := make(map[string]struct{})
		for _, p := range h.MatchPatterns {
			u := strings.Replace(p, `\`, ``, -1)
			idxStart := strings.Index(u, `www.`)
			idxEnd := strings.Index(u[idxStart:], `/`)
			u = u[:idxStart+idxEnd]
			urlMap[u] = struct{}{}
		}
		var urls []string
		for u := range urlMap {
			urls = append(urls, u)
		}
		fmt.Println("\t" + h.Title + ": " + strings.Join(urls, ", "))
	}
	for _, h := range novelSiteConfigurations {
		urlMap := make(map[string]struct{})
		for _, p := range h.Sites {
			u := strings.Replace(p.TOCURLPattern, `\`, ``, -1)
			idxStart := strings.Index(u, `www.`)
			idxEnd := strings.Index(u[idxStart:], `/`)
			u = u[:idxStart+idxEnd]
			urlMap[u] = struct{}{}
		}
		var urls []string
		for u := range urlMap {
			urls = append(urls, u)
		}
		fmt.Println("\t" + h.Title + ": " + strings.Join(urls, ", "))
	}
}

func readConfigFile(opts *Options) bool {
	configFile := opts.ConfigFile
	if b, e := fsutil.FileExists(configFile); e != nil || !b {
		configFile = filepath.Join("preset", opts.ConfigFile)
		if b, e = fsutil.FileExists(configFile); e != nil || !b {
			configFile = filepath.Join("preset", opts.ConfigFile+".conf")
			if b, e = fsutil.FileExists(configFile); e != nil || !b {
				configFile = filepath.Join("preset", opts.ConfigFile+".json")
				if b, e = fsutil.FileExists(configFile); e != nil || !b {
					log.Println("cannot find configuration file ", opts.ConfigFile)
					return false
				}
			}
		}
	}

	contentFd, err := os.OpenFile(configFile, os.O_RDONLY, 0644)
	if err != nil {
		log.Println("opening config file ", configFile, " for reading failed ", err)
		return false
	}

	contentC, err := ioutil.ReadAll(contentFd)
	contentFd.Close()
	if err != nil {
		log.Println("reading config file ", configFile, " failed ", err)
		return false
	}

	var options map[string]interface{}
	if err = json.Unmarshal(contentC, &options); err != nil {
		log.Println("unmarshall configurations failed", err)
		return false
	}

	if f, ok := options["format"]; ok {
		if v := f.(string); len(v) > 0 {
			opts.Format = v
		}
	}
	if f, ok := options["pageType"]; ok {
		if v := f.(string); len(v) > 0 {
			opts.PageType = v
		}
	}
	if f, ok := options["pageWidth"]; ok {
		if v := f.(float64); v > 0 {
			opts.PageWidth = v
		}
	}
	if f, ok := options["pageHeight"]; ok {
		if v := f.(float64); v > 0 {
			opts.PageHeight = v
		}
	}
	if f, ok := options["fontFile"]; ok {
		if v := f.(string); len(v) > 0 {
			opts.FontFile = v
		}
	}
	if f, ok := options["fromChapter"]; ok {
		if v := f.(int); v > 0 {
			opts.FromChapter = v
		}
	}
	if f, ok := options["fromTitle"]; ok {
		if v := f.(string); len(v) > 0 {
			opts.FromTitle = v
		}
	}
	if f, ok := options["toChapter"]; ok {
		if v := f.(int); v > 0 {
			opts.ToChapter = v
		}
	}
	if f, ok := options["toTitle"]; ok {
		if v := f.(string); len(v) > 0 {
			opts.ToTitle = v
		}
	}

	if f, ok := options["leftMargin"]; ok {
		if v := f.(float64); v > 0 {
			opts.LeftMargin = v
		}
	}
	if f, ok := options["topMargin"]; ok {
		if v := f.(float64); v > 0 {
			opts.TopMargin = v
		}
	}
	if f, ok := options["lineSpacing"]; ok {
		if v := f.(float64); v > 0 {
			opts.LineSpacing = v
		}
	}
	if f, ok := options["titleFontSize"]; ok {
		if v := f.(float64); v > 0 {
			opts.TitleFontSize = int(v)
		}
	}
	if f, ok := options["contentFontSize"]; ok {
		if v := f.(float64); v > 0 {
			opts.ContentFontSize = int(v)
		}
	}
	if f, ok := options["pagesPerFile"]; ok {
		if v := f.(float64); v > 0 {
			opts.PagesPerFile = int(v)
		}
	}
	if f, ok := options["chaptersPerFile"]; ok {
		if v := f.(float64); v > 0 {
			opts.ChaptersPerFile = int(v)
		}
	}
	if f, ok := options["retries"]; ok {
		if v := f.(float64); v > 0 {
			opts.RetryCount = int(v)
		}
	}
	if f, ok := options["timeout"]; ok {
		if v := f.(float64); v > 0 {
			opts.Timeout = int(v)
		}
	}
	if f, ok := options["parallel"]; ok {
		if v := f.(float64); v > 0 {
			opts.ParallelCount = int64(v)
		}
	}
	return true
}

func downloadBook(novelURL string, ch chan bool) {
	for _, h := range novelSiteHandlers {
		for _, pattern := range h.MatchPatterns {
			r, _ := regexp.Compile(pattern)
			if r.MatchString(novelURL) {
				gen := ebook.NewBook(opts.Format)
				gen.SetFontSize(opts.TitleFontSize, opts.ContentFontSize)
				gen.SetLineSpacing(opts.LineSpacing)
				gen.PagesPerFile(opts.PagesPerFile)
				gen.ChaptersPerFile(opts.ChaptersPerFile)
				gen.SetMargins(opts.LeftMargin, opts.TopMargin)
				gen.SetPageType(opts.PageType)
				gen.SetPageSize(opts.PageWidth, opts.PageHeight)
				gen.SetFontFile(opts.FontFile)
				gen.Output(opts.OutputFile)
				gen.Info()
				h.Download(novelURL, gen)
				fmt.Println("downloaded", novelURL)
				ch <- true
				return
			}
		}
	}
	for _, h := range novelSiteConfigurations {
		for _, site := range h.Sites {
			r, _ := regexp.Compile(site.TOCURLPattern)
			if r.MatchString(novelURL) {
				gen := ebook.NewBook(opts.Format)
				gen.SetFontSize(opts.TitleFontSize, opts.ContentFontSize)
				gen.SetLineSpacing(opts.LineSpacing)
				gen.PagesPerFile(opts.PagesPerFile)
				gen.ChaptersPerFile(opts.ChaptersPerFile)
				gen.SetMargins(opts.LeftMargin, opts.TopMargin)
				gen.SetPageType(opts.PageType)
				gen.SetPageSize(opts.PageWidth, opts.PageHeight)
				gen.SetFontFile(opts.FontFile)
				gen.Output(opts.OutputFile)
				gen.Info()
				h.Download(novelURL, gen)
				fmt.Println("downloaded", novelURL)
				ch <- true
				return
			}
		}
	}
	fmt.Println("not downloaded", novelURL)
	ch <- false
}

func main() {
	fmt.Println("getnovel SHA1:", sha1ver, "build at", buildTime)
	if len(os.Args) < 2 {
		fmt.Println("使用方法：\n\tgetnovel 小说目录网址")
		listCommandHandler()
		return
	}

	opts = Options{
		Format:          "mobi",
		List:            false,
		LeftMargin:      10,
		TopMargin:       10,
		PageHeight:      841.89,
		PageWidth:       595.28,
		TitleFontSize:   24,
		ContentFontSize: 18,
		LineSpacing:     1.2,
		PagesPerFile:    0,
		ChaptersPerFile: 0,
		FontFile:        filepath.Join("fonts", "CustomFont.ttf"),
		RetryCount:      3,
		Timeout:         60,
		ParallelCount:   10,
	}

	args, err := flags.Parse(&opts)
	if err != nil {
		return
	}

	if opts.List {
		listCommandHandler()
		return
	}

	if opts.ListenAndServe != "" {
		dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
		if err != nil {
			log.Fatal(err)
		}
		ifaces, err := net.Interfaces()
		var ips []string
		for _, i := range ifaces {

			addrs, err := i.Addrs()
			if err != nil {
				fmt.Println(err)
				continue
			}
			for _, addr := range addrs {
				switch v := addr.(type) {
				case *net.IPNet:
					if v.IP.IsLoopback() || v.IP.IsLinkLocalMulticast() || v.IP.IsLinkLocalUnicast() {
						break
					}
					ips = append(ips, "\t"+v.IP.String())
				case *net.IPAddr:
					if v.IP.IsLoopback() || v.IP.IsLinkLocalMulticast() || v.IP.IsLinkLocalUnicast() {
						break
					}
					ips = append(ips, "\t"+v.IP.String())
				}
			}
		}
		fmt.Println("Local IP:")
		fmt.Println(strings.Join(ips, "\n"))
		fmt.Println("starting http server on", opts.ListenAndServe)
		log.Fatal(http.ListenAndServe(opts.ListenAndServe, http.FileServer(http.Dir(dir))))
		return
	}

	readNovelSiteConfigurations()

	if !readConfigFile(&opts) {
		return
	}

	downloadedChannel := make(chan bool)
	donwloadCount := 0
	for _, novelURL := range args {
		_, e := url.Parse(novelURL)
		if e != nil {
			fmt.Println("invalid URL", novelURL)
			continue
		}
		donwloadCount++
		go downloadBook(novelURL, downloadedChannel)
	}

	downloaded := false
	for i := 0; i < donwloadCount; i++ {
		ch := <-downloadedChannel
		downloaded = (downloaded || ch)
	}

	if !downloaded {
		fmt.Println("使用方法：\n\tgetnovel 小说目录网址")
		listCommandHandler()
	}
}
<commit_msg>(*)fix config file parse<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/dfordsoft/golib/fsutil"

	"github.com/dfordsoft/golib/ebook"
	flags "github.com/jessevdk/go-flags"
)

// Options for all command line options
type Options struct {
	ListenAndServe  string  `long:"httpServe" description:"set http listen and serve address, example: :8080"`
	Format          string  `short:"f" long:"format" description:"set generated file format, candidate values: mobi, epub, pdf"`
	List            bool    `short:"l" long:"list" description:"list supported novel websites"`
	LeftMargin      float64 `long:"leftMargin" description:"set left margin for PDF format"`
	TopMargin       float64 `long:"topMargin" description:"set top margin for PDF format"`
	PageWidth       float64 `long:"pageWidth" description:"set page width for PDF format(unit: mm)"`
	PageHeight      float64 `long:"pageHeight" description:"set page height for PDF format(unit: mm)"`
	PageType        string  `short:"p" long:"pageType" description:"set page type for PDF format, add suffix to output file name"`
	TitleFontSize   int     `long:"titleFontSize" description:"set title font point size for PDF format"`
	ContentFontSize int     `long:"contentFontSize" description:"set content font point size for PDF format"`
	LineSpacing     float64 `long:"lineSpacing" description:"set line spacing rate for PDF format"`
	PagesPerFile    int     `long:"pagesPerFile" description:"split the big single PDF file to several smaller PDF files, how many pages should be included in a file, 0 means don't split"`
	ChaptersPerFile int     `long:"chaptersPerFile" description:"split the big signle PDF file to several smaller PDF files, how many chapters should be included in a file, 0 means don't split"`
	FontFile        string  `long:"fontFile" description:"set TTF font file path"`
	RetryCount      int     `short:"r" long:"retries" description:"download retry count"`
	Timeout         int     `short:"t" long:"timeout" description:"download timeout seconds"`
	ParallelCount   int64   `long:"parallel" description:"parallel count for downloading"`
	ConfigFile      string  `short:"c" long:"config" description:"read configurations from local file"`
	OutputFile      string  `short:"o" long:"output" description:"output file path"`
	FromChapter     int     `long:"fromChapter" description:"from chapter"`
	FromTitle       string  `long:"fromTitle" description:"from title"`
	ToChapter       int     `long:"toChapter" description:"to chapter"`
	ToTitle         string  `long:"toTitle" description:"to title"`
}

type tocPattern struct {
	host            string
	bookTitle       string
	bookTitlePos    int
	item            string
	articleTitlePos int
	articleURLPos   int
	isAbsoluteURL   bool
}

type pageContentMarker struct {
	host  string
	start []byte
	end   []byte
}

type novelSiteHandler struct {
	Title         string
	MatchPatterns []string
	Download      func(string, ebook.IBook)
}

var (
	novelSiteHandlers []*novelSiteHandler
	opts              Options
	sha1ver           string // sha1 revision used to build the program
	buildTime         string // when the executable was built
)

func registerNovelSiteHandler(h *novelSiteHandler) {
	novelSiteHandlers = append(novelSiteHandlers, h)
}

func listCommandHandler() {
	fmt.Println("支持小说网站：")
	for _, h := range novelSiteHandlers {
		urlMap := make(map[string]struct{})
		for _, p := range h.MatchPatterns {
			u := strings.Replace(p, `\`, ``, -1)
			idxStart := strings.Index(u, `www.`)
			idxEnd := strings.Index(u[idxStart:], `/`)
			u = u[:idxStart+idxEnd]
			urlMap[u] = struct{}{}
		}
		var urls []string
		for u := range urlMap {
			urls = append(urls, u)
		}
		fmt.Println("\t" + h.Title + ": " + strings.Join(urls, ", "))
	}
	for _, h := range novelSiteConfigurations {
		urlMap := make(map[string]struct{})
		for _, p := range h.Sites {
			u := strings.Replace(p.TOCURLPattern, `\`, ``, -1)
			idxStart := strings.Index(u, `www.`)
			idxEnd := strings.Index(u[idxStart:], `/`)
			u = u[:idxStart+idxEnd]
			urlMap[u] = struct{}{}
		}
		var urls []string
		for u := range urlMap {
			urls = append(urls, u)
		}
		fmt.Println("\t" + h.Title + ": " + strings.Join(urls, ", "))
	}
}

func readConfigFile(opts *Options) bool {
	configFile := opts.ConfigFile
	if b, e := fsutil.FileExists(configFile); e != nil || !b {
		configFile = filepath.Join("preset", opts.ConfigFile)
		if b, e = fsutil.FileExists(configFile); e != nil || !b {
			configFile = filepath.Join("preset", opts.ConfigFile+".conf")
			if b, e = fsutil.FileExists(configFile); e != nil || !b {
				configFile = filepath.Join("preset", opts.ConfigFile+".json")
				if b, e = fsutil.FileExists(configFile); e != nil || !b {
					log.Println("cannot find configuration file ", opts.ConfigFile)
					return false
				}
			}
		}
	}

	contentFd, err := os.OpenFile(configFile, os.O_RDONLY, 0644)
	if err != nil {
		log.Println("opening config file ", configFile, " for reading failed ", err)
		return false
	}

	contentC, err := ioutil.ReadAll(contentFd)
	contentFd.Close()
	if err != nil {
		log.Println("reading config file ", configFile, " failed ", err)
		return false
	}

	var options map[string]interface{}
	if err = json.Unmarshal(contentC, &options); err != nil {
		log.Println("unmarshall configurations failed", err)
		return false
	}

	if f, ok := options["format"]; ok {
		if v := f.(string); len(v) > 0 {
			opts.Format = v
		}
	}
	if f, ok := options["pageType"]; ok {
		if v := f.(string); len(v) > 0 {
			opts.PageType = v
		}
	}
	if f, ok := options["pageWidth"]; ok {
		if v := f.(float64); v > 0 {
			opts.PageWidth = v
		}
	}
	if f, ok := options["pageHeight"]; ok {
		if v := f.(float64); v > 0 {
			opts.PageHeight = v
		}
	}
	if f, ok := options["fontFile"]; ok {
		if v := f.(string); len(v) > 0 {
			opts.FontFile = v
		}
	}
	if f, ok := options["fromChapter"]; ok {
		if v := f.(float64); v > 0 {
			opts.FromChapter = int(v)
		}
	}
	if f, ok := options["fromTitle"]; ok {
		if v := f.(string); len(v) > 0 {
			opts.FromTitle = v
		}
	}
	if f, ok := options["toChapter"]; ok {
		if v := f.(float64); v > 0 {
			opts.ToChapter = int(v)
		}
	}
	if f, ok := options["toTitle"]; ok {
		if v := f.(string); len(v) > 0 {
			opts.ToTitle = v
		}
	}

	if f, ok := options["leftMargin"]; ok {
		if v := f.(float64); v > 0 {
			opts.LeftMargin = v
		}
	}
	if f, ok := options["topMargin"]; ok {
		if v := f.(float64); v > 0 {
			opts.TopMargin = v
		}
	}
	if f, ok := options["lineSpacing"]; ok {
		if v := f.(float64); v > 0 {
			opts.LineSpacing = v
		}
	}
	if f, ok := options["titleFontSize"]; ok {
		if v := f.(float64); v > 0 {
			opts.TitleFontSize = int(v)
		}
	}
	if f, ok := options["contentFontSize"]; ok {
		if v := f.(float64); v > 0 {
			opts.ContentFontSize = int(v)
		}
	}
	if f, ok := options["pagesPerFile"]; ok {
		if v := f.(float64); v > 0 {
			opts.PagesPerFile = int(v)
		}
	}
	if f, ok := options["chaptersPerFile"]; ok {
		if v := f.(float64); v > 0 {
			opts.ChaptersPerFile = int(v)
		}
	}
	if f, ok := options["retries"]; ok {
		if v := f.(float64); v > 0 {
			opts.RetryCount = int(v)
		}
	}
	if f, ok := options["timeout"]; ok {
		if v := f.(float64); v > 0 {
			opts.Timeout = int(v)
		}
	}
	if f, ok := options["parallel"]; ok {
		if v := f.(float64); v > 0 {
			opts.ParallelCount = int64(v)
		}
	}
	return true
}

func downloadBook(novelURL string, ch chan bool) {
	for _, h := range novelSiteHandlers {
		for _, pattern := range h.MatchPatterns {
			r, _ := regexp.Compile(pattern)
			if r.MatchString(novelURL) {
				gen := ebook.NewBook(opts.Format)
				gen.SetFontSize(opts.TitleFontSize, opts.ContentFontSize)
				gen.SetLineSpacing(opts.LineSpacing)
				gen.PagesPerFile(opts.PagesPerFile)
				gen.ChaptersPerFile(opts.ChaptersPerFile)
				gen.SetMargins(opts.LeftMargin, opts.TopMargin)
				gen.SetPageType(opts.PageType)
				gen.SetPageSize(opts.PageWidth, opts.PageHeight)
				gen.SetFontFile(opts.FontFile)
				gen.Output(opts.OutputFile)
				gen.Info()
				h.Download(novelURL, gen)
				fmt.Println("downloaded", novelURL)
				ch <- true
				return
			}
		}
	}
	for _, h := range novelSiteConfigurations {
		for _, site := range h.Sites {
			r, _ := regexp.Compile(site.TOCURLPattern)
			if r.MatchString(novelURL) {
				gen := ebook.NewBook(opts.Format)
				gen.SetFontSize(opts.TitleFontSize, opts.ContentFontSize)
				gen.SetLineSpacing(opts.LineSpacing)
				gen.PagesPerFile(opts.PagesPerFile)
				gen.ChaptersPerFile(opts.ChaptersPerFile)
				gen.SetMargins(opts.LeftMargin, opts.TopMargin)
				gen.SetPageType(opts.PageType)
				gen.SetPageSize(opts.PageWidth, opts.PageHeight)
				gen.SetFontFile(opts.FontFile)
				gen.Output(opts.OutputFile)
				gen.Info()
				h.Download(novelURL, gen)
				fmt.Println("downloaded", novelURL)
				ch <- true
				return
			}
		}
	}
	fmt.Println("not downloaded", novelURL)
	ch <- false
}

func main() {
	fmt.Println("getnovel SHA1:", sha1ver, "build at", buildTime)
	if len(os.Args) < 2 {
		fmt.Println("使用方法：\n\tgetnovel 小说目录网址")
		listCommandHandler()
		return
	}

	opts = Options{
		Format:          "mobi",
		List:            false,
		LeftMargin:      10,
		TopMargin:       10,
		PageHeight:      841.89,
		PageWidth:       595.28,
		TitleFontSize:   24,
		ContentFontSize: 18,
		LineSpacing:     1.2,
		PagesPerFile:    0,
		ChaptersPerFile: 0,
		FontFile:        filepath.Join("fonts", "CustomFont.ttf"),
		RetryCount:      3,
		Timeout:         60,
		ParallelCount:   10,
	}

	args, err := flags.Parse(&opts)
	if err != nil {
		return
	}

	if opts.List {
		listCommandHandler()
		return
	}

	if opts.ListenAndServe != "" {
		dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
		if err != nil {
			log.Fatal(err)
		}
		ifaces, err := net.Interfaces()
		var ips []string
		for _, i := range ifaces {

			addrs, err := i.Addrs()
			if err != nil {
				fmt.Println(err)
				continue
			}
			for _, addr := range addrs {
				switch v := addr.(type) {
				case *net.IPNet:
					if v.IP.IsLoopback() || v.IP.IsLinkLocalMulticast() || v.IP.IsLinkLocalUnicast() {
						break
					}
					ips = append(ips, "\t"+v.IP.String())
				case *net.IPAddr:
					if v.IP.IsLoopback() || v.IP.IsLinkLocalMulticast() || v.IP.IsLinkLocalUnicast() {
						break
					}
					ips = append(ips, "\t"+v.IP.String())
				}
			}
		}
		fmt.Println("Local IP:")
		fmt.Println(strings.Join(ips, "\n"))
		fmt.Println("starting http server on", opts.ListenAndServe)
		log.Fatal(http.ListenAndServe(opts.ListenAndServe, http.FileServer(http.Dir(dir))))
		return
	}

	readNovelSiteConfigurations()

	if !readConfigFile(&opts) {
		return
	}

	downloadedChannel := make(chan bool)
	donwloadCount := 0
	for _, novelURL := range args {
		_, e := url.Parse(novelURL)
		if e != nil {
			fmt.Println("invalid URL", novelURL)
			continue
		}
		donwloadCount++
		go downloadBook(novelURL, downloadedChannel)
	}

	downloaded := false
	for i := 0; i < donwloadCount; i++ {
		ch := <-downloadedChannel
		downloaded = (downloaded || ch)
	}

	if !downloaded {
		fmt.Println("使用方法：\n\tgetnovel 小说目录网址")
		listCommandHandler()
	}
}
<|endoftext|>


<commit_before>package main

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

const (
	defaultURL  = "https://api.companieshouse.gov.uk"
	contentType = "application/json"
)

type Company struct {
	Etag                  string         `json:"etag"`
	Accounts              AnnualAccounts `json:"accounts"`
	AnnualReturn          AnnualReturn   `json:"annual_return"`
	Branch                Branch         `json:"branch_company_details"`
	CanFile               bool           `json:"can_file"`
	CompanyName           string         `json:"company_name"`
	CompanyNumber         string         `json:"company_number"`
	CompanyStatus         string         `json:"company_status"`
	CompanyStatusDetail   string         `json:"company_status_detail"`
	ConfirmationStatement AnnualReturn   `json:"confirmation_statement"`
	DateOfCessation       string         `json:"date_of_cessation"`
	DateOfCreation        string         `json:"date_of_creation"`
	ForeignCompany        ForeignCompany `json:"foreign_company_details"`
	Liquidated            bool           `json:"has_been_liquidated"`
	Charges               bool           `json:"has_charges"`
	InsolvencyHistory     bool           `json:"has_insolvency_history"`
	Cic                   bool           `json:"is_community_interest_company"`
	Jurisdiction          string         `json:"jurisdiction"`
	LastMembersList       string         `json:"last_full_members_list_date"`
	Links                 Links          `json:"links"`
	PartialData           string         `json:"partial_data_available"`
	PreviousNames         []PreviousName `json:"previous_company_names"`
	RegisteredOffice      Address        `json:"registered_office_address"`
	RoDispute             bool           `json:"registered_office_is_in_dispute"`
	SicCodes              []string       `json:"sic_codes"`
	CompanyType           string         `json:"type"`
	RoUndeliverable       bool           `json:"undeliverable_registered_office_address"`
}

type AnnualAccounts struct {
	RefDate      RefDate `json:"accounting_reference_date"`
	LastAccounts struct {
		MadeUpTo string `json:"made_up_to"`
		Type     string `json:"type"`
	} `json:"last_accounts"`
	NextDue      string `json:"next_due"`
	NextMadeUpTo string `json:"next_made_up_to"`
	Overdue      bool   `json:"overdue"`
}

type RefDate struct {
	Day   string `json:"day"`
	Month string `json:"month"`
}

type AnnualReturn struct {
	LastMadeUpTo string `json:"last_made_up_to"`
	NextDue      string `json:"next_due"`
	NextMadeUpTo string `json:"next_made_up_to"`
	Overdue      bool   `json:"overdue"`
}

type Branch struct {
	Activity            string `json:"business_activity"`
	ParentCompanyCame   string `json:"parent_company_name"`
	ParentCompanyNumber string `json:"parent_company_number"`
}

type ForeignCompany struct {
	AccountingRequirement struct {
		AccountType string `json:"foreign_account_type"`
		Terms       string `json:"terms_of_account_publication"`
	} `json:"accounting_requirement"`
	Accounts struct {
		From   RefDate `json:"account_period_from"`
		To     RefDate `json:"account_period_to"`
		Within struct {
			Months int `json:"months"`
		} `json:"must_file_within"`
	} `json:"accounts`
	BusinessActivity    string `json:"business_activity"`
	CompanyType         string `json:"company_type"`
	GovernedBy          string `json:"governed_by"`
	FinanceInstitution  bool   `json:"is_a_credit_finance_institution"`
	OriginatingRegistry struct {
		Country string `json:"country"`
		Name    string `json:"name"`
	} `json:"originating_registry"`
	RegistrationNumber string `json:"registration_number"`
}

type Links struct {
	Charges       string `json:"charges"`
	FilingHistory string `json:"filing_history"`
	Insolvency    string `json:"insolvency"`
	Officers      string `json:"officers"`
	Psc           string `json:"persons_with_significant_control"`
	PscStatements string `json:"persons_with_significant_control_statements`
	Registers     string `json:"registers"`
	Self          string `json:"self"`
}

type PreviousName struct {
	CeasedOn      string `json:"ceased_on"`
	EffectiveFrom string `json:"effective_from"`
	Name          string `json:"name"`
}

type Address struct {
	Address1 string `json:"address_line_1"`
	Address2 string `json:"address_line_2"`
	CareOf   string `json:"care_of"`
	Country  string `json:"country"`
	Locality string `json:"locality"`
	PoBox    string `json:"po_box"`
	Postcode string `json:"postal_code"`
	Premises string `json:"premises"`
	Region   string `json:"region"`
}

type CoHouseAPI struct {
	apiKey        string
	companyNumber string
}

func Explore(n string) *CoHouseAPI {
	ak := os.Getenv("CHOUSE_APIKEY")
	if ak == "" {
		fmt.Println("ERR: Env variable 'CHOUSE_APIKEY' is empty.")
		os.Exit(0)
	}
	return &CoHouseAPI{apiKey: ak, companyNumber: n}
}

func (ch *CoHouseAPI) callApi(path string) ([]byte, error) {
	url := defaultURL + path

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.SetBasicAuth(ch.apiKey, "")
	req.Header.Set("Accept", contentType)

	client := &http.Client{}
	resp, errc := client.Do(req)
	if errc != nil {
		return nil, errc
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return nil, errors.New(resp.Status)
	}

	return ioutil.ReadAll(resp.Body)
}

func (ch *CoHouseAPI) Company() (*Company, error) {
	c := &Company{}

	body, err := ch.callApi("/company/" + ch.companyNumber)
	if err != nil {
		return c, err
	}

	err = json.Unmarshal(body, &c)
	if err != nil {
		return c, err
	}

	return c, nil
}
<commit_msg>Add endpoint for Filing History<commit_after>package main

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

const (
	defaultURL  = "https://api.companieshouse.gov.uk"
	contentType = "application/json"
)

type Company struct {
	Etag                  string         `json:"etag"`
	Accounts              AnnualAccounts `json:"accounts"`
	AnnualReturn          AnnualReturn   `json:"annual_return"`
	Branch                Branch         `json:"branch_company_details"`
	CanFile               bool           `json:"can_file"`
	CompanyName           string         `json:"company_name"`
	CompanyNumber         string         `json:"company_number"`
	CompanyStatus         string         `json:"company_status"`
	CompanyStatusDetail   string         `json:"company_status_detail"`
	ConfirmationStatement AnnualReturn   `json:"confirmation_statement"`
	DateOfCessation       string         `json:"date_of_cessation"`
	DateOfCreation        string         `json:"date_of_creation"`
	ForeignCompany        ForeignCompany `json:"foreign_company_details"`
	Liquidated            bool           `json:"has_been_liquidated"`
	Charges               bool           `json:"has_charges"`
	InsolvencyHistory     bool           `json:"has_insolvency_history"`
	Cic                   bool           `json:"is_community_interest_company"`
	Jurisdiction          string         `json:"jurisdiction"`
	LastMembersList       string         `json:"last_full_members_list_date"`
	Links                 Links          `json:"links"`
	PartialData           string         `json:"partial_data_available"`
	PreviousNames         []PreviousName `json:"previous_company_names"`
	RegisteredOffice      Address        `json:"registered_office_address"`
	RoDispute             bool           `json:"registered_office_is_in_dispute"`
	SicCodes              []string       `json:"sic_codes"`
	CompanyType           string         `json:"type"`
	RoUndeliverable       bool           `json:"undeliverable_registered_office_address"`
}

type AnnualAccounts struct {
	RefDate      RefDate `json:"accounting_reference_date"`
	LastAccounts struct {
		MadeUpTo string `json:"made_up_to"`
		Type     string `json:"type"`
	} `json:"last_accounts"`
	NextDue      string `json:"next_due"`
	NextMadeUpTo string `json:"next_made_up_to"`
	Overdue      bool   `json:"overdue"`
}

type RefDate struct {
	Day   string `json:"day"`
	Month string `json:"month"`
}

type AnnualReturn struct {
	LastMadeUpTo string `json:"last_made_up_to"`
	NextDue      string `json:"next_due"`
	NextMadeUpTo string `json:"next_made_up_to"`
	Overdue      bool   `json:"overdue"`
}

type Branch struct {
	Activity            string `json:"business_activity"`
	ParentCompanyCame   string `json:"parent_company_name"`
	ParentCompanyNumber string `json:"parent_company_number"`
}

type ForeignCompany struct {
	AccountingRequirement struct {
		AccountType string `json:"foreign_account_type"`
		Terms       string `json:"terms_of_account_publication"`
	} `json:"accounting_requirement"`
	Accounts struct {
		From   RefDate `json:"account_period_from"`
		To     RefDate `json:"account_period_to"`
		Within struct {
			Months int `json:"months"`
		} `json:"must_file_within"`
	} `json:"accounts`
	BusinessActivity    string `json:"business_activity"`
	CompanyType         string `json:"company_type"`
	GovernedBy          string `json:"governed_by"`
	FinanceInstitution  bool   `json:"is_a_credit_finance_institution"`
	OriginatingRegistry struct {
		Country string `json:"country"`
		Name    string `json:"name"`
	} `json:"originating_registry"`
	RegistrationNumber string `json:"registration_number"`
}

type Links struct {
	Charges       string `json:"charges"`
	FilingHistory string `json:"filing_history"`
	Insolvency    string `json:"insolvency"`
	Officers      string `json:"officers"`
	Psc           string `json:"persons_with_significant_control"`
	PscStatements string `json:"persons_with_significant_control_statements`
	Registers     string `json:"registers"`
	Self          string `json:"self"`
}

type PreviousName struct {
	CeasedOn      string `json:"ceased_on"`
	EffectiveFrom string `json:"effective_from"`
	Name          string `json:"name"`
}

type Address struct {
	Address1 string `json:"address_line_1"`
	Address2 string `json:"address_line_2"`
	CareOf   string `json:"care_of"`
	Country  string `json:"country"`
	Locality string `json:"locality"`
	PoBox    string `json:"po_box"`
	Postcode string `json:"postal_code"`
	Premises string `json:"premises"`
	Region   string `json:"region"`
}

type FilingHistoryList struct {
	Etag         string              `json:"etag"`
	Status       string              `json:"filing_history_status"`
	Items        []FilingHistoryItem `json:"items"`
	ItemsPerPage int                 `json:"items_per_page"`
	Kind         string              `json:"kind"`
	Start        int                 `json:"start_index"`
	TotalCount   int                 `json:"total_count"`
}

type FilingHistoryItem struct {
	Annotations []struct {
		Annotation        string `json:"annotation"`
		Category          string `json:"category"`
		Date              string `json:"date"`
		Description       string `json:"description"`
		DescriptionValues struct {
			Description string `json:"description"`
		} `json:"description_values"`
		Type string `json:"type"`
	} `json:"annotations"`
	Associated []struct {
		ActionDate        int    `json:"action_date"`
		Category          string `json:"category"`
		Date              string `json:"date"`
		Description       string `json:"description"`
		DescriptionValues struct {
			Capital []struct {
				Currency string `json:"currency"`
				Figure   string `json:"figure"`
			} `json:"capital"`
			Date string `json:"date"`
		} `json:"description_values"`
		OriginalDescription string `json:"original_description"`
		Type                string `json:"type"`
	} `json:"associated_filings"`
	DescriptionValues struct {
		MadeUpDate      string `json:"made_up_date"`
		OfficerName     string `json:"officer_name"`
		AppointmentDate string `json:"appointment_date"`
		NewAddress      string `json:"new_address"`
		ChangeDate      string `json:"change_date"`
		OldAddress      string `json:"old_address"`
		Date            string `json:"date"`
		Capital         []struct {
			Figure   string `json:"figure"`
			Currency string `json:"currency"`
		} `json:"capital"`
	} `json:"description_values"`
	Barcode     string `json:"barcode"`
	Category    string `json:"category"`
	Date        string `json:"date"`
	ActionDate  string `json:"action_date"`
	Description string `json:"description"`
	Links       Links  `json:"links"`
	Pages       int    `json:"pages"`
	PaperFiled  bool   `json:"paper_filed"`
	Resolutions []struct {
		Category          string `json:"category"`
		DeltaAt           string `json:"delta_at"`
		Description       string `json:"description"`
		DescriptionValues struct {
			Description string `json:"description"`
			ResType     string `json:"res_type"`
		} `json:"description_values"`
		DocumentID  string `json:"document_id"`
		ReceiveDate string `json:"receive_date"`
		// It's either Array or String...
		// Subcategory struct `json:"subcategory"`
		Type string `json:"type"`
	} `json:"resolutions"`
	Subcategory   string `json:"subcategory"`
	TransactionID string `json:"transaction_id"`
	Type          string `json:"type"`
}

type CoHouseAPI struct {
	apiKey        string
	companyNumber string
}

func Explore(n string) *CoHouseAPI {
	ak := os.Getenv("CHOUSE_APIKEY")
	if ak == "" {
		fmt.Println("ERR: Env variable 'CHOUSE_APIKEY' is empty.")
		os.Exit(0)
	}
	return &CoHouseAPI{apiKey: ak, companyNumber: n}
}

func (ch *CoHouseAPI) callApi(path string) ([]byte, error) {
	url := defaultURL + path

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return nil, err
	}
	req.SetBasicAuth(ch.apiKey, "")
	req.Header.Set("Accept", contentType)

	client := &http.Client{}
	resp, errc := client.Do(req)
	if errc != nil {
		return nil, errc
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return nil, errors.New(resp.Status)
	}

	return ioutil.ReadAll(resp.Body)
}

func (ch *CoHouseAPI) Company() (*Company, error) {
	c := &Company{}

	body, err := ch.callApi("/company/" + ch.companyNumber)
	if err != nil {
		return c, err
	}

	err = json.Unmarshal(body, &c)
	if err != nil {
		return c, err
	}

	return c, nil
}

func (ch *CoHouseAPI) Filings() (*FilingHistoryList, error) {
	fhl := &FilingHistoryList{}

	body, err := ch.callApi("/company/" + ch.companyNumber + "/filing-history")
	if err != nil {
		return fhl, err
	}

	err = json.Unmarshal(body, &fhl)
	if err != nil {
		return fhl, err
	}

	return fhl, nil
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"log"
	"os"

	"github.com/codegangsta/cli"
	docker "github.com/fsouza/go-dockerclient"
	consul "github.com/hashicorp/consul/api"
)

var (
	docker_client docker.Client
)

type ConsulEvent struct {
	Status string `json:"Status,omitempty" yaml:"Status,omitempty"`
	ID     string `json:"ID,omitempty" yaml:"ID,omitempty"`
	From   string `json:"From,omitempty" yaml:"From,omitempty"`
	Time   int64  `json:"Time,omitempty" yaml:"Time,omitempty"`
	Host   string `json:"Host,omitempty" yaml:"Host,omitempty"`
}

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

func bridgeEvents(docker_socket string, consul_addr string, consul_token string, host string) {
	// Docker Client
	endpoint := "unix://" + docker_socket
	docker_client, err := docker.NewClient(endpoint)

	assert(err)

	consul_config := consul.DefaultConfig()
	consul_config.Address = consul_addr

	if consul_token != "" {
		consul_config.Token = consul_token
	}

	// Consul Client
	consul_client, err := consul.NewClient(consul_config)

	assert(err)

	consul_events := consul_client.Event()

	events := make(chan *docker.APIEvents)
	assert(docker_client.AddEventListener(events))
	log.Println("Listening for Docker events ...")

	quit := make(chan struct{})

	for msg := range events {
		b, json_err := json.Marshal(msg)
		if json_err != nil {
			log.Println("Docker Event: Error Parsing JSON", json_err)
			return
		}

		evt := ConsulEvent{}
		json.Unmarshal(b, &evt)
		evt.Host = host

		c, json_err := json.Marshal(evt)
		if json_err != nil {
			log.Println("Docker Event: Error Parsing JSON", json_err)
			return
		}

		e := &consul.UserEvent{Name: "docker", Payload: c}
		q := &consul.WriteOptions{}

		msg, _, fire_err := consul_events.Fire(e, q)
		if fire_err != nil {
			log.Println("Docker Event: Consul Event Fire Error,", fire_err)
			return
		}

		log.Println("Docker Event: Consul Event Delivered,", msg)
	}

	close(quit)
	log.Fatal("Docker event loop closed") // todo: reconnect?
}

func main() {
	app := cli.NewApp()

	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "docker-socket",
			Value: "/var/run/docker.sock",
			Usage: "Path to Docker Socket",
		},
		cli.StringFlag{
			Name:   "consul-http-addr",
			Value:  "localhost:8500",
			Usage:  "Consul HTTP ADDR",
			EnvVar: "CONSUL_ADDR",
		},
		cli.StringFlag{
			Name:   "consul-token",
			Usage:  "Consul ACL Token",
			EnvVar: "CONSUL_TOKEN",
		},
		cli.StringFlag{
			Name:   "hostname",
			Usage:  "Hostname to use for events",
			EnvVar: "HOSTNAME",
		},
	}

	app.Name = "consul-docker-event-bridge"
	app.Usage = "Send all docker events to consul"
	app.Version = "1.0.0"
	app.Action = func(c *cli.Context) {
		bridgeEvents(c.String("docker-socket"), c.String("consul-http-addr"), c.String("consul-token"), c.String("host"))
	}

	app.Run(os.Args)
}
<commit_msg>fixing hostname<commit_after>package main

import (
	"encoding/json"
	"log"
	"os"

	"github.com/codegangsta/cli"
	docker "github.com/fsouza/go-dockerclient"
	consul "github.com/hashicorp/consul/api"
)

var (
	docker_client docker.Client
)

type ConsulEvent struct {
	Status string `json:"Status,omitempty" yaml:"Status,omitempty"`
	ID     string `json:"ID,omitempty" yaml:"ID,omitempty"`
	From   string `json:"From,omitempty" yaml:"From,omitempty"`
	Time   int64  `json:"Time,omitempty" yaml:"Time,omitempty"`
	Host   string `json:"Host,omitempty" yaml:"Host,omitempty"`
}

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

func bridgeEvents(docker_socket string, consul_addr string, consul_token string, host string) {
	// Docker Client
	endpoint := "unix://" + docker_socket
	docker_client, err := docker.NewClient(endpoint)

	assert(err)

	consul_config := consul.DefaultConfig()
	consul_config.Address = consul_addr

	if consul_token != "" {
		consul_config.Token = consul_token
	}

	// Consul Client
	consul_client, err := consul.NewClient(consul_config)

	assert(err)

	consul_events := consul_client.Event()

	events := make(chan *docker.APIEvents)
	assert(docker_client.AddEventListener(events))
	log.Println("Listening for Docker events ...")

	quit := make(chan struct{})

	for msg := range events {
		b, json_err := json.Marshal(msg)
		if json_err != nil {
			log.Println("Docker Event: Error Parsing JSON", json_err)
			return
		}

		evt := ConsulEvent{}
		json.Unmarshal(b, &evt)
		evt.Host = host

		c, json_err := json.Marshal(evt)
		if json_err != nil {
			log.Println("Docker Event: Error Parsing JSON", json_err)
			return
		}

		e := &consul.UserEvent{Name: "docker", Payload: c}
		q := &consul.WriteOptions{}

		msg, _, fire_err := consul_events.Fire(e, q)
		if fire_err != nil {
			log.Println("Docker Event: Consul Event Fire Error,", fire_err)
			return
		}

		log.Println("Docker Event: Consul Event Delivered,", msg)
	}

	close(quit)
	log.Fatal("Docker event loop closed") // todo: reconnect?
}

func main() {
	app := cli.NewApp()

	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "docker-socket",
			Value: "/var/run/docker.sock",
			Usage: "Path to Docker Socket",
		},
		cli.StringFlag{
			Name:   "consul-http-addr",
			Value:  "localhost:8500",
			Usage:  "Consul HTTP ADDR",
			EnvVar: "CONSUL_ADDR",
		},
		cli.StringFlag{
			Name:   "consul-token",
			Usage:  "Consul ACL Token",
			EnvVar: "CONSUL_TOKEN",
		},
		cli.StringFlag{
			Name:   "hostname",
			Usage:  "Hostname to use for events",
			EnvVar: "HOSTNAME",
		},
	}

	app.Name = "consul-docker-event-bridge"
	app.Usage = "Send all docker events to consul"
	app.Version = "1.0.1"
	app.Action = func(c *cli.Context) {
		bridgeEvents(c.String("docker-socket"), c.String("consul-http-addr"), c.String("consul-token"), c.String("hostname"))
	}

	app.Run(os.Args)
}
<|endoftext|>