<commit_before>// Prometheus tail_exporter - heavily heavily modified from the basic
// graphite_exporter by bbrazil.

package main

import (
	"bufio"
	"bytes"
	"flag"
	"io"
	"net"
	"net/http"
	"strconv"

	"github.com/hpcloud/tail"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/log"
	"github.com/wrouesnel/tail_exporter/config"
	"os"
	"strings"
	"sync"
)

// Namespace is the metric namespace of this collector
const Namespace string = "tail_collector"

var (
	listeningAddress = flag.String("web.listen-address", ":9130", "Address on which to expose metrics.")
	metricsPath      = flag.String("web.telemetry-path", "/metrics", "Path under which to expose Prometheus metrics.")
	collectorAddress = flag.String("collector.listen-address", ":9129", "TCP and UDP address on which to accept lines")
	configFile       = flag.String("config.file", "", "Configuration file path")
)

// TailCollector implements the main collector process.
type TailCollector struct {
	cfg     *config.Config                   // Configuration
	metrics map[string]*prometheus.MetricVec // map of initialized metrics
	mmtx    *sync.Mutex                      // Metric initialization lock for map writes

	regexCh []chan string // list of regex processors

	numMetrics    prometheus.Gauge   // our own metric + lets initialization succeed
	ingestedLines prometheus.Counter // number of lines we've ingested
	//rejectedLines *prometheus.CounterVec // number of rejected values
	//timedoutMetrics prometheus.Counter // number of metrics which have been dropped due to internal timeouts
}

func newTailCollector(cfg *config.Config) *TailCollector {
	c := TailCollector{}
	c.cfg = cfg
	c.metrics = make(map[string]*prometheus.MetricVec)
	c.mmtx = new(sync.Mutex)
	c.regexCh = make([]chan string, len(cfg.MetricConfigs))

	// Initialize regex processors
	for idx, mp := range cfg.MetricConfigs {
		ch := make(chan string, 1)
		c.regexCh[idx] = ch
		go c.lineProcessor(ch, mp)
	}

	// Set constant metrics
	c.numMetrics = prometheus.NewGauge(
		prometheus.GaugeOpts{
			Namespace: Namespace,
			Name:      "metric_regexes",
			Help:      "currently configured number of metric regexes",
		},
	)

	c.ingestedLines = prometheus.NewCounter(
		prometheus.CounterOpts{
			Namespace: Namespace,
			Name:      "ingested_lines_total",
			Help:      "total number of lines ingested by collection inputs",
		},
	)

	//	c.rejectedLines = prometheus.NewCounterVec(
	//		prometheus.CounterOpts{
	//			Namespace: Namespace,
	//			Name: "rejected_lines_total",
	//			Help: "total number of lines rejected during parsing",
	//		},
	//		[]string{"reason"},
	//	)

	c.numMetrics.Set(float64(len(cfg.MetricConfigs)))
	return &c
}

// Reads until the current connection is closed
func (c *TailCollector) processReader(reader io.Reader) {
	lineScanner := bufio.NewScanner(reader)
	for {
		if ok := lineScanner.Scan(); !ok {
			break
		}
		c.IngestLine(lineScanner.Text())
	}
}

// IngestLine consumes a line from the file tailing engine
func (c *TailCollector) IngestLine(line string) {
	c.ingestedLines.Inc()
	// Dispatch the line to all active regex parsers
	for _, ch := range c.regexCh {
		ch <- line
	}
}

func (c *TailCollector) initalizeMetric(cfg *config.MetricParser) *prometheus.MetricVec {
	// Lock the map to writes (reads should be okay I think?)
	c.mmtx.Lock()
	defer c.mmtx.Unlock()

	// Setup the label set
	labelSet := make([]string, len(cfg.Labels))
	for idx, v := range cfg.Labels {
		labelSet[idx] = v.Name
	}

	var metricVec *prometheus.MetricVec

	switch cfg.Type {
	case config.METRIC_COUNTER:
		metricVec = prometheus.NewCounterVec(
			prometheus.CounterOpts{
				Name: cfg.Name,
				Help: cfg.Help,
			},
			labelSet,
		).MetricVec

	case config.METRIC_GAUGE:
		metricVec = prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Name: cfg.Name,
				Help: cfg.Help,
			},
			labelSet,
		).MetricVec
	default:
		metricVec = prometheus.NewUntypedVec(
			prometheus.UntypedOpts{
				Name: cfg.Name,
				Help: cfg.Help,
			},
			labelSet,
		).MetricVec
	}
	c.metrics[cfg.Name] = metricVec
	return metricVec
}

// Processes lines through the regexes we have loaded
func (c *TailCollector) lineProcessor(lineCh chan string, cfg config.MetricParser) {
	// Run all regexes
	labelValues := make([]string, len(cfg.Labels))

processloop:
	for line := range lineCh {
		m := cfg.Regex.MatcherString(line, 0)
		if !m.Matches() {
			continue
		}
		// Got a match. See if we have this metric already.
		var metricVec *prometheus.MetricVec
		var ok bool
		metricVec, ok = c.metrics[cfg.Name]
		if !ok {
			metricVec = c.initalizeMetric(&cfg)
		}

		// Calculate label values
		for idx, v := range cfg.Labels {
			switch v.Value.FieldType {
			case config.LVALUE_LITERAL:
				labelValues[idx] = v.Value.Literal
			case config.LVALUE_CAPTUREGROUP_NAMED:
				if !m.NamedPresent(v.Value.CaptureGroupName) {
					if v.HasDefault {
						labelValues[idx] = v.Default
					} else {
						log.With("name", cfg.Name).
							With("group_name", v.Value.CaptureGroupName).
							With("line", line).
							Warnln("Dropping unconvertible capture value")
						continue processloop
					}
				}
				labelValues[idx] = m.NamedString(v.Value.CaptureGroupName)
			case config.LVALUE_CAPTUREGROUP:
				if v.HasDefault && (m.GroupString(v.Value.CaptureGroup) == "") {
					labelValues[idx] = v.Default
				} else {
					labelValues[idx] = m.GroupString(v.Value.CaptureGroup)
				}
			default:
				log.Warnln("Got no recognized type - assuming literal")
				labelValues[idx] = v.Value.Literal
			}
		}

		// Get the metric we will adjust
		metric := metricVec.WithLabelValues(labelValues...)

		switch cfg.Value.FieldType {
		case config.VALUE_LITERAL:
			switch t := metric.(type) {
			case prometheus.Gauge:
				t.Set(cfg.Value.Literal)
			case prometheus.Counter:
				t.Set(cfg.Value.Literal)
			case prometheus.Untyped:
				t.Set(cfg.Value.Literal)
			default:
				log.With("name", cfg.Name).Errorf("Unknown type for metric: %T", t)
			}

		case config.VALUE_CAPTUREGROUP:
			valstr := m.GroupString(cfg.Value.CaptureGroup)
			val, err := strconv.ParseFloat(valstr, 64)
			if err != nil {
				log.With("name", cfg.Name).
					With("group_name", cfg.Value.CaptureGroup).
					With("line", line).
					With("value", valstr).
					Warnln("Dropping line with unconvertible capture value")
				continue
			}

			switch t := metric.(type) {
			case prometheus.Gauge:
				t.Set(val)
			case prometheus.Counter:
				t.Add(val)
			case prometheus.Untyped:
				t.Set(val)
			default:
				log.With("name", cfg.Name).Errorf("Unknown type for metric: %T", t)
			}

		case config.VALUE_CAPTUREGROUP_NAMED:
			if !m.NamedPresent(cfg.Value.CaptureGroupName) {
				log.With("name", cfg.Name).
					With("group_name", cfg.Value.CaptureGroup).
					With("line", line).
					Warnln("Dropping line with missing capture value")
				continue
			}
			valstr := m.NamedString(cfg.Value.CaptureGroupName)
			val, err := strconv.ParseFloat(valstr, 64)
			if err != nil {
				log.With("name", cfg.Name).
					With("group_name", cfg.Value.CaptureGroupName).
					With("line", line).
					With("value", valstr).
					Warnln("Dropping line with unconvertible capture value")
				continue
			}

			switch t := metric.(type) {
			case prometheus.Gauge:
				t.Set(val)
			case prometheus.Counter:
				t.Set(val)
			case prometheus.Untyped:
				t.Set(val)
			default:
				log.With("name", cfg.Name).Errorf("Unknown type for metric: %T", t)
			}

		case config.VALUE_INC:
			switch t := metric.(type) {
			case prometheus.Gauge:
				t.Inc()
			case prometheus.Counter:
				t.Inc()
			case prometheus.Untyped:
				t.Inc()
			default:
				log.With("name", cfg.Name).Errorf("Unknown type for metric: %T", t)
			}

		case config.VALUE_SUB:
			switch t := metric.(type) {
			case prometheus.Gauge:
				t.Dec()
			case prometheus.Counter:
				// Subtract means reset for a counter
				t.Reset()
			case prometheus.Untyped:
				t.Dec()
			default:
				log.With("name", cfg.Name).Errorf("Unknown type for metric: %T", t)
			}
		}
	}
}

// Collect implements prometheus.Collector.
func (c *TailCollector) Collect(ch chan<- prometheus.Metric) {
	c.numMetrics.Collect(ch)
	c.ingestedLines.Collect(ch)
	//c.rejectedLines.Collect(ch)

	for _, v := range c.metrics {
		v.Collect(ch)
	}
}

// Describe implements prometheus.Collector.
func (c *TailCollector) Describe(ch chan<- *prometheus.Desc) {
	c.numMetrics.Describe(ch)
	c.ingestedLines.Describe(ch)
	//c.rejectedLines.Describe(ch)

	for _, v := range c.metrics {
		v.Describe(ch)
	}
}

func main() {
	flag.Parse()
	http.Handle(*metricsPath, prometheus.Handler())

	cfg, err := config.LoadFile(*configFile)
	if err != nil {
		log.Fatalln("Configuration file could not be read.", err)
	}

	c := newTailCollector(cfg)
	prometheus.MustRegister(c)

	// If args then start file/fifo collectors
	if len(flag.Args()) > 0 {
		for _, filename := range flag.Args() {
			go func(filename string) {
				var isPipe bool
				st, err := os.Stat(filename)
				if err == nil {
					if st.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
						isPipe = true
					}
				} else {
					isPipe = false
				}

				t, err := tail.TailFile(filename, tail.Config{
					Location: &tail.SeekInfo{0, os.SEEK_END},
					ReOpen:   true,
					Follow:   isPipe,
				})
				if err != nil {
					log.Errorln("Error tailing file:", filename)
				}

				for line := range t.Lines {
					c.IngestLine(line.Text)
				}

			}(filename)
		}
	}

	// If collector address present, then start port collector.
	if *collectorAddress != "" {
		tcpSock, err := net.Listen("tcp", *collectorAddress)
		if err != nil {
			log.Fatalf("Error binding to TCP socket: %s", err)
		}
		go func() {
			for {
				conn, err := tcpSock.Accept()
				if err != nil {
					log.Errorf("Error accepting TCP connection: %s", err)
					continue
				}
				go func() {
					defer conn.Close()
					c.processReader(conn)
				}()
			}
		}()

		udpAddress, err := net.ResolveUDPAddr("udp", *collectorAddress)
		if err != nil {
			log.Fatalf("Error resolving UDP address: %s", err)
		}
		udpSock, err := net.ListenUDP("udp", udpAddress)
		if err != nil {
			log.Fatalf("Error listening to UDP address: %s", err)
		}
		go func() {
			defer udpSock.Close()
			for {
				buf := make([]byte, 65536)
				chars, srcAddress, err := udpSock.ReadFromUDP(buf)
				if err != nil {
					log.Errorf("Error reading UDP packet from %s: %s", srcAddress, err)
					continue
				}
				go c.processReader(bytes.NewReader(buf[0:chars]))
			}
		}()
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`
      <title>Tail Exporter</title>

TCP/UDP Tail Exporter

Accepting raw lines over TCP and UDP on ` + *collectorAddress + `

Watching files for lines:` + strings.Join(flag.Args(), ", ") + `

<a href="` + *metricsPath + `">Metrics

Config

` +
			cfg.Original +
			`

      `))
	})

	log.Infof("Starting Server: %s", *listeningAddress)
	http.ListenAndServe(*listeningAddress, nil)
}
<commit_msg>Use the SettableCounter type to allow for flexible counter metrics.<commit_after>// Prometheus tail_exporter - heavily heavily modified from the basic
// graphite_exporter by bbrazil.

package main

import (
	"bufio"
	"bytes"
	"flag"
	"io"
	"net"
	"net/http"
	"strconv"

	"github.com/hpcloud/tail"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/common/log"
	"github.com/wrouesnel/tail_exporter/config"
	"os"
	"strings"
	"sync"
)

// Namespace is the metric namespace of this collector
const Namespace string = "tail_collector"

var (
	listeningAddress = flag.String("web.listen-address", ":9130", "Address on which to expose metrics.")
	metricsPath      = flag.String("web.telemetry-path", "/metrics", "Path under which to expose Prometheus metrics.")
	collectorAddress = flag.String("collector.listen-address", ":9129", "TCP and UDP address on which to accept lines")
	configFile       = flag.String("config.file", "", "Configuration file path")
)

// TailCollector implements the main collector process.
type TailCollector struct {
	cfg     *config.Config                   // Configuration
	metrics map[string]*prometheus.MetricVec // map of initialized metrics
	mmtx    *sync.Mutex                      // Metric initialization lock for map writes

	regexCh []chan string // list of regex processors

	numMetrics    prometheus.Gauge   // our own metric + lets initialization succeed
	ingestedLines prometheus.Counter // number of lines we've ingested
	//rejectedLines *prometheus.CounterVec // number of rejected values
	//timedoutMetrics prometheus.Counter // number of metrics which have been dropped due to internal timeouts
}

func newTailCollector(cfg *config.Config) *TailCollector {
	c := TailCollector{}
	c.cfg = cfg
	c.metrics = make(map[string]*prometheus.MetricVec)
	c.mmtx = new(sync.Mutex)
	c.regexCh = make([]chan string, len(cfg.MetricConfigs))

	// Initialize regex processors
	for idx, mp := range cfg.MetricConfigs {
		ch := make(chan string, 1)
		c.regexCh[idx] = ch
		go c.lineProcessor(ch, mp)
	}

	// Set constant metrics
	c.numMetrics = prometheus.NewGauge(
		prometheus.GaugeOpts{
			Namespace: Namespace,
			Name:      "metric_regexes",
			Help:      "currently configured number of metric regexes",
		},
	)

	c.ingestedLines = prometheus.NewCounter(
		prometheus.CounterOpts{
			Namespace: Namespace,
			Name:      "ingested_lines_total",
			Help:      "total number of lines ingested by collection inputs",
		},
	)

	//	c.rejectedLines = prometheus.NewCounterVec(
	//		prometheus.CounterOpts{
	//			Namespace: Namespace,
	//			Name: "rejected_lines_total",
	//			Help: "total number of lines rejected during parsing",
	//		},
	//		[]string{"reason"},
	//	)

	c.numMetrics.Set(float64(len(cfg.MetricConfigs)))
	return &c
}

// Reads until the current connection is closed
func (c *TailCollector) processReader(reader io.Reader) {
	lineScanner := bufio.NewScanner(reader)
	for {
		if ok := lineScanner.Scan(); !ok {
			break
		}
		c.IngestLine(lineScanner.Text())
	}
}

// IngestLine consumes a line from the file tailing engine
func (c *TailCollector) IngestLine(line string) {
	c.ingestedLines.Inc()
	// Dispatch the line to all active regex parsers
	for _, ch := range c.regexCh {
		ch <- line
	}
}

func (c *TailCollector) initalizeMetric(cfg *config.MetricParser) *prometheus.MetricVec {
	// Lock the map to writes (reads should be okay I think?)
	c.mmtx.Lock()
	defer c.mmtx.Unlock()

	// Setup the label set
	labelSet := make([]string, len(cfg.Labels))
	for idx, v := range cfg.Labels {
		labelSet[idx] = v.Name
	}

	var metricVec *prometheus.MetricVec

	switch cfg.Type {
	case config.METRIC_COUNTER:
		metricVec = prometheus.NewSettableCounter(
			prometheus.CounterOpts{
				Name: cfg.Name,
				Help: cfg.Help,
			},
			labelSet,
		).MetricVec

	case config.METRIC_GAUGE:
		metricVec = prometheus.NewGaugeVec(
			prometheus.GaugeOpts{
				Name: cfg.Name,
				Help: cfg.Help,
			},
			labelSet,
		).MetricVec
	default:
		metricVec = prometheus.NewUntypedVec(
			prometheus.UntypedOpts{
				Name: cfg.Name,
				Help: cfg.Help,
			},
			labelSet,
		).MetricVec
	}
	c.metrics[cfg.Name] = metricVec
	return metricVec
}

// Processes lines through the regexes we have loaded
func (c *TailCollector) lineProcessor(lineCh chan string, cfg config.MetricParser) {
	// Run all regexes
	labelValues := make([]string, len(cfg.Labels))

processloop:
	for line := range lineCh {
		m := cfg.Regex.MatcherString(line, 0)
		if !m.Matches() {
			continue
		}
		// Got a match. See if we have this metric already.
		var metricVec *prometheus.MetricVec
		var ok bool
		metricVec, ok = c.metrics[cfg.Name]
		if !ok {
			metricVec = c.initalizeMetric(&cfg)
		}

		// Calculate label values
		for idx, v := range cfg.Labels {
			switch v.Value.FieldType {
			case config.LVALUE_LITERAL:
				labelValues[idx] = v.Value.Literal
			case config.LVALUE_CAPTUREGROUP_NAMED:
				if !m.NamedPresent(v.Value.CaptureGroupName) {
					if v.HasDefault {
						labelValues[idx] = v.Default
					} else {
						log.With("name", cfg.Name).
							With("group_name", v.Value.CaptureGroupName).
							With("line", line).
							Warnln("Dropping unconvertible capture value")
						continue processloop
					}
				}
				labelValues[idx] = m.NamedString(v.Value.CaptureGroupName)
			case config.LVALUE_CAPTUREGROUP:
				if v.HasDefault && (m.GroupString(v.Value.CaptureGroup) == "") {
					labelValues[idx] = v.Default
				} else {
					labelValues[idx] = m.GroupString(v.Value.CaptureGroup)
				}
			default:
				log.Warnln("Got no recognized type - assuming literal")
				labelValues[idx] = v.Value.Literal
			}
		}

		// Get the metric we will adjust
		metric := metricVec.WithLabelValues(labelValues...)

		switch cfg.Value.FieldType {
		case config.VALUE_LITERAL:
			switch t := metric.(type) {
			case prometheus.Gauge:
				t.Set(cfg.Value.Literal)
			case prometheus.Counter:
				t.Set(cfg.Value.Literal)
			case prometheus.Untyped:
				t.Set(cfg.Value.Literal)
			default:
				log.With("name", cfg.Name).Errorf("Unknown type for metric: %T", t)
			}

		case config.VALUE_CAPTUREGROUP:
			valstr := m.GroupString(cfg.Value.CaptureGroup)
			val, err := strconv.ParseFloat(valstr, 64)
			if err != nil {
				log.With("name", cfg.Name).
					With("group_name", cfg.Value.CaptureGroup).
					With("line", line).
					With("value", valstr).
					Warnln("Dropping line with unconvertible capture value")
				continue
			}

			switch t := metric.(type) {
			case prometheus.Gauge:
				t.Set(val)
			case prometheus.Counter:
				t.Add(val)
			case prometheus.Untyped:
				t.Set(val)
			default:
				log.With("name", cfg.Name).Errorf("Unknown type for metric: %T", t)
			}

		case config.VALUE_CAPTUREGROUP_NAMED:
			if !m.NamedPresent(cfg.Value.CaptureGroupName) {
				log.With("name", cfg.Name).
					With("group_name", cfg.Value.CaptureGroup).
					With("line", line).
					Warnln("Dropping line with missing capture value")
				continue
			}
			valstr := m.NamedString(cfg.Value.CaptureGroupName)
			val, err := strconv.ParseFloat(valstr, 64)
			if err != nil {
				log.With("name", cfg.Name).
					With("group_name", cfg.Value.CaptureGroupName).
					With("line", line).
					With("value", valstr).
					Warnln("Dropping line with unconvertible capture value")
				continue
			}

			switch t := metric.(type) {
			case prometheus.Gauge:
				t.Set(val)
			case prometheus.SettableCounter:
				t.Set(val)
			case prometheus.Untyped:
				t.Set(val)
			default:
				log.With("name", cfg.Name).Errorf("Unknown type for metric: %T", t)
			}

		case config.VALUE_INC:
			switch t := metric.(type) {
			case prometheus.Gauge:
				t.Inc()
			case prometheus.SettableCounter:
				t.Inc()
			case prometheus.Untyped:
				t.Inc()
			default:
				log.With("name", cfg.Name).Errorf("Unknown type for metric: %T", t)
			}

		case config.VALUE_SUB:
			switch t := metric.(type) {
			case prometheus.Gauge:
				t.Dec()
			case prometheus.SettableCounter:
				// Subtract means reset for a counter
				t.Reset()
			case prometheus.Untyped:
				t.Dec()
			default:
				log.With("name", cfg.Name).Errorf("Unknown type for metric: %T", t)
			}
		}
	}
}

// Collect implements prometheus.Collector.
func (c *TailCollector) Collect(ch chan<- prometheus.Metric) {
	c.numMetrics.Collect(ch)
	c.ingestedLines.Collect(ch)
	//c.rejectedLines.Collect(ch)

	for _, v := range c.metrics {
		v.Collect(ch)
	}
}

// Describe implements prometheus.Collector.
func (c *TailCollector) Describe(ch chan<- *prometheus.Desc) {
	c.numMetrics.Describe(ch)
	c.ingestedLines.Describe(ch)
	//c.rejectedLines.Describe(ch)

	for _, v := range c.metrics {
		v.Describe(ch)
	}
}

func main() {
	flag.Parse()
	http.Handle(*metricsPath, prometheus.Handler())

	cfg, err := config.LoadFile(*configFile)
	if err != nil {
		log.Fatalln("Configuration file could not be read.", err)
	}

	c := newTailCollector(cfg)
	prometheus.MustRegister(c)

	// If args then start file/fifo collectors
	if len(flag.Args()) > 0 {
		for _, filename := range flag.Args() {
			go func(filename string) {
				var isPipe bool
				st, err := os.Stat(filename)
				if err == nil {
					if st.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
						isPipe = true
					}
				} else {
					isPipe = false
				}

				t, err := tail.TailFile(filename, tail.Config{
					Location: &tail.SeekInfo{0, os.SEEK_END},
					ReOpen:   true,
					Follow:   isPipe,
				})
				if err != nil {
					log.Errorln("Error tailing file:", filename)
				}

				for line := range t.Lines {
					c.IngestLine(line.Text)
				}

			}(filename)
		}
	}

	// If collector address present, then start port collector.
	if *collectorAddress != "" {
		tcpSock, err := net.Listen("tcp", *collectorAddress)
		if err != nil {
			log.Fatalf("Error binding to TCP socket: %s", err)
		}
		go func() {
			for {
				conn, err := tcpSock.Accept()
				if err != nil {
					log.Errorf("Error accepting TCP connection: %s", err)
					continue
				}
				go func() {
					defer conn.Close()
					c.processReader(conn)
				}()
			}
		}()

		udpAddress, err := net.ResolveUDPAddr("udp", *collectorAddress)
		if err != nil {
			log.Fatalf("Error resolving UDP address: %s", err)
		}
		udpSock, err := net.ListenUDP("udp", udpAddress)
		if err != nil {
			log.Fatalf("Error listening to UDP address: %s", err)
		}
		go func() {
			defer udpSock.Close()
			for {
				buf := make([]byte, 65536)
				chars, srcAddress, err := udpSock.ReadFromUDP(buf)
				if err != nil {
					log.Errorf("Error reading UDP packet from %s: %s", srcAddress, err)
					continue
				}
				go c.processReader(bytes.NewReader(buf[0:chars]))
			}
		}()
	}

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(`
      <title>Tail Exporter</title>

TCP/UDP Tail Exporter

Accepting raw lines over TCP and UDP on ` + *collectorAddress + `

Watching files for lines:` + strings.Join(flag.Args(), ", ") + `

<a href="` + *metricsPath + `">Metrics

Config

` +
			cfg.Original +
			`

      `))
	})

	log.Infof("Starting Server: %s", *listeningAddress)
	http.ListenAndServe(*listeningAddress, nil)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"github.com/julienschmidt/httprouter"
)

var (
	songs_root = "./songs"
	books_root = "./books"
)

func main() {
	filepath.Walk(songs_root, loadSongs)
	filepath.Walk(books_root, loadBooks)

	fmt.Printf("%d Songs loaded.\n", len(loadedSongs))
	fmt.Printf("%d Books loaded.\n", len(loadedBooks))

	//Sort lists by title
	sort.Sort(ByTitle(loadedSongs))
	sort.Sort(ByTitle(loadedBooks))

	r := httprouter.New()

	r.GET("/", indexHandler)
	r.GET("/index.php", indexHandler)
	r.GET("/index.html", indexHandler)
	r.GET("/song/:song", songHandler)
	r.GET("/pdf/song/:song", songPdfHandler)
	r.GET("/pdf/book/:book", bookPdfHandler)
	r.GET("/book/:book/index", bookIndexHandler)
	r.GET("/book/:book/song/:number", bookHandler)
	r.ServeFiles("/css/*filepath", http.Dir("css"))
	r.ServeFiles("/js/*filepath", http.Dir("js"))

	log.Fatal(http.ListenAndServe(":8090", r))

}

type DisplayList struct {
	Link  string
	Title string
}

// ByTitle implements sort.Interface for []DisplayList based on
// the Title field.
type ByTitle []DisplayList

func (a ByTitle) Len() int      { return len(a) }
func (a ByTitle) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByTitle) Less(i, j int) bool {
	num_regex := regexp.MustCompile("^[0-9]")

	i_pos := num_regex.FindAllStringIndex(a[i].Title, 1)
	j_pos := num_regex.FindAllStringIndex(a[j].Title, 1)

	i_num := (len(i_pos) > 0)
	j_num := (len(j_pos) > 0)

	//neither title begins with a number,
	//or both begin with a number, normal sort
	if i_num == j_num {
		return a[i].Title < a[j].Title
	}

	//I is a number, put it at the end
	if i_num {
		return false
	}

	return true
}

func (song DisplayList) MatchTitle(title string) bool {
	return song.Title == title
}

func (song Song) MatchNumber(num int) bool {
	return song.SongNumber == num
}

type IndexPage struct {
	Title        string
	Recent       []DisplayList
	Songs        []DisplayList
	Books        []DisplayList
	ShowIndigo   bool
	SelectedSong string
	SelectedBook string
	Error        string
}

func (i IndexPage) HasSong() bool {
	return len(i.SelectedSong) > 0
}

func (i IndexPage) HasBook() bool {
	return len(i.SelectedBook) > 0
}

type SongPage struct {
	Song     Song
	Songbook Songbook
	NextSong int
	PrevSong int
	Selected int
	IndexPage
}

type BookPage struct {
	Songbook Songbook
	Selected int
	IndexPage
}

var loadedSongs = make([]DisplayList, 0)
var loadedBooks = make([]DisplayList, 0)
var recent = make([]DisplayList, 0)
var last_err = ""

// indexHandler is an HTTP handler that serves the index page.
func indexHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	t, err := template.ParseFiles("templates/index.tmpl", "templates/_song_select.tmpl", "templates/_book_select.tmpl")
	if err != nil {
		panic(err)
	}

	data := getBasicIndexData()
	data.ShowIndigo = true

	if err := t.ExecuteTemplate(w, "index.tmpl", data); err != nil {
		log.Println(err)
	}
}

func loadSongFile(title string, transpose int) (*Song, error) {
	filename := songs_root + "/" + title + ".song"

	song, err := ParseSongFile(filename, transpose)

	if err != nil {
		return nil, err
	}

	return song, nil
}

func getBasicIndexData() IndexPage {
	index_data := IndexPage{
		Title:        "Indigo Song Book",
		Recent:       recent,
		Songs:        loadedSongs,
		Books:        loadedBooks,
		ShowIndigo:   false,
		SelectedSong: "",
		SelectedBook: "",
		Error:        last_err}

	last_err = ""

	return index_data
}

func bookIndexHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	sbook, err := ParseSongbookFile(books_root+"/"+p.ByName("book")+".songlist", songs_root)

	if err != nil {
		fmt.Println(err)
	}

	index := getBasicIndexData()
	index.SelectedBook = sbook.Title

	book_data := &BookPage{
		Songbook:  *sbook,
		IndexPage: index}

	temp, err := template.ParseFiles(
		"templates/index.tmpl",
		"templates/_book_navigation.tmpl",
		"templates/book_index.tmpl")
	if err != nil {
		panic(err)
	}

	if err := temp.ExecuteTemplate(w, "book_index.tmpl", book_data); err != nil {
		log.Println(err)
	}
}

func bookHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	num := p.ByName("number")
	n, _ := strconv.Atoi(num)

	var transpose = r.FormValue("transpose")
	if len(transpose) == 0 {
		transpose = "0"
	}
	t, err := strconv.Atoi(transpose)

	sbook, err := ParseSongbookFile(books_root+"/"+p.ByName("book")+".songlist", songs_root)

	if err != nil {
		fmt.Println(err)
		http.NotFound(w, r)
	}

	keys := GetSongOrder(sbook)

	prev := n
	next := n
	for _, i := range keys {
		if i < n {
			prev = i
		}

		if i > n {
			next = i
			break
		}
	}

	if next == n {
		next = 0
	}

	if prev == n {
		prev = 0
	}

	song, ok := sbook.Songs[n]

	//Number does not exist in songbook
	if !ok {
		index := httprouter.CleanPath(r.URL.String() + "/../../index")
		last_err = "Song '" + strconv.Itoa(n) + "' not found in songbook."
		http.Redirect(w, r, index, 302)
		return
	}
	song.Transpose = t

	index := getBasicIndexData()
	index.SelectedSong = song.Title
	index.SelectedBook = sbook.Title

	updateRecent(song.Link(), song.Title)

	page_data := &SongPage{
		Song:      song,
		Songbook:  *sbook,
		PrevSong:  prev,
		NextSong:  next,
		Selected:  song.SongNumber,
		IndexPage: index}

	temp, err := template.ParseFiles(
		"templates/index.tmpl",
		"templates/_song_select.tmpl",
		"templates/_book_select.tmpl",
		"templates/_display_song.tmpl",
		"templates/_book_navigation.tmpl",
		"templates/book_song.tmpl")
	if err != nil {
		panic(err)
	}

	if err := temp.ExecuteTemplate(w, "book_song.tmpl", page_data); err != nil {
		log.Println(err)
	}
}

func updateRecent(link string, title string) {
	//if the song is already in the list, move it to the top
	for i, dl := range recent {
		if dl.Title == title {
			//remove element
			recent = append(recent[:i], recent[i+1:]...)
			break
		}
	}

	recent = append([]DisplayList{DisplayList{Link: link, Title: title}}, recent...)
	if len(recent) > 5 {
		recent = recent[0:5]
	}
}

func songHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	var transpose = r.FormValue("transpose")
	if len(transpose) == 0 {
		transpose = "0"
	}
	t, err := strconv.Atoi(transpose)

	data, err := loadSongFile(p.ByName("song"), t)
	if err != nil {
		log.Println(err)

		home := httprouter.CleanPath(r.URL.String() + "/../..")
		last_err = "Song '" + p.ByName("song") + "' not found."
		http.Redirect(w, r, home, 302)
		return
	}

	updateRecent(p.ByName("song"), data.Title)

	index := getBasicIndexData()
	index.SelectedSong = data.Title

	page_data := &SongPage{
		Song:      *data,
		IndexPage: index}

	temp, err := template.ParseGlob("templates/*.tmpl")
	if err != nil {
		panic(err)
	}

	if err := temp.ExecuteTemplate(w, "song.tmpl", page_data); err != nil {
		log.Println(err)
	}
}

func songPdfHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	var transpose = r.FormValue("transpose")
	if len(transpose) == 0 {
		transpose = "0"
	}
	t, err := strconv.Atoi(transpose)

	song, err := loadSongFile(p.ByName("song"), t)
	if err != nil {
		log.Println(err)
		http.NotFound(w, r)
		return
	}

	pdf, err := WriteSongPDF(song)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// stream straight to client(browser)
	w.Header().Set("Content-type", "application/pdf")

	if _, err := pdf.WriteTo(w); err != nil {
		fmt.Fprintf(w, "%s", err)
	}

	w.Write([]byte("PDF Generated"))
}

func bookPdfHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	sbook, err := ParseSongbookFile(books_root+"/"+p.ByName("book")+".songlist", songs_root)

	if err != nil {
		log.Println(err)
		http.NotFound(w, r)
		return
	}

	pdf, err := WriteBookPDF(sbook)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	if pdf == nil {
		return
	}

	// stream straight to client(browser)
	w.Header().Set("Content-type", "application/pdf")

	if _, err := pdf.WriteTo(w); err != nil {
		fmt.Fprintf(w, "%s", err)
	}

	w.Write([]byte("PDF Generated"))
}

func loadSongs(path string, f os.FileInfo, err error) error {
	if f.IsDir() && f.Name() != songs_root[2:] {
		return filepath.SkipDir
	} else if f.IsDir() {
		return nil
	}

	if strings.HasSuffix(strings.ToLower(f.Name()), ".song") {
		song, err := ParseSongFile(songs_root+"/"+f.Name(), 0)
		link := song.Filename[0 : len(song.Filename)-len(".song")]
		loadedSongs = append(loadedSongs, DisplayList{Link: link, Title: song.Title})

		return err
	}

	return nil
}

func loadBooks(path string, f os.FileInfo, err error) error {
	if f.IsDir() && f.Name() != books_root[2:] {
		return filepath.SkipDir
	} else if f.IsDir() {
		return nil
	}

	if strings.HasSuffix(strings.ToLower(f.Name()), ".songlist") {
		book, err := ParseSongbookFile(books_root+"/"+f.Name(), songs_root)
		link := book.Filename[0:len(book.Filename)-len(".songlist")] + "/index"
		loadedBooks = append(loadedBooks, DisplayList{Link: link, Title: book.Title})

		return err
	}

	return nil
}

// readLines reads a whole file into memory
// and returns a slice of its lines.
func readLines(path string) ([]string, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	var lines []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}
	return lines, scanner.Err()
}
<commit_msg>Add sanity check for missing song/book folders<commit_after>package main

import (
	"bufio"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"

	"github.com/julienschmidt/httprouter"
)

var (
	songs_root = "./songs"
	books_root = "./books"
)

func main() {
	//basic sanity check
	_, err := os.Stat(songs_root)
	if os.IsNotExist(err) {
		fmt.Printf("Error: Songs path (%s) does not exist.\n", songs_root)
		return
	}

	_, err = os.Stat(books_root)
	if os.IsNotExist(err) {
		fmt.Printf("Error: Books path (%s) does not exist.\n", books_root)
		return
	}

	filepath.Walk(songs_root, loadSongs)
	filepath.Walk(books_root, loadBooks)

	fmt.Printf("%d Songs loaded.\n", len(loadedSongs))
	fmt.Printf("%d Books loaded.\n", len(loadedBooks))

	//Sort lists by title
	sort.Sort(ByTitle(loadedSongs))
	sort.Sort(ByTitle(loadedBooks))

	r := httprouter.New()

	r.GET("/", indexHandler)
	r.GET("/index.php", indexHandler)
	r.GET("/index.html", indexHandler)
	r.GET("/song/:song", songHandler)
	r.GET("/pdf/song/:song", songPdfHandler)
	r.GET("/pdf/book/:book", bookPdfHandler)
	r.GET("/book/:book/index", bookIndexHandler)
	r.GET("/book/:book/song/:number", bookHandler)
	r.ServeFiles("/css/*filepath", http.Dir("css"))
	r.ServeFiles("/js/*filepath", http.Dir("js"))

	log.Fatal(http.ListenAndServe(":8090", r))

}

type DisplayList struct {
	Link  string
	Title string
}

// ByTitle implements sort.Interface for []DisplayList based on
// the Title field.
type ByTitle []DisplayList

func (a ByTitle) Len() int      { return len(a) }
func (a ByTitle) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByTitle) Less(i, j int) bool {
	num_regex := regexp.MustCompile("^[0-9]")

	i_pos := num_regex.FindAllStringIndex(a[i].Title, 1)
	j_pos := num_regex.FindAllStringIndex(a[j].Title, 1)

	i_num := (len(i_pos) > 0)
	j_num := (len(j_pos) > 0)

	//neither title begins with a number,
	//or both begin with a number, normal sort
	if i_num == j_num {
		return a[i].Title < a[j].Title
	}

	//I is a number, put it at the end
	if i_num {
		return false
	}

	return true
}

func (song DisplayList) MatchTitle(title string) bool {
	return song.Title == title
}

func (song Song) MatchNumber(num int) bool {
	return song.SongNumber == num
}

type IndexPage struct {
	Title        string
	Recent       []DisplayList
	Songs        []DisplayList
	Books        []DisplayList
	ShowIndigo   bool
	SelectedSong string
	SelectedBook string
	Error        string
}

func (i IndexPage) HasSong() bool {
	return len(i.SelectedSong) > 0
}

func (i IndexPage) HasBook() bool {
	return len(i.SelectedBook) > 0
}

type SongPage struct {
	Song     Song
	Songbook Songbook
	NextSong int
	PrevSong int
	Selected int
	IndexPage
}

type BookPage struct {
	Songbook Songbook
	Selected int
	IndexPage
}

var loadedSongs = make([]DisplayList, 0)
var loadedBooks = make([]DisplayList, 0)
var recent = make([]DisplayList, 0)
var last_err = ""

// indexHandler is an HTTP handler that serves the index page.
func indexHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	t, err := template.ParseFiles("templates/index.tmpl", "templates/_song_select.tmpl", "templates/_book_select.tmpl")
	if err != nil {
		panic(err)
	}

	data := getBasicIndexData()
	data.ShowIndigo = true

	if err := t.ExecuteTemplate(w, "index.tmpl", data); err != nil {
		log.Println(err)
	}
}

func loadSongFile(title string, transpose int) (*Song, error) {
	filename := songs_root + "/" + title + ".song"

	song, err := ParseSongFile(filename, transpose)

	if err != nil {
		return nil, err
	}

	return song, nil
}

func getBasicIndexData() IndexPage {
	index_data := IndexPage{
		Title:        "Indigo Song Book",
		Recent:       recent,
		Songs:        loadedSongs,
		Books:        loadedBooks,
		ShowIndigo:   false,
		SelectedSong: "",
		SelectedBook: "",
		Error:        last_err}

	last_err = ""

	return index_data
}

func bookIndexHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	sbook, err := ParseSongbookFile(books_root+"/"+p.ByName("book")+".songlist", songs_root)

	if err != nil {
		fmt.Println(err)
	}

	index := getBasicIndexData()
	index.SelectedBook = sbook.Title

	book_data := &BookPage{
		Songbook:  *sbook,
		IndexPage: index}

	temp, err := template.ParseFiles(
		"templates/index.tmpl",
		"templates/_book_navigation.tmpl",
		"templates/book_index.tmpl")
	if err != nil {
		panic(err)
	}

	if err := temp.ExecuteTemplate(w, "book_index.tmpl", book_data); err != nil {
		log.Println(err)
	}
}

func bookHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	num := p.ByName("number")
	n, _ := strconv.Atoi(num)

	var transpose = r.FormValue("transpose")
	if len(transpose) == 0 {
		transpose = "0"
	}
	t, err := strconv.Atoi(transpose)

	sbook, err := ParseSongbookFile(books_root+"/"+p.ByName("book")+".songlist", songs_root)

	if err != nil {
		fmt.Println(err)
		http.NotFound(w, r)
	}

	keys := GetSongOrder(sbook)

	prev := n
	next := n
	for _, i := range keys {
		if i < n {
			prev = i
		}

		if i > n {
			next = i
			break
		}
	}

	if next == n {
		next = 0
	}

	if prev == n {
		prev = 0
	}

	song, ok := sbook.Songs[n]

	//Number does not exist in songbook
	if !ok {
		index := httprouter.CleanPath(r.URL.String() + "/../../index")
		last_err = "Song '" + strconv.Itoa(n) + "' not found in songbook."
		http.Redirect(w, r, index, 302)
		return
	}
	song.Transpose = t

	index := getBasicIndexData()
	index.SelectedSong = song.Title
	index.SelectedBook = sbook.Title

	updateRecent(song.Link(), song.Title)

	page_data := &SongPage{
		Song:      song,
		Songbook:  *sbook,
		PrevSong:  prev,
		NextSong:  next,
		Selected:  song.SongNumber,
		IndexPage: index}

	temp, err := template.ParseFiles(
		"templates/index.tmpl",
		"templates/_song_select.tmpl",
		"templates/_book_select.tmpl",
		"templates/_display_song.tmpl",
		"templates/_book_navigation.tmpl",
		"templates/book_song.tmpl")
	if err != nil {
		panic(err)
	}

	if err := temp.ExecuteTemplate(w, "book_song.tmpl", page_data); err != nil {
		log.Println(err)
	}
}

func updateRecent(link string, title string) {
	//if the song is already in the list, move it to the top
	for i, dl := range recent {
		if dl.Title == title {
			//remove element
			recent = append(recent[:i], recent[i+1:]...)
			break
		}
	}

	recent = append([]DisplayList{DisplayList{Link: link, Title: title}}, recent...)
	if len(recent) > 5 {
		recent = recent[0:5]
	}
}

func songHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	var transpose = r.FormValue("transpose")
	if len(transpose) == 0 {
		transpose = "0"
	}
	t, err := strconv.Atoi(transpose)

	data, err := loadSongFile(p.ByName("song"), t)
	if err != nil {
		log.Println(err)

		home := httprouter.CleanPath(r.URL.String() + "/../..")
		last_err = "Song '" + p.ByName("song") + "' not found."
		http.Redirect(w, r, home, 302)
		return
	}

	updateRecent(p.ByName("song"), data.Title)

	index := getBasicIndexData()
	index.SelectedSong = data.Title

	page_data := &SongPage{
		Song:      *data,
		IndexPage: index}

	temp, err := template.ParseGlob("templates/*.tmpl")
	if err != nil {
		panic(err)
	}

	if err := temp.ExecuteTemplate(w, "song.tmpl", page_data); err != nil {
		log.Println(err)
	}
}

func songPdfHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	var transpose = r.FormValue("transpose")
	if len(transpose) == 0 {
		transpose = "0"
	}
	t, err := strconv.Atoi(transpose)

	song, err := loadSongFile(p.ByName("song"), t)
	if err != nil {
		log.Println(err)
		http.NotFound(w, r)
		return
	}

	pdf, err := WriteSongPDF(song)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// stream straight to client(browser)
	w.Header().Set("Content-type", "application/pdf")

	if _, err := pdf.WriteTo(w); err != nil {
		fmt.Fprintf(w, "%s", err)
	}

	w.Write([]byte("PDF Generated"))
}

func bookPdfHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	sbook, err := ParseSongbookFile(books_root+"/"+p.ByName("book")+".songlist", songs_root)

	if err != nil {
		log.Println(err)
		http.NotFound(w, r)
		return
	}

	pdf, err := WriteBookPDF(sbook)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	if pdf == nil {
		return
	}

	// stream straight to client(browser)
	w.Header().Set("Content-type", "application/pdf")

	if _, err := pdf.WriteTo(w); err != nil {
		fmt.Fprintf(w, "%s", err)
	}

	w.Write([]byte("PDF Generated"))
}

func loadSongs(path string, f os.FileInfo, err error) error {
	if f.IsDir() && f.Name() != songs_root[2:] {
		return filepath.SkipDir
	} else if f.IsDir() {
		return nil
	}

	if strings.HasSuffix(strings.ToLower(f.Name()), ".song") {
		song, err := ParseSongFile(songs_root+"/"+f.Name(), 0)
		link := song.Filename[0 : len(song.Filename)-len(".song")]
		loadedSongs = append(loadedSongs, DisplayList{Link: link, Title: song.Title})

		return err
	}

	return nil
}

func loadBooks(path string, f os.FileInfo, err error) error {
	if f.IsDir() && f.Name() != books_root[2:] {
		return filepath.SkipDir
	} else if f.IsDir() {
		return nil
	}

	if strings.HasSuffix(strings.ToLower(f.Name()), ".songlist") {
		book, err := ParseSongbookFile(books_root+"/"+f.Name(), songs_root)
		link := book.Filename[0:len(book.Filename)-len(".songlist")] + "/index"
		loadedBooks = append(loadedBooks, DisplayList{Link: link, Title: book.Title})

		return err
	}

	return nil
}

// readLines reads a whole file into memory
// and returns a slice of its lines.
func readLines(path string) ([]string, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	var lines []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}
	return lines, scanner.Err()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"strings"
	"time"

	"github.com/nelsam/hel/mocks"
	"github.com/nelsam/hel/packages"
	"github.com/nelsam/hel/types"
	"github.com/spf13/cobra"
)

var (
	cmd           *cobra.Command
	goimportsPath string
)

func init() {
	output, err := exec.Command("which", "goimports").Output()
	if err != nil {
		fmt.Println("Could not locate goimports: ", err.Error())
		fmt.Println("If goimports is not installed, please install it somewhere in your path.  " +
			"See https://godoc.org/golang.org/x/tools/cmd/goimports.")
		os.Exit(1)
	}
	goimportsPath = strings.TrimSpace(string(output))

	cmd = &cobra.Command{
		Use:   "hel",
		Short: "A mock generator for Go",
		Long: "A simple mock generator.  The origin of the name is the Norse goddess, Hel, " +
			"who guards over the souls of those unworthy to enter Valhalla.  You can probably " +
			"guess how much I like mocks.",
		Run: func(cmd *cobra.Command, args []string) {
			if len(args) > 0 {
				fmt.Println("Invalid usage.\n")
				err := cmd.Help()
				if err != nil {
					panic(err)
				}
				os.Exit(1)
			}
			packagePatterns, err := cmd.Flags().GetStringSlice("package")
			if err != nil {
				panic(err)
			}
			typePatterns, err := cmd.Flags().GetStringSlice("type")
			if err != nil {
				panic(err)
			}
			outputName, err := cmd.Flags().GetString("output")
			if err != nil {
				panic(err)
			}
			chanSize, err := cmd.Flags().GetInt("chan-size")
			if err != nil {
				panic(err)
			}
			fmt.Printf("Loading directories matching pattern"+pluralize(packagePatterns, "", "s")+" %v", packagePatterns)
			var dirList []packages.Dir
			progress(func() {
				dirList = packages.Load(packagePatterns...)
			})
			fmt.Print("\n")
			fmt.Println("Found directories:")
			for _, dir := range dirList {
				fmt.Println("  " + dir.Path())
			}
			fmt.Print("\n")

			fmt.Printf("Loading interface types in matching directories")
			var typeDirs types.Dirs
			progress(func() {
				godirs := make([]types.GoDir, 0, len(dirList))
				for _, dir := range dirList {
					godirs = append(godirs, dir)
				}
				typeDirs = types.Load(godirs...).Filter(typePatterns...)
			})
			fmt.Print("\n\n")

			fmt.Printf("Generating mocks in output file %s", outputName)
			progress(func() {
				for _, typeDir := range typeDirs {
					mockPath, err := makeMocks(typeDir, outputName, chanSize)
					if err != nil {
						panic(err)
					}
					if err = exec.Command(goimportsPath, "-w", mockPath).Run(); err != nil {
						panic(err)
					}
				}
			})
			fmt.Print("\n")
		},
	}
	cmd.Flags().StringSliceP("package", "p", []string{"."}, "The package(s) to generate mocks for.")
	cmd.Flags().StringSliceP("type", "t", []string{}, "The type(s) to generate mocks for.  If no types "+
		"are passed in, all exported interface types will be generated.")
	cmd.Flags().StringP("output", "o", "helheim_test.go", "The file to write generated mocks to.  Since hel does "+
		"not generate exported types, this file will be saved directly in all packages with generated mocks.  "+
		"Also note that, since the types are not exported, you will want the file to end in '_test.go'.")
	cmd.Flags().IntP("chan-size", "s", 100, "The size of channels used for method calls.")
}

func makeMocks(types types.Dir, fileName string, chanSize int) (filePath string, err error) {
	mocks, err := mocks.Generate(types)
	if err != nil {
		return "", err
	}
	if len(mocks) == 0 {
		return "", nil
	}
	if types.Package() != types.TestPackage() {
		mocks.PrependLocalPackage(types.Package())
	}
	filePath = filepath.Join(types.Dir(), fileName)
	f, err := os.Create(filePath)
	if err != nil {
		return "", err
	}
	defer f.Close()
	return filePath, mocks.Output(types.TestPackage(), chanSize, f)
}

func progress(f func()) {
	stop, done := make(chan struct{}), make(chan struct{})
	defer func() {
		close(stop)
		<-done
	}()
	go showProgress(stop, done)
	f()
}

func showProgress(stop <-chan struct{}, done chan<- struct{}) {
	defer close(done)
	ticker := time.NewTicker(time.Second / 2)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			fmt.Print(".")
		case <-stop:
			return
		}
	}
}

type lengther interface {
	Len() int
}

func pluralize(values interface{}, singular, plural string) string {
	length := findLength(values)
	if length == 1 {
		return singular
	}
	return plural
}

func findLength(values interface{}) int {
	if lengther, ok := values.(lengther); ok {
		return lengther.Len()
	}
	return reflect.ValueOf(values).Len()
}

func main() {
	cmd.Execute()
}
<commit_msg>Fix panic running against an empty package<commit_after>package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"strings"
	"time"

	"github.com/nelsam/hel/mocks"
	"github.com/nelsam/hel/packages"
	"github.com/nelsam/hel/types"
	"github.com/spf13/cobra"
)

var (
	cmd           *cobra.Command
	goimportsPath string
)

func init() {
	output, err := exec.Command("which", "goimports").Output()
	if err != nil {
		fmt.Println("Could not locate goimports: ", err.Error())
		fmt.Println("If goimports is not installed, please install it somewhere in your path.  " +
			"See https://godoc.org/golang.org/x/tools/cmd/goimports.")
		os.Exit(1)
	}
	goimportsPath = strings.TrimSpace(string(output))

	cmd = &cobra.Command{
		Use:   "hel",
		Short: "A mock generator for Go",
		Long: "A simple mock generator.  The origin of the name is the Norse goddess, Hel, " +
			"who guards over the souls of those unworthy to enter Valhalla.  You can probably " +
			"guess how much I like mocks.",
		Run: func(cmd *cobra.Command, args []string) {
			if len(args) > 0 {
				fmt.Println("Invalid usage.\n")
				err := cmd.Help()
				if err != nil {
					panic(err)
				}
				os.Exit(1)
			}
			packagePatterns, err := cmd.Flags().GetStringSlice("package")
			if err != nil {
				panic(err)
			}
			typePatterns, err := cmd.Flags().GetStringSlice("type")
			if err != nil {
				panic(err)
			}
			outputName, err := cmd.Flags().GetString("output")
			if err != nil {
				panic(err)
			}
			chanSize, err := cmd.Flags().GetInt("chan-size")
			if err != nil {
				panic(err)
			}
			fmt.Printf("Loading directories matching pattern"+pluralize(packagePatterns, "", "s")+" %v", packagePatterns)
			var dirList []packages.Dir
			progress(func() {
				dirList = packages.Load(packagePatterns...)
			})
			fmt.Print("\n")
			fmt.Println("Found directories:")
			for _, dir := range dirList {
				fmt.Println("  " + dir.Path())
			}
			fmt.Print("\n")

			fmt.Printf("Loading interface types in matching directories")
			var typeDirs types.Dirs
			progress(func() {
				godirs := make([]types.GoDir, 0, len(dirList))
				for _, dir := range dirList {
					godirs = append(godirs, dir)
				}
				typeDirs = types.Load(godirs...).Filter(typePatterns...)
			})
			fmt.Print("\n\n")

			fmt.Printf("Generating mocks in output file %s", outputName)
			progress(func() {
				for _, typeDir := range typeDirs {
					mockPath, err := makeMocks(typeDir, outputName, chanSize)
					if err != nil {
						panic(err)
					}
					if mockPath != "" {
						if err = exec.Command(goimportsPath, "-w", mockPath).Run(); err != nil {
							panic(err)
						}
					}
				}
			})
			fmt.Print("\n")
		},
	}
	cmd.Flags().StringSliceP("package", "p", []string{"."}, "The package(s) to generate mocks for.")
	cmd.Flags().StringSliceP("type", "t", []string{}, "The type(s) to generate mocks for.  If no types "+
		"are passed in, all exported interface types will be generated.")
	cmd.Flags().StringP("output", "o", "helheim_test.go", "The file to write generated mocks to.  Since hel does "+
		"not generate exported types, this file will be saved directly in all packages with generated mocks.  "+
		"Also note that, since the types are not exported, you will want the file to end in '_test.go'.")
	cmd.Flags().IntP("chan-size", "s", 100, "The size of channels used for method calls.")
}

func makeMocks(types types.Dir, fileName string, chanSize int) (filePath string, err error) {
	mocks, err := mocks.Generate(types)
	if err != nil {
		return "", err
	}
	if len(mocks) == 0 {
		return "", nil
	}
	if types.Package() != types.TestPackage() {
		mocks.PrependLocalPackage(types.Package())
	}
	filePath = filepath.Join(types.Dir(), fileName)
	f, err := os.Create(filePath)
	if err != nil {
		return "", err
	}
	defer f.Close()
	return filePath, mocks.Output(types.TestPackage(), chanSize, f)
}

func progress(f func()) {
	stop, done := make(chan struct{}), make(chan struct{})
	defer func() {
		close(stop)
		<-done
	}()
	go showProgress(stop, done)
	f()
}

func showProgress(stop <-chan struct{}, done chan<- struct{}) {
	defer close(done)
	ticker := time.NewTicker(time.Second / 2)
	defer ticker.Stop()
	for {
		select {
		case <-ticker.C:
			fmt.Print(".")
		case <-stop:
			return
		}
	}
}

type lengther interface {
	Len() int
}

func pluralize(values interface{}, singular, plural string) string {
	length := findLength(values)
	if length == 1 {
		return singular
	}
	return plural
}

func findLength(values interface{}) int {
	if lengther, ok := values.(lengther); ok {
		return lengther.Len()
	}
	return reflect.ValueOf(values).Len()
}

func main() {
	cmd.Execute()
}
<|endoftext|>


<commit_before>package main

//go:generate esc -o templates.go ./templates

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/bukalapak/snowboard/adapter/drafter"
	"github.com/bukalapak/snowboard/adapter/drafterc"
	snowboard "github.com/bukalapak/snowboard/parser"
	"github.com/fsnotify/fsnotify"
	xerrors "github.com/pkg/errors"
	"github.com/urfave/cli"
)

var versionStr string
var (
	engine  snowboard.Parser
	engineC snowboard.Parser
)

func main() {
	engine = drafter.Engine{}
	engineC = drafterc.Engine{}

	cli.VersionPrinter = func(c *cli.Context) {
		fmt.Fprintf(c.App.Writer, "Snowboard version: %s\n", c.App.Version)
		fmt.Fprintf(c.App.Writer, "Drafter version: %s\n", engine.Version())
	}

	if versionStr == "" {
		versionStr = "HEAD"
	}

	app := cli.NewApp()
	app.Name = "snowboard"
	app.Usage = "API blueprint toolkit"
	app.Version = versionStr
	app.Commands = []cli.Command{
		{
			Name:  "lint",
			Usage: "Validate API blueprint",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "i",
					Usage: "API blueprint file",
				},
				cli.BoolFlag{
					Name:  "c",
					Usage: "Use character index instead of line and row number",
				},
			},
			Action: func(c *cli.Context) error {
				if err := validate(c, c.String("i"), c.Bool("c")); err != nil {
					if strings.Contains(err.Error(), "read failed") {
						return xerrors.Cause(err)
					}

					io.WriteString(c.App.Writer, err.Error())
				}

				return nil
			},
		},
		{
			Name:  "html",
			Usage: "Render HTML documentation",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "i",
					Usage: "API blueprint file",
				},
				cli.StringFlag{
					Name:  "o",
					Value: "index.html",
					Usage: "HTML file",
				},
				cli.StringFlag{
					Name:  "t",
					Value: "alpha",
					Usage: "Template for HTML documentation",
				},
				cli.BoolFlag{
					Name:  "s",
					Usage: "Serve HTML via HTTP server",
				},
				cli.StringFlag{
					Name:  "b",
					Value: "127.0.0.1:8088",
					Usage: "HTTP server listen address",
				},
			},
			Action: func(c *cli.Context) error {
				if c.Bool("s") {
					return watchHTML(c, c.String("i"), c.String("o"), c.String("t"), c.String("b"))
				}

				return renderHTML(c, c.String("i"), c.String("o"), c.String("t"))
			},
		},
		{
			Name:  "apib",
			Usage: "Render API blueprint",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "i",
					Usage: "API blueprint file",
				},
				cli.StringFlag{
					Name:  "o",
					Usage: "API blueprint output file",
				},
			},
			Action: func(c *cli.Context) error {
				return renderAPIB(c, c.String("i"), c.String("o"))
			},
		},
		{
			Name:  "mock",
			Usage: "Run Mock server",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "i",
					Usage: "API blueprint file",
				},
				cli.StringFlag{
					Name:  "b",
					Value: "127.0.0.1:8087",
					Usage: "HTTP server listen address",
				},
			},
			Action: func(c *cli.Context) error {
				return serveMock(c, c.String("b"), c.String("i"))
			},
		},
		{
			Name:  "adapter",
			Usage: "Snowboard adapter",
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "c",
					Usage: "Copy snowboard adapter to desired location",
				},
				cli.StringFlag{
					Name:  "p",
					Value: ".",
					Usage: "Adapter location dir",
				},
			},
			Action: func(c *cli.Context) error {
				if c.Bool("c") {
					return installAdapters(c, c.String("p"))
				}

				return nil
			},
		},
	}
	app.Run(os.Args)
}

func readFile(fn string) ([]byte, error) {
	info, err := os.Stat(fn)
	if err != nil {
		return nil, errors.New("File is not exist")
	}

	if info.IsDir() {
		return nil, errors.New("File is a directory")
	}

	return ioutil.ReadFile(fn)
}

func readTemplate(fn string) ([]byte, error) {
	tf, err := readFile(fn)
	if err == nil {
		return tf, nil
	}

	fs := FS(false)
	ff, err := fs.Open("/templates/" + fn + ".html")
	if err != nil {
		return nil, err
	}

	defer ff.Close()
	return ioutil.ReadAll(ff)
}

func renderHTML(c *cli.Context, input, output, tplFile string) error {
	bp, err := snowboard.Load(input, engine)
	if err != nil {
		return err
	}

	of, err := os.Create(output)
	if err != nil {
		return err
	}
	defer of.Close()

	tf, err := readTemplate(tplFile)
	if err != nil {
		return err
	}

	err = snowboard.HTML(string(tf), of, bp)
	if err != nil {
		return err
	}

	fmt.Fprintln(c.App.Writer, "HTML has been generated!")
	return nil
}

func renderAPIB(c *cli.Context, input, output string) error {
	b, err := snowboard.Read(input)
	if err != nil {
		return err
	}

	of, err := os.Create(output)
	if err != nil {
		return err
	}
	defer of.Close()

	_, err = io.Copy(of, bytes.NewReader(b))
	if err != nil {
		return err
	}

	fmt.Fprintln(c.App.Writer, "API blueprint has been generated!")
	return nil
}

func validate(c *cli.Context, input string, charIdx bool) error {
	b, err := readFile(input)
	if err != nil {
		return xerrors.Wrap(err, "read failed")
	}

	bf := bytes.NewReader(b)

	if charIdx {
		out, err := snowboard.Validate(bf, engine)
		if err != nil {
			return err
		}

		if out == nil {
			fmt.Fprintln(c.App.Writer, "OK")
			return nil
		}

		var buf bytes.Buffer

		s := "--------"
		w := tabwriter.NewWriter(&buf, 8, 0, 0, ' ', tabwriter.Debug)
		fmt.Fprintln(w, "Char Index\tDescription")
		fmt.Fprintf(w, "%s\t%s\n", s, strings.Repeat(s, 8))

		for _, n := range out.Annotations {
			for _, m := range n.SourceMaps {
				fmt.Fprintf(w, "%d:%d\t%s\n", m.Row, m.Col, n.Description)
			}
		}

		w.Flush()

		if len(out.Annotations) > 0 {
			return errors.New(buf.String())
		}
	} else {
		b, err := engineC.Validate(bf)
		if err != nil {
			return err
		}

		var buf bytes.Buffer

		w := tabwriter.NewWriter(&buf, 8, 0, 0, ' ', tabwriter.Debug)
		fmt.Fprintln(w, "Location\tSeverity\tDescription")

		fmt.Fprintf(w, "%s\t%s\t%s\n", dash(42), dash(16), dash(80))

		if string(b) == "" {
			fmt.Fprintln(c.App.Writer, "OK")
			return nil
		}

		ns := strings.Split(string(b), "\n")

		for _, n := range ns {
			nn := strings.SplitN(n, "; ", 2)
			mm := strings.SplitN(nn[0], "  ", 2)
			fmt.Fprintf(w, "%s\t%s\t%s\n", nn[1], mm[0], mm[1])
		}

		w.Flush()
		return errors.New(buf.String())
	}

	return nil
}

func dash(n int) string {
	return strings.Repeat("-", n)
}

func watchHTML(c *cli.Context, input, output, tplFile, bind string) error {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}
	defer watcher.Close()

	done := make(chan bool)
	go func() {
		for {
			select {
			case event := <-watcher.Events:
				if event.Op&fsnotify.Write == fsnotify.Write {
					renderHTML(c, input, output, tplFile)
				}
			case err := <-watcher.Errors:
				fmt.Fprintln(c.App.Writer, err)
			}
		}
	}()

	err = watcher.Add(input)
	if err != nil {
		return err
	}

	if _, err = os.Stat(tplFile); err == nil {
		err = watcher.Add(tplFile)
		if err != nil {
			return err
		}
	}

	renderHTML(c, input, output, tplFile)
	serveHTML(bind, output)

	<-done

	return nil
}

func serveHTML(bind, output string) error {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, output)
	})

	return http.ListenAndServe(bind, nil)
}

func serveMock(c *cli.Context, bind, input string) error {
	bp, err := snowboard.Load(input, engine)
	if err != nil {
		return err
	}

	fmt.Fprintf(c.App.Writer, "Mock server is ready. Use %s\n", bind)
	fmt.Fprintln(c.App.Writer, "Available Routes:")

	ms := snowboard.Mock(bp)
	for _, m := range ms {
		fmt.Fprintf(c.App.Writer, "%s\t%d\t%s\n", m.Method, m.StatusCode, m.Pattern)
	}

	h := snowboard.MockHandler(ms)
	return http.ListenAndServe(bind, h)
}

func installAdapters(c *cli.Context, dir string) error {
	n := drafterc.Engine{}
	name, err := n.CopyExec(dir)
	if err != nil {
		return err
	}

	fmt.Fprintln(c.App.Writer, "Snowboard adapter installed!\nLocation: "+name)
	return nil
}
<commit_msg>replace flag -i with subcommand first argument<commit_after>package main

//go:generate esc -o templates.go ./templates

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/bukalapak/snowboard/adapter/drafter"
	"github.com/bukalapak/snowboard/adapter/drafterc"
	snowboard "github.com/bukalapak/snowboard/parser"
	"github.com/fsnotify/fsnotify"
	xerrors "github.com/pkg/errors"
	"github.com/urfave/cli"
)

var versionStr string
var (
	engine  snowboard.Parser
	engineC snowboard.Parser
)

func main() {
	engine = drafter.Engine{}
	engineC = drafterc.Engine{}

	cli.VersionPrinter = func(c *cli.Context) {
		fmt.Fprintf(c.App.Writer, "Snowboard version: %s\n", c.App.Version)
		fmt.Fprintf(c.App.Writer, "Drafter version: %s\n", engine.Version())
	}

	if versionStr == "" {
		versionStr = "HEAD"
	}

	app := cli.NewApp()
	app.Name = "snowboard"
	app.Usage = "API blueprint toolkit"
	app.Version = versionStr
	app.Before = func(c *cli.Context) error {
		if c.Args().Get(1) == "" {
			cli.ShowCommandHelp(c, c.Args().Get(0))
		}

		return nil
	}
	app.Commands = []cli.Command{
		{
			Name:  "lint",
			Usage: "Validate API blueprint",
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "c",
					Usage: "Use character index instead of line and row number",
				},
			},
			Action: func(c *cli.Context) error {
				if c.Args().Get(0) == "" {
					return nil
				}

				if err := validate(c, c.Args().Get(0), c.Bool("c")); err != nil {
					if strings.Contains(err.Error(), "read failed") {
						return xerrors.Cause(err)
					}

					io.WriteString(c.App.Writer, err.Error())
				}

				return nil
			},
		},
		{
			Name:  "html",
			Usage: "Render HTML documentation",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "o",
					Value: "index.html",
					Usage: "HTML file",
				},
				cli.StringFlag{
					Name:  "t",
					Value: "alpha",
					Usage: "Template for HTML documentation",
				},
				cli.BoolFlag{
					Name:  "s",
					Usage: "Serve HTML via HTTP server",
				},
				cli.StringFlag{
					Name:  "b",
					Value: "127.0.0.1:8088",
					Usage: "HTTP server listen address",
				},
			},
			Action: func(c *cli.Context) error {
				if c.Args().Get(0) == "" {
					return nil
				}

				if c.Bool("s") {
					return watchHTML(c, c.Args().Get(0), c.String("o"), c.String("t"), c.String("b"))
				}

				return renderHTML(c, c.Args().Get(0), c.String("o"), c.String("t"))
			},
		},
		{
			Name:  "apib",
			Usage: "Render API blueprint",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "o",
					Usage: "API blueprint output file",
				},
			},
			Action: func(c *cli.Context) error {
				if c.Args().Get(0) == "" {
					return nil
				}

				return renderAPIB(c, c.Args().Get(0), c.String("o"))
			},
		},
		{
			Name:  "mock",
			Usage: "Run Mock server",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "b",
					Value: "127.0.0.1:8087",
					Usage: "HTTP server listen address",
				},
			},
			Action: func(c *cli.Context) error {
				if c.Args().Get(0) == "" {
					return nil
				}

				return serveMock(c, c.String("b"), c.Args().Get(0))
			},
		},
		{
			Name:  "adapter",
			Usage: "Snowboard adapter",
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "c",
					Usage: "Copy snowboard adapter to desired location",
				},
				cli.StringFlag{
					Name:  "p",
					Value: ".",
					Usage: "Adapter location dir",
				},
			},
			Action: func(c *cli.Context) error {
				if c.Bool("c") {
					return installAdapters(c, c.String("p"))
				}

				return nil
			},
		},
	}
	app.Run(os.Args)
}

func readFile(fn string) ([]byte, error) {
	info, err := os.Stat(fn)
	if err != nil {
		return nil, errors.New("File is not exist")
	}

	if info.IsDir() {
		return nil, errors.New("File is a directory")
	}

	return ioutil.ReadFile(fn)
}

func readTemplate(fn string) ([]byte, error) {
	tf, err := readFile(fn)
	if err == nil {
		return tf, nil
	}

	fs := FS(false)
	ff, err := fs.Open("/templates/" + fn + ".html")
	if err != nil {
		return nil, err
	}

	defer ff.Close()
	return ioutil.ReadAll(ff)
}

func renderHTML(c *cli.Context, input, output, tplFile string) error {
	bp, err := snowboard.Load(input, engine)
	if err != nil {
		return err
	}

	of, err := os.Create(output)
	if err != nil {
		return err
	}
	defer of.Close()

	tf, err := readTemplate(tplFile)
	if err != nil {
		return err
	}

	err = snowboard.HTML(string(tf), of, bp)
	if err != nil {
		return err
	}

	fmt.Fprintln(c.App.Writer, "HTML has been generated!")
	return nil
}

func renderAPIB(c *cli.Context, input, output string) error {
	b, err := snowboard.Read(input)
	if err != nil {
		return err
	}

	of, err := os.Create(output)
	if err != nil {
		return err
	}
	defer of.Close()

	_, err = io.Copy(of, bytes.NewReader(b))
	if err != nil {
		return err
	}

	fmt.Fprintln(c.App.Writer, "API blueprint has been generated!")
	return nil
}

func validate(c *cli.Context, input string, charIdx bool) error {
	b, err := readFile(input)
	if err != nil {
		return xerrors.Wrap(err, "read failed")
	}

	bf := bytes.NewReader(b)

	if charIdx {
		out, err := snowboard.Validate(bf, engine)
		if err != nil {
			return err
		}

		if out == nil {
			fmt.Fprintln(c.App.Writer, "OK")
			return nil
		}

		var buf bytes.Buffer

		s := "--------"
		w := tabwriter.NewWriter(&buf, 8, 0, 0, ' ', tabwriter.Debug)
		fmt.Fprintln(w, "Char Index\tDescription")
		fmt.Fprintf(w, "%s\t%s\n", s, strings.Repeat(s, 8))

		for _, n := range out.Annotations {
			for _, m := range n.SourceMaps {
				fmt.Fprintf(w, "%d:%d\t%s\n", m.Row, m.Col, n.Description)
			}
		}

		w.Flush()

		if len(out.Annotations) > 0 {
			return errors.New(buf.String())
		}
	} else {
		b, err := engineC.Validate(bf)
		if err != nil {
			return err
		}

		var buf bytes.Buffer

		w := tabwriter.NewWriter(&buf, 8, 0, 0, ' ', tabwriter.Debug)
		fmt.Fprintln(w, "Location\tSeverity\tDescription")

		fmt.Fprintf(w, "%s\t%s\t%s\n", dash(42), dash(16), dash(80))

		if string(b) == "" {
			fmt.Fprintln(c.App.Writer, "OK")
			return nil
		}

		ns := strings.Split(string(b), "\n")

		for _, n := range ns {
			nn := strings.SplitN(n, "; ", 2)
			mm := strings.SplitN(nn[0], "  ", 2)
			fmt.Fprintf(w, "%s\t%s\t%s\n", nn[1], mm[0], mm[1])
		}

		w.Flush()
		return errors.New(buf.String())
	}

	return nil
}

func dash(n int) string {
	return strings.Repeat("-", n)
}

func watchHTML(c *cli.Context, input, output, tplFile, bind string) error {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return err
	}
	defer watcher.Close()

	done := make(chan bool)
	go func() {
		for {
			select {
			case event := <-watcher.Events:
				if event.Op&fsnotify.Write == fsnotify.Write {
					renderHTML(c, input, output, tplFile)
				}
			case err := <-watcher.Errors:
				fmt.Fprintln(c.App.Writer, err)
			}
		}
	}()

	err = watcher.Add(input)
	if err != nil {
		return err
	}

	if _, err = os.Stat(tplFile); err == nil {
		err = watcher.Add(tplFile)
		if err != nil {
			return err
		}
	}

	renderHTML(c, input, output, tplFile)
	serveHTML(bind, output)

	<-done

	return nil
}

func serveHTML(bind, output string) error {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, output)
	})

	return http.ListenAndServe(bind, nil)
}

func serveMock(c *cli.Context, bind, input string) error {
	bp, err := snowboard.Load(input, engine)
	if err != nil {
		return err
	}

	fmt.Fprintf(c.App.Writer, "Mock server is ready. Use %s\n", bind)
	fmt.Fprintln(c.App.Writer, "Available Routes:")

	ms := snowboard.Mock(bp)
	for _, m := range ms {
		fmt.Fprintf(c.App.Writer, "%s\t%d\t%s\n", m.Method, m.StatusCode, m.Pattern)
	}

	h := snowboard.MockHandler(ms)
	return http.ListenAndServe(bind, h)
}

func installAdapters(c *cli.Context, dir string) error {
	n := drafterc.Engine{}
	name, err := n.CopyExec(dir)
	if err != nil {
		return err
	}

	fmt.Fprintln(c.App.Writer, "Snowboard adapter installed!\nLocation: "+name)
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/codegangsta/martini"
	"github.com/martini-contrib/auth"
	"github.com/martini-contrib/gzip"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
)

func main() {
	var config, port, api string

	flag.StringVar(&config, "c", "config.json", "Config file")
	flag.StringVar(&port, "p", "8080", "Port")
	flag.StringVar(&api, "a", "DEFAULT", "API key")

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, `Usage: %s [options]

Barycenter serves a JSON configuration file over HTTP
using basic authentication (so run it over SSL).

Run an endpoint as follows:

  %s -c config.json -a DEFAULT -p 8080

You can then make a request against the endpoint.

  curl -u DEFAULT: 127.0.0.1:8080

OPTIONS:
`, os.Args[0], os.Args[0])
		flag.PrintDefaults()
	}

	flag.Parse()

	if flag.NArg() != 0 {
		flag.Usage()
		os.Exit(1)
	}

	json, err := ioutil.ReadFile(config)
	if err != nil {
		log.Fatalf("Could not read configuration: %s", err)
	}

	m := martini.Classic()
	m.Use(gzip.All())
	m.Use(auth.Basic(api, ""))

	m.Get("/", func(w http.ResponseWriter, req *http.Request) string {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		return string(json)
	})

	http.ListenAndServe(":"+port, m)

	sigchan := make(chan os.Signal)
	signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM)
	<-sigchan
}
<commit_msg>Nuke pointless signal handling<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"

	"github.com/codegangsta/martini"
	"github.com/martini-contrib/auth"
	"github.com/martini-contrib/gzip"
)

func main() {
	var config, port, api string

	flag.StringVar(&config, "c", "config.json", "Config file")
	flag.StringVar(&port, "p", "8080", "Port")
	flag.StringVar(&api, "a", "DEFAULT", "API key")

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, `Usage: %s [options]

Barycenter serves a JSON configuration file over HTTP
using basic authentication (so run it over SSL).

Run an endpoint as follows:

  %s -c config.json -a DEFAULT -p 8080

You can then make a request against the endpoint.

  curl -u DEFAULT: 127.0.0.1:8080

OPTIONS:
`, os.Args[0], os.Args[0])
		flag.PrintDefaults()
	}

	flag.Parse()

	if flag.NArg() != 0 {
		flag.Usage()
		os.Exit(1)
	}

	json, err := ioutil.ReadFile(config)
	if err != nil {
		log.Fatalf("Could not read configuration: %s", err)
	}

	m := martini.Classic()
	m.Use(gzip.All())
	m.Use(auth.Basic(api, ""))

	m.Get("/", func(w http.ResponseWriter, req *http.Request) string {
		w.Header().Set("Content-Type", "application/json; charset=utf-8")
		return string(json)
	})

	http.ListenAndServe(":"+port, m)
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"log"
	"os"
	"time"

	"github.com/ChimeraCoder/anaconda"
	_ "github.com/go-sql-driver/mysql"
	"github.com/shiwork/favpost/config"
	"github.com/shiwork/favpost/model"
	"github.com/shiwork/favpost/server"
	"github.com/shiwork/favpost/storage"
)

var confPath = os.Getenv("FAVPOST_CONFIG")

func main() {
	conf, err := config.Parse(confPath)
	if err != nil {
		log.Fatal(err)
	}

	db, err := sql.Open("mysql", conf.DbDsn)
	if err != nil {
		log.Fatalf("Db initialize error %v\n", err)
	}
	err = storage.Prepare(db)
	if err != nil {
		log.Fatalf("Db prepare error: %v\n", err)
	}

	anaconda.SetConsumerKey(conf.Consumer.ConsumerKey)
	anaconda.SetConsumerSecret(conf.Consumer.ConsumerSecret)

	// このへんは後で
	//	var callbackURL = "http://localhost:9001/auth/callback"
	//	credentials, err := anaconda.AuthorizationURL(callbackURL)

	//var user_id = int64(90649479) // @shiwork
	userRepos := model.GetUserRepository(db)
	go func() {
		for {
			// 酷いけどとりあえず全ユーザーを取得して処理を回す
			users := &[]model.User{}
			users, err = userRepos.GetAll()
			// sleep 5min
			time.Sleep(5 * time.Minute)

			for _, user := range *users {
				//user := &model.User{}
				//user, err = userRepos.Get(user_id)
				api := anaconda.NewTwitterApi(user.AccessToken.Token, user.AccessToken.Secret)
				searchResult, _ := api.GetFavorites(nil)

				for _, tweet := range searchResult {
					if len(tweet.Entities.Media) > 0 {

						exists, _ := storage.Exists(db, tweet)
						if !exists {
							storage.Add(db, tweet)
							model.SlackShare{conf.WebHookURL}.Share(tweet)
						}
					}
				}
			}

			// sleep 5min
			time.Sleep(5 * time.Minute)
		}
	}()

	/*
		fmt.Println("run http")
		http.HandleFunc("/", server.SayhelloName)
		err = http.ListenAndServe(":9090", nil)
		if err != nil {
			fmt.Println("http Listen error")
			log.Fatal("ListenAndServe: ", err)
		}
		fmt.Println("end of main func.")
	*/
	server.Run(conf, db)
}
<commit_msg>Port 80に変更<commit_after>package main

import (
	"database/sql"
	"log"
	"os"
	"time"

	"github.com/ChimeraCoder/anaconda"
	_ "github.com/go-sql-driver/mysql"
	"github.com/shiwork/favpost/config"
	"github.com/shiwork/favpost/model"
	"github.com/shiwork/favpost/server"
	"github.com/shiwork/favpost/storage"
	"flag"
)

var confPath = os.Getenv("FAVPOST_CONFIG")

func main() {
	conf, err := config.Parse(confPath)
	if err != nil {
		log.Fatal(err)
	}

	db, err := sql.Open("mysql", conf.DbDsn)
	if err != nil {
		log.Fatalf("Db initialize error %v\n", err)
	}
	err = storage.Prepare(db)
	if err != nil {
		log.Fatalf("Db prepare error: %v\n", err)
	}

	flag.Set("bind", ":80")

	anaconda.SetConsumerKey(conf.Consumer.ConsumerKey)
	anaconda.SetConsumerSecret(conf.Consumer.ConsumerSecret)

	// このへんは後で
	//	var callbackURL = "http://localhost:9001/auth/callback"
	//	credentials, err := anaconda.AuthorizationURL(callbackURL)

	//var user_id = int64(90649479) // @shiwork
	userRepos := model.GetUserRepository(db)
	go func() {
		for {
			// 酷いけどとりあえず全ユーザーを取得して処理を回す
			users := &[]model.User{}
			users, err = userRepos.GetAll()
			// sleep 5min
			time.Sleep(5 * time.Minute)

			for _, user := range *users {
				//user := &model.User{}
				//user, err = userRepos.Get(user_id)
				api := anaconda.NewTwitterApi(user.AccessToken.Token, user.AccessToken.Secret)
				searchResult, _ := api.GetFavorites(nil)

				for _, tweet := range searchResult {
					if len(tweet.Entities.Media) > 0 {

						exists, _ := storage.Exists(db, tweet)
						if !exists {
							storage.Add(db, tweet)
							model.SlackShare{conf.WebHookURL}.Share(tweet)
						}
					}
				}
			}

			// sleep 5min
			time.Sleep(5 * time.Minute)
		}
	}()

	/*
		fmt.Println("run http")
		http.HandleFunc("/", server.SayhelloName)
		err = http.ListenAndServe(":9090", nil)
		if err != nil {
			fmt.Println("http Listen error")
			log.Fatal("ListenAndServe: ", err)
		}
		fmt.Println("end of main func.")
	*/
	server.Run(conf, db)
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"crypto/tls"
	"flag"
	"fmt"
	"net"
	"net/http"
	"os"
	"os/signal"
	"sync/atomic"
	"syscall"
	"time"

	"github.com/hagen1778/chproxy/config"
	"github.com/hagen1778/chproxy/log"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"golang.org/x/crypto/acme/autocert"
)

var configFile = flag.String("config", "proxy.yml", "Proxy configuration filename")

var (
	proxy    = newReverseProxy()
	networks atomic.Value
)

func main() {
	flag.Parse()

	log.Infof("Loading config: %s", *configFile)
	cfg, err := reloadConfig()
	if err != nil {
		log.Fatalf("error while loading config: %s", err)
	}
	log.Infof("Loading config %s: successful", *configFile)

	c := make(chan os.Signal)
	signal.Notify(c, syscall.SIGHUP)
	go func() {
		for {
			switch <-c {
			case syscall.SIGHUP:
				log.Infof("SIGHUP received. Going to reload config %s ...", *configFile)
				if _, err := reloadConfig(); err != nil {
					log.Errorf("error while reloading config: %s", err)
					continue
				}
				log.Infof("Reloading config %s: successful", *configFile)
			}
		}
	}()

	if cfg.IsTLS {
		log.Fatalf("TLS server error on %q: %s", cfg.ListenAddr, serveTLS(cfg.ListenAddr, cfg.TLSConfig))
	}

	log.Fatalf("Server error on %q: %s", cfg.ListenAddr, serve(cfg.ListenAddr))
}

func serveTLS(addr string, tlsConfig config.TLSConfig) error {
	ln := newTLSListener(addr, tlsConfig)

	log.Infof("Serving https on %q", addr)
	return listenAndServe(ln)
}

func serve(addr string) error {
	ln := newListener(addr)

	log.Infof("Serving http on %q", addr)
	return listenAndServe(ln)
}

func newListener(laddr string) net.Listener {
	ln, err := net.Listen("tcp4", laddr)
	if err != nil {
		log.Fatalf("cannot listen for %q: %s", laddr, err)
	}
	return &netListener{
		ln,
	}
}

func newTLSListener(laddr string, tlsConf config.TLSConfig) net.Listener {
	tlsConfig := tls.Config{
		PreferServerCipherSuites: true,
		CurvePreferences: []tls.CurveID{
			tls.CurveP256,
			tls.X25519,
		},
	}

	if len(tlsConf.KeyFile) > 0 && len(tlsConf.CertFile) > 0 {
		cert, err := tls.LoadX509KeyPair(tlsConf.CertFile, tlsConf.KeyFile)
		if err != nil {
			log.Fatalf("cannot load cert for `tls_config.cert_file`=%q, `tls_config.key_file`=%q: %s",
				tlsConf.CertFile, tlsConf.KeyFile, err)
		}
		tlsConfig.Certificates = []tls.Certificate{cert}
	} else {
		if len(tlsConf.CertCacheDir) > 0 {
			if _, err := os.Stat(tlsConf.CertCacheDir); os.IsNotExist(err) {
				log.Fatalf("folder %q does not exist", tlsConf.CertCacheDir)
			}
		}

		var hp autocert.HostPolicy
		if len(tlsConf.HostPolicy) != 0 {
			allowedHosts := make(map[string]struct{}, len(tlsConf.HostPolicy))
			for _, v := range tlsConf.HostPolicy {
				allowedHosts[v] = struct{}{}
			}

			hp = func(_ context.Context, host string) error {
				if _, ok := allowedHosts[host]; ok {
					return nil
				}

				return fmt.Errorf("host %q doesn't match `host_policy` configuration", host)
			}
		}

		m := autocert.Manager{
			Prompt:     autocert.AcceptTOS,
			Cache:      autocert.DirCache(tlsConf.CertCacheDir),
			HostPolicy: hp,
		}

		tlsConfig.GetCertificate = m.GetCertificate
	}

	ln := newListener(laddr)
	return tls.NewListener(ln, &tlsConfig)
}

func listenAndServe(ln net.Listener) error {
	s := &http.Server{
		TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
		Handler:      http.HandlerFunc(serveHTTP),
		ReadTimeout:  time.Minute,
		WriteTimeout: time.Minute,
		IdleTimeout:  time.Minute * 10,
		ErrorLog:     log.ErrorLogger,
	}

	return s.Serve(ln)
}

var promHandler = promhttp.Handler()

func serveHTTP(rw http.ResponseWriter, r *http.Request) {
	switch r.URL.Path {
	case "/favicon.ico":
	case "/metrics":
		promHandler.ServeHTTP(rw, r)
	case "/":
		goodRequest.Inc()
		proxy.ServeHTTP(rw, r)
	default:
		badRequest.Inc()
		log.Debugf("Unsupported path: %s", r.URL.Path)
	}
}

type netListener struct {
	net.Listener
}

func (ln *netListener) Accept() (net.Conn, error) {
	for {
		conn, err := ln.Listener.Accept()
		if err != nil {
			return nil, err
		}

		remoteAddr := conn.RemoteAddr().String()
		allowedNetworks := networks.Load().(*config.Networks)
		if !allowedNetworks.Contains(remoteAddr) {
			log.Errorf("connections are not allowed from %s", remoteAddr)
			conn.Close()
			continue
		}

		return conn, nil
	}
}

func reloadConfig() (*config.Server, error) {
	cfg, err := config.LoadFile(*configFile)
	if err != nil {
		return nil, fmt.Errorf("can't load config %q: %s", *configFile, err)
	}

	if err := proxy.ApplyConfig(cfg); err != nil {
		return nil, err
	}

	networks.Store(&cfg.Networks)
	log.SetDebug(cfg.LogDebug)
	return &cfg.Server, nil
}
<commit_msg>make error processing more readable<commit_after>package main

import (
	"context"
	"crypto/tls"
	"flag"
	"fmt"
	"net"
	"net/http"
	"os"
	"os/signal"
	"sync/atomic"
	"syscall"
	"time"

	"github.com/hagen1778/chproxy/config"
	"github.com/hagen1778/chproxy/log"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"golang.org/x/crypto/acme/autocert"
)

var configFile = flag.String("config", "proxy.yml", "Proxy configuration filename")

var (
	proxy    = newReverseProxy()
	networks atomic.Value
)

func main() {
	flag.Parse()

	log.Infof("Loading config: %s", *configFile)
	cfg, err := reloadConfig()
	if err != nil {
		log.Fatalf("error while loading config: %s", err)
	}
	log.Infof("Loading config %s: successful", *configFile)

	c := make(chan os.Signal)
	signal.Notify(c, syscall.SIGHUP)
	go func() {
		for {
			switch <-c {
			case syscall.SIGHUP:
				log.Infof("SIGHUP received. Going to reload config %s ...", *configFile)
				if _, err := reloadConfig(); err != nil {
					log.Errorf("error while reloading config: %s", err)
					continue
				}
				log.Infof("Reloading config %s: successful", *configFile)
			}
		}
	}()

	if cfg.IsTLS {
		if err := serveTLS(cfg.ListenAddr, cfg.TLSConfig); err != nil {
			log.Fatalf("TLS server error on %q: %s", cfg.ListenAddr, err)
		}
	}

	if err := serve(cfg.ListenAddr); err != nil {
		log.Fatalf("Server error on %q: %s", cfg.ListenAddr, err)
	}
}

func serveTLS(addr string, tlsConfig config.TLSConfig) error {
	ln := newTLSListener(addr, tlsConfig)

	log.Infof("Serving https on %q", addr)
	return listenAndServe(ln)
}

func serve(addr string) error {
	ln := newListener(addr)

	log.Infof("Serving http on %q", addr)
	return listenAndServe(ln)
}

func newListener(laddr string) net.Listener {
	ln, err := net.Listen("tcp4", laddr)
	if err != nil {
		log.Fatalf("cannot listen for %q: %s", laddr, err)
	}
	return &netListener{
		ln,
	}
}

func newTLSListener(laddr string, tlsConf config.TLSConfig) net.Listener {
	tlsConfig := tls.Config{
		PreferServerCipherSuites: true,
		CurvePreferences: []tls.CurveID{
			tls.CurveP256,
			tls.X25519,
		},
	}

	if len(tlsConf.KeyFile) > 0 && len(tlsConf.CertFile) > 0 {
		cert, err := tls.LoadX509KeyPair(tlsConf.CertFile, tlsConf.KeyFile)
		if err != nil {
			log.Fatalf("cannot load cert for `tls_config.cert_file`=%q, `tls_config.key_file`=%q: %s",
				tlsConf.CertFile, tlsConf.KeyFile, err)
		}
		tlsConfig.Certificates = []tls.Certificate{cert}
	} else {
		if len(tlsConf.CertCacheDir) > 0 {
			if _, err := os.Stat(tlsConf.CertCacheDir); os.IsNotExist(err) {
				log.Fatalf("folder %q does not exist", tlsConf.CertCacheDir)
			}
		}

		var hp autocert.HostPolicy
		if len(tlsConf.HostPolicy) != 0 {
			allowedHosts := make(map[string]struct{}, len(tlsConf.HostPolicy))
			for _, v := range tlsConf.HostPolicy {
				allowedHosts[v] = struct{}{}
			}

			hp = func(_ context.Context, host string) error {
				if _, ok := allowedHosts[host]; ok {
					return nil
				}

				return fmt.Errorf("host %q doesn't match `host_policy` configuration", host)
			}
		}

		m := autocert.Manager{
			Prompt:     autocert.AcceptTOS,
			Cache:      autocert.DirCache(tlsConf.CertCacheDir),
			HostPolicy: hp,
		}

		tlsConfig.GetCertificate = m.GetCertificate
	}

	ln := newListener(laddr)
	return tls.NewListener(ln, &tlsConfig)
}

func listenAndServe(ln net.Listener) error {
	s := &http.Server{
		TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
		Handler:      http.HandlerFunc(serveHTTP),
		ReadTimeout:  time.Minute,
		WriteTimeout: time.Minute,
		IdleTimeout:  time.Minute * 10,
		ErrorLog:     log.ErrorLogger,
	}

	return s.Serve(ln)
}

var promHandler = promhttp.Handler()

func serveHTTP(rw http.ResponseWriter, r *http.Request) {
	switch r.URL.Path {
	case "/favicon.ico":
	case "/metrics":
		promHandler.ServeHTTP(rw, r)
	case "/":
		goodRequest.Inc()
		proxy.ServeHTTP(rw, r)
	default:
		badRequest.Inc()
		log.Debugf("Unsupported path: %s", r.URL.Path)
	}
}

type netListener struct {
	net.Listener
}

func (ln *netListener) Accept() (net.Conn, error) {
	for {
		conn, err := ln.Listener.Accept()
		if err != nil {
			return nil, err
		}

		remoteAddr := conn.RemoteAddr().String()
		allowedNetworks := networks.Load().(*config.Networks)
		if !allowedNetworks.Contains(remoteAddr) {
			log.Errorf("connections are not allowed from %s", remoteAddr)
			conn.Close()
			continue
		}

		return conn, nil
	}
}

func reloadConfig() (*config.Server, error) {
	cfg, err := config.LoadFile(*configFile)
	if err != nil {
		return nil, fmt.Errorf("can't load config %q: %s", *configFile, err)
	}

	if err := proxy.ApplyConfig(cfg); err != nil {
		return nil, err
	}

	networks.Store(&cfg.Networks)
	log.SetDebug(cfg.LogDebug)
	return &cfg.Server, nil
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net/http"
	"runtime/debug"

	"github.com/ayoisaiah/stellar-photos-server/config"
	"github.com/ayoisaiah/stellar-photos-server/dropbox"
	"github.com/ayoisaiah/stellar-photos-server/googledrive"
	"github.com/ayoisaiah/stellar-photos-server/onedrive"
	"github.com/ayoisaiah/stellar-photos-server/unsplash"
	"github.com/ayoisaiah/stellar-photos-server/utils"
	"github.com/ayoisaiah/stellar-photos-server/weather"
	"github.com/joho/godotenv"
	"github.com/rs/cors"
	"gopkg.in/natefinch/lumberjack.v2"
)

type rootHandler func(w http.ResponseWriter, r *http.Request) error

func (fn rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Handle panics
	defer func() {
		if err := recover(); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			log.Println("err: ", err, "trace: ", string(debug.Stack()))
		}
	}()

	err := fn(w, r)
	if err == nil {
		return
	}

	log.Printf("An error accured: %v\n", err)

	clientError, ok := err.(utils.ClientError)
	if !ok {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	body, err := clientError.ResponseBody()
	if err != nil {
		log.Printf("An error accured: %v\n", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	status, headers := clientError.ResponseHeaders()
	for k, v := range headers {
		w.Header().Set(k, v)
	}

	w.WriteHeader(status)
	w.Write(body)
}

// newRouter creates and returns a new HTTP request multiplexer
func newRouter() *http.ServeMux {
	mux := http.NewServeMux()

	mux.Handle("/download-photo/", rootHandler(unsplash.DownloadPhoto))
	mux.Handle("/search-unsplash/", rootHandler(unsplash.SearchUnsplash))
	mux.Handle("/random-photo/", rootHandler(unsplash.GetRandomPhoto))
	mux.Handle("/validate-collections/", rootHandler(unsplash.ValidateCollections))
	mux.Handle("/get-weather/", rootHandler(weather.GetForecast))
	mux.Handle("/dropbox/key/", rootHandler(dropbox.SendDropboxKey))
	mux.Handle("/dropbox/save/", rootHandler(dropbox.SaveToDropbox))
	mux.Handle("/onedrive/id/", rootHandler(onedrive.SendOnedriveID))
	mux.Handle("/onedrive/auth/", rootHandler(onedrive.AuthorizeOnedrive))
	mux.Handle("/onedrive/refresh/", rootHandler(onedrive.RefreshOnedriveToken))
	mux.Handle("/googledrive/key/", rootHandler(googledrive.SendGoogleDriveKey))
	mux.Handle("/googledrive/auth/", rootHandler(googledrive.AuthorizeGoogleDrive))
	mux.Handle("/googledrive/refresh/", rootHandler(googledrive.RefreshGoogleDriveToken))
	mux.Handle("/googledrive/save/", rootHandler(googledrive.SaveToGoogleDrive))

	return mux
}

func run() error {
	godotenv.Load()

	// Initialising the config package will crash the program if one of
	// the required Env values is not set
	conf := config.New()

	// Set output for log statements
	log.SetOutput(&lumberjack.Logger{
		Filename:   "stellar.log",
		MaxSize:    5,
		MaxBackups: 3,
		MaxAge:     28,
		Compress:   false,
	})

	port := ":" + conf.Port

	mux := newRouter()

	handler := cors.Default().Handler(mux)

	srv := &http.Server{
		Addr:    port,
		Handler: handler,
	}

	return srv.ListenAndServe()
}

func main() {
	if err := run(); err != nil {
		log.Fatal(err)
	}
}
<commit_msg>Set log output to stdout<commit_after>package main

import (
	"log"
	"net/http"
	"runtime/debug"

	"github.com/ayoisaiah/stellar-photos-server/config"
	"github.com/ayoisaiah/stellar-photos-server/dropbox"
	"github.com/ayoisaiah/stellar-photos-server/googledrive"
	"github.com/ayoisaiah/stellar-photos-server/onedrive"
	"github.com/ayoisaiah/stellar-photos-server/unsplash"
	"github.com/ayoisaiah/stellar-photos-server/utils"
	"github.com/ayoisaiah/stellar-photos-server/weather"
	"github.com/joho/godotenv"
	"github.com/rs/cors"
)

type rootHandler func(w http.ResponseWriter, r *http.Request) error

func (fn rootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Handle panics
	defer func() {
		if err := recover(); err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			log.Println("err: ", err, "trace: ", string(debug.Stack()))
		}
	}()

	err := fn(w, r)
	if err == nil {
		return
	}

	log.Printf("An error accured: %v\n", err)

	clientError, ok := err.(utils.ClientError)
	if !ok {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	body, err := clientError.ResponseBody()
	if err != nil {
		log.Printf("An error accured: %v\n", err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	status, headers := clientError.ResponseHeaders()
	for k, v := range headers {
		w.Header().Set(k, v)
	}

	w.WriteHeader(status)
	w.Write(body)
}

// newRouter creates and returns a new HTTP request multiplexer
func newRouter() *http.ServeMux {
	mux := http.NewServeMux()

	mux.Handle("/download-photo/", rootHandler(unsplash.DownloadPhoto))
	mux.Handle("/search-unsplash/", rootHandler(unsplash.SearchUnsplash))
	mux.Handle("/random-photo/", rootHandler(unsplash.GetRandomPhoto))
	mux.Handle("/validate-collections/", rootHandler(unsplash.ValidateCollections))
	mux.Handle("/get-weather/", rootHandler(weather.GetForecast))
	mux.Handle("/dropbox/key/", rootHandler(dropbox.SendDropboxKey))
	mux.Handle("/dropbox/save/", rootHandler(dropbox.SaveToDropbox))
	mux.Handle("/onedrive/id/", rootHandler(onedrive.SendOnedriveID))
	mux.Handle("/onedrive/auth/", rootHandler(onedrive.AuthorizeOnedrive))
	mux.Handle("/onedrive/refresh/", rootHandler(onedrive.RefreshOnedriveToken))
	mux.Handle("/googledrive/key/", rootHandler(googledrive.SendGoogleDriveKey))
	mux.Handle("/googledrive/auth/", rootHandler(googledrive.AuthorizeGoogleDrive))
	mux.Handle("/googledrive/refresh/", rootHandler(googledrive.RefreshGoogleDriveToken))
	mux.Handle("/googledrive/save/", rootHandler(googledrive.SaveToGoogleDrive))

	return mux
}

func run() error {
	godotenv.Load()

	// Initialising the config package will crash the program if one of
	// the required Env values is not set
	conf := config.New()

	port := ":" + conf.Port

	mux := newRouter()

	handler := cors.Default().Handler(mux)

	srv := &http.Server{
		Addr:    port,
		Handler: handler,
	}

	return srv.ListenAndServe()
}

func main() {
	if err := run(); err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>