<commit_before>// Package c2go contains the main function for running the executable.
package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/elliotchance/c2go/ast"
	"github.com/elliotchance/c2go/program"
	"github.com/elliotchance/c2go/transpiler"
)

// Version can be requested through the command line with:
//
//     c2go -v
//
// See https://github.com/elliotchance/c2go/wiki/Release-Process
const Version = "0.13.3"

// ProgramArgs - arguments of program
type ProgramArgs struct {
	verbose     bool
	ast         bool
	inputFile   string
	outputFile  string
	packageName string
}

func readAST(data []byte) []string {
	uncolored := regexp.MustCompile(`\x1b\[[\d;]+m`).ReplaceAll(data, []byte{})
	return strings.Split(string(uncolored), "\n")
}

type treeNode struct {
	indent int
	node   ast.Node
}

func convertLinesToNodes(lines []string) []treeNode {
	nodes := []treeNode{}
	for _, line := range lines {
		if strings.TrimSpace(line) == "" {
			continue
		}

		// It is tempting to discard null AST nodes, but these may
		// have semantic importance: for example, they represent omitted
		// for-loop conditions, as in for(;;).
		line = strings.Replace(line, "<<<NULL>>>", "NullStmt", 1)

		indentAndType := regexp.MustCompile("^([|\\- `]*)(\\w+)").FindStringSubmatch(line)
		if len(indentAndType) == 0 {
			panic(fmt.Sprintf("Cannot understand line '%s'", line))
		}

		offset := len(indentAndType[1])
		node := ast.Parse(line[offset:])

		indentLevel := len(indentAndType[1]) / 2
		nodes = append(nodes, treeNode{indentLevel, node})
	}

	return nodes
}

// buildTree convert an array of nodes, each prefixed with a depth into a tree.
func buildTree(nodes []treeNode, depth int) []ast.Node {
	if len(nodes) == 0 {
		return []ast.Node{}
	}

	// Split the list into sections, treat each section as a a tree with its own root.
	sections := [][]treeNode{}
	for _, node := range nodes {
		if node.indent == depth {
			sections = append(sections, []treeNode{node})
		} else {
			sections[len(sections)-1] = append(sections[len(sections)-1], node)
		}
	}

	results := []ast.Node{}
	for _, section := range sections {
		slice := []treeNode{}
		for _, n := range section {
			if n.indent > depth {
				slice = append(slice, n)
			}
		}

		children := buildTree(slice, depth+1)
		for _, child := range children {
			section[0].node.AddChild(child)
		}
		results = append(results, section[0].node)
	}

	return results
}

/* Dead code
// ToJSON - tree convert to JSON
func ToJSON(tree []interface{}) []map[string]interface{} {
	r := make([]map[string]interface{}, len(tree))

	for j, n := range tree {
		rn := reflect.ValueOf(n).Elem()
		r[j] = make(map[string]interface{})
		r[j]["node"] = rn.Type().Name()

		for i := 0; i < rn.NumField(); i++ {
			name := strings.ToLower(rn.Type().Field(i).Name)
			value := rn.Field(i).Interface()

			if name == "children" {
				v := value.([]interface{})

				if len(v) == 0 {
					continue
				}

				value = ToJSON(v)
			}

			r[j][name] = value
		}
	}

	return r
}
*/

// stringToLines - convert string to string lines
func stringToLines(s string) (lines []string, err error) {
	scanner := bufio.NewScanner(strings.NewReader(s))
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}

	if err = scanner.Err(); err != nil {
		return lines, fmt.Errorf("reading standard input: %v", err)
	}

	return lines, nil
}

type headerTypes bool

const (
	systemHeader   headerTypes = true
	internalHeader headerTypes = false
)

type include struct {
	headerName string
	typeHeader headerTypes
}

func (inc include) String() (s string) {
	s += fmt.Sprintf("\tHeader name : %v\n", inc.headerName)
	s += fmt.Sprintf("\tType : ")
	switch inc.typeHeader {
	case systemHeader:
		s += fmt.Sprintf("System header\n")
	case internalHeader:
		s += fmt.Sprintf("Internal header\n")
	}
	return
}

// parseForFoundLostIncluse - parsing string to
// found lost includes in c code after clang
//
// Example of input:
// E:\Temp\c2go001preprocess.c:1:10: fatal error: 'AbsoluteWrongInclude.h' file not found
// #include <AbsoluteWrongInclude.h>
//          ^~~~~~~~~~~~~~~~~~~~~~~~
// tests\struct.c:3:10: fatal error: 'stdio.h' file not found
//                #include <stdio.h>
//                         ^~~~~~~~~
//                1 error generated.
func parseForFoundLostIncluse(s string) (includes []include, err error) {
	lines, err := stringToLines(s)
	if err != nil {
		return includes, err
	}
	includeName := "#include"
	regSystem := regexp.MustCompile("<(.*?)>")
	regInternal := regexp.MustCompile("\"(.*?)\"")
	for _, line := range lines {
		if !strings.Contains(line, includeName) {
			continue
		}
		// Now, we know - the string line have "#include"
		// Find the name of header file
		var inc include
		if strings.ContainsAny(line, "<>") {
			// system header
			inc.headerName = regSystem.FindString(line)
			inc.typeHeader = systemHeader
		} else {
			// internal header
			inc.headerName = regInternal.FindString(line)
			inc.typeHeader = internalHeader
		}
		inc.headerName = inc.headerName[1 : len(inc.headerName)-1]
		includes = append(includes, inc)
	}
	return includes, nil
}

// Start - base function
func Start(args ProgramArgs) error {
	if os.Getenv("GOPATH") == "" {
		return fmt.Errorf("The $GOPATH must be set")
	}

	// 1. Compile it first (checking for errors)
	_, err := os.Stat(args.inputFile)
	if err != nil {
		return fmt.Errorf("Input file is not found")
	}

	// 2. Preprocess
	var pp []byte
	{
		cmd := exec.Command("clang", "-E", args.inputFile)
		var out bytes.Buffer
		var stderr bytes.Buffer
		cmd.Stdout = &out
		cmd.Stderr = &stderr
		err = cmd.Run()
		if err != nil {
			var errorResult string
			// add error message from stdErr
			errorResult += fmt.Sprintf("preprocess failed: %v\nStdErr = %v\n", err, stderr.String())
			// parse error output for found lost includes
			lostIncludes, err := parseForFoundLostIncluse(stderr.String())
			if err != nil {
				errorResult += fmt.Sprintf("Cannot parse for found lost includes: %v", err)
			} else {
				for _, lostInclude := range lostIncludes {
					errorResult += fmt.Sprintf("Lost #include:\n %v\n", lostInclude)
				}
			}
			return fmt.Errorf(errorResult)
		}
		pp = []byte(out.String())
	}

	ppFilePath := path.Join(os.TempDir(), "pp.c")
	err = ioutil.WriteFile(ppFilePath, pp, 0644)
	if err != nil {
		return fmt.Errorf("writing to /tmp/pp.c failed: %v", err)
	}

	// 3. Generate JSON from AST
	astPP, err := exec.Command("clang", "-Xclang", "-ast-dump", "-fsyntax-only", ppFilePath).Output()
	if err != nil {
		// If clang fails it still prints out the AST, so we have to run it
		// again to get the real error.
		errBody, _ := exec.Command("clang", ppFilePath).CombinedOutput()

		panic("clang failed: " + err.Error() + ":\n\n" + string(errBody))
	}

	lines := readAST(astPP)
	if args.ast {
		for _, l := range lines {
			fmt.Println(l)
		}
		fmt.Println()
	}

	nodes := convertLinesToNodes(lines)
	tree := buildTree(nodes, 0)

	p := program.NewProgram()
	p.Verbose = args.verbose

	err = transpiler.TranspileAST(args.inputFile, args.packageName, p, tree[0].(ast.Node))
	if err != nil {
		panic(err)
	}

	outputFilePath := args.outputFile

	if outputFilePath == "" {
		cleanFileName := filepath.Clean(filepath.Base(args.inputFile))
		extension := filepath.Ext(args.inputFile)

		outputFilePath = cleanFileName[0:len(cleanFileName)-len(extension)] + ".go"
	}

	err = ioutil.WriteFile(outputFilePath, []byte(p.String()), 0755)
	if err != nil {
		return fmt.Errorf("writing C output file failed: %v", err)
	}

	return nil
}

// newTempFile - returns temp file
func newTempFile(dir, prefix, suffix string) (*os.File, error) {
	for index := 1; index < 10000; index++ {
		path := filepath.Join(dir, fmt.Sprintf("%s%03d%s", prefix, index, suffix))
		if _, err := os.Stat(path); err != nil {
			return os.Create(path)
		}
	}
	return nil, fmt.Errorf("could not create file: %s%03d%s", prefix, 1, suffix)
}

func main() {
	var (
		versionFlag       = flag.Bool("v", false, "print the version and exit")
		transpileCommand  = flag.NewFlagSet("transpile", flag.ContinueOnError)
		verboseFlag       = transpileCommand.Bool("V", false, "print progress as comments")
		outputFlag        = transpileCommand.String("o", "", "output Go generated code to the specified file")
		packageFlag       = transpileCommand.String("p", "main", "set the name of the generated package")
		transpileHelpFlag = transpileCommand.Bool("h", false, "print help information")
		astCommand        = flag.NewFlagSet("ast", flag.ContinueOnError)
		astHelpFlag       = astCommand.Bool("h", false, "print help information")
	)

	flag.Usage = func() {
		usage := "Usage: %s [-v] [<command>] [<flags>] file.c\n\n"
		usage += "Commands:\n"
		usage += "  transpile\ttranspile an input C source file to Go\n"
		usage += "  ast\t\tprint AST before translated Go code\n\n"

		usage += "Flags:\n"
		fmt.Fprintf(os.Stderr, usage, os.Args[0])
		flag.PrintDefaults()

		// print flags of transpile command
		fmt.Println("\nFlags of transpile command:")
		transpileCommand.PrintDefaults()

		// print flags of ast command
		fmt.Println("\nFlags of ast command:")
		astCommand.PrintDefaults()

		// examples
		fmt.Println("\nExamples of flag using:")
		fmt.Println("\nc2go -h\n\treturn the help", "")
		fmt.Println("\nc2go transpile -o source.go source.c\n\ttranspiling file source.c to Go file with name source.go")
	}

	flag.Parse()

	if *versionFlag {
		// Simply print out the version and exit.
		fmt.Println(Version)
		return
	}

	if flag.NArg() < 1 {
		flag.Usage()
		os.Exit(1)
	}

	args := ProgramArgs{verbose: *verboseFlag, ast: false}

	switch os.Args[1] {
	case "ast":
		err := astCommand.Parse(os.Args[2:])
		if err != nil {
			fmt.Printf("ast command cannot parse: %v", err)
			os.Exit(1)
		}

		if *astHelpFlag || astCommand.NArg() == 0 {
			fmt.Fprintf(os.Stderr, "Usage: %s ast file.c\n", os.Args[0])
			astCommand.PrintDefaults()
			os.Exit(1)
		}

		args.ast = true
		args.inputFile = astCommand.Arg(0)
	case "transpile":
		err := transpileCommand.Parse(os.Args[2:])
		if err != nil {
			fmt.Printf("transpile command cannot parse: %v", err)
			os.Exit(1)
		}

		if *transpileHelpFlag || transpileCommand.NArg() == 0 {
			fmt.Fprintf(os.Stderr, "Usage: %s transpile [-V] [-o file.go] [-p package] file.c\n", os.Args[0])
			transpileCommand.PrintDefaults()
			os.Exit(1)
		}

		args.inputFile = transpileCommand.Arg(0)
		args.outputFile = *outputFlag
		args.packageName = *packageFlag
	default:
		flag.Usage()
		os.Exit(1)
	}

	if err := Start(args); err != nil {
		fmt.Printf("Error: %v", err)
		os.Exit(1)
	}
}
<commit_msg>prepare for deleting system header files<commit_after>// Package c2go contains the main function for running the executable.
package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/elliotchance/c2go/ast"
	"github.com/elliotchance/c2go/program"
	"github.com/elliotchance/c2go/transpiler"
)

// Version can be requested through the command line with:
//
//     c2go -v
//
// See https://github.com/elliotchance/c2go/wiki/Release-Process
const Version = "0.13.3"

// ProgramArgs - arguments of program
type ProgramArgs struct {
	verbose     bool
	ast         bool
	inputFile   string
	outputFile  string
	packageName string
}

func readAST(data []byte) []string {
	uncolored := regexp.MustCompile(`\x1b\[[\d;]+m`).ReplaceAll(data, []byte{})
	return strings.Split(string(uncolored), "\n")
}

type treeNode struct {
	indent int
	node   ast.Node
}

func convertLinesToNodes(lines []string) []treeNode {
	nodes := []treeNode{}
	for _, line := range lines {
		if strings.TrimSpace(line) == "" {
			continue
		}

		// It is tempting to discard null AST nodes, but these may
		// have semantic importance: for example, they represent omitted
		// for-loop conditions, as in for(;;).
		line = strings.Replace(line, "<<<NULL>>>", "NullStmt", 1)

		indentAndType := regexp.MustCompile("^([|\\- `]*)(\\w+)").FindStringSubmatch(line)
		if len(indentAndType) == 0 {
			panic(fmt.Sprintf("Cannot understand line '%s'", line))
		}

		offset := len(indentAndType[1])
		node := ast.Parse(line[offset:])

		indentLevel := len(indentAndType[1]) / 2
		nodes = append(nodes, treeNode{indentLevel, node})
	}

	return nodes
}

// buildTree convert an array of nodes, each prefixed with a depth into a tree.
func buildTree(nodes []treeNode, depth int) []ast.Node {
	if len(nodes) == 0 {
		return []ast.Node{}
	}

	// Split the list into sections, treat each section as a a tree with its own root.
	sections := [][]treeNode{}
	for _, node := range nodes {
		if node.indent == depth {
			sections = append(sections, []treeNode{node})
		} else {
			sections[len(sections)-1] = append(sections[len(sections)-1], node)
		}
	}

	results := []ast.Node{}
	for _, section := range sections {
		slice := []treeNode{}
		for _, n := range section {
			if n.indent > depth {
				slice = append(slice, n)
			}
		}

		children := buildTree(slice, depth+1)
		for _, child := range children {
			section[0].node.AddChild(child)
		}
		results = append(results, section[0].node)
	}

	return results
}

/* Dead code
// ToJSON - tree convert to JSON
func ToJSON(tree []interface{}) []map[string]interface{} {
	r := make([]map[string]interface{}, len(tree))

	for j, n := range tree {
		rn := reflect.ValueOf(n).Elem()
		r[j] = make(map[string]interface{})
		r[j]["node"] = rn.Type().Name()

		for i := 0; i < rn.NumField(); i++ {
			name := strings.ToLower(rn.Type().Field(i).Name)
			value := rn.Field(i).Interface()

			if name == "children" {
				v := value.([]interface{})

				if len(v) == 0 {
					continue
				}

				value = ToJSON(v)
			}

			r[j][name] = value
		}
	}

	return r
}
*/

// stringToLines - convert string to string lines
func stringToLines(s string) (lines []string, err error) {
	scanner := bufio.NewScanner(strings.NewReader(s))
	for scanner.Scan() {
		lines = append(lines, scanner.Text())
	}

	if err = scanner.Err(); err != nil {
		return lines, fmt.Errorf("reading standard input: %v", err)
	}

	return lines, nil
}

type headerTypes bool

const (
	systemHeader   headerTypes = true
	internalHeader headerTypes = false
)

type include struct {
	headerName string
	typeHeader headerTypes
}

func (inc include) String() (s string) {
	s += fmt.Sprintf("\tHeader name : %v\n", inc.headerName)
	s += fmt.Sprintf("\tType : ")
	switch inc.typeHeader {
	case systemHeader:
		s += fmt.Sprintf("System header\n")
	case internalHeader:
		s += fmt.Sprintf("Internal header\n")
	}
	return
}

// parseForFoundLostIncluse - parsing string to
// found lost includes in c code after clang
//
// Example of input:
// E:\Temp\c2go001preprocess.c:1:10: fatal error: 'AbsoluteWrongInclude.h' file not found
// #include <AbsoluteWrongInclude.h>
//          ^~~~~~~~~~~~~~~~~~~~~~~~
// tests\struct.c:3:10: fatal error: 'stdio.h' file not found
//                #include <stdio.h>
//                         ^~~~~~~~~
//                1 error generated.
func parseForFoundLostIncluse(s string) (includes []include, err error) {
	lines, err := stringToLines(s)
	if err != nil {
		return includes, err
	}
	includeName := "#include"
	regSystem := regexp.MustCompile("<(.*?)>")
	regInternal := regexp.MustCompile("\"(.*?)\"")
	for _, line := range lines {
		if !strings.Contains(line, includeName) {
			continue
		}
		// Now, we know - the string line have "#include"
		// Find the name of header file
		var inc include
		if strings.ContainsAny(line, "<>") {
			// system header
			inc.headerName = regSystem.FindString(line)
			inc.typeHeader = systemHeader
		} else {
			// internal header
			inc.headerName = regInternal.FindString(line)
			inc.typeHeader = internalHeader
		}
		inc.headerName = inc.headerName[1 : len(inc.headerName)-1]
		includes = append(includes, inc)
	}
	return includes, nil
}

// Start - base function
func Start(args ProgramArgs) error {
	if os.Getenv("GOPATH") == "" {
		return fmt.Errorf("The $GOPATH must be set")
	}

	// 1. Compile it first (checking for errors)
	_, err := os.Stat(args.inputFile)
	if err != nil {
		return fmt.Errorf("Input file is not found")
	}

	// 2. Preprocess
	var pp []byte
	{
		cmd := exec.Command("clang", "-E", args.inputFile)
		var out bytes.Buffer
		var stderr bytes.Buffer
		cmd.Stdout = &out
		cmd.Stderr = &stderr
		err = cmd.Run()
		if err != nil {
			var errorResult string
			// add error message from stdErr
			errorResult += fmt.Sprintf("preprocess failed: %v\nStdErr = %v\n", err, stderr.String())
			// parse error output for found lost includes
			lostIncludes, err := parseForFoundLostIncluse(stderr.String())
			if err != nil {
				errorResult += fmt.Sprintf("Cannot parse for found lost includes: %v", err)
			} else {
				// NEED TO APPROVE
				// Theoretically: system headers is not need for transpiling,
				// and system header need only for clang correct working,
				// but internal header is important
				var haveLostInternalHeader bool
				for _, lostInclude := range lostIncludes {
					if lostInclude.typeHeader == internalHeader {
						haveLostInternalHeader = true
						break
					}
				}
				if haveLostInternalHeader {
					for _, lostInclude := range lostIncludes {
						errorResult += fmt.Sprintf("Lost #include:\n %v\n", lostInclude)
					}
				} else {
					// now, we found lost system header file
					// so, we can try remove lost system header file and try again

					// ADD : MORE LOGIC

					panic(fmt.Errorf("Present state of error = %v", errorResult))
				}
			}
			return fmt.Errorf(errorResult)
		}
		pp = []byte(out.String())
	}

	ppFilePath := path.Join(os.TempDir(), "pp.c")
	err = ioutil.WriteFile(ppFilePath, pp, 0644)
	if err != nil {
		return fmt.Errorf("writing to /tmp/pp.c failed: %v", err)
	}

	// 3. Generate JSON from AST
	astPP, err := exec.Command("clang", "-Xclang", "-ast-dump", "-fsyntax-only", ppFilePath).Output()
	if err != nil {
		// If clang fails it still prints out the AST, so we have to run it
		// again to get the real error.
		errBody, _ := exec.Command("clang", ppFilePath).CombinedOutput()

		panic("clang failed: " + err.Error() + ":\n\n" + string(errBody))
	}

	lines := readAST(astPP)
	if args.ast {
		for _, l := range lines {
			fmt.Println(l)
		}
		fmt.Println()
	}

	nodes := convertLinesToNodes(lines)
	tree := buildTree(nodes, 0)

	p := program.NewProgram()
	p.Verbose = args.verbose

	err = transpiler.TranspileAST(args.inputFile, args.packageName, p, tree[0].(ast.Node))
	if err != nil {
		panic(err)
	}

	outputFilePath := args.outputFile

	if outputFilePath == "" {
		cleanFileName := filepath.Clean(filepath.Base(args.inputFile))
		extension := filepath.Ext(args.inputFile)

		outputFilePath = cleanFileName[0:len(cleanFileName)-len(extension)] + ".go"
	}

	err = ioutil.WriteFile(outputFilePath, []byte(p.String()), 0755)
	if err != nil {
		return fmt.Errorf("writing C output file failed: %v", err)
	}

	return nil
}

// newTempFile - returns temp file
func newTempFile(dir, prefix, suffix string) (*os.File, error) {
	for index := 1; index < 10000; index++ {
		path := filepath.Join(dir, fmt.Sprintf("%s%03d%s", prefix, index, suffix))
		if _, err := os.Stat(path); err != nil {
			return os.Create(path)
		}
	}
	return nil, fmt.Errorf("could not create file: %s%03d%s", prefix, 1, suffix)
}

func main() {
	var (
		versionFlag       = flag.Bool("v", false, "print the version and exit")
		transpileCommand  = flag.NewFlagSet("transpile", flag.ContinueOnError)
		verboseFlag       = transpileCommand.Bool("V", false, "print progress as comments")
		outputFlag        = transpileCommand.String("o", "", "output Go generated code to the specified file")
		packageFlag       = transpileCommand.String("p", "main", "set the name of the generated package")
		transpileHelpFlag = transpileCommand.Bool("h", false, "print help information")
		astCommand        = flag.NewFlagSet("ast", flag.ContinueOnError)
		astHelpFlag       = astCommand.Bool("h", false, "print help information")
	)

	flag.Usage = func() {
		usage := "Usage: %s [-v] [<command>] [<flags>] file.c\n\n"
		usage += "Commands:\n"
		usage += "  transpile\ttranspile an input C source file to Go\n"
		usage += "  ast\t\tprint AST before translated Go code\n\n"

		usage += "Flags:\n"
		fmt.Fprintf(os.Stderr, usage, os.Args[0])
		flag.PrintDefaults()

		// print flags of transpile command
		fmt.Println("\nFlags of transpile command:")
		transpileCommand.PrintDefaults()

		// print flags of ast command
		fmt.Println("\nFlags of ast command:")
		astCommand.PrintDefaults()

		// examples
		fmt.Println("\nExamples of flag using:")
		fmt.Println("\nc2go -h\n\treturn the help", "")
		fmt.Println("\nc2go transpile -o source.go source.c\n\ttranspiling file source.c to Go file with name source.go")
	}

	flag.Parse()

	if *versionFlag {
		// Simply print out the version and exit.
		fmt.Println(Version)
		return
	}

	if flag.NArg() < 1 {
		flag.Usage()
		os.Exit(1)
	}

	args := ProgramArgs{verbose: *verboseFlag, ast: false}

	switch os.Args[1] {
	case "ast":
		err := astCommand.Parse(os.Args[2:])
		if err != nil {
			fmt.Printf("ast command cannot parse: %v", err)
			os.Exit(1)
		}

		if *astHelpFlag || astCommand.NArg() == 0 {
			fmt.Fprintf(os.Stderr, "Usage: %s ast file.c\n", os.Args[0])
			astCommand.PrintDefaults()
			os.Exit(1)
		}

		args.ast = true
		args.inputFile = astCommand.Arg(0)
	case "transpile":
		err := transpileCommand.Parse(os.Args[2:])
		if err != nil {
			fmt.Printf("transpile command cannot parse: %v", err)
			os.Exit(1)
		}

		if *transpileHelpFlag || transpileCommand.NArg() == 0 {
			fmt.Fprintf(os.Stderr, "Usage: %s transpile [-V] [-o file.go] [-p package] file.c\n", os.Args[0])
			transpileCommand.PrintDefaults()
			os.Exit(1)
		}

		args.inputFile = transpileCommand.Arg(0)
		args.outputFile = *outputFlag
		args.packageName = *packageFlag
	default:
		flag.Usage()
		os.Exit(1)
	}

	if err := Start(args); err != nil {
		fmt.Printf("Error: %v", err)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"syscall"
	"unicode"
)

var debugFile string
var logFile string
var cmdsFile string
var header Header

// Header defines the struct of the header in the i3bar protocol.
type Header struct {
	Version     int  `json:"version"`
	StopSignal  int  `json:"stop_signal,omitempty"`
	ContSignal  int  `json:"cont_signal,omitempty"`
	ClickEvents bool `json:"click_events,omitempty"`
}

// Block defines the struct of blocks in the i3bar protocol.
type Block struct {
	FullText            string `json:"full_text"`
	ShortText           string `json:"short_text,omitempty"`
	Color               string `json:"color,omitempty"`
	MinWidth            int    `json:"min_width,omitempty"`
	Align               string `json:"align,omitempty"`
	Name                string `json:"name,omitempty"`
	Instance            string `json:"instance,omitempty"`
	Urgent              bool   `json:"urgent,omitempty"`
	Separator           bool   `json:"separator,omitempty"`
	SeparatorBlockWidth int    `json:"separator_block_width,omitempty"`
}

// String implements Stringer interface.
func (b Block) String() string {
	return b.FullText
}

// A CmdIO defines a cmd that will feed the i3bar.
type CmdIO struct {
	// Cmd is the command being run
	Cmd *exec.Cmd
	// reader is the underlying stream where Cmd outputs data.
	reader io.ReadCloser
}

// BlockAggregate relates a CmdIO to the Blocks it produced during one update.
type BlockAggregate struct {
	CmdIO  *CmdIO
	Blocks []*Block
}

// NewCmdIO creates a new CmdIO from command c.
// c must be properly quoted for a shell as it's passed to sh -c.
func NewCmdIO(c string) (*CmdIO, error) {
	cmd := exec.Command(os.Getenv("SHELL"), "-c", c)
	reader, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	cmdio := CmdIO{
		Cmd:    cmd,
		reader: reader,
	}
	return &cmdio, nil
}

// Start runs the command of CmdIO and feeds the BlockAggregatesCh channel
// with the Blocks it produces.
func (c *CmdIO) Start(blockAggregatesCh chan<- *BlockAggregate) error {
	if err := c.Cmd.Start(); err != nil {
		return err
	}
	go func() {
		// We'll handle a few cases here.
		// If JSON is output from i3status, then we need
		// to ignore the i3bar header and opening [,
		// then ignore leading comma on each line.
		// If JSON is output from a script, it assumes the
		// author will not have the header and [, but maybe the comma
		r := bufio.NewReader(c.reader)
		// try Read a header first
		ruune, _, err := r.ReadRune()
		if err != nil {
			log.Println(err)
			return
		}
		if ruune == '{' {
			// Consume the header line
			if _, err := r.ReadString('\n'); err != nil {
				log.Println(err)
				return
			}
			// Consume the next line (opening bracket)
			if _, err := r.ReadString('\n'); err != nil {
				log.Println(err)
				return
			}
		} else {
			r.UnreadRune()
		}
		dec := json.NewDecoder(r)
		defer c.reader.Close()
		for {
			var b []*Block
			// Ignore unwanted chars first
		IgnoreChars:
			for {
				ruune, _, err := r.ReadRune()
				if err != nil {
					log.Println(err)
					break IgnoreChars
				}
				switch {
				case unicode.IsSpace(ruune):
					// Loop again
				case ruune == ',':
					break IgnoreChars
				default:
					r.UnreadRune()
					break IgnoreChars
				}
			}
			if err := dec.Decode(&b); err != nil {
				if err == io.EOF {
					log.Println("reached EOF")
					return
				}
				log.Printf("Invalid JSON input: all decoding methods failed (%v)\n", err)
			} else {
				blockAggregatesCh <- &BlockAggregate{c, b}
			}
		}
	}()
	return nil
}

// BlockAggregator fans-in all Blocks produced by a list of CmdIO and sends it to the writer W.
type BlockAggregator struct {
	// Blocks keeps track of which CmdIO produced which Block list.
	Blocks map[*CmdIO][]*Block
	// CmdIOs keeps an ordered list of the CmdIOs being aggregated.
	CmdIOs []*CmdIO
	// W is where multiplexed input blocks are written to.
	W io.Writer
}

// NewBlockAggregator returns a BlockAggregator which will write to w.
func NewBlockAggregator(w io.Writer) *BlockAggregator {
	return &BlockAggregator{
		Blocks: make(map[*CmdIO][]*Block),
		CmdIOs: make([]*CmdIO, 0),
		W:      w,
	}
}

// Aggregate starts aggregating data coming from the BlockAggregates channel.
func (ba *BlockAggregator) Aggregate(blockAggregates <-chan *BlockAggregate) {
	jw := json.NewEncoder(ba.W)
	for blockAggregate := range blockAggregates {
		ba.Blocks[blockAggregate.CmdIO] = blockAggregate.Blocks
		blocksUpdate := make([]*Block, 0)
		for _, cmdio := range ba.CmdIOs {
			blocksUpdate = append(blocksUpdate, ba.Blocks[cmdio]...)
		}
		if err := jw.Encode(blocksUpdate); err != nil {
			log.Println(err)
		}
		ba.W.Write([]byte(","))
	}
}

func init() {
	flag.StringVar(&debugFile, "debug-file", "", "Outputs JSON to this file as well; for debugging what is sent to i3bar.")
	flag.StringVar(&logFile, "log-file", "", "Logs i3cat events in this file. Defaults to STDERR")
	flag.StringVar(&cmdsFile, "cmd-file", "$HOME/.i3/i3cat.conf", "File listing of the commands to run. It will read from STDIN if - is provided")
	flag.IntVar(&header.Version, "header-version", 1, "The i3bar header version")
	flag.IntVar(&header.StopSignal, "header-stopsignal", 0, "The i3bar header stop_signal. i3cat will send this signal to the processes it manages.")
	flag.IntVar(&header.ContSignal, "header-contsignal", 0, "The i3bar header cont_signal. i3cat will send this signal to the processes it manages.")
	flag.BoolVar(&header.ClickEvents, "header-clickevents", false, "The i3bar header click_events")
	flag.Parse()
}

func main() {
	// Read and parse commands to run.
	var cmdsReader io.ReadCloser
	if cmdsFile == "-" {
		cmdsReader = ioutil.NopCloser(os.Stdin)
	} else {
		f, err := os.Open(os.ExpandEnv(cmdsFile))
		if err != nil {
			log.Fatal(err)
		}
		cmdsReader = f
	}
	commands := make([]string, 0)
	scanner := bufio.NewScanner(cmdsReader)
	for scanner.Scan() {
		cmd := strings.TrimSpace(scanner.Text())
		if cmd != "" && !strings.HasPrefix(cmd, "#") {
			commands = append(commands, cmd)
		}
	}
	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}
	cmdsReader.Close()

	// Init log output.
	if logFile != "" {
		f, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0660)
		if err != nil {
			log.Fatal(err)
		}
		defer f.Close()
		log.SetOutput(f)
	}

	// Init where i3cat will print its output.
	var out io.Writer
	if debugFile != "" {
		f, err := os.OpenFile(debugFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0660)
		if err != nil {
			log.Fatal(err)
		}
		defer f.Close()
		out = io.MultiWriter(os.Stdout, f)
	} else {
		out = os.Stdout
	}

	// Resolve defaults for header signals
	sigstop := syscall.SIGSTOP
	sigcont := syscall.SIGCONT
	if header.StopSignal > 0 {
		sigstop = syscall.Signal(header.StopSignal)
	}
	if header.ContSignal > 0 {
		sigcont = syscall.Signal(header.ContSignal)
	}
	header.StopSignal = int(syscall.SIGUSR1)
	header.ContSignal = int(syscall.SIGUSR2)

	// We print the header of i3bar
	hb, err := json.Marshal(header)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Fprintf(out, "%s\n[\n", hb)

	// Create the block aggregator and start the commands
	blocksCh := make(chan *BlockAggregate)
	ba := NewBlockAggregator(out)
	for _, c := range commands {
		cmdio, err := NewCmdIO(c)
		if err != nil {
			log.Fatal(err)
		}
		ba.CmdIOs = append(ba.CmdIOs, cmdio)
		if err := cmdio.Start(blocksCh); err != nil {
			log.Fatal(err)
		}
	}

	go ba.Aggregate(blocksCh)

	// Listen for worthy signals
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGUSR1, syscall.SIGUSR2)

	for {
		// TODO handle sigcont and sigstop received from i3bar, and forward to cmds
		s := <-c
		switch s {
		case syscall.SIGTERM:
			fallthrough
		case os.Interrupt:
			// Kill all processes on interrupt
			log.Println("SIGINT or SIGTERM received: terminating all processes...")
			for _, cmdio := range ba.CmdIOs {
				if err := cmdio.Cmd.Process.Signal(syscall.SIGTERM); err != nil {
					log.Println(err)
					if err := cmdio.Cmd.Process.Kill(); err != nil {
						log.Println(err)
					}
				}
			}
			os.Exit(0)
		case syscall.SIGUSR1:
			log.Printf("SIGUSR1 received: forwarding signal %d to all processes...\n", sigstop)
			for _, cmdio := range ba.CmdIOs {
				if err := cmdio.Cmd.Process.Signal(sigstop); err != nil {
					log.Println(err)
				}
			}
		case syscall.SIGUSR2:
			log.Printf("SIGUSR1 received: forwarding signal %d to all processes...\n", sigcont)
			for _, cmdio := range ba.CmdIOs {
				if err := cmdio.Cmd.Process.Signal(sigcont); err != nil {
					log.Println(err)
				}
			}
		}
	}
}
<commit_msg>Prevent looping forever on invalid input and send an error block to i3bar<commit_after>package main

import (
	"bufio"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"strings"
	"syscall"
	"unicode"
)

var debugFile string
var logFile string
var cmdsFile string
var header Header

// Header defines the struct of the header in the i3bar protocol.
type Header struct {
	Version     int  `json:"version"`
	StopSignal  int  `json:"stop_signal,omitempty"`
	ContSignal  int  `json:"cont_signal,omitempty"`
	ClickEvents bool `json:"click_events,omitempty"`
}

// Block defines the struct of blocks in the i3bar protocol.
type Block struct {
	FullText            string `json:"full_text"`
	ShortText           string `json:"short_text,omitempty"`
	Color               string `json:"color,omitempty"`
	MinWidth            int    `json:"min_width,omitempty"`
	Align               string `json:"align,omitempty"`
	Name                string `json:"name,omitempty"`
	Instance            string `json:"instance,omitempty"`
	Urgent              bool   `json:"urgent,omitempty"`
	Separator           bool   `json:"separator,omitempty"`
	SeparatorBlockWidth int    `json:"separator_block_width,omitempty"`
}

// String implements Stringer interface.
func (b Block) String() string {
	return b.FullText
}

// A CmdIO defines a cmd that will feed the i3bar.
type CmdIO struct {
	// Cmd is the command being run
	Cmd *exec.Cmd
	// reader is the underlying stream where Cmd outputs data.
	reader io.ReadCloser
}

// BlockAggregate relates a CmdIO to the Blocks it produced during one update.
type BlockAggregate struct {
	CmdIO  *CmdIO
	Blocks []*Block
}

// NewCmdIO creates a new CmdIO from command c.
// c must be properly quoted for a shell as it's passed to sh -c.
func NewCmdIO(c string) (*CmdIO, error) {
	cmd := exec.Command(os.Getenv("SHELL"), "-c", c)
	reader, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}

	cmdio := CmdIO{
		Cmd:    cmd,
		reader: reader,
	}
	return &cmdio, nil
}

// Start runs the command of CmdIO and feeds the BlockAggregatesCh channel
// with the Blocks it produces.
func (c *CmdIO) Start(blockAggregatesCh chan<- *BlockAggregate) error {
	if err := c.Cmd.Start(); err != nil {
		return err
	}
	go func() {
		// We'll handle a few cases here.
		// If JSON is output from i3status, then we need
		// to ignore the i3bar header and opening [,
		// then ignore leading comma on each line.
		// If JSON is output from a script, it assumes the
		// author will not have the header and [, but maybe the comma
		r := bufio.NewReader(c.reader)
		// try Read a header first
		ruune, _, err := r.ReadRune()
		if err != nil {
			log.Println(err)
			return
		}
		if ruune == '{' {
			// Consume the header line
			if _, err := r.ReadString('\n'); err != nil {
				log.Println(err)
				return
			}
			// Consume the next line (opening bracket)
			if _, err := r.ReadString('\n'); err != nil {
				log.Println(err)
				return
			}
		} else {
			r.UnreadRune()
		}
		dec := json.NewDecoder(r)
		defer c.reader.Close()
		for {
			var b []*Block
			// Ignore unwanted chars first
		IgnoreChars:
			for {
				ruune, _, err := r.ReadRune()
				if err != nil {
					log.Println(err)
					break IgnoreChars
				}
				switch {
				case unicode.IsSpace(ruune):
					// Loop again
				case ruune == ',':
					break IgnoreChars
				default:
					r.UnreadRune()
					break IgnoreChars
				}
			}
			if err := dec.Decode(&b); err != nil {
				if err == io.EOF {
					log.Println("reached EOF")
					return
				}
				log.Printf("Invalid JSON input: all decoding methods failed (%v)\n", err)
				// consume all remaining data to prevent looping forever on a decoding err
				for r.Buffered() > 0 {
					_, err := r.ReadByte()
					if err != nil {
						log.Println(err)
					}
				}
				// send an error block
				b = []*Block{
					{
						FullText: fmt.Sprintf("Error parsing input: %v", err),
						Color:    "#FF0000",
					},
				}
			}
			blockAggregatesCh <- &BlockAggregate{c, b}
		}
	}()
	return nil
}

// BlockAggregator fans-in all Blocks produced by a list of CmdIO and sends it to the writer W.
type BlockAggregator struct {
	// Blocks keeps track of which CmdIO produced which Block list.
	Blocks map[*CmdIO][]*Block
	// CmdIOs keeps an ordered list of the CmdIOs being aggregated.
	CmdIOs []*CmdIO
	// W is where multiplexed input blocks are written to.
	W io.Writer
}

// NewBlockAggregator returns a BlockAggregator which will write to w.
func NewBlockAggregator(w io.Writer) *BlockAggregator {
	return &BlockAggregator{
		Blocks: make(map[*CmdIO][]*Block),
		CmdIOs: make([]*CmdIO, 0),
		W:      w,
	}
}

// Aggregate starts aggregating data coming from the BlockAggregates channel.
func (ba *BlockAggregator) Aggregate(blockAggregates <-chan *BlockAggregate) {
	jw := json.NewEncoder(ba.W)
	for blockAggregate := range blockAggregates {
		ba.Blocks[blockAggregate.CmdIO] = blockAggregate.Blocks
		blocksUpdate := make([]*Block, 0)
		for _, cmdio := range ba.CmdIOs {
			blocksUpdate = append(blocksUpdate, ba.Blocks[cmdio]...)
		}
		if err := jw.Encode(blocksUpdate); err != nil {
			log.Println(err)
		}
		ba.W.Write([]byte(","))
	}
}

func init() {
	flag.StringVar(&debugFile, "debug-file", "", "Outputs JSON to this file as well; for debugging what is sent to i3bar.")
	flag.StringVar(&logFile, "log-file", "", "Logs i3cat events in this file. Defaults to STDERR")
	flag.StringVar(&cmdsFile, "cmd-file", "$HOME/.i3/i3cat.conf", "File listing of the commands to run. It will read from STDIN if - is provided")
	flag.IntVar(&header.Version, "header-version", 1, "The i3bar header version")
	flag.IntVar(&header.StopSignal, "header-stopsignal", 0, "The i3bar header stop_signal. i3cat will send this signal to the processes it manages.")
	flag.IntVar(&header.ContSignal, "header-contsignal", 0, "The i3bar header cont_signal. i3cat will send this signal to the processes it manages.")
	flag.BoolVar(&header.ClickEvents, "header-clickevents", false, "The i3bar header click_events")
	flag.Parse()
}

func main() {
	// Read and parse commands to run.
	var cmdsReader io.ReadCloser
	if cmdsFile == "-" {
		cmdsReader = ioutil.NopCloser(os.Stdin)
	} else {
		f, err := os.Open(os.ExpandEnv(cmdsFile))
		if err != nil {
			log.Fatal(err)
		}
		cmdsReader = f
	}
	commands := make([]string, 0)
	scanner := bufio.NewScanner(cmdsReader)
	for scanner.Scan() {
		cmd := strings.TrimSpace(scanner.Text())
		if cmd != "" && !strings.HasPrefix(cmd, "#") {
			commands = append(commands, cmd)
		}
	}
	if err := scanner.Err(); err != nil {
		log.Fatal(err)
	}
	cmdsReader.Close()

	// Init log output.
	if logFile != "" {
		f, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0660)
		if err != nil {
			log.Fatal(err)
		}
		defer f.Close()
		log.SetOutput(f)
	}

	// Init where i3cat will print its output.
	var out io.Writer
	if debugFile != "" {
		f, err := os.OpenFile(debugFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0660)
		if err != nil {
			log.Fatal(err)
		}
		defer f.Close()
		out = io.MultiWriter(os.Stdout, f)
	} else {
		out = os.Stdout
	}

	// Resolve defaults for header signals
	sigstop := syscall.SIGSTOP
	sigcont := syscall.SIGCONT
	if header.StopSignal > 0 {
		sigstop = syscall.Signal(header.StopSignal)
	}
	if header.ContSignal > 0 {
		sigcont = syscall.Signal(header.ContSignal)
	}
	header.StopSignal = int(syscall.SIGUSR1)
	header.ContSignal = int(syscall.SIGUSR2)

	// We print the header of i3bar
	hb, err := json.Marshal(header)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Fprintf(out, "%s\n[\n", hb)

	// Create the block aggregator and start the commands
	blocksCh := make(chan *BlockAggregate)
	ba := NewBlockAggregator(out)
	for _, c := range commands {
		cmdio, err := NewCmdIO(c)
		if err != nil {
			log.Fatal(err)
		}
		ba.CmdIOs = append(ba.CmdIOs, cmdio)
		if err := cmdio.Start(blocksCh); err != nil {
			log.Fatal(err)
		}
	}

	go ba.Aggregate(blocksCh)

	// Listen for worthy signals
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGUSR1, syscall.SIGUSR2)

	for {
		// TODO handle sigcont and sigstop received from i3bar, and forward to cmds
		s := <-c
		switch s {
		case syscall.SIGTERM:
			fallthrough
		case os.Interrupt:
			// Kill all processes on interrupt
			log.Println("SIGINT or SIGTERM received: terminating all processes...")
			for _, cmdio := range ba.CmdIOs {
				if err := cmdio.Cmd.Process.Signal(syscall.SIGTERM); err != nil {
					log.Println(err)
					if err := cmdio.Cmd.Process.Kill(); err != nil {
						log.Println(err)
					}
				}
			}
			os.Exit(0)
		case syscall.SIGUSR1:
			log.Printf("SIGUSR1 received: forwarding signal %d to all processes...\n", sigstop)
			for _, cmdio := range ba.CmdIOs {
				if err := cmdio.Cmd.Process.Signal(sigstop); err != nil {
					log.Println(err)
				}
			}
		case syscall.SIGUSR2:
			log.Printf("SIGUSR1 received: forwarding signal %d to all processes...\n", sigcont)
			for _, cmdio := range ba.CmdIOs {
				if err := cmdio.Cmd.Process.Signal(sigcont); err != nil {
					log.Println(err)
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"runtime"
	"time"
)

var knownPaths []string
var boottime time.Time

func main() {

	boottime = time.Now()

	tls := true

	currDir, err := os.Getwd()
	if err != nil {
		log.Panicf("Getwd: %s", err)
	}

	var addr, key, cert string

	flag.StringVar(&key, "key", "key.pem", "TLS key file")
	flag.StringVar(&cert, "cert", "cert.pem", "TLS cert file")
	flag.StringVar(&addr, "addr", ":8080", "listen address")
	flag.Parse()

	if !fileExists(key) {
		log.Printf("TLS key file not found: %s - disabling TLS", key)
		tls = false
	}

	if !fileExists(cert) {
		log.Printf("TLS cert file not found: %s - disabling TLS", cert)
		tls = false
	}

	http.HandleFunc("/", rootHandler) // default handler

	registerStatic("/www/", currDir)

	log.Printf("serving on port TCP %s TLS=%v", addr, tls)

	if tls {
		if err := http.ListenAndServeTLS(addr, cert, key, nil); err != nil {
			log.Panicf("ListenAndServeTLS: %s: %s", addr, err)
		}
		return
	}

	if err := http.ListenAndServe(addr, nil); err != nil {
		log.Panicf("ListenAndServe: %s: %s", addr, err)
	}
}

func fileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

type staticHandler struct {
	innerHandler http.Handler
}

func registerStatic(path, dir string) {
	http.Handle(path, staticHandler{http.StripPrefix(path, http.FileServer(http.Dir(dir)))})
	knownPaths = append(knownPaths, path)
	log.Printf("registering static directory %s as www path %s", dir, path)
}

func (handler staticHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Printf("staticHandler.ServeHTTP url=%s from=%s", r.URL.Path, r.RemoteAddr)
	handler.innerHandler.ServeHTTP(w, r)
}

func rootHandler(w http.ResponseWriter, r *http.Request) {
	msg := fmt.Sprintf("rootHandler: url=%s from=%s", r.URL.Path, r.RemoteAddr)
	log.Print(msg)

	var paths string
	for _, p := range knownPaths {
		paths += fmt.Sprintf("%s
", p, p)
	}

	var errMsg string
	if r.URL.Path != "/" {
		errMsg = fmt.Sprintf("
Path not found!
Path not found: [%s]", r.URL.Path)
	}

	rootStr :=
		`<!DOCTYPE html>

    <title>gowebhello root page</title>

gowebhello root page

    gowebhello is a simple golang replacement for 'python -m SimpleHTTPServer'.

Welcome!

	Golang version: %s

	Application version: 3

	Application arguments: %v

	Application dir: %s

	Server hostname: %s

	Your address: %s

	Current time: %s

	Uptime: %s

    %s

All known paths:

    %s

`

	cwd, errCwd := os.Getwd()
	if errCwd != nil {
		cwd = cwd + " (error: " + errCwd.Error() + ")"
	}

	host, errHost := os.Hostname()
	if errHost != nil {
		host = host + " (error: " + errHost.Error() + ")"
	}

	now := time.Now()

	rootPage := fmt.Sprintf(rootStr, runtime.Version(), os.Args, cwd, host, r.RemoteAddr, now, time.Since(boottime), errMsg, paths)

	io.WriteString(w, rootPage)
}
<commit_msg>Redirect HTTP to HTTPS.<commit_after>package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"runtime"
	"strings"
	"time"
)

var knownPaths []string
var boottime time.Time

func main() {

	boottime = time.Now()

	tls := true

	currDir, err := os.Getwd()
	if err != nil {
		log.Panicf("Getwd: %s", err)
	}

	var addr, httpsAddr, key, cert string

	flag.StringVar(&key, "key", "key.pem", "TLS key file")
	flag.StringVar(&cert, "cert", "cert.pem", "TLS cert file")
	flag.StringVar(&addr, "addr", ":8080", "HTTP listen address")
	flag.StringVar(&httpsAddr, "httpsAddr", ":8443", "HTTPS listen address")
	flag.Parse()

	if !fileExists(key) {
		log.Printf("TLS key file not found: %s - disabling TLS", key)
		tls = false
	}

	if !fileExists(cert) {
		log.Printf("TLS cert file not found: %s - disabling TLS", cert)
		tls = false
	}

	http.HandleFunc("/", rootHandler) // default handler

	registerStatic("/www/", currDir)

	log.Printf("serving on port TCP HTTP=%s HTTPS=%s TLS=%v", addr, httpsAddr, tls)

	if tls {

		httpPort := "80"
		h := strings.Split(addr, ":")
		if len(h) > 1 {
			httpPort = h[1]
		}

		httpsPort := "443"
		hs := strings.Split(httpsAddr, ":")
		if len(hs) > 1 {
			httpsPort = hs[1]
		}

		if httpPort != httpsPort {
			// Installs http-to-https redirect server
			go func() {
				log.Printf("installing redirect from HTTP=%s to HTTPS=%s", addr, httpsPort)

				redirectTLS := func(w http.ResponseWriter, r *http.Request) {
					host := strings.Split(r.Host, ":")[0]
					http.Redirect(w, r, "https://"+host+":"+httpsPort+r.RequestURI, http.StatusMovedPermanently)
				}

				if err := http.ListenAndServe(addr, http.HandlerFunc(redirectTLS)); err != nil {
					log.Fatalf("redirect: ListenAndServe: %s: %v", addr, err)
				}
			}()
		}

		// Serve TLS
		if err := http.ListenAndServeTLS(httpsAddr, cert, key, nil); err != nil {
			log.Panicf("ListenAndServeTLS: %s: %v", httpsAddr, err)
		}
		return
	}

	if err := http.ListenAndServe(addr, nil); err != nil {
		log.Panicf("ListenAndServe: %s: %v", addr, err)
	}
}

func fileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

type staticHandler struct {
	innerHandler http.Handler
}

func registerStatic(path, dir string) {
	http.Handle(path, staticHandler{http.StripPrefix(path, http.FileServer(http.Dir(dir)))})
	knownPaths = append(knownPaths, path)
	log.Printf("registering static directory %s as www path %s", dir, path)
}

func (handler staticHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Printf("staticHandler.ServeHTTP url=%s from=%s", r.URL.Path, r.RemoteAddr)
	handler.innerHandler.ServeHTTP(w, r)
}

func rootHandler(w http.ResponseWriter, r *http.Request) {
	msg := fmt.Sprintf("rootHandler: url=%s from=%s", r.URL.Path, r.RemoteAddr)
	log.Print(msg)

	var paths string
	for _, p := range knownPaths {
		paths += fmt.Sprintf("%s
", p, p)
	}

	var errMsg string
	if r.URL.Path != "/" {
		errMsg = fmt.Sprintf("
Path not found!
Path not found: [%s]", r.URL.Path)
	}

	rootStr :=
		`<!DOCTYPE html>

    <title>gowebhello root page</title>

gowebhello root page

    gowebhello is a simple golang replacement for 'python -m SimpleHTTPServer'.

Welcome!

	Golang version: %s

	Application version: 3

	Application arguments: %v

	Application dir: %s

	Server hostname: %s

	Your address: %s

	Current time: %s

	Uptime: %s

    %s

All known paths:

    %s

`

	cwd, errCwd := os.Getwd()
	if errCwd != nil {
		cwd = cwd + " (error: " + errCwd.Error() + ")"
	}

	host, errHost := os.Hostname()
	if errHost != nil {
		host = host + " (error: " + errHost.Error() + ")"
	}

	now := time.Now()

	rootPage := fmt.Sprintf(rootStr, runtime.Version(), os.Args, cwd, host, r.RemoteAddr, now, time.Since(boottime), errMsg, paths)

	io.WriteString(w, rootPage)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"time"

	"github.com/nstratos/mdt/ui"

	"github.com/nsf/termbox-go"
)

// Global holder of captured key presses.
var captures = make([]ui.Capture, 0)

// Logs to .txt file in program's directory, named: S-E hz day date month time
// where S is start hz and E is end hz, e.g. '15-19 hz wed 27 dec 22.09.txt'
func logCaptures() error {
	c := ui.GetConfig()
	if len(captures) == 0 {
		return nil
	}
	format := "Mon 02 Jan 15.04"
	filename := fmt.Sprintf("%v-%v hz %v", c.StartHz, c.EndHz, time.Now().Format(format))
	f, err := os.Create(filename + ".txt")
	if err != nil {
		return err
	}
	_, err = f.WriteString(fmt.Sprintf("%v\r\nMode: %v\r\n", filename, c.Mode))
	if err != nil {
		return err
	}
	for _, capt := range captures {
		_, err = f.WriteString(
			fmt.Sprintf("%.2fhz @ %.2f base hz, on %v %v\r\n",
				capt.Hz, c.BaseHz, capt.Timestamp(), capt.Label()))
		if err != nil {
			return err
		}
	}
	// Emptying capture holder.
	captures = nil
	captures = make([]ui.Capture, 0)
	return nil
}

func main() {

	if err := ui.Init(); err != nil {
		log.Println("Could not initialize: ", err)
		if werr := ioutil.WriteFile("debug.txt", []byte(fmt.Sprintf("%s", err)), 0644); werr != nil {
			log.Fatalln(werr)
			os.Exit(1)
		}
		os.Exit(1)
	}
	ui.DrawAll()
	defer ui.Close()

	letter := make(chan rune)
	input := make(chan *ui.Entry)
	start := make(chan bool)
	done := make(chan bool)
	endTimer := make(chan bool)
	defer close(letter)
	defer close(input)
	defer close(start)
	defer close(done)
	defer close(endTimer)
	go captureEvents(letter, input, start, done)
	capturing := false
	timerEnded := false
loop:
	for {
		select {
		case <-start:
			ui.DeselectAllInputs()
			capturing = !capturing
			if capturing {
				c := ui.GetConfig()
				go timer(c.TotalTime*60, c.Offset*60, letter, endTimer)
				timerEnded = false
			}
			if !capturing && !timerEnded {
				endTimer <- true
				if err := logCaptures(); err != nil {
					ui.Debug(fmt.Sprintf("Error logging to txt file: %v", err))
				}
			}
		case timerEnded, _ = <-endTimer:
			capturing = false
			if err := logCaptures(); err != nil {
				ui.Debug(fmt.Sprintf("Error logging to txt file: %v", err))
			}
		case l := <-letter:
			// If the timer is on, we keep resending the letter to the channel so
			// that it will be eventually captured by the timer. If the timer is
			// not on, we discard the letter. Without this case the channel would
			// block forever if it was sent a letter without timer to consume it.
			if capturing {
				letter <- l
			}
		case in := <-input:
			if si := ui.SelectedInput(); si != nil {
				if in.Enter {
					if err := si.Valid(); err != nil {
						ui.UpdateText(fmt.Sprintf("Invalid value (%v)", err))
						continue
					}
					m, err := si.ValueMap()
					if err != nil {
						ui.UpdateText(fmt.Sprintf("%v", err))
						continue
					}
					c := ui.GetConfig()
					if err := c.Update(m); err != nil {
						ui.UpdateText(fmt.Sprintf("%v", err))
						continue
					}
					if err := c.Validate(); err != nil {
						ui.UpdateText(fmt.Sprintf("Invalid value (%v)", err))
						continue
					}
					if err := c.Save(); err != nil {
						ui.UpdateText(fmt.Sprintf("Could not save (%v)", err))
						continue
					}
					ui.UpdateConfig(c)
					ui.ReloadInputs(c)
					ui.UpdateText("Configuration changed successfully.")
					ui.DeselectAllInputs()
				} else {
					si.SetBuf(in)
				}
			}
		case <-done:
			if ui.SelectedInput() == nil {
				break loop
			}
			ui.DeselectAllInputs()
			ui.ResetText()
		}
	}
}

func timer(maxSeconds, offsetSeconds int, letter chan rune, end chan bool) {
	seconds := 0
	expired := time.NewTimer(time.Second * time.Duration(maxSeconds)).C
	tick := time.NewTicker(time.Second).C
	ui.UpdateTimer(seconds)
	ui.UpdateText("New Session started, press 'space' to stop, 'Esc' to quit.")
	ui.Debug(fmt.Sprintf("Key Capturing starts in %v", ui.FormatTimer(offsetSeconds)))
	for {
		select {
		case l := <-letter:
			// If user has set an offset it means that we have to wait for that amount
			// of seconds. Thus unless it reaches 0 we ignore label keypresses.
			if offsetSeconds == 0 {
				capture := ui.Capture{Value: l, Seconds: seconds, Hz: ui.CurrentHz(seconds)}
				captures = append(captures, capture)
				ui.UpdateText(ui.RecordedKeyText(l, seconds))
			}
		case <-end:
			ui.UpdateText("Session stopped manually.")
			return
		case <-expired:
			end <- true
			ui.UpdateText("Session ended.")
			return
		case <-tick:
			seconds++
			ui.UpdateTimer(seconds)
			if offsetSeconds == 0 {
				ui.Debug("Key Capturing has started")
			} else {
				offsetSeconds--
				ui.Debug(fmt.Sprintf("Key Capturing starts in %v", ui.FormatTimer(offsetSeconds)))
			}

		}
	}
}

func captureEvents(letter chan rune, input chan *ui.Entry, start, done chan bool) {
	started := false
	for {
		ev := termbox.PollEvent()
		switch {
		case ev.Key == termbox.KeyEsc:
			done <- true
		case ev.Key == termbox.KeySpace:
			started = !started
			start <- started
		case ui.AllowedEntry(ev):
			input <- ui.NewEntry(ev)
		case supportedLabel(ev.Ch):
			letter <- ev.Ch
		case ev.Type == termbox.EventResize:
			ui.DrawAll()
		case ev.Type == termbox.EventMouse:
			cell := ui.GetCell(ev.MouseX, ev.MouseY)
			if cell.Input != nil {
				if cell.Input.Type == ui.InputSwitch {
					ui.DeselectAllInputs()
					if err := cell.Input.Switch(); err != nil {
						ui.UpdateText(fmt.Sprintf("%v", err))
						//ui.Debug(fmt.Sprintf("switch. %v", err))
						continue
					}
				} else {
					cell.Input.SetSelected(true)
				}
			} else {
				ui.DeselectAllInputs()
			}
		}
	}
}

// 'a' = 97	-> visual imagination
// 'd' = 100	-> language thought
// 'e' = 101	-> language voice
// 'q' = 113	-> visual memory
// 's' = 115	-> auditory imagination
// 'w' = 119	-> auditory memory
func supportedLabel(key rune) bool {
	if key == 'a' || key == 'd' || key == 'e' ||
		key == 'q' || key == 's' || key == 'w' {
		return true
	}
	return false
}
<commit_msg>Add flag to print program version and exit<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"runtime"
	"time"

	"github.com/nstratos/mdt/ui"

	"github.com/nsf/termbox-go"
)

// Global holder of captured key presses.
var captures = make([]ui.Capture, 0)

var (
	showVersion = flag.Bool("v", false, "print program version and exit")
)

// Logs to .txt file in program's directory, named: S-E hz day date month time
// where S is start hz and E is end hz, e.g. '15-19 hz wed 27 dec 22.09.txt'
func logCaptures() error {
	c := ui.GetConfig()
	if len(captures) == 0 {
		return nil
	}
	format := "Mon 02 Jan 15.04"
	filename := fmt.Sprintf("%v-%v hz %v", c.StartHz, c.EndHz, time.Now().Format(format))
	f, err := os.Create(filename + ".txt")
	if err != nil {
		return err
	}
	_, err = f.WriteString(fmt.Sprintf("%v\r\nMode: %v\r\n", filename, c.Mode))
	if err != nil {
		return err
	}
	for _, capt := range captures {
		_, err = f.WriteString(
			fmt.Sprintf("%.2fhz @ %.2f base hz, on %v %v\r\n",
				capt.Hz, c.BaseHz, capt.Timestamp(), capt.Label()))
		if err != nil {
			return err
		}
	}
	// Emptying capture holder.
	captures = nil
	captures = make([]ui.Capture, 0)
	return nil
}

func main() {
	flag.Parse()
	if *showVersion {
		fmt.Printf("%s %s (runtime: %s)\n", os.Args[0], ui.Version, runtime.Version())
		os.Exit(0)
	}

	if err := ui.Init(); err != nil {
		log.Println("Could not initialize: ", err)
		if werr := ioutil.WriteFile("debug.txt", []byte(fmt.Sprintf("%s", err)), 0644); werr != nil {
			log.Fatalln(werr)
			os.Exit(1)
		}
		os.Exit(1)
	}
	ui.DrawAll()
	defer ui.Close()

	letter := make(chan rune)
	input := make(chan *ui.Entry)
	start := make(chan bool)
	done := make(chan bool)
	endTimer := make(chan bool)
	defer close(letter)
	defer close(input)
	defer close(start)
	defer close(done)
	defer close(endTimer)
	go captureEvents(letter, input, start, done)
	capturing := false
	timerEnded := false
loop:
	for {
		select {
		case <-start:
			ui.DeselectAllInputs()
			capturing = !capturing
			if capturing {
				c := ui.GetConfig()
				go timer(c.TotalTime*60, c.Offset*60, letter, endTimer)
				timerEnded = false
			}
			if !capturing && !timerEnded {
				endTimer <- true
				if err := logCaptures(); err != nil {
					ui.Debug(fmt.Sprintf("Error logging to txt file: %v", err))
				}
			}
		case timerEnded, _ = <-endTimer:
			capturing = false
			if err := logCaptures(); err != nil {
				ui.Debug(fmt.Sprintf("Error logging to txt file: %v", err))
			}
		case l := <-letter:
			// If the timer is on, we keep resending the letter to the channel so
			// that it will be eventually captured by the timer. If the timer is
			// not on, we discard the letter. Without this case the channel would
			// block forever if it was sent a letter without timer to consume it.
			if capturing {
				letter <- l
			}
		case in := <-input:
			if si := ui.SelectedInput(); si != nil {
				if in.Enter {
					if err := si.Valid(); err != nil {
						ui.UpdateText(fmt.Sprintf("Invalid value (%v)", err))
						continue
					}
					m, err := si.ValueMap()
					if err != nil {
						ui.UpdateText(fmt.Sprintf("%v", err))
						continue
					}
					c := ui.GetConfig()
					if err := c.Update(m); err != nil {
						ui.UpdateText(fmt.Sprintf("%v", err))
						continue
					}
					if err := c.Validate(); err != nil {
						ui.UpdateText(fmt.Sprintf("Invalid value (%v)", err))
						continue
					}
					if err := c.Save(); err != nil {
						ui.UpdateText(fmt.Sprintf("Could not save (%v)", err))
						continue
					}
					ui.UpdateConfig(c)
					ui.ReloadInputs(c)
					ui.UpdateText("Configuration changed successfully.")
					ui.DeselectAllInputs()
				} else {
					si.SetBuf(in)
				}
			}
		case <-done:
			if ui.SelectedInput() == nil {
				break loop
			}
			ui.DeselectAllInputs()
			ui.ResetText()
		}
	}
}

func timer(maxSeconds, offsetSeconds int, letter chan rune, end chan bool) {
	seconds := 0
	expired := time.NewTimer(time.Second * time.Duration(maxSeconds)).C
	tick := time.NewTicker(time.Second).C
	ui.UpdateTimer(seconds)
	ui.UpdateText("New Session started, press 'space' to stop, 'Esc' to quit.")
	ui.Debug(fmt.Sprintf("Key Capturing starts in %v", ui.FormatTimer(offsetSeconds)))
	for {
		select {
		case l := <-letter:
			// If user has set an offset it means that we have to wait for that amount
			// of seconds. Thus unless it reaches 0 we ignore label keypresses.
			if offsetSeconds == 0 {
				capture := ui.Capture{Value: l, Seconds: seconds, Hz: ui.CurrentHz(seconds)}
				captures = append(captures, capture)
				ui.UpdateText(ui.RecordedKeyText(l, seconds))
			}
		case <-end:
			ui.UpdateText("Session stopped manually.")
			return
		case <-expired:
			end <- true
			ui.UpdateText("Session ended.")
			return
		case <-tick:
			seconds++
			ui.UpdateTimer(seconds)
			if offsetSeconds == 0 {
				ui.Debug("Key Capturing has started")
			} else {
				offsetSeconds--
				ui.Debug(fmt.Sprintf("Key Capturing starts in %v", ui.FormatTimer(offsetSeconds)))
			}

		}
	}
}

func captureEvents(letter chan rune, input chan *ui.Entry, start, done chan bool) {
	started := false
	for {
		ev := termbox.PollEvent()
		switch {
		case ev.Key == termbox.KeyEsc:
			done <- true
		case ev.Key == termbox.KeySpace:
			started = !started
			start <- started
		case ui.AllowedEntry(ev):
			input <- ui.NewEntry(ev)
		case supportedLabel(ev.Ch):
			letter <- ev.Ch
		case ev.Type == termbox.EventResize:
			ui.DrawAll()
		case ev.Type == termbox.EventMouse:
			cell := ui.GetCell(ev.MouseX, ev.MouseY)
			if cell.Input != nil {
				if cell.Input.Type == ui.InputSwitch {
					ui.DeselectAllInputs()
					if err := cell.Input.Switch(); err != nil {
						ui.UpdateText(fmt.Sprintf("%v", err))
						//ui.Debug(fmt.Sprintf("switch. %v", err))
						continue
					}
				} else {
					cell.Input.SetSelected(true)
				}
			} else {
				ui.DeselectAllInputs()
			}
		}
	}
}

// 'a' = 97	-> visual imagination
// 'd' = 100	-> language thought
// 'e' = 101	-> language voice
// 'q' = 113	-> visual memory
// 's' = 115	-> auditory imagination
// 'w' = 119	-> auditory memory
func supportedLabel(key rune) bool {
	if key == 'a' || key == 'd' || key == 'e' ||
		key == 'q' || key == 's' || key == 'w' {
		return true
	}
	return false
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/DMarby/picsum-photos/api"
	memoryCache "github.com/DMarby/picsum-photos/cache/memory"
	fileDatabase "github.com/DMarby/picsum-photos/database/file"
	"github.com/DMarby/picsum-photos/health"
	vipsProcessor "github.com/DMarby/picsum-photos/image/vips"
	"github.com/DMarby/picsum-photos/logger"
	fileStorage "github.com/DMarby/picsum-photos/storage/file"
	"github.com/jamiealquiza/envy"
	"go.uber.org/zap"
)

func waitForInterrupt(ctx context.Context) error {
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
	select {
	case sig := <-c:
		return fmt.Errorf("received signal %s", sig)
	case <-ctx.Done():
		return errors.New("canceled")
	}
}

// Http timeouts
const (
	readTimeout    = 5 * time.Second
	writeTimeout   = time.Minute
	handlerTimeout = 45 * time.Second
)

const (
	maxImageSize = 5000       // The max allowed image width/height to be requested
	staticPath   = "./static" // Path where the static files are located
)

func main() {
	// Set up commandline flags
	listen := flag.String("listen", ":8080", "listen address")
	rootURL := flag.String("root-url", "https://picsum.photos", "root url")
	loglevel := zap.LevelFlag("log-level", zap.InfoLevel, "log level (default \"info\") (debug, info, warn, error, dpanic, panic, fatal)")

	// Parse environment variables
	envy.Parse("PICSUM")

	// Parse commandline flags
	flag.Parse()

	// Initialize the logger
	log := logger.New(*loglevel)
	defer log.Sync()

	// Set up context for shutting down
	shutdownCtx, shutdown := context.WithCancel(context.Background())
	defer shutdown()

	// Get imageProcessor instance
	imageProcessorCtx, imageProcessorCancel := context.WithCancel(context.Background())
	defer imageProcessorCancel()

	imageProcessor, err := vipsProcessor.GetInstance(imageProcessorCtx, log)
	if err != nil {
		log.Fatalf("error initializing image processor %s", err.Error())
	}

	// Initialize the storage
	storage, err := fileStorage.New("./test/fixtures/file")
	if err != nil {
		log.Fatalf("error initializing storage %s", err.Error())
		return
	}

	// Initialize the cache
	cache := memoryCache.New()
	defer cache.Shutdown()

	imageCache := api.NewCache(cache, storage)

	// Initialize the database
	database, err := fileDatabase.New("./test/fixtures/file/metadata.json")
	if err != nil {
		log.Fatalf("error initializing database %s", err.Error())
		return
	}
	defer database.Shutdown()

	// Initialize and start the health checker
	checkerCtx, checkerCancel := context.WithCancel(context.Background())
	defer checkerCancel()

	checker := &health.Checker{
		Ctx:            checkerCtx,
		ImageProcessor: imageProcessor,
		Storage:        storage,
		Database:       database,
		Cache:          cache,
	}
	go checker.Run()

	// Start and listen on http
	api := &api.API{
		ImageProcessor: imageProcessor,
		Cache:          imageCache,
		Database:       database,
		HealthChecker:  checker,
		Log:            log,
		MaxImageSize:   maxImageSize,
		RootURL:        *rootURL,
		StaticPath:     staticPath,
		HandlerTimeout: handlerTimeout,
	}
	server := &http.Server{
		Addr:         *listen,
		Handler:      api.Router(),
		ReadTimeout:  readTimeout,
		WriteTimeout: writeTimeout,
	}

	go func() {
		if err := server.ListenAndServe(); err != nil {
			log.Infof("shutting down the http server: %s", err)
			shutdown()
		}
	}()

	log.Infof("http server listening on %s", *listen)

	// Wait for shutdown or error
	err = waitForInterrupt(shutdownCtx)
	log.Infof("shutting down: %s", err)

	// Shut down http server
	serverCtx, serverCancel := context.WithTimeout(context.Background(), writeTimeout)
	defer serverCancel()
	if err := server.Shutdown(serverCtx); err != nil {
		log.Warnf("error shutting down: %s", err)
	}
}
<commit_msg>Add flags for all the backends<commit_after>package main

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/DMarby/picsum-photos/api"
	"github.com/DMarby/picsum-photos/cache"
	"github.com/DMarby/picsum-photos/cache/memory"
	"github.com/DMarby/picsum-photos/cache/redis"
	"github.com/DMarby/picsum-photos/database"
	fileDatabase "github.com/DMarby/picsum-photos/database/file"
	"github.com/DMarby/picsum-photos/database/postgresql"
	"github.com/DMarby/picsum-photos/health"
	"github.com/DMarby/picsum-photos/image/vips"
	"github.com/DMarby/picsum-photos/logger"
	"github.com/DMarby/picsum-photos/storage"
	fileStorage "github.com/DMarby/picsum-photos/storage/file"
	"github.com/DMarby/picsum-photos/storage/spaces"

	"github.com/jamiealquiza/envy"
	"go.uber.org/zap"
)

func waitForInterrupt(ctx context.Context) error {
	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
	select {
	case sig := <-c:
		return fmt.Errorf("received signal %s", sig)
	case <-ctx.Done():
		return errors.New("canceled")
	}
}

// Http timeouts
const (
	readTimeout    = 5 * time.Second
	writeTimeout   = time.Minute
	handlerTimeout = 45 * time.Second
)

const (
	maxImageSize = 5000       // The max allowed image width/height to be requested
	staticPath   = "./static" // Path where the static files are located
)

// Comandline flags
var (
	// Global
	listen   = flag.String("listen", ":8080", "listen address")
	rootURL  = flag.String("root-url", "https://picsum.photos", "root url")
	loglevel = zap.LevelFlag("log-level", zap.InfoLevel, "log level (default \"info\") (debug, info, warn, error, dpanic, panic, fatal)")

	// Storage
	storageBackend = flag.String("storage", "file", "which storage backend to use (file, spaces)")

	// Storage - File
	storageFilePath = flag.String("storage-file-path", "./test/fixtures/file", "path to the file storage")

	// Storage - Spaces
	storageSpacesSpace     = flag.String("storage-spaces-space", "", "digitalocean space to use")
	storageSpacesRegion    = flag.String("storage-spaces-region", "", "spaces region")
	storageSpacesAccessKey = flag.String("storage-spaces-access-key", "", "spaces access key")
	storageSpacesSecretKey = flag.String("storage-spaces-secret-key", "", "spaces secret key")

	// Cache
	cacheBackend = flag.String("cache", "memory", "which cache backend to use (memory, redis)")

	// Cache - Redis
	cacheRedisAddress  = flag.String("cache-redis-address", "redis://127.0.0.1:6379", "redis address, may contain authentication details")
	cacheRedisPoolSize = flag.Int("cache-redis-pool-size", 10, "redis connection pool size")

	// Database
	databaseBackend = flag.String("database", "file", "which database backend to use (file, postgresql)")

	// Database - File
	databaseFilePath = flag.String("database-file-path", "./test/fixtures/file/metadata.json", "path to the database file")

	// Database - Postgresql
	databasePostgresqlAddress = flag.String("database-postgresql-address", "postgresql://postgres@127.0.0.1/postgres", "postgresql address")
)

func main() {
	// Parse environment variables
	envy.Parse("PICSUM")

	// Parse commandline flags
	flag.Parse()

	// Initialize the logger
	log := logger.New(*loglevel)
	defer log.Sync()

	// Set up context for shutting down
	shutdownCtx, shutdown := context.WithCancel(context.Background())
	defer shutdown()

	// Get imageProcessor instance
	imageProcessorCtx, imageProcessorCancel := context.WithCancel(context.Background())
	defer imageProcessorCancel()

	imageProcessor, err := vips.GetInstance(imageProcessorCtx, log)
	if err != nil {
		log.Fatalf("error initializing image processor %s", err.Error())
	}

	// Initialize the storage, cache and database
	storage, cache, database, err := setupBackends()
	if err != nil {
		log.Fatalf("error initializing backends: %s", err)
	}
	defer cache.Shutdown()
	defer database.Shutdown()

	// Initialize and start the health checker
	checkerCtx, checkerCancel := context.WithCancel(context.Background())
	defer checkerCancel()

	checker := &health.Checker{
		Ctx:            checkerCtx,
		ImageProcessor: imageProcessor,
		Storage:        storage,
		Database:       database,
		Cache:          cache,
	}
	go checker.Run()

	// Start and listen on http
	api := &api.API{
		ImageProcessor: imageProcessor,
		Cache:          api.NewCache(cache, storage),
		Database:       database,
		HealthChecker:  checker,
		Log:            log,
		MaxImageSize:   maxImageSize,
		RootURL:        *rootURL,
		StaticPath:     staticPath,
		HandlerTimeout: handlerTimeout,
	}
	server := &http.Server{
		Addr:         *listen,
		Handler:      api.Router(),
		ReadTimeout:  readTimeout,
		WriteTimeout: writeTimeout,
	}

	go func() {
		if err := server.ListenAndServe(); err != nil {
			log.Infof("shutting down the http server: %s", err)
			shutdown()
		}
	}()

	log.Infof("http server listening on %s", *listen)

	// Wait for shutdown or error
	err = waitForInterrupt(shutdownCtx)
	log.Infof("shutting down: %s", err)

	// Shut down http server
	serverCtx, serverCancel := context.WithTimeout(context.Background(), writeTimeout)
	defer serverCancel()
	if err := server.Shutdown(serverCtx); err != nil {
		log.Warnf("error shutting down: %s", err)
	}
}

func setupBackends() (storage storage.Provider, cache cache.Provider, database database.Provider, err error) {
	// Storage
	switch *storageBackend {
	case "file":
		storage, err = fileStorage.New(*storageFilePath)
	case "spaces":
		storage, err = spaces.New(*storageSpacesSpace, *storageSpacesRegion, *storageSpacesAccessKey, *storageSpacesSecretKey)
	default:
		err = fmt.Errorf("invalid storage backend")
	}

	if err != nil {
		return
	}

	// Cache
	switch *cacheBackend {
	case "memory":
		cache = memory.New()
	case "redis":
		cache, err = redis.New(*cacheRedisAddress, *cacheRedisPoolSize)
	default:
		err = fmt.Errorf("invalid cache backend")
	}

	if err != nil {
		return
	}

	// Database
	switch *databaseBackend {
	case "file":
		database, err = fileDatabase.New(*databaseFilePath)
	case "postgresql":
		database, err = postgresql.New(*databasePostgresqlAddress)
	default:
		err = fmt.Errorf("invalid database backend")
	}

	return
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"
)

const (
	cormFilename = "Cormfile"
	cormDir      = "_corm"
)

var errCannotParseLine = errors.New("cannot parse line")

var vcsMetaDir = []string{".svn", ".git", ".hg"}

type repository struct {
	Path   string
	Commit string
}

func parse(s string) (*repository, error) {
	fields := strings.Fields(s)
	switch len(fields) {
	case 1:
		return &repository{Path: fields[0]}, nil
	case 2:
		return &repository{Path: fields[0], Commit: fields[1]}, nil
	default:
		return nil, errCannotParseLine
	}
}

func exists(filepath string) bool {
	_, err := os.Stat(filepath)
	return err == nil
}

func readCorm(filepath string) ([]*repository, error) {
	f, err := os.Open(filepath)
	if err != nil {
		return nil, fmt.Errorf("cannot open Cormfile: %s", err)
	}
	defer f.Close()

	repos := make([]*repository, 0, 30)

	s := bufio.NewScanner(f)
	for s.Scan() {
		line := s.Text()
		if line == "" {
			continue
		}

		repo, err := parse(line)
		if err != nil {
			fmt.Fprintf(os.Stderr, "SKIPPED: %s: %s\n", err, line)
			continue
		}
		repos = append(repos, repo)
	}

	return repos, nil
}

func goGet(repo *repository) error {
	fmt.Printf("go get %s\n", repo.Path)
	err := exec.Command("go", "get", repo.Path).Run()
	// Commit が指定されている場合の処理は後で実装する
	return err
}

func newCopyFileFun(srcBase string, destBase string) filepath.WalkFunc {

	return func(srcPath string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		relPath, err := filepath.Rel(srcBase, srcPath)
		if err != nil {
			return err
		}

		destPath := filepath.Join(destBase, relPath)

		if info.IsDir() {
			index := sort.SearchStrings(vcsMetaDir, info.Name())
			if index < len(vcsMetaDir) {
				return filepath.SkipDir
			}

			return os.MkdirAll(destPath, info.Mode())
		}

		if exists(destPath) {
			return nil
		}

		return os.Link(srcPath, destPath)
	}

}

func export(src string, dest string) error {
	copyFile := newCopyFileFun(src, dest)
	err := filepath.Walk(src, copyFile)
	return err
}

func paths() (string, string, error) {
	cur, err := os.Getwd()
	if err != nil {
		return "", "", err
	}

	dirty := filepath.Join(cur, cormDir)
	return cur, dirty, err
}

func mainCmd() int {
	curdir, dirtyVendorDir, err := paths()
	if err != nil {
		fmt.Fprintln(os.Stderr, "cannot get directory")
		return 1
	}

	cormfile := filepath.Join(curdir, cormFilename)
	if !exists(cormfile) {
		fmt.Fprintf(os.Stderr, "%s does not exists\n", cormfile)
		return 1
	}

	repos, err := readCorm(cormfile)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return 1
	}

	if len(repos) == 0 {
		fmt.Fprintln(os.Stderr, "no repositories in Cormfile")
		return 1
	}

	gopath := os.Getenv("GOPATH")
	os.Setenv("GOPATH", fmt.Sprintf("%s:%s", dirtyVendorDir, gopath))

	for _, repo := range repos {
		err := goGet(repo)
		if err != nil {
			fmt.Fprintf(os.Stderr, "cannot exec go get: %s, %s\n", repo.Path, err)
		}
	}

	return 0
}

func exportCmd() int {
	curdir, dirtyVendorDir, err := paths()
	if err != nil {
		fmt.Fprintln(os.Stderr, "cannot get directory")
		return 1
	}

	dirtyVendorSrcDir := filepath.Join(dirtyVendorDir, "src")
	cleanVendorDir := filepath.Join(curdir, "vendor")
	err = export(dirtyVendorSrcDir, cleanVendorDir)
	if err != nil {
		fmt.Fprintln(os.Stderr, "export error:", err)
	}

	return 0
}

func usage() int {
	fmt.Println(`Usage: corm command
	install	:	install packages from Cormfile.
	export	:	export packages to vendor directory.`)

	return 1
}

func main() {
	sort.Strings(vcsMetaDir)

	if len(os.Args) == 1 {
		os.Exit(usage())
	}

	command := os.Args[1]

	switch command {
	case "install":
		os.Exit(mainCmd())
	case "export":
		os.Exit(exportCmd())
	default:
		os.Exit(usage())
	}
}
<commit_msg>ファイルのパスとか整理<commit_after>package main

import (
	"bufio"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"
)

const (
	cormFilename = "Cormfile"
	cormDir      = "_corm"
)

var (
	currentDir     string
	dirtyVendorDir string
)

var errCannotParseLine = errors.New("cannot parse line")

var vcsMetaDir = []string{".svn", ".git", ".hg"}

type repository struct {
	Path   string
	Commit string
}

func parse(s string) (*repository, error) {
	fields := strings.Fields(s)
	switch len(fields) {
	case 1:
		return &repository{Path: fields[0]}, nil
	case 2:
		return &repository{Path: fields[0], Commit: fields[1]}, nil
	default:
		return nil, errCannotParseLine
	}
}

func exists(filepath string) bool {
	_, err := os.Stat(filepath)
	return err == nil
}

func readCorm(filepath string) ([]*repository, error) {
	f, err := os.Open(filepath)
	if err != nil {
		return nil, fmt.Errorf("cannot open Cormfile: %s", err)
	}
	defer f.Close()

	repos := make([]*repository, 0, 30)

	s := bufio.NewScanner(f)
	for s.Scan() {
		line := s.Text()
		if line == "" {
			continue
		}

		repo, err := parse(line)
		if err != nil {
			fmt.Fprintf(os.Stderr, "SKIPPED: %s: %s\n", err, line)
			continue
		}
		repos = append(repos, repo)
	}

	return repos, nil
}

func goGet(repo *repository) error {
	fmt.Printf("go get %s\n", repo.Path)
	err := exec.Command("go", "get", repo.Path).Run()
	// Commit が指定されている場合の処理は後で実装する
	return err
}

func newCopyFileFun(srcBase string, destBase string) filepath.WalkFunc {

	return func(srcPath string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		relPath, err := filepath.Rel(srcBase, srcPath)
		if err != nil {
			return err
		}

		destPath := filepath.Join(destBase, relPath)

		if info.IsDir() {
			index := sort.SearchStrings(vcsMetaDir, info.Name())
			if index < len(vcsMetaDir) {
				return filepath.SkipDir
			}

			return os.MkdirAll(destPath, info.Mode())
		}

		if exists(destPath) {
			return nil
		}

		return os.Link(srcPath, destPath)
	}

}

func export(src string, dest string) error {
	copyFile := newCopyFileFun(src, dest)
	err := filepath.Walk(src, copyFile)
	return err
}

func init() {
	var err error
	currentDir, err = os.Getwd()
	if err != nil {
		fmt.Fprintln(os.Stderr, "cannot get current directory")
		os.Exit(1)
	}

	dirtyVendorDir = filepath.Join(currentDir, cormDir)
}

func fakeGopath() {

	gopath := os.Getenv("GOPATH")
	os.Setenv("GOPATH", fmt.Sprintf("%s:%s", dirtyVendorDir, gopath))

}

func mainCmd() int {
	cormfile := filepath.Join(currentDir, cormFilename)
	if !exists(cormfile) {
		fmt.Fprintf(os.Stderr, "%s does not exists\n", cormfile)
		return 1
	}

	repos, err := readCorm(cormfile)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return 1
	}

	if len(repos) == 0 {
		fmt.Fprintln(os.Stderr, "no repositories in Cormfile")
		return 1
	}

	fakeGopath()
	for _, repo := range repos {
		err := goGet(repo)
		if err != nil {
			fmt.Fprintf(os.Stderr, "cannot exec go get: %s, %s\n", repo.Path, err)
		}
	}

	return 0
}

func exportCmd() int {
	dirtyVendorSrcDir := filepath.Join(dirtyVendorDir, "src")
	cleanVendorDir := filepath.Join(currentDir, "vendor")
	err := export(dirtyVendorSrcDir, cleanVendorDir)
	if err != nil {
		fmt.Fprintln(os.Stderr, "export error:", err)
	}

	return 0
}

func usage() int {
	fmt.Println(`Usage: corm command
	install	:	install packages from Cormfile.
	export	:	export packages to vendor directory.`)

	return 1
}

func main() {
	sort.Strings(vcsMetaDir)

	if len(os.Args) == 1 {
		os.Exit(usage())
	}

	command := os.Args[1]

	switch command {
	case "install":
		os.Exit(mainCmd())
	case "export":
		os.Exit(exportCmd())
	default:
		os.Exit(usage())
	}
}
<|endoftext|>


<commit_before>package main

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

	"gopkg.in/yaml.v2"

	"github.com/AstromechZA/spiro/templatefactory"
)

const usageString = `
Spiro is an template structure generator that uses Golangs text/template library. It accepts both single files as well
as directory trees as input and will interpret any template calls found inside the files and the file/directory names.

The rule-set is probably a bit complex to display here, but the following links are useful:

- https://golang.org/pkg/text/template
- https://gohugo.io/templates/go-templates/

Some additional template functions are supplied:

- 'title': capitalise string
- 'upper': convert string to upper case
- 'lower': convert string to lower case
- 'now': return current time object (time.Time)

See the project homepage for more documentation: https://github.com/AstromechZA/spiro

The spec file should be in JSON or Yaml form and will be passed to each template invocation.

$ spiro [options] {input template} {spec file} {output directory}
`

const logoImage = `
  _________      .__
 /   _____/_____ |__|______  ____
 \_____  \\____ \|  \_  __ \/  _ \
 /        \  |_> >  ||  | \(  <_> )
/_______  /   __/|__||__|   \____/
        \/|__|
`

var Version = "<unofficial build>"
var GitCommit = "<commit unknown>"
var GitState = "<changes unknown>"
var BuildDate = "<no date>"

func copyFileContents(src, dst string) error {
	in, err := os.Open(src)
	if err != nil {
		return err
	}
	defer in.Close()
	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer func() {
		cerr := out.Close()
		if err == nil {
			err = cerr
		}
	}()
	if _, err = io.Copy(out, in); err != nil {
		return err
	}
	return out.Sync()
}

func processDir(templateString string, spec *map[string]interface{}, outputDir string, tf *templatefactory.TemplateFactory) error {
	fromBase := path.Base(templateString)
	toBase := fromBase
	if tf.StringContainsTemplating(fromBase) {
		var err error
		toBase, err = tf.Render(fromBase)
		if err != nil {
			return fmt.Errorf("Error while processing '%s': %s", templateString, err.Error())
		}
	}
	toBase = strings.TrimSpace(toBase)
	if len(toBase) == 0 {
		fmt.Printf("Skipping '%s' since the name evaluated to ''\n", templateString)
		return nil
	}

	newOutputDir := path.Join(outputDir, toBase)
	fmt.Printf("Processing '%s/' -> '%s/'\n", templateString, newOutputDir)
	if err := os.Mkdir(newOutputDir, 0755); err != nil && !os.IsExist(err) {
		return fmt.Errorf("Error while processing '%s': %s", templateString, err.Error())
	}

	items, _ := ioutil.ReadDir(templateString)
	for _, item := range items {
		if err := process(path.Join(templateString, item.Name()), spec, newOutputDir, tf); err != nil {
			return err
		}
	}
	return nil
}

func processFile(templateString string, spec *map[string]interface{}, outputDir string, tf *templatefactory.TemplateFactory) error {
	fromBase := path.Base(templateString)
	toBase := fromBase
	if tf.StringContainsTemplating(fromBase) {
		var err error
		toBase, err = tf.Render(fromBase)
		if err != nil {
			return fmt.Errorf("Error while processing '%s': %s", templateString, err.Error())
		}
	}
	toBase = strings.TrimSpace(toBase)
	if len(toBase) == 0 {
		fmt.Printf("Skipping '%s' since the name evaluated to ''\n", templateString)
		return nil
	}

	if strings.HasSuffix(toBase, ".templated") {
		toBase = toBase[:len(toBase)-10]
		if len(toBase) == 0 {
			fmt.Printf("Skipping '%s' since the name evaluated to ''\n", templateString)
			return nil
		}
		fmt.Printf("Processing '%s' -> '%s'\n", templateString, path.Join(outputDir, toBase))
		inputBytes, err := ioutil.ReadFile(templateString)
		if err != nil {
			return fmt.Errorf("Error while reading '%s': %s", templateString, err.Error())
		}
		outputBytes, err := tf.Render(string(inputBytes))
		if err != nil {
			return fmt.Errorf("Error while rendering template for '%s': %s", templateString, err.Error())
		}
		if err := ioutil.WriteFile(path.Join(outputDir, toBase), []byte(outputBytes), 0644); err != nil {
			return fmt.Errorf("Error while writing file bytes for '%s': %s", templateString, err.Error())
		}
	} else {
		fmt.Printf("Processing '%s' -> '%s'\n", templateString, path.Join(outputDir, toBase))
		if err := copyFileContents(templateString, path.Join(outputDir, toBase)); err != nil {
			return fmt.Errorf("Error while copying file bytes for '%s': %s", templateString, err.Error())
		}
	}

	info, _ := os.Stat(templateString)
	if err := os.Chmod(path.Join(outputDir, toBase), info.Mode()); err != nil {
		return fmt.Errorf("Error while writing file permissions for '%s': %s", templateString, err.Error())
	}

	return nil
}

func process(templateString string, spec *map[string]interface{}, outputDir string, tf *templatefactory.TemplateFactory) error {
	stat, err := os.Stat(templateString)
	if err != nil {
		return fmt.Errorf("Error processing template %s: %s", templateString, err.Error())
	}
	if stat.IsDir() {
		return processDir(templateString, spec, outputDir, tf)
	}
	return processFile(templateString, spec, outputDir, tf)
}

func readSpec(specFile string) (*map[string]interface{}, error) {
	specBytes, err := ioutil.ReadFile(specFile)
	if err != nil {
		return nil, fmt.Errorf("Could not read json spec file: %s", err.Error())
	}
	var spec map[string]interface{}
	if strings.HasSuffix(specFile, ".json") {
		err = json.Unmarshal(specBytes, &spec)
		if err != nil {
			return nil, fmt.Errorf("Could not parse json spec file: %s", err.Error())
		}
		return &spec, nil
	} else if strings.HasSuffix(specFile, ".yaml") || strings.HasSuffix(specFile, ".yml") {
		err = yaml.Unmarshal(specBytes, &spec)
		if err != nil {
			return nil, fmt.Errorf("Could not parse yaml spec file: %s", err.Error())
		}
		return &spec, nil
	} else {
		return nil, fmt.Errorf("I do not know how to parse the spec, expected .json, .yaml, or .yml")
	}
}

func mainInner() error {

	// first set up config flag options
	versionFlag := flag.Bool("version", false, "Print the version string")

	// set a more verbose usage message.
	flag.Usage = func() {
		os.Stderr.WriteString(strings.TrimSpace(usageString) + "\n\n")
		flag.PrintDefaults()
	}
	// parse them
	flag.Parse()

	// do arg checking
	if *versionFlag {
		fmt.Printf("Version: %s (%s-%s) on %s \n", Version, GitCommit, GitState, BuildDate)
		fmt.Println(logoImage)
		fmt.Println("Project: github.com/AstromechZA/spiro")
		return nil
	}
	if flag.NArg() != 3 {
		flag.Usage()
		os.Exit(1)
	}

	inputTemplate := flag.Arg(0)
	specFile := flag.Arg(1)
	outputDirectory := flag.Arg(2)

	if _, err := os.Stat(inputTemplate); err != nil {
		if os.IsNotExist(err) {
			return fmt.Errorf("Input template '%s' does not exist!", inputTemplate)
		}
		return fmt.Errorf("Input template '%s' cannot be read! (%s)", inputTemplate, err.Error())
	}
	if stat, err := os.Stat(specFile); err != nil {
		if os.IsNotExist(err) {
			return fmt.Errorf("Spec file '%s' does not exist!", specFile)
		}
		return fmt.Errorf("Spec file '%s' cannot be read! (%s)", specFile, err.Error())
	} else if stat.IsDir() {
		return fmt.Errorf("Spec file '%s' cannot be a directory!", specFile)
	}
	if stat, err := os.Stat(outputDirectory); err != nil {
		if os.IsNotExist(err) {
			return fmt.Errorf("Output directory '%s' does not exist!", outputDirectory)
		}
		return fmt.Errorf("Output directory '%s' cannot be read! (%s)", outputDirectory, err.Error())
	} else if !stat.IsDir() {
		return fmt.Errorf("Output directory '%s' cannot be a file!", specFile)
	}

	if spec, err := readSpec(specFile); err != nil {
		return err
	} else {
		tf := templatefactory.NewTemplateFactory()
		if err := tf.SetSpec(spec); err != nil {
			return err
		}
		tf.RegisterTemplateFunction("title", strings.Title)
		tf.RegisterTemplateFunction("lower", strings.ToLower)
		tf.RegisterTemplateFunction("upper", strings.ToUpper)
		tf.RegisterTemplateFunction("now", time.Now)
		tf.RegisterTemplateFunction("json", Jsonify)
		tf.RegisterTemplateFunction("jsonindent", JsonifyIndent)
		tf.RegisterTemplateFunction("unescape", Unescape)
		return process(inputTemplate, spec, outputDirectory, tf)
	}
}

func main() {
	if err := mainInner(); err != nil {
		os.Stderr.WriteString(err.Error() + "\n")
		os.Exit(1)
	}
}
<commit_msg>added version number check in the spec file<commit_after>package main

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

	"gopkg.in/yaml.v2"

	"github.com/AstromechZA/spiro/templatefactory"
)

const usageString = `
Spiro is an template structure generator that uses Golangs text/template library. It accepts both single files as well
as directory trees as input and will interpret any template calls found inside the files and the file/directory names.

The rule-set is probably a bit complex to display here, but the following links are useful:

- https://golang.org/pkg/text/template
- https://gohugo.io/templates/go-templates/

Some additional template functions are supplied:

- 'title': capitalise string
- 'upper': convert string to upper case
- 'lower': convert string to lower case
- 'now': return current time object (time.Time)

See the project homepage for more documentation: https://github.com/AstromechZA/spiro

The spec file should be in JSON or Yaml form and will be passed to each template invocation.

$ spiro [options] {input template} {spec file} {output directory}
`

const logoImage = `
  _________      .__
 /   _____/_____ |__|______  ____
 \_____  \\____ \|  \_  __ \/  _ \
 /        \  |_> >  ||  | \(  <_> )
/_______  /   __/|__||__|   \____/
        \/|__|
`

var Version = "<unofficial build>"
var GitCommit = "<commit unknown>"
var GitState = "<changes unknown>"
var BuildDate = "<no date>"

func copyFileContents(src, dst string) error {
	in, err := os.Open(src)
	if err != nil {
		return err
	}
	defer in.Close()
	out, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer func() {
		cerr := out.Close()
		if err == nil {
			err = cerr
		}
	}()
	if _, err = io.Copy(out, in); err != nil {
		return err
	}
	return out.Sync()
}

func processDir(templateString string, spec *map[string]interface{}, outputDir string, tf *templatefactory.TemplateFactory) error {
	fromBase := path.Base(templateString)
	toBase := fromBase
	if tf.StringContainsTemplating(fromBase) {
		var err error
		toBase, err = tf.Render(fromBase)
		if err != nil {
			return fmt.Errorf("Error while processing '%s': %s", templateString, err.Error())
		}
	}
	toBase = strings.TrimSpace(toBase)
	if len(toBase) == 0 {
		fmt.Printf("Skipping '%s' since the name evaluated to ''\n", templateString)
		return nil
	}

	newOutputDir := path.Join(outputDir, toBase)
	fmt.Printf("Processing '%s/' -> '%s/'\n", templateString, newOutputDir)
	if err := os.Mkdir(newOutputDir, 0755); err != nil && !os.IsExist(err) {
		return fmt.Errorf("Error while processing '%s': %s", templateString, err.Error())
	}

	items, _ := ioutil.ReadDir(templateString)
	for _, item := range items {
		if err := process(path.Join(templateString, item.Name()), spec, newOutputDir, tf); err != nil {
			return err
		}
	}
	return nil
}

func processFile(templateString string, spec *map[string]interface{}, outputDir string, tf *templatefactory.TemplateFactory) error {
	fromBase := path.Base(templateString)
	toBase := fromBase
	if tf.StringContainsTemplating(fromBase) {
		var err error
		toBase, err = tf.Render(fromBase)
		if err != nil {
			return fmt.Errorf("Error while processing '%s': %s", templateString, err.Error())
		}
	}
	toBase = strings.TrimSpace(toBase)
	if len(toBase) == 0 {
		fmt.Printf("Skipping '%s' since the name evaluated to ''\n", templateString)
		return nil
	}

	if strings.HasSuffix(toBase, ".templated") {
		toBase = toBase[:len(toBase)-10]
		if len(toBase) == 0 {
			fmt.Printf("Skipping '%s' since the name evaluated to ''\n", templateString)
			return nil
		}
		fmt.Printf("Processing '%s' -> '%s'\n", templateString, path.Join(outputDir, toBase))
		inputBytes, err := ioutil.ReadFile(templateString)
		if err != nil {
			return fmt.Errorf("Error while reading '%s': %s", templateString, err.Error())
		}
		outputBytes, err := tf.Render(string(inputBytes))
		if err != nil {
			return fmt.Errorf("Error while rendering template for '%s': %s", templateString, err.Error())
		}
		if err := ioutil.WriteFile(path.Join(outputDir, toBase), []byte(outputBytes), 0644); err != nil {
			return fmt.Errorf("Error while writing file bytes for '%s': %s", templateString, err.Error())
		}
	} else {
		fmt.Printf("Processing '%s' -> '%s'\n", templateString, path.Join(outputDir, toBase))
		if err := copyFileContents(templateString, path.Join(outputDir, toBase)); err != nil {
			return fmt.Errorf("Error while copying file bytes for '%s': %s", templateString, err.Error())
		}
	}

	info, _ := os.Stat(templateString)
	if err := os.Chmod(path.Join(outputDir, toBase), info.Mode()); err != nil {
		return fmt.Errorf("Error while writing file permissions for '%s': %s", templateString, err.Error())
	}

	return nil
}

func process(templateString string, spec *map[string]interface{}, outputDir string, tf *templatefactory.TemplateFactory) error {
	stat, err := os.Stat(templateString)
	if err != nil {
		return fmt.Errorf("Error processing template %s: %s", templateString, err.Error())
	}
	if stat.IsDir() {
		return processDir(templateString, spec, outputDir, tf)
	}
	return processFile(templateString, spec, outputDir, tf)
}

func readSpec(specFile string) (*map[string]interface{}, error) {
	specBytes, err := ioutil.ReadFile(specFile)
	if err != nil {
		return nil, fmt.Errorf("Could not read json spec file: %s", err.Error())
	}
	var spec map[string]interface{}
	if strings.HasSuffix(specFile, ".json") {
		err = json.Unmarshal(specBytes, &spec)
		if err != nil {
			return nil, fmt.Errorf("Could not parse json spec file: %s", err.Error())
		}
		return &spec, nil
	} else if strings.HasSuffix(specFile, ".yaml") || strings.HasSuffix(specFile, ".yml") {
		err = yaml.Unmarshal(specBytes, &spec)
		if err != nil {
			return nil, fmt.Errorf("Could not parse yaml spec file: %s", err.Error())
		}
		return &spec, nil
	} else {
		return nil, fmt.Errorf("I do not know how to parse the spec, expected .json, .yaml, or .yml")
	}
}

// Build an integer from a version string. The version string can contain 3 numbers and each number can be a maximum
// of 999. 1.2.3 -> 100200300.
func buildVersionInt(versionString string) (uint64, error) {
	parts := strings.Split(versionString, ".")
	var value uint64
	for index := 0; index < 3; index++ {
		if len(parts) > index {
			v, err := strconv.Atoi(parts[index])
			if v < 0 {
				v = 0
			} else if v > 999 {
				v = 999
			}
			if err != nil {
				return value, fmt.Errorf("Could not parse version part %s in %s", parts[index], versionString)
			}
			value += uint64(v)
		}
		value *= 1000
	}
	return value, nil
}

// This function compares the current version to the one in the spec file. If the running version is too low, return
// an error. The version numbers are compared as integers.
func checkVersionIfNecessary(spec *map[string]interface{}) error {
	if minVersion, ok := (*spec)["_spiro_min_version_"]; ok {
		if minVersionString, ok := minVersion.(string); ok {
			if minVersionValue, err := buildVersionInt(minVersionString); err != nil {
				return err
			} else if currentVersionValue, err := buildVersionInt(Version); err != nil {
				return err
			} else if currentVersionValue < minVersionValue {
				return fmt.Errorf("Spiro template lists minimum version %s but you're using %s!", minVersionString, Version)
			}
		}
	}
	return nil
}

func mainInner() error {

	// first set up config flag options
	versionFlag := flag.Bool("version", false, "Print the version string")

	// set a more verbose usage message.
	flag.Usage = func() {
		os.Stderr.WriteString(strings.TrimSpace(usageString) + "\n\n")
		flag.PrintDefaults()
	}
	// parse them
	flag.Parse()

	// do arg checking
	if *versionFlag {
		fmt.Printf("Version: %s (%s-%s) on %s \n", Version, GitCommit, GitState, BuildDate)
		fmt.Println(logoImage)
		fmt.Println("Project: github.com/AstromechZA/spiro")
		return nil
	}
	if flag.NArg() != 3 {
		flag.Usage()
		os.Exit(1)
	}

	inputTemplate := flag.Arg(0)
	specFile := flag.Arg(1)
	outputDirectory := flag.Arg(2)

	if _, err := os.Stat(inputTemplate); err != nil {
		if os.IsNotExist(err) {
			return fmt.Errorf("Input template '%s' does not exist!", inputTemplate)
		}
		return fmt.Errorf("Input template '%s' cannot be read! (%s)", inputTemplate, err.Error())
	}
	if stat, err := os.Stat(specFile); err != nil {
		if os.IsNotExist(err) {
			return fmt.Errorf("Spec file '%s' does not exist!", specFile)
		}
		return fmt.Errorf("Spec file '%s' cannot be read! (%s)", specFile, err.Error())
	} else if stat.IsDir() {
		return fmt.Errorf("Spec file '%s' cannot be a directory!", specFile)
	}
	if stat, err := os.Stat(outputDirectory); err != nil {
		if os.IsNotExist(err) {
			return fmt.Errorf("Output directory '%s' does not exist!", outputDirectory)
		}
		return fmt.Errorf("Output directory '%s' cannot be read! (%s)", outputDirectory, err.Error())
	} else if !stat.IsDir() {
		return fmt.Errorf("Output directory '%s' cannot be a file!", specFile)
	}

	if spec, err := readSpec(specFile); err != nil {
		return err
	} else if err := checkVersionIfNecessary(spec); err != nil {
		return err
	} else {
		tf := templatefactory.NewTemplateFactory()
		if err := tf.SetSpec(spec); err != nil {
			return err
		}
		tf.RegisterTemplateFunction("title", strings.Title)
		tf.RegisterTemplateFunction("lower", strings.ToLower)
		tf.RegisterTemplateFunction("upper", strings.ToUpper)
		tf.RegisterTemplateFunction("now", time.Now)
		tf.RegisterTemplateFunction("json", Jsonify)
		tf.RegisterTemplateFunction("jsonindent", JsonifyIndent)
		tf.RegisterTemplateFunction("unescape", Unescape)
		return process(inputTemplate, spec, outputDirectory, tf)
	}
}

func main() {
	if err := mainInner(); err != nil {
		os.Stderr.WriteString(err.Error() + "\n")
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net"

	"github.com/paultag/go-dictd/database"
	"github.com/paultag/go-dictd/dictd"
)

func main() {
	server := dictd.NewServer("pault.ag")
	levelDB, err := database.NewLevelDBDatabase("/home/tag/jargon.leveldb", "jargon file")

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

	server.RegisterDatabase(levelDB, "jargon")

	link, err := net.Listen("tcp", ":2017")
	if err != nil {
		log.Fatal(err)
	}

	for {
		conn, err := link.Accept()
		if err != nil {
			log.Printf("Error: %s", err)
		}
		go dictd.Handle(&server, conn)
	}
}
<commit_msg>stupid<commit_after>package main

import (
	"log"
	"net"

	"github.com/paultag/go-dictd/database"
	"github.com/paultag/go-dictd/dictd"
)

func main() {
	server := dictd.NewServer("pault.ag")
	levelDB, err := database.NewLevelDBDatabase("/home/tag/jargon.ldb", "jargon file")

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

	server.RegisterDatabase(levelDB, "jargon")

	link, err := net.Listen("tcp", ":2017")
	if err != nil {
		log.Fatal(err)
	}

	for {
		conn, err := link.Accept()
		if err != nil {
			log.Printf("Error: %s", err)
		}
		go dictd.Handle(&server, conn)
	}
}
<|endoftext|>