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

// This is a blog server for articles written in present format.
// It powers blog.golang.org.
package main

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"time"

	"code.google.com/p/go.blog/pkg/atom"
	_ "code.google.com/p/go.talks/pkg/playground"
	"code.google.com/p/go.talks/pkg/present"
)

const (
	hostname     = "blog.golang.org"
	baseURL      = "http://" + hostname
	homeArticles = 5  // articles to display on the home page
	feedArticles = 10 // articles to include in Atom and JSON feeds
)

var validJSONPFunc = regexp.MustCompile(`(?i)^[a-z_][a-z0-9_.]*$`)

// Doc represents an article, adorned with presentation data:
// its absolute path and related articles.
type Doc struct {
	*present.Doc
	Permalink    string
	Path         string
	Related      []*Doc
	Newer, Older *Doc
	HTML         template.HTML // rendered article
}

// Server implements an http.Handler that serves blog articles.
type Server struct {
	docs     []*Doc
	tags     []string
	docPaths map[string]*Doc
	docTags  map[string][]*Doc
	template struct {
		home, index, article, doc *template.Template
	}
	atomFeed []byte // pre-rendered Atom feed
	jsonFeed []byte // pre-rendered JSON feed
	content  http.Handler
}

// NewServer constructs a new Server, serving articles from the specified
// contentPath generated from templates from templatePath.
func NewServer(contentPath, templatePath string) (*Server, error) {
	present.PlayEnabled = true

	root := filepath.Join(templatePath, "root.tmpl")
	parse := func(name string) (*template.Template, error) {
		t := template.New("").Funcs(funcMap)
		return t.ParseFiles(root, filepath.Join(templatePath, name))
	}

	s := &Server{}

	// Parse templates.
	var err error
	s.template.home, err = parse("home.tmpl")
	if err != nil {
		return nil, err
	}
	s.template.index, err = parse("index.tmpl")
	if err != nil {
		return nil, err
	}
	s.template.article, err = parse("article.tmpl")
	if err != nil {
		return nil, err
	}
	p := present.Template().Funcs(funcMap)
	s.template.doc, err = p.ParseFiles(filepath.Join(templatePath, "doc.tmpl"))
	if err != nil {
		return nil, err
	}

	// Load content.
	err = s.loadDocs(filepath.Clean(contentPath))
	if err != nil {
		return nil, err
	}

	err = s.renderAtomFeed()
	if err != nil {
		return nil, err
	}

	err = s.renderJSONFeed()
	if err != nil {
		return nil, err
	}

	// Set up content file server.
	s.content = http.FileServer(http.Dir(contentPath))

	return s, nil
}

var funcMap = template.FuncMap{
	"sectioned": sectioned,
	"authors":   authors,
}

// sectioned returns true if the provided Doc contains more than one section.
// This is used to control whether to display the table of contents and headings.
func sectioned(d *present.Doc) bool {
	return len(d.Sections) > 1
}

// authors returns a comma-separated list of author names.
func authors(authors []present.Author) string {
	var b bytes.Buffer
	last := len(authors) - 1
	for i, a := range authors {
		if i > 0 {
			if i == last {
				b.WriteString(" and ")
			} else {
				b.WriteString(", ")
			}
		}
		b.WriteString(authorName(a))
	}
	return b.String()
}

// authorName returns the first line of the Author text: the author's name.
func authorName(a present.Author) string {
	el := a.TextElem()
	if len(el) == 0 {
		return ""
	}
	text, ok := el[0].(present.Text)
	if !ok || len(text.Lines) == 0 {
		return ""
	}
	return text.Lines[0]
}

// loadDocs reads all content from the provided file system root, renders all
// the articles it finds, adds them to the Server's docs field, computes the
// denormalized docPaths, docTags, and tags fields, and populates the various
// helper fields (Next, Previous, Related) for each Doc.
func (s *Server) loadDocs(root string) error {
	// Read content into docs field.
	const ext = ".article"
	fn := func(p string, info os.FileInfo, err error) error {
		if filepath.Ext(p) != ext {
			return nil
		}
		f, err := os.Open(p)
		if err != nil {
			return err
		}
		defer f.Close()
		d, err := present.Parse(f, p, 0)
		if err != nil {
			return err
		}
		html := new(bytes.Buffer)
		err = d.Render(html, s.template.doc)
		if err != nil {
			return err
		}
		p = p[len(root) : len(p)-len(ext)] // trim root and extension
		s.docs = append(s.docs, &Doc{
			Doc:       d,
			Path:      p,
			Permalink: baseURL + p,
			HTML:      template.HTML(html.String()),
		})
		return nil
	}
	err := filepath.Walk(root, fn)
	if err != nil {
		return err
	}
	sort.Sort(docsByTime(s.docs))

	// Pull out doc paths and tags and put in reverse-associating maps.
	s.docPaths = make(map[string]*Doc)
	s.docTags = make(map[string][]*Doc)
	for _, d := range s.docs {
		s.docPaths[d.Path] = d
		for _, t := range d.Tags {
			s.docTags[t] = append(s.docTags[t], d)
		}
	}

	// Pull out unique sorted list of tags.
	for t := range s.docTags {
		s.tags = append(s.tags, t)
	}
	sort.Strings(s.tags)

	// Set up presentation-related fields, Newer, Older, and Related.
	for _, doc := range s.docs {
		// Newer, Older: docs adjacent to doc
		for i := range s.docs {
			if s.docs[i] != doc {
				continue
			}
			if i > 0 {
				doc.Newer = s.docs[i-1]
			}
			if i+1 < len(s.docs) {
				doc.Older = s.docs[i+1]
			}
			break
		}

		// Related: all docs that share tags with doc.
		related := make(map[*Doc]bool)
		for _, t := range doc.Tags {
			for _, d := range s.docTags[t] {
				if d != doc {
					related[d] = true
				}
			}
		}
		for d := range related {
			doc.Related = append(doc.Related, d)
		}
		sort.Sort(docsByTime(doc.Related))
	}

	return nil
}

// renderAtomFeed generates an XML Atom feed and stores it in the Server's
// atomFeed field.
func (s *Server) renderAtomFeed() error {
	var updated time.Time
	if len(s.docs) > 1 {
		updated = s.docs[0].Time
	}
	feed := atom.Feed{
		Title:   "The Go Programming Language Blog",
		ID:      "tag:" + hostname + ",2013:" + hostname,
		Updated: atom.Time(updated),
		Link: []atom.Link{{
			Rel:  "self",
			Href: baseURL + "/feed.atom",
		}},
	}
	for i, doc := range s.docs {
		if i >= feedArticles {
			break
		}
		e := &atom.Entry{
			Title: doc.Title,
			ID:    feed.ID + doc.Path,
			Link: []atom.Link{{
				Rel:  "alternate",
				Href: baseURL + doc.Path,
			}},
			Published: atom.Time(doc.Time),
			Updated:   atom.Time(doc.Time),
			Summary: &atom.Text{
				Type: "html",
				Body: summary(doc),
			},
			Content: &atom.Text{
				Type: "html",
				Body: string(doc.HTML),
			},
			Author: &atom.Person{
				Name: authors(doc.Authors),
			},
		}
		feed.Entry = append(feed.Entry, e)
	}
	data, err := xml.Marshal(&feed)
	if err != nil {
		return err
	}
	s.atomFeed = data
	return nil
}

type jsonItem struct {
	Title   string
	Link    string
	Time    time.Time
	Summary string
	Content string
	Author  string
}

// renderJSONFeed generates a JSON feed and stores it in the Server's jsonFeed
// field.
func (s *Server) renderJSONFeed() error {
	var feed []jsonItem
	for i, doc := range s.docs {
		if i >= feedArticles {
			break
		}
		item := jsonItem{
			Title:   doc.Title,
			Link:    baseURL + doc.Path,
			Time:    doc.Time,
			Summary: summary(doc),
			Content: string(doc.HTML),
			Author:  authors(doc.Authors),
		}
		feed = append(feed, item)
	}
	data, err := json.Marshal(feed)
	if err != nil {
		return err
	}
	s.jsonFeed = data
	return nil
}

// summary returns the first paragraph of text from the provided Doc.
func summary(d *Doc) string {
	if len(d.Sections) == 0 {
		return ""
	}
	for _, elem := range d.Sections[0].Elem {
		text, ok := elem.(present.Text)
		if !ok || text.Pre {
			// skip everything but non-text elements
			continue
		}
		var buf bytes.Buffer
		for _, s := range text.Lines {
			buf.WriteString(string(present.Style(s)))
			buf.WriteByte('\n')
		}
		return buf.String()
	}
	return ""
}

// rootData encapsulates data destined for the root template.
type rootData struct {
	Doc  *Doc
	Data interface{}
}

// ServeHTTP servers either an article list or a single article.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var (
		d rootData
		t *template.Template
	)
	switch p := r.URL.Path; p {
	case "/":
		d.Data = s.docs
		if len(s.docs) > homeArticles {
			d.Data = s.docs[:homeArticles]
		}
		t = s.template.home
	case "/index":
		d.Data = s.docs
		t = s.template.index
	case "/feed.atom", "/feeds/posts/default":
		w.Header().Set("Content-type", "application/atom+xml; charset=utf-8")
		w.Write(s.atomFeed)
		return
	case "/.json":
		if p := r.FormValue("jsonp"); validJSONPFunc.MatchString(p) {
			w.Header().Set("Content-type", "application/javascript; charset=utf-8")
			fmt.Fprintf(w, "%v(%s)", p, s.jsonFeed)
			return
		}
		w.Header().Set("Content-type", "application/json")
		w.Write(s.jsonFeed)
		return
	default:
		doc, ok := s.docPaths[p]
		if !ok {
			// Not a doc; try to just serve static content.
			s.content.ServeHTTP(w, r)
			return
		}
		d.Doc = doc
		t = s.template.article
	}
	err := t.ExecuteTemplate(w, "root", d)
	if err != nil {
		log.Println(err)
	}
}

// docsByTime implements sort.Interface, sorting Docs by their Time field.
type docsByTime []*Doc

func (s docsByTime) Len() int           { return len(s) }
func (s docsByTime) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s docsByTime) Less(i, j int) bool { return s[i].Time.After(s[j].Time) }
<commit_msg>[x/blog] go.blog/cmd/blog: fix JSON content-type<commit_after>// Copyright 2013 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This is a blog server for articles written in present format.
// It powers blog.golang.org.
package main

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"time"

	"code.google.com/p/go.blog/pkg/atom"
	_ "code.google.com/p/go.talks/pkg/playground"
	"code.google.com/p/go.talks/pkg/present"
)

const (
	hostname     = "blog.golang.org"
	baseURL      = "http://" + hostname
	homeArticles = 5  // articles to display on the home page
	feedArticles = 10 // articles to include in Atom and JSON feeds
)

var validJSONPFunc = regexp.MustCompile(`(?i)^[a-z_][a-z0-9_.]*$`)

// Doc represents an article, adorned with presentation data:
// its absolute path and related articles.
type Doc struct {
	*present.Doc
	Permalink    string
	Path         string
	Related      []*Doc
	Newer, Older *Doc
	HTML         template.HTML // rendered article
}

// Server implements an http.Handler that serves blog articles.
type Server struct {
	docs     []*Doc
	tags     []string
	docPaths map[string]*Doc
	docTags  map[string][]*Doc
	template struct {
		home, index, article, doc *template.Template
	}
	atomFeed []byte // pre-rendered Atom feed
	jsonFeed []byte // pre-rendered JSON feed
	content  http.Handler
}

// NewServer constructs a new Server, serving articles from the specified
// contentPath generated from templates from templatePath.
func NewServer(contentPath, templatePath string) (*Server, error) {
	present.PlayEnabled = true

	root := filepath.Join(templatePath, "root.tmpl")
	parse := func(name string) (*template.Template, error) {
		t := template.New("").Funcs(funcMap)
		return t.ParseFiles(root, filepath.Join(templatePath, name))
	}

	s := &Server{}

	// Parse templates.
	var err error
	s.template.home, err = parse("home.tmpl")
	if err != nil {
		return nil, err
	}
	s.template.index, err = parse("index.tmpl")
	if err != nil {
		return nil, err
	}
	s.template.article, err = parse("article.tmpl")
	if err != nil {
		return nil, err
	}
	p := present.Template().Funcs(funcMap)
	s.template.doc, err = p.ParseFiles(filepath.Join(templatePath, "doc.tmpl"))
	if err != nil {
		return nil, err
	}

	// Load content.
	err = s.loadDocs(filepath.Clean(contentPath))
	if err != nil {
		return nil, err
	}

	err = s.renderAtomFeed()
	if err != nil {
		return nil, err
	}

	err = s.renderJSONFeed()
	if err != nil {
		return nil, err
	}

	// Set up content file server.
	s.content = http.FileServer(http.Dir(contentPath))

	return s, nil
}

var funcMap = template.FuncMap{
	"sectioned": sectioned,
	"authors":   authors,
}

// sectioned returns true if the provided Doc contains more than one section.
// This is used to control whether to display the table of contents and headings.
func sectioned(d *present.Doc) bool {
	return len(d.Sections) > 1
}

// authors returns a comma-separated list of author names.
func authors(authors []present.Author) string {
	var b bytes.Buffer
	last := len(authors) - 1
	for i, a := range authors {
		if i > 0 {
			if i == last {
				b.WriteString(" and ")
			} else {
				b.WriteString(", ")
			}
		}
		b.WriteString(authorName(a))
	}
	return b.String()
}

// authorName returns the first line of the Author text: the author's name.
func authorName(a present.Author) string {
	el := a.TextElem()
	if len(el) == 0 {
		return ""
	}
	text, ok := el[0].(present.Text)
	if !ok || len(text.Lines) == 0 {
		return ""
	}
	return text.Lines[0]
}

// loadDocs reads all content from the provided file system root, renders all
// the articles it finds, adds them to the Server's docs field, computes the
// denormalized docPaths, docTags, and tags fields, and populates the various
// helper fields (Next, Previous, Related) for each Doc.
func (s *Server) loadDocs(root string) error {
	// Read content into docs field.
	const ext = ".article"
	fn := func(p string, info os.FileInfo, err error) error {
		if filepath.Ext(p) != ext {
			return nil
		}
		f, err := os.Open(p)
		if err != nil {
			return err
		}
		defer f.Close()
		d, err := present.Parse(f, p, 0)
		if err != nil {
			return err
		}
		html := new(bytes.Buffer)
		err = d.Render(html, s.template.doc)
		if err != nil {
			return err
		}
		p = p[len(root) : len(p)-len(ext)] // trim root and extension
		s.docs = append(s.docs, &Doc{
			Doc:       d,
			Path:      p,
			Permalink: baseURL + p,
			HTML:      template.HTML(html.String()),
		})
		return nil
	}
	err := filepath.Walk(root, fn)
	if err != nil {
		return err
	}
	sort.Sort(docsByTime(s.docs))

	// Pull out doc paths and tags and put in reverse-associating maps.
	s.docPaths = make(map[string]*Doc)
	s.docTags = make(map[string][]*Doc)
	for _, d := range s.docs {
		s.docPaths[d.Path] = d
		for _, t := range d.Tags {
			s.docTags[t] = append(s.docTags[t], d)
		}
	}

	// Pull out unique sorted list of tags.
	for t := range s.docTags {
		s.tags = append(s.tags, t)
	}
	sort.Strings(s.tags)

	// Set up presentation-related fields, Newer, Older, and Related.
	for _, doc := range s.docs {
		// Newer, Older: docs adjacent to doc
		for i := range s.docs {
			if s.docs[i] != doc {
				continue
			}
			if i > 0 {
				doc.Newer = s.docs[i-1]
			}
			if i+1 < len(s.docs) {
				doc.Older = s.docs[i+1]
			}
			break
		}

		// Related: all docs that share tags with doc.
		related := make(map[*Doc]bool)
		for _, t := range doc.Tags {
			for _, d := range s.docTags[t] {
				if d != doc {
					related[d] = true
				}
			}
		}
		for d := range related {
			doc.Related = append(doc.Related, d)
		}
		sort.Sort(docsByTime(doc.Related))
	}

	return nil
}

// renderAtomFeed generates an XML Atom feed and stores it in the Server's
// atomFeed field.
func (s *Server) renderAtomFeed() error {
	var updated time.Time
	if len(s.docs) > 1 {
		updated = s.docs[0].Time
	}
	feed := atom.Feed{
		Title:   "The Go Programming Language Blog",
		ID:      "tag:" + hostname + ",2013:" + hostname,
		Updated: atom.Time(updated),
		Link: []atom.Link{{
			Rel:  "self",
			Href: baseURL + "/feed.atom",
		}},
	}
	for i, doc := range s.docs {
		if i >= feedArticles {
			break
		}
		e := &atom.Entry{
			Title: doc.Title,
			ID:    feed.ID + doc.Path,
			Link: []atom.Link{{
				Rel:  "alternate",
				Href: baseURL + doc.Path,
			}},
			Published: atom.Time(doc.Time),
			Updated:   atom.Time(doc.Time),
			Summary: &atom.Text{
				Type: "html",
				Body: summary(doc),
			},
			Content: &atom.Text{
				Type: "html",
				Body: string(doc.HTML),
			},
			Author: &atom.Person{
				Name: authors(doc.Authors),
			},
		}
		feed.Entry = append(feed.Entry, e)
	}
	data, err := xml.Marshal(&feed)
	if err != nil {
		return err
	}
	s.atomFeed = data
	return nil
}

type jsonItem struct {
	Title   string
	Link    string
	Time    time.Time
	Summary string
	Content string
	Author  string
}

// renderJSONFeed generates a JSON feed and stores it in the Server's jsonFeed
// field.
func (s *Server) renderJSONFeed() error {
	var feed []jsonItem
	for i, doc := range s.docs {
		if i >= feedArticles {
			break
		}
		item := jsonItem{
			Title:   doc.Title,
			Link:    baseURL + doc.Path,
			Time:    doc.Time,
			Summary: summary(doc),
			Content: string(doc.HTML),
			Author:  authors(doc.Authors),
		}
		feed = append(feed, item)
	}
	data, err := json.Marshal(feed)
	if err != nil {
		return err
	}
	s.jsonFeed = data
	return nil
}

// summary returns the first paragraph of text from the provided Doc.
func summary(d *Doc) string {
	if len(d.Sections) == 0 {
		return ""
	}
	for _, elem := range d.Sections[0].Elem {
		text, ok := elem.(present.Text)
		if !ok || text.Pre {
			// skip everything but non-text elements
			continue
		}
		var buf bytes.Buffer
		for _, s := range text.Lines {
			buf.WriteString(string(present.Style(s)))
			buf.WriteByte('\n')
		}
		return buf.String()
	}
	return ""
}

// rootData encapsulates data destined for the root template.
type rootData struct {
	Doc  *Doc
	Data interface{}
}

// ServeHTTP servers either an article list or a single article.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var (
		d rootData
		t *template.Template
	)
	switch p := r.URL.Path; p {
	case "/":
		d.Data = s.docs
		if len(s.docs) > homeArticles {
			d.Data = s.docs[:homeArticles]
		}
		t = s.template.home
	case "/index":
		d.Data = s.docs
		t = s.template.index
	case "/feed.atom", "/feeds/posts/default":
		w.Header().Set("Content-type", "application/atom+xml; charset=utf-8")
		w.Write(s.atomFeed)
		return
	case "/.json":
		if p := r.FormValue("jsonp"); validJSONPFunc.MatchString(p) {
			w.Header().Set("Content-type", "application/javascript; charset=utf-8")
			fmt.Fprintf(w, "%v(%s)", p, s.jsonFeed)
			return
		}
		w.Header().Set("Content-type", "application/json; charset=utf-8")
		w.Write(s.jsonFeed)
		return
	default:
		doc, ok := s.docPaths[p]
		if !ok {
			// Not a doc; try to just serve static content.
			s.content.ServeHTTP(w, r)
			return
		}
		d.Doc = doc
		t = s.template.article
	}
	err := t.ExecuteTemplate(w, "root", d)
	if err != nil {
		log.Println(err)
	}
}

// docsByTime implements sort.Interface, sorting Docs by their Time field.
type docsByTime []*Doc

func (s docsByTime) Len() int           { return len(s) }
func (s docsByTime) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s docsByTime) Less(i, j int) bool { return s[i].Time.After(s[j].Time) }
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	"gopkg.in/yaml.v2"
)

// special keys in settings
const (
	DEFAULT_KEY = "default"
	SOURCE_KEY  = "source"
	TARGET_KEY  = "target"
)

// environment variable names
const (
	HOME_DIRECTORY = "HOME"
)

// temporary definitions for dev
const (
	SETTINGS_FILE_NAME = "dotorrc.sample.yml"
	SOURCE_PATH        = "/Users/janus/work/dev/github/dotfiles"
)

func main() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Fprintln(os.Stderr, r)
		}
	}()

	settings, err := ReadSettings(SETTINGS_FILE_NAME)
	if err != nil {
		panic(err)
	}

	rules, err := BuildRules(settings)
	if err != nil {
		panic(err)
	}

	err = CreateSymbolicLinks(rules, SOURCE_PATH)
	if err != nil {
		panic(err)
	}
}

func ReadSettings(filepath string) (map[interface{}]interface{}, error) {
	file, err := ioutil.ReadFile(filepath)
	if err != nil {
		return nil, err
	}

	settings := make(map[interface{}]interface{})
	if err := yaml.Unmarshal(file, &settings); err != nil {
		return nil, err
	}

	return settings, nil
}

func BuildRules(settings map[interface{}]interface{}) (map[string]string, error) {
	defaultRules, err := BuildSpecificOsRules(DEFAULT_KEY, settings)
	if err != nil {
		return nil, err
	}

	osRules, err := BuildSpecificOsRules(runtime.GOOS, settings)
	if err != nil {
		return nil, err
	}

	rules := Extend(defaultRules, osRules)
	return rules, err
}

func BuildSpecificOsRules(os string, settings map[interface{}]interface{}) (map[string]string, error) {
	rules := make(map[string]string)
	if _, hasKey := settings[os]; !hasKey {
		return rules, nil
	}

	for _, value := range settings[os].([]interface{}) {
		switch value.(type) {
		case string:
			rules[value.(string)] = value.(string)
		case map[interface{}]interface{}:
			setting := value.(map[interface{}]interface{})
			_, hasSource := setting[SOURCE_KEY]
			_, hasTarget := setting[TARGET_KEY]
			if !(hasSource && hasTarget) {
				return nil, fmt.Errorf("specify key-values in format of \"%s:<source file name>\" and \"%s:<target file name>\" to change sysmbolic link names", SOURCE_KEY, TARGET_KEY)
			}
			source := setting[SOURCE_KEY].(string)
			target := setting[TARGET_KEY].(string)
			rules[source] = target
		}
	}

	return rules, nil
}

func Extend(m1, m2 map[string]string) map[string]string {
	result := map[string]string{}

	for v, k := range m1 {
		result[k] = v
	}
	for v, k := range m2 {
		result[k] = v
	}
	return (result)
}

func CreateSymbolicLinks(rules map[string]string, sourceDirectoryPath string) error {
	targetDirectoryAbsolutePath, err := GetHomeDirectory()
	if err != nil {
		return err
	}
	sourceDirectoryAbsolutePath, err := filepath.Abs(sourceDirectoryPath)
	if err != nil {
		return err
	}

	fmt.Printf("%s => %s\n", sourceDirectoryAbsolutePath, targetDirectoryAbsolutePath)

	for source, target := range rules {
		sourceAbsolutePath := filepath.Join(sourceDirectoryAbsolutePath, source)
		targetAbsolutePath := filepath.Join(targetDirectoryAbsolutePath, target)
		if !ExistsPath(sourceAbsolutePath) {
			fmt.Printf("source file \"%s\" is not exists. skipping.\n", targetAbsolutePath)
			continue
		}
		if ExistsPath(targetAbsolutePath) {
			fmt.Printf("target file \"%s\" is already exists. skipping.\n", targetAbsolutePath)
			continue
		}
		fmt.Printf("creating symbolic link: %s => %s\n", sourceAbsolutePath, targetAbsolutePath)
		// TODO: use os.Symlink()
	}

	return nil
}

func GetHomeDirectory() (dir string, err error) {
	environmentVariables := GetEnvironmentVariables()
	if _, ok := environmentVariables[HOME_DIRECTORY]; !ok {
		return "", fmt.Errorf("Define the environment variable \"%s\"", HOME_DIRECTORY)
	}
	return environmentVariables[HOME_DIRECTORY], nil
}

func GetEnvironmentVariables() map[string]string {
	environmentVariables := make(map[string]string)
	for _, item := range os.Environ() {
		splited := strings.Split(item, "=")
		environmentVariables[splited[0]] = splited[1]
	}
	return environmentVariables
}

func ExistsPath(path string) bool {
	_, err := os.Stat(path)
	return !os.IsNotExist(err)
}
<commit_msg>[WIP] follow code conventions<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	"gopkg.in/yaml.v2"
)

// special keys in settings
const (
	DEFAULT_KEY = "default"
	SOURCE_KEY  = "source"
	TARGET_KEY  = "target"
)

// environment variable names
const (
	HOME_DIRECTORY = "HOME"
)

// temporary definitions for dev
const (
	SETTINGS_FILE_NAME = "dotorrc.sample.yml"
	SOURCE_PATH        = "/Users/janus/work/dev/github/dotfiles"
)

func main() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Fprintln(os.Stderr, r)
		}
	}()

	settings, err := ReadSettings(SETTINGS_FILE_NAME)
	if err != nil {
		panic(err)
	}

	rules, err := buildRules(settings)
	if err != nil {
		panic(err)
	}

	err = createSymbolicLinks(rules, SOURCE_PATH)
	if err != nil {
		panic(err)
	}
}

func ReadSettings(filepath string) (map[interface{}]interface{}, error) {
	file, err := ioutil.ReadFile(filepath)
	if err != nil {
		return nil, err
	}

	settings := make(map[interface{}]interface{})
	if err := yaml.Unmarshal(file, &settings); err != nil {
		return nil, err
	}

	return settings, nil
}

func buildRules(settings map[interface{}]interface{}) (map[string]string, error) {
	defaultRules, err := buildSpecificOsRules(DEFAULT_KEY, settings)
	if err != nil {
		return nil, err
	}

	osRules, err := buildSpecificOsRules(runtime.GOOS, settings)
	if err != nil {
		return nil, err
	}

	rules := extend(defaultRules, osRules)
	return rules, err
}

func buildSpecificOsRules(os string, settings map[interface{}]interface{}) (map[string]string, error) {
	rules := make(map[string]string)
	if _, hasKey := settings[os]; !hasKey {
		return rules, nil
	}

	for _, value := range settings[os].([]interface{}) {
		switch value.(type) {
		case string:
			rules[value.(string)] = value.(string)
		case map[interface{}]interface{}:
			setting := value.(map[interface{}]interface{})
			_, hasSource := setting[SOURCE_KEY]
			_, hasTarget := setting[TARGET_KEY]
			if !(hasSource && hasTarget) {
				return nil, fmt.Errorf("specify key-values in format of \"%s:<source file name>\" and \"%s:<target file name>\" to change sysmbolic link names", SOURCE_KEY, TARGET_KEY)
			}
			source := setting[SOURCE_KEY].(string)
			target := setting[TARGET_KEY].(string)
			rules[source] = target
		}
	}

	return rules, nil
}

func extend(m1, m2 map[string]string) map[string]string {
	result := map[string]string{}

	for v, k := range m1 {
		result[k] = v
	}
	for v, k := range m2 {
		result[k] = v
	}
	return (result)
}

func createSymbolicLinks(rules map[string]string, sourceDirectoryPath string) error {
	targetDirectoryAbsolutePath, err := GetHomeDirectory()
	if err != nil {
		return err
	}
	sourceDirectoryAbsolutePath, err := filepath.Abs(sourceDirectoryPath)
	if err != nil {
		return err
	}

	fmt.Printf("%s => %s\n", sourceDirectoryAbsolutePath, targetDirectoryAbsolutePath)

	for source, target := range rules {
		sourceAbsolutePath := filepath.Join(sourceDirectoryAbsolutePath, source)
		targetAbsolutePath := filepath.Join(targetDirectoryAbsolutePath, target)
		if !ExistsPath(sourceAbsolutePath) {
			fmt.Printf("source file \"%s\" is not exists. skipping.\n", targetAbsolutePath)
			continue
		}
		if ExistsPath(targetAbsolutePath) {
			fmt.Printf("target file \"%s\" is already exists. skipping.\n", targetAbsolutePath)
			continue
		}
		fmt.Printf("creating symbolic link: %s => %s\n", sourceAbsolutePath, targetAbsolutePath)
		// TODO: use os.Symlink()
	}

	return nil
}

func GetHomeDirectory() (dir string, err error) {
	environmentVariables := GetEnvironmentVariables()
	if _, ok := environmentVariables[HOME_DIRECTORY]; !ok {
		return "", fmt.Errorf("Define the environment variable \"%s\"", HOME_DIRECTORY)
	}
	return environmentVariables[HOME_DIRECTORY], nil
}

func GetEnvironmentVariables() map[string]string {
	environmentVariables := make(map[string]string)
	for _, item := range os.Environ() {
		splited := strings.Split(item, "=")
		environmentVariables[splited[0]] = splited[1]
	}
	return environmentVariables
}

func ExistsPath(path string) bool {
	_, err := os.Stat(path)
	return !os.IsNotExist(err)
}
<|endoftext|>


<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package maas

import (
	"errors"
	"os"

	"github.com/juju/loggo"

	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/instance"
	"launchpad.net/juju-core/juju/osenv"
	"launchpad.net/juju-core/utils"
)

// Logger for the MAAS provider.
var logger = loggo.GetLogger("juju.provider.maas")

type maasEnvironProvider struct{}

var _ environs.EnvironProvider = (*maasEnvironProvider)(nil)

var providerInstance maasEnvironProvider

func init() {
	environs.RegisterProvider("maas", maasEnvironProvider{})
}

func (maasEnvironProvider) Open(cfg *config.Config) (environs.Environ, error) {
	logger.Debugf("opening environment %q.", cfg.Name())
	env, err := NewEnviron(cfg)
	if err != nil {
		return nil, err
	}
	return env, nil
}

var errAgentNameAlreadySet = errors.New(
	"maas-agent-name is already set; this should not be set by hand")

func (p maasEnvironProvider) Prepare(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) {
	attrs := cfg.UnknownAttrs()
	oldName, found := attrs["maas-agent-name"]
	if found && oldName != "" {
		return nil, errAgentNameAlreadySet
	}
	uuid, err := utils.NewUUID()
	if err != nil {
		return nil, err
	}
	attrs["maas-agent-name"] = uuid.String()
	cfg, err = cfg.Apply(attrs)
	if err != nil {
		return nil, err
	}
	return p.Open(cfg)
}

// Boilerplate config YAML.  Don't mess with the indentation or add newlines!
var boilerplateYAML = `
# https://juju.ubuntu.com/docs/config-maas.html
maas:
    type: maas

    # maas-server specifies the location of the MAAS server. It must
    # specify the base path.
    #
    maas-server: 'http://192.168.1.1/MAAS/'

    # maas-oauth holds the OAuth credentials from MAAS.
    #
    maas-oauth: '<add your OAuth credentials from MAAS here>'

`[1:]

// BoilerplateConfig is specified in the EnvironProvider interface.
func (maasEnvironProvider) BoilerplateConfig() string {
	return boilerplateYAML
}

// SecretAttrs is specified in the EnvironProvider interface.
func (prov maasEnvironProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) {
	secretAttrs := make(map[string]string)
	maasCfg, err := prov.newConfig(cfg)
	if err != nil {
		return nil, err
	}
	secretAttrs["maas-oauth"] = maasCfg.maasOAuth()
	return secretAttrs, nil
}
<commit_msg>Fix compile<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package maas

import (
	"errors"

	"github.com/juju/loggo"

	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/utils"
)

// Logger for the MAAS provider.
var logger = loggo.GetLogger("juju.provider.maas")

type maasEnvironProvider struct{}

var _ environs.EnvironProvider = (*maasEnvironProvider)(nil)

var providerInstance maasEnvironProvider

func init() {
	environs.RegisterProvider("maas", maasEnvironProvider{})
}

func (maasEnvironProvider) Open(cfg *config.Config) (environs.Environ, error) {
	logger.Debugf("opening environment %q.", cfg.Name())
	env, err := NewEnviron(cfg)
	if err != nil {
		return nil, err
	}
	return env, nil
}

var errAgentNameAlreadySet = errors.New(
	"maas-agent-name is already set; this should not be set by hand")

func (p maasEnvironProvider) Prepare(ctx environs.BootstrapContext, cfg *config.Config) (environs.Environ, error) {
	attrs := cfg.UnknownAttrs()
	oldName, found := attrs["maas-agent-name"]
	if found && oldName != "" {
		return nil, errAgentNameAlreadySet
	}
	uuid, err := utils.NewUUID()
	if err != nil {
		return nil, err
	}
	attrs["maas-agent-name"] = uuid.String()
	cfg, err = cfg.Apply(attrs)
	if err != nil {
		return nil, err
	}
	return p.Open(cfg)
}

// Boilerplate config YAML.  Don't mess with the indentation or add newlines!
var boilerplateYAML = `
# https://juju.ubuntu.com/docs/config-maas.html
maas:
    type: maas

    # maas-server specifies the location of the MAAS server. It must
    # specify the base path.
    #
    maas-server: 'http://192.168.1.1/MAAS/'

    # maas-oauth holds the OAuth credentials from MAAS.
    #
    maas-oauth: '<add your OAuth credentials from MAAS here>'

`[1:]

// BoilerplateConfig is specified in the EnvironProvider interface.
func (maasEnvironProvider) BoilerplateConfig() string {
	return boilerplateYAML
}

// SecretAttrs is specified in the EnvironProvider interface.
func (prov maasEnvironProvider) SecretAttrs(cfg *config.Config) (map[string]string, error) {
	secretAttrs := make(map[string]string)
	maasCfg, err := prov.newConfig(cfg)
	if err != nil {
		return nil, err
	}
	secretAttrs["maas-oauth"] = maasCfg.maasOAuth()
	return secretAttrs, nil
}
<|endoftext|>


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

package tcpproxy

import (
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"
)

func TestUserspaceProxy(t *testing.T) {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()

	want := "hello proxy"
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, want)
	}))
	defer ts.Close()

	u, err := url.Parse(ts.URL)
	if err != nil {
		t.Fatal(err)
	}

	var port uint16
	fmt.Sscanf(u.Port(), "%d", &port)
	p := TCPProxy{
		Listener:  l,
		Endpoints: []*net.SRV{{Target: u.Hostname(), Port: port}},
	}
	go p.Run()
	defer p.Stop()

	u.Host = l.Addr().String()

	res, err := http.Get(u.String())
	if err != nil {
		t.Fatal(err)
	}
	got, gerr := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if gerr != nil {
		t.Fatal(gerr)
	}

	if string(got) != want {
		t.Errorf("got = %s, want %s", got, want)
	}
}
<commit_msg>tcpproxy: add a test.<commit_after>// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tcpproxy

import (
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"
)

func TestUserspaceProxy(t *testing.T) {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()

	want := "hello proxy"
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, want)
	}))
	defer ts.Close()

	u, err := url.Parse(ts.URL)
	if err != nil {
		t.Fatal(err)
	}

	var port uint16
	fmt.Sscanf(u.Port(), "%d", &port)
	p := TCPProxy{
		Listener:  l,
		Endpoints: []*net.SRV{{Target: u.Hostname(), Port: port}},
	}
	go p.Run()
	defer p.Stop()

	u.Host = l.Addr().String()

	res, err := http.Get(u.String())
	if err != nil {
		t.Fatal(err)
	}
	got, gerr := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if gerr != nil {
		t.Fatal(gerr)
	}

	if string(got) != want {
		t.Errorf("got = %s, want %s", got, want)
	}
}

func TestUserspaceProxyPriority(t *testing.T) {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()

	backends := []struct {
		Payload  string
		Priority uint16
	}{
		{"hello proxy 1", 1},
		{"hello proxy 2", 2},
		{"hello proxy 3", 3},
	}

	var eps []*net.SRV
	var front *url.URL
	for _, b := range backends {
		backend := b
		ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			fmt.Fprint(w, backend.Payload)
		}))
		defer ts.Close()

		front, err = url.Parse(ts.URL)
		if err != nil {
			t.Fatal(err)
		}

		var port uint16
		fmt.Sscanf(front.Port(), "%d", &port)

		ep := &net.SRV{Target: front.Hostname(), Port: port, Priority: backend.Priority}
		eps = append(eps, ep)
	}

	p := TCPProxy{
		Listener:  l,
		Endpoints: eps,
	}
	go p.Run()
	defer p.Stop()

	front.Host = l.Addr().String()

	res, err := http.Get(front.String())
	if err != nil {
		t.Fatal(err)
	}
	got, gerr := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if gerr != nil {
		t.Fatal(gerr)
	}

	want := "hello proxy 1"
	if string(got) != want {
		t.Errorf("got = %s, want %s", got, want)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/karrick/godirwalk"
	"github.com/pkg/errors"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Fprintf(os.Stderr, "usage: %s dir1 [dir2 [dir3...]]\n", filepath.Base(os.Args[0]))
		os.Exit(2)
	}

	scratchBuffer := make([]byte, 64*1024) // allocate once and re-use each time
	var count, total int
	var err error

	for _, arg := range os.Args[1:] {
		count, err = pruneEmptyDirectories(arg, scratchBuffer)
		total += count
		if err != nil {
			break
		}
	}

	fmt.Fprintf(os.Stderr, "Removed %d empty directories\n", total)
	if err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
		os.Exit(1)
	}
}

func pruneEmptyDirectories(osDirname string, scratchBuffer []byte) (int, error) {
	var count int

	err := godirwalk.Walk(osDirname, &godirwalk.Options{
		Unsorted:      true,
		ScratchBuffer: scratchBuffer,
		Callback: func(_ string, _ *godirwalk.Dirent) error {
			// no-op while diving in; all the fun happens in PostChildrenCallback
			return nil
		},
		PostChildrenCallback: func(osPathname string, _ *godirwalk.Dirent) error {
			deChildren, err := godirwalk.ReadDirents(osPathname, scratchBuffer)
			if err != nil {
				return errors.Wrap(err, "cannot ReadDirents")
			}
			// NOTE: ReadDirents skips "." and ".."
			if len(deChildren) > 0 {
				return nil // this directory has children; no additional work here
			}
			if osPathname == osDirname {
				return nil // do not remove provided root directory
			}
			err = os.Remove(osPathname)
			if err == nil {
				count++
			}
			return err
		},
	})

	return count, err
}
<commit_msg>remove dependency from example<commit_after>package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/karrick/godirwalk"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Fprintf(os.Stderr, "usage: %s dir1 [dir2 [dir3...]]\n", filepath.Base(os.Args[0]))
		os.Exit(2)
	}

	scratchBuffer := make([]byte, 64*1024) // allocate once and re-use each time
	var count, total int
	var err error

	for _, arg := range os.Args[1:] {
		count, err = pruneEmptyDirectories(arg, scratchBuffer)
		total += count
		if err != nil {
			break
		}
	}

	fmt.Fprintf(os.Stderr, "Removed %d empty directories\n", total)
	if err != nil {
		fmt.Fprintf(os.Stderr, "ERROR: %s\n", err)
		os.Exit(1)
	}
}

func pruneEmptyDirectories(osDirname string, scratchBuffer []byte) (int, error) {
	var count int

	err := godirwalk.Walk(osDirname, &godirwalk.Options{
		Unsorted:      true,
		ScratchBuffer: scratchBuffer,
		Callback: func(_ string, _ *godirwalk.Dirent) error {
			// no-op while diving in; all the fun happens in PostChildrenCallback
			return nil
		},
		PostChildrenCallback: func(osPathname string, _ *godirwalk.Dirent) error {
			deChildren, err := godirwalk.ReadDirents(osPathname, scratchBuffer)
			if err != nil {
				return err
			}
			// NOTE: ReadDirents skips "." and ".."
			if len(deChildren) > 0 {
				return nil // this directory has children; no additional work here
			}
			if osPathname == osDirname {
				return nil // do not remove provided root directory
			}
			err = os.Remove(osPathname)
			if err == nil {
				count++
			}
			return err
		},
	})

	return count, err
}
<|endoftext|>


<commit_before>package main

import (
	"image"
	"os"

	"log"
	"net/url"

	"net/http"

	"github.com/marc-gr/asciize"

	"flag"
	"fmt"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
)

// BuildVersion is the version of the binary. It is automatically filled by the build step.
var BuildVersion = "dev"

func main() {
	src, opts := parseFlags()
	a := asciize.NewAsciizer(opts...)

	var m image.Image
	var err error

	// We check if it is a URL
	switch _, err = url.ParseRequestURI(src); err {
	case nil:
		m, err = imageFromURL(src)
	default:
		m, err = imageFromFile(src)
	}

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

	ascii, err := a.Asciize(m)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(ascii)
}

func imageFromURL(src string) (image.Image, error) {
	r, err := http.Get(src)
	if err != nil {
		return nil, err
	}
	defer r.Body.Close()

	m, _, err := image.Decode(r.Body)
	return m, err
}

func imageFromFile(src string) (image.Image, error) {
	f, err := os.Open(src)
	if err != nil {
		log.Fatal(err)
	}

	m, _, err := image.Decode(f)
	return m, err
}

func parseFlags() (src string, opts []asciize.Option) {
	flag.StringVar(&src, "src", "", "Define the source of the image. Can be a local file or a URL")

	f := flag.String("f", "text", "Output format. Can be \"text\" or \"html\"")
	w := flag.Uint("w", 100, "Target width")
	cs := flag.String("cs", string(asciize.DefaultCharset()), "Used to define a custom charset")
	c := flag.Bool("c", false, "If set to true the output will be colored (ANSI or HTML)")
	i := flag.Bool("i", false, "If set to true the charset will be reversed. Can improve results for some images")
	v := flag.Bool("v", false, "Shows the build version")

	flag.Parse()

	if len(os.Args) == 1 {
		flag.PrintDefaults()
		os.Exit(0)
	}

	if *v {
		fmt.Printf("version: %s\n", BuildVersion)
		os.Exit(0)
	}

	if src == "" {
		fmt.Printf("\n\tmissing parameter: src parameter is required\n\n")
		flag.PrintDefaults()
		os.Exit(1)
	}

	switch asciize.OutputFormat(*f) {
	case asciize.FormatHTML:
		opts = append(opts, asciize.Format(asciize.FormatHTML))
	case "", asciize.FormatText:
		opts = append(opts, asciize.Format(asciize.FormatText))
	default:
		fmt.Printf("\n\tinvalid parameter: unknown format %q\n\n", *f)
		flag.PrintDefaults()
		os.Exit(1)
	}

	opts = append(
		opts,
		asciize.Width(*w),
		asciize.Charset([]byte(*cs)),
		asciize.Colored(*c),
		asciize.InvertCharset(*i),
	)

	return
}
<commit_msg>Improve url detection<commit_after>package main

import (
	"image"
	"os"

	"log"
	"net/url"

	"net/http"

	"github.com/marc-gr/asciize"

	"flag"
	"fmt"
	_ "image/gif"
	_ "image/jpeg"
	_ "image/png"
)

// BuildVersion is the version of the binary. It is automatically filled by the build step.
var BuildVersion = "dev"

func main() {
	src, opts := parseFlags()
	a := asciize.NewAsciizer(opts...)

	var m image.Image
	// We check if it is a URL
	u, err := url.ParseRequestURI(src)
	if err == nil && u.Host != "" {
		m, err = imageFromURL(src)
	} else {
		m, err = imageFromFile(src)
	}

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

	ascii, err := a.Asciize(m)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(ascii)
}

func imageFromURL(src string) (image.Image, error) {
	r, err := http.Get(src)
	if err != nil {
		return nil, err
	}
	defer r.Body.Close()

	m, _, err := image.Decode(r.Body)
	return m, err
}

func imageFromFile(src string) (image.Image, error) {
	f, err := os.Open(src)
	if err != nil {
		log.Fatal(err)
	}

	m, _, err := image.Decode(f)
	return m, err
}

func parseFlags() (src string, opts []asciize.Option) {
	flag.StringVar(&src, "src", "", "Define the source of the image. Can be a local file or a URL")

	f := flag.String("f", "text", "Output format. Can be \"text\" or \"html\"")
	w := flag.Uint("w", 100, "Target width")
	cs := flag.String("cs", string(asciize.DefaultCharset()), "Used to define a custom charset")
	c := flag.Bool("c", false, "If set to true the output will be colored (ANSI or HTML)")
	i := flag.Bool("i", false, "If set to true the charset will be reversed. Can improve results for some images")
	v := flag.Bool("v", false, "Shows the build version")

	flag.Parse()

	if len(os.Args) == 1 {
		flag.PrintDefaults()
		os.Exit(0)
	}

	if *v {
		fmt.Printf("version: %s\n", BuildVersion)
		os.Exit(0)
	}

	if src == "" {
		fmt.Printf("\n\tmissing parameter: src parameter is required\n\n")
		flag.PrintDefaults()
		os.Exit(1)
	}

	switch asciize.OutputFormat(*f) {
	case asciize.FormatHTML:
		opts = append(opts, asciize.Format(asciize.FormatHTML))
	case "", asciize.FormatText:
		opts = append(opts, asciize.Format(asciize.FormatText))
	default:
		fmt.Printf("\n\tinvalid parameter: unknown format %q\n\n", *f)
		flag.PrintDefaults()
		os.Exit(1)
	}

	opts = append(
		opts,
		asciize.Width(*w),
		asciize.Charset([]byte(*cs)),
		asciize.Colored(*c),
		asciize.InvertCharset(*i),
	)

	return
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"net/http"
	"os"
	"strings"
	"time"

	log "github.com/sirupsen/logrus"

	source "github.com/coreos-inc/alm/catalog"
	"github.com/coreos-inc/alm/client"
	"github.com/coreos-inc/alm/operators/catalog"
)

func main() {
	// Parse the command-line flags.
	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	kubeConfigPath := flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
	wakeupInterval := flag.Duration("interval", 15*time.Minute, "wake up interval")
	watchedNamespaces := flag.String("watchedNamespaces", "", "comma separated list of namespaces that catalog watches")
	namespace := flag.String("namespace", "", "namespace where catalog will run and install catalog resources")
	catalogDirectory := flag.String("directory", "/var/catalog_resources", "path to directory with resources to load into the in-memory catalog")
	flag.Parse()

	inMemoryCatalog, err := source.NewInMemoryFromDirectory(*catalogDirectory)
	if err != nil {
		log.Fatalf("Error loading in memory catalog from %s: %s", *catalogDirectory, err.Error())
	}

	alphaCatalogClient, err := client.NewAlphaCatalogEntryClient(*kubeConfigPath)
	if err != nil {
		log.Fatalf("Couldn't create alpha catalog entry client: %s", err.Error())
	}
	catalogStore := source.CustomResourceCatalogStore{
		Client:    alphaCatalogClient,
		Namespace: *namespace,
	}
	entries, err := catalogStore.Sync(inMemoryCatalog)
	if err != nil {
		log.Fatalf("couldn't sync entries from catalog to AlphaCatalogEntries in cluster: %s", err.Error())
	}
	for _, entry := range entries {
		if entry != nil {
			log.Infof("created AlphaCatalogEntry: %s", entry.Name)
		}
	}

	// Serve a health check.
	http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	})
	go http.ListenAndServe(":8080", nil)

	// Create a new instance of the operator.
	catalogOperator, err := catalog.NewOperator(*kubeConfigPath, *wakeupInterval, []source.Source{inMemoryCatalog}, strings.Split(*watchedNamespaces, ",")...)
	if err != nil {
		log.Panicf("error configuring operator: %s", err.Error())
	}

	// TODO: Handle any signals to shutdown cleanly.
	stop := make(chan struct{})
	catalogOperator.Run(stop)
	close(stop)

	panic("unreachable")
}
<commit_msg>refactor(cmd/catalog/main): clean up file<commit_after>package main

import (
	"flag"
	"net/http"
	"os"
	"strings"
	"time"

	log "github.com/sirupsen/logrus"

	source "github.com/coreos-inc/alm/catalog"
	"github.com/coreos-inc/alm/client"
	"github.com/coreos-inc/alm/operators/catalog"
)

const (
	defaultWakeupInterval   = 15 * time.Minute
	defaultCatalogNamespace = "tectonic-system"
	defaultCatalogDirectory = "/var/catalog_resources"
)

var (
	kubeConfigPath    string
	wakeupInterval    time.Duration
	watchedNamespaces string
	catalogNamespace  string
	catalogDirectory  string
)

func init() {
	flag.StringVar(&kubeConfigPath,
		"kubeconfig", "", "absolute path to the kubeconfig file")

	flag.DurationVar(&wakeupInterval,
		"interval", defaultWakeupInterval, "wakeup interval")

	flag.StringVar(&watchedNamespaces,
		"watchedNamespaces", "", "comma separated list of namespaces that catalog watches, leave empty to watch all namespaces")

	flag.StringVar(&catalogNamespace,
		"namespace", defaultCatalogNamespace, "namespace where catalog will run and install catalog resources")

	flag.StringVar(&catalogDirectory,
		"directory", defaultCatalogDirectory, "path to directory with resources to load into the in-memory catalog")
}

func main() {
	// Parse the command-line flags.
	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	flag.Parse()

	inMemoryCatalog, err := source.NewInMemoryFromDirectory(catalogDirectory)
	if err != nil {
		log.Fatalf("Error loading in memory catalog from %s: %s", catalogDirectory, err.Error())
	}

	alphaCatalogClient, err := client.NewAlphaCatalogEntryClient(kubeConfigPath)
	if err != nil {
		log.Fatalf("Couldn't create alpha catalog entry client: %s", err.Error())
	}
	catalogStore := source.CustomResourceCatalogStore{
		Client:    alphaCatalogClient,
		Namespace: *namespace,
	}
	entries, err := catalogStore.Sync(inMemoryCatalog)
	if err != nil {
		log.Fatalf("couldn't sync entries from catalog to AlphaCatalogEntries in cluster: %s", err.Error())
	}
	log.Infof("created %d AlphaCatalogEntry resources", len(entries))

	// Serve a health check.
	http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	})
	go http.ListenAndServe(":8080", nil)

	// Create a new instance of the operator.
	catalogOperator, err := catalog.NewOperator(kubeConfigPath, wakeupInterval,
		[]source.Source{inMemoryCatalog}, strings.Split(watchedNamespaces, ",")...)
	if err != nil {
		log.Panicf("error configuring operator: %s", err.Error())
	}

	// TODO: Handle any signals to shutdown cleanly.
	stop := make(chan struct{})
	catalogOperator.Run(stop)
	close(stop)

	panic("unreachable")
}
<|endoftext|>


<commit_before>////////////////////////////////////////////////////////////////////////////
// Porgram: easygen
// Purpose: Easy to use universal code/text generator
// Authors: Tong Sun (c) 2015-2021, All rights reserved
////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////
// Program start

/*

Command easygen is an easy to use universal code/text generator.

It can be used as a text or html generator for arbitrary purposes with arbitrary data and templates.

It can be used as a code generator, or anything that is structurally repetitive. Some command line parameter handling code generator are provided as examples, including the Go's built-in flag package, and the viper & cobra package.

You can even use easygen as a generic Go template testing tool using the -ts commandline option.

*/
package main

import (
	"flag"
	"os"
	"strings"

	"github.com/go-easygen/easygen"
	"github.com/go-easygen/easygen/egCal"
	"github.com/go-easygen/easygen/egFilePath"
	"github.com/go-easygen/easygen/egVar"
)

//go:generate sh -v easygen.gen.sh

////////////////////////////////////////////////////////////////////////////
// Constant and data type/structure definitions

////////////////////////////////////////////////////////////////////////////
// Global variables definitions

var (
	progname = "easygen"
	version  = "5.1.7"
	date     = "2021-09-25"
)

////////////////////////////////////////////////////////////////////////////
// Main

func main() {
	flag.Usage = Usage
	flag.Parse()

	// One mandatory non-flag arguments
	if flag.NArg() < 1 {
		Usage()
	}

	args := flag.Args()
	// There is only one command line argument
	if len(args) == 1 {
		// when template_name is comma-separated list, data_filename must be given
		if strings.Contains(args[0], ",") {
			Usage()
		}
		// else, dup template_name as data_filename
		args = append(args, args[0])
	}

	tmpl0 := easygen.NewTemplate().Customize()
	tmpl := tmpl0.Funcs(easygen.FuncDefs()).
		Funcs(egVar.FuncDefs()).Funcs(egCal.FuncDefs()).Funcs(egFilePath.FuncDefs())

	var err error
	if len(easygen.Opts.TemplateStr) > 0 {
		err = easygen.Process0(tmpl, os.Stdout, easygen.Opts.TemplateStr, args...)
	} else {
		err = easygen.Process2(tmpl, os.Stdout, args[0], args[1:]...)
	}
	if err != nil {
		panic(err)
	}
}
<commit_msg>- [#] version 5.1.8<commit_after>////////////////////////////////////////////////////////////////////////////
// Porgram: easygen
// Purpose: Easy to use universal code/text generator
// Authors: Tong Sun (c) 2015-2021, All rights reserved
////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////
// Program start

/*

Command easygen is an easy to use universal code/text generator.

It can be used as a text or html generator for arbitrary purposes with arbitrary data and templates.

It can be used as a code generator, or anything that is structurally repetitive. Some command line parameter handling code generator are provided as examples, including the Go's built-in flag package, and the viper & cobra package.

You can even use easygen as a generic Go template testing tool using the -ts commandline option.

*/
package main

import (
	"flag"
	"os"
	"strings"

	"github.com/go-easygen/easygen"
	"github.com/go-easygen/easygen/egCal"
	"github.com/go-easygen/easygen/egFilePath"
	"github.com/go-easygen/easygen/egVar"
)

//go:generate sh -v easygen.gen.sh

////////////////////////////////////////////////////////////////////////////
// Constant and data type/structure definitions

////////////////////////////////////////////////////////////////////////////
// Global variables definitions

var (
	progname = "easygen"
	version  = "5.1.8"
	date     = "2021-12-25"
)

////////////////////////////////////////////////////////////////////////////
// Main

func main() {
	flag.Usage = Usage
	flag.Parse()

	// One mandatory non-flag arguments
	if flag.NArg() < 1 {
		Usage()
	}

	args := flag.Args()
	// There is only one command line argument
	if len(args) == 1 {
		// when template_name is comma-separated list, data_filename must be given
		if strings.Contains(args[0], ",") {
			Usage()
		}
		// else, dup template_name as data_filename
		args = append(args, args[0])
	}

	tmpl0 := easygen.NewTemplate().Customize()
	tmpl := tmpl0.Funcs(easygen.FuncDefs()).
		Funcs(egVar.FuncDefs()).Funcs(egCal.FuncDefs()).Funcs(egFilePath.FuncDefs())

	var err error
	if len(easygen.Opts.TemplateStr) > 0 {
		err = easygen.Process0(tmpl, os.Stdout, easygen.Opts.TemplateStr, args...)
	} else {
		err = easygen.Process2(tmpl, os.Stdout, args[0], args[1:]...)
	}
	if err != nil {
		panic(err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"runtime/debug"
	"runtime/pprof"
	"runtime/trace"
	"strings"
	"time"

	"github.com/evanw/esbuild/internal/logger"
	"github.com/evanw/esbuild/pkg/cli"
)

var helpText = func(colors logger.Colors) string {
	return `
` + colors.Bold + `Usage:` + colors.Default + `
  esbuild [options] [entry points]

` + colors.Bold + `Documentation:` + colors.Default + `
  ` + colors.Underline + `https://esbuild.github.io/` + colors.Default + `

` + colors.Bold + `Repository:` + colors.Default + `
  ` + colors.Underline + `https://github.com/evanw/esbuild` + colors.Default + `

` + colors.Bold + `Simple options:` + colors.Default + `
  --bundle              Bundle all dependencies into the output files
  --define:K=V          Substitute K with V while parsing
  --external:M          Exclude module M from the bundle (can use * wildcards)
  --format=...          Output format (iife | cjs | esm, no default when not
                        bundling, otherwise default is iife when platform
                        is browser and cjs when platform is node)
  --loader:X=L          Use loader L to load file extension X, where L is
                        one of: js | jsx | ts | tsx | json | text | base64 |
                        file | dataurl | binary
  --minify              Minify the output (sets all --minify-* flags)
  --outdir=...          The output directory (for multiple entry points)
  --outfile=...         The output file (for one entry point)
  --platform=...        Platform target (browser | node | neutral,
                        default browser)
  --serve=...           Start a local HTTP server on this host:port for outputs
  --sourcemap           Emit a source map
  --splitting           Enable code splitting (currently only for esm)
  --summary             Print some helpful information at the end of a build
  --target=...          Environment target (e.g. es2017, chrome58, firefox57,
                        safari11, edge16, node10, default esnext)
  --watch               Watch mode: rebuild on file system changes

` + colors.Bold + `Advanced options:` + colors.Default + `
  --banner=...              Text to be prepended to each output file
  --charset=utf8            Do not escape UTF-8 code points
  --color=...               Force use of color terminal escapes (true | false)
  --error-limit=...         Maximum error count or 0 to disable (default 10)
  --footer=...              Text to be appended to each output file
  --global-name=...         The name of the global for the IIFE format
  --inject:F                Import the file F into all input files and
                            automatically replace matching globals with imports
  --jsx-factory=...         What to use for JSX instead of React.createElement
  --jsx-fragment=...        What to use for JSX instead of React.Fragment
  --keep-names              Preserve "name" on functions and classes
  --log-level=...           Disable logging (info | warning | error | silent,
                            default info)
  --main-fields=...         Override the main file order in package.json
                            (default "browser,module,main" when platform is
                            browser and "main,module" when platform is node)
  --metafile=...            Write metadata about the build to a JSON file
  --minify-whitespace       Remove whitespace in output files
  --minify-identifiers      Shorten identifiers in output files
  --minify-syntax           Use equivalent but shorter syntax in output files
  --out-extension:.js=.mjs  Use a custom output extension instead of ".js"
  --outbase=...             The base path used to determine entry point output
                            paths (for multiple entry points)
  --preserve-symlinks       Disable symlink resolution for module lookup
  --public-path=...         Set the base URL for the "file" loader
  --pure:N                  Mark the name N as a pure function for tree shaking
  --resolve-extensions=...  A comma-separated list of implicit extensions
                            (default ".tsx,.ts,.jsx,.mjs,.cjs,.js,.css,.json")
  --servedir=...            What to serve in addition to generated output files
  --sourcefile=...          Set the source file for the source map (for stdin)
  --sourcemap=external      Do not link to the source map with a comment
  --sourcemap=inline        Emit the source map with an inline data URL
  --sources-content=false   Omit "sourcesContent" in generated source maps
  --tree-shaking=...        Set to "ignore-annotations" to work with packages
                            that have incorrect tree-shaking annotations
  --tsconfig=...            Use this tsconfig.json file instead of other ones
  --version                 Print the current version (` + esbuildVersion + `) and exit

` + colors.Bold + `Examples:` + colors.Default + `
  ` + colors.Dim + `# Produces dist/entry_point.js and dist/entry_point.js.map` + colors.Default + `
  esbuild --bundle entry_point.js --outdir=dist --minify --sourcemap

  ` + colors.Dim + `# Allow JSX syntax in .js files` + colors.Default + `
  esbuild --bundle entry_point.js --outfile=out.js --loader:.js=jsx

  ` + colors.Dim + `# Substitute the identifier RELEASE for the literal true` + colors.Default + `
  esbuild example.js --outfile=out.js --define:RELEASE=true

  ` + colors.Dim + `# Provide input via stdin, get output via stdout` + colors.Default + `
  esbuild --minify --loader=ts < input.ts > output.js

  ` + colors.Dim + `# Automatically rebuild when input files are changed` + colors.Default + `
  esbuild app.ts --bundle --watch

  ` + colors.Dim + `# Start a local HTTP server for everything in "public"` + colors.Default + `
  esbuild app.ts --bundle --servedir=public --outdir=public/js

`
}

func main() {
	osArgs := os.Args[1:]
	heapFile := ""
	traceFile := ""
	cpuprofileFile := ""
	isRunningService := false
	sendPings := false

	// Do an initial scan over the argument list
	argsEnd := 0
	for _, arg := range osArgs {
		switch {
		// Show help if a common help flag is provided
		case arg == "-h", arg == "-help", arg == "--help", arg == "/?":
			logger.PrintText(os.Stdout, logger.LevelSilent, os.Args, helpText)
			os.Exit(0)

		// Special-case the version flag here
		case arg == "--version":
			fmt.Printf("%s\n", esbuildVersion)
			os.Exit(0)

		case strings.HasPrefix(arg, "--heap="):
			heapFile = arg[len("--heap="):]

		case strings.HasPrefix(arg, "--trace="):
			traceFile = arg[len("--trace="):]

		case strings.HasPrefix(arg, "--cpuprofile="):
			cpuprofileFile = arg[len("--cpuprofile="):]

		// This flag turns the process into a long-running service that uses
		// message passing with the host process over stdin/stdout
		case strings.HasPrefix(arg, "--service="):
			hostVersion := arg[len("--service="):]
			isRunningService = true

			// Validate the host's version number to make sure esbuild was installed
			// correctly. This check was added because some people have reported
			// errors that appear to indicate an incorrect installation.
			if hostVersion != esbuildVersion {
				logger.PrintErrorToStderr(osArgs,
					fmt.Sprintf("Cannot start service: Host version %q does not match binary version %q",
						hostVersion, esbuildVersion))
				os.Exit(1)
			}

		case strings.HasPrefix(arg, "--ping"):
			sendPings = true

		default:
			// Strip any arguments that were handled above
			osArgs[argsEnd] = arg
			argsEnd++
		}
	}
	osArgs = osArgs[:argsEnd]

	// Run in service mode if requested
	if isRunningService {
		runService(sendPings)
		return
	}

	// Print help text when there are no arguments
	if len(osArgs) == 0 && logger.GetTerminalInfo(os.Stdin).IsTTY {
		logger.PrintText(os.Stdout, logger.LevelSilent, osArgs, helpText)
		os.Exit(0)
	}

	// Capture the defer statements below so the "done" message comes last
	exitCode := 1
	func() {
		// To view a CPU trace, use "go tool trace [file]". Note that the trace
		// viewer doesn't work under Windows Subsystem for Linux for some reason.
		if traceFile != "" {
			f, err := os.Create(traceFile)
			if err != nil {
				logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
					"Failed to create trace file: %s", err.Error()))
				return
			}
			defer f.Close()
			trace.Start(f)
			defer trace.Stop()
		}

		// To view a heap trace, use "go tool pprof [file]" and type "top". You can
		// also drop it into https://speedscope.app and use the "left heavy" or
		// "sandwich" view modes.
		if heapFile != "" {
			f, err := os.Create(heapFile)
			if err != nil {
				logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
					"Failed to create heap file: %s", err.Error()))
				return
			}
			defer func() {
				if err := pprof.WriteHeapProfile(f); err != nil {
					logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
						"Failed to write heap profile: %s", err.Error()))
				}
				f.Close()
			}()
		}

		// To view a CPU profile, drop the file into https://speedscope.app.
		// Note: Running the CPU profiler doesn't work under Windows subsystem for
		// Linux. The profiler has to be built for native Windows and run using the
		// command prompt instead.
		if cpuprofileFile != "" {
			f, err := os.Create(cpuprofileFile)
			if err != nil {
				logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
					"Failed to create cpuprofile file: %s", err.Error()))
				return
			}
			defer f.Close()
			pprof.StartCPUProfile(f)
			defer pprof.StopCPUProfile()
		}

		if cpuprofileFile != "" {
			// The CPU profiler in Go only runs at 100 Hz, which is far too slow to
			// return useful information for esbuild, since it's so fast. Let's keep
			// running for 30 seconds straight, which should give us 3,000 samples.
			seconds := 30.0
			start := time.Now()
			for time.Since(start).Seconds() < seconds {
				exitCode = cli.Run(osArgs)
			}
		} else {
			// Don't disable the GC if this is a long-running process
			isServe := false
			for _, arg := range osArgs {
				if arg == "--serve" || arg == "--watch" || strings.HasPrefix(arg, "--serve=") {
					isServe = true
					break
				}
			}

			// Disable the GC since we're just going to allocate a bunch of memory
			// and then exit anyway. This speedup is not insignificant. Make sure to
			// only do this here once we know that we're not going to be a long-lived
			// process though.
			if !isServe {
				debug.SetGCPercent(-1)
			}

			exitCode = cli.Run(osArgs)
		}
	}()

	os.Exit(exitCode)
}
<commit_msg>help text: public => www<commit_after>package main

import (
	"fmt"
	"os"
	"runtime/debug"
	"runtime/pprof"
	"runtime/trace"
	"strings"
	"time"

	"github.com/evanw/esbuild/internal/logger"
	"github.com/evanw/esbuild/pkg/cli"
)

var helpText = func(colors logger.Colors) string {
	return `
` + colors.Bold + `Usage:` + colors.Default + `
  esbuild [options] [entry points]

` + colors.Bold + `Documentation:` + colors.Default + `
  ` + colors.Underline + `https://esbuild.github.io/` + colors.Default + `

` + colors.Bold + `Repository:` + colors.Default + `
  ` + colors.Underline + `https://github.com/evanw/esbuild` + colors.Default + `

` + colors.Bold + `Simple options:` + colors.Default + `
  --bundle              Bundle all dependencies into the output files
  --define:K=V          Substitute K with V while parsing
  --external:M          Exclude module M from the bundle (can use * wildcards)
  --format=...          Output format (iife | cjs | esm, no default when not
                        bundling, otherwise default is iife when platform
                        is browser and cjs when platform is node)
  --loader:X=L          Use loader L to load file extension X, where L is
                        one of: js | jsx | ts | tsx | json | text | base64 |
                        file | dataurl | binary
  --minify              Minify the output (sets all --minify-* flags)
  --outdir=...          The output directory (for multiple entry points)
  --outfile=...         The output file (for one entry point)
  --platform=...        Platform target (browser | node | neutral,
                        default browser)
  --serve=...           Start a local HTTP server on this host:port for outputs
  --sourcemap           Emit a source map
  --splitting           Enable code splitting (currently only for esm)
  --summary             Print some helpful information at the end of a build
  --target=...          Environment target (e.g. es2017, chrome58, firefox57,
                        safari11, edge16, node10, default esnext)
  --watch               Watch mode: rebuild on file system changes

` + colors.Bold + `Advanced options:` + colors.Default + `
  --banner=...              Text to be prepended to each output file
  --charset=utf8            Do not escape UTF-8 code points
  --color=...               Force use of color terminal escapes (true | false)
  --error-limit=...         Maximum error count or 0 to disable (default 10)
  --footer=...              Text to be appended to each output file
  --global-name=...         The name of the global for the IIFE format
  --inject:F                Import the file F into all input files and
                            automatically replace matching globals with imports
  --jsx-factory=...         What to use for JSX instead of React.createElement
  --jsx-fragment=...        What to use for JSX instead of React.Fragment
  --keep-names              Preserve "name" on functions and classes
  --log-level=...           Disable logging (info | warning | error | silent,
                            default info)
  --main-fields=...         Override the main file order in package.json
                            (default "browser,module,main" when platform is
                            browser and "main,module" when platform is node)
  --metafile=...            Write metadata about the build to a JSON file
  --minify-whitespace       Remove whitespace in output files
  --minify-identifiers      Shorten identifiers in output files
  --minify-syntax           Use equivalent but shorter syntax in output files
  --out-extension:.js=.mjs  Use a custom output extension instead of ".js"
  --outbase=...             The base path used to determine entry point output
                            paths (for multiple entry points)
  --preserve-symlinks       Disable symlink resolution for module lookup
  --public-path=...         Set the base URL for the "file" loader
  --pure:N                  Mark the name N as a pure function for tree shaking
  --resolve-extensions=...  A comma-separated list of implicit extensions
                            (default ".tsx,.ts,.jsx,.mjs,.cjs,.js,.css,.json")
  --servedir=...            What to serve in addition to generated output files
  --sourcefile=...          Set the source file for the source map (for stdin)
  --sourcemap=external      Do not link to the source map with a comment
  --sourcemap=inline        Emit the source map with an inline data URL
  --sources-content=false   Omit "sourcesContent" in generated source maps
  --tree-shaking=...        Set to "ignore-annotations" to work with packages
                            that have incorrect tree-shaking annotations
  --tsconfig=...            Use this tsconfig.json file instead of other ones
  --version                 Print the current version (` + esbuildVersion + `) and exit

` + colors.Bold + `Examples:` + colors.Default + `
  ` + colors.Dim + `# Produces dist/entry_point.js and dist/entry_point.js.map` + colors.Default + `
  esbuild --bundle entry_point.js --outdir=dist --minify --sourcemap

  ` + colors.Dim + `# Allow JSX syntax in .js files` + colors.Default + `
  esbuild --bundle entry_point.js --outfile=out.js --loader:.js=jsx

  ` + colors.Dim + `# Substitute the identifier RELEASE for the literal true` + colors.Default + `
  esbuild example.js --outfile=out.js --define:RELEASE=true

  ` + colors.Dim + `# Provide input via stdin, get output via stdout` + colors.Default + `
  esbuild --minify --loader=ts < input.ts > output.js

  ` + colors.Dim + `# Automatically rebuild when input files are changed` + colors.Default + `
  esbuild app.ts --bundle --watch

  ` + colors.Dim + `# Start a local HTTP server for everything in "www"` + colors.Default + `
  esbuild app.ts --bundle --servedir=www --outdir=www/js

`
}

func main() {
	osArgs := os.Args[1:]
	heapFile := ""
	traceFile := ""
	cpuprofileFile := ""
	isRunningService := false
	sendPings := false

	// Do an initial scan over the argument list
	argsEnd := 0
	for _, arg := range osArgs {
		switch {
		// Show help if a common help flag is provided
		case arg == "-h", arg == "-help", arg == "--help", arg == "/?":
			logger.PrintText(os.Stdout, logger.LevelSilent, os.Args, helpText)
			os.Exit(0)

		// Special-case the version flag here
		case arg == "--version":
			fmt.Printf("%s\n", esbuildVersion)
			os.Exit(0)

		case strings.HasPrefix(arg, "--heap="):
			heapFile = arg[len("--heap="):]

		case strings.HasPrefix(arg, "--trace="):
			traceFile = arg[len("--trace="):]

		case strings.HasPrefix(arg, "--cpuprofile="):
			cpuprofileFile = arg[len("--cpuprofile="):]

		// This flag turns the process into a long-running service that uses
		// message passing with the host process over stdin/stdout
		case strings.HasPrefix(arg, "--service="):
			hostVersion := arg[len("--service="):]
			isRunningService = true

			// Validate the host's version number to make sure esbuild was installed
			// correctly. This check was added because some people have reported
			// errors that appear to indicate an incorrect installation.
			if hostVersion != esbuildVersion {
				logger.PrintErrorToStderr(osArgs,
					fmt.Sprintf("Cannot start service: Host version %q does not match binary version %q",
						hostVersion, esbuildVersion))
				os.Exit(1)
			}

		case strings.HasPrefix(arg, "--ping"):
			sendPings = true

		default:
			// Strip any arguments that were handled above
			osArgs[argsEnd] = arg
			argsEnd++
		}
	}
	osArgs = osArgs[:argsEnd]

	// Run in service mode if requested
	if isRunningService {
		runService(sendPings)
		return
	}

	// Print help text when there are no arguments
	if len(osArgs) == 0 && logger.GetTerminalInfo(os.Stdin).IsTTY {
		logger.PrintText(os.Stdout, logger.LevelSilent, osArgs, helpText)
		os.Exit(0)
	}

	// Capture the defer statements below so the "done" message comes last
	exitCode := 1
	func() {
		// To view a CPU trace, use "go tool trace [file]". Note that the trace
		// viewer doesn't work under Windows Subsystem for Linux for some reason.
		if traceFile != "" {
			f, err := os.Create(traceFile)
			if err != nil {
				logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
					"Failed to create trace file: %s", err.Error()))
				return
			}
			defer f.Close()
			trace.Start(f)
			defer trace.Stop()
		}

		// To view a heap trace, use "go tool pprof [file]" and type "top". You can
		// also drop it into https://speedscope.app and use the "left heavy" or
		// "sandwich" view modes.
		if heapFile != "" {
			f, err := os.Create(heapFile)
			if err != nil {
				logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
					"Failed to create heap file: %s", err.Error()))
				return
			}
			defer func() {
				if err := pprof.WriteHeapProfile(f); err != nil {
					logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
						"Failed to write heap profile: %s", err.Error()))
				}
				f.Close()
			}()
		}

		// To view a CPU profile, drop the file into https://speedscope.app.
		// Note: Running the CPU profiler doesn't work under Windows subsystem for
		// Linux. The profiler has to be built for native Windows and run using the
		// command prompt instead.
		if cpuprofileFile != "" {
			f, err := os.Create(cpuprofileFile)
			if err != nil {
				logger.PrintErrorToStderr(osArgs, fmt.Sprintf(
					"Failed to create cpuprofile file: %s", err.Error()))
				return
			}
			defer f.Close()
			pprof.StartCPUProfile(f)
			defer pprof.StopCPUProfile()
		}

		if cpuprofileFile != "" {
			// The CPU profiler in Go only runs at 100 Hz, which is far too slow to
			// return useful information for esbuild, since it's so fast. Let's keep
			// running for 30 seconds straight, which should give us 3,000 samples.
			seconds := 30.0
			start := time.Now()
			for time.Since(start).Seconds() < seconds {
				exitCode = cli.Run(osArgs)
			}
		} else {
			// Don't disable the GC if this is a long-running process
			isServe := false
			for _, arg := range osArgs {
				if arg == "--serve" || arg == "--watch" || strings.HasPrefix(arg, "--serve=") {
					isServe = true
					break
				}
			}

			// Disable the GC since we're just going to allocate a bunch of memory
			// and then exit anyway. This speedup is not insignificant. Make sure to
			// only do this here once we know that we're not going to be a long-lived
			// process though.
			if !isServe {
				debug.SetGCPercent(-1)
			}

			exitCode = cli.Run(osArgs)
		}
	}()

	os.Exit(exitCode)
}
<|endoftext|>


<commit_before>package main

import (
	"time"

	"github.com/hnakamur/zap-ltsv"
	"github.com/uber-go/zap"
)

type Auth struct {
	ExpiresAt time.Time `json:"expires_at"`
	// Since we'll need to send the token to the browser, we include it in the
	// struct's JSON representation.
	Token string `json:"token"`
}

func (a Auth) MarshalLog(kv zap.KeyValue) error {
	kv.AddInt64("expires_at", a.ExpiresAt.UnixNano())
	// We don't want to log sensitive data.
	kv.AddString("token", "---")
	return nil
}

type User struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
	Auth Auth   `auth:"auth"`
}

func (u User) MarshalLog(kv zap.KeyValue) error {
	kv.AddString("name", u.Name)
	kv.AddInt("age", u.Age)
	return kv.AddMarshaler("auth", u.Auth)
}

func main() {
	logger := zap.New(
		ltsv.NewLTSVEncoder(),
	)
	logger.Warn("Log without structured data...")
	logger.Warn("Log without structured data\nwith newline...")
	logger.Warn(
		"Or use strongly-typed wrappers to add structured context.",
		zap.String("library", "zap"),
		zap.Duration("latency", time.Nanosecond),
	)

	child := logger.With(
		zap.String("user", "jane@test.com"),
		zap.Int("visits", 42),
	)
	child.Error("Oh no!")
	child.Info("Yes!")

	jane := User{
		Name: "Jane Doe",
		Age:  42,
		Auth: Auth{
			ExpiresAt: time.Unix(0, 100),
			Token:     "super secret",
		},
	}

	logger.Info("Successful login.", zap.Marshaler("user", jane))
}
<commit_msg>Add example main<commit_after><|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"regexp"
	"strconv"
	"strings"

	log "github.com/Sirupsen/logrus"
)

var logLevel int
var root string

func init() {
	const (
		logLevelUsage = "Set log level (0,1,2,3,4,5, higher is more logging)."
	)

	flag.IntVar(&logLevel, "log-level", int(log.ErrorLevel), logLevelUsage)
}

type ShowInList struct {
	Name    string `json:"name"`
	Summary string `json:"summary"`
	Image   struct {
		Medium   string `json:"medium"`
		Original string `json:"original"`
	} `json:"image"`

	URL string `json:"url"`
}

type SingleShow struct {
	Name    string `json:"name"`
	Summary string `json:"summary"`
	Image   struct {
		Medium   string `json:"medium"`
		Original string `json:"original"`
	} `json:"image"`

	SeasonURLs []string `json:"season_urls"`
}

type Season struct {
	Name    string `json:"name"`
	Summary string `json:"summary"`
	Image   struct {
		Medium   string `json:"medium"`
		Original string `json:"original"`
	} `json:"image"`

	Number   int               `json:"number"`
	Episodes []internalEpisode `json:"episodes"`
}

type commonEpisode struct {
	Number  int    `json:"number"`
	Name    string `json:"name"`
	Summary string `json:"summary"`
	Image   struct {
		Medium   string `json:"medium"`
		Original string `json:"original"`
	} `json:"image"`
}

type internalEpisode struct {
	commonEpisode

	URL string `json:"url"`
}

type SingleEpisode struct {
	commonEpisode

	VideoURL     string `json:"video_url"`
	ShowName     string `json:"show_name"`
	SeasonNumber int    `json:"season_number"`
}

func findMatchingShow(file os.FileInfo) *TvMazeShow {
	contextLogger := log.WithField("file", file.Name())
	tvMaze := TvMazeClient{
		URLTemplate: tvMazeURLTemplate,
		logger:      contextLogger,
	}

	tvMazeShow, err := tvMaze.Find(file.Name())
	if err != nil || tvMazeShow == nil {
		contextLogger.Debug("No match")
		return nil
	}
	contextLogger.WithField("show", tvMazeShow.Name).Debug("Found match")

	return tvMazeShow
}

func writeEpisodeJSON(episode SingleEpisode) error {
	episodeDir := path.Join(
		episode.ShowName,
		strconv.Itoa(episode.SeasonNumber),
		urlify(episode.Name),
	)
	err := os.Mkdir(episodeDir, 0744)
	if err != nil {
		return err
	}

	file, err := os.Create(path.Join(
		episodeDir,
		"episode.json"),
	)
	if err != nil {
		return err
	}
	defer file.Close()

	if err := json.NewEncoder(file).Encode(episode); err != nil {
		return err
	}

	return nil
}

func episodes(seasonNumber int, show *TvMazeShow) []SingleEpisode {
	episodes := []SingleEpisode{}

	for _, episode := range show.Embedded.Episodes {
		if int(episode.Season) != seasonNumber {
			continue
		}

		// Check if episode exists on disk
		if !episodeExists(path.Join(show.Name, strconv.Itoa(seasonNumber)), episode) {
			continue
		}

		episodes = append(episodes, SingleEpisode{
			commonEpisode: commonEpisode{
				Number:  int(episode.Episode),
				Name:    episode.Name,
				Summary: episode.Summary,
				Image:   episode.Image,
			},

			VideoURL: path.Join(show.Name, strconv.Itoa(seasonNumber), episodeVideoFile(
				path.Join(show.Name, strconv.Itoa(seasonNumber)), episode)),
			ShowName:     show.Name,
			SeasonNumber: seasonNumber,
		})
	}

	return episodes
}

func writeEpisodeJSONs(show *TvMazeShow) {
	for _, seasonNumber := range seasons(show) {
		if _, err := os.Stat(path.Join(show.Name, strconv.Itoa(seasonNumber))); err != nil {
			continue
		}

		for _, episode := range episodes(seasonNumber, show) {
			if err := writeEpisodeJSON(episode); err != nil {
				log.WithFields(log.Fields{
					"err":     err,
					"season":  seasonNumber,
					"episode": episode.Number,
				}).Error("Error writing episode")
			}
		}
	}
}

func episodeVideoFile(seasonDir string, episode TvMazeEpisode) string {
	files, err := ioutil.ReadDir(seasonDir)
	if err != nil {
		log.WithFields(log.Fields{
			"season": episode.Season,
			"err":    err,
		}).Error("Error reading season directory")
	}

	for _, file := range files {
		if file.IsDir() {
			log.WithField("file", file.Name()).Debug("skipping")
			continue
		}

		if strings.Contains(file.Name(), fmt.Sprintf("S%02dE%02d", episode.Season, episode.Episode)) {
			return file.Name()
		}
	}

	return ""
}

func episodeExists(seasonDir string, episode TvMazeEpisode) bool {
	if episodeVideoFile(seasonDir, episode) == "" {
		return false
	}
	return true
}

func season(number int, show *TvMazeShow) Season {
	season := Season{
		Name:    show.Name,
		Summary: show.Summary,
		Image:   show.Image,
		Number:  number,
	}

	episodes := []internalEpisode{}

	for _, episode := range show.Embedded.Episodes {
		if int(episode.Season) != number {
			continue
		}

		// Check if episode exists on disk
		if !episodeExists(path.Join(show.Name, strconv.Itoa(number)), episode) {
			continue
		}

		episodes = append(episodes, internalEpisode{
			commonEpisode: commonEpisode{
				Number:  int(episode.Episode),
				Name:    episode.Name,
				Summary: episode.Summary,
				Image:   episode.Image,
			},
			URL: "/" + path.Join(show.Name, strconv.Itoa(number), urlify(episode.Name)),
		})
	}

	season.Episodes = episodes

	return season
}

func writeSeasonJSON(seasonNumber int, show *TvMazeShow) error {
	file, err := os.Create(path.Join(show.Name, strconv.Itoa(seasonNumber), "season.json"))
	if err != nil {
		return err
	}
	defer file.Close()

	if err := json.NewEncoder(file).Encode(season(seasonNumber, show)); err != nil {
		return err
	}

	return nil
}

func writeSeasonJSONs(show *TvMazeShow) {
	for _, seasonNumber := range seasons(show) {
		if _, err := os.Stat(path.Join(show.Name, strconv.Itoa(seasonNumber))); err != nil {
			continue
		}

		if err := writeSeasonJSON(seasonNumber, show); err != nil {
			log.WithFields(log.Fields{
				"err":    err,
				"season": seasonNumber,
			}).Error("Error writing season")
		}
	}
}

func urlify(name string) string {
	re := regexp.MustCompile("[^a-zA-Z0-9]")
	return re.ReplaceAllString(name, "-")
}

func unique(list []int) []int {
	unique := []int{}
	for _, item := range list {
		found := false
		for _, uniqueItem := range unique {
			if item == uniqueItem {
				found = true
			}
		}
		if !found {
			unique = append(unique, item)
		}
	}

	return unique
}

func seasons(show *TvMazeShow) []int {
	seasons := []int{}
	for _, episode := range show.Embedded.Episodes {
		seasons = append(seasons, int(episode.Season))
	}

	return unique(seasons)
}

func writeShowJSON(show *TvMazeShow) error {
	singleShow := SingleShow{
		Name:       show.Name,
		Summary:    show.Summary,
		Image:      show.Image,
		SeasonURLs: []string{},
	}

	for _, season := range seasons(show) {
		if _, err := os.Stat(path.Join(show.Name, strconv.Itoa(season))); err == nil {
			singleShow.SeasonURLs = append(singleShow.SeasonURLs, "/"+show.Name+"/"+strconv.Itoa(season))
		}
	}

	file, err := os.Create(path.Join(show.Name, "show.json"))
	if err != nil {
		return err
	}
	defer file.Close()

	if err := json.NewEncoder(file).Encode(singleShow); err != nil {
		return err
	}

	return nil
}

func convertToShowInList(show *TvMazeShow) ShowInList {
	return ShowInList{
		Name:    show.Name,
		Summary: show.Summary,
		Image:   show.Image,
		URL:     "/" + show.Name,
	}
}

func writeShowsJSON(shows []ShowInList) {
	file, err := os.Create("shows.json")
	if err != nil {
		log.WithField("err", err).Error("Error opening shows.json")
		return
	}

	if err = json.NewEncoder(file).Encode(shows); err != nil {
		log.WithField("err", err).Error("Error writing shows.json")
		return
	}
}

func main() {
	flag.Parse()
	log.SetLevel(log.Level(logLevel))
	log.Info("Started fetcher")

	root = flag.Args()[0]

	if err := os.Chdir(root); err != nil {
		log.WithFields(log.Fields{
			"err":  err,
			"root": root,
		}).Fatal("Error changing working dir")
	}

	files, err := ioutil.ReadDir(".")
	if err != nil {
		log.WithFields(log.Fields{
			"err": err,
		}).Fatal("Error initializing Fetcher")
	}

	shows := []ShowInList{}
	for _, file := range files {
		if !file.IsDir() {
			log.WithField("file", file.Name()).Debug("skipping")
			continue
		}

		tvMazeShow := findMatchingShow(file)
		if tvMazeShow != nil {
			show := convertToShowInList(tvMazeShow)
			shows = append(shows, show)

			writeShowJSON(tvMazeShow)     // 1x show.json
			writeSeasonJSONs(tvMazeShow)  // Nx season.json
			writeEpisodeJSONs(tvMazeShow) // Mx episode.json
		}
	}

	writeShowsJSON(shows)
}
<commit_msg>dont error on existing dir<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"regexp"
	"strconv"
	"strings"

	log "github.com/Sirupsen/logrus"
)

var logLevel int
var root string

func init() {
	const (
		logLevelUsage = "Set log level (0,1,2,3,4,5, higher is more logging)."
	)

	flag.IntVar(&logLevel, "log-level", int(log.ErrorLevel), logLevelUsage)
}

type ShowInList struct {
	Name    string `json:"name"`
	Summary string `json:"summary"`
	Image   struct {
		Medium   string `json:"medium"`
		Original string `json:"original"`
	} `json:"image"`

	URL string `json:"url"`
}

type SingleShow struct {
	Name    string `json:"name"`
	Summary string `json:"summary"`
	Image   struct {
		Medium   string `json:"medium"`
		Original string `json:"original"`
	} `json:"image"`

	SeasonURLs []string `json:"season_urls"`
}

type Season struct {
	Name    string `json:"name"`
	Summary string `json:"summary"`
	Image   struct {
		Medium   string `json:"medium"`
		Original string `json:"original"`
	} `json:"image"`

	Number   int               `json:"number"`
	Episodes []internalEpisode `json:"episodes"`
}

type commonEpisode struct {
	Number  int    `json:"number"`
	Name    string `json:"name"`
	Summary string `json:"summary"`
	Image   struct {
		Medium   string `json:"medium"`
		Original string `json:"original"`
	} `json:"image"`
}

type internalEpisode struct {
	commonEpisode

	URL string `json:"url"`
}

type SingleEpisode struct {
	commonEpisode

	VideoURL     string `json:"video_url"`
	ShowName     string `json:"show_name"`
	SeasonNumber int    `json:"season_number"`
}

func findMatchingShow(file os.FileInfo) *TvMazeShow {
	contextLogger := log.WithField("file", file.Name())
	tvMaze := TvMazeClient{
		URLTemplate: tvMazeURLTemplate,
		logger:      contextLogger,
	}

	tvMazeShow, err := tvMaze.Find(file.Name())
	if err != nil || tvMazeShow == nil {
		contextLogger.Debug("No match")
		return nil
	}
	contextLogger.WithField("show", tvMazeShow.Name).Debug("Found match")

	return tvMazeShow
}

func writeEpisodeJSON(episode SingleEpisode) error {
	episodeDir := path.Join(
		episode.ShowName,
		strconv.Itoa(episode.SeasonNumber),
		urlify(episode.Name),
	)

	if _, err := os.Stat(episodeDir); err != nil {
		err := os.Mkdir(episodeDir, 0744)
		if err != nil {
			return err
		}
	}

	file, err := os.Create(path.Join(
		episodeDir,
		"episode.json"),
	)
	if err != nil {
		return err
	}
	defer file.Close()

	if err := json.NewEncoder(file).Encode(episode); err != nil {
		return err
	}

	return nil
}

func episodes(seasonNumber int, show *TvMazeShow) []SingleEpisode {
	episodes := []SingleEpisode{}

	for _, episode := range show.Embedded.Episodes {
		if int(episode.Season) != seasonNumber {
			continue
		}

		// Check if episode exists on disk
		if !episodeExists(path.Join(show.Name, strconv.Itoa(seasonNumber)), episode) {
			continue
		}

		episodes = append(episodes, SingleEpisode{
			commonEpisode: commonEpisode{
				Number:  int(episode.Episode),
				Name:    episode.Name,
				Summary: episode.Summary,
				Image:   episode.Image,
			},

			VideoURL: path.Join(show.Name, strconv.Itoa(seasonNumber), episodeVideoFile(
				path.Join(show.Name, strconv.Itoa(seasonNumber)), episode)),
			ShowName:     show.Name,
			SeasonNumber: seasonNumber,
		})
	}

	return episodes
}

func writeEpisodeJSONs(show *TvMazeShow) {
	for _, seasonNumber := range seasons(show) {
		if _, err := os.Stat(path.Join(show.Name, strconv.Itoa(seasonNumber))); err != nil {
			continue
		}

		for _, episode := range episodes(seasonNumber, show) {
			if err := writeEpisodeJSON(episode); err != nil {
				log.WithFields(log.Fields{
					"err":     err,
					"season":  seasonNumber,
					"episode": episode.Number,
				}).Error("Error writing episode")
			}
		}
	}
}

func episodeVideoFile(seasonDir string, episode TvMazeEpisode) string {
	files, err := ioutil.ReadDir(seasonDir)
	if err != nil {
		log.WithFields(log.Fields{
			"season": episode.Season,
			"err":    err,
		}).Error("Error reading season directory")
	}

	for _, file := range files {
		if file.IsDir() {
			log.WithField("file", file.Name()).Debug("skipping")
			continue
		}

		if strings.Contains(file.Name(), fmt.Sprintf("S%02dE%02d", episode.Season, episode.Episode)) {
			return file.Name()
		}
	}

	return ""
}

func episodeExists(seasonDir string, episode TvMazeEpisode) bool {
	if episodeVideoFile(seasonDir, episode) == "" {
		return false
	}
	return true
}

func season(number int, show *TvMazeShow) Season {
	season := Season{
		Name:    show.Name,
		Summary: show.Summary,
		Image:   show.Image,
		Number:  number,
	}

	episodes := []internalEpisode{}

	for _, episode := range show.Embedded.Episodes {
		if int(episode.Season) != number {
			continue
		}

		// Check if episode exists on disk
		if !episodeExists(path.Join(show.Name, strconv.Itoa(number)), episode) {
			continue
		}

		episodes = append(episodes, internalEpisode{
			commonEpisode: commonEpisode{
				Number:  int(episode.Episode),
				Name:    episode.Name,
				Summary: episode.Summary,
				Image:   episode.Image,
			},
			URL: "/" + path.Join(show.Name, strconv.Itoa(number), urlify(episode.Name)),
		})
	}

	season.Episodes = episodes

	return season
}

func writeSeasonJSON(seasonNumber int, show *TvMazeShow) error {
	file, err := os.Create(path.Join(show.Name, strconv.Itoa(seasonNumber), "season.json"))
	if err != nil {
		return err
	}
	defer file.Close()

	if err := json.NewEncoder(file).Encode(season(seasonNumber, show)); err != nil {
		return err
	}

	return nil
}

func writeSeasonJSONs(show *TvMazeShow) {
	for _, seasonNumber := range seasons(show) {
		if _, err := os.Stat(path.Join(show.Name, strconv.Itoa(seasonNumber))); err != nil {
			continue
		}

		if err := writeSeasonJSON(seasonNumber, show); err != nil {
			log.WithFields(log.Fields{
				"err":    err,
				"season": seasonNumber,
			}).Error("Error writing season")
		}
	}
}

func urlify(name string) string {
	re := regexp.MustCompile("[^a-zA-Z0-9]")
	return re.ReplaceAllString(name, "-")
}

func unique(list []int) []int {
	unique := []int{}
	for _, item := range list {
		found := false
		for _, uniqueItem := range unique {
			if item == uniqueItem {
				found = true
			}
		}
		if !found {
			unique = append(unique, item)
		}
	}

	return unique
}

func seasons(show *TvMazeShow) []int {
	seasons := []int{}
	for _, episode := range show.Embedded.Episodes {
		seasons = append(seasons, int(episode.Season))
	}

	return unique(seasons)
}

func writeShowJSON(show *TvMazeShow) error {
	singleShow := SingleShow{
		Name:       show.Name,
		Summary:    show.Summary,
		Image:      show.Image,
		SeasonURLs: []string{},
	}

	for _, season := range seasons(show) {
		if _, err := os.Stat(path.Join(show.Name, strconv.Itoa(season))); err == nil {
			singleShow.SeasonURLs = append(singleShow.SeasonURLs, "/"+show.Name+"/"+strconv.Itoa(season))
		}
	}

	file, err := os.Create(path.Join(show.Name, "show.json"))
	if err != nil {
		return err
	}
	defer file.Close()

	if err := json.NewEncoder(file).Encode(singleShow); err != nil {
		return err
	}

	return nil
}

func convertToShowInList(show *TvMazeShow) ShowInList {
	return ShowInList{
		Name:    show.Name,
		Summary: show.Summary,
		Image:   show.Image,
		URL:     "/" + show.Name,
	}
}

func writeShowsJSON(shows []ShowInList) {
	file, err := os.Create("shows.json")
	if err != nil {
		log.WithField("err", err).Error("Error opening shows.json")
		return
	}

	if err = json.NewEncoder(file).Encode(shows); err != nil {
		log.WithField("err", err).Error("Error writing shows.json")
		return
	}
}

func main() {
	flag.Parse()
	log.SetLevel(log.Level(logLevel))
	log.Info("Started fetcher")

	root = flag.Args()[0]

	if err := os.Chdir(root); err != nil {
		log.WithFields(log.Fields{
			"err":  err,
			"root": root,
		}).Fatal("Error changing working dir")
	}

	files, err := ioutil.ReadDir(".")
	if err != nil {
		log.WithFields(log.Fields{
			"err": err,
		}).Fatal("Error initializing Fetcher")
	}

	shows := []ShowInList{}
	for _, file := range files {
		if !file.IsDir() {
			log.WithField("file", file.Name()).Debug("skipping")
			continue
		}

		tvMazeShow := findMatchingShow(file)
		if tvMazeShow != nil {
			show := convertToShowInList(tvMazeShow)
			shows = append(shows, show)

			writeShowJSON(tvMazeShow)     // 1x show.json
			writeSeasonJSONs(tvMazeShow)  // Nx season.json
			writeEpisodeJSONs(tvMazeShow) // Mx episode.json
		}
	}

	writeShowsJSON(shows)
}
<|endoftext|>