<commit_before>package main

import (
	"fmt"
	"github.com/albrow/prtty"
	"github.com/spf13/cobra"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"text/template"
)

const (
	version = "temple version X.X.X (develop)"
)

var (
	// NOTE: GOPATH might consist of multiple paths. If that is the case, we look in the first path.
	gopath        = strings.Split(os.Getenv("GOPATH"), string(os.PathListSeparator))[0]
	templePath    = filepath.Join(gopath, "src", "github.com", "albrow", "temple")
	generatedTmpl = template.Must(template.ParseFiles(filepath.Join(templePath, "templates.go.tmpl")))
)

func main() {
	cmdBuild := &cobra.Command{
		Use:   "build <src> <dest>",
		Short: "Compile the templates in the src directory and write generated go code to the dest file.",
		Long: `The build command will compile the .tmpl files found in the src directory,

along with the .tmpl files found in the layouts and includes directories (if
provided). It generates go source code containing the compiled templates and
writes it to the dest file.

The build command works best if your templates are organized to approximate
template inheritance, as described in this article:
https://elithrar.github.io/article/approximating-html-template-inheritance/.
However, if you don't want organize your templates this way, the generated go
file will give you direct access to the builtin html templates
(*template.Template objects from the html/template package), so you can combine
parse trees manually. You also have the option of not combining parse trees at
all, and simply having each .tmpl file represent a stand-alone template.

The generated go file is designed to be fairly readable for humans, so feel
free to take a look. (Just don't edit it directly!)

## Includes

If provided, all .tmpl files in the includes directory are referred to as
"includes templates" or simply "includes". Includes are parsed first (before
layouts and regular templates). Includes should contain .tmpl files for things
like the <head> section, which are shared between different layouts, or other
components that are shared between different regular templates. No .tmpl file in
the includes directory can conflict with any other .tmpl file (e.g. they cannot
declare sub-templates of the same name). All the includes will be added to the
parse tree for the layouts and all other templates via the template.AddParseTree
method. It is safe for includes to reference each other, as long as they don't
conflict or create cyclical references.

## Layouts

If provided, all .tmpl files in the layouts directory are referred to as "layout
templates" or simply "layouts". Layouts are parsed after includes and before
regular templates. Typically, layouts will be referenced by a regular template,
and will expect the regular template to define certain sub-templates (e.g.
"content" or "title"), which will then be inserted into the layout. An
application will almost always want to have at least one layout, conventially
called "app.tmpl", which regular templates will use. No .tmpl file in the
layouts directory can conflict with any other .tmpl file (e.g. they cannot
declare sub-templates of the same name). If includes were also provided, all
includes will be added to the parse tree for each layout via the
template.AddParseTree method. Therefore a layout can reference any template in
includes. Layouts can also reference each other, as long as they don't conflict
or create cylclical references. All layouts will be added to the parse tree for
the regular templates in the src directory via the template.AddParseTree method.

## Regular Templates

All the .tmpl files found in the src directory are referred to as "regular
templates", or simply "templates", and are parsed last. All layouts and includes
(if any) are added to the parse tree for each template via the
template.AddParseTree method. Therefore templates can reference both layouts and
includes. Since regular templates will never be parsed together, they can
conflict with eachother (e.g. they can declare sub-templates of the same name).
As a consequence, regular templates also cannot reference eachother.

`,
		Run: func(cmd *cobra.Command, args []string) {
			if len(args) != 2 {
				prtty.Error.Fatal("temple build requires exactly 2 arguments: the src directory and the dest file.")
			}
			includes := cmd.Flag("includes").Value.String()
			layouts := cmd.Flag("layouts").Value.String()
			if err := build(args[0], args[1], includes, layouts); err != nil {
				prtty.Error.Fatal(err)
			}
		},
	}
	cmdBuild.Flags().String("includes", "", "(optional) The directory to look for includes. Includes are .tmpl files that are shared between layouts and all templates.")
	cmdBuild.Flags().String("layouts", "", "(optional) The directory to look for layouts. Layouts are .tmpl shared between all templates and have access to includes.")

	cmdVersion := &cobra.Command{
		Use:   "version",
		Short: "Print the current version number.",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println(version)
		},
	}

	rootCmd := &cobra.Command{
		Use:   "temple",
		Short: "A command line tool for sharing go templates between client and server.",
	}
	rootCmd.AddCommand(cmdBuild, cmdVersion)
	if err := rootCmd.Execute(); err != nil {
		prtty.Error.Fatal(err)
	}
}

type TemplateData struct {
	PackageName string
	Templates   []*TemplateFile
	Includes    []*TemplateFile
	Layouts     []*TemplateFile
}

type TemplateFile struct {
	VarName string
	Name    string
	Source  string
}

func NewTemplateFile(filename string) (*TemplateFile, error) {
	// name is everything after the last slash, not including the file extension
	name := strings.TrimSuffix(filepath.Base(filename), ".tmpl")
	// varName is just the name titlized so it is an exported variable
	varName := strings.Title(name)
	fileContents, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return &TemplateFile{
		VarName: varName,
		Name:    name,
		Source:  string(fileContents),
	}, nil
}

func ParseTemplateFiles(dir string) ([]*TemplateFile, error) {
	templateFiles := []*TemplateFile{}
	files, err := filepath.Glob(filepath.Join(dir, "*.tmpl"))
	if err != nil {
		return nil, err
	}
	if len(files) == 0 {
		prtty.Warn.Printf("    WARNING: No .tmpl files found in %s", dir)
	}
	for _, filename := range files {
		prtty.Default.Printf("    %s", filename)
		tf, err := NewTemplateFile(filename)
		if err != nil {
			return nil, err
		}
		templateFiles = append(templateFiles, tf)
	}
	return templateFiles, nil
}

func build(src, dest, includes, layouts string) error {
	prtty.Info.Println("--> building...")
	prtty.Default.Printf("    src: %s", src)
	prtty.Default.Printf("    dest: %s", dest)
	if includes != "" {
		prtty.Default.Printf("    includes: %s", includes)
	}
	if layouts != "" {
		prtty.Default.Printf("    layouts: %s", layouts)
	}

	packageName := filepath.Base(filepath.Dir(dest))
	templateData := TemplateData{
		PackageName: packageName,
	}

	if includes != "" {
		prtty.Info.Println("--> parsing includes...")
		includes, err := ParseTemplateFiles(includes)
		if err != nil {
			return err
		}
		templateData.Includes = includes
	}
	if layouts != "" {
		prtty.Info.Println("--> parsing layouts...")
		layouts, err := ParseTemplateFiles(layouts)
		if err != nil {
			return err
		}
		templateData.Layouts = layouts
	}
	prtty.Info.Println("--> parsing templates...")
	templates, err := ParseTemplateFiles(src)
	if err != nil {
		return err
	}
	templateData.Templates = templates

	prtty.Info.Println("--> generating go code...")
	if err := os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
		return err
	}
	if !strings.HasSuffix(dest, ".go") {
		dest += ".go"
	}
	destFile, err := os.Create(dest)
	if err != nil {
		return err
	}
	prtty.Success.Printf("    CREATE %s", dest)
	if err := generatedTmpl.Execute(destFile, templateData); err != nil {
		return err
	}

	prtty.Info.Println("--> done")
	return nil
}
<commit_msg>Format generated code with gofmt<commit_after>package main

import (
	"fmt"
	"github.com/albrow/prtty"
	"github.com/spf13/cobra"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"text/template"
)

const (
	version = "temple version X.X.X (develop)"
)

var (
	// NOTE: GOPATH might consist of multiple paths. If that is the case, we look in the first path.
	gopath        = strings.Split(os.Getenv("GOPATH"), string(os.PathListSeparator))[0]
	templePath    = filepath.Join(gopath, "src", "github.com", "albrow", "temple")
	generatedTmpl = template.Must(template.ParseFiles(filepath.Join(templePath, "templates.go.tmpl")))
)

func main() {
	cmdBuild := &cobra.Command{
		Use:   "build <src> <dest>",
		Short: "Compile the templates in the src directory and write generated go code to the dest file.",
		Long: `The build command will compile the .tmpl files found in the src directory,

along with the .tmpl files found in the layouts and includes directories (if
provided). It generates go source code containing the compiled templates and
writes it to the dest file.

The build command works best if your templates are organized to approximate
template inheritance, as described in this article:
https://elithrar.github.io/article/approximating-html-template-inheritance/.
However, if you don't want organize your templates this way, the generated go
file will give you direct access to the builtin html templates
(*template.Template objects from the html/template package), so you can combine
parse trees manually. You also have the option of not combining parse trees at
all, and simply having each .tmpl file represent a stand-alone template.

The generated go file is designed to be fairly readable for humans, so feel
free to take a look. (Just don't edit it directly!)

## Includes

If provided, all .tmpl files in the includes directory are referred to as
"includes templates" or simply "includes". Includes are parsed first (before
layouts and regular templates). Includes should contain .tmpl files for things
like the <head> section, which are shared between different layouts, or other
components that are shared between different regular templates. No .tmpl file in
the includes directory can conflict with any other .tmpl file (e.g. they cannot
declare sub-templates of the same name). All the includes will be added to the
parse tree for the layouts and all other templates via the template.AddParseTree
method. It is safe for includes to reference each other, as long as they don't
conflict or create cyclical references.

## Layouts

If provided, all .tmpl files in the layouts directory are referred to as "layout
templates" or simply "layouts". Layouts are parsed after includes and before
regular templates. Typically, layouts will be referenced by a regular template,
and will expect the regular template to define certain sub-templates (e.g.
"content" or "title"), which will then be inserted into the layout. An
application will almost always want to have at least one layout, conventially
called "app.tmpl", which regular templates will use. No .tmpl file in the
layouts directory can conflict with any other .tmpl file (e.g. they cannot
declare sub-templates of the same name). If includes were also provided, all
includes will be added to the parse tree for each layout via the
template.AddParseTree method. Therefore a layout can reference any template in
includes. Layouts can also reference each other, as long as they don't conflict
or create cylclical references. All layouts will be added to the parse tree for
the regular templates in the src directory via the template.AddParseTree method.

## Regular Templates

All the .tmpl files found in the src directory are referred to as "regular
templates", or simply "templates", and are parsed last. All layouts and includes
(if any) are added to the parse tree for each template via the
template.AddParseTree method. Therefore templates can reference both layouts and
includes. Since regular templates will never be parsed together, they can
conflict with eachother (e.g. they can declare sub-templates of the same name).
As a consequence, regular templates also cannot reference eachother.

`,
		Run: func(cmd *cobra.Command, args []string) {
			if len(args) != 2 {
				prtty.Error.Fatal("temple build requires exactly 2 arguments: the src directory and the dest file.")
			}
			includes := cmd.Flag("includes").Value.String()
			layouts := cmd.Flag("layouts").Value.String()
			if err := build(args[0], args[1], includes, layouts); err != nil {
				prtty.Error.Fatal(err)
			}
		},
	}
	cmdBuild.Flags().String("includes", "", "(optional) The directory to look for includes. Includes are .tmpl files that are shared between layouts and all templates.")
	cmdBuild.Flags().String("layouts", "", "(optional) The directory to look for layouts. Layouts are .tmpl shared between all templates and have access to includes.")

	cmdVersion := &cobra.Command{
		Use:   "version",
		Short: "Print the current version number.",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println(version)
		},
	}

	rootCmd := &cobra.Command{
		Use:   "temple",
		Short: "A command line tool for sharing go templates between client and server.",
	}
	rootCmd.AddCommand(cmdBuild, cmdVersion)
	if err := rootCmd.Execute(); err != nil {
		prtty.Error.Fatal(err)
	}
}

type TemplateData struct {
	PackageName string
	Templates   []*TemplateFile
	Includes    []*TemplateFile
	Layouts     []*TemplateFile
}

type TemplateFile struct {
	VarName string
	Name    string
	Source  string
}

func build(src, dest, includes, layouts string) error {
	prtty.Info.Println("--> building...")
	prtty.Default.Printf("    src: %s", src)
	prtty.Default.Printf("    dest: %s", dest)
	if includes != "" {
		prtty.Default.Printf("    includes: %s", includes)
	}
	if layouts != "" {
		prtty.Default.Printf("    layouts: %s", layouts)
	}
	templateData, err := generateTemplateData(src, dest, includes, layouts)
	if err != nil {
		return err
	}
	if err := writeFile(templateData, dest); err != nil {
		return err
	}
	if err := formatFile(dest); err != nil {
		return err
	}
	prtty.Info.Println("--> done")
	return nil
}

func NewTemplateFile(filename string) (*TemplateFile, error) {
	// name is everything after the last slash, not including the file extension
	name := strings.TrimSuffix(filepath.Base(filename), ".tmpl")
	// varName is just the name titlized so it is an exported variable
	varName := strings.Title(name)
	fileContents, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	return &TemplateFile{
		VarName: varName,
		Name:    name,
		Source:  string(fileContents),
	}, nil
}

func ParseTemplateFiles(dir string) ([]*TemplateFile, error) {
	templateFiles := []*TemplateFile{}
	files, err := filepath.Glob(filepath.Join(dir, "*.tmpl"))
	if err != nil {
		return nil, err
	}
	if len(files) == 0 {
		prtty.Warn.Printf("    WARNING: No .tmpl files found in %s", dir)
	}
	for _, filename := range files {
		prtty.Default.Printf("    %s", filename)
		tf, err := NewTemplateFile(filename)
		if err != nil {
			return nil, err
		}
		templateFiles = append(templateFiles, tf)
	}
	return templateFiles, nil
}

func generateTemplateData(src, dest, includes, layouts string) (TemplateData, error) {
	packageName := filepath.Base(filepath.Dir(dest))
	templateData := TemplateData{
		PackageName: packageName,
	}

	if includes != "" {
		prtty.Info.Println("--> parsing includes...")
		includes, err := ParseTemplateFiles(includes)
		if err != nil {
			return templateData, err
		}
		templateData.Includes = includes
	}
	if layouts != "" {
		prtty.Info.Println("--> parsing layouts...")
		layouts, err := ParseTemplateFiles(layouts)
		if err != nil {
			return templateData, err
		}
		templateData.Layouts = layouts
	}
	prtty.Info.Println("--> parsing templates...")
	templates, err := ParseTemplateFiles(src)
	if err != nil {
		return templateData, err
	}
	templateData.Templates = templates
	return templateData, nil
}

func writeFile(data TemplateData, dest string) error {
	prtty.Info.Println("--> generating go code...")
	if err := os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
		return err
	}
	if !strings.HasSuffix(dest, ".go") {
		dest += ".go"
	}
	destFile, err := os.Create(dest)
	if err != nil {
		return err
	}
	prtty.Success.Printf("    CREATE %s", dest)
	if err := generatedTmpl.Execute(destFile, data); err != nil {
		return err
	}
	return nil
}

func formatFile(dest string) error {
	if _, err := exec.LookPath("gofmt"); err != nil {
		// gofmt is not installed or is not in PATH
		return nil
	}
	prtty.Default.Println("    formatting with gofmt...")
	output, err := exec.Command("gofmt", "-w", dest).CombinedOutput()
	if err != nil {
		return err
	}
	if output != nil && len(output) > 0 {
		prtty.Default.Printf("    %s", string(output))
	}
	return nil
}
<|endoftext|>


<commit_before>// Command glow generates Go OpenGL bindings. See http://github.com/errcw/glow.
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

var specURL = "https://cvs.khronos.org/svn/repos/ogl/trunk/doc/registry/public/api"
var specRegexp = regexp.MustCompile(`^(gl|glx|egl|wgl)\.xml$`)

var docURLs = []string{
	"https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/docs/man2",
	"https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/docs/man3",
	"https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/docs/man4"}
var docRegexp = regexp.MustCompile(`^[ew]?gl[^u_].*\.xml$`)

func download(name string, args []string) {
	flags := flag.NewFlagSet(name, flag.ExitOnError)
	xmlDir := flags.String("d", "xml", "XML directory")
	flags.Parse(args)

	specDir := filepath.Join(*xmlDir, "spec")
	if err := os.MkdirAll(specDir, 0755); err != nil {
		log.Fatalln("error creating specification output directory:", err)
	}

	docDir := filepath.Join(*xmlDir, "doc")
	if err := os.MkdirAll(docDir, 0755); err != nil {
		log.Fatalln("error creating documentation output directory:", err)
	}

	rev, err := DownloadSvnDir(specURL, specRegexp, specDir)
	if err != nil {
		log.Fatalln("error downloading specification files:", err)
	}

	specVersionFile := filepath.Join(specDir, "REVISION")
	if err := ioutil.WriteFile(specVersionFile, []byte(rev), 0644); err != nil {
		log.Fatalln("error writing spec revision metadata file:", err)
	}

	for _, url := range docURLs {
		if _, err := DownloadSvnDir(url, docRegexp, docDir); err != nil {
			log.Fatalln("error downloading documentation files:", err)
		}
	}
}

func generate(name string, args []string) {
	flags := flag.NewFlagSet(name, flag.ExitOnError)
	xmlDir := flags.String("xml", importPathToDir("github.com/go-gl/glow/xml"), "XML directory")
	tmplDir := flags.String("tmpl", importPathToDir("github.com/go-gl/glow/tmpl"), "Template directory")
	outDir := flags.String("out", "gl", "Output directory")
	api := flags.String("api", "", "API to generate (e.g., gl)")
	ver := flags.String("version", "", "API version to generate (e.g., 4.1)")
	profile := flags.String("profile", "", "API profile to generate (e.g., core)")
	addext := flags.String("addext", ".*", "Regular expression of extensions to include (e.g., .*)")
	remext := flags.String("remext", "$^", "Regular expression of extensions to exclude (e.g., .*)")
	restrict := flags.String("restrict", "", "JSON file of symbols to restrict symbol generation")
	lenientInit := flags.Bool("lenientInit", false, "When true missing functions do not fail Init")
	flags.Parse(args)

	version, err := ParseVersion(*ver)
	if err != nil {
		log.Fatalln("error parsing version:", err)
	}

	addExtRegexp, err := regexp.Compile(*addext)
	if err != nil {
		log.Fatalln("error parsing extension inclusion regexp:", err)
	}

	remExtRegexp, err := regexp.Compile(*remext)
	if err != nil {
		log.Fatalln("error parsing extension exclusion regexp:", err)
	}

	packageSpec := &PackageSpec{
		API:          *api,
		Version:      version,
		Profile:      *profile,
		TmplDir:      *tmplDir,
		AddExtRegexp: addExtRegexp,
		RemExtRegexp: remExtRegexp,
		LenientInit:  *lenientInit,
	}

	specs, rev := parseSpecifications(*xmlDir)
	docs := parseDocumentation(*xmlDir)

	var pkg *Package
	for _, spec := range specs {
		if spec.HasPackage(packageSpec) {
			pkg = spec.ToPackage(packageSpec)
			pkg.SpecRev = rev
			docs.AddDocs(pkg)
			if len(*restrict) > 0 {
				performRestriction(pkg, *restrict)
			}
			if err := pkg.GeneratePackage(*outDir); err != nil {
				log.Fatalln("error generating package:", err)
			}
			break
		}
	}
	if pkg == nil {
		log.Fatalln("unable to generate package:", packageSpec)
	}
	log.Println("generated package in", *outDir)
}

// Converts a slice string into a simple lookup map.
func lookupMap(s []string) map[string]bool {
	lookup := make(map[string]bool, len(s))
	for _, str := range s {
		lookup[str] = true
	}
	return lookup
}

type jsonRestriction struct {
	Enums     []string
	Functions []string
}

// Reads the given JSON file path into jsonRestriction and filters the package
// accordingly.
func performRestriction(pkg *Package, jsonPath string) {
	data, err := ioutil.ReadFile(jsonPath)
	if err != nil {
		log.Fatalln("error reading JSON restriction file:", err)
	}
	var r jsonRestriction
	if err = json.Unmarshal(data, &r); err != nil {
		log.Fatalln("error parsing JSON restriction file:", err)
	}
	pkg.Filter(lookupMap(r.Enums), lookupMap(r.Functions))
}

func parseSpecifications(xmlDir string) ([]*Specification, string) {
	specDir := filepath.Join(xmlDir, "spec")
	specFiles, err := ioutil.ReadDir(specDir)
	if err != nil {
		log.Fatalln("error reading spec file entries:", err)
	}

	specs := make([]*Specification, 0, len(specFiles))
	for _, specFile := range specFiles {
		if !strings.HasSuffix(specFile.Name(), "xml") {
			continue
		}
		spec, err := NewSpecification(filepath.Join(specDir, specFile.Name()))
		if err != nil {
			log.Fatalln("error parsing specification:", specFile.Name(), err)
		}
		specs = append(specs, spec)
	}

	rev, err := ioutil.ReadFile(filepath.Join(specDir, "REVISION"))
	if err != nil {
		log.Fatalln("error reading spec revision file:", err)
	}

	return specs, string(rev)
}

func parseDocumentation(xmlDir string) Documentation {
	docDir := filepath.Join(xmlDir, "doc")
	docFiles, err := ioutil.ReadDir(docDir)
	if err != nil {
		log.Fatalln("error reading doc file entries:", err)
	}

	docs := make([]string, 0, len(docFiles))
	for _, docFile := range docFiles {
		docs = append(docs, filepath.Join(docDir, docFile.Name()))
	}

	doc, err := NewDocumentation(docs)
	if err != nil {
		log.Fatalln("error parsing documentation:", err)
	}

	return doc
}

// PackageSpec describes a package to be generated.
type PackageSpec struct {
	API          string
	Version      Version
	Profile      string // If "all" overrides the version spec
	TmplDir      string
	AddExtRegexp *regexp.Regexp
	RemExtRegexp *regexp.Regexp
	LenientInit  bool
}

func printUsage(name string) {
	fmt.Printf("Usage: %s command [arguments]\n", name)
	fmt.Println("Commands:")
	fmt.Println("  download  Downloads specification and documentation XML files")
	fmt.Println("  generate  Generates bindings")
	fmt.Printf("Use %s <command> -help for a detailed command description\n", name)
}

func main() {
	name := os.Args[0]
	args := os.Args[1:]

	if len(args) < 1 {
		printUsage(name)
		os.Exit(-1)
	}

	command := args[0]
	switch command {
	case "download":
		download("download", args[1:])
	case "generate":
		generate("generate", args[1:])
	default:
		fmt.Printf("Unknown command: '%s'\n", command)
		printUsage(name)
		os.Exit(-1)
	}
}
<commit_msg>Improve readability by grouping flag variables.<commit_after>// Command glow generates Go OpenGL bindings. See http://github.com/errcw/glow.
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

var specURL = "https://cvs.khronos.org/svn/repos/ogl/trunk/doc/registry/public/api"
var specRegexp = regexp.MustCompile(`^(gl|glx|egl|wgl)\.xml$`)

var docURLs = []string{
	"https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/docs/man2",
	"https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/docs/man3",
	"https://cvs.khronos.org/svn/repos/ogl/trunk/ecosystem/public/sdk/docs/man4"}
var docRegexp = regexp.MustCompile(`^[ew]?gl[^u_].*\.xml$`)

func download(name string, args []string) {
	flags := flag.NewFlagSet(name, flag.ExitOnError)
	xmlDir := flags.String("d", "xml", "XML directory")
	flags.Parse(args)

	specDir := filepath.Join(*xmlDir, "spec")
	if err := os.MkdirAll(specDir, 0755); err != nil {
		log.Fatalln("error creating specification output directory:", err)
	}

	docDir := filepath.Join(*xmlDir, "doc")
	if err := os.MkdirAll(docDir, 0755); err != nil {
		log.Fatalln("error creating documentation output directory:", err)
	}

	rev, err := DownloadSvnDir(specURL, specRegexp, specDir)
	if err != nil {
		log.Fatalln("error downloading specification files:", err)
	}

	specVersionFile := filepath.Join(specDir, "REVISION")
	if err := ioutil.WriteFile(specVersionFile, []byte(rev), 0644); err != nil {
		log.Fatalln("error writing spec revision metadata file:", err)
	}

	for _, url := range docURLs {
		if _, err := DownloadSvnDir(url, docRegexp, docDir); err != nil {
			log.Fatalln("error downloading documentation files:", err)
		}
	}
}

func generate(name string, args []string) {
	flags := flag.NewFlagSet(name, flag.ExitOnError)
	var (
		xmlDir      = flags.String("xml", importPathToDir("github.com/go-gl/glow/xml"), "XML directory")
		tmplDir     = flags.String("tmpl", importPathToDir("github.com/go-gl/glow/tmpl"), "Template directory")
		outDir      = flags.String("out", "gl", "Output directory")
		api         = flags.String("api", "", "API to generate (e.g., gl)")
		ver         = flags.String("version", "", "API version to generate (e.g., 4.1)")
		profile     = flags.String("profile", "", "API profile to generate (e.g., core)")
		addext      = flags.String("addext", ".*", "Regular expression of extensions to include (e.g., .*)")
		remext      = flags.String("remext", "$^", "Regular expression of extensions to exclude (e.g., .*)")
		restrict    = flags.String("restrict", "", "JSON file of symbols to restrict symbol generation")
		lenientInit = flags.Bool("lenientInit", false, "When true missing functions do not fail Init")
	)
	flags.Parse(args)

	version, err := ParseVersion(*ver)
	if err != nil {
		log.Fatalln("error parsing version:", err)
	}

	addExtRegexp, err := regexp.Compile(*addext)
	if err != nil {
		log.Fatalln("error parsing extension inclusion regexp:", err)
	}

	remExtRegexp, err := regexp.Compile(*remext)
	if err != nil {
		log.Fatalln("error parsing extension exclusion regexp:", err)
	}

	packageSpec := &PackageSpec{
		API:          *api,
		Version:      version,
		Profile:      *profile,
		TmplDir:      *tmplDir,
		AddExtRegexp: addExtRegexp,
		RemExtRegexp: remExtRegexp,
		LenientInit:  *lenientInit,
	}

	specs, rev := parseSpecifications(*xmlDir)
	docs := parseDocumentation(*xmlDir)

	var pkg *Package
	for _, spec := range specs {
		if spec.HasPackage(packageSpec) {
			pkg = spec.ToPackage(packageSpec)
			pkg.SpecRev = rev
			docs.AddDocs(pkg)
			if len(*restrict) > 0 {
				performRestriction(pkg, *restrict)
			}
			if err := pkg.GeneratePackage(*outDir); err != nil {
				log.Fatalln("error generating package:", err)
			}
			break
		}
	}
	if pkg == nil {
		log.Fatalln("unable to generate package:", packageSpec)
	}
	log.Println("generated package in", *outDir)
}

// Converts a slice string into a simple lookup map.
func lookupMap(s []string) map[string]bool {
	lookup := make(map[string]bool, len(s))
	for _, str := range s {
		lookup[str] = true
	}
	return lookup
}

type jsonRestriction struct {
	Enums     []string
	Functions []string
}

// Reads the given JSON file path into jsonRestriction and filters the package
// accordingly.
func performRestriction(pkg *Package, jsonPath string) {
	data, err := ioutil.ReadFile(jsonPath)
	if err != nil {
		log.Fatalln("error reading JSON restriction file:", err)
	}
	var r jsonRestriction
	if err = json.Unmarshal(data, &r); err != nil {
		log.Fatalln("error parsing JSON restriction file:", err)
	}
	pkg.Filter(lookupMap(r.Enums), lookupMap(r.Functions))
}

func parseSpecifications(xmlDir string) ([]*Specification, string) {
	specDir := filepath.Join(xmlDir, "spec")
	specFiles, err := ioutil.ReadDir(specDir)
	if err != nil {
		log.Fatalln("error reading spec file entries:", err)
	}

	specs := make([]*Specification, 0, len(specFiles))
	for _, specFile := range specFiles {
		if !strings.HasSuffix(specFile.Name(), "xml") {
			continue
		}
		spec, err := NewSpecification(filepath.Join(specDir, specFile.Name()))
		if err != nil {
			log.Fatalln("error parsing specification:", specFile.Name(), err)
		}
		specs = append(specs, spec)
	}

	rev, err := ioutil.ReadFile(filepath.Join(specDir, "REVISION"))
	if err != nil {
		log.Fatalln("error reading spec revision file:", err)
	}

	return specs, string(rev)
}

func parseDocumentation(xmlDir string) Documentation {
	docDir := filepath.Join(xmlDir, "doc")
	docFiles, err := ioutil.ReadDir(docDir)
	if err != nil {
		log.Fatalln("error reading doc file entries:", err)
	}

	docs := make([]string, 0, len(docFiles))
	for _, docFile := range docFiles {
		docs = append(docs, filepath.Join(docDir, docFile.Name()))
	}

	doc, err := NewDocumentation(docs)
	if err != nil {
		log.Fatalln("error parsing documentation:", err)
	}

	return doc
}

// PackageSpec describes a package to be generated.
type PackageSpec struct {
	API          string
	Version      Version
	Profile      string // If "all" overrides the version spec
	TmplDir      string
	AddExtRegexp *regexp.Regexp
	RemExtRegexp *regexp.Regexp
	LenientInit  bool
}

func printUsage(name string) {
	fmt.Printf("Usage: %s command [arguments]\n", name)
	fmt.Println("Commands:")
	fmt.Println("  download  Downloads specification and documentation XML files")
	fmt.Println("  generate  Generates bindings")
	fmt.Printf("Use %s <command> -help for a detailed command description\n", name)
}

func main() {
	name := os.Args[0]
	args := os.Args[1:]

	if len(args) < 1 {
		printUsage(name)
		os.Exit(-1)
	}

	command := args[0]
	switch command {
	case "download":
		download("download", args[1:])
	case "generate":
		generate("generate", args[1:])
	default:
		fmt.Printf("Unknown command: '%s'\n", command)
		printUsage(name)
		os.Exit(-1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"code.google.com/p/go.net/html"
	"fmt"
	"github.com/jcgregorio/piccolo/piccolo"
	"io"
	"log"
	"os"
	"path/filepath"
	"sort"
	"text/template"
	"time"
)

const (
	SITE_TITLE = "BitWorking"
	DOMAIN     = "http://bitworking.org/"
	FEED_LEN   = 10
)

var shortMonths = [...]string{
	"Jan",
	"Feb",
	"Mar",
	"Apr",
	"May",
	"Jun",
	"Jul",
	"Aug",
	"Sep",
	"Oct",
	"Nov",
	"Dec",
}

func fatalf(format string, args ...interface{}) {
	fmt.Printf(format, args...)
	os.Exit(1)
}

// ShortMonth returns the short English name of the month ("Jan", "Feb", ...).
func ShortMonth(m time.Month) string { return shortMonths[m-1] }

type datediffer func(time.Time) string

// datediff returns a function that formats the archive entries correctly.
//
// The returned function is a closure that keeps track of the last time.Time it
// saw which it needs to do the formatting correctly.
func datediff() datediffer {
	var last time.Time

	return func(t time.Time) string {
		r := ""
		if t.After(last) {
			r = fmt.Sprintf("foo %#v", t)
		}
		// If years differ, emit year, month, day
		if t.Year() != last.Year() {
			r = fmt.Sprintf("%d
\n
%s
\n
 %d", t.Year(), ShortMonth(t.Month()), t.Day())
		} else if t.Month() != last.Month() {
			r = fmt.Sprintf("%s
\n
 %d", ShortMonth(t.Month()), t.Day())
		} else {
			r = fmt.Sprintf("%d", t.Day())
		}
		last = t
		return r
	}
}

// trunc10 formats a time to just the year, month and day in ISO format.
func trunc10(t time.Time) string {
	return t.Format("2006-01-02")
}

// rfc339 formats a time in RFC3339 format.
func rfc3339(t time.Time) string {
	return t.Format(time.RFC3339)
}

// Templates contains all the parsed templates.
type Templates struct {
	IndexHTML   *template.Template
	IndexAtom   *template.Template
	ArchiveHTML *template.Template
	EntryHTML   *template.Template
}

func loadTemplate(d *piccolo.DocSet, name string) *template.Template {
	funcMap := template.FuncMap{
		"datediff": datediff(),
		"trunc10":  trunc10,
		"rfc3339":  rfc3339,
	}

	fullname := filepath.Join(d.Root, "tpl", name)
	return template.Must(template.New(name).Funcs(funcMap).ParseFiles(fullname))
}

func loadTemplates(d *piccolo.DocSet) *Templates {
	return &Templates{
		IndexHTML:   loadTemplate(d, "index.html"),
		IndexAtom:   loadTemplate(d, "index.atom"),
		ArchiveHTML: loadTemplate(d, "archive.html"),
		EntryHTML:   loadTemplate(d, "entry.html"),
	}
}

// Expand expands the template with the given data.
func Expand(d *piccolo.DocSet, t *template.Template, data interface{}, path string) error {
	dst, err := d.Dest(path)
	if err != nil {
		return err
	}
	dstDir, _ := filepath.Split(dst)
	if err := os.MkdirAll(dstDir, 0755); err != nil {
		return err
	}
	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer out.Close()
	t.Execute(out, data)
	return nil
}

// Include loads the include file given the docset d.
//
// Returns the extracted HTML and the time the file was last modified.
func Include(d *piccolo.DocSet, filename, element string) (string, time.Time, error) {
	fullname := filepath.Join(d.Root, "inc", filename)

	f, err := os.Open(fullname)
	if err != nil {
		return "", time.Time{}, err
	}
	defer f.Close()

	stat, err := f.Stat()
	if err != nil {
		return "", time.Time{}, err
	}
	t := stat.ModTime()

	doc, err := html.Parse(f)
	if err != nil {
		return "", time.Time{}, err
	}

	var found func(*html.Node)
	children := []*html.Node{}
	found = func(n *html.Node) {
		if n.Type == html.ElementNode && n.Data == element {
			for c := n.FirstChild; c != nil; c = c.NextSibling {
				children = append(children, c)
			}
			return
		}
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			found(c)
		}
	}
	found(doc)
	return StrFromNodes(children), t, nil
}

// Newest returns the most recent of all the times passed in.
func Newest(times ...time.Time) time.Time {
	newest := times[0]
	for _, t := range times {
		if t.After(newest) {
			newest = t
		}
	}
	return newest
}

// StrFromNodes returns the string of the rendered html.Nodes.
func StrFromNodes(nodes []*html.Node) string {
	buf := bytes.NewBuffer([]byte{})
	for _, h := range nodes {
		html.Render(buf, h)
	}
	return buf.String()
}

// Entry represents a single blog entry.
type Entry struct {
	// Path is the source file path.
	Path string

	// Title is the title of the entry.
	Title string

	// URL is the relative URL of the file.
	URL string

	// Created is the created time.
	Created time.Time

	// Upated is the updated time.
	Updated time.Time

	// Body is the string representation of the body element, w/o
	// the  tags.
	Body string
}

// EntryByCreated is a type that allows sorting Entries by their created time.
type EntryByCreated []*Entry

func (s EntryByCreated) Len() int           { return len(s) }
func (s EntryByCreated) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s EntryByCreated) Less(i, j int) bool { return s[i].Created.After(s[j].Created) }

// TemplateData is the data used for expanding the index and archive (html and atom) templates.
type TemplateData struct {
	// Domain is the domain name the site will be served from.
	Domain string

	SiteTitle string
	Header    string
	Titlebar  string
	Footer    string
	Entries   []*Entry

	// Most recent time anything on the site was updated.
	Updated time.Time
}

func modifiedTime(path string) time.Time {
	mod := time.Time{}
	if stat, err := os.Stat(path); err == nil {
		mod = stat.ModTime()
	}
	return mod
}

func incMust(s string, t time.Time, err error) (string, time.Time) {
	if err != nil {
		log.Fatalf("Error loading header: %v\n", err)
	}
	return s, t
}

func main() {
	cwd, err := os.Getwd()
	if err != nil {
		log.Fatalf("Failed to get cwd: %v\n", err)
	}
	d, err := piccolo.NewDocSet(cwd)
	if err != nil {
		log.Fatalf("Error building docset: %v\n", err)
	}
	fmt.Printf("Root: %s\n", d.Root)

	templates := loadTemplates(d)

	headerStr, headerMod := incMust(Include(d, "header.html", "head"))
	footerStr, footerMod := incMust(Include(d, "footer.html", "body"))
	titlebarStr, titlebarMod := incMust(Include(d, "titlebar.html", "body"))

	entryMod := modifiedTime(filepath.Join(d.Root, "tpl", "entry.html"))

	incMod := Newest(headerMod, footerMod, titlebarMod, entryMod)

	oneentry := make([]*Entry, 1)
	data := &TemplateData{
		Domain:    DOMAIN,
		SiteTitle: SITE_TITLE,
		Header:    headerStr,
		Titlebar:  titlebarStr,
		Footer:    footerStr,
		Entries:   oneentry,
	}

	entries := make([]*Entry, 0)

	// Walk the docset and copy over files, possibly transformed.  Collect all
	// the entries along the way.
	walker := func(path string, info os.FileInfo, err error) error {
		attr, err := d.Path(path)
		if err != nil {
			return err
		}
		if info.IsDir() && attr.Has(piccolo.IGNORE) {
			return filepath.SkipDir
		}
		dest, err := d.Dest(path)
		if err != nil {
			return err
		}
		destMod := modifiedTime(dest)
		if !info.IsDir() && attr.Has(piccolo.INCLUDE) {
			if filepath.Ext(path) == ".html" {
				fileinfo, err := piccolo.CreationDateSaved(path)
				if err != nil {
					return err
				}
				url, err := d.URL(path)
				if err != nil {
					return err
				}
				entries = append(entries, &Entry{
					Path:    path,
					Title:   fileinfo.Title,
					URL:     url,
					Created: fileinfo.Created,
					Updated: fileinfo.Updated,
				})
				if Newest(fileinfo.Updated, incMod).After(destMod) {
					fmt.Printf("INCLUDE:  %v\n", dest)

					// Use the data for template expansion, but with only one entry in it.
					data.Entries[0] = entries[len(entries)-1]
					data.Entries[0].Body = StrFromNodes(fileinfo.Body())
					if err := Expand(d, templates.EntryHTML, data, path); err != nil {
						return err
					}
				}
			}
		}
		if !info.IsDir() && attr.Has(piccolo.VERBATIM) {
			if info.ModTime().After(destMod) {
				fmt.Printf("VERBATIM: %v\n", dest)
				if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
					return err
				}
				dst, err := os.Create(dest)
				if err != nil {
					return err
				}
				defer dst.Close()
				src, err := os.Open(path)
				if err != nil {
					return err
				}
				defer src.Close()
				_, err = io.Copy(dst, src)
				if err != nil {
					return err
				}
			}
		}
		return nil
	}
	err = filepath.Walk(d.Root, walker)
	if err != nil {
		fatalf("Error walking: %v\n", err)
	}

	sort.Sort(EntryByCreated(entries))
	data.Entries = entries

	// TODO(jcgregorio) This is actually wrong, need to sort by Upated first.
	data.Updated = entries[0].Updated

	if err := Expand(d, templates.ArchiveHTML, data, filepath.Join(d.Archive, "index.html")); err != nil {
		fatalf("Error building archive: %v\n", err)
	}

	// Take the first 10 items from the list, expand the Body, then pass to templates.
	latest := entries[:FEED_LEN]
	for _, e := range latest {
		fi, _ := piccolo.CreationDateSaved(e.Path)
		e.Body = StrFromNodes(fi.Body())
	}
	data.Entries = latest

	if err := Expand(d, templates.IndexHTML, data, filepath.Join(d.Main, "index.html")); err != nil {
		fatalf("Error building archive: %v\n", err)
	}

	if err := Expand(d, templates.IndexAtom, data, filepath.Join(d.Feed, "index.atom")); err != nil {
		fatalf("Error building feed: %v\n", err)
	}
}
<commit_msg>Inline the compiled CSS<commit_after>package main

import (
	"bytes"
	"code.google.com/p/go.net/html"
	"fmt"
	"github.com/jcgregorio/piccolo/piccolo"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"sort"
	"text/template"
	"time"
)

const (
	SITE_TITLE = "BitWorking"
	DOMAIN     = "http://bitworking.org/"
	FEED_LEN   = 10
)

var shortMonths = [...]string{
	"Jan",
	"Feb",
	"Mar",
	"Apr",
	"May",
	"Jun",
	"Jul",
	"Aug",
	"Sep",
	"Oct",
	"Nov",
	"Dec",
}

func fatalf(format string, args ...interface{}) {
	fmt.Printf(format, args...)
	os.Exit(1)
}

// ShortMonth returns the short English name of the month ("Jan", "Feb", ...).
func ShortMonth(m time.Month) string { return shortMonths[m-1] }

type datediffer func(time.Time) string

// datediff returns a function that formats the archive entries correctly.
//
// The returned function is a closure that keeps track of the last time.Time it
// saw which it needs to do the formatting correctly.
func datediff() datediffer {
	var last time.Time

	return func(t time.Time) string {
		r := ""
		if t.After(last) {
			r = fmt.Sprintf("foo %#v", t)
		}
		// If years differ, emit year, month, day
		if t.Year() != last.Year() {
			r = fmt.Sprintf("%d
\n
%s
\n
 %d", t.Year(), ShortMonth(t.Month()), t.Day())
		} else if t.Month() != last.Month() {
			r = fmt.Sprintf("%s
\n
 %d", ShortMonth(t.Month()), t.Day())
		} else {
			r = fmt.Sprintf("%d", t.Day())
		}
		last = t
		return r
	}
}

// trunc10 formats a time to just the year, month and day in ISO format.
func trunc10(t time.Time) string {
	return t.Format("2006-01-02")
}

// rfc339 formats a time in RFC3339 format.
func rfc3339(t time.Time) string {
	return t.Format(time.RFC3339)
}

// Templates contains all the parsed templates.
type Templates struct {
	IndexHTML   *template.Template
	IndexAtom   *template.Template
	ArchiveHTML *template.Template
	EntryHTML   *template.Template
}

func loadTemplate(d *piccolo.DocSet, name string) *template.Template {
	funcMap := template.FuncMap{
		"datediff": datediff(),
		"trunc10":  trunc10,
		"rfc3339":  rfc3339,
	}

	fullname := filepath.Join(d.Root, "tpl", name)
	return template.Must(template.New(name).Funcs(funcMap).ParseFiles(fullname))
}

func loadTemplates(d *piccolo.DocSet) *Templates {
	return &Templates{
		IndexHTML:   loadTemplate(d, "index.html"),
		IndexAtom:   loadTemplate(d, "index.atom"),
		ArchiveHTML: loadTemplate(d, "archive.html"),
		EntryHTML:   loadTemplate(d, "entry.html"),
	}
}

// Expand expands the template with the given data.
func Expand(d *piccolo.DocSet, t *template.Template, data interface{}, path string) error {
	dst, err := d.Dest(path)
	if err != nil {
		return err
	}
	dstDir, _ := filepath.Split(dst)
	if err := os.MkdirAll(dstDir, 0755); err != nil {
		return err
	}
	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer out.Close()
	t.Execute(out, data)
	return nil
}

// SimpleInclude loads the include file given the docset d.
//
func SimpleInclude(d *piccolo.DocSet, filename string) (string, time.Time, error) {
	fullname := filepath.Join(d.Root, filename)

	f, err := os.Open(fullname)
	if err != nil {
		return "", time.Time{}, err
	}
	defer f.Close()

	stat, err := f.Stat()
	if err != nil {
		return "", time.Time{}, err
	}
	t := stat.ModTime()
	b, err := ioutil.ReadAll(f)
	if err != nil {
		return "", time.Time{}, err
	}
	return string(b), t, nil
}

// Include loads the include file given the docset d.
//
// Returns the extracted HTML and the time the file was last modified.
func Include(d *piccolo.DocSet, filename, element string) (string, time.Time, error) {
	fullname := filepath.Join(d.Root, "inc", filename)

	f, err := os.Open(fullname)
	if err != nil {
		return "", time.Time{}, err
	}
	defer f.Close()

	stat, err := f.Stat()
	if err != nil {
		return "", time.Time{}, err
	}
	t := stat.ModTime()

	doc, err := html.Parse(f)
	if err != nil {
		return "", time.Time{}, err
	}

	var found func(*html.Node)
	children := []*html.Node{}
	found = func(n *html.Node) {
		if n.Type == html.ElementNode && n.Data == element {
			for c := n.FirstChild; c != nil; c = c.NextSibling {
				children = append(children, c)
			}
			return
		}
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			found(c)
		}
	}
	found(doc)
	return StrFromNodes(children), t, nil
}

// Newest returns the most recent of all the times passed in.
func Newest(times ...time.Time) time.Time {
	newest := times[0]
	for _, t := range times {
		if t.After(newest) {
			newest = t
		}
	}
	return newest
}

// StrFromNodes returns the string of the rendered html.Nodes.
func StrFromNodes(nodes []*html.Node) string {
	buf := bytes.NewBuffer([]byte{})
	for _, h := range nodes {
		html.Render(buf, h)
	}
	return buf.String()
}

// Entry represents a single blog entry.
type Entry struct {
	// Path is the source file path.
	Path string

	// Title is the title of the entry.
	Title string

	// URL is the relative URL of the file.
	URL string

	// Created is the created time.
	Created time.Time

	// Upated is the updated time.
	Updated time.Time

	// Body is the string representation of the body element, w/o
	// the  tags.
	Body string
}

// EntryByCreated is a type that allows sorting Entries by their created time.
type EntryByCreated []*Entry

func (s EntryByCreated) Len() int           { return len(s) }
func (s EntryByCreated) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s EntryByCreated) Less(i, j int) bool { return s[i].Created.After(s[j].Created) }

// TemplateData is the data used for expanding the index and archive (html and atom) templates.
type TemplateData struct {
	// Domain is the domain name the site will be served from.
	Domain string

	SiteTitle string
	Header    string
	InlineCSS string
	Titlebar  string
	Footer    string
	Entries   []*Entry

	// Most recent time anything on the site was updated.
	Updated time.Time
}

func modifiedTime(path string) time.Time {
	mod := time.Time{}
	if stat, err := os.Stat(path); err == nil {
		mod = stat.ModTime()
	}
	return mod
}

func incMust(s string, t time.Time, err error) (string, time.Time) {
	if err != nil {
		log.Fatalf("Error loading header: %v\n", err)
	}
	return s, t
}

func main() {
	cwd, err := os.Getwd()
	if err != nil {
		log.Fatalf("Failed to get cwd: %v\n", err)
	}
	d, err := piccolo.NewDocSet(cwd)
	if err != nil {
		log.Fatalf("Error building docset: %v\n", err)
	}
	fmt.Printf("Root: %s\n", d.Root)

	templates := loadTemplates(d)

	headerStr, headerMod := incMust(Include(d, "header.html", "head"))
	inlineCss, inlineCssMod := incMust(SimpleInclude(d, "css/b.css"))
	footerStr, footerMod := incMust(Include(d, "footer.html", "body"))
	titlebarStr, titlebarMod := incMust(Include(d, "titlebar.html", "body"))

	entryMod := modifiedTime(filepath.Join(d.Root, "tpl", "entry.html"))

	incMod := Newest(headerMod, inlineCssMod, footerMod, titlebarMod, entryMod)

	oneentry := make([]*Entry, 1)
	data := &TemplateData{
		Domain:    DOMAIN,
		SiteTitle: SITE_TITLE,
		Header:    headerStr,
		InlineCSS: string(inlineCss),
		Titlebar:  titlebarStr,
		Footer:    footerStr,
		Entries:   oneentry,
	}

	entries := make([]*Entry, 0)

	// Walk the docset and copy over files, possibly transformed.  Collect all
	// the entries along the way.
	walker := func(path string, info os.FileInfo, err error) error {
		attr, err := d.Path(path)
		if err != nil {
			return err
		}
		if info.IsDir() && attr.Has(piccolo.IGNORE) {
			return filepath.SkipDir
		}
		dest, err := d.Dest(path)
		if err != nil {
			return err
		}
		destMod := modifiedTime(dest)
		if !info.IsDir() && attr.Has(piccolo.INCLUDE) {
			if filepath.Ext(path) == ".html" {
				fileinfo, err := piccolo.CreationDateSaved(path)
				if err != nil {
					return err
				}
				url, err := d.URL(path)
				if err != nil {
					return err
				}
				entries = append(entries, &Entry{
					Path:    path,
					Title:   fileinfo.Title,
					URL:     url,
					Created: fileinfo.Created,
					Updated: fileinfo.Updated,
				})
				if Newest(fileinfo.Updated, incMod).After(destMod) {
					fmt.Printf("INCLUDE:  %v\n", dest)

					// Use the data for template expansion, but with only one entry in it.
					data.Entries[0] = entries[len(entries)-1]
					data.Entries[0].Body = StrFromNodes(fileinfo.Body())
					if err := Expand(d, templates.EntryHTML, data, path); err != nil {
						return err
					}
				}
			}
		}
		if !info.IsDir() && attr.Has(piccolo.VERBATIM) {
			if info.ModTime().After(destMod) {
				fmt.Printf("VERBATIM: %v\n", dest)
				if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil {
					return err
				}
				dst, err := os.Create(dest)
				if err != nil {
					return err
				}
				defer dst.Close()
				src, err := os.Open(path)
				if err != nil {
					return err
				}
				defer src.Close()
				_, err = io.Copy(dst, src)
				if err != nil {
					return err
				}
			}
		}
		return nil
	}
	err = filepath.Walk(d.Root, walker)
	if err != nil {
		fatalf("Error walking: %v\n", err)
	}

	sort.Sort(EntryByCreated(entries))
	data.Entries = entries

	// TODO(jcgregorio) This is actually wrong, need to sort by Upated first.
	data.Updated = entries[0].Updated

	if err := Expand(d, templates.ArchiveHTML, data, filepath.Join(d.Archive, "index.html")); err != nil {
		fatalf("Error building archive: %v\n", err)
	}

	// Take the first 10 items from the list, expand the Body, then pass to templates.
	latest := entries[:FEED_LEN]
	for _, e := range latest {
		fi, _ := piccolo.CreationDateSaved(e.Path)
		e.Body = StrFromNodes(fi.Body())
	}
	data.Entries = latest

	if err := Expand(d, templates.IndexHTML, data, filepath.Join(d.Main, "index.html")); err != nil {
		fatalf("Error building archive: %v\n", err)
	}

	if err := Expand(d, templates.IndexAtom, data, filepath.Join(d.Feed, "index.atom")); err != nil {
		fatalf("Error building feed: %v\n", err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"flag"

	"net/http"
	"os"
	"time"

	"github.com/ewhal/nyaa/cache"
	"github.com/ewhal/nyaa/config"
	"github.com/ewhal/nyaa/db"
	"github.com/ewhal/nyaa/network"
	"github.com/ewhal/nyaa/router"
	"github.com/ewhal/nyaa/service/scraper"
	"github.com/ewhal/nyaa/service/torrent/filesizeFetcher"
	"github.com/ewhal/nyaa/util/languages"
	"github.com/ewhal/nyaa/util/log"
	"github.com/ewhal/nyaa/util/search"
	"github.com/ewhal/nyaa/util/signals"
)

// RunServer runs webapp mainloop
func RunServer(conf *config.Config) {
	http.Handle("/", router.Router)

	// Set up server,
	srv := &http.Server{
		WriteTimeout: 5 * time.Second,
		ReadTimeout:  5 * time.Second,
	}
	l, err := network.CreateHTTPListener(conf)
	log.CheckError(err)
	if err == nil {
		// add http server to be closed gracefully
		signals.RegisterCloser(&network.GracefulHttpCloser{
			Server:   srv,
			Listener: l,
		})
		log.Infof("listening on %s", l.Addr())
		err := srv.Serve(l)
		if err != nil && err != network.ErrListenerStopped {
			log.CheckError(err)
		}

	}
}

// RunScraper runs tracker scraper mainloop
func RunScraper(conf *config.Config) {

	// bind to network
	pc, err := network.CreateScraperSocket(conf)
	if err != nil {
		log.Fatalf("failed to bind udp socket for scraper: %s", err)
	}
	// configure tracker scraperv
	var scraper *scraperService.Scraper
	scraper, err = scraperService.New(&conf.Scrape)
	if err != nil {
		pc.Close()
		log.Fatalf("failed to configure scraper: %s", err)
	}

	workers := conf.Scrape.NumWorkers
	if workers < 1 {
		workers = 1
	}

	// register udp socket with signals
	signals.RegisterCloser(pc)
	// register scraper with signals
	signals.RegisterCloser(scraper)
	// run udp scraper worker
	for workers > 0 {
		log.Infof("starting up worker %d", workers)
		go scraper.RunWorker(pc)
		workers--
	}
	// run scraper
	go scraper.Run()
	scraper.Wait()
}

// RunFilesizeFetcher runs the database filesize fetcher main loop
func RunFilesizeFetcher(conf *config.Config) {
	fetcher, err := filesizeFetcher.New(&conf.FilesizeFetcher)
	if err != nil {
		log.Fatalf("failed to start fetcher, %s", err)
		return
	}

	signals.RegisterCloser(fetcher)
	fetcher.RunAsync()
	fetcher.Wait()
}

func main() {
	conf := config.New()
	processFlags := conf.BindFlags()
	defaults := flag.Bool("print-defaults", false, "print the default configuration file on stdout")
	mode := flag.String("mode", "webapp", "which mode to run daemon in, either webapp, scraper or filesize_fetcher")
	flag.Float64Var(&conf.Cache.Size, "c", config.DefaultCacheSize, "size of the search cache in MB")

	flag.Parse()
	if *defaults {
		stdout := bufio.NewWriter(os.Stdout)
		err := conf.Pretty(stdout)
		if err != nil {
			log.Fatal(err.Error())
		}
		err = stdout.Flush()
		if err != nil {
			log.Fatal(err.Error())
		}
		os.Exit(0)
	} else {
		err := processFlags()
		if err != nil {
			log.CheckError(err)
		}
		db.ORM, err = db.GormInit(conf, db.DefaultLogger)
		if err != nil {
			log.Fatal(err.Error())
		}
		err = languages.InitI18n(conf.I18n)
		if err != nil {
			log.Fatal(err.Error())
		}
		err = cache.Configure(&conf.Cache)
		if err != nil {
			log.Fatal(err.Error())
		}
		err = search.Configure(&conf.Search)
		if err != nil {
			log.Fatal(err.Error())
		}
		go signals.Handle()
		if len(config.TorrentFileStorage) > 0 {
			err := os.MkdirAll(config.TorrentFileStorage, 0700)
			if err != nil {
				log.Fatal(err.Error())
			}
		}
		if *mode == "scraper" {
			RunScraper(conf)
		} else if *mode == "webapp" {
			RunServer(conf)
		} else if *mode == "filesize_fetcher" {
			RunFilesizeFetcher(conf)
		} else {
			log.Fatalf("invalid runtime mode: %s", *mode)
		}
	}
}
<commit_msg>rename filesize_fetcher to metadata_fetcher<commit_after>package main

import (
	"bufio"
	"flag"

	"net/http"
	"os"
	"time"

	"github.com/ewhal/nyaa/cache"
	"github.com/ewhal/nyaa/config"
	"github.com/ewhal/nyaa/db"
	"github.com/ewhal/nyaa/network"
	"github.com/ewhal/nyaa/router"
	"github.com/ewhal/nyaa/service/scraper"
	"github.com/ewhal/nyaa/service/torrent/filesizeFetcher"
	"github.com/ewhal/nyaa/util/languages"
	"github.com/ewhal/nyaa/util/log"
	"github.com/ewhal/nyaa/util/search"
	"github.com/ewhal/nyaa/util/signals"
)

// RunServer runs webapp mainloop
func RunServer(conf *config.Config) {
	http.Handle("/", router.Router)

	// Set up server,
	srv := &http.Server{
		WriteTimeout: 5 * time.Second,
		ReadTimeout:  5 * time.Second,
	}
	l, err := network.CreateHTTPListener(conf)
	log.CheckError(err)
	if err == nil {
		// add http server to be closed gracefully
		signals.RegisterCloser(&network.GracefulHttpCloser{
			Server:   srv,
			Listener: l,
		})
		log.Infof("listening on %s", l.Addr())
		err := srv.Serve(l)
		if err != nil && err != network.ErrListenerStopped {
			log.CheckError(err)
		}

	}
}

// RunScraper runs tracker scraper mainloop
func RunScraper(conf *config.Config) {

	// bind to network
	pc, err := network.CreateScraperSocket(conf)
	if err != nil {
		log.Fatalf("failed to bind udp socket for scraper: %s", err)
	}
	// configure tracker scraperv
	var scraper *scraperService.Scraper
	scraper, err = scraperService.New(&conf.Scrape)
	if err != nil {
		pc.Close()
		log.Fatalf("failed to configure scraper: %s", err)
	}

	workers := conf.Scrape.NumWorkers
	if workers < 1 {
		workers = 1
	}

	// register udp socket with signals
	signals.RegisterCloser(pc)
	// register scraper with signals
	signals.RegisterCloser(scraper)
	// run udp scraper worker
	for workers > 0 {
		log.Infof("starting up worker %d", workers)
		go scraper.RunWorker(pc)
		workers--
	}
	// run scraper
	go scraper.Run()
	scraper.Wait()
}

// RunFilesizeFetcher runs the database filesize fetcher main loop
func RunFilesizeFetcher(conf *config.Config) {
	fetcher, err := filesizeFetcher.New(&conf.FilesizeFetcher)
	if err != nil {
		log.Fatalf("failed to start fetcher, %s", err)
		return
	}

	signals.RegisterCloser(fetcher)
	fetcher.RunAsync()
	fetcher.Wait()
}

func main() {
	conf := config.New()
	processFlags := conf.BindFlags()
	defaults := flag.Bool("print-defaults", false, "print the default configuration file on stdout")
	mode := flag.String("mode", "webapp", "which mode to run daemon in, either webapp, scraper or filesize_fetcher")
	flag.Float64Var(&conf.Cache.Size, "c", config.DefaultCacheSize, "size of the search cache in MB")

	flag.Parse()
	if *defaults {
		stdout := bufio.NewWriter(os.Stdout)
		err := conf.Pretty(stdout)
		if err != nil {
			log.Fatal(err.Error())
		}
		err = stdout.Flush()
		if err != nil {
			log.Fatal(err.Error())
		}
		os.Exit(0)
	} else {
		err := processFlags()
		if err != nil {
			log.CheckError(err)
		}
		db.ORM, err = db.GormInit(conf, db.DefaultLogger)
		if err != nil {
			log.Fatal(err.Error())
		}
		err = languages.InitI18n(conf.I18n)
		if err != nil {
			log.Fatal(err.Error())
		}
		err = cache.Configure(&conf.Cache)
		if err != nil {
			log.Fatal(err.Error())
		}
		err = search.Configure(&conf.Search)
		if err != nil {
			log.Fatal(err.Error())
		}
		go signals.Handle()
		if len(config.TorrentFileStorage) > 0 {
			err := os.MkdirAll(config.TorrentFileStorage, 0700)
			if err != nil {
				log.Fatal(err.Error())
			}
		}
		if *mode == "scraper" {
			RunScraper(conf)
		} else if *mode == "webapp" {
			RunServer(conf)
		} else if *mode == "metadata_fetcher" {
			RunFilesizeFetcher(conf)
		} else {
			log.Fatalf("invalid runtime mode: %s", *mode)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
)

type (
	version struct {
		Ref string `json:"ref"`
	}
	inputJSON struct {
		Params  map[string]string `json:"params"`
		Source  map[string]string `json:"source"`
		Version version           `json:"version"`
	}
	metadata struct {
		Name  string `json:"name"`
		Value string `json:"value"`
	}
	checkoutputJSON []version
	inoutputJSON    struct {
		Version  version    `json:"version"`
		Metadata []metadata `json:"metadata"`
	}
	outoutputJSON struct {
		Version  version    `json:"version"`
		Metadata []metadata `json:"metadata"`
	}
)

func check(input inputJSON) {

	// PARSE THE JSON FILE /tmp/input.json
	var source1 = input.Source["source1"]
	var source2 = input.Source["source2"]
	var ref = input.Version.Ref
	fmt.Fprintln(os.Stderr, "source are")
	fmt.Fprintln(os.Stderr, source1, source2)
	fmt.Fprintln(os.Stderr, "")
	fmt.Fprintln(os.Stderr, "ref is")
	fmt.Fprintln(os.Stderr, ref)
	fmt.Fprintln(os.Stderr, "")

	// CHECK (THE RESOURCE VERSION(s)) *******************************************************************
	// Mimic a fetch and output the following versions for IN.

	ver1 := "123"
	ver2 := "3de"
	ver3 := "456"

	// OUTPUT **************************************************************************************
	output := &checkoutputJSON{
		{Ref: ver1},
		{Ref: ver2},
		{Ref: ver3},
	}

	fmt.Fprintln(os.Stderr, ".json output is:")
	b, _ := json.MarshalIndent(output, "", "  ")
	fmt.Fprintln(os.Stderr, string(b))
	fmt.Fprintln(os.Stderr, "")

	// Encode .json and send to stdout
	json.NewEncoder(os.Stdout).Encode(&output)

}

func in(input inputJSON) {

	// PARSE THE JSON FILE /tmp/input.json
	var source1 = input.Source["source1"]
	var source2 = input.Source["source2"]
	var param1 = input.Params["param1"]
	var param2 = input.Params["param2"]
	var ref = input.Version.Ref
	fmt.Fprintln(os.Stderr, "source are")
	fmt.Fprintln(os.Stderr, source1, source2)
	fmt.Fprintln(os.Stderr, "")
	fmt.Fprintln(os.Stderr, "params are")
	fmt.Fprintln(os.Stderr, param1, param2)
	fmt.Fprintln(os.Stderr, "")
	fmt.Fprintln(os.Stderr, "ref is")
	fmt.Fprintln(os.Stderr, ref)
	fmt.Fprintln(os.Stderr, "")

	// IN (FETCH THE RESOURCE) *************************************************************************
	// Mimic a fetch and place a fetched.json file in the working directory that contains the following.

	jsonfile := "This is a file I made"

	// Create a fake fetched file
	filewrite, err := os.Create("fetch.json")
	if err != nil {
		log.Fatal("Cannot create file", err)
	}
	defer filewrite.Close()
	fmt.Fprintf(filewrite, jsonfile)

	//ls -lat $WORKING_DIR
	files, _ := ioutil.ReadDir("./")
	for _, f := range files {
		fmt.Fprintln(os.Stderr, f.Name())
	}
	fmt.Fprintln(os.Stderr, "")

	// Cat the file
	fmt.Fprintln(os.Stderr, "Cat fetch.json")
	file, err := os.Open("fetch.json")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()
	bb, err := ioutil.ReadAll(file)
	fmt.Print(string(bb))
	fmt.Fprintln(os.Stderr, "")
	fmt.Fprintln(os.Stderr, "")

	var monkeyname = "Larry"

	// OUTPUT **************************************************************************************
	output := &inoutputJSON{
		Version: version{Ref: ref},
		Metadata: []metadata{
			{Name: "nameofmonkey", Value: monkeyname},
			{Name: "author", Value: "Jeff DeCola"},
		},
	}

	fmt.Fprintln(os.Stderr, ".json output is:")
	b, _ := json.MarshalIndent(output, "", "  ")
	fmt.Fprintln(os.Stderr, string(b))
	fmt.Fprintln(os.Stderr, "")

	// Encode .json and send to stdout
	json.NewEncoder(os.Stdout).Encode(&output)

}

func out(input inputJSON) {

	// PARSE THE JSON FILE /tmp/input.json
	var param1 = input.Params["param1"]
	var param2 = input.Params["param2"]
	var source1 = input.Source["source1"]
	var source2 = input.Source["source2"]
	fmt.Fprintln(os.Stderr, "params are")
	fmt.Fprintln(os.Stderr, param1, param2)
	fmt.Fprintln(os.Stderr, "")
	fmt.Fprintln(os.Stderr, "source are")
	fmt.Fprintln(os.Stderr, source1, source2)
	fmt.Fprintln(os.Stderr, "")

	// OUT (UPDATE THE RESOURCE) *************************************************************************
	// Mimic an out.

	var monkeyname = "Henry"
	var ref = "123"

	// OUTPUT **************************************************************************************
	output := &outoutputJSON{
		Version: version{Ref: ref},
		Metadata: []metadata{
			{Name: "nameofmonkey", Value: monkeyname},
			{Name: "author", Value: "Jeff DeCola"},
		},
	}

	fmt.Fprintln(os.Stderr, ".json output is:")
	b, _ := json.MarshalIndent(output, "", "  ")
	fmt.Fprintln(os.Stderr, string(b))
	fmt.Fprintln(os.Stderr, "")

	// Encode .json and send to stdout
	json.NewEncoder(os.Stdout).Encode(&output)

}

func main() {

	// Decode the .json from stdin and place in a .json struct.
	var input inputJSON
	var decoder = json.NewDecoder(os.Stdin)

	err := decoder.Decode(&input)

	// Test if error reading stdin .json format
	if err != nil {
		panic("Failed to decode stdin")
	}

	switch os.Args[1] {
	case "check":
		fmt.Fprintln(os.Stderr, "CHECK (THE RESOURCE VERSION(s))")
		fmt.Fprintln(os.Stderr, "")
	case "in":
		fmt.Fprintln(os.Stderr, "IN (FETCH THE RESOURCE)")
		fmt.Fprintln(os.Stderr, "")
	case "out":
		fmt.Fprintln(os.Stderr, "OUT (UPDATE THE RESOURCE)")
		fmt.Fprintln(os.Stderr, "")
	}

	// Get the working directory from arg $2
	var workingdir = os.Args[2]
	fmt.Fprintln(os.Stderr, "WORKING_DIR = ", workingdir)
	fmt.Fprintln(os.Stderr, "List whats in the working directory")
	//ls -lat $WORKING_DIR
	files, _ := ioutil.ReadDir("./")
	for _, f := range files {
		fmt.Fprintln(os.Stderr, f.Name())
	}
	fmt.Fprintln(os.Stderr, "")

	// List whats in the input stdin .json
	// MashalIndent makes it print nicely
	fmt.Fprintln(os.Stderr, "This is the input stdin .json format:")
	b, _ := json.MarshalIndent(input, "", "  ")
	fmt.Fprintln(os.Stderr, string(b))
	fmt.Fprintln(os.Stderr, "")

	switch os.Args[1] {
	case "check":
		check(input)
	case "in":
		in(input)
	case "out":
		out(input)
	}

}
<commit_msg>check does not have a $PWD<commit_after>package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
)

type (
	version struct {
		Ref string `json:"ref"`
	}
	inputJSON struct {
		Params  map[string]string `json:"params"`
		Source  map[string]string `json:"source"`
		Version version           `json:"version"`
	}
	metadata struct {
		Name  string `json:"name"`
		Value string `json:"value"`
	}
	checkoutputJSON []version
	inoutputJSON    struct {
		Version  version    `json:"version"`
		Metadata []metadata `json:"metadata"`
	}
	outoutputJSON struct {
		Version  version    `json:"version"`
		Metadata []metadata `json:"metadata"`
	}
)

func check(input inputJSON) {

	// PARSE THE JSON FILE /tmp/input.json
	var source1 = input.Source["source1"]
	var source2 = input.Source["source2"]
	var ref = input.Version.Ref
	fmt.Fprintln(os.Stderr, "source are")
	fmt.Fprintln(os.Stderr, source1, source2)
	fmt.Fprintln(os.Stderr, "")
	fmt.Fprintln(os.Stderr, "ref is")
	fmt.Fprintln(os.Stderr, ref)
	fmt.Fprintln(os.Stderr, "")

	// CHECK (THE RESOURCE VERSION(s)) *******************************************************************
	// Mimic a fetch and output the following versions for IN.

	ver1 := "123"
	ver2 := "3de"
	ver3 := "456"

	// OUTPUT **************************************************************************************
	output := &checkoutputJSON{
		{Ref: ver1},
		{Ref: ver2},
		{Ref: ver3},
	}

	fmt.Fprintln(os.Stderr, ".json output is:")
	b, _ := json.MarshalIndent(output, "", "  ")
	fmt.Fprintln(os.Stderr, string(b))
	fmt.Fprintln(os.Stderr, "")

	// Encode .json and send to stdout
	json.NewEncoder(os.Stdout).Encode(&output)

}

func in(input inputJSON) {

	// PARSE THE JSON FILE /tmp/input.json
	var source1 = input.Source["source1"]
	var source2 = input.Source["source2"]
	var param1 = input.Params["param1"]
	var param2 = input.Params["param2"]
	var ref = input.Version.Ref
	fmt.Fprintln(os.Stderr, "source are")
	fmt.Fprintln(os.Stderr, source1, source2)
	fmt.Fprintln(os.Stderr, "")
	fmt.Fprintln(os.Stderr, "params are")
	fmt.Fprintln(os.Stderr, param1, param2)
	fmt.Fprintln(os.Stderr, "")
	fmt.Fprintln(os.Stderr, "ref is")
	fmt.Fprintln(os.Stderr, ref)
	fmt.Fprintln(os.Stderr, "")

	// IN (FETCH THE RESOURCE) *************************************************************************
	// Mimic a fetch and place a fetched.json file in the working directory that contains the following.

	jsonfile := "This is a file I made"

	// Create a fake fetched file
	filewrite, err := os.Create("fetch.json")
	if err != nil {
		log.Fatal("Cannot create file", err)
	}
	defer filewrite.Close()
	fmt.Fprintf(filewrite, jsonfile)

	//ls -lat $WORKING_DIR
	files, _ := ioutil.ReadDir("./")
	for _, f := range files {
		fmt.Fprintln(os.Stderr, f.Name())
	}
	fmt.Fprintln(os.Stderr, "")

	// Cat the file
	fmt.Fprintln(os.Stderr, "Cat fetch.json")
	file, err := os.Open("fetch.json")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()
	bb, err := ioutil.ReadAll(file)
	fmt.Print(string(bb))
	fmt.Fprintln(os.Stderr, "")
	fmt.Fprintln(os.Stderr, "")

	var monkeyname = "Larry"

	// OUTPUT **************************************************************************************
	output := &inoutputJSON{
		Version: version{Ref: ref},
		Metadata: []metadata{
			{Name: "nameofmonkey", Value: monkeyname},
			{Name: "author", Value: "Jeff DeCola"},
		},
	}

	fmt.Fprintln(os.Stderr, ".json output is:")
	b, _ := json.MarshalIndent(output, "", "  ")
	fmt.Fprintln(os.Stderr, string(b))
	fmt.Fprintln(os.Stderr, "")

	// Encode .json and send to stdout
	json.NewEncoder(os.Stdout).Encode(&output)

}

func out(input inputJSON) {

	// PARSE THE JSON FILE /tmp/input.json
	var param1 = input.Params["param1"]
	var param2 = input.Params["param2"]
	var source1 = input.Source["source1"]
	var source2 = input.Source["source2"]
	fmt.Fprintln(os.Stderr, "params are")
	fmt.Fprintln(os.Stderr, param1, param2)
	fmt.Fprintln(os.Stderr, "")
	fmt.Fprintln(os.Stderr, "source are")
	fmt.Fprintln(os.Stderr, source1, source2)
	fmt.Fprintln(os.Stderr, "")

	// OUT (UPDATE THE RESOURCE) *************************************************************************
	// Mimic an out.

	var monkeyname = "Henry"
	var ref = "123"

	// OUTPUT **************************************************************************************
	output := &outoutputJSON{
		Version: version{Ref: ref},
		Metadata: []metadata{
			{Name: "nameofmonkey", Value: monkeyname},
			{Name: "author", Value: "Jeff DeCola"},
		},
	}

	fmt.Fprintln(os.Stderr, ".json output is:")
	b, _ := json.MarshalIndent(output, "", "  ")
	fmt.Fprintln(os.Stderr, string(b))
	fmt.Fprintln(os.Stderr, "")

	// Encode .json and send to stdout
	json.NewEncoder(os.Stdout).Encode(&output)

}

func main() {

	// Decode the .json from stdin and place in a .json struct.
	var input inputJSON
	var decoder = json.NewDecoder(os.Stdin)

	err := decoder.Decode(&input)

	// Test if error reading stdin .json format
	if err != nil {
		panic("Failed to decode stdin")
	}

	switch os.Args[1] {
	case "check":
		fmt.Fprintln(os.Stderr, "CHECK (THE RESOURCE VERSION(s))")
		fmt.Fprintln(os.Stderr, "")
	case "in":
		fmt.Fprintln(os.Stderr, "IN (FETCH THE RESOURCE)")
		fmt.Fprintln(os.Stderr, "")
	case "out":
		fmt.Fprintln(os.Stderr, "OUT (UPDATE THE RESOURCE)")
		fmt.Fprintln(os.Stderr, "")
	}

	// Get the working directory from arg $2 (Only for IN and OUT)
	if os.Args[1] != "check" {
		var workingdir = os.Args[2]
		fmt.Fprintln(os.Stderr, "WORKING_DIR = ", workingdir)
		fmt.Fprintln(os.Stderr, "List whats in the working directory")
		//ls -lat $WORKING_DIR
		files, _ := ioutil.ReadDir("./")
		for _, f := range files {
			fmt.Fprintln(os.Stderr, f.Name())
		}
		fmt.Fprintln(os.Stderr, "")
	}

	// List whats in the input stdin .json
	// MashalIndent makes it print nicely
	fmt.Fprintln(os.Stderr, "This is the input stdin .json format:")
	b, _ := json.MarshalIndent(input, "", "  ")
	fmt.Fprintln(os.Stderr, string(b))
	fmt.Fprintln(os.Stderr, "")

	switch os.Args[1] {
	case "check":
		check(input)
	case "in":
		in(input)
	case "out":
		out(input)
	}

}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/codegangsta/negroni"
	"github.com/davecgh/go-spew/spew"
	"github.com/gorilla/mux"
	logging "github.com/op/go-logging"
	"github.com/unrolled/render"
)

type GithubPushEventPayload struct {
	Hook struct {
		Config struct {
			Secret string `json:"secret"`
		} `json:"config"`
	} `json:"hook"`
	Repository struct {
		FullName string `json:"full_name"`
		HtmlUrl  string `json:"html_url"`
	} `json:"repository"`
}

var log = logging.MustGetLogger("streamLog")
var format = "%{color}%{time:15:04:05} => %{color:reset} %{message}"

func streamCommand(name string, args ...string) error {
	cmd := exec.Command(name, args...)
	cmdReader, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}
	scanner := bufio.NewScanner(cmdReader)
	go func() {
		for scanner.Scan() {
			log.Notice(scanner.Text())
		}
	}()
	err = cmd.Start()
	if err != nil {
		return err
	}
	err = cmd.Wait()
	if err != nil {
		return err
	}
	return nil
}

func main() {
	logBackend := logging.NewLogBackend(os.Stderr, "", 0)
	logging.SetBackend(logBackend)
	logging.SetFormatter(logging.MustStringFormatter(format))
	logging.SetLevel(logging.NOTICE, "streamLog")
	homeDir := os.Getenv("HOME")

	app := cli.NewApp()
	app.Name = "tarzan"
	app.Usage = "naive cached automated build implementation"
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "p,port",
			Value: "3000",
			Usage: "port to serve tarzan on",
		},
		cli.StringFlag{
			Name:  "alt-registry",
			Value: "",
			Usage: "alternative registry to push images to instead of Docker Hub",
		},
		cli.StringFlag{
			Name:  "secret",
			Value: "",
			Usage: "secret to use when receiving webhook payload",
		},
		cli.StringFlag{
			Name:  "hub-name",
			Value: "",
			Usage: "specify a username on Docker Hub which is different than your Github handle",
		},
	}

	app.Action = func(c *cli.Context) {
		if _, err := os.Stat(fmt.Sprintf("%s/.dockercfg", homeDir)); err != nil {
			if os.IsNotExist(err) {
				log.Warning("Detected no Docker Hub login.  Please log in now.")
				cmd := exec.Command("docker", "login")
				cmd.Stdin = os.Stdin
				cmd.Stdout = os.Stdout
				cmd.Stderr = os.Stderr
				if err := cmd.Run(); err != nil {
					fmt.Fprintln(os.Stderr, "Error running docker login")
					os.Exit(1)
				}
			}
		}
		r := render.New(render.Options{})
		router := mux.NewRouter()
		router.HandleFunc("/build", func(w http.ResponseWriter, req *http.Request) {
			var (
				payload GithubPushEventPayload
			)
			decoder := json.NewDecoder(req.Body)
			err := decoder.Decode(&payload)
			if err != nil {
				fmt.Fprintln(os.Stderr, "Error decoding Github push payload:", err)
			}
			spew.Dump(payload)
			if c.String("secret") == "" || payload.Hook.Config.Secret == c.String("secret") {
				githubFullName := payload.Repository.FullName
				repoPath := fmt.Sprintf("./repos/%s", githubFullName)
				repoUrl := payload.Repository.HtmlUrl
				if _, err := os.Stat(repoPath); err != nil {
					if os.IsNotExist(err) {
						fmt.Println("Executing command", "git clone --recursive", repoUrl, repoPath)
						if err := exec.Command("git", "clone", "--recursive", repoUrl, repoPath).Run(); err != nil {
							fmt.Fprintln(os.Stderr, "Error cloning git repository:", err)
						}
					} else {
						fmt.Fprintln(os.Stderr, "Error stat-ing directory", repoPath, ":", err)
					}
					os.Chdir(repoPath)
				} else {
					os.Chdir(repoPath)
					fmt.Println("Pulling existing repository")
					if err := exec.Command("git", "pull").Run(); err != nil {
						fmt.Fprintln(os.Stderr, "Error pulling git repository:", err)
						r.JSON(w, http.StatusInternalServerError, "")
					}
				}

				namespacedImage := ""
				splitImage := strings.Split(githubFullName, "/")
				imageBase := splitImage[len(splitImage)-1]
				if c.String("hub-name") == "" {
					if c.String("alt-registry") == "" {
						namespacedImage = githubFullName
					} else {
						namespacedImage = fmt.Sprintf("%s/%s", c.String("alt-registry"), imageBase)
					}
				} else {
					namespacedImage = fmt.Sprintf("%s/%s", c.String("hub-name"), imageBase)
				}

				fmt.Println("Building docker image")
				err := streamCommand("docker", "build", "-t", namespacedImage, ".")
				if err != nil {
					fmt.Fprintln(os.Stderr, "Error building docker image for", namespacedImage, ":", err)
					r.JSON(w, http.StatusInternalServerError, map[string]interface{}{
						"Error": err,
					})
				}

				registryName := ""
				if c.String("alt-registry") != "" {
					registryName = c.String("alt-registry")
				} else {
					registryName = "Docker Hub"
				}
				fmt.Println(fmt.Sprintf("Pushing image back to specified registry (%s)...", registryName))
				err = streamCommand("docker", "push", namespacedImage)
				if err != nil {
					fmt.Fprintln(os.Stderr, "Error pushing docker image for", namespacedImage, ":", err)
					r.JSON(w, http.StatusInternalServerError, map[string]interface{}{
						"Error": err,
					})
				}
			} else {
				r.JSON(w, http.StatusInternalServerError, map[string]interface{}{
					"Error": "Secret from payload was invalid",
				})
			}
			r.JSON(w, http.StatusOK, "")
		}).Methods("POST")

		n := negroni.Classic()
		n.UseHandler(router)
		n.Run(fmt.Sprintf(":%s", c.String("port")))

	}

	app.Run(os.Args)
}
<commit_msg>Add --docker-binary-name flag<commit_after>package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/codegangsta/negroni"
	"github.com/davecgh/go-spew/spew"
	"github.com/gorilla/mux"
	logging "github.com/op/go-logging"
	"github.com/unrolled/render"
)

type GithubPushEventPayload struct {
	Hook struct {
		Config struct {
			Secret string `json:"secret"`
		} `json:"config"`
	} `json:"hook"`
	Repository struct {
		FullName string `json:"full_name"`
		HtmlUrl  string `json:"html_url"`
	} `json:"repository"`
}

var log = logging.MustGetLogger("streamLog")
var format = "%{color}%{time:15:04:05} => %{color:reset} %{message}"

func streamCommand(name string, args ...string) error {
	cmd := exec.Command(name, args...)
	cmdReader, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}
	scanner := bufio.NewScanner(cmdReader)
	go func() {
		for scanner.Scan() {
			log.Notice(scanner.Text())
		}
	}()
	err = cmd.Start()
	if err != nil {
		return err
	}
	err = cmd.Wait()
	if err != nil {
		return err
	}
	return nil
}

func main() {
	logBackend := logging.NewLogBackend(os.Stderr, "", 0)
	logging.SetBackend(logBackend)
	logging.SetFormatter(logging.MustStringFormatter(format))
	logging.SetLevel(logging.NOTICE, "streamLog")
	homeDir := os.Getenv("HOME")

	app := cli.NewApp()
	app.Name = "tarzan"
	app.Usage = "naive cached automated build implementation"
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "p,port",
			Value: "3000",
			Usage: "port to serve tarzan on",
		},
		cli.StringFlag{
			Name:  "alt-registry",
			Value: "",
			Usage: "alternative registry to push images to instead of Docker Hub",
		},
		cli.StringFlag{
			Name:  "secret",
			Value: "",
			Usage: "secret to use when receiving webhook payload",
		},
		cli.StringFlag{
			Name:  "hub-name",
			Value: "",
			Usage: "specify a username on Docker Hub which is different than your Github handle",
		},
		cli.StringFlag{
			Name:  "docker-binary-name",
			Value: "docker",
			Usage: "specify the docker binary name (if it is not docker in $PATH)",
		},
	}

	app.Action = func(c *cli.Context) {
		dockerBinary := c.String("docker-binary-name")
		if _, err := os.Stat(fmt.Sprintf("%s/.dockercfg", homeDir)); err != nil {
			if os.IsNotExist(err) {
				log.Warning("Detected no Docker Hub login.  Please log in now.")
				cmd := exec.Command(dockerBinary, "login")
				cmd.Stdin = os.Stdin
				cmd.Stdout = os.Stdout
				cmd.Stderr = os.Stderr
				if err := cmd.Run(); err != nil {
					fmt.Fprintln(os.Stderr, "Error running docker login")
					os.Exit(1)
				}
			}
		}
		r := render.New(render.Options{})
		router := mux.NewRouter()
		router.HandleFunc("/build", func(w http.ResponseWriter, req *http.Request) {
			var (
				payload GithubPushEventPayload
			)
			decoder := json.NewDecoder(req.Body)
			err := decoder.Decode(&payload)
			if err != nil {
				fmt.Fprintln(os.Stderr, "Error decoding Github push payload:", err)
			}
			spew.Dump(payload)
			if c.String("secret") == "" || payload.Hook.Config.Secret == c.String("secret") {
				githubFullName := payload.Repository.FullName
				repoPath := fmt.Sprintf("./repos/%s", githubFullName)
				repoUrl := payload.Repository.HtmlUrl
				if _, err := os.Stat(repoPath); err != nil {
					if os.IsNotExist(err) {
						fmt.Println("Executing command", "git clone --recursive", repoUrl, repoPath)
						if err := exec.Command("git", "clone", "--recursive", repoUrl, repoPath).Run(); err != nil {
							fmt.Fprintln(os.Stderr, "Error cloning git repository:", err)
						}
					} else {
						fmt.Fprintln(os.Stderr, "Error stat-ing directory", repoPath, ":", err)
					}
					os.Chdir(repoPath)
				} else {
					os.Chdir(repoPath)
					fmt.Println("Pulling existing repository")
					if err := exec.Command("git", "pull").Run(); err != nil {
						fmt.Fprintln(os.Stderr, "Error pulling git repository:", err)
						r.JSON(w, http.StatusInternalServerError, "")
					}
				}

				namespacedImage := ""
				splitImage := strings.Split(githubFullName, "/")
				imageBase := splitImage[len(splitImage)-1]
				if c.String("hub-name") == "" {
					if c.String("alt-registry") == "" {
						namespacedImage = githubFullName
					} else {
						namespacedImage = fmt.Sprintf("%s/%s", c.String("alt-registry"), imageBase)
					}
				} else {
					namespacedImage = fmt.Sprintf("%s/%s", c.String("hub-name"), imageBase)
				}

				fmt.Println("Building docker image")
				err := streamCommand(dockerBinary, "build", "-t", namespacedImage, ".")
				if err != nil {
					fmt.Fprintln(os.Stderr, "Error building docker image for", namespacedImage, ":", err)
					r.JSON(w, http.StatusInternalServerError, map[string]interface{}{
						"Error": err,
					})
				}

				registryName := ""
				if c.String("alt-registry") != "" {
					registryName = c.String("alt-registry")
				} else {
					registryName = "Docker Hub"
				}
				fmt.Println(fmt.Sprintf("Pushing image back to specified registry (%s)...", registryName))
				err = streamCommand(dockerBinary, "push", namespacedImage)
				if err != nil {
					fmt.Fprintln(os.Stderr, "Error pushing docker image for", namespacedImage, ":", err)
					r.JSON(w, http.StatusInternalServerError, map[string]interface{}{
						"Error": err,
					})
				}
			} else {
				r.JSON(w, http.StatusInternalServerError, map[string]interface{}{
					"Error": "Secret from payload was invalid",
				})
			}
			r.JSON(w, http.StatusOK, "")
		}).Methods("POST")

		n := negroni.Classic()
		n.UseHandler(router)
		n.Run(fmt.Sprintf(":%s", c.String("port")))

	}

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


<commit_before>package redisence

import (
	"fmt"
	"strconv"
	"sync"
	"time"

	gredis "github.com/garyburd/redigo/redis"
	"github.com/koding/redis"
)

type Status int

const (
	Offline Status = iota
	Online
	Closed
)

// Event is the data type for
// occuring events in the system
type Event struct {
	// Id is the given key by the application
	Id string

	// Status holds the changing type of event
	Status Status
}

// Prefix for redisence package
const RedisencePrefix = "redisence"

// Session holds the required connection data for redis
type Session struct {
	// main redis connection
	redis *redis.RedisSession

	// inactiveDuration specifies no-probe allowance time
	inactiveDuration time.Duration

	// receiving offline events pattern
	becameOfflinePattern string

	// receiving online events pattern
	becameOnlinePattern string
}

func New(server string, db int, inactiveDuration time.Duration) (*Session, error) {
	redis, err := redis.NewRedisSession(&redis.RedisConf{Server: server, DB: db})
	if err != nil {
		return nil, err
	}
	redis.SetPrefix(RedisencePrefix)

	return &Session{
		redis:                redis,
		becameOfflinePattern: fmt.Sprintf("__keyevent@%d__:expired", db),
		becameOnlinePattern:  fmt.Sprintf("__keyevent@%d__:set", db),
		inactiveDuration:     inactiveDuration,
	}, nil
}

// Ping resets the expiration time for any given key
// if key doesnt exists, it means user is now online
// Whenever application gets any prob from a client
// should call this function
func (s *Session) Ping(ids ...string) error {
	if len(ids) == 1 {
		// if member exits increase ttl
		if s.redis.Expire(ids[0], s.inactiveDuration) == nil {
			return nil
		}

		// if member doesnt exist set it
		return s.redis.Setex(ids[0], s.inactiveDuration, ids[0])
	}

	existance, err := s.sendMultiExpire(ids)
	if err != nil {
		return err
	}

	return s.sendMultiSetIfRequired(ids, existance)
}

func (s *Session) sendMultiSetIfRequired(ids []string, existance []int) error {
	if len(ids) != len(existance) {
		return fmt.Errorf("Length is not same Ids: %d Existance: %d", len(ids), len(existance))
	}

	// cache inactive duration as string
	seconds := strconv.Itoa(int(s.inactiveDuration.Seconds()))

	// get one connection from pool
	c := s.redis.Pool().Get()

	// item count for non-existent members
	notExistsCount := 0

	for i, exists := range existance {
		// `0` means, member doesnt exists in presence system
		if exists != 0 {
			continue
		}

		// init multi command lazily
		if notExistsCount == 0 {
			c.Send("MULTI")
		}

		notExistsCount++
		c.Send("SETEX", s.redis.AddPrefix(ids[i]), seconds, ids[i])
	}

	// execute multi command if only we flushed some to connection
	if notExistsCount != 0 {
		// ignore values
		if _, err := c.Do("EXEC"); err != nil {
			return err
		}
	}

	// do not forget to close the connection
	if err := c.Close(); err != nil {
		return err
	}

	return nil
}

func (s *Session) sendMultiExpire(ids []string) ([]int, error) {
	// cache inactive duration as string
	seconds := strconv.Itoa(int(s.inactiveDuration.Seconds()))

	// get one connection from pool
	c := s.redis.Pool().Get()

	// init multi command
	c.Send("MULTI")

	// send expire command for all members
	for _, id := range ids {
		c.Send("EXPIRE", s.redis.AddPrefix(id), seconds)
	}

	// execute command
	r, err := c.Do("EXEC")
	if err != nil {
		return make([]int, 0), err
	}

	// close connection
	if err := c.Close(); err != nil {
		return make([]int, 0), err
	}

	values, err := s.redis.Values(r)
	if err != nil {
		return make([]int, 0), err
	}

	res := make([]int, len(values))
	for i, value := range values {
		res[i], err = s.redis.Int(value)
		if err != nil {
			return make([]int, 0), err
		}

	}

	return res, nil
}

// Status returns the current status a key from system
// TODO use variadic function arguments
func (s *Session) Status(id string) Status {
	// to-do use MGET instead of exists
	if s.redis.Exists(id) {
		return Online
	}

	return Offline
}

// createEvent Creates the event with the required properties
func (s *Session) createEvent(n gredis.PMessage) Event {
	e := Event{}

	switch n.Pattern {
	case s.becameOfflinePattern:
		e.Id = string(n.Data[len(RedisencePrefix)+1:])
		e.Status = Offline
	case s.becameOnlinePattern:
		e.Id = string(n.Data[len(RedisencePrefix)+1:])
		e.Status = Online
	default:
		//ignore other events
	}

	return e
}

// ListenStatusChanges pubscribes to the redis and
// gets online and offline status changes from it
func (s *Session) ListenStatusChanges(events chan Event) {
	psc := s.redis.CreatePubSubConn()

	psc.PSubscribe(s.becameOnlinePattern, s.becameOfflinePattern)

	var wg sync.WaitGroup
	wg.Add(1)

	go func() {
		defer wg.Done()
		for {
			switch n := psc.Receive().(type) {
			case gredis.PMessage:
				events <- s.createEvent(n)
			case error:
				fmt.Printf("error: %v\n", n)
				return
			}
		}
	}()

	// avoid lock
	go func() {
		wg.Wait()
		psc.PUnsubscribe(s.becameOfflinePattern, s.becameOnlinePattern)
		psc.Close()
		events <- Event{Status: Closed}
	}()
}
<commit_msg>Redisence: implement variadic function for `status`function<commit_after>package redisence

import (
	"fmt"
	"strconv"
	"sync"
	"time"

	gredis "github.com/garyburd/redigo/redis"
	"github.com/koding/redis"
)

type Status int

const (
	Offline Status = iota
	Online
	Closed
)

// Event is the data type for
// occuring events in the system
type Event struct {
	// Id is the given key by the application
	Id string

	// Status holds the changing type of event
	Status Status
}

// Prefix for redisence package
const RedisencePrefix = "redisence"

// Session holds the required connection data for redis
type Session struct {
	// main redis connection
	redis *redis.RedisSession

	// inactiveDuration specifies no-probe allowance time
	inactiveDuration time.Duration

	// receiving offline events pattern
	becameOfflinePattern string

	// receiving online events pattern
	becameOnlinePattern string
}

func New(server string, db int, inactiveDuration time.Duration) (*Session, error) {
	redis, err := redis.NewRedisSession(&redis.RedisConf{Server: server, DB: db})
	if err != nil {
		return nil, err
	}
	redis.SetPrefix(RedisencePrefix)

	return &Session{
		redis:                redis,
		becameOfflinePattern: fmt.Sprintf("__keyevent@%d__:expired", db),
		becameOnlinePattern:  fmt.Sprintf("__keyevent@%d__:set", db),
		inactiveDuration:     inactiveDuration,
	}, nil
}

// Ping resets the expiration time for any given key
// if key doesnt exists, it means user is now online
// Whenever application gets any prob from a client
// should call this function
func (s *Session) Ping(ids ...string) error {
	if len(ids) == 1 {
		// if member exits increase ttl
		if s.redis.Expire(ids[0], s.inactiveDuration) == nil {
			return nil
		}

		// if member doesnt exist set it
		return s.redis.Setex(ids[0], s.inactiveDuration, ids[0])
	}

	existance, err := s.sendMultiExpire(ids)
	if err != nil {
		return err
	}

	return s.sendMultiSetIfRequired(ids, existance)
}

func (s *Session) sendMultiSetIfRequired(ids []string, existance []int) error {
	if len(ids) != len(existance) {
		return fmt.Errorf("Length is not same Ids: %d Existance: %d", len(ids), len(existance))
	}

	// cache inactive duration as string
	seconds := strconv.Itoa(int(s.inactiveDuration.Seconds()))

	// get one connection from pool
	c := s.redis.Pool().Get()

	// item count for non-existent members
	notExistsCount := 0

	for i, exists := range existance {
		// `0` means, member doesnt exists in presence system
		if exists != 0 {
			continue
		}

		// init multi command lazily
		if notExistsCount == 0 {
			c.Send("MULTI")
		}

		notExistsCount++
		c.Send("SETEX", s.redis.AddPrefix(ids[i]), seconds, ids[i])
	}

	// execute multi command if only we flushed some to connection
	if notExistsCount != 0 {
		// ignore values
		if _, err := c.Do("EXEC"); err != nil {
			return err
		}
	}

	// do not forget to close the connection
	if err := c.Close(); err != nil {
		return err
	}

	return nil
}

func (s *Session) sendMultiExpire(ids []string) ([]int, error) {
	// cache inactive duration as string
	seconds := strconv.Itoa(int(s.inactiveDuration.Seconds()))

	// get one connection from pool
	c := s.redis.Pool().Get()

	// init multi command
	c.Send("MULTI")

	// send expire command for all members
	for _, id := range ids {
		c.Send("EXPIRE", s.redis.AddPrefix(id), seconds)
	}

	// execute command
	r, err := c.Do("EXEC")
	if err != nil {
		return make([]int, 0), err
	}

	// close connection
	if err := c.Close(); err != nil {
		return make([]int, 0), err
	}

	values, err := s.redis.Values(r)
	if err != nil {
		return make([]int, 0), err
	}

	res := make([]int, len(values))
	for i, value := range values {
		res[i], err = s.redis.Int(value)
		if err != nil {
			return make([]int, 0), err
		}

	}

	return res, nil
}

// Status returns the current status a key from system
func (s *Session) Status(ids ...string) ([]Event, error) {
	// get one connection from pool
	c := s.redis.Pool().Get()

	// init multi command
	c.Send("MULTI")

	// send expire command for all members
	for _, id := range ids {
		c.Send("EXISTS", s.redis.AddPrefix(id))
	}

	// execute command
	r, err := c.Do("EXEC")
	if err != nil {
		return make([]Event, 0), err
	}

	// close connection
	if err := c.Close(); err != nil {
		return make([]Event, 0), err
	}

	values, err := s.redis.Values(r)
	if err != nil {
		return make([]Event, 0), err
	}

	res := make([]Event, len(values))
	for i, value := range values {
		status, err := s.redis.Int(value)
		if err != nil {
			return make([]Event, 0), err
		}

		e := Event{
			Id:     ids[i],
			Status: Status(status),
		}

		// if status == 0 {
		// 	e.Status = Offline
		// } else {
		// 	e.Status = Online
		// }
		res[i] = e
	}

	return res, nil
}

// createEvent Creates the event with the required properties
func (s *Session) createEvent(n gredis.PMessage) Event {
	e := Event{}

	switch n.Pattern {
	case s.becameOfflinePattern:
		e.Id = string(n.Data[len(RedisencePrefix)+1:])
		e.Status = Offline
	case s.becameOnlinePattern:
		e.Id = string(n.Data[len(RedisencePrefix)+1:])
		e.Status = Online
	default:
		//ignore other events
	}

	return e
}

// ListenStatusChanges pubscribes to the redis and
// gets online and offline status changes from it
func (s *Session) ListenStatusChanges(events chan Event) {
	psc := s.redis.CreatePubSubConn()

	psc.PSubscribe(s.becameOnlinePattern, s.becameOfflinePattern)

	var wg sync.WaitGroup
	wg.Add(1)

	go func() {
		defer wg.Done()
		for {
			switch n := psc.Receive().(type) {
			case gredis.PMessage:
				events <- s.createEvent(n)
			case error:
				fmt.Printf("error: %v\n", n)
				return
			}
		}
	}()

	// avoid lock
	go func() {
		wg.Wait()
		psc.PUnsubscribe(s.becameOfflinePattern, s.becameOnlinePattern)
		psc.Close()
		events <- Event{Status: Closed}
	}()
}
<|endoftext|>