<commit_before>// Main package wraps sprite_sass tool for use with the command line
// See -h for list of available options
package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"runtime/pprof"
	"strings"
	"sync"
	"time"

	"github.com/fatih/color"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	libsass "github.com/wellington/go-libsass"
	"github.com/wellington/wellington/version"

	wt "github.com/wellington/wellington"
	_ "github.com/wellington/wellington/handlers"
)

var (
	proj                          string
	includes                      []string
	font, dir, gen                string
	mainFile, style               string
	comments, watch               bool
	cpuprofile, buildDir          string
	jsDir                         string
	ishttp, showHelp, showVersion bool
	httpPath                      string
	timeB                         bool
	config                        string
	debug                         bool
	cachebust                     string

	// unused
	relativeAssets bool
	cssDir         string
)

/*
   --app APP                    Tell compass what kind of application it is integrating with. E.g. rails
   --fonts-dir FONTS_DIR        The directory where you keep your fonts.
*/
func init() {

	// Interoperability args
}

func flags(set *pflag.FlagSet) {
	// Unused cli args
	set.StringVarP(&buildDir, "build", "b", "",
		"Path to target directory to place generated CSS, relative paths inside project directory are preserved")
	set.BoolVarP(&comments, "comment", "", false, "Turn on source comments")
	set.BoolVar(&debug, "debug", false, "Show detailed debug information")

	var nothingb bool
	set.BoolVar(&debug, "debug-info", false, "")
	set.MarkDeprecated("debug-info", "Use --debug instead")

	set.StringVarP(&dir, "dir", "d", "",
		"Path to locate images for spriting and image functions")
	set.StringVar(&dir, "images-dir", "", "")
	set.MarkDeprecated("images-dir", "Use -d instead")

	set.StringVar(&font, "font", ".", "Path to directory containing fonts")
	set.StringVar(&gen, "generated-images-path", "", "")
	set.MarkDeprecated("generated-images-path", "Use --gen instead")
	set.StringVar(&gen, "gen", ".", "Path to place generated images")

	set.StringVarP(&proj, "proj", "p", "",
		"Path to directory containing Sass stylesheets")
	set.BoolVar(&nothingb, "no-line-comments", false, "UNSUPPORTED: Disable line comments, use comments")
	set.MarkDeprecated("no-line-comments", "Use --comments instead")
	set.BoolVar(&relativeAssets, "relative-assets", false, "UNSUPPORTED: Make compass asset helpers generate relative urls to assets.")

	set.BoolVarP(&showVersion, "version", "v", false, "Show the app version")
	set.StringVar(&cachebust, "cachebust", "", "Defeat cache by appending timestamps to static assets ie. ts, sum, timestamp")
	set.StringVarP(&style, "style", "s", "nested",
		`nested style of output CSS
                        available options: nested, expanded, compact, compressed`)
	set.StringVar(&style, "output-style", "nested", "")
	set.MarkDeprecated("output-style", "Use --style instead")
	set.BoolVar(&timeB, "time", false, "Retrieve timing information")

	var nothing string
	set.StringVar(&nothing, "require", "", "")
	set.MarkDeprecated("require", "Compass backwards compat, Not supported")
	set.MarkDeprecated("require", "Not supported")
	set.StringVar(&nothing, "environment", "", "")
	set.MarkDeprecated("environment", "Not supported")
	set.StringSliceVar(&includes, "includes", nil, "Include Sass from additional directories")
	set.StringSliceVarP(&includes, "", "I", nil, "")
	set.MarkDeprecated("I", "Compass backwards compat, use --includes instead")
	set.StringVar(&buildDir, "css-dir", "",
		"Compass backwards compat. Reference locations relative to Sass project directory")
	set.MarkDeprecated("css-dir", "Use -b instead")
	set.StringVar(&jsDir, "javascripts-dir", "", "")
	set.MarkDeprecated("javascripts-dir", "Compass backwards compat, ignored")
	set.StringSliceVar(&includes, "sass-dir", nil,
		"Compass backwards compat, use --includes instead")
	set.StringVarP(&config, "config", "c", "",
		"Temporarily disabled: Location of the config file")

	set.StringVar(&cpuprofile, "cpuprofile", "", "Go runtime cpu profilling for debugging")
}

var compileCmd = &cobra.Command{
	Use:   "compile",
	Short: "Compile Sass stylesheets to CSS",
	Long: `Fast compilation of Sass stylesheets to CSS. For usage consult
the documentation at https://github.com/wellington/wellington#wellington`,
	Run: Compile,
}

var watchCmd = &cobra.Command{
	Use:   "watch",
	Short: "Watch Sass files for changes and rebuild CSS",
	Long:  ``,
	Run:   Watch,
}

var httpCmd = &cobra.Command{
	Use:   "serve",
	Short: "Starts a http server that will convert Sass to CSS",
	Long:  ``,
	Run:   Serve,
}

func init() {
	hostname := os.Getenv("HOSTNAME")
	if len(hostname) > 0 {
		if !strings.HasPrefix(hostname, "http") {
			hostname = "http://" + hostname
		}
	} else if host, err := os.Hostname(); err == nil {
		hostname = "http://" + host
	}
	httpCmd.Flags().StringVar(&httpPath, "httppath", hostname,
		"Only for HTTP, overrides generated sprite paths to support http")

}

func root() {
	flags(wtCmd.PersistentFlags())
}

// AddCommands attaches the cli subcommands ie. http, compile to the
// main cli entrypoint.
func AddCommands() {
	wtCmd.AddCommand(httpCmd)
	wtCmd.AddCommand(compileCmd)
	wtCmd.AddCommand(watchCmd)
}

var wtCmd = &cobra.Command{
	Use:   "wt",
	Short: "wt is a Sass project tool made to handle large projects. It uses the libSass compiler for efficiency and speed.",
	Run:   Compile,
}

func main() {
	AddCommands()
	root()
	wtCmd.Execute()
}

func argExit() bool {

	if showVersion {
		fmt.Printf("   libsass: %s\n", libsass.Version())
		fmt.Printf("Wellington: %s\n", version.Version)
		return true
	}

	if showHelp {
		fmt.Println("Please specify input filepath.")
		fmt.Println("\nAvailable options:")
		//flag.PrintDefaults()
		return true
	}
	return false

}

func makeabs(wd string, path string) string {
	if filepath.IsAbs(path) {
		return path
	}
	return filepath.Join(wd, path)
}

func parseBuildArgs(paths []string) *wt.BuildArgs {
	style, ok := libsass.Style[style]

	if !ok {
		style = libsass.NESTED_STYLE
	}

	wd, err := os.Getwd()
	if err != nil {
		log.Fatal("could not find working directory", err)
	}

	proj = makeabs(wd, proj)

	incs := make([]string, len(includes))
	for i := range includes {
		incs[i] = makeabs(wd, includes[i])
	}

	dir = makeabs(wd, dir)
	font = makeabs(wd, font)
	if len(buildDir) > 0 {
		buildDir = makeabs(wd, buildDir)
		// If buildDir specified, make relative to that
		gen = makeabs(buildDir, gen)
	} else {
		gen = makeabs(wd, gen)
	}

	gba := &wt.BuildArgs{
		ImageDir:  dir,
		BuildDir:  buildDir,
		Includes:  append([]string{proj}, incs...),
		Font:      font,
		Style:     style,
		Gen:       gen,
		Comments:  comments,
		CacheBust: cachebust,
	}
	gba.WithPaths(paths)
	return gba
}

func globalRun(paths []string) (*wt.SafePartialMap, *wt.BuildArgs) {
	// fmt.Printf("paths: %s args: % #v\n", paths, pflag.Args())
	if argExit() {
		return nil, nil
	}

	// Profiling code
	if cpuprofile != "" {
		f, err := os.Create(cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		log.Println("Starting profiler")
		pprof.StartCPUProfile(f)
		defer func() {
			pprof.StopCPUProfile()
			err := f.Close()
			if err != nil {
				log.Fatal(err)
			}
			log.Println("Stopping Profiller")
		}()
	}

	for _, v := range paths {
		if strings.HasPrefix(v, "-") {
			log.Fatalf("Please specify flags before other arguments: %s", v)
		}
	}

	if gen != "" {
		err := os.MkdirAll(gen, 0755)
		if err != nil {
			log.Fatal(err)
		}
	}

	pMap := wt.NewPartialMap()
	gba := parseBuildArgs(paths)
	if debug {
		log.Printf("      Font  Dir: %s\n", gba.Font)
		log.Printf("      Image Dir: %s\n", gba.ImageDir)
		log.Printf("      Build Dir: %s\n", gba.BuildDir)
		log.Printf("Build Image Dir: %s\n", gba.Gen)
		log.Printf(" Include Dir(s): %s\n", gba.Includes)
		log.Println("===================================")
	}
	return pMap, gba

}

// Watch accepts a set of paths starting a recursive file watcher
func Watch(cmd *cobra.Command, paths []string) {
	pMap, gba := globalRun(paths)
	var err error
	bOpts := wt.NewBuild(paths, gba, pMap)
	err = bOpts.Run()
	if err != nil {
		log.Fatal(err)
	}
	w, err := wt.NewWatcher(&wt.WatchOptions{
		Paths:      paths,
		BArgs:      gba,
		PartialMap: pMap,
	})
	if err != nil {
		log.Fatal("failed to start watcher: ", err)
	}
	err = w.Watch()
	if err != nil {
		log.Fatal("filewatcher error: ", err)
	}

	fmt.Println("File watcher started use `ctrl+d` to exit")
	in := bufio.NewReader(os.Stdin)
	for {
		_, err := in.ReadString(' ')
		if err != nil {
			if err == io.EOF {
				os.Exit(0)
			}
			fmt.Println("error", err)
		}
	}
}

// lis is exposed so test suite can shut it down
var lis net.Listener

// Serve starts a web server accepting POST calls and return CSS
func Serve(cmd *cobra.Command, paths []string) {

	_, gba := globalRun(paths)
	if len(gba.Gen) == 0 {
		log.Fatal("Must pass an image build directory to use HTTP")
	}

	http.Handle("/build/", wt.FileHandler(gba.Gen))
	log.Println("Web server started on :12345")

	var err error
	lis, err = net.Listen("tcp", ":12345")
	if err != nil {
		log.Fatal("Error listening on :12345", err)
	}

	http.HandleFunc("/", wt.HTTPHandler(gba, httpPath))
	http.Serve(lis, nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
	log.Println("Server closed")
}

// Compile handles compile files and stdin operations.
func Compile(cmd *cobra.Command, paths []string) {
	start := time.Now()
	pMap, gba := globalRun(paths)
	if gba == nil {
		return
	}

	defer func() {
		log.Printf("Compilation took: %s\n", time.Since(start))
	}()

	run(paths, pMap, gba)
}

// Run is the main entrypoint for the cli.
func run(paths []string, pMap *wt.SafePartialMap, gba *wt.BuildArgs) {

	// No paths given, read from stdin and wait
	if len(paths) == 0 {

		fmt.Println("Reading from stdin, -h for help")
		out := os.Stdout
		in := os.Stdin
		comp, err := wt.FromBuildArgs(out, in, gba)
		if err != nil {
			log.Fatal(err)
		}
		err = comp.Run()
		if err != nil {
			color.Red(err.Error())
		}
		return
	}

	bOpts := wt.NewBuild(paths, gba, pMap)

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

	// FIXME: move this to a Payload.Close() method

	// Before shutting down, check that every sprite has been
	// flushed to disk.
	img := sync.WaitGroup{}
	pMap.RLock()
	// It's not currently possible to wait on Image. This is often
	// to inline images, so it shouldn't be a factor...
	// for _, s := range gba.Payload.Image().M {
	// 	img.Add(1)
	// 	err := s.Wait()
	// 	img.Done()
	// 	if err != nil {
	// 		log.Printf("error writing image: %s\n", err)
	// 	}
	// }
	for _, s := range gba.Payload.Sprite().M {
		img.Add(1)
		err := s.Wait()
		img.Done()
		if err != nil {
			log.Printf("error writing sprite: %s\n", err)
		}
	}
	img.Wait()
	pMap.RUnlock()

}
<commit_msg>bump this message to stderr<commit_after>// Main package wraps sprite_sass tool for use with the command line
// See -h for list of available options
package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"runtime/pprof"
	"strings"
	"sync"
	"time"

	"github.com/fatih/color"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	libsass "github.com/wellington/go-libsass"
	"github.com/wellington/wellington/version"

	wt "github.com/wellington/wellington"
	_ "github.com/wellington/wellington/handlers"
)

var (
	proj                          string
	includes                      []string
	font, dir, gen                string
	mainFile, style               string
	comments, watch               bool
	cpuprofile, buildDir          string
	jsDir                         string
	ishttp, showHelp, showVersion bool
	httpPath                      string
	timeB                         bool
	config                        string
	debug                         bool
	cachebust                     string

	// unused
	relativeAssets bool
	cssDir         string
)

/*
   --app APP                    Tell compass what kind of application it is integrating with. E.g. rails
   --fonts-dir FONTS_DIR        The directory where you keep your fonts.
*/
func init() {

	// Interoperability args
}

func flags(set *pflag.FlagSet) {
	// Unused cli args
	set.StringVarP(&buildDir, "build", "b", "",
		"Path to target directory to place generated CSS, relative paths inside project directory are preserved")
	set.BoolVarP(&comments, "comment", "", false, "Turn on source comments")
	set.BoolVar(&debug, "debug", false, "Show detailed debug information")

	var nothingb bool
	set.BoolVar(&debug, "debug-info", false, "")
	set.MarkDeprecated("debug-info", "Use --debug instead")

	set.StringVarP(&dir, "dir", "d", "",
		"Path to locate images for spriting and image functions")
	set.StringVar(&dir, "images-dir", "", "")
	set.MarkDeprecated("images-dir", "Use -d instead")

	set.StringVar(&font, "font", ".", "Path to directory containing fonts")
	set.StringVar(&gen, "generated-images-path", "", "")
	set.MarkDeprecated("generated-images-path", "Use --gen instead")
	set.StringVar(&gen, "gen", ".", "Path to place generated images")

	set.StringVarP(&proj, "proj", "p", "",
		"Path to directory containing Sass stylesheets")
	set.BoolVar(&nothingb, "no-line-comments", false, "UNSUPPORTED: Disable line comments, use comments")
	set.MarkDeprecated("no-line-comments", "Use --comments instead")
	set.BoolVar(&relativeAssets, "relative-assets", false, "UNSUPPORTED: Make compass asset helpers generate relative urls to assets.")

	set.BoolVarP(&showVersion, "version", "v", false, "Show the app version")
	set.StringVar(&cachebust, "cachebust", "", "Defeat cache by appending timestamps to static assets ie. ts, sum, timestamp")
	set.StringVarP(&style, "style", "s", "nested",
		`nested style of output CSS
                        available options: nested, expanded, compact, compressed`)
	set.StringVar(&style, "output-style", "nested", "")
	set.MarkDeprecated("output-style", "Use --style instead")
	set.BoolVar(&timeB, "time", false, "Retrieve timing information")

	var nothing string
	set.StringVar(&nothing, "require", "", "")
	set.MarkDeprecated("require", "Compass backwards compat, Not supported")
	set.MarkDeprecated("require", "Not supported")
	set.StringVar(&nothing, "environment", "", "")
	set.MarkDeprecated("environment", "Not supported")
	set.StringSliceVar(&includes, "includes", nil, "Include Sass from additional directories")
	set.StringSliceVarP(&includes, "", "I", nil, "")
	set.MarkDeprecated("I", "Compass backwards compat, use --includes instead")
	set.StringVar(&buildDir, "css-dir", "",
		"Compass backwards compat. Reference locations relative to Sass project directory")
	set.MarkDeprecated("css-dir", "Use -b instead")
	set.StringVar(&jsDir, "javascripts-dir", "", "")
	set.MarkDeprecated("javascripts-dir", "Compass backwards compat, ignored")
	set.StringSliceVar(&includes, "sass-dir", nil,
		"Compass backwards compat, use --includes instead")
	set.StringVarP(&config, "config", "c", "",
		"Temporarily disabled: Location of the config file")

	set.StringVar(&cpuprofile, "cpuprofile", "", "Go runtime cpu profilling for debugging")
}

var compileCmd = &cobra.Command{
	Use:   "compile",
	Short: "Compile Sass stylesheets to CSS",
	Long: `Fast compilation of Sass stylesheets to CSS. For usage consult
the documentation at https://github.com/wellington/wellington#wellington`,
	Run: Compile,
}

var watchCmd = &cobra.Command{
	Use:   "watch",
	Short: "Watch Sass files for changes and rebuild CSS",
	Long:  ``,
	Run:   Watch,
}

var httpCmd = &cobra.Command{
	Use:   "serve",
	Short: "Starts a http server that will convert Sass to CSS",
	Long:  ``,
	Run:   Serve,
}

func init() {
	hostname := os.Getenv("HOSTNAME")
	if len(hostname) > 0 {
		if !strings.HasPrefix(hostname, "http") {
			hostname = "http://" + hostname
		}
	} else if host, err := os.Hostname(); err == nil {
		hostname = "http://" + host
	}
	httpCmd.Flags().StringVar(&httpPath, "httppath", hostname,
		"Only for HTTP, overrides generated sprite paths to support http")

}

func root() {
	flags(wtCmd.PersistentFlags())
}

// AddCommands attaches the cli subcommands ie. http, compile to the
// main cli entrypoint.
func AddCommands() {
	wtCmd.AddCommand(httpCmd)
	wtCmd.AddCommand(compileCmd)
	wtCmd.AddCommand(watchCmd)
}

var wtCmd = &cobra.Command{
	Use:   "wt",
	Short: "wt is a Sass project tool made to handle large projects. It uses the libSass compiler for efficiency and speed.",
	Run:   Compile,
}

func main() {
	AddCommands()
	root()
	wtCmd.Execute()
}

func argExit() bool {

	if showVersion {
		fmt.Printf("   libsass: %s\n", libsass.Version())
		fmt.Printf("Wellington: %s\n", version.Version)
		return true
	}

	if showHelp {
		fmt.Println("Please specify input filepath.")
		fmt.Println("\nAvailable options:")
		//flag.PrintDefaults()
		return true
	}
	return false

}

func makeabs(wd string, path string) string {
	if filepath.IsAbs(path) {
		return path
	}
	return filepath.Join(wd, path)
}

func parseBuildArgs(paths []string) *wt.BuildArgs {
	style, ok := libsass.Style[style]

	if !ok {
		style = libsass.NESTED_STYLE
	}

	wd, err := os.Getwd()
	if err != nil {
		log.Fatal("could not find working directory", err)
	}

	proj = makeabs(wd, proj)

	incs := make([]string, len(includes))
	for i := range includes {
		incs[i] = makeabs(wd, includes[i])
	}

	dir = makeabs(wd, dir)
	font = makeabs(wd, font)
	if len(buildDir) > 0 {
		buildDir = makeabs(wd, buildDir)
		// If buildDir specified, make relative to that
		gen = makeabs(buildDir, gen)
	} else {
		gen = makeabs(wd, gen)
	}

	gba := &wt.BuildArgs{
		ImageDir:  dir,
		BuildDir:  buildDir,
		Includes:  append([]string{proj}, incs...),
		Font:      font,
		Style:     style,
		Gen:       gen,
		Comments:  comments,
		CacheBust: cachebust,
	}
	gba.WithPaths(paths)
	return gba
}

func globalRun(paths []string) (*wt.SafePartialMap, *wt.BuildArgs) {
	// fmt.Printf("paths: %s args: % #v\n", paths, pflag.Args())
	if argExit() {
		return nil, nil
	}

	// Profiling code
	if cpuprofile != "" {
		f, err := os.Create(cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		log.Println("Starting profiler")
		pprof.StartCPUProfile(f)
		defer func() {
			pprof.StopCPUProfile()
			err := f.Close()
			if err != nil {
				log.Fatal(err)
			}
			log.Println("Stopping Profiller")
		}()
	}

	for _, v := range paths {
		if strings.HasPrefix(v, "-") {
			log.Fatalf("Please specify flags before other arguments: %s", v)
		}
	}

	if gen != "" {
		err := os.MkdirAll(gen, 0755)
		if err != nil {
			log.Fatal(err)
		}
	}

	pMap := wt.NewPartialMap()
	gba := parseBuildArgs(paths)
	if debug {
		log.Printf("      Font  Dir: %s\n", gba.Font)
		log.Printf("      Image Dir: %s\n", gba.ImageDir)
		log.Printf("      Build Dir: %s\n", gba.BuildDir)
		log.Printf("Build Image Dir: %s\n", gba.Gen)
		log.Printf(" Include Dir(s): %s\n", gba.Includes)
		log.Println("===================================")
	}
	return pMap, gba

}

// Watch accepts a set of paths starting a recursive file watcher
func Watch(cmd *cobra.Command, paths []string) {
	pMap, gba := globalRun(paths)
	var err error
	bOpts := wt.NewBuild(paths, gba, pMap)
	err = bOpts.Run()
	if err != nil {
		log.Fatal(err)
	}
	w, err := wt.NewWatcher(&wt.WatchOptions{
		Paths:      paths,
		BArgs:      gba,
		PartialMap: pMap,
	})
	if err != nil {
		log.Fatal("failed to start watcher: ", err)
	}
	err = w.Watch()
	if err != nil {
		log.Fatal("filewatcher error: ", err)
	}

	fmt.Println("File watcher started use `ctrl+d` to exit")
	in := bufio.NewReader(os.Stdin)
	for {
		_, err := in.ReadString(' ')
		if err != nil {
			if err == io.EOF {
				os.Exit(0)
			}
			fmt.Println("error", err)
		}
	}
}

// lis is exposed so test suite can shut it down
var lis net.Listener

// Serve starts a web server accepting POST calls and return CSS
func Serve(cmd *cobra.Command, paths []string) {

	_, gba := globalRun(paths)
	if len(gba.Gen) == 0 {
		log.Fatal("Must pass an image build directory to use HTTP")
	}

	http.Handle("/build/", wt.FileHandler(gba.Gen))
	log.Println("Web server started on :12345")

	var err error
	lis, err = net.Listen("tcp", ":12345")
	if err != nil {
		log.Fatal("Error listening on :12345", err)
	}

	http.HandleFunc("/", wt.HTTPHandler(gba, httpPath))
	http.Serve(lis, nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
	log.Println("Server closed")
}

// Compile handles compile files and stdin operations.
func Compile(cmd *cobra.Command, paths []string) {
	start := time.Now()
	pMap, gba := globalRun(paths)
	if gba == nil {
		return
	}

	defer func() {
		log.Printf("Compilation took: %s\n", time.Since(start))
	}()

	run(paths, pMap, gba)
}

// Run is the main entrypoint for the cli.
func run(paths []string, pMap *wt.SafePartialMap, gba *wt.BuildArgs) {

	// No paths given, read from stdin and wait
	if len(paths) == 0 {

		log.Println("Reading from stdin, -h for help")
		out := os.Stdout
		in := os.Stdin
		comp, err := wt.FromBuildArgs(out, in, gba)
		if err != nil {
			log.Fatal(err)
		}
		err = comp.Run()
		if err != nil {
			color.Red(err.Error())
		}
		return
	}

	bOpts := wt.NewBuild(paths, gba, pMap)

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

	// FIXME: move this to a Payload.Close() method

	// Before shutting down, check that every sprite has been
	// flushed to disk.
	img := sync.WaitGroup{}
	pMap.RLock()
	// It's not currently possible to wait on Image. This is often
	// to inline images, so it shouldn't be a factor...
	// for _, s := range gba.Payload.Image().M {
	// 	img.Add(1)
	// 	err := s.Wait()
	// 	img.Done()
	// 	if err != nil {
	// 		log.Printf("error writing image: %s\n", err)
	// 	}
	// }
	for _, s := range gba.Payload.Sprite().M {
		img.Add(1)
		err := s.Wait()
		img.Done()
		if err != nil {
			log.Printf("error writing sprite: %s\n", err)
		}
	}
	img.Wait()
	pMap.RUnlock()

}
<|endoftext|>


<commit_before>package cli

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"

	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/flynn/go-docopt"
	tuf "github.com/flynn/flynn/Godeps/_workspace/src/github.com/flynn/go-tuf/client"
	"github.com/flynn/flynn/pinkerton/layer"
	"github.com/flynn/flynn/pkg/cluster"
)

func init() {
	Register("update", runUpdate, `
usage: flynn-host update [--driver=<name>] [--root=<path>] [--repository=<uri>] [--tuf-db=<path>]

Options:
  -d --driver=<name>       image storage driver [default: aufs]
  -r --root=<path>         image storage root [default: /var/lib/docker]
  -u --repository=<uri>    image repository URI [default: https://dl.flynn.io/tuf]
  -t --tuf-db=<path>       local TUF file [default: /etc/flynn/tuf.db]

Update Flynn components`)
}

func runUpdate(args *docopt.Args) error {
	// create and update a TUF client
	local, err := tuf.FileLocalStore(args.String["--tuf-db"])
	if err != nil {
		return err
	}
	remote, err := tuf.HTTPRemoteStore(args.String["--repository"], nil)
	if err != nil {
		return err
	}
	client := tuf.NewClient(local, remote)
	if _, err := client.Update(); err != nil && !tuf.IsLatestSnapshot(err) {
		return err
	}

	// read the TUF db so we can pass it to hosts
	tufDB, err := ioutil.ReadFile(args.String["--tuf-db"])
	if err != nil {
		return err
	}

	// get list of hosts
	clusterClient, err := cluster.NewClient()
	if err != nil {
		return err
	}
	hosts, err := clusterClient.ListHosts()
	if err != nil {
		return err
	}
	if len(hosts) == 0 {
		return errors.New("no hosts found")
	}

	hostErrs := make(chan error)
	for _, h := range hosts {
		go func(hostID string) {
			host, err := clusterClient.DialHost(hostID)
			if err != nil {
				hostErrs <- err
				return
			}
			ch := make(chan *layer.PullInfo)
			stream, err := host.PullImages(
				args.String["--repository"],
				args.String["--driver"],
				args.String["--root"],
				bytes.NewReader(tufDB),
				ch,
			)
			if err != nil {
				hostErrs <- err
				return
			}
			defer stream.Close()
			for info := range ch {
				fmt.Printf("==> %s : %s %s %s\n", hostID, info.Repo, info.ID, info.Status)
			}
			hostErrs <- stream.Err()
		}(h.ID)
	}
	var hostErr error
	for _, h := range hosts {
		if err := <-hostErrs; err != nil {
			fmt.Printf("update: error running update on host %s: %s\n", h.ID, err)
			hostErr = err
		}
	}
	return hostErr
}
<commit_msg>host/cli: Add logging to update command<commit_after>package cli

import (
	"bytes"
	"errors"
	"io/ioutil"

	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/flynn/go-docopt"
	tuf "github.com/flynn/flynn/Godeps/_workspace/src/github.com/flynn/go-tuf/client"
	"github.com/flynn/flynn/Godeps/_workspace/src/gopkg.in/inconshreveable/log15.v2"
	"github.com/flynn/flynn/pinkerton/layer"
	"github.com/flynn/flynn/pkg/cluster"
)

func init() {
	Register("update", runUpdate, `
usage: flynn-host update [--driver=<name>] [--root=<path>] [--repository=<uri>] [--tuf-db=<path>]

Options:
  -d --driver=<name>       image storage driver [default: aufs]
  -r --root=<path>         image storage root [default: /var/lib/docker]
  -u --repository=<uri>    image repository URI [default: https://dl.flynn.io/tuf]
  -t --tuf-db=<path>       local TUF file [default: /etc/flynn/tuf.db]

Update Flynn components`)
}

func runUpdate(args *docopt.Args) error {
	log := log15.New()

	// create and update a TUF client
	log.Info("initializing TUF client")
	local, err := tuf.FileLocalStore(args.String["--tuf-db"])
	if err != nil {
		log.Error("error creating local TUF client", "err", err)
		return err
	}
	remote, err := tuf.HTTPRemoteStore(args.String["--repository"], nil)
	if err != nil {
		log.Error("error creating remote TUF client", "err", err)
		return err
	}
	client := tuf.NewClient(local, remote)

	log.Info("updating TUF data")
	if _, err := client.Update(); err != nil && !tuf.IsLatestSnapshot(err) {
		log.Error("error updating TUF client", "err", err)
		return err
	}

	// read the TUF db so we can pass it to hosts
	log.Info("reading TUF database")
	tufDB, err := ioutil.ReadFile(args.String["--tuf-db"])
	if err != nil {
		log.Error("error reading the TUF database", "err", err)
		return err
	}

	log.Info("getting host list")
	clusterClient, err := cluster.NewClient()
	if err != nil {
		log.Error("error creating cluster client", "err", err)
		return err
	}
	hosts, err := clusterClient.ListHosts()
	if err != nil {
		log.Error("error getting host list", "err", err)
		return err
	}
	if len(hosts) == 0 {
		return errors.New("no hosts found")
	}

	log.Info("pulling images on all hosts")
	hostErrs := make(chan error)
	for _, h := range hosts {
		go func(hostID string) {
			log := log.New("host", hostID)

			log.Info("connecting to host")
			host, err := clusterClient.DialHost(hostID)
			if err != nil {
				log.Error("error connecting to host", "err", err)
				hostErrs <- err
				return
			}

			log.Info("pulling images")
			ch := make(chan *layer.PullInfo)
			stream, err := host.PullImages(
				args.String["--repository"],
				args.String["--driver"],
				args.String["--root"],
				bytes.NewReader(tufDB),
				ch,
			)
			if err != nil {
				log.Error("error pulling images", "err", err)
				hostErrs <- err
				return
			}
			defer stream.Close()
			for info := range ch {
				log.Info("pulled image layer", "name", info.Repo, "id", info.ID, "status", info.Status)
			}
			hostErrs <- stream.Err()
		}(h.ID)
	}
	var hostErr error
	for _, h := range hosts {
		if err := <-hostErrs; err != nil {
			log.Error("error pulling images", "host", h.ID, "err", err)
			hostErr = err
			continue
		}
		log.Info("images pulled successfully", "host", h.ID)
	}
	return hostErr
}
<|endoftext|>


<commit_before>package nio

import (
	"bytes"
	"testing"

	"github.com/djherbis/buffer"
)

func TestCopy(t *testing.T) {
	buf := buffer.New(1024)
	input := bytes.NewBuffer(nil)
	output := bytes.NewBuffer(nil)

	input.Write([]byte("hello world"))
	n, err := Copy(output, input, buf)
	if err != nil {
		t.Errorf(err.Error())
	}
	if int(n) != len("hello world") {
		t.Errorf("wrote wrong # of bytes")
	}

	if !bytes.Equal(output.Bytes(), []byte("hello world")) {
		t.Errorf("output didn't match: %s", output.Bytes())
	}
}

func TestPipe(t *testing.T) {
	buf := buffer.New(1024)
	r, w := Pipe(buf)

	data := []byte("the quick brown fox jumps over the lazy dog")
	if _, err := w.Write(data); err != nil {
		t.Error(err.Error())
		return
	}

	result := make([]byte, 1024)
	n, err := r.Read(result)
	if err != nil {
		t.Error(err.Error())
		return
	}
	result = result[:n]

	if !bytes.Equal(data, result) {
		t.Errorf("exp [%s]\ngot[%s]", string(data), string(result))
	}

}
<commit_msg>added closed pipe test<commit_after>package nio

import (
	"bytes"
	"io"
	"testing"

	"github.com/djherbis/buffer"
)

func TestCopy(t *testing.T) {
	buf := buffer.New(1024)
	input := bytes.NewBuffer(nil)
	output := bytes.NewBuffer(nil)

	input.Write([]byte("hello world"))
	n, err := Copy(output, input, buf)
	if err != nil {
		t.Errorf(err.Error())
	}
	if int(n) != len("hello world") {
		t.Errorf("wrote wrong # of bytes")
	}

	if !bytes.Equal(output.Bytes(), []byte("hello world")) {
		t.Errorf("output didn't match: %s", output.Bytes())
	}
}

func TestPipeCloseEarly(t *testing.T) {
	buf := buffer.New(1024)
	r, w := Pipe(buf)
	r.Close()
	_, err := w.Write([]byte("hello world"))
	if err != io.ErrClosedPipe {
		t.Errorf("expected closed pipe")
	}
}

func TestPipe(t *testing.T) {
	buf := buffer.New(1024)
	r, w := Pipe(buf)
	defer r.Close()

	data := []byte("the quick brown fox jumps over the lazy dog")
	if _, err := w.Write(data); err != nil {
		t.Error(err.Error())
		return
	}
	w.Close()

	result := make([]byte, 1024)
	n, err := r.Read(result)
	if err != nil {
		t.Error(err.Error())
		return
	}
	result = result[:n]

	if !bytes.Equal(data, result) {
		t.Errorf("exp [%s]\ngot[%s]", string(data), string(result))
	}

}
<|endoftext|>


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

package main

import (
	"flag"
	"fmt"
	"github.com/bmizerany/pat"
	"github.com/tsuru/config"
	"github.com/tsuru/gandalf/api"
	"log"
	"net/http"
)

const version = "0.4.1"

func main() {
	dry := flag.Bool("dry", false, "dry-run: does not start the server (for testing purpose)")
	configFile := flag.String("config", "/etc/gandalf.conf", "Gandalf configuration file")
	gVersion := flag.Bool("version", false, "Print version and exit")
	flag.Parse()

	if *gVersion {
		fmt.Printf("gandalf-webserver version %s\n", version)
		return
	}

	err := config.ReadAndWatchConfigFile(*configFile)
	if err != nil {
		msg := `Could not find gandalf config file. Searched on %s.
For an example conf check gandalf/etc/gandalf.conf file.\n %s`
		log.Panicf(msg, *configFile, err)
	}
	router := pat.New()
	router.Post("/user/:name/key", http.HandlerFunc(api.AddKey))
	router.Del("/user/:name/key/:keyname", http.HandlerFunc(api.RemoveKey))
	router.Get("/user/:name/keys", http.HandlerFunc(api.ListKeys))
	router.Post("/user", http.HandlerFunc(api.NewUser))
	router.Del("/user/:name", http.HandlerFunc(api.RemoveUser))
	router.Post("/repository", http.HandlerFunc(api.NewRepository))
	router.Post("/repository/grant", http.HandlerFunc(api.GrantAccess))
	router.Del("/repository/revoke", http.HandlerFunc(api.RevokeAccess))
	router.Del("/repository/:name", http.HandlerFunc(api.RemoveRepository))
	router.Get("/repository/:name", http.HandlerFunc(api.GetRepository))
	router.Put("/repository/:name", http.HandlerFunc(api.RenameRepository))
	router.Get("/repository/:name/archive", http.HandlerFunc(api.GetArchive))
	router.Get("/repository/:name/contents", http.HandlerFunc(api.GetFileContents))
	router.Get("/repository/:name/tree", http.HandlerFunc(api.GetTree))
	router.Get("/repository/:name/branches", http.HandlerFunc(api.GetBranches))
	router.Get("/repository/:name/tags", http.HandlerFunc(api.GetTags))
	router.Get("/repository/:name/diff/commits", http.HandlerFunc(api.GetDiff))
	router.Post("/repository/:name/commit", http.HandlerFunc(api.Commit))
	router.Get("/repository/:name/logs", http.HandlerFunc(api.GetLog))
	router.Get("/healthcheck/", http.HandlerFunc(api.HealthCheck))
	router.Post("/hook/:name", http.HandlerFunc(api.AddHook))

	bind, err := config.GetString("bind")
	if err != nil {
		var perr error
		bind, perr = config.GetString("webserver:port")
		if perr != nil {
			panic(err)
		}
	}
	if !*dry {
		log.Fatal(http.ListenAndServe(bind, router))
	}
}
<commit_msg>Release 0.5.0<commit_after>// Copyright 2014 gandalf authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"github.com/bmizerany/pat"
	"github.com/tsuru/config"
	"github.com/tsuru/gandalf/api"
	"log"
	"net/http"
)

const version = "0.5.0"

func main() {
	dry := flag.Bool("dry", false, "dry-run: does not start the server (for testing purpose)")
	configFile := flag.String("config", "/etc/gandalf.conf", "Gandalf configuration file")
	gVersion := flag.Bool("version", false, "Print version and exit")
	flag.Parse()

	if *gVersion {
		fmt.Printf("gandalf-webserver version %s\n", version)
		return
	}

	err := config.ReadAndWatchConfigFile(*configFile)
	if err != nil {
		msg := `Could not find gandalf config file. Searched on %s.
For an example conf check gandalf/etc/gandalf.conf file.\n %s`
		log.Panicf(msg, *configFile, err)
	}
	router := pat.New()
	router.Post("/user/:name/key", http.HandlerFunc(api.AddKey))
	router.Del("/user/:name/key/:keyname", http.HandlerFunc(api.RemoveKey))
	router.Get("/user/:name/keys", http.HandlerFunc(api.ListKeys))
	router.Post("/user", http.HandlerFunc(api.NewUser))
	router.Del("/user/:name", http.HandlerFunc(api.RemoveUser))
	router.Post("/repository", http.HandlerFunc(api.NewRepository))
	router.Post("/repository/grant", http.HandlerFunc(api.GrantAccess))
	router.Del("/repository/revoke", http.HandlerFunc(api.RevokeAccess))
	router.Del("/repository/:name", http.HandlerFunc(api.RemoveRepository))
	router.Get("/repository/:name", http.HandlerFunc(api.GetRepository))
	router.Put("/repository/:name", http.HandlerFunc(api.RenameRepository))
	router.Get("/repository/:name/archive", http.HandlerFunc(api.GetArchive))
	router.Get("/repository/:name/contents", http.HandlerFunc(api.GetFileContents))
	router.Get("/repository/:name/tree", http.HandlerFunc(api.GetTree))
	router.Get("/repository/:name/branches", http.HandlerFunc(api.GetBranches))
	router.Get("/repository/:name/tags", http.HandlerFunc(api.GetTags))
	router.Get("/repository/:name/diff/commits", http.HandlerFunc(api.GetDiff))
	router.Post("/repository/:name/commit", http.HandlerFunc(api.Commit))
	router.Get("/repository/:name/logs", http.HandlerFunc(api.GetLog))
	router.Get("/healthcheck/", http.HandlerFunc(api.HealthCheck))
	router.Post("/hook/:name", http.HandlerFunc(api.AddHook))

	bind, err := config.GetString("bind")
	if err != nil {
		var perr error
		bind, perr = config.GetString("webserver:port")
		if perr != nil {
			panic(err)
		}
	}
	if !*dry {
		log.Fatal(http.ListenAndServe(bind, router))
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
)

const version = "1.0.5"

func main() {
	fmt.Println("Go server version", version)

	DB = sqlInit()
	defer sqlClose()

	router := http.NewServeMux()

	router.HandleFunc("/", handlerHome)
	router.HandleFunc("/sql/", handlerSQL)
	router.HandleFunc("/sql/json", handlerSQLJSON)
	router.HandleFunc("/sql/xml", handlerSQLXML)

	http.ListenAndServe(":8081", router)

}
<commit_msg>use a semantic versioning string http://semver.org/<commit_after>package main

import (
	"fmt"
	"net/http"
)

// http://semver.org/
const version = "0.1.5"

func main() {
	fmt.Println("Go server version", version)

	DB = sqlInit()
	defer sqlClose()

	router := http.NewServeMux()

	router.HandleFunc("/", handlerHome)
	router.HandleFunc("/sql/", handlerSQL)
	router.HandleFunc("/sql/json", handlerSQLJSON)
	router.HandleFunc("/sql/xml", handlerSQLXML)

	http.ListenAndServe(":8081", router)

}
<|endoftext|>


<commit_before>/*
Copyright 2011 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"bytes"
	"exec"
	"flag"
	"fmt"
	"http"
	"http/cgi"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"template"
	"time"
)

const defaultAddr = ":31798" // default webserver address

var h1TitlePattern = regexp.MustCompile(`<h1>(.+)</h1>`)

var (
	httpAddr            = flag.String("http", defaultAddr, "HTTP service address (e.g., '"+defaultAddr+"')")
	httpsAddr           = flag.String("https", "", "HTTPS service address")
	root                = flag.String("root", "", "Website root (parent of 'static', 'content', and 'tmpl")
	gitwebScript        = flag.String("gitwebscript", "/usr/lib/cgi-bin/gitweb.cgi", "Path to gitweb.cgi, or blank to disable.")
	gitwebFiles         = flag.String("gitwebfiles", "/usr/share/gitweb/static", "Path to gitweb's static files.")
	logDir              = flag.String("logdir", "", "Directory to write log files to (one per hour), or empty to not log.")
	logStdout           = flag.Bool("logstdout", true, "Write to stdout?")
	tlsCertFile         = flag.String("tlscert", "", "TLS cert file")
	tlsKeyFile          = flag.String("tlskey", "", "TLS private key file")
	gerritUser          = flag.String("gerrituser", "ubuntu", "Gerrit host's username")
	gerritHost          = flag.String("gerrithost", "gerrit-proxy", "Gerrit host, or empty.")
	pageHtml, errorHtml *template.Template
)

var fmap = template.FormatterMap{
	"":         textFmt,
	"html":     htmlFmt,
	"html-esc": htmlEscFmt,
}

// Template formatter for "" (default) format.
func textFmt(w io.Writer, format string, x ...interface{}) {
	writeAny(w, false, x[0])
}

// Template formatter for "html" format.
func htmlFmt(w io.Writer, format string, x ...interface{}) {
	writeAny(w, true, x[0])
}

// Template formatter for "html-esc" format.
func htmlEscFmt(w io.Writer, format string, x ...interface{}) {
	var buf bytes.Buffer
	writeAny(&buf, false, x[0])
	template.HTMLEscape(w, buf.Bytes())
}

// Write anything to w; optionally html-escaped.
func writeAny(w io.Writer, html bool, x interface{}) {
	switch v := x.(type) {
	case []byte:
		writeText(w, v, html)
	case string:
		writeText(w, []byte(v), html)
	default:
		if html {
			var buf bytes.Buffer
			fmt.Fprint(&buf, x)
			writeText(w, buf.Bytes(), true)
		} else {
			fmt.Fprint(w, x)
		}
	}
}

// Write text to w; optionally html-escaped.
func writeText(w io.Writer, text []byte, html bool) {
	if html {
		template.HTMLEscape(w, text)
		return
	}
	w.Write(text)
}

func applyTemplate(t *template.Template, name string, data interface{}) []byte {
	var buf bytes.Buffer
	if err := t.Execute(&buf, data); err != nil {
		log.Printf("%s.Execute: %s", name, err)
	}
	return buf.Bytes()
}

func servePage(w http.ResponseWriter, title, subtitle string, content []byte) {
	d := struct {
		Title    string
		Subtitle string
		Content  []byte
	}{
		title,
		subtitle,
		content,
	}

	if err := pageHtml.Execute(w, &d); err != nil {
		log.Printf("godocHTML.Execute: %s", err)
	}
}

func readTemplate(name string) *template.Template {
	fileName := filepath.Join(*root, "tmpl", name)
	data, err := ioutil.ReadFile(fileName)
	if err != nil {
		log.Fatalf("ReadFile %s: %v", fileName, err)
	}
	t, err := template.Parse(string(data), fmap)
	if err != nil {
		log.Fatalf("%s: %v", fileName, err)
	}
	return t
}

func readTemplates() {
	pageHtml = readTemplate("page.html")
	errorHtml = readTemplate("error.html")
}

func serveError(w http.ResponseWriter, r *http.Request, relpath string, err os.Error) {
	contents := applyTemplate(errorHtml, "errorHtml", err) // err may contain an absolute path!
	w.WriteHeader(http.StatusNotFound)
	servePage(w, "File "+relpath, "", contents)
}

func mainHandler(rw http.ResponseWriter, req *http.Request) {
	relPath := req.URL.Path[1:] // serveFile URL paths start with '/'
	if strings.Contains(relPath, "..") {
		return
	}

	if strings.HasPrefix(relPath, "gw/") {
		path := relPath[3:]
		http.Redirect(rw, req, "/code/?p=camlistore.git;f="+path+";hb=master", http.StatusFound)
		return
	}

	absPath := filepath.Join(*root, "content", relPath)
	fi, err := os.Lstat(absPath)
	if err != nil {
		log.Print(err)
		serveError(rw, req, relPath, err)
		return
	}
	if fi.IsDirectory() {
		relPath += "/index.html"
		absPath = filepath.Join(*root, "content", relPath)
		fi, err = os.Lstat(absPath)
		if err != nil {
			log.Print(err)
			serveError(rw, req, relPath, err)
			return
		}
	}

	switch {
	case fi.IsRegular():
		serveFile(rw, req, relPath, absPath)
	}
}

func serveFile(rw http.ResponseWriter, req *http.Request, relPath, absPath string) {
	data, err := ioutil.ReadFile(absPath)
	if err != nil {
		serveError(rw, req, absPath, err)
		return
	}

	title := ""
	if m := h1TitlePattern.FindSubmatch(data); len(m) > 1 {
		title = string(m[1])
	}

	servePage(rw, title, "", data)
}

type gitwebHandler struct {
	Cgi    http.Handler
	Static http.Handler
}

func (h *gitwebHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	if r.URL.RawPath == "/code/" ||
		strings.HasPrefix(r.URL.RawPath, "/code/?") {
		h.Cgi.ServeHTTP(rw, r)
	} else {
		h.Static.ServeHTTP(rw, r)
	}
}

type noWwwHandler struct {
	Handler http.Handler
}

func (h *noWwwHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	host := strings.ToLower(r.Host)
	if host == "www.camlistore.org" {
		http.Redirect(rw, r, "http://camlistore.org"+r.URL.RawPath, http.StatusFound)
		return
	}
	h.Handler.ServeHTTP(rw, r)
}

func fixupGitwebFiles() {
	fi, err := os.Stat(*gitwebFiles)
	if err != nil || !fi.IsDirectory() {
		if *gitwebFiles == "/usr/share/gitweb/static" {
			// Old Debian/Ubuntu location
			*gitwebFiles = "/usr/share/gitweb"
		}
	}
}

func main() {
	flag.Parse()
	readTemplates()

	if *root == "" {
		var err os.Error
		*root, err = os.Getwd()
		if err != nil {
			log.Fatalf("Failed to getwd: %v", err)
		}
	}

	fixupGitwebFiles()

	latestGits := filepath.Join(*root, "latestgits")
	os.Mkdir(latestGits, 0700)
	if *gerritHost != "" {
		go rsyncFromGerrit(latestGits)
	}

	mux := http.DefaultServeMux
	mux.Handle("/favicon.ico", http.FileServer(http.Dir(filepath.Join(*root, "static"))))
	mux.Handle("/robots.txt", http.FileServer(http.Dir(filepath.Join(*root, "static"))))
	mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(filepath.Join(*root, "static")))))
	mux.Handle("/talks/", http.StripPrefix("/talks/", http.FileServer(http.Dir(filepath.Join(*root, "talks")))))

	gerritUrl, _ := http.ParseURL("http://gerrit-proxy:8000/")
	var gerritHandler http.Handler = http.NewSingleHostReverseProxy(gerritUrl)
	if *httpsAddr != "" {
		proxyHandler := gerritHandler
		gerritHandler = http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
			if req.TLS != nil {
				proxyHandler.ServeHTTP(rw, req)
				return
			}
			http.Redirect(rw, req, "https://camlistore.org"+req.URL.RawPath, http.StatusFound)
		})
	}
	mux.Handle("/r/", gerritHandler)

	testCgi := &cgi.Handler{Path: filepath.Join(*root, "test.cgi"),
		Root: "/test.cgi",
	}
	mux.Handle("/test.cgi", testCgi)
	mux.Handle("/test.cgi/foo", testCgi)

	mux.Handle("/code", http.RedirectHandler("/code/", http.StatusFound))
	if *gitwebScript != "" {
		env := os.Environ()
		env = append(env, "GITWEB_CONFIG="+filepath.Join(*root, "gitweb-camli.conf"))
		env = append(env, "CAMWEB_ROOT="+filepath.Join(*root))
		env = append(env, "CAMWEB_GITDIR="+latestGits)
		mux.Handle("/code/", &fixUpGitwebUrls{&gitwebHandler{
			Cgi: &cgi.Handler{
				Path: *gitwebScript,
				Root: "/code/",
				Env:  env,
			},
			Static: http.StripPrefix("/code/", http.FileServer(http.Dir(*gitwebFiles))),
		}})
	}
	mux.HandleFunc("/", mainHandler)

	var handler http.Handler = &noWwwHandler{Handler: mux}
	if *logDir != "" || *logStdout {
		handler = NewLoggingHandler(handler, *logDir, *logStdout)
	}

	errch := make(chan os.Error)

	httpServer := &http.Server{
		Addr:         *httpAddr,
		Handler:      handler,
		ReadTimeout:  connTimeoutNanos,
		WriteTimeout: connTimeoutNanos,
	}
	go func() {
		errch <- httpServer.ListenAndServe()
	}()

	if *httpsAddr != "" {
		httpsServer := new(http.Server)
		*httpsServer = *httpServer
		httpsServer.Addr = *httpsAddr
		go func() {
			errch <- httpsServer.ListenAndServeTLS(*tlsCertFile, *tlsKeyFile)
		}()
	}

	log.Fatalf("Serve error: %v", <-errch)
}

const connTimeoutNanos = 15e9

type fixUpGitwebUrls struct {
	handler http.Handler
}

// Not sure what's making these broken URLs like:
//
//   http://localhost:8080/code/?p=camlistore.git%3Bf=doc/json-signing/json-signing.txt%3Bhb=master
//
// ... but something is.  Maybe Buzz?  For now just re-write them
// . Doesn't seem to be a bug in the CGI implementation, though, which
// is what I'd originally suspected.
func (fu *fixUpGitwebUrls) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	oldUrl := req.RawURL
	newUrl := strings.Replace(oldUrl, "%3B", ";", -1)
	if newUrl == oldUrl {
		fu.handler.ServeHTTP(rw, req)
		return
	}
	http.Redirect(rw, req, newUrl, http.StatusFound)
}

func rsyncFromGerrit(dest string) {
	for {
		err := exec.Command("rsync", "-avPW", *gerritUser+"@"+*gerritHost+":gerrit/git/", dest+"/").Run()
		if err != nil {
			log.Printf("rsync from gerrit = %v", err)
		}
		time.Sleep(10e9)
	}
}
<commit_msg>tame greedy regex (see title tag in http://camlistore.org/docs/contributing)<commit_after>/*
Copyright 2011 Google Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"bytes"
	"exec"
	"flag"
	"fmt"
	"http"
	"http/cgi"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"template"
	"time"
)

const defaultAddr = ":31798" // default webserver address

var h1TitlePattern = regexp.MustCompile(`<h1>([^<]+)</h1>`)

var (
	httpAddr            = flag.String("http", defaultAddr, "HTTP service address (e.g., '"+defaultAddr+"')")
	httpsAddr           = flag.String("https", "", "HTTPS service address")
	root                = flag.String("root", "", "Website root (parent of 'static', 'content', and 'tmpl")
	gitwebScript        = flag.String("gitwebscript", "/usr/lib/cgi-bin/gitweb.cgi", "Path to gitweb.cgi, or blank to disable.")
	gitwebFiles         = flag.String("gitwebfiles", "/usr/share/gitweb/static", "Path to gitweb's static files.")
	logDir              = flag.String("logdir", "", "Directory to write log files to (one per hour), or empty to not log.")
	logStdout           = flag.Bool("logstdout", true, "Write to stdout?")
	tlsCertFile         = flag.String("tlscert", "", "TLS cert file")
	tlsKeyFile          = flag.String("tlskey", "", "TLS private key file")
	gerritUser          = flag.String("gerrituser", "ubuntu", "Gerrit host's username")
	gerritHost          = flag.String("gerrithost", "gerrit-proxy", "Gerrit host, or empty.")
	pageHtml, errorHtml *template.Template
)

var fmap = template.FormatterMap{
	"":         textFmt,
	"html":     htmlFmt,
	"html-esc": htmlEscFmt,
}

// Template formatter for "" (default) format.
func textFmt(w io.Writer, format string, x ...interface{}) {
	writeAny(w, false, x[0])
}

// Template formatter for "html" format.
func htmlFmt(w io.Writer, format string, x ...interface{}) {
	writeAny(w, true, x[0])
}

// Template formatter for "html-esc" format.
func htmlEscFmt(w io.Writer, format string, x ...interface{}) {
	var buf bytes.Buffer
	writeAny(&buf, false, x[0])
	template.HTMLEscape(w, buf.Bytes())
}

// Write anything to w; optionally html-escaped.
func writeAny(w io.Writer, html bool, x interface{}) {
	switch v := x.(type) {
	case []byte:
		writeText(w, v, html)
	case string:
		writeText(w, []byte(v), html)
	default:
		if html {
			var buf bytes.Buffer
			fmt.Fprint(&buf, x)
			writeText(w, buf.Bytes(), true)
		} else {
			fmt.Fprint(w, x)
		}
	}
}

// Write text to w; optionally html-escaped.
func writeText(w io.Writer, text []byte, html bool) {
	if html {
		template.HTMLEscape(w, text)
		return
	}
	w.Write(text)
}

func applyTemplate(t *template.Template, name string, data interface{}) []byte {
	var buf bytes.Buffer
	if err := t.Execute(&buf, data); err != nil {
		log.Printf("%s.Execute: %s", name, err)
	}
	return buf.Bytes()
}

func servePage(w http.ResponseWriter, title, subtitle string, content []byte) {
	d := struct {
		Title    string
		Subtitle string
		Content  []byte
	}{
		title,
		subtitle,
		content,
	}

	if err := pageHtml.Execute(w, &d); err != nil {
		log.Printf("godocHTML.Execute: %s", err)
	}
}

func readTemplate(name string) *template.Template {
	fileName := filepath.Join(*root, "tmpl", name)
	data, err := ioutil.ReadFile(fileName)
	if err != nil {
		log.Fatalf("ReadFile %s: %v", fileName, err)
	}
	t, err := template.Parse(string(data), fmap)
	if err != nil {
		log.Fatalf("%s: %v", fileName, err)
	}
	return t
}

func readTemplates() {
	pageHtml = readTemplate("page.html")
	errorHtml = readTemplate("error.html")
}

func serveError(w http.ResponseWriter, r *http.Request, relpath string, err os.Error) {
	contents := applyTemplate(errorHtml, "errorHtml", err) // err may contain an absolute path!
	w.WriteHeader(http.StatusNotFound)
	servePage(w, "File "+relpath, "", contents)
}

func mainHandler(rw http.ResponseWriter, req *http.Request) {
	relPath := req.URL.Path[1:] // serveFile URL paths start with '/'
	if strings.Contains(relPath, "..") {
		return
	}

	if strings.HasPrefix(relPath, "gw/") {
		path := relPath[3:]
		http.Redirect(rw, req, "/code/?p=camlistore.git;f="+path+";hb=master", http.StatusFound)
		return
	}

	absPath := filepath.Join(*root, "content", relPath)
	fi, err := os.Lstat(absPath)
	if err != nil {
		log.Print(err)
		serveError(rw, req, relPath, err)
		return
	}
	if fi.IsDirectory() {
		relPath += "/index.html"
		absPath = filepath.Join(*root, "content", relPath)
		fi, err = os.Lstat(absPath)
		if err != nil {
			log.Print(err)
			serveError(rw, req, relPath, err)
			return
		}
	}

	switch {
	case fi.IsRegular():
		serveFile(rw, req, relPath, absPath)
	}
}

func serveFile(rw http.ResponseWriter, req *http.Request, relPath, absPath string) {
	data, err := ioutil.ReadFile(absPath)
	if err != nil {
		serveError(rw, req, absPath, err)
		return
	}

	title := ""
	if m := h1TitlePattern.FindSubmatch(data); len(m) > 1 {
		title = string(m[1])
	}

	servePage(rw, title, "", data)
}

type gitwebHandler struct {
	Cgi    http.Handler
	Static http.Handler
}

func (h *gitwebHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	if r.URL.RawPath == "/code/" ||
		strings.HasPrefix(r.URL.RawPath, "/code/?") {
		h.Cgi.ServeHTTP(rw, r)
	} else {
		h.Static.ServeHTTP(rw, r)
	}
}

type noWwwHandler struct {
	Handler http.Handler
}

func (h *noWwwHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
	host := strings.ToLower(r.Host)
	if host == "www.camlistore.org" {
		http.Redirect(rw, r, "http://camlistore.org"+r.URL.RawPath, http.StatusFound)
		return
	}
	h.Handler.ServeHTTP(rw, r)
}

func fixupGitwebFiles() {
	fi, err := os.Stat(*gitwebFiles)
	if err != nil || !fi.IsDirectory() {
		if *gitwebFiles == "/usr/share/gitweb/static" {
			// Old Debian/Ubuntu location
			*gitwebFiles = "/usr/share/gitweb"
		}
	}
}

func main() {
	flag.Parse()
	readTemplates()

	if *root == "" {
		var err os.Error
		*root, err = os.Getwd()
		if err != nil {
			log.Fatalf("Failed to getwd: %v", err)
		}
	}

	fixupGitwebFiles()

	latestGits := filepath.Join(*root, "latestgits")
	os.Mkdir(latestGits, 0700)
	if *gerritHost != "" {
		go rsyncFromGerrit(latestGits)
	}

	mux := http.DefaultServeMux
	mux.Handle("/favicon.ico", http.FileServer(http.Dir(filepath.Join(*root, "static"))))
	mux.Handle("/robots.txt", http.FileServer(http.Dir(filepath.Join(*root, "static"))))
	mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(filepath.Join(*root, "static")))))
	mux.Handle("/talks/", http.StripPrefix("/talks/", http.FileServer(http.Dir(filepath.Join(*root, "talks")))))

	gerritUrl, _ := http.ParseURL("http://gerrit-proxy:8000/")
	var gerritHandler http.Handler = http.NewSingleHostReverseProxy(gerritUrl)
	if *httpsAddr != "" {
		proxyHandler := gerritHandler
		gerritHandler = http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
			if req.TLS != nil {
				proxyHandler.ServeHTTP(rw, req)
				return
			}
			http.Redirect(rw, req, "https://camlistore.org"+req.URL.RawPath, http.StatusFound)
		})
	}
	mux.Handle("/r/", gerritHandler)

	testCgi := &cgi.Handler{Path: filepath.Join(*root, "test.cgi"),
		Root: "/test.cgi",
	}
	mux.Handle("/test.cgi", testCgi)
	mux.Handle("/test.cgi/foo", testCgi)

	mux.Handle("/code", http.RedirectHandler("/code/", http.StatusFound))
	if *gitwebScript != "" {
		env := os.Environ()
		env = append(env, "GITWEB_CONFIG="+filepath.Join(*root, "gitweb-camli.conf"))
		env = append(env, "CAMWEB_ROOT="+filepath.Join(*root))
		env = append(env, "CAMWEB_GITDIR="+latestGits)
		mux.Handle("/code/", &fixUpGitwebUrls{&gitwebHandler{
			Cgi: &cgi.Handler{
				Path: *gitwebScript,
				Root: "/code/",
				Env:  env,
			},
			Static: http.StripPrefix("/code/", http.FileServer(http.Dir(*gitwebFiles))),
		}})
	}
	mux.HandleFunc("/", mainHandler)

	var handler http.Handler = &noWwwHandler{Handler: mux}
	if *logDir != "" || *logStdout {
		handler = NewLoggingHandler(handler, *logDir, *logStdout)
	}

	errch := make(chan os.Error)

	httpServer := &http.Server{
		Addr:         *httpAddr,
		Handler:      handler,
		ReadTimeout:  connTimeoutNanos,
		WriteTimeout: connTimeoutNanos,
	}
	go func() {
		errch <- httpServer.ListenAndServe()
	}()

	if *httpsAddr != "" {
		httpsServer := new(http.Server)
		*httpsServer = *httpServer
		httpsServer.Addr = *httpsAddr
		go func() {
			errch <- httpsServer.ListenAndServeTLS(*tlsCertFile, *tlsKeyFile)
		}()
	}

	log.Fatalf("Serve error: %v", <-errch)
}

const connTimeoutNanos = 15e9

type fixUpGitwebUrls struct {
	handler http.Handler
}

// Not sure what's making these broken URLs like:
//
//   http://localhost:8080/code/?p=camlistore.git%3Bf=doc/json-signing/json-signing.txt%3Bhb=master
//
// ... but something is.  Maybe Buzz?  For now just re-write them
// . Doesn't seem to be a bug in the CGI implementation, though, which
// is what I'd originally suspected.
func (fu *fixUpGitwebUrls) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
	oldUrl := req.RawURL
	newUrl := strings.Replace(oldUrl, "%3B", ";", -1)
	if newUrl == oldUrl {
		fu.handler.ServeHTTP(rw, req)
		return
	}
	http.Redirect(rw, req, newUrl, http.StatusFound)
}

func rsyncFromGerrit(dest string) {
	for {
		err := exec.Command("rsync", "-avPW", *gerritUser+"@"+*gerritHost+":gerrit/git/", dest+"/").Run()
		if err != nil {
			log.Printf("rsync from gerrit = %v", err)
		}
		time.Sleep(10e9)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"archive/zip"
	"bytes"
	"encoding/json"
	"fmt"
	"go/build"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"strings"

	. "github.com/AlekSi/nut"
)

type Config struct {
	Token string
	V     bool
}

const (
	ConfigFileName = ".nut.json"
	ConfigFilePerm = 0644
)

var (
	WorkspaceDir string // current workspace (first path in GOPATH)
	SrcDir       string // src directory in current workspace
	NutDir       string // nut directory in current workspace

	// Maps import prefixes to hosts serving nuts.
	// Three reasons for it:
	//   - third-party nut servers (TODO to be implemented);
	//   - testing with dev_appserver;
	//   - no GAE for second-level domains.
	NutImportPrefixes = map[string]string{"gonuts.io": "www.gonuts.io"}

	config Config
	vHelp  string = fmt.Sprintf("be verbose, may be read from ~/%s", ConfigFileName)
)

func init() {
	log.SetFlags(0)

	srcDirs := build.Default.SrcDirs()[1:]
	if len(srcDirs) == 0 {
		env := os.Getenv("GOPATH")
		if env == "" {
			log.Print("GOPATH environment variable is empty.")
		} else {
			log.Printf("Workspaces in GOPATH environment variable (%s), or their src subpaths don't exist.", env)
		}
		log.Fatal("Setup a workspace as described there: http://golang.org/doc/code.html")
	}

	SrcDir = srcDirs[0]
	WorkspaceDir = filepath.Join(SrcDir, "..")
	NutDir = filepath.Join(WorkspaceDir, "nut")

	u, err := user.Current()
	if err != nil {
		_, err = os.Stat(u.HomeDir)
	}
	if err != nil {
		log.Printf("Warning: Can't detect current user home directory: %s", err)
		return
	}

	path := filepath.Join(u.HomeDir, ConfigFileName)
	b, err := ioutil.ReadFile(path)
	if err == nil {
		err = json.Unmarshal(b, &config)
	}
	if err != nil && !os.IsNotExist(err) {
		log.Printf("Warning: Can't load config from %s: %s\n", path, err)
		config = Config{}
	}

	if !os.IsNotExist(err) {
		b, err = json.MarshalIndent(config, "", "  ")
		if err == nil {
			err = ioutil.WriteFile(path, b, ConfigFilePerm)
		}
		if err != nil {
			log.Printf("Warning: Can't write config to %s: %s\n", path, err)
		}
	}

	env := os.Getenv("GONUTS_IO_SERVER")
	if env != "" {
		NutImportPrefixes["gonuts.io"] = env
	}
}

func PanicIfErr(err error) {
	if err != nil {
		log.Panic(err)
	}
}

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

// TODO common functions there are mess for now

// Read spec file.
func ReadSpec(fileName string) (spec *Spec) {
	f, err := os.Open(fileName)
	PanicIfErr(err)
	defer f.Close()
	spec = new(Spec)
	_, err = spec.ReadFrom(f)
	PanicIfErr(err)
	return
}

// Read nut file.
func ReadNut(fileName string) (b []byte, nf *NutFile) {
	var err error
	b, err = ioutil.ReadFile(fileName)
	PanicIfErr(err)
	nf = new(NutFile)
	_, err = nf.ReadFrom(bytes.NewReader(b))
	PanicIfErr(err)
	return
}

// Write nut to GOPATH/nut/<prefix>/<name>-<version>.nut
func WriteNut(b []byte, prefix string, verbose bool) string {
	nf := new(NutFile)
	_, err := nf.ReadFrom(bytes.NewReader(b))
	PanicIfErr(err)

	// create GOPATH/nut/<prefix>
	dir := filepath.Join(NutDir, prefix)
	PanicIfErr(os.MkdirAll(dir, WorkspaceDirPerm))

	// write file
	dstFilepath := filepath.Join(dir, nf.FileName())
	if verbose {
		log.Printf("Writing %s ...", dstFilepath)
	}
	PanicIfErr(ioutil.WriteFile(dstFilepath, b, NutFilePerm))
	return dstFilepath
}

// Copy nut to GOPATH/nut/<prefix>/<name>-<version>.nut
func CopyNut(nutFilepath string, prefix string, verbose bool) {
	b, nf := ReadNut(nutFilepath)

	// create GOPATH/nut/<prefix>
	dir := filepath.Join(NutDir, prefix)
	PanicIfErr(os.MkdirAll(dir, WorkspaceDirPerm))

	// write file
	dstFilepath := filepath.Join(dir, nf.FileName())
	if verbose {
		log.Printf("Copying %s to %s ...", nutFilepath, dstFilepath)
	}
	PanicIfErr(ioutil.WriteFile(dstFilepath, b, NutFilePerm))
}

// Pack files into nut file with given fileName.
func PackNut(fileName string, files []string, verbose bool) {
	// write nut to temporary file first
	nutFile, err := ioutil.TempFile("", "nut-")
	PanicIfErr(err)
	defer func() {
		if nutFile != nil {
			PanicIfErr(os.Remove(nutFile.Name()))
		}
	}()

	nutWriter := zip.NewWriter(nutFile)
	defer func() {
		if nutWriter != nil {
			PanicIfErr(nutWriter.Close())
		}
	}()

	// add files to nut with all meta information
	for _, file := range files {
		if verbose {
			log.Printf("Packing %s ...", file)
		}

		fi, err := os.Stat(file)
		PanicIfErr(err)

		fh, err := zip.FileInfoHeader(fi)
		PanicIfErr(err)
		fh.Name = file

		f, err := nutWriter.CreateHeader(fh)
		PanicIfErr(err)

		b, err := ioutil.ReadFile(file)
		PanicIfErr(err)

		_, err = f.Write(b)
		PanicIfErr(err)
	}

	err = nutWriter.Close()
	nutWriter = nil
	PanicIfErr(err)

	PanicIfErr(nutFile.Close())

	// move file to specified location and fix permissions
	if verbose {
		log.Printf("Creating %s ...", fileName)
	}
	_, err = os.Stat(fileName)
	if err == nil {
		// required on Windows
		PanicIfErr(os.Remove(fileName))
	}
	PanicIfErr(os.Rename(nutFile.Name(), fileName))
	nutFile = nil
	PanicIfErr(os.Chmod(fileName, NutFilePerm))
}

// Unpack nut file with given fileName into dir. Creates dir if needed. Removes dir first if asked.
func UnpackNut(fileName string, dir string, removeDir, verbose bool) {
	// check dir
	_, err := os.Stat(dir)
	if err == nil && removeDir {
		if verbose {
			log.Printf("Removing existing directory %s ...", dir)
		}
		os.RemoveAll(dir)
	}
	PanicIfErr(os.MkdirAll(dir, WorkspaceDirPerm))

	_, nf := ReadNut(fileName)

	for _, file := range nf.Reader.File {
		if verbose {
			log.Printf("Unpacking %s ...", file.Name)
		}

		rc, err := file.Open()
		PanicIfErr(err)
		defer rc.Close()

		b, err := ioutil.ReadAll(rc)
		PanicIfErr(err)

		PanicIfErr(ioutil.WriteFile(filepath.Join(dir, file.Name), b, file.Mode()))
	}
}

// Call 'go install <path>'.
func InstallPackage(path string, verbose bool) {
	args := []string{"install"}
	if verbose {
		args = append(args, "-v")
	}
	args = append(args, path)
	c := exec.Command("go", args...)
	if verbose {
		log.Printf("Running %q", strings.Join(c.Args, " "))
	}
	out, err := c.CombinedOutput()
	if verbose || err != nil {
		log.Print(string(out))
	}
	FatalIfErr(err)
}

// Return imports present in NutImportPrefixes without altering them.
func NutImports(imports []string) (nuts []string) {
	for _, imp := range imports {
		p := strings.Split(imp, "/")
		if _, ok := NutImportPrefixes[p[0]]; ok {
			nuts = append(nuts, imp)
		}
	}
	return
}
<commit_msg>DON'T PANIC!<commit_after>package main

import (
	"archive/zip"
	"bytes"
	"encoding/json"
	"fmt"
	"go/build"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"strings"

	. "github.com/AlekSi/nut"
)

type Config struct {
	Token string
	V     bool
}

const (
	ConfigFileName = ".nut.json"
	ConfigFilePerm = 0644
)

var (
	WorkspaceDir string // current workspace (first path in GOPATH)
	SrcDir       string // src directory in current workspace
	NutDir       string // nut directory in current workspace

	// Maps import prefixes to hosts serving nuts.
	// Three reasons for it:
	//   - third-party nut servers (TODO to be implemented);
	//   - testing with dev_appserver;
	//   - no GAE for second-level domains.
	NutImportPrefixes = map[string]string{"gonuts.io": "www.gonuts.io"}

	config Config
	vHelp  string = fmt.Sprintf("be verbose, may be read from ~/%s", ConfigFileName)
)

func init() {
	log.SetFlags(0)

	srcDirs := build.Default.SrcDirs()[1:]
	if len(srcDirs) == 0 {
		env := os.Getenv("GOPATH")
		if env == "" {
			log.Print("GOPATH environment variable is empty.")
		} else {
			log.Printf("Workspaces in GOPATH environment variable (%s), or their src subpaths don't exist.", env)
		}
		log.Fatal("Setup a workspace as described there: http://golang.org/doc/code.html")
	}

	SrcDir = srcDirs[0]
	WorkspaceDir = filepath.Join(SrcDir, "..")
	NutDir = filepath.Join(WorkspaceDir, "nut")

	u, err := user.Current()
	if err != nil {
		_, err = os.Stat(u.HomeDir)
	}
	if err != nil {
		log.Printf("Warning: Can't detect current user home directory: %s", err)
		return
	}

	path := filepath.Join(u.HomeDir, ConfigFileName)
	b, err := ioutil.ReadFile(path)
	if err == nil {
		err = json.Unmarshal(b, &config)
	}
	if err != nil && !os.IsNotExist(err) {
		log.Printf("Warning: Can't load config from %s: %s\n", path, err)
		config = Config{}
	}

	if !os.IsNotExist(err) {
		b, err = json.MarshalIndent(config, "", "  ")
		if err == nil {
			err = ioutil.WriteFile(path, b, ConfigFilePerm)
		}
		if err != nil {
			log.Printf("Warning: Can't write config to %s: %s\n", path, err)
		}
	}

	env := os.Getenv("GONUTS_IO_SERVER")
	if env != "" {
		NutImportPrefixes["gonuts.io"] = env
	}
}

func PanicIfErr(err error) {
	if err != nil {
		log.Panic(err)
	}
}

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

// TODO common functions there are mess for now

// Read spec file.
func ReadSpec(fileName string) (spec *Spec) {
	f, err := os.Open(fileName)
	PanicIfErr(err)
	defer f.Close()
	spec = new(Spec)
	_, err = spec.ReadFrom(f)
	PanicIfErr(err)
	return
}

// Read nut file.
func ReadNut(fileName string) (b []byte, nf *NutFile) {
	var err error
	b, err = ioutil.ReadFile(fileName)
	PanicIfErr(err)
	nf = new(NutFile)
	_, err = nf.ReadFrom(bytes.NewReader(b))
	PanicIfErr(err)
	return
}

// Write nut to GOPATH/nut/<prefix>/<name>-<version>.nut
func WriteNut(b []byte, prefix string, verbose bool) string {
	nf := new(NutFile)
	_, err := nf.ReadFrom(bytes.NewReader(b))
	PanicIfErr(err)

	// create GOPATH/nut/<prefix>
	dir := filepath.Join(NutDir, prefix)
	PanicIfErr(os.MkdirAll(dir, WorkspaceDirPerm))

	// write file
	dstFilepath := filepath.Join(dir, nf.FileName())
	if verbose {
		log.Printf("Writing %s ...", dstFilepath)
	}
	PanicIfErr(ioutil.WriteFile(dstFilepath, b, NutFilePerm))
	return dstFilepath
}

// Copy nut to GOPATH/nut/<prefix>/<name>-<version>.nut
func CopyNut(nutFilepath string, prefix string, verbose bool) {
	b, nf := ReadNut(nutFilepath)

	// create GOPATH/nut/<prefix>
	dir := filepath.Join(NutDir, prefix)
	PanicIfErr(os.MkdirAll(dir, WorkspaceDirPerm))

	// write file
	dstFilepath := filepath.Join(dir, nf.FileName())
	if verbose {
		log.Printf("Copying %s to %s ...", nutFilepath, dstFilepath)
	}
	PanicIfErr(ioutil.WriteFile(dstFilepath, b, NutFilePerm))
}

// Pack files into nut file with given fileName.
func PackNut(fileName string, files []string, verbose bool) {
	// write nut to temporary file first
	nutFile, err := ioutil.TempFile("", "nut-")
	PanicIfErr(err)
	defer func() {
		if nutFile != nil {
			PanicIfErr(os.Remove(nutFile.Name()))
		}
	}()

	nutWriter := zip.NewWriter(nutFile)
	defer func() {
		if nutWriter != nil {
			PanicIfErr(nutWriter.Close())
		}
	}()

	// add files to nut with all meta information
	for _, file := range files {
		if verbose {
			log.Printf("Packing %s ...", file)
		}

		fi, err := os.Stat(file)
		FatalIfErr(err)

		fh, err := zip.FileInfoHeader(fi)
		PanicIfErr(err)
		fh.Name = file

		f, err := nutWriter.CreateHeader(fh)
		PanicIfErr(err)

		b, err := ioutil.ReadFile(file)
		PanicIfErr(err)

		_, err = f.Write(b)
		PanicIfErr(err)
	}

	err = nutWriter.Close()
	nutWriter = nil
	PanicIfErr(err)

	PanicIfErr(nutFile.Close())

	// move file to specified location and fix permissions
	if verbose {
		log.Printf("Creating %s ...", fileName)
	}
	_, err = os.Stat(fileName)
	if err == nil {
		// required on Windows
		PanicIfErr(os.Remove(fileName))
	}
	PanicIfErr(os.Rename(nutFile.Name(), fileName))
	nutFile = nil
	PanicIfErr(os.Chmod(fileName, NutFilePerm))
}

// Unpack nut file with given fileName into dir. Creates dir if needed. Removes dir first if asked.
func UnpackNut(fileName string, dir string, removeDir, verbose bool) {
	// check dir
	_, err := os.Stat(dir)
	if err == nil && removeDir {
		if verbose {
			log.Printf("Removing existing directory %s ...", dir)
		}
		os.RemoveAll(dir)
	}
	PanicIfErr(os.MkdirAll(dir, WorkspaceDirPerm))

	_, nf := ReadNut(fileName)

	for _, file := range nf.Reader.File {
		if verbose {
			log.Printf("Unpacking %s ...", file.Name)
		}

		rc, err := file.Open()
		PanicIfErr(err)
		defer rc.Close()

		b, err := ioutil.ReadAll(rc)
		PanicIfErr(err)

		PanicIfErr(ioutil.WriteFile(filepath.Join(dir, file.Name), b, file.Mode()))
	}
}

// Call 'go install <path>'.
func InstallPackage(path string, verbose bool) {
	args := []string{"install"}
	if verbose {
		args = append(args, "-v")
	}
	args = append(args, path)
	c := exec.Command("go", args...)
	if verbose {
		log.Printf("Running %q", strings.Join(c.Args, " "))
	}
	out, err := c.CombinedOutput()
	if verbose || err != nil {
		log.Print(string(out))
	}
	FatalIfErr(err)
}

// Return imports present in NutImportPrefixes without altering them.
func NutImports(imports []string) (nuts []string) {
	for _, imp := range imports {
		p := strings.Split(imp, "/")
		if _, ok := NutImportPrefixes[p[0]]; ok {
			nuts = append(nuts, imp)
		}
	}
	return
}
<|endoftext|>


<commit_before>// Package xml minifies XML1.0 following the specifications at http://www.w3.org/TR/xml/.
package xml // import "github.com/tdewolff/minify/xml"

import (
	"io"

	"github.com/tdewolff/minify"
	"github.com/tdewolff/parse"
	"github.com/tdewolff/parse/xml"
)

var (
	isBytes    = []byte("=")
	spaceBytes = []byte(" ")
	voidBytes  = []byte("/>")
)

////////////////////////////////////////////////////////////////

// DefaultMinifier is the default minifier.
var DefaultMinifier = &Minifier{}

// Minifier is an XML minifier.
type Minifier struct {
	KeepWhitespace bool
}

// Minify minifies XML data, it reads from r and writes to w.
func Minify(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error {
	return DefaultMinifier.Minify(m, w, r, params)
}

// Minify minifies XML data, it reads from r and writes to w.
func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]string) error {
	omitSpace := true // on true the next text token must not start with a space

	attrByteBuffer := make([]byte, 0, 64)

	l := xml.NewLexer(r)
	defer l.Restore()

	tb := NewTokenBuffer(l)
	for {
		t := *tb.Shift()
		if t.TokenType == xml.CDATAToken {
			if text, useText := xml.EscapeCDATAVal(&attrByteBuffer, t.Text); useText {
				t.TokenType = xml.TextToken
				t.Data = text
			}
		}
		switch t.TokenType {
		case xml.ErrorToken:
			if l.Err() == io.EOF {
				return nil
			}
			return l.Err()
		case xml.DOCTYPEToken:
			if _, err := w.Write(t.Data); err != nil {
				return err
			}
		case xml.CDATAToken:
			if _, err := w.Write(t.Data); err != nil {
				return err
			}
			if len(t.Text) > 0 && parse.IsWhitespace(t.Text[len(t.Text)-1]) {
				omitSpace = true
			}
		case xml.TextToken:
			t.Data = parse.ReplaceMultipleWhitespace(t.Data)

			// whitespace removal; trim left
			if omitSpace && (t.Data[0] == ' ' || t.Data[0] == '\n') {
				t.Data = t.Data[1:]
			}

			// whitespace removal; trim right
			omitSpace = false
			if len(t.Data) == 0 {
				omitSpace = true
			} else if t.Data[len(t.Data)-1] == ' ' || t.Data[len(t.Data)-1] == '\n' {
				omitSpace = true
				i := 0
				for {
					next := tb.Peek(i)
					// trim if EOF, text token with whitespace begin or block token
					if next.TokenType == xml.ErrorToken {
						t.Data = t.Data[:len(t.Data)-1]
						omitSpace = false
						break
					} else if next.TokenType == xml.TextToken {
						// this only happens when a comment, doctype, cdata startpi tag was in between
						// remove if the text token starts with a whitespace
						if len(next.Data) > 0 && parse.IsWhitespace(next.Data[0]) {
							t.Data = t.Data[:len(t.Data)-1]
							omitSpace = false
						}
						break
					} else if next.TokenType == xml.CDATAToken {
						if len(next.Text) > 0 && parse.IsWhitespace(next.Text[0]) {
							t.Data = t.Data[:len(t.Data)-1]
							omitSpace = false
						}
						break
					} else if next.TokenType == xml.StartTagToken || next.TokenType == xml.EndTagToken {
						if !o.KeepWhitespace {
							t.Data = t.Data[:len(t.Data)-1]
							omitSpace = false
						}
						break
					}
					i++
				}
			}

			if _, err := w.Write(t.Data); err != nil {
				return err
			}
		case xml.StartTagToken:
			if o.KeepWhitespace {
				omitSpace = false
			}
			if _, err := w.Write(t.Data); err != nil {
				return err
			}
		case xml.StartTagPIToken:
			if _, err := w.Write(t.Data); err != nil {
				return err
			}
		case xml.AttributeToken:
			if _, err := w.Write(spaceBytes); err != nil {
				return err
			}
			if _, err := w.Write(t.Text); err != nil {
				return err
			}
			if _, err := w.Write(isBytes); err != nil {
				return err
			}

			if len(t.AttrVal) < 2 {
				if _, err := w.Write(t.AttrVal); err != nil {
					return err
				}
			} else {
				// prefer single or double quotes depending on what occurs more often in value
				val := xml.EscapeAttrVal(&attrByteBuffer, t.AttrVal[1:len(t.AttrVal)-1])
				if _, err := w.Write(val); err != nil {
					return err
				}
			}
		case xml.StartTagCloseToken:
			next := tb.Peek(0)
			skipExtra := false
			if next.TokenType == xml.TextToken && parse.IsAllWhitespace(next.Data) {
				next = tb.Peek(1)
				skipExtra = true
			}
			if next.TokenType == xml.EndTagToken {
				// collapse empty tags to single void tag
				tb.Shift()
				if skipExtra {
					tb.Shift()
				}
				if _, err := w.Write(voidBytes); err != nil {
					return err
				}
			} else {
				if _, err := w.Write(t.Text); err != nil {
					return err
				}
			}
		case xml.StartTagCloseVoidToken:
			if _, err := w.Write(t.Text); err != nil {
				return err
			}
		case xml.StartTagClosePIToken:
			if _, err := w.Write(t.Text); err != nil {
				return err
			}
		case xml.EndTagToken:
			if o.KeepWhitespace {
				omitSpace = false
			}
			if len(t.Data) > 3+len(t.Text) {
				t.Data[2+len(t.Text)] = '>'
				t.Data = t.Data[:3+len(t.Text)]
			}
			if _, err := w.Write(t.Data); err != nil {
				return err
			}
		}
	}
}
<commit_msg>Fuzz bugfix for empty CDATA<commit_after>// Package xml minifies XML1.0 following the specifications at http://www.w3.org/TR/xml/.
package xml // import "github.com/tdewolff/minify/xml"

import (
	"io"

	"github.com/tdewolff/minify"
	"github.com/tdewolff/parse"
	"github.com/tdewolff/parse/xml"
)

var (
	isBytes    = []byte("=")
	spaceBytes = []byte(" ")
	voidBytes  = []byte("/>")
)

////////////////////////////////////////////////////////////////

// DefaultMinifier is the default minifier.
var DefaultMinifier = &Minifier{}

// Minifier is an XML minifier.
type Minifier struct {
	KeepWhitespace bool
}

// Minify minifies XML data, it reads from r and writes to w.
func Minify(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error {
	return DefaultMinifier.Minify(m, w, r, params)
}

// Minify minifies XML data, it reads from r and writes to w.
func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]string) error {
	omitSpace := true // on true the next text token must not start with a space

	attrByteBuffer := make([]byte, 0, 64)

	l := xml.NewLexer(r)
	defer l.Restore()

	tb := NewTokenBuffer(l)
	for {
		t := *tb.Shift()
		if t.TokenType == xml.CDATAToken {
			if len(t.Text) == 0 {
				continue
			}
			if text, useText := xml.EscapeCDATAVal(&attrByteBuffer, t.Text); useText {
				t.TokenType = xml.TextToken
				t.Data = text
			}
		}
		switch t.TokenType {
		case xml.ErrorToken:
			if l.Err() == io.EOF {
				return nil
			}
			return l.Err()
		case xml.DOCTYPEToken:
			if _, err := w.Write(t.Data); err != nil {
				return err
			}
		case xml.CDATAToken:
			if _, err := w.Write(t.Data); err != nil {
				return err
			}
			if len(t.Text) > 0 && parse.IsWhitespace(t.Text[len(t.Text)-1]) {
				omitSpace = true
			}
		case xml.TextToken:
			t.Data = parse.ReplaceMultipleWhitespace(t.Data)

			// whitespace removal; trim left
			if omitSpace && (t.Data[0] == ' ' || t.Data[0] == '\n') {
				t.Data = t.Data[1:]
			}

			// whitespace removal; trim right
			omitSpace = false
			if len(t.Data) == 0 {
				omitSpace = true
			} else if t.Data[len(t.Data)-1] == ' ' || t.Data[len(t.Data)-1] == '\n' {
				omitSpace = true
				i := 0
				for {
					next := tb.Peek(i)
					// trim if EOF, text token with whitespace begin or block token
					if next.TokenType == xml.ErrorToken {
						t.Data = t.Data[:len(t.Data)-1]
						omitSpace = false
						break
					} else if next.TokenType == xml.TextToken {
						// this only happens when a comment, doctype, cdata startpi tag was in between
						// remove if the text token starts with a whitespace
						if len(next.Data) > 0 && parse.IsWhitespace(next.Data[0]) {
							t.Data = t.Data[:len(t.Data)-1]
							omitSpace = false
						}
						break
					} else if next.TokenType == xml.CDATAToken {
						if len(next.Text) > 0 && parse.IsWhitespace(next.Text[0]) {
							t.Data = t.Data[:len(t.Data)-1]
							omitSpace = false
						}
						break
					} else if next.TokenType == xml.StartTagToken || next.TokenType == xml.EndTagToken {
						if !o.KeepWhitespace {
							t.Data = t.Data[:len(t.Data)-1]
							omitSpace = false
						}
						break
					}
					i++
				}
			}

			if _, err := w.Write(t.Data); err != nil {
				return err
			}
		case xml.StartTagToken:
			if o.KeepWhitespace {
				omitSpace = false
			}
			if _, err := w.Write(t.Data); err != nil {
				return err
			}
		case xml.StartTagPIToken:
			if _, err := w.Write(t.Data); err != nil {
				return err
			}
		case xml.AttributeToken:
			if _, err := w.Write(spaceBytes); err != nil {
				return err
			}
			if _, err := w.Write(t.Text); err != nil {
				return err
			}
			if _, err := w.Write(isBytes); err != nil {
				return err
			}

			if len(t.AttrVal) < 2 {
				if _, err := w.Write(t.AttrVal); err != nil {
					return err
				}
			} else {
				// prefer single or double quotes depending on what occurs more often in value
				val := xml.EscapeAttrVal(&attrByteBuffer, t.AttrVal[1:len(t.AttrVal)-1])
				if _, err := w.Write(val); err != nil {
					return err
				}
			}
		case xml.StartTagCloseToken:
			next := tb.Peek(0)
			skipExtra := false
			if next.TokenType == xml.TextToken && parse.IsAllWhitespace(next.Data) {
				next = tb.Peek(1)
				skipExtra = true
			}
			if next.TokenType == xml.EndTagToken {
				// collapse empty tags to single void tag
				tb.Shift()
				if skipExtra {
					tb.Shift()
				}
				if _, err := w.Write(voidBytes); err != nil {
					return err
				}
			} else {
				if _, err := w.Write(t.Text); err != nil {
					return err
				}
			}
		case xml.StartTagCloseVoidToken:
			if _, err := w.Write(t.Text); err != nil {
				return err
			}
		case xml.StartTagClosePIToken:
			if _, err := w.Write(t.Text); err != nil {
				return err
			}
		case xml.EndTagToken:
			if o.KeepWhitespace {
				omitSpace = false
			}
			if len(t.Data) > 3+len(t.Text) {
				t.Data[2+len(t.Text)] = '>'
				t.Data = t.Data[:3+len(t.Text)]
			}
			if _, err := w.Write(t.Data); err != nil {
				return err
			}
		}
	}
}
<|endoftext|>


<commit_before>// This package scrapes the HTML of xvideos.com and gives you information you can use in your go programs
//
// Refer to http://github.com/pdevty/xvideos for more infomation
//
//  package main
//
//  import (
// 		"fmt"
// 		"github.com/pdevty/xvideos"
// 		"log"
//  )
//
//  func main() {
//  	// url to be set xvideos list page
// 		xv, err := xvideos.Get("http://jp.xvideos.com/c/asian_woman-32/")
// 		if err != nil {
// 			log.Fatal(err)
// 		}
//  	for _, v := range xv {
// 			fmt.Println(v.Id, v.Url, v.Duration, v.Rating, v.Thumbnail, v.Title, v.Tags)
// 		}
//  }
package xvideos

import (
	"github.com/PuerkitoBio/goquery"
	"net/url"
	"regexp"
	"runtime"
	"strings"
)

// get func return xvideo data struct
type Xvideo struct {
	Id        string
	Title     string
	Thumbnail string
	Url       string
	Duration  string
	Rating    string
	Tags      []string
}

// get xvideos tags
func getTags(url string) (tags []string, err error) {
	doc, err := goquery.NewDocument(url)
	if err != nil {
		return nil, err
	}
	re := regexp.MustCompile("/tags/[a-zA-Z]+")
	doc.Find("ul#video-tags li a").Each(func(_ int, s *goquery.Selection) {
		tag := s.Text()
		href, _ := s.Attr("href")
		if re.MatchString(href) {
			tags = append(tags, tag)
		}
	})
	return tags, nil
}

// get xvideos information. url to be set xvideos list page
func Get(rawurl string) (xvideos []Xvideo, err error) {
	u, err := url.Parse(rawurl)
	if err != nil {
		return nil, err
	}
	base := u.Scheme + "://" + u.Host
	doc, err := goquery.NewDocument(u.String())
	if err != nil {
		return nil, err
	}
	cpus := runtime.NumCPU()
	runtime.GOMAXPROCS(cpus)
	re := regexp.MustCompile("[0-9]*%")
	ch := make(chan map[int][]string)
	doc.Find("div.thumbBlock").Each(func(i int, s *goquery.Selection) {
		xv := Xvideo{}
		url, _ := s.Find("p a").Attr("href")
		xv.Url = base + url
		go func() {
			tags, _ := getTags(xv.Url)
			ch <- map[int][]string{i: tags}
		}()
		thumbnail, _ := s.Find("img").Attr("src")
		xv.Thumbnail = thumbnail
		xv.Title = s.Find("p a").Text()
		xv.Duration = s.Find("span.duration").Text()
		xv.Rating = re.FindString(s.Find("span.bg").Text())
		id, _ := s.Attr("id")
		xv.Id = strings.TrimLeft(id, "video_")
		xvideos = append(xvideos, xv)
	})
	for i := 0; i < len(xvideos); i++ {
		m := <-ch
		for k := range m {
			xvideos[k].Tags = m[k]
		}
	}
	return xvideos, nil
}
<commit_msg>update comments<commit_after>// This package scrapes the HTML of xvideos.com and gives you information you can use in your go programs
//
// Refer to http://github.com/pdevty/xvideos for more infomation
//
//  package main
//
//  import (
// 	  "fmt"
// 	  "github.com/pdevty/xvideos"
// 	  "log"
//  )
//
//  func main() {
//    // url to be set xvideos list page
// 	  xv, err := xvideos.Get("http://jp.xvideos.com/c/asian_woman-32/")
// 	  if err != nil {
// 	    log.Fatal(err)
// 	  }
//    for _, v := range xv {
// 	    fmt.Println(v.Id, v.Url, v.Duration, v.Rating, v.Thumbnail, v.Title, v.Tags)
// 	  }
//  }
package xvideos

import (
	"github.com/PuerkitoBio/goquery"
	"net/url"
	"regexp"
	"runtime"
	"strings"
)

// get func return xvideo data struct
type Xvideo struct {
	Id        string
	Title     string
	Thumbnail string
	Url       string
	Duration  string
	Rating    string
	Tags      []string
}

// get xvideos tags
func getTags(url string) (tags []string, err error) {
	doc, err := goquery.NewDocument(url)
	if err != nil {
		return nil, err
	}
	re := regexp.MustCompile("/tags/[a-zA-Z]+")
	doc.Find("ul#video-tags li a").Each(func(_ int, s *goquery.Selection) {
		tag := s.Text()
		href, _ := s.Attr("href")
		if re.MatchString(href) {
			tags = append(tags, tag)
		}
	})
	return tags, nil
}

// get xvideos information. url to be set xvideos list page
func Get(rawurl string) (xvideos []Xvideo, err error) {
	u, err := url.Parse(rawurl)
	if err != nil {
		return nil, err
	}
	base := u.Scheme + "://" + u.Host
	doc, err := goquery.NewDocument(u.String())
	if err != nil {
		return nil, err
	}
	cpus := runtime.NumCPU()
	runtime.GOMAXPROCS(cpus)
	re := regexp.MustCompile("[0-9]*%")
	ch := make(chan map[int][]string)
	doc.Find("div.thumbBlock").Each(func(i int, s *goquery.Selection) {
		xv := Xvideo{}
		url, _ := s.Find("p a").Attr("href")
		xv.Url = base + url
		go func() {
			tags, _ := getTags(xv.Url)
			ch <- map[int][]string{i: tags}
		}()
		thumbnail, _ := s.Find("img").Attr("src")
		xv.Thumbnail = thumbnail
		xv.Title = s.Find("p a").Text()
		xv.Duration = s.Find("span.duration").Text()
		xv.Rating = re.FindString(s.Find("span.bg").Text())
		id, _ := s.Attr("id")
		xv.Id = strings.TrimLeft(id, "video_")
		xvideos = append(xvideos, xv)
	})
	for i := 0; i < len(xvideos); i++ {
		m := <-ch
		for k := range m {
			xvideos[k].Tags = m[k]
		}
	}
	return xvideos, nil
}
<|endoftext|>


<commit_before>package paystack

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"time"

	"github.com/mitchellh/mapstructure"
)

const (
	// library version
	version = "0.1.0"

	// defaultHTTPTimeout is the default timeout on the http client
	defaultHTTPTimeout = 60 * time.Second

	// base URL for all Paystack API requests
	baseURL = "https://api.paystack.co"

	// User agent used when communicating with the Paystack API.
	userAgent = "paystack-go/" + version
)

type service struct {
	client *Client
}

// Client manages communication with the Paystack API
type Client struct {
	common service      // Reuse a single struct instead of allocating one for each service on the heap.
	client *http.Client // HTTP client used to communicate with the API.

	// the API Key used to authenticate all Paystack API requests
	key string

	baseURL *url.URL

	logger Logger
	// Services supported by the Paystack API.
	// Miscellaneous actions are directly implemented on the Client object
	Customer     *CustomerService
	Transaction  *TransactionService
	SubAccount   *SubAccountService
	Plan         *PlanService
	Subscription *SubscriptionService
	Page         *PageService
	Settlement   *SettlementService
	Transfer     *TransferService
	Charge       *ChargeService
	Bank         *BankService
	BulkCharge   *BulkChargeService

	LoggingEnabled bool
	Log            Logger
}

// Logger interface for custom loggers
type Logger interface {
	Printf(format string, v ...interface{})
}

// Metadata is an key-value pairs added to Paystack API requests
type Metadata map[string]interface{}

// Response represents arbitrary response data
type Response map[string]interface{}

// RequestValues aliased to url.Values as a workaround
type RequestValues url.Values

// MarshalJSON to handle custom JSON decoding for RequestValues
func (v RequestValues) MarshalJSON() ([]byte, error) {
	m := make(map[string]interface{}, 3)
	for k, val := range v {
		m[k] = val[0]
	}
	return json.Marshal(m)
}

// ListMeta is pagination metadata for paginated responses from the Paystack API
type ListMeta struct {
	Total     int `json:"total"`
	Skipped   int `json:"skipped"`
	PerPage   int `json:"perPage"`
	Page      int `json:"page"`
	PageCount int `json:"pageCount"`
}

// NewClient creates a new Paystack API client with the given API key
// and HTTP client, allowing overriding of the HTTP client to use.
// This is useful if you're running in a Google AppEngine environment
// where the http.DefaultClient is not available.
func NewClient(key string, httpClient *http.Client) *Client {
	if httpClient == nil {
		httpClient = &http.Client{Timeout: defaultHTTPTimeout}
	}

	u, _ := url.Parse(baseURL)
	c := &Client{
		client:         httpClient,
		key:            key,
		baseURL:        u,
		LoggingEnabled: true,
		Log:            log.New(os.Stderr, "", log.LstdFlags),
	}

	c.common.client = c
	c.Customer = (*CustomerService)(&c.common)
	c.Transaction = (*TransactionService)(&c.common)
	c.SubAccount = (*SubAccountService)(&c.common)
	c.Plan = (*PlanService)(&c.common)
	c.Subscription = (*SubscriptionService)(&c.common)
	c.Page = (*PageService)(&c.common)
	c.Settlement = (*SettlementService)(&c.common)
	c.Transfer = (*TransferService)(&c.common)
	c.Charge = (*ChargeService)(&c.common)
	c.Bank = (*BankService)(&c.common)
	c.BulkCharge = (*BulkChargeService)(&c.common)

	return c
}

// Call actually does the HTTP request to Paystack API
func (c *Client) Call(method, path string, body, v interface{}) error {
	var buf io.ReadWriter
	if body != nil {
		buf = new(bytes.Buffer)
		err := json.NewEncoder(buf).Encode(body)
		if err != nil {
			return err
		}
	}
	u, _ := c.baseURL.Parse(path)
	req, err := http.NewRequest(method, u.String(), buf)

	if err != nil {
		if c.LoggingEnabled {
			c.Log.Printf("Cannot create Paystack request: %v\n", err)
		}
		return err
	}

	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	req.Header.Set("Authorization", "Bearer "+c.key)
	if ua := req.Header.Get("User-Agent"); ua == "" {
		req.Header.Set("User-Agent", userAgent)
	} else {
		req.Header.Set("User-Agent", userAgent+" "+ua)
	}

	if c.LoggingEnabled {
		c.Log.Printf("Requesting %v %v%v\n", req.Method, req.URL.Host, req.URL.Path)
		c.Log.Printf("POST request data %v\n", buf)
	}

	start := time.Now()

	resp, err := c.client.Do(req)
	if err != nil {
		return err
	}

	if c.LoggingEnabled {
		c.Log.Printf("Completed in %v\n", time.Since(start))
	}

	defer resp.Body.Close()
	return c.decodeResponse(resp, v)
}

// ResolveCardBIN docs https://developers.paystack.co/v1.0/reference#resolve-card-bin
func (c *Client) ResolveCardBIN(bin int) (Response, error) {
	u := fmt.Sprintf("/decision/bin/%d", bin)
	resp := Response{}
	err := c.Call("GET", u, nil, &resp)

	return resp, err
}

// CheckBalance docs https://developers.paystack.co/v1.0/reference#resolve-card-bin
func (c *Client) CheckBalance() (Response, error) {
	resp := Response{}
	err := c.Call("GET", "balance", nil, &resp)
	// check balance 'data' node is an array
	resp2 := resp["data"].([]interface{})[0].(map[string]interface{})
	return resp2, err
}

// GetSessionTimeout fetches payment session timeout
func (c *Client) GetSessionTimeout() (Response, error) {
	resp := Response{}
	err := c.Call("GET", "/integration/payment_session_timeout", nil, &resp)
	return resp, err
}

// UpdateSessionTimeout updates payment session timeout
func (c *Client) UpdateSessionTimeout(timeout int) (Response, error) {
	data := url.Values{}
	data.Add("timeout", string(timeout))
	resp := Response{}
	u := "/integration/payment_session_timeout"
	err := c.Call("PUT", u, data, &resp)
	return resp, err
}

// INTERNALS
func paginateURL(path string, count, offset int) string {
	return fmt.Sprintf("%s?perPage=%d&page=%d", path, count, offset)
}

func mapstruct(data interface{}, v interface{}) error {
	config := &mapstructure.DecoderConfig{
		Result:           v,
		TagName:          "json",
		WeaklyTypedInput: true,
	}
	decoder, err := mapstructure.NewDecoder(config)
	if err = decoder.Decode(data); err != nil {
		return err
	}
	return nil
}

func mustGetTestKey() string {
	key := os.Getenv("PAYSTACK_KEY")

	if len(key) == 0 {
		panic("PAYSTACK_KEY environment variable is not set\n")
	}

	return key
}

// decodeResponse decodes the JSON response from the Twitter API.
// The actual response will be written to the `v` parameter
func (c *Client) decodeResponse(httpResp *http.Response, v interface{}) error {
	var resp Response
	respBody, err := ioutil.ReadAll(httpResp.Body)
	json.Unmarshal(respBody, &resp)

	if status, _ := resp["status"].(bool); !status || httpResp.StatusCode >= 400 {
		if c.LoggingEnabled {
			c.Log.Printf("Paystack error: %+v", err)
		}
		return newAPIError(httpResp)
	}

	if c.LoggingEnabled {
		c.Log.Printf("Paystack response: %v\n", resp)
	}

	if data, ok := resp["data"]; ok {
		switch t := resp["data"].(type) {
		case map[string]interface{}:
			return mapstruct(data, v)
		default:
			_ = t
			return mapstruct(resp, v)
		}
	}
	// if response data does not contain data key, map entire response to v
	return mapstruct(resp, v)
}
<commit_msg>minor cosmetic refactoring<commit_after>package paystack

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"time"

	"github.com/mitchellh/mapstructure"
)

const (
	// library version
	version = "0.1.0"

	// defaultHTTPTimeout is the default timeout on the http client
	defaultHTTPTimeout = 60 * time.Second

	// base URL for all Paystack API requests
	baseURL = "https://api.paystack.co"

	// User agent used when communicating with the Paystack API.
	userAgent = "paystack-go/" + version
)

type service struct {
	client *Client
}

// Client manages communication with the Paystack API
type Client struct {
	common service      // Reuse a single struct instead of allocating one for each service on the heap.
	client *http.Client // HTTP client used to communicate with the API.

	// the API Key used to authenticate all Paystack API requests
	key string

	baseURL *url.URL

	logger Logger
	// Services supported by the Paystack API.
	// Miscellaneous actions are directly implemented on the Client object
	Customer     *CustomerService
	Transaction  *TransactionService
	SubAccount   *SubAccountService
	Plan         *PlanService
	Subscription *SubscriptionService
	Page         *PageService
	Settlement   *SettlementService
	Transfer     *TransferService
	Charge       *ChargeService
	Bank         *BankService
	BulkCharge   *BulkChargeService

	LoggingEnabled bool
	Log            Logger
}

// Logger interface for custom loggers
type Logger interface {
	Printf(format string, v ...interface{})
}

// Metadata is an key-value pairs added to Paystack API requests
type Metadata map[string]interface{}

// Response represents arbitrary response data
type Response map[string]interface{}

// RequestValues aliased to url.Values as a workaround
type RequestValues url.Values

// MarshalJSON to handle custom JSON decoding for RequestValues
func (v RequestValues) MarshalJSON() ([]byte, error) {
	m := make(map[string]interface{}, 3)
	for k, val := range v {
		m[k] = val[0]
	}
	return json.Marshal(m)
}

// ListMeta is pagination metadata for paginated responses from the Paystack API
type ListMeta struct {
	Total     int `json:"total"`
	Skipped   int `json:"skipped"`
	PerPage   int `json:"perPage"`
	Page      int `json:"page"`
	PageCount int `json:"pageCount"`
}

// NewClient creates a new Paystack API client with the given API key
// and HTTP client, allowing overriding of the HTTP client to use.
// This is useful if you're running in a Google AppEngine environment
// where the http.DefaultClient is not available.
func NewClient(key string, httpClient *http.Client) *Client {
	if httpClient == nil {
		httpClient = &http.Client{Timeout: defaultHTTPTimeout}
	}

	u, _ := url.Parse(baseURL)
	c := &Client{
		client:         httpClient,
		key:            key,
		baseURL:        u,
		LoggingEnabled: true,
		Log:            log.New(os.Stderr, "", log.LstdFlags),
	}

	c.common.client = c
	c.Customer = (*CustomerService)(&c.common)
	c.Transaction = (*TransactionService)(&c.common)
	c.SubAccount = (*SubAccountService)(&c.common)
	c.Plan = (*PlanService)(&c.common)
	c.Subscription = (*SubscriptionService)(&c.common)
	c.Page = (*PageService)(&c.common)
	c.Settlement = (*SettlementService)(&c.common)
	c.Transfer = (*TransferService)(&c.common)
	c.Charge = (*ChargeService)(&c.common)
	c.Bank = (*BankService)(&c.common)
	c.BulkCharge = (*BulkChargeService)(&c.common)

	return c
}

// Call actually does the HTTP request to Paystack API
func (c *Client) Call(method, path string, body, v interface{}) error {
	var buf io.ReadWriter
	if body != nil {
		buf = new(bytes.Buffer)
		err := json.NewEncoder(buf).Encode(body)
		if err != nil {
			return err
		}
	}
	u, _ := c.baseURL.Parse(path)
	req, err := http.NewRequest(method, u.String(), buf)

	if err != nil {
		if c.LoggingEnabled {
			c.Log.Printf("Cannot create Paystack request: %v\n", err)
		}
		return err
	}

	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	req.Header.Set("Authorization", "Bearer "+c.key)
	if ua := req.Header.Get("User-Agent"); ua == "" {
		req.Header.Set("User-Agent", userAgent)
	} else {
		req.Header.Set("User-Agent", userAgent+" "+ua)
	}

	if c.LoggingEnabled {
		c.Log.Printf("Requesting %v %v%v\n", req.Method, req.URL.Host, req.URL.Path)
		c.Log.Printf("POST request data %v\n", buf)
	}

	start := time.Now()

	resp, err := c.client.Do(req)
	if err != nil {
		return err
	}

	if c.LoggingEnabled {
		c.Log.Printf("Completed in %v\n", time.Since(start))
	}

	defer resp.Body.Close()
	return c.decodeResponse(resp, v)
}

// ResolveCardBIN docs https://developers.paystack.co/v1.0/reference#resolve-card-bin
func (c *Client) ResolveCardBIN(bin int) (Response, error) {
	u := fmt.Sprintf("/decision/bin/%d", bin)
	resp := Response{}
	err := c.Call("GET", u, nil, &resp)

	return resp, err
}

// CheckBalance docs https://developers.paystack.co/v1.0/reference#resolve-card-bin
func (c *Client) CheckBalance() (Response, error) {
	resp := Response{}
	err := c.Call("GET", "balance", nil, &resp)
	// check balance 'data' node is an array
	resp2 := resp["data"].([]interface{})[0].(map[string]interface{})
	return resp2, err
}

// GetSessionTimeout fetches payment session timeout
func (c *Client) GetSessionTimeout() (Response, error) {
	resp := Response{}
	err := c.Call("GET", "/integration/payment_session_timeout", nil, &resp)
	return resp, err
}

// UpdateSessionTimeout updates payment session timeout
func (c *Client) UpdateSessionTimeout(timeout int) (Response, error) {
	data := url.Values{}
	data.Add("timeout", string(timeout))
	resp := Response{}
	u := "/integration/payment_session_timeout"
	err := c.Call("PUT", u, data, &resp)
	return resp, err
}

// INTERNALS
func paginateURL(path string, count, offset int) string {
	return fmt.Sprintf("%s?perPage=%d&page=%d", path, count, offset)
}

func mapstruct(data interface{}, v interface{}) error {
	config := &mapstructure.DecoderConfig{
		Result:           v,
		TagName:          "json",
		WeaklyTypedInput: true,
	}
	decoder, err := mapstructure.NewDecoder(config)
	if err != nil {
		return err
	}
	err = decoder.Decode(data);
	return err
}

func mustGetTestKey() string {
	key := os.Getenv("PAYSTACK_KEY")

	if len(key) == 0 {
		panic("PAYSTACK_KEY environment variable is not set\n")
	}

	return key
}

// decodeResponse decodes the JSON response from the Twitter API.
// The actual response will be written to the `v` parameter
func (c *Client) decodeResponse(httpResp *http.Response, v interface{}) error {
	var resp Response
	respBody, err := ioutil.ReadAll(httpResp.Body)
	json.Unmarshal(respBody, &resp)

	if status, _ := resp["status"].(bool); !status || httpResp.StatusCode >= 400 {
		if c.LoggingEnabled {
			c.Log.Printf("Paystack error: %+v", err)
		}
		return newAPIError(httpResp)
	}

	if c.LoggingEnabled {
		c.Log.Printf("Paystack response: %v\n", resp)
	}

	if data, ok := resp["data"]; ok {
		switch t := resp["data"].(type) {
		case map[string]interface{}:
			return mapstruct(data, v)
		default:
			_ = t
			return mapstruct(resp, v)
		}
	}
	// if response data does not contain data key, map entire response to v
	return mapstruct(resp, v)
}
<|endoftext|>