<commit_before>// Copyright 2016 Jacob Taylor jacob@ablox.io
// License: Apache2 - http://www.apache.org/licenses/LICENSE-2.0
package main

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"github.com/goji/httpauth"
	"github.com/rjeczalik/notify"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
	"sort"
	"time"
)

type ReplicatServer struct {
	Cluster      string
	Name         string
	Address      string
	LastReceived DirTreeMap
	LastSent     DirTreeMap
}

var serverMap = make(map[string]ReplicatServer)

type Event struct {
	Source       string
	Name         string
	OriginalName string
	Message      string
	Time         time.Time
	IsDirectory  bool
}

var events = make([]Event, 0, 100)

func main() {
	fmt.Println("replicat initializing....")
	rand.Seed(int64(time.Now().Nanosecond()))

	SetupCli()

	// Update the path that traffic is served from to be the filsystem canonical path. This will allow the event folders that come in to match what we have.
	symPath, err := filepath.EvalSymlinks(globalSettings.Directory)
	if err != nil {
		panic(err)
	}

	if symPath != globalSettings.Directory {
		fmt.Printf("Updating serving directory to: %s\n", symPath)
		globalSettings.Directory = symPath
	}

	listOfFileInfo, err := createListOfFolders()
	if err != nil {
		log.Fatal(err)
	}

	// Make the channel buffered to ensure no event is dropped. Notify will drop
	// an event if the receiver is not able to keep up the sending pace.
	fsEventsChannel := make(chan notify.EventInfo, 1)

	// Set up a watch point listening for events within a directory tree rooted at the specified folder
	err = notify.Watch(globalSettings.Directory+"/...", fsEventsChannel, notify.All)
	if err != nil {
		log.Fatal(err)
	}
	defer notify.Stop(fsEventsChannel)

	fmt.Printf("replicat %s online....\n", globalSettings.Name)
	defer fmt.Println("End of line")

	//globalSettings.Address = GlobalServerMap[globalSettings.Name].Address

	totalFiles := 0
	for _, fileInfoList := range listOfFileInfo {
		totalFiles += len(fileInfoList)
	}

	fmt.Printf("Now keeping an eye on %d folders and %d files located under: %s\n", len(listOfFileInfo), totalFiles, globalSettings.Directory)
	go func(c chan notify.EventInfo) {
		for {
			ei := <-c

			// todo add ignore file
			// sendEvent to manager (if it's available)
			fullPath := string(ei.Path())
			directoryLength := len(globalSettings.Directory)

			path := fullPath
			if len(fullPath) >= directoryLength && globalSettings.Directory == fullPath[:directoryLength] {
				// update the path to not have this prefix
				path = fullPath[directoryLength+1:]
			}

			//fmt.Printf("Call to isdir resulted in %v\n", ei.IsDir())

			//// Check if it is a directory based on our directory tree first. Then check the file system
			var isDirectory bool
			//_, exists := listOfFileInfo[fullPath]
			//if exists == true {
			//	isDirectory = true
			//} else {
			//}
			// Get the file info. Useful if we need to restrict our actions to directories
			info, err := os.Stat(fullPath)
			if err == nil {
				isDirectory = info.IsDir()
			} else {
				_, exists := listOfFileInfo[fullPath]
				if exists == true {
					isDirectory = true
				}
			}

			event := Event{Name: ei.Event().String(), Message: path, IsDirectory: isDirectory}
			fmt.Printf("event raw data: %v with path: %v\n", ei.Event(), path)

			// todo copy the source folder on a rename to the event
			sendEvent(&event, globalSettings.ManagerAddress, globalSettings.ManagerCredentials)

			// sendEvent to peers (if any)
			//for name, server := range GlobalServerMap {
			//	if name != globalSettings.Name {
			//		fmt.Printf("sending to peer %s\n", name)
			//		sendEvent(&event, server.Address, globalSettings.ManagerCredentials)
			//	}
			//}
			for k, v := range serverMap {
				if k != globalSettings.Name {
					fmt.Printf("sending to peer %s at %s\n", k, v.Address)
					sendEvent(&event, v.Address, globalSettings.ManagerCredentials)
				}
			}

			log.Println("Got event:" + ei.Event().String() + ", with Path:" + ei.Path())
		}
	}(fsEventsChannel)

	http.Handle("/event/", httpauth.SimpleBasicAuth("replicat", "isthecat")(http.HandlerFunc(eventHandler)))
	http.Handle("/tree/", httpauth.SimpleBasicAuth("replicat", "isthecat")(http.HandlerFunc(folderTreeHandler)))
	http.Handle("/config/", httpauth.SimpleBasicAuth("replicat", "isthecat")(http.HandlerFunc(configHandler)))

	go bootstrapAndServe()

	dotCount := 0
	sleepSeconds := time.Duration(25 + rand.Intn(10))
	fmt.Printf("Full Sync time set to: %d seconds\n", sleepSeconds)
	for {
		// Randomize the sync time to decrease oscillation
		time.Sleep(time.Second * sleepSeconds)
		changed, updatedState, newPaths, deletedPaths, matchingPaths := checkForChanges(listOfFileInfo, nil)
		if changed {
			fmt.Println("\nWe have changes, ship it (also updating saved state now that the changes were tracked)")
			fmt.Printf("@Path report: new %d, deleted %d, matching %d, original %d, updated %d\n", len(newPaths), len(deletedPaths), len(matchingPaths), len(listOfFileInfo), len(updatedState))
			fmt.Printf("@New paths: %v\n", newPaths)
			fmt.Printf("@Deleted paths: %v\n", deletedPaths)
			fmt.Println("******************************************************")
			listOfFileInfo = updatedState

			// Post the changes to the other side.
			sendFolderTree(listOfFileInfo)

		} else {
			fmt.Print(".")
			dotCount++
			if dotCount%100 == 0 {
				fmt.Println("")
			}
		}
	}
}

/*
Send the folder tree from this node to another node for comparison
*/
func sendFolderTree(initialTree DirTreeMap) {
	var tree = make(DirTreeMap)
	prefixLength := 1 // the slash prefix
	for key, value := range initialTree {
		if key == "" {
			continue
		}
		fmt.Printf("key is: '%s', length is: %d\n", key, prefixLength)
		key = key[prefixLength:]
		tree[key] = value
	}

	// package up the tree
	jsonStr, err := json.Marshal(tree)
	if err != nil {
		panic(err)
	}
	buffer := bytes.NewBuffer(jsonStr)
	client := &http.Client{}

	data := []byte(globalSettings.ManagerCredentials)
	authHash := base64.StdEncoding.EncodeToString(data)

	for k, v := range serverMap {
		// don't send an update to ourselves
		if k == globalSettings.Name {
			continue
		}

		url := "http://" + v.Address + "/tree/"
		fmt.Printf("Posting folder tree to node: %s at URL: %s\n", k, url)

		req, err := http.NewRequest("POST", url, buffer)
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", "Basic "+authHash)

		resp, err := client.Do(req)
		if err != nil {
			fmt.Printf("we encountered an error!\n%s\n", err)
			//panic(err)
			continue
		}

		//fmt.Println("response Status:", resp.Status)
		//fmt.Println("response Headers:", resp.Header)
		_, _ = ioutil.ReadAll(resp.Body)
		//body, _ := ioutil.ReadAll(resp.Body)
		//fmt.Println("response Body:", string(body))
		resp.Body.Close()
	}
}

func folderTreeHandler(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		listOfFileInfo, err := createListOfFolders()
		if err != nil {
			log.Fatal(err)
		}
		json.NewEncoder(w).Encode(listOfFileInfo)
		fmt.Printf("Sending tree of size %d back to client\n", len(listOfFileInfo))
	case "POST":
		//log.Println("Got POST: ", r.Body)
		decoder := json.NewDecoder(r.Body)

		var remoteTreePreTranslate DirTreeMap
		err := decoder.Decode(&remoteTreePreTranslate)
		if err != nil {
			panic("bad json body")
		}

		var remoteTree = make(DirTreeMap)
		for key, value := range remoteTreePreTranslate {
			//fmt.Printf("key is: '%s'\n", key)
			key = fmt.Sprintf("%s/%s", globalSettings.Directory, key)
			remoteTree[key] = value
		}

		// todo get the node name in here
		fmt.Printf("Received a tree map from: %s\n%s\n", r.RemoteAddr, remoteTree)

		// let's compare to the current one we have.
		_, _, newPaths, deletedPaths, _ := checkForChanges(remoteTree, nil)

		// update the paths to be consistent. Remember, everything is backwards since this is comparing the other side to us instead of two successive states
		// deletion will show up as a new path.

		fmt.Printf("about to check for deletions: %s\n", newPaths)

		// delete folders that were deleted. We delete first, then add to make sure that the old ones will not be in the way
		if len(newPaths) > 0 {
			fmt.Println("Paths were deleted on the other side. Delete them here")
			// Reverse sort the paths so the most specific is first. This allows us to get away without a recursive delete
			sort.Sort(sort.Reverse(sort.StringSlice(newPaths)))
			for _, pathName := range newPaths {
				if globalSettings.Directory == "" {
					fmt.Sprintf("Trying to delete invalid path: %s\n", pathName)
					panic("Path information is not right. Do not delete")
				} else if pathName == globalSettings.Directory {
					fmt.Printf("We had a request to delete the base path. Skipping: %s\n", pathName)
					continue
				}

				// stop on any error except for not exist. We are trying to delete it anyway (or rather, it should have been deleted already)
				err = os.Remove(pathName)
				if err != nil && !os.IsNotExist(err) {
					panic(err)
				}
			}
		}

		// add folders that were created
		fmt.Printf("about to check for new folders: %s\n", deletedPaths)
		if len(deletedPaths) > 0 {
			fmt.Println("Paths were added on the other side. Create them here")
			for _, newPathName := range deletedPaths {
				fmt.Printf("pathname is: %s\n", newPathName)
				//if newPathName == "" {
				//	continue
				//}
				//newFullPath := fmt.Sprintf("%s/%s", globalSettings.Directory, newPathName)
				//fmt.Printf("new full path is: %s\n", newFullPath)
				err = os.Mkdir(newPathName, os.ModeDir+os.ModePerm)
				//todo figure out how to catch a path exists error.
				//if err != nil && err != os.ErrExist {
				//	panic(err)
				//}
			}
		}

	}
}

func sendEvent(event *Event, address string, credentials string) {
	url := "http://" + address + "/event/"
	fmt.Printf("target url: %s\n", url)

	// Set the event source (server name)
	event.Source = globalSettings.Name

	jsonStr, _ := json.Marshal(event)
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
	req.Header.Set("Content-Type", "application/json")

	data := []byte(credentials)
	authHash := base64.StdEncoding.EncodeToString(data)
	req.Header.Add("Authorization", "Basic "+authHash)

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		log.Println(err)
	} else {
		defer resp.Body.Close()

		fmt.Println("response Status:", resp.Status)
		fmt.Println("response Headers:", resp.Header)
		body, _ := ioutil.ReadAll(resp.Body)
		fmt.Println("response Body:", string(body))
	}
}

func eventHandler(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		json.NewEncoder(w).Encode(events)
	case "POST":
		log.Println("Got POST: ", r.Body)
		decoder := json.NewDecoder(r.Body)
		var event Event
		err := decoder.Decode(&event)
		if err != nil {
			panic("bad json body")
		}

		log.Println(event.Name + ", path: " + event.Message)
		log.Printf("Event info: %v\n", event)

		//// Only do stuff for directories at the moment.
		//if event.IsDirectory == false {
		//	fmt.Println("The event is not for a directory, skipping")
		//	return
		//}

		pathName := globalSettings.Directory + "/" + event.Message

		switch event.Name {
		case "notify.Create":
			os.Mkdir(pathName, os.ModeDir+os.ModePerm)
			// todo figure out how to catch a path exists error
			fmt.Printf("create event found. Should be creating: %s\n", pathName)
		case "notify.Remove":
			os.Remove(pathName)
			fmt.Printf("remove attempted on: %s\n", pathName)
		case "notify.Rename":
			os.Remove(pathName)
			fmt.Printf("rename attempted on: %s\n", pathName)
		default:
			fmt.Printf("Unknown event found, doing nothing. Event: %s\n", event.Name)
		}

		events = append([]Event{event}, events...)
	}
}
<commit_msg>cleaning up the file monitoring loop into an external function. Changing the dirtreemaps to have relative paths only. better handle errors for create and rename to allow for the desired case to have already occurred<commit_after>// Copyright 2016 Jacob Taylor jacob@ablox.io
// License: Apache2 - http://www.apache.org/licenses/LICENSE-2.0
package main

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"github.com/goji/httpauth"
	"github.com/rjeczalik/notify"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
	"sort"
	"time"
	"syscall"
)

type ReplicatServer struct {
	Cluster      string
	Name         string
	Address      string
	LastReceived DirTreeMap
	LastSent     DirTreeMap
}

var serverMap = make(map[string]ReplicatServer)

type Event struct {
	Source       string
	Name         string
	Message      string
	Time         time.Time
	IsDirectory  bool
}

var events = make([]Event, 0, 100)

func main() {
	fmt.Println("replicat initializing....")
	rand.Seed(int64(time.Now().Nanosecond()))

	SetupCli()

	// Update the path that traffic is served from to be the filsystem canonical path. This will allow the event folders that come in to match what we have.
	symPath, err := filepath.EvalSymlinks(globalSettings.Directory)
	if err != nil {
		panic(err)
	}

	if symPath != globalSettings.Directory {
		fmt.Printf("Updating serving directory to: %s\n", symPath)
		globalSettings.Directory = symPath
	}

	listOfFileInfo, err := createListOfFolders()
	if err != nil {
		log.Fatal(err)
	}

	// Make the channel buffered to ensure no event is dropped. Notify will drop
	// an event if the receiver is not able to keep up the sending pace.
	fsEventsChannel := make(chan notify.EventInfo, 1)

	//setup the processing loop for handling filesystem events
	go filesystemMonitorLoop(fsEventsChannel, &listOfFileInfo)

	// Set up a watch point listening for events within a directory tree rooted at the specified folder
	err = notify.Watch(globalSettings.Directory+"/...", fsEventsChannel, notify.All)
	if err != nil {
		log.Fatal(err)
	}
	defer notify.Stop(fsEventsChannel)

	fmt.Printf("replicat %s online....\n", globalSettings.Name)
	defer fmt.Println("End of line")

	totalFiles := 0
	for _, fileInfoList := range listOfFileInfo {
		totalFiles += len(fileInfoList)
	}

	fmt.Printf("Now keeping an eye on %d folders and %d files located under: %s\n", len(listOfFileInfo), totalFiles, globalSettings.Directory)

	http.Handle("/event/", httpauth.SimpleBasicAuth("replicat", "isthecat")(http.HandlerFunc(eventHandler)))
	http.Handle("/tree/", httpauth.SimpleBasicAuth("replicat", "isthecat")(http.HandlerFunc(folderTreeHandler)))
	http.Handle("/config/", httpauth.SimpleBasicAuth("replicat", "isthecat")(http.HandlerFunc(configHandler)))

	go bootstrapAndServe()

	dotCount := 0
	sleepSeconds := time.Duration(25 + rand.Intn(10))
	fmt.Printf("Full Sync time set to: %d seconds\n", sleepSeconds)
	for {
		// Randomize the sync time to decrease oscillation
		time.Sleep(time.Second * sleepSeconds)
		changed, updatedState, newPaths, deletedPaths, matchingPaths := checkForChanges(listOfFileInfo, nil)
		if changed {
			fmt.Println("\nWe have changes, ship it (also updating saved state now that the changes were tracked)")
			fmt.Printf("@Path report: new %d, deleted %d, matching %d, original %d, updated %d\n", len(newPaths), len(deletedPaths), len(matchingPaths), len(listOfFileInfo), len(updatedState))
			fmt.Printf("@New paths: %v\n", newPaths)
			fmt.Printf("@Deleted paths: %v\n", deletedPaths)
			fmt.Println("******************************************************")
			listOfFileInfo = updatedState

			// Post the changes to the other side.
			sendFolderTree(listOfFileInfo)

		} else {
			fmt.Print(".")
			dotCount++
			if dotCount%100 == 0 {
				fmt.Println("")
			}
		}
	}
}

// Monitor the filesystem looking for changes to files we are keeping track of.
func filesystemMonitorLoop(c chan notify.EventInfo, listOfFileInfo *DirTreeMap) {
	directoryLength := len(globalSettings.Directory)
	for {
		ei := <-c

		fullPath := string(ei.Path())

		path := fullPath
		if len(fullPath) >= directoryLength && globalSettings.Directory == fullPath[:directoryLength] {
			// update the path to not have this prefix
			path = fullPath[directoryLength+1:]
		}

		//todo was working on this code. Should have unit tests for the monitor filesystem. It looks like rename has two unrelated events. Is this right? How do we tell it is a rename and not a remove?
		// It looks like there is some sort of reversal of paths now. where adds are being removed from the other side.

		// Check to see if this was a path we knew about
		_, isDirectory := (*listOfFileInfo)[path]
		var iNode uint64
		// if we have not found it yet, check to see if it can be stated
		info, err := os.Stat(fullPath)
		if err == nil {
			isDirectory = info.IsDir()
			sysInterface := info.Sys()
			fmt.Printf("sysInterface: %v\n", sysInterface)
			if sysInterface != nil {
				//foo := syscall.Stat_t(sysInterface)
				foo := sysInterface.(*syscall.Stat_t)
				iNode = foo.Ino
			}
		}
		//if isDirectory == false {
		//	info, err := os.Stat(fullPath)
		//	if err == nil {
		//		isDirectory = info.IsDir()
		//	}
		//
		//	// try and get the inode number
		//
		//	//inode = info.Sys().Ino
		//}

		event := Event{Name: ei.Event().String(), Message: path}
		fmt.Printf("event raw data: %v with path: %v IsDirectory %v iNode: %s\n", ei.Event(), path, isDirectory, iNode)

		if isDirectory {
			// sendEvent to manager
			sendEvent(&event, globalSettings.ManagerAddress, globalSettings.ManagerCredentials)

			// SendEvent to all peers
			for k, v := range serverMap {
				if k != globalSettings.Name {
					fmt.Printf("sending to peer %s at %s\n", k, v.Address)
					sendEvent(&event, v.Address, globalSettings.ManagerCredentials)
				}
			}
		}
	}
}

/*
Send the folder tree from this node to another node for comparison
*/
func sendFolderTree(initialTree DirTreeMap) {
	var tree = make(DirTreeMap)
	prefixLength := 1 // the slash prefix
	for key, value := range initialTree {
		if key == "" {
			continue
		}
		fmt.Printf("key is: '%s', length is: %d\n", key, prefixLength)
		key = key[prefixLength:]
		tree[key] = value
	}

	// package up the tree
	jsonStr, err := json.Marshal(tree)
	if err != nil {
		panic(err)
	}
	buffer := bytes.NewBuffer(jsonStr)
	client := &http.Client{}

	data := []byte(globalSettings.ManagerCredentials)
	authHash := base64.StdEncoding.EncodeToString(data)

	for k, v := range serverMap {
		// don't send an update to ourselves
		if k == globalSettings.Name {
			continue
		}

		url := "http://" + v.Address + "/tree/"
		fmt.Printf("Posting folder tree to node: %s at URL: %s\n", k, url)

		req, err := http.NewRequest("POST", url, buffer)
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", "Basic "+authHash)

		resp, err := client.Do(req)
		if err != nil {
			fmt.Printf("we encountered an error!\n%s\n", err)
			//panic(err)
			continue
		}

		//fmt.Println("response Status:", resp.Status)
		//fmt.Println("response Headers:", resp.Header)
		_, _ = ioutil.ReadAll(resp.Body)
		//body, _ := ioutil.ReadAll(resp.Body)
		//fmt.Println("response Body:", string(body))
		resp.Body.Close()
	}
}

func folderTreeHandler(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		listOfFileInfo, err := createListOfFolders()
		if err != nil {
			log.Fatal(err)
		}
		json.NewEncoder(w).Encode(listOfFileInfo)
		fmt.Printf("Sending tree of size %d back to client\n", len(listOfFileInfo))
	case "POST":
		//log.Println("Got POST: ", r.Body)
		decoder := json.NewDecoder(r.Body)

		var remoteTreePreTranslate DirTreeMap
		err := decoder.Decode(&remoteTreePreTranslate)
		if err != nil {
			panic("bad json body")
		}

		var remoteTree = make(DirTreeMap)
		for key, value := range remoteTreePreTranslate {
			//fmt.Printf("key is: '%s'\n", key)
			key = fmt.Sprintf("%s/%s", globalSettings.Directory, key)
			remoteTree[key] = value
		}

		// todo get the node name in here
		fmt.Printf("Received a tree map from: %s\n%s\n", r.RemoteAddr, remoteTree)

		// let's compare to the current one we have.
		_, _, newPaths, deletedPaths, _ := checkForChanges(remoteTree, nil)

		// update the paths to be consistent. Remember, everything is backwards since this is comparing the other side to us instead of two successive states
		// deletion will show up as a new path.

		fmt.Printf("about to check for deletions: %s\n", newPaths)

		// delete folders that were deleted. We delete first, then add to make sure that the old ones will not be in the way
		if len(newPaths) > 0 {
			if globalSettings.Directory == "" {
				panic("globalSettings.Directory is not configured correctly. Aborting")
			}

			fmt.Println("Paths were deleted on the other side. Delete them here")

			fmt.Printf("Paths to delete\nBefore sort:\n%v\n", newPaths)
			// Reverse sort the paths so the most specific is first. This allows us to get away without a recursive delete
			sort.Sort(sort.Reverse(sort.StringSlice(newPaths)))
			fmt.Printf("Paths to delete after sort\nAfter sort:\n%v\n", newPaths)
			for _, relativePath := range newPaths {
				if relativePath == "" || relativePath == "/" {
					fmt.Printf("We had a request to delete the base path. Skipping: %s\n", relativePath)
					continue
				}
				fullPath := globalSettings.Directory + relativePath
				fmt.Printf("Full path is: %s\n", fullPath)

				fmt.Printf("%s: about to remove\n", fullPath)

				// stop on any error except for not exist. We are trying to delete it anyway (or rather, it should have been deleted already)
				err = os.Remove(fullPath)
				if err != nil && !os.IsNotExist(err) {
					panic(err)
				}
				fmt.Printf("%s: done removing (err = %v)\n", fullPath, err)
				err = nil
			}
		}

		// add folders that were created
		fmt.Printf("about to check for new folders: %s\n", deletedPaths)
		if len(deletedPaths) > 0 {
			fmt.Println("Paths were added on the other side. Create them here")
			for _, newPathName := range deletedPaths {
				fmt.Printf("pathname is: %s\n", newPathName)
				//if newPathName == "" {
				//	continue
				//}
				//newFullPath := fmt.Sprintf("%s/%s", globalSettings.Directory, newPathName)
				//fmt.Printf("new full path is: %s\n", newFullPath)
				err = os.Mkdir(newPathName, os.ModeDir+os.ModePerm)
				//todo figure out how to catch a path exists error.
				//if err != nil && err != os.ErrExist {
				//	panic(err)
				//}
			}
		}

	}
}

func sendEvent(event *Event, address string, credentials string) {
	url := "http://" + address + "/event/"
	fmt.Printf("target url: %s\n", url)

	// Set the event source (server name)
	event.Source = globalSettings.Name

	jsonStr, _ := json.Marshal(event)
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
	req.Header.Set("Content-Type", "application/json")

	data := []byte(credentials)
	authHash := base64.StdEncoding.EncodeToString(data)
	req.Header.Add("Authorization", "Basic "+authHash)

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		log.Println(err)
	} else {
		defer resp.Body.Close()

		fmt.Println("response Status:", resp.Status)
		fmt.Println("response Headers:", resp.Header)
		body, _ := ioutil.ReadAll(resp.Body)
		fmt.Println("response Body:", string(body))
	}
}

func eventHandler(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case "GET":
		json.NewEncoder(w).Encode(events)
	case "POST":
		log.Println("Got POST: ", r.Body)
		decoder := json.NewDecoder(r.Body)
		var event Event
		err := decoder.Decode(&event)
		if err != nil {
			panic("bad json body")
		}

		log.Println(event.Name + ", path: " + event.Message)
		log.Printf("Event info: %v\n", event)

		pathName := globalSettings.Directory + "/" + event.Message

		switch event.Name {
		case "notify.Create":
			err = os.Mkdir(pathName, os.ModeDir+os.ModePerm)
			if err != nil && !os.IsExist(err) {
				panic(fmt.Sprintf("Error creating folder %s: %v\n", pathName, err))
			}
			fmt.Printf("notify.Create: %s\n", pathName)
		case "notify.Remove":
			err = os.Remove(pathName)
			if err != nil && !os.IsNotExist(err) {
				panic(fmt.Sprintf("Error deleting folder %s: %v\n", pathName, err))
			}
			fmt.Printf("notify.Remove: %s\n", pathName)
		//case "notify.Rename":
		//	err = os.Remove(pathName)
		//	if err != nil && !os.IsNotExist(err) {
		//		panic(fmt.Sprintf("Error deleting folder that was renamed %s: %v\n", pathName, err))
		//	}
		//	fmt.Printf("notify.Rename: %s\n", pathName)
		default:
			fmt.Printf("Unknown event found, doing nothing. Event: %v\n", event)
		}

		events = append([]Event{event}, events...)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/tls"
	"fmt"
	"github.com/codegangsta/cli"
	mail "gopkg.in/gomail.v1"
	"io/ioutil"
	"os"
)

type Configuration struct {
	Server struct {
		TLS      bool   `json:"tls"`
		Host     string `json:"host"`
		Port     int    `json:"port"`
		User     string `json:"user"`
		Password string `json:"password"`
	} `json:"server"`
	From struct {
		Name    string `json:"name"`
		Address string `json:"address"`
	} `json:"from"`
	To []struct {
		Name    string `json:"name"`
		Address string `json:"address"`
	} `json:"to"`
}

func main() {
	app := cli.NewApp()
	app.Name = "valiant"
	app.Usage = "A brave mail pigeon"
	app.Author = "Romain Baugue"
	app.Email = "romain.baugue@elwinar.com"
	app.Version = "1-dev"
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "configuration,c",
			Value: "valiant.json",
			Usage: "configuration file",
		},
		cli.StringFlag{
			Name:  "body,b",
			Usage: "body file",
		},
		cli.StringFlag{
			Name:  "subject,s",
			Usage: "message subject",
		},
	}
	app.Action = action

	app.Run(os.Args)
}

func action(ctx *cli.Context) {
	var cfg Configuration
	var err error

	err = Load(ctx.String("configuration"), &cfg)
	if err != nil {
		fmt.Println("Unable to load configuration:", err)
		return
	}

	message := mail.NewMessage()
	message.SetHeader("From", fmt.Sprintf("%s <%s>", cfg.From.Name, cfg.From.Address))

	var to []string
	for _, t := range cfg.To {
		to = append(to, fmt.Sprintf("%s <%s>", t.Name, t.Address))
	}
	message.SetHeader("To", to...)
	message.SetHeader("Subject", ctx.String("subject"))

	body, err := ioutil.ReadFile(ctx.String("body"))
	if err != nil {
		fmt.Println("Unable to load body:", err)
		return
	}
	message.SetBody("text/html", string(body))

	var settings []mail.MailerSetting
	if cfg.Server.TLS {
		settings = append(settings, mail.SetTLSConfig(&tls.Config{
			InsecureSkipVerify: true,
			ServerName:         cfg.Server.Host,
		}))
	}
	mailer := mail.NewMailer(cfg.Server.Host, cfg.Server.User, cfg.Server.Password, cfg.Server.Port, settings...)

	if err := mailer.Send(message); err != nil {
		fmt.Println("Unable to deliver mail:", err)
		return
	}
}
<commit_msg>Bump version number to 1<commit_after>package main

import (
	"crypto/tls"
	"fmt"
	"github.com/codegangsta/cli"
	mail "gopkg.in/gomail.v1"
	"io/ioutil"
	"os"
)

type Configuration struct {
	Server struct {
		TLS      bool   `json:"tls"`
		Host     string `json:"host"`
		Port     int    `json:"port"`
		User     string `json:"user"`
		Password string `json:"password"`
	} `json:"server"`
	From struct {
		Name    string `json:"name"`
		Address string `json:"address"`
	} `json:"from"`
	To []struct {
		Name    string `json:"name"`
		Address string `json:"address"`
	} `json:"to"`
}

func main() {
	app := cli.NewApp()
	app.Name = "valiant"
	app.Usage = "A brave mail pigeon"
	app.Author = "Romain Baugue"
	app.Email = "romain.baugue@elwinar.com"
	app.Version = "1"
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "configuration,c",
			Value: "valiant.json",
			Usage: "configuration file",
		},
		cli.StringFlag{
			Name:  "body,b",
			Usage: "body file",
		},
		cli.StringFlag{
			Name:  "subject,s",
			Usage: "message subject",
		},
	}
	app.Action = action

	app.Run(os.Args)
}

func action(ctx *cli.Context) {
	var cfg Configuration
	var err error

	err = Load(ctx.String("configuration"), &cfg)
	if err != nil {
		fmt.Println("Unable to load configuration:", err)
		return
	}

	message := mail.NewMessage()
	message.SetHeader("From", fmt.Sprintf("%s <%s>", cfg.From.Name, cfg.From.Address))

	var to []string
	for _, t := range cfg.To {
		to = append(to, fmt.Sprintf("%s <%s>", t.Name, t.Address))
	}
	message.SetHeader("To", to...)
	message.SetHeader("Subject", ctx.String("subject"))

	body, err := ioutil.ReadFile(ctx.String("body"))
	if err != nil {
		fmt.Println("Unable to load body:", err)
		return
	}
	message.SetBody("text/html", string(body))

	var settings []mail.MailerSetting
	if cfg.Server.TLS {
		settings = append(settings, mail.SetTLSConfig(&tls.Config{
			InsecureSkipVerify: true,
			ServerName:         cfg.Server.Host,
		}))
	}
	mailer := mail.NewMailer(cfg.Server.Host, cfg.Server.User, cfg.Server.Password, cfg.Server.Port, settings...)

	if err := mailer.Send(message); err != nil {
		fmt.Println("Unable to deliver mail:", err)
		return
	}
}
<|endoftext|>


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

package main

import (
	"context"
	"flag"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"

	"cloud.google.com/go/compute/metadata"
	"cloud.google.com/go/trace"
)

var (
	projectID     string
	listen        string
	target        string
	tlsCert       string
	tlsKey        string
	enableLogging bool
)

func main() {
	ctx := context.Background()

	flag.StringVar(&projectID, "project", "", "google cloud project ID")
	flag.StringVar(&listen, "http", ":6996", "host:port proxy listens")
	flag.StringVar(&target, "target", "", "target server")
	flag.StringVar(&tlsCert, "tls-cert", "", "TLS cert file to start an HTTPS proxy")
	flag.StringVar(&tlsKey, "tls-key", "", "TLS key file to start an HTTPS proxy")
	flag.BoolVar(&enableLogging, "enable-logging", false, "set to enable logging to stackdriver")
	flag.Parse()

	if projectID == "" {
		// Try to retrieve it from metadata server.
		if metadata.OnGCE() {
			pid, err := metadata.ProjectID()
			if err != nil {
				log.Fatalf("Cannot get project ID from metadata server: %v", err)
			}
			projectID = pid
		}
	}
	// TODO(jbd): Show usage if projectID is not set.

	tc, err := trace.NewClient(ctx, projectID)
	if err != nil {
		log.Fatalf("Cannot initiate trace client: %v", err)
	}
	sp, _ := trace.NewLimitedSampler(1, 1<<32)
	tc.SetSamplingPolicy(sp)

	url, err := url.Parse(target)
	if err != nil {
		log.Fatal(err)
	}

	proxy := httputil.NewSingleHostReverseProxy(url)
	proxy.Transport = &transport{
		Trace: tc,
	}

	if tlsCert != "" && tlsKey != "" {
		log.Fatal(http.ListenAndServeTLS(listen, tlsCert, tlsKey, proxy))
	} else {
		log.Fatal(http.ListenAndServe(listen, proxy))
	}
}

type transport struct {
	Trace *trace.Client
}

func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
	s := t.Trace.SpanFromRequest(req)
	defer s.Finish()

	req.Header.Set("X-Cloud-Trace-Context", s.Header())
	resp, err := http.DefaultTransport.RoundTrip(req)
	if err != nil {
		s.SetLabel("error", err.Error())
	}
	return resp, err
}
<commit_msg>allow users to set a sampling rate<commit_after>// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"context"
	"flag"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"

	"cloud.google.com/go/compute/metadata"
	"cloud.google.com/go/trace"
)

var (
	projectID string
	listen    string
	target    string
	tlsCert   string
	tlsKey    string
	traceFrac float64

	enableLogging bool
)

func main() {
	ctx := context.Background()

	flag.StringVar(&projectID, "project", "", "google cloud project ID")
	flag.StringVar(&listen, "http", ":6996", "host:port proxy listens")
	flag.StringVar(&target, "target", "", "target server")
	flag.StringVar(&tlsCert, "tls-cert", "", "TLS cert file to start an HTTPS proxy")
	flag.StringVar(&tlsKey, "tls-key", "", "TLS key file to start an HTTPS proxy")
	flag.Float64Var(&traceFrac, "trace-fraction", 1, "sampling fraction for tracing")
	flag.BoolVar(&enableLogging, "enable-logging", false, "set to enable logging to stackdriver")
	flag.Parse()

	if projectID == "" {
		// Try to retrieve it from metadata server.
		if metadata.OnGCE() {
			pid, err := metadata.ProjectID()
			if err != nil {
				log.Fatalf("Cannot get project ID from metadata server: %v", err)
			}
			projectID = pid
		}
	}
	// TODO(jbd): Show usage if projectID is not set.

	tc, err := trace.NewClient(ctx, projectID)
	if err != nil {
		log.Fatalf("Cannot initiate trace client: %v", err)
	}
	sp, _ := trace.NewLimitedSampler(traceFrac, 1<<32)
	tc.SetSamplingPolicy(sp)

	url, err := url.Parse(target)
	if err != nil {
		log.Fatal(err)
	}

	proxy := httputil.NewSingleHostReverseProxy(url)
	proxy.Transport = &transport{
		Trace: tc,
	}

	if tlsCert != "" && tlsKey != "" {
		log.Fatal(http.ListenAndServeTLS(listen, tlsCert, tlsKey, proxy))
	} else {
		log.Fatal(http.ListenAndServe(listen, proxy))
	}
}

type transport struct {
	Trace *trace.Client
}

func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
	s := t.Trace.SpanFromRequest(req)
	defer s.Finish()

	req.Header.Set("X-Cloud-Trace-Context", s.Header())
	resp, err := http.DefaultTransport.RoundTrip(req)
	if err != nil {
		s.SetLabel("error", err.Error())
	}
	return resp, err
}
<|endoftext|>


<commit_before>package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	"github.com/loadimpact/speedboat/aggregate"
	"github.com/loadimpact/speedboat/loadtest"
	"github.com/loadimpact/speedboat/runner"
	"github.com/loadimpact/speedboat/runner/simple"
	"golang.org/x/net/context"
	"io/ioutil"
	"os"
	"path"
	"time"
)

func makeTest(c *cli.Context) (test loadtest.LoadTest, err error) {
	base := ""
	conf := loadtest.NewConfig()
	if len(c.Args()) > 0 {
		filename := c.Args()[0]
		base = path.Dir(filename)
		data, err := ioutil.ReadFile(filename)
		if err != nil {
			return test, err
		}

		loadtest.ParseConfig(data, &conf)
	}

	if c.IsSet("script") {
		conf.Script = c.String("script")
		base = ""
	}
	if c.IsSet("url") {
		conf.URL = c.String("url")
	}
	if c.IsSet("duration") {
		conf.Duration = c.Duration("duration").String()
	}
	if c.IsSet("vus") {
		conf.VUs = c.Int("vus")
	}

	test, err = conf.Compile()
	if err != nil {
		return test, err
	}

	if test.Script != "" {
		srcb, err := ioutil.ReadFile(path.Join(base, test.Script))
		if err != nil {
			return test, err
		}
		test.Source = string(srcb)
	}

	return test, nil
}

func run(test loadtest.LoadTest, r runner.Runner) <-chan runner.Result {
	ch := make(chan runner.Result)

	go func() {
		defer close(ch)

		timeout := time.Duration(0)
		for _, stage := range test.Stages {
			timeout += stage.Duration
		}

		ctx, _ := context.WithTimeout(context.Background(), timeout)
		scale := make(chan int, 1)
		scale <- test.Stages[0].VUs.Start

		for res := range runner.Run(ctx, r, scale) {
			ch <- res
		}
	}()

	return ch
}

func action(c *cli.Context) {
	test, err := makeTest(c)
	if err != nil {
		log.WithError(err).Fatal("Configuration error")
	}

	r := simple.New()
	r.URL = test.URL

	stats := aggregate.Stats{}
	stats.Time.Values = make([]time.Duration, 30000000)[:0]
	for res := range aggregate.Aggregate(&stats, run(test, r)) {
		switch {
		case res.Error != nil:
			l := log.WithError(res.Error)
			if res.Time != time.Duration(0) {
				l = l.WithField("t", res.Time)
			}
			l.Error("Error")
		case res.Text != "":
			l := log.WithField("text", res.Text)
			if res.Time != time.Duration(0) {
				l = l.WithField("t", res.Time)
			}
			l.Info("Log")
		default:
			// log.WithField("t", res.Time).Debug("Metric")
		}
	}

	log.WithField("results", stats.Results).Info("Finished")
	log.WithFields(log.Fields{
		"min": stats.Time.Min,
		"max": stats.Time.Max,
		"med": stats.Time.Med,
		"avg": stats.Time.Avg,
	}).Info("Time")

	bucketInterval := time.Duration(100) * time.Millisecond
	buckets := [][]time.Duration{}
	for _, t := range stats.Time.Values {
		b := int(t / bucketInterval)
		for len(buckets) <= b {
			buckets = append(buckets, []time.Duration{})
		}
		buckets[b] = append(buckets[b], t)
	}
	for i, bucket := range buckets {
		log.WithFields(log.Fields{
			"i":   i,
			"len": len(bucket),
		}).Info("Bucket")
	}
}

// Configure the global logger.
func configureLogging(c *cli.Context) {
	if c.GlobalBool("verbose") {
		log.SetLevel(log.DebugLevel)
	}
}

func main() {
	// Free up -v and -h for our own flags
	cli.VersionFlag.Name = "version"
	cli.HelpFlag.Name = "help, ?"

	// Bootstrap using action-registered commandline flags
	app := cli.NewApp()
	app.Name = "speedboat"
	app.Usage = "A next-generation load generator"
	app.Version = "0.0.1a1"
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "verbose, v",
			Usage: "More verbose output",
		},
		cli.StringFlag{
			Name:  "script, s",
			Usage: "Script to run (do not use with --url)",
		},
		cli.StringFlag{
			Name:  "url",
			Usage: "URL to test (do not use with --script)",
		},
		cli.IntFlag{
			Name:  "vus, u",
			Usage: "Number of VUs to simulate",
			Value: 10,
		},
		cli.DurationFlag{
			Name:  "duration, d",
			Usage: "Test duration",
			Value: time.Duration(10) * time.Second,
		},
	}
	app.Before = func(c *cli.Context) error {
		configureLogging(c)
		return nil
	}
	app.Action = action
	app.Run(os.Args)
}
<commit_msg>Make this read like a pipeline<commit_after>package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	"github.com/loadimpact/speedboat/aggregate"
	"github.com/loadimpact/speedboat/loadtest"
	"github.com/loadimpact/speedboat/runner"
	"github.com/loadimpact/speedboat/runner/simple"
	"golang.org/x/net/context"
	"io/ioutil"
	"os"
	"path"
	"time"
)

func makeTest(c *cli.Context) (test loadtest.LoadTest, err error) {
	base := ""
	conf := loadtest.NewConfig()
	if len(c.Args()) > 0 {
		filename := c.Args()[0]
		base = path.Dir(filename)
		data, err := ioutil.ReadFile(filename)
		if err != nil {
			return test, err
		}

		loadtest.ParseConfig(data, &conf)
	}

	if c.IsSet("script") {
		conf.Script = c.String("script")
		base = ""
	}
	if c.IsSet("url") {
		conf.URL = c.String("url")
	}
	if c.IsSet("duration") {
		conf.Duration = c.Duration("duration").String()
	}
	if c.IsSet("vus") {
		conf.VUs = c.Int("vus")
	}

	test, err = conf.Compile()
	if err != nil {
		return test, err
	}

	if test.Script != "" {
		srcb, err := ioutil.ReadFile(path.Join(base, test.Script))
		if err != nil {
			return test, err
		}
		test.Source = string(srcb)
	}

	return test, nil
}

func run(test loadtest.LoadTest, r runner.Runner) <-chan runner.Result {
	ch := make(chan runner.Result)

	go func() {
		defer close(ch)

		timeout := time.Duration(0)
		for _, stage := range test.Stages {
			timeout += stage.Duration
		}

		ctx, _ := context.WithTimeout(context.Background(), timeout)
		scale := make(chan int, 1)
		scale <- test.Stages[0].VUs.Start

		for res := range runner.Run(ctx, r, scale) {
			ch <- res
		}
	}()

	return ch
}

func action(c *cli.Context) {
	test, err := makeTest(c)
	if err != nil {
		log.WithError(err).Fatal("Configuration error")
	}

	r := simple.New()
	r.URL = test.URL

	// Start the pipeline by just running requests
	pipeline := run(test, r)

	// Stick result aggregation onto it
	stats := aggregate.Stats{}
	stats.Time.Values = make([]time.Duration, 30000000)[:0]
	pipeline = aggregate.Aggregate(&stats, pipeline)

	for res := range pipeline {
		switch {
		case res.Error != nil:
			l := log.WithError(res.Error)
			if res.Time != time.Duration(0) {
				l = l.WithField("t", res.Time)
			}
			l.Error("Error")
		case res.Text != "":
			l := log.WithField("text", res.Text)
			if res.Time != time.Duration(0) {
				l = l.WithField("t", res.Time)
			}
			l.Info("Log")
		default:
			// log.WithField("t", res.Time).Debug("Metric")
		}
	}

	log.WithField("results", stats.Results).Info("Finished")
	log.WithFields(log.Fields{
		"min": stats.Time.Min,
		"max": stats.Time.Max,
		"med": stats.Time.Med,
		"avg": stats.Time.Avg,
	}).Info("Time")

	bucketInterval := time.Duration(100) * time.Millisecond
	buckets := [][]time.Duration{}
	for _, t := range stats.Time.Values {
		b := int(t / bucketInterval)
		for len(buckets) <= b {
			buckets = append(buckets, []time.Duration{})
		}
		buckets[b] = append(buckets[b], t)
	}
	for i, bucket := range buckets {
		log.WithFields(log.Fields{
			"i":   i,
			"len": len(bucket),
		}).Info("Bucket")
	}
}

// Configure the global logger.
func configureLogging(c *cli.Context) {
	if c.GlobalBool("verbose") {
		log.SetLevel(log.DebugLevel)
	}
}

func main() {
	// Free up -v and -h for our own flags
	cli.VersionFlag.Name = "version"
	cli.HelpFlag.Name = "help, ?"

	// Bootstrap using action-registered commandline flags
	app := cli.NewApp()
	app.Name = "speedboat"
	app.Usage = "A next-generation load generator"
	app.Version = "0.0.1a1"
	app.Flags = []cli.Flag{
		cli.BoolFlag{
			Name:  "verbose, v",
			Usage: "More verbose output",
		},
		cli.StringFlag{
			Name:  "script, s",
			Usage: "Script to run (do not use with --url)",
		},
		cli.StringFlag{
			Name:  "url",
			Usage: "URL to test (do not use with --script)",
		},
		cli.IntFlag{
			Name:  "vus, u",
			Usage: "Number of VUs to simulate",
			Value: 10,
		},
		cli.DurationFlag{
			Name:  "duration, d",
			Usage: "Test duration",
			Value: time.Duration(10) * time.Second,
		},
	}
	app.Before = func(c *cli.Context) error {
		configureLogging(c)
		return nil
	}
	app.Action = action
	app.Run(os.Args)
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net/http"
	"time"
)

//custom type
type timeHandler struct {
	format string
}

// ServeHTTP for custom time which makes it a handler
func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	tm := time.Now().Format(th.format)
	w.Write([]byte("The time is: " + tm))
}

func main() {
	mux := http.NewServeMux()

	rh := http.RedirectHandler("http://example.org", 307)
	mux.Handle("/foo", rh)

	//time
	th1123 := &timeHandler{format: time.RFC1123}
	mux.Handle("/time/rfc1123", th1123)

	//reuse!
	th3339 := &timeHandler{format: time.RFC3339}
	mux.Handle("/time/rfc3339", th3339)
	log.Println("Listening...")
	http.ListenAndServe(":3000", mux)
}
<commit_msg>One off hander method<commit_after>package main

import (
	"log"
	"net/http"
	"time"
)

//custom type
type timeHandler struct {
	format string
}

// ServeHTTP for custom time which makes it a handler
func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	tm := time.Now().Format(th.format)
	w.Write([]byte("The time is: " + tm))
}

//One off method handler
func timeHandlerOneOff(w http.ResponseWriter, r *http.Request) {
	tm := time.Now().Format(time.RFC1123)
	w.Write([]byte("The time is: " + tm))
}

func main() {
	mux := http.NewServeMux()

	rh := http.RedirectHandler("http://example.org", 307)
	mux.Handle("/foo", rh)

	//time
	th1123 := &timeHandler{format: time.RFC1123}
	mux.Handle("/time/rfc1123", th1123)

	//reuse!
	th3339 := &timeHandler{format: time.RFC3339}
	mux.Handle("/time/rfc3339", th3339)

	//one off method
	thOneOff := http.HandlerFunc(timeHandlerOneOff)
	mux.Handle("/time/oneoff", thOneOff)

	log.Println("Listening...")
	http.ListenAndServe(":3000", mux)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	//"net/http"
	//"log"
	"os"
	"strconv"
	"distribute"
	// "scrawler"
	// "segment"
)
// Can be run in 3 ways:
// 1) Master (e.g., go run main.go master localhost:7777)
// 2) Worker (e.g., go run main.go worker localhost:7777 localhost:7778 &)
// 3) Master or Worker (e.g., go run main.go worker sequential)
func main() {
	if len(os.Args) < 1 {
		fmt.Printf("%s: see usage comments in file\n", os.Args[0])
	}
	switch os.Args[1] {
	case "master":
		if len(os.Args) == 3 {
			distribute.RunMaster(os.Args[2])
		}
	case "worker":
		if len(os.Args) == 4 {
			distribute.RunWorker(os.Args[2], os.Args[3])
		}
	case "single":
		if len(os.Args) == 5 {
			threadNum, err1 := strconv.Atoi(os.Args[2])
			jobNum, err2 := strconv.Atoi(os.Args[3])
			if err1 != nil || err2 != nil {
				fmt.Printf("%s: see usage comments in file %d\n", os.Args[0], len(os.Args))
			}
			distribute.RunSingle(threadNum, jobNum, os.Args[4])
		} else {
			fmt.Printf("%s: see usage comments in file %d\n", os.Args[0], len(os.Args))
		}
	}
}

/*type StatisticsServer struct {
	port     string
}

func InitServer(port string) *StatisticsServer {
  s = &StatisticsServer{
		port:    port,
	}
  return s
}

func (s *StatisticsServer) Start() {
	go func () {
		log.Printf("STARTING REDISMQ SERVER ON PORT %s", s.port)
		err := http.ListenAndServe(":"+s.port, nil)
		if err != nil {
			log.Fatalf("REDISMQ SERVER SHUTTING DOWN [%s]\n\n", err.Error())
		}
	}()
}*/
<commit_msg>some example<commit_after>package main

import (
	"fmt"
	//"net/http"
	//"log"
	"os"
	"strconv"
	"distribute"
	// "scrawler"
	// "segment"
	"unsafe"
)
// Can be run in 3 ways:
// 1) Master (e.g., go run main.go master localhost:7777)
// 2) Worker (e.g., go run main.go worker localhost:7777 localhost:7778 &)
// 3) Master or Worker (e.g., go run main.go worker sequential)
func main() {
	if len(os.Args) < 2 {
		sz := 100
		p := make([]int, sz)
		p[2] = 5
		// INVALID: end points outside allocated space.
		// b := make([]byte, n)
		// end = unsafe.Pointer(uintptr(unsafe.Pointer(&b[0])) + uintptr(n))
		fmt.Printf("%s: see usage comments in file %d\n", os.Args[0], *(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&p[0])) + 2*unsafe.Sizeof(p[0]))))
		return
	}
	switch os.Args[1] {
	case "master":
		if len(os.Args) == 3 {
			distribute.RunMaster(os.Args[2])
		}
	case "worker":
		if len(os.Args) == 4 {
			distribute.RunWorker(os.Args[2], os.Args[3])
		}
	case "single":
		if len(os.Args) == 5 {
			threadNum, err1 := strconv.Atoi(os.Args[2])
			jobNum, err2 := strconv.Atoi(os.Args[3])
			if err1 != nil || err2 != nil {
				fmt.Printf("%s: see usage comments in file %d\n", os.Args[0], len(os.Args))
			}
			distribute.RunSingle(threadNum, jobNum, os.Args[4])
		} else {
			fmt.Printf("%s: see usage comments in file %d\n", os.Args[0], len(os.Args))
		}
	}
}

/*type StatisticsServer struct {
	port     string
}

func InitServer(port string) *StatisticsServer {
  s = &StatisticsServer{
		port:    port,
	}
  return s
}

func (s *StatisticsServer) Start() {
	go func () {
		log.Printf("STARTING REDISMQ SERVER ON PORT %s", s.port)
		err := http.ListenAndServe(":"+s.port, nil)
		if err != nil {
			log.Fatalf("REDISMQ SERVER SHUTTING DOWN [%s]\n\n", err.Error())
		}
	}()
}*/
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/omakoto/mlib"
	"github.com/omakoto/tabular/tabular"
)

var (
	useCsv             = flag.Bool("c", false, "input is CSV")
	useTsv             = flag.Bool("t", false, "input is TSV")
	useSpaceSeparated  = flag.Bool("s", true, "input is space-separated")
	useCustomSeparator = flag.String("p", "", "use regex as a separator")
)

func main() {
	flag.Parse()

	in := mlib.GetFilesReaderFromArgs()

	var reader <-chan []string
	switch {
	case *useCsv:
		reader = tabular.CsvReader(in)
	case *useTsv:
		reader = tabular.TsvReader(in)
	case *useSpaceSeparated:
		reader = tabular.RegexpSeparatedReader(in, `\s+`)
	case *useCustomSeparator != "":
		reader = tabular.RegexpSeparatedReader(in, *useCustomSeparator)
	}

	for l := range tabular.Tabular(reader) {
		fmt.Print(l)
		fmt.Print("\n")
	}
}
<commit_msg>Work as simple joiner for now<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/omakoto/mlib"
	"github.com/omakoto/tabular/tabular"
)

var (
	useCsv             = flag.Bool("c", false, "input is CSV")
	useTsv             = flag.Bool("t", false, "input is TSV")
	useSpaceSeparated  = flag.Bool("s", true, "input is space-separated")
	useCustomSeparator = flag.String("p", "", "use regex as a separator")
)

func main() {
	flag.Parse()

	in := mlib.GetFilesReaderFromArgs()

	var reader <-chan []string
	switch {
	case *useCsv:
		reader = tabular.CsvReader(in)
	case *useTsv:
		reader = tabular.TsvReader(in)
	case *useCustomSeparator != "":
		reader = tabular.RegexpSeparatedReader(in, *useCustomSeparator)
	case *useSpaceSeparated: // Default, check it last.
		reader = tabular.RegexpSeparatedReader(in, `\s+`)
	}

	for l := range tabular.Tabular(reader) {
		fmt.Print(l)
		fmt.Print("\n")
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"time"

	"github.com/0xAX/notificator"
	desktop "github.com/axet/desktop/go"
	"github.com/bradylove/concourse-monitor/assets"
)

var (
	teamName          = flag.String("team-name", "main", "team name to monitor")
	concourseURL      = flag.String("concourse-url", "", "url for concourse instance")
	refreshIntSeconds = flag.Int("refresh-interval", 15, "interval for pulling status from concourse")
)

func init() {
	flag.Parse()
}

func main() {
	if *concourseURL == "" {
		log.Fatalf("concourse-url cannot be empty")
	}

	refreshInt := time.Duration(*refreshIntSeconds) * time.Second

	notify := notificator.New(notificator.Options{
		DefaultIcon: assets.CCIconPath,
		AppName:     "Concourse Monitor",
	})
	cache := NewCache(notify)

	tray := desktop.DesktopSysTrayNew()
	icon := assets.Image("icons/cc_icon.png")

	menu := []desktop.Menu{
		desktop.Menu{Type: desktop.MenuItem, Enabled: false, Name: "Loading..."},
	}

	tray.SetIcon(icon)
	tray.SetTitle("Concourse")
	tray.SetMenu(menu)
	tray.Show()

	go func() {
		syncState(tray, cache)

		for range time.Tick(refreshInt) {
			syncState(tray, cache)
		}
	}()

	desktop.Main()
}

func openInBrowser(path string) desktop.MenuAction {
	return func(*desktop.Menu) {
		desktop.BrowserOpenURI(fmt.Sprint(*concourseURL, path))
	}
}

func syncState(tray *desktop.DesktopSysTray, cache *Cache) {
	client := NewConcourseClient(*concourseURL)

	pipelines, err := client.GetPipelines(*teamName)
	if err != nil {
		log.Println("Failed to fetch pipelines: %s", err)
	}

	cache.Update(pipelines)

	if len(pipelines) < 1 {
		menu := []desktop.Menu{
			desktop.Menu{Type: desktop.MenuItem, Enabled: false, Name: "No pipelines configured..."},
		}

		tray.SetMenu(menu)
		tray.Update()
		return
	}

	var menu []desktop.Menu
	for _, p := range pipelines {
		menu = append(menu, pipelineToMenu(p))
	}

	tray.SetMenu(menu)
	tray.Update()
}

func pipelineToMenu(p *Pipeline) desktop.Menu {
	return desktop.Menu{
		Type:    desktop.MenuItem,
		Enabled: true,
		Name:    p.Name,
		Menu:    jobsToMenus(p.Jobs),
		Action:  openInBrowser(p.URL),
		Icon:    p.StatusIcon(),
	}
}

func jobsToMenus(jobs []*Job) []desktop.Menu {
	var menu []desktop.Menu
	for _, j := range jobs {
		item := desktop.Menu{
			Type:    desktop.MenuItem,
			Icon:    j.StatusIcon(),
			Enabled: true,
			Name:    j.Name,
			Action:  openInBrowser(j.URL),
		}
		menu = append(menu, item)
	}

	return menu
}
<commit_msg>Add ability to run concourse monitor in the background<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"time"

	"github.com/0xAX/notificator"
	desktop "github.com/axet/desktop/go"
	"github.com/bradylove/concourse-monitor/assets"
)

var (
	teamName          = flag.String("team-name", "main", "team name to monitor")
	concourseURL      = flag.String("concourse-url", "", "url for concourse instance")
	refreshIntSeconds = flag.Int("refresh-interval", 15, "interval for pulling status from concourse")
	deamonize         = flag.Bool("d", false, "run concourse-monitor in the background")
)

func init() {
	flag.Parse()
}

func main() {
	if *concourseURL == "" {
		log.Fatalf("concourse-url cannot be empty")
	}

	if *deamonize {
		var args []string

		for _, a := range os.Args[1:] {
			if a != "-d" {
				args = append(args, a)
			}
		}

		fmt.Println("Starting concourse-monitor in the background")
		cmd := exec.Command(os.Args[0], args...)
		cmd.Start()

		os.Exit(0)
	}

	refreshInt := time.Duration(*refreshIntSeconds) * time.Second

	notify := notificator.New(notificator.Options{
		DefaultIcon: assets.CCIconPath,
		AppName:     "Concourse Monitor",
	})
	cache := NewCache(notify)

	tray := desktop.DesktopSysTrayNew()
	icon := assets.Image("icons/cc_icon.png")

	menu := []desktop.Menu{
		desktop.Menu{Type: desktop.MenuItem, Enabled: false, Name: "Loading..."},
	}

	tray.SetIcon(icon)
	tray.SetTitle("Concourse")
	tray.SetMenu(menu)
	tray.Show()

	go func() {
		syncState(tray, cache)

		for range time.Tick(refreshInt) {
			syncState(tray, cache)
		}
	}()

	desktop.Main()
}

func openInBrowser(path string) desktop.MenuAction {
	return func(*desktop.Menu) {
		desktop.BrowserOpenURI(fmt.Sprint(*concourseURL, path))
	}
}

func syncState(tray *desktop.DesktopSysTray, cache *Cache) {
	client := NewConcourseClient(*concourseURL)

	pipelines, err := client.GetPipelines(*teamName)
	if err != nil {
		log.Println("Failed to fetch pipelines: %s", err)
	}

	cache.Update(pipelines)

	if len(pipelines) < 1 {
		menu := []desktop.Menu{
			desktop.Menu{Type: desktop.MenuItem, Enabled: false, Name: "No pipelines configured..."},
		}

		tray.SetMenu(menu)
		tray.Update()
		return
	}

	var menu []desktop.Menu
	for _, p := range pipelines {
		menu = append(menu, pipelineToMenu(p))
	}

	tray.SetMenu(menu)
	tray.Update()
}

func pipelineToMenu(p *Pipeline) desktop.Menu {
	return desktop.Menu{
		Type:    desktop.MenuItem,
		Enabled: true,
		Name:    p.Name,
		Menu:    jobsToMenus(p.Jobs),
		Action:  openInBrowser(p.URL),
		Icon:    p.StatusIcon(),
	}
}

func jobsToMenus(jobs []*Job) []desktop.Menu {
	var menu []desktop.Menu
	for _, j := range jobs {
		item := desktop.Menu{
			Type:    desktop.MenuItem,
			Icon:    j.StatusIcon(),
			Enabled: true,
			Name:    j.Name,
			Action:  openInBrowser(j.URL),
		}
		menu = append(menu, item)
	}

	return menu
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/urfave/cli"
)

var (
	noop    = false
	build   = ""
	version = "dev-build"
)

type StdinScanner struct {
	*bufio.Scanner
	tee bool
}

func NewStdinScanner(tee bool) *StdinScanner {
	return &StdinScanner{bufio.NewScanner(os.Stdin), tee}
}

func (s *StdinScanner) StreamBytes() chan []byte {
	ch := make(chan []byte)
	s.Split(bufio.ScanBytes)
	go func() {
		for s.Scan() {
			b := s.Bytes()
			ch <- b
			if s.tee {
				fmt.Printf("%s", b)
			}
		}
		failOnError(s.Err(), "error reading input")
		close(ch)
	}()
	return ch
}

func (s *StdinScanner) StreamLines() chan string {
	ch := make(chan string)
	s.Split(bufio.ScanLines)
	go func() {
		for s.Scan() {
			ch <- s.Text()
			if s.tee {
				fmt.Println(s.Text())
			}
		}
		failOnError(s.Err(), "error reading input")
		close(ch)
	}()
	return ch
}

func writeTemp(byteCh chan []byte) string {
	tmp, err := ioutil.TempFile(os.TempDir(), "slackcat-")
	failOnError(err, "unable to create tmpfile")

	w := bufio.NewWriter(tmp)
	for b := range byteCh {
		_, err := w.Write(b)
		failOnError(err, "error writing to tmpfile")
	}
	w.Flush()

	return tmp.Name()
}

func handleUsageError(c *cli.Context, err error, _ bool) error {
	fmt.Fprintf(c.App.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
	cli.ShowAppHelp(c)
	return cli.NewExitError("", 1)
}

func printFullVersion(c *cli.Context) {
	fmt.Fprintf(c.App.Writer, "%v version %v, build %v\n", c.App.Name, c.App.Version, build)
}

func main() {
	cli.VersionPrinter = printFullVersion

	app := cli.NewApp()
	app.Name = "slackcat"
	app.Usage = "redirect a file to slack"
	app.Version = version
	app.OnUsageError = handleUsageError
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "channel, c",
			Usage: "Slack channel or group to post to",
		},
		cli.StringFlag{
			Name:  "comment",
			Usage: "Initial comment for snippet",
		},
		cli.BoolFlag{
			Name:  "configure",
			Usage: "Configure Slackcat via oauth",
		},
		cli.StringFlag{
			Name:  "filename, n",
			Usage: "Filename for upload. Defaults to current timestamp",
		},
		cli.StringFlag{
			Name:  "filetype",
			Usage: "Specify filetype for syntax highlighting",
		},
		cli.BoolFlag{
			Name:  "list",
			Usage: "List team channel names",
		},
		cli.BoolFlag{
			Name:  "noop",
			Usage: "Skip posting file to Slack. Useful for testing",
		},
		cli.BoolFlag{
			Name:  "stream, s",
			Usage: "Stream messages to Slack continuously instead of uploading a single snippet",
		},
		cli.BoolFlag{
			Name:  "tee, t",
			Usage: "Print stdin to screen before posting",
		},
		cli.StringFlag{
			Name:  "username, u",
			Usage: "Stream messages as given bot user. Defaults to auth user",
		},
		cli.StringFlag{
			Name:  "iconemoji, i",
			Usage: "Stream messages as given bot icon emoji. Defaults to auth user's icon",
		},
	}

	app.Action = func(c *cli.Context) {
		if c.Bool("configure") {
			configureOA()
			os.Exit(0)
		}

		configPath, exists := getConfigPath()
		if !exists {
			exitErr(fmt.Errorf("missing config file at %s\nuse --configure to create", configPath))
		}
		config := ReadConfig(configPath)

		team, channel, err := config.parseChannelOpt(c.String("channel"))
		failOnError(err)

		noop = c.Bool("noop")
		username := c.String("username")
		iconEmoji := c.String("iconemoji")
		fileName := c.String("filename")
		fileType := c.String("filetype")
		fileComment := c.String("comment")

		if c.Bool("list") {
			for teamName, token := range config.Teams {
				InitAPI(token)
				for _, n := range listChannels() {
					fmt.Printf("[%s] [channel]  %s\n", teamName, n)
				}
				for _, n := range listGroups() {
					fmt.Printf("[%s] [group]  %s\n", teamName, n)
				}
				for _, n := range listIms() {
					fmt.Printf("[%s] [im]  %s\n", teamName, n)
				}
			}
			os.Exit(0)
		}

		token := config.Teams[team]
		if token == "" {
			exitErr(fmt.Errorf("no such team: %s", team))
		}

		InitAPI(token)
		slackcat := newSlackcat(username, iconEmoji, channel)

		if len(c.Args()) > 0 {
			if c.Bool("stream") {
				output("filepath provided, ignoring stream option")
			}
			filePath := c.Args()[0]
			if fileName == "" {
				fileName = filepath.Base(filePath)
			}
			slackcat.postFile(filePath, fileName, fileType, fileComment)
			os.Exit(0)
		}

		scanner := NewStdinScanner(c.Bool("tee"))

		if c.Bool("stream") {
			slackcat.stream(scanner.StreamLines())
		} else {
			filePath := writeTemp(scanner.StreamBytes())
			defer os.Remove(filePath)
			slackcat.postFile(filePath, fileName, fileType, fileComment)
			os.Exit(0)
		}
	}

	app.Run(os.Args)

}
<commit_msg>add token opt to skip config<commit_after>package main

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"

	"github.com/urfave/cli"
)

var (
	noop    = false
	build   = ""
	version = "dev-build"
)

type StdinScanner struct {
	*bufio.Scanner
	tee bool
}

func NewStdinScanner(tee bool) *StdinScanner {
	return &StdinScanner{bufio.NewScanner(os.Stdin), tee}
}

func (s *StdinScanner) StreamBytes() chan []byte {
	ch := make(chan []byte)
	s.Split(bufio.ScanBytes)
	go func() {
		for s.Scan() {
			b := s.Bytes()
			ch <- b
			if s.tee {
				fmt.Printf("%s", b)
			}
		}
		failOnError(s.Err(), "error reading input")
		close(ch)
	}()
	return ch
}

func (s *StdinScanner) StreamLines() chan string {
	ch := make(chan string)
	s.Split(bufio.ScanLines)
	go func() {
		for s.Scan() {
			ch <- s.Text()
			if s.tee {
				fmt.Println(s.Text())
			}
		}
		failOnError(s.Err(), "error reading input")
		close(ch)
	}()
	return ch
}

func writeTemp(byteCh chan []byte) string {
	tmp, err := ioutil.TempFile(os.TempDir(), "slackcat-")
	failOnError(err, "unable to create tmpfile")

	w := bufio.NewWriter(tmp)
	for b := range byteCh {
		_, err := w.Write(b)
		failOnError(err, "error writing to tmpfile")
	}
	w.Flush()

	return tmp.Name()
}

func handleUsageError(c *cli.Context, err error, _ bool) error {
	fmt.Fprintf(c.App.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error())
	cli.ShowAppHelp(c)
	return cli.NewExitError("", 1)
}

func printFullVersion(c *cli.Context) {
	fmt.Fprintf(c.App.Writer, "%v version %v, build %v\n", c.App.Name, c.App.Version, build)
}

func main() {
	cli.VersionPrinter = printFullVersion

	app := cli.NewApp()
	app.Name = "slackcat"
	app.Usage = "redirect a file to slack"
	app.Version = version
	app.OnUsageError = handleUsageError
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "channel, c",
			Usage: "Slack channel or group to post to",
		},
		cli.StringFlag{
			Name:  "comment",
			Usage: "Initial comment for snippet",
		},
		cli.BoolFlag{
			Name:  "configure",
			Usage: "Configure Slackcat via oauth",
		},
		cli.StringFlag{
			Name:  "filename, n",
			Usage: "Filename for upload. Defaults to current timestamp",
		},
		cli.StringFlag{
			Name:  "filetype",
			Usage: "Specify filetype for syntax highlighting",
		},
		cli.BoolFlag{
			Name:  "list",
			Usage: "List team channel names",
		},
		cli.BoolFlag{
			Name:  "noop",
			Usage: "Skip posting file to Slack. Useful for testing",
		},
		cli.BoolFlag{
			Name:  "stream, s",
			Usage: "Stream messages to Slack continuously instead of uploading a single snippet",
		},
		cli.BoolFlag{
			Name:  "tee, t",
			Usage: "Print stdin to screen before posting",
		},
		cli.StringFlag{
			Name:  "token",
			Usage: "Optional Slack token to use, ignoring config file",
		},
		cli.StringFlag{
			Name:  "username, u",
			Usage: "Stream messages as given bot user. Defaults to auth user",
		},
		cli.StringFlag{
			Name:  "iconemoji, i",
			Usage: "Stream messages as given bot icon emoji. Defaults to auth user's icon",
		},
	}

	app.Action = func(c *cli.Context) {
		var config *Config

		if c.Bool("configure") {
			configureOA()
			os.Exit(0)
		}

		if c.String("token") != "" {
			config = &Config{
				Teams: map[string]string{
					"default": c.String("token"),
				},
				DefaultTeam: "default",
			}
		} else {
			configPath, exists := getConfigPath()
			if !exists {
				exitErr(fmt.Errorf("missing config file at %s\nuse --configure to create", configPath))
			}
			config = ReadConfig(configPath)
		}

		if c.Bool("list") {
			for teamName, token := range config.Teams {
				InitAPI(token)
				for _, n := range listChannels() {
					fmt.Printf("[%s] [channel]  %s\n", teamName, n)
				}
				for _, n := range listGroups() {
					fmt.Printf("[%s] [group]  %s\n", teamName, n)
				}
				for _, n := range listIms() {
					fmt.Printf("[%s] [im]  %s\n", teamName, n)
				}
			}
			os.Exit(0)
		}

		team, channel, err := config.parseChannelOpt(c.String("channel"))
		failOnError(err)

		noop = c.Bool("noop")
		username := c.String("username")
		iconEmoji := c.String("iconemoji")
		fileName := c.String("filename")
		fileType := c.String("filetype")
		fileComment := c.String("comment")

		token := config.Teams[team]
		if token == "" {
			exitErr(fmt.Errorf("no such team: %s", team))
		}

		InitAPI(token)
		slackcat := newSlackcat(username, iconEmoji, channel)

		if len(c.Args()) > 0 {
			if c.Bool("stream") {
				output("filepath provided, ignoring stream option")
			}
			filePath := c.Args()[0]
			if fileName == "" {
				fileName = filepath.Base(filePath)
			}
			slackcat.postFile(filePath, fileName, fileType, fileComment)
			os.Exit(0)
		}

		scanner := NewStdinScanner(c.Bool("tee"))

		if c.Bool("stream") {
			slackcat.stream(scanner.StreamLines())
		} else {
			filePath := writeTemp(scanner.StreamBytes())
			defer os.Remove(filePath)
			slackcat.postFile(filePath, fileName, fileType, fileComment)
			os.Exit(0)
		}
	}

	app.Run(os.Args)

}
<|endoftext|>


<commit_before>package main

// This software is an expense tracker i made to read the transactions exported from
// my bank account.

import (
	"flag"
	"fmt"
	"go-cli-bank/categories"
	"go-cli-bank/infrastructure"
	"go-cli-bank/reports"
	"os"

	docopt "github.com/docopt/docopt-go"
	// flag "github.com/ogier/pflag"
)

// CommandCreateCategory handles category creation command
func CommandCreateCategory(name string) error {

	dbHandler := infrastructure.DatabaseHandler{}
	cr := categories.CategoryRepository{DBHandler: &dbHandler}
	i := categories.Interactor{Repository: &cr}
	_, err := i.NewCategory(name)
	if err != nil {
		return err
	}

	return nil
}

// CommandShowReport handles report commands
func CommandShowReport(inputFilePath string) error {
	file, err := infrastructure.OpenFile(inputFilePath)
	if err != nil {
		return err
	}
	defer file.Close()

	err = reports.MonthlyReport(file)
	if err != nil {
		return err
	}

	return nil
}

func errorf(format string, args ...interface{}) {
	fmt.Fprintln(os.Stderr, format, args)
	os.Exit(2)
}

var usage = `Your command line money manager.

Usage:
	go-cli-bank -h | --help

Options:
	-h --help     Show this screen.`

func main() {

	arguments, _ := docopt.Parse(usage, nil, true, "Go CLI Bank 0.0.1", false)
	fmt.Println(arguments)

	inputFilePath := flag.String("load", "", "Specify the path to the input file")
	showReport := flag.Bool("report", false, "Show report")
	createCategory := flag.String("category", "", "Create category")
	flag.Parse()

	if *createCategory != "" {
		if err := CommandCreateCategory(*createCategory); err != nil {
			errorf("Failed to create category: %v\n", err)
		}
	}

	if *showReport {
		if err := CommandShowReport(*inputFilePath); err != nil {
			errorf("Failed to show report: %v\n", err)
		}
	}

}

// var DATABASE_NAME string = "./go-cli-bank.db"
// var DATABASE_ENGINE = "sqlite3"

// func toExcel(value decimal.Decimal, description string) {
// 	var file *xlsx.File
// 	var sheet *xlsx.Sheet
// 	var row *xlsx.Row
// 	var cell *xlsx.Cell
// 	var err error

// 	file = xlsx.NewFile()
// 	sheet, err = file.AddSheet("Sheet1")
// 	if err != nil {
// 		fmt.Printf(err.Error())
// 	}
// 	row = sheet.AddRow()
// 	cell = row.AddCell()
// 	cell.Value = description
// 	cell = row.AddCell()
// 	// cell.Value = strconv.FormatFloat(value, 'f', 2, 64)
// 	cell.Value = value.String()

// 	err = file.Save("MyXLSXFile.xlsx")
// 	if err != nil {
// 		fmt.Printf(err.Error())
// 	}
// }
<commit_msg>Uses docopt to parse report arguments<commit_after>package main

// This software is an expense tracker i made to read the transactions exported from
// my bank account.

import (
	"fmt"
	"go-cli-bank/categories"
	"go-cli-bank/infrastructure"
	"go-cli-bank/reports"
	"os"

	docopt "github.com/docopt/docopt-go"
	// flag "github.com/ogier/pflag"
)

// CommandCreateCategory handles category creation command
func CommandCreateCategory(name string) error {

	dbHandler := infrastructure.DatabaseHandler{}
	cr := categories.CategoryRepository{DBHandler: &dbHandler}
	i := categories.Interactor{Repository: &cr}
	_, err := i.NewCategory(name)
	if err != nil {
		return err
	}

	return nil
}

// CommandShowReport handles report commands
func CommandShowReport(inputFilePath string) error {
	file, err := infrastructure.OpenFile(inputFilePath)
	if err != nil {
		return err
	}
	defer file.Close()

	err = reports.MonthlyReport(file)
	if err != nil {
		return err
	}

	return nil
}

func errorf(format string, args ...interface{}) {
	fmt.Fprintln(os.Stderr, format, args)
	os.Exit(2)
}

var usage = `Your command line money manager.

Usage:
	go-cli-bank -h | --help
	go-cli-bank report --input <file>

Options:
	-f --input    The path to the transactions list.
	-h --help     Show this screen.
`

func main() {

	arguments, _ := docopt.Parse(usage, nil, true, "Go CLI Bank 0.0.1", false)

	// createCategory := arguments[]
	// inputFilePath := ""

	// inputFilePath := flag.String("load", "", "Specify the path to the input file")
	// showReport := flag.Bool("report", false, "Show report")
	// createCategory := flag.String("category", "", "Create category")
	// flag.Parse()

	// if createCategory {
	// 	if err := CommandCreateCategory(createCategory); err != nil {
	// 		errorf("Failed to create category: %v\n", err)
	// 	}
	// }

	if arguments["report"].(bool) {
		if err := CommandShowReport(arguments["<file>"].(string)); err != nil {
			errorf("Failed to show report: %v\n", err)
		}
	}

}

// var DATABASE_NAME string = "./go-cli-bank.db"
// var DATABASE_ENGINE = "sqlite3"

// func toExcel(value decimal.Decimal, description string) {
// 	var file *xlsx.File
// 	var sheet *xlsx.Sheet
// 	var row *xlsx.Row
// 	var cell *xlsx.Cell
// 	var err error

// 	file = xlsx.NewFile()
// 	sheet, err = file.AddSheet("Sheet1")
// 	if err != nil {
// 		fmt.Printf(err.Error())
// 	}
// 	row = sheet.AddRow()
// 	cell = row.AddCell()
// 	cell.Value = description
// 	cell = row.AddCell()
// 	// cell.Value = strconv.FormatFloat(value, 'f', 2, 64)
// 	cell.Value = value.String()

// 	err = file.Save("MyXLSXFile.xlsx")
// 	if err != nil {
// 		fmt.Printf(err.Error())
// 	}
// }
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"
)

const (
	version = "0.8.7"
)

func init() {
	flag.Usage = func() {
		h := "Usage:\n"
		h += "  bozr [OPTIONS] (DIR|FILE)\n\n"

		h += "Options:\n"
		h += "  -d, --debug		Enable debug mode\n"
		h += "  -H, --host		Server to test\n"
		h += "  -h, --help		Print usage\n"
		h += "  -i, --info		Enable info mode. Print request and response details.\n"
		h += "      --junit		Enable junit xml reporter\n"
		h += "  -v, --version		Print version information and quit\n\n"

		h += "Examples:\n"
		h += "  bozr ./examples\n"
		h += "  bozr -H http://example.com ./examples \n"

		fmt.Fprintf(os.Stderr, h)
	}
}

var (
	suiteDir    string
	hostFlag    string
	infoFlag    bool
	debugFlag   bool
	helpFlag    bool
	versionFlag bool
	junitFlag   bool

	info  *log.Logger
	debug *log.Logger
)

func initLogger() {
	infoHandler := ioutil.Discard
	debugHandler := ioutil.Discard

	if infoFlag {
		infoHandler = os.Stdout
	}

	if debugFlag {
		debugHandler = os.Stdout
	}

	info = log.New(infoHandler, "", 0)
	debug = log.New(debugHandler, "DEBUG: ", log.Ltime|log.Lshortfile)
}

func main() {
	flag.BoolVar(&debugFlag, "d", false, "Enable debug mode.")
	flag.BoolVar(&debugFlag, "debug", false, "Enable debug mode")

	flag.BoolVar(&infoFlag, "i", false, "Enable info mode. Print request and response details.")
	flag.BoolVar(&infoFlag, "info", false, "Enable info mode. Print request and response details.")

	flag.StringVar(&hostFlag, "H", "", "Test server address. Example: http://example.com/api.")

	flag.BoolVar(&helpFlag, "h", false, "Print usage")
	flag.BoolVar(&helpFlag, "help", false, "Print usage")

	flag.BoolVar(&versionFlag, "v", false, "Print version information and quit")
	flag.BoolVar(&versionFlag, "version", false, "Print version information and quit")

	flag.BoolVar(&junitFlag, "junit", false, "Enable junit xml reporter")

	flag.Parse()

	initLogger()

	if versionFlag {
		fmt.Println("bozr version " + version)
		return
	}

	if helpFlag {
		flag.Usage()
		return
	}

	if len(hostFlag) > 0 {
		_, err := url.ParseRequestURI(hostFlag)
		if err != nil {
			fmt.Println("Invalid host is specified.")
			os.Exit(1)
			return
		}
	}

	src := flag.Arg(0)

	if src == "" {
		fmt.Print("You must specify a directory or file with tests.\n\n")
		flag.Usage()
		os.Exit(1)
		return
	}

	// check specified source dir/file exists
	_, err := os.Lstat(src)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
		return
	}

	loader := NewDirLoader(src)

	reporters := []Reporter{NewConsoleReporter()}
	if junitFlag {
		path, _ := filepath.Abs("./report")
		reporters = append(reporters, NewJUnitReporter(path))
	}
	reporter := NewMultiReporter(reporters...)

	// test case runner?
	for suite := range loader {
		for _, testCase := range suite.Cases {

			result := TestResult{
				Suite: suite,
				Case:  testCase,
			}

			if testCase.Ignore != "" {
				result.Skipped = true
				result.SkippedMsg = testCase.Ignore
				reporter.Report(result)
				continue
			}

			rememberedMap := make(map[string]interface{})
			start := time.Now()
			for _, c := range testCase.Calls {
				addAll(c.Args, rememberedMap)
				terr := call(suite, testCase, c, rememberedMap)
				if terr != nil {
					result.Error = terr
					break
				}
			}

			result.Duration = time.Since(start)

			reporter.Report(result)
		}
	}

	reporter.Flush()
}

func addAll(src, target map[string]interface{}) {
	for key, val := range src {
		target[key] = val
	}
}

func call(testSuite TestSuite, testCase TestCase, call Call, rememberMap map[string]interface{}) *TError {
	debug.Printf("Starting call: %s - %s", testSuite.Name, testCase.Name)
	terr := &TError{}

	on := call.On

	dat := []byte(on.Body)
	if on.BodyFile != "" {
		uri, err := toAbsPath(testSuite.Dir, on.BodyFile)
		if err != nil {
			terr.Cause = err
			return terr
		}

		if d, err := ioutil.ReadFile(uri); err == nil {
			dat = d
		} else {
			terr.Cause = fmt.Errorf("Can't read body file: %s", err.Error())
			return terr
		}
	}

	req, err := populateRequest(on, string(dat), rememberMap)
	if err != nil {
		terr.Cause = err
		return terr
	}

	printRequestInfo(req, dat)

	client := &http.Client{}

	resp, err := client.Do(req)

	if err != nil {
		debug.Print("Error when sending request", err)
		terr.Cause = err
		return terr
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		debug.Print("Error reading response")
		terr.Cause = err
		return terr
	}

	testResp := Response{http: *resp, body: body}
	terr.Resp = testResp

	info.Println(strings.Repeat("-", 50))
	info.Println(testResp.ToString())
	info.Println("")

	exps, err := expectations(call, testSuite.Dir)
	if err != nil {
		terr.Cause = err
		return terr
	}

	for _, exp := range exps {
		checkErr := exp.check(testResp)
		if checkErr != nil {
			terr.Cause = checkErr
			return terr
		}
	}

	m, err := testResp.parseBody()
	if err != nil {
		debug.Print("Can't parse response body to Map for [remember]")
		terr.Cause = err
		return terr
	}

	err = rememberBody(m, call.Remember.Body, rememberMap)
	debug.Print("Remember: ", rememberMap)
	if err != nil {
		debug.Print("Error remember")
		terr.Cause = err
		return terr
	}

	rememberHeaders(testResp.http.Header, call.Remember.Headers, rememberMap)

	return nil
}

func populateRequest(on On, body string, rememberMap map[string]interface{}) (*http.Request, error) {

	urlStr, err := urlPrefix(populateRememberedVars(on.URL, rememberMap))
	if err != nil {
		return nil, errors.New("Cannot create request. Invalid url: " + on.URL)
	}

	body = populateRememberedVars(body, rememberMap)
	dat := []byte(body)

	req, err := http.NewRequest(on.Method, urlStr, bytes.NewBuffer(dat))
	if err != nil {
		return nil, err
	}

	for key, value := range on.Headers {
		req.Header.Add(key, populateRememberedVars(value, rememberMap))
	}

	q := req.URL.Query()
	for key, value := range on.Params {
		q.Add(key, populateRememberedVars(value, rememberMap))
	}
	req.URL.RawQuery = q.Encode()

	return req, nil
}

func urlPrefix(p string) (string, error) {
	if strings.HasPrefix(p, "http://") || strings.HasPrefix(p, "https://") {
		return p, nil
	}

	return concatURL(hostFlag, p)
}

func concatURL(base string, p string) (string, error) {
	baseURL, err := url.ParseRequestURI(base)
	if err != nil {
		return "", err
	}
	return baseURL.Scheme + "://" + baseURL.Host + path.Join(baseURL.Path, p), nil
}

func populateRememberedVars(str string, rememberMap map[string]interface{}) string {
	res := str
	for varName, val := range rememberMap {
		placeholder := "{" + varName + "}"
		res = strings.Replace(res, placeholder, toString(val), -1)
	}
	return res
}

// toString returns value suitable to insert as an argument
// if value if a float where decimal part is zero - convert to int
func toString(rw interface{}) string {
	var sv interface{} = rw
	if fv, ok := rw.(float64); ok {
		_, frac := math.Modf(fv)
		if frac == 0 {
			sv = int(fv)
		}
	}

	return fmt.Sprintf("%v", sv)
}

func expectations(call Call, srcDir string) ([]ResponseExpectation, error) {
	var exps []ResponseExpectation
	if call.Expect.StatusCode != 0 {
		exps = append(exps, StatusCodeExpectation{statusCode: call.Expect.StatusCode})
	}

	if call.Expect.hasSchema() {
		var (
			schemeURI string
			err       error
		)

		if call.Expect.BodySchemaFile != "" {
			schemeURI, err = toAbsPath(srcDir, call.Expect.BodySchemaFile)
			if err != nil {
				return nil, err
			}
			schemeURI = "file:///" + schemeURI
		}

		if call.Expect.BodySchemaURI != "" {
			isHTTP := strings.HasPrefix(call.Expect.BodySchemaURI, "http://")
			isHTTPS := strings.HasPrefix(call.Expect.BodySchemaURI, "https://")
			if !(isHTTP || isHTTPS) {
				schemeURI = hostFlag + call.Expect.BodySchemaURI
			} else {
				schemeURI = call.Expect.BodySchemaURI
			}
		}
		exps = append(exps, BodySchemaExpectation{schemaURI: schemeURI})
	}

	if len(call.Expect.Body) > 0 {
		exps = append(exps, BodyExpectation{pathExpectations: call.Expect.Body})
	}

	if len(call.Expect.Absent) > 0 {
		exps = append(exps, AbsentExpectation{paths: call.Expect.Absent})
	}

	if len(call.Expect.Headers) > 0 {
		for k, v := range call.Expect.Headers {
			exps = append(exps, HeaderExpectation{Name: k, Value: v})
		}
	}

	if call.Expect.ContentType != "" {
		exps = append(exps, ContentTypeExpectation{call.Expect.ContentType})
	}

	// and so on
	return exps, nil
}

func toAbsPath(srcDir string, assetPath string) (string, error) {
	if filepath.IsAbs(assetPath) {
		// ignore srcDir
		return assetPath, nil
	}

	uri, err := filepath.Abs(filepath.Join(suiteDir, srcDir, assetPath))
	if err != nil {
		return "", errors.New("Invalid file path: " + assetPath)
	}

	return filepath.ToSlash(uri), nil
}

func rememberBody(body interface{}, remember map[string]string, rememberedMap map[string]interface{}) (err error) {

	for varName, pathLine := range remember {

		if rememberVar, err := GetByPath(body, pathLine); err == nil {
			rememberedMap[varName] = rememberVar
		} else {
			strErr := fmt.Sprintf("Remembered value not found, path: %v", pathLine)
			err = errors.New(strErr)
		}
		//fmt.Printf("v: %v\n", getByPath(bodyMap, b...))
	}

	return err
}

func rememberHeaders(header http.Header, remember map[string]string, rememberedMap map[string]interface{}) {
	for valueName, headerName := range remember {
		value := header.Get(headerName)
		if value == "" {
			continue
		}

		rememberedMap[valueName] = value
	}
}

func printRequestInfo(req *http.Request, body []byte) {
	info.Println()
	info.Printf("%s %s %s\n", req.Method, req.URL.String(), req.Proto)

	if len(req.Header) > 0 {
		info.Println()
	}

	for k, v := range req.Header {
		info.Printf("%s: %s", k, strings.Join(v, " "))
	}
	info.Println()

	if len(body) > 0 {
		info.Printf(string(body))
	}
}
<commit_msg>increase version to 0.8.8<commit_after>package main

import (
	"bytes"
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"strings"
	"time"
)

const (
	version = "0.8.8"
)

func init() {
	flag.Usage = func() {
		h := "Usage:\n"
		h += "  bozr [OPTIONS] (DIR|FILE)\n\n"

		h += "Options:\n"
		h += "  -d, --debug		Enable debug mode\n"
		h += "  -H, --host		Server to test\n"
		h += "  -h, --help		Print usage\n"
		h += "  -i, --info		Enable info mode. Print request and response details.\n"
		h += "      --junit		Enable junit xml reporter\n"
		h += "  -v, --version		Print version information and quit\n\n"

		h += "Examples:\n"
		h += "  bozr ./examples\n"
		h += "  bozr -H http://example.com ./examples \n"

		fmt.Fprintf(os.Stderr, h)
	}
}

var (
	suiteDir    string
	hostFlag    string
	infoFlag    bool
	debugFlag   bool
	helpFlag    bool
	versionFlag bool
	junitFlag   bool

	info  *log.Logger
	debug *log.Logger
)

func initLogger() {
	infoHandler := ioutil.Discard
	debugHandler := ioutil.Discard

	if infoFlag {
		infoHandler = os.Stdout
	}

	if debugFlag {
		debugHandler = os.Stdout
	}

	info = log.New(infoHandler, "", 0)
	debug = log.New(debugHandler, "DEBUG: ", log.Ltime|log.Lshortfile)
}

func main() {
	flag.BoolVar(&debugFlag, "d", false, "Enable debug mode.")
	flag.BoolVar(&debugFlag, "debug", false, "Enable debug mode")

	flag.BoolVar(&infoFlag, "i", false, "Enable info mode. Print request and response details.")
	flag.BoolVar(&infoFlag, "info", false, "Enable info mode. Print request and response details.")

	flag.StringVar(&hostFlag, "H", "", "Test server address. Example: http://example.com/api.")

	flag.BoolVar(&helpFlag, "h", false, "Print usage")
	flag.BoolVar(&helpFlag, "help", false, "Print usage")

	flag.BoolVar(&versionFlag, "v", false, "Print version information and quit")
	flag.BoolVar(&versionFlag, "version", false, "Print version information and quit")

	flag.BoolVar(&junitFlag, "junit", false, "Enable junit xml reporter")

	flag.Parse()

	initLogger()

	if versionFlag {
		fmt.Println("bozr version " + version)
		return
	}

	if helpFlag {
		flag.Usage()
		return
	}

	if len(hostFlag) > 0 {
		_, err := url.ParseRequestURI(hostFlag)
		if err != nil {
			fmt.Println("Invalid host is specified.")
			os.Exit(1)
			return
		}
	}

	src := flag.Arg(0)

	if src == "" {
		fmt.Print("You must specify a directory or file with tests.\n\n")
		flag.Usage()
		os.Exit(1)
		return
	}

	// check specified source dir/file exists
	_, err := os.Lstat(src)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
		return
	}

	loader := NewDirLoader(src)

	reporters := []Reporter{NewConsoleReporter()}
	if junitFlag {
		path, _ := filepath.Abs("./report")
		reporters = append(reporters, NewJUnitReporter(path))
	}
	reporter := NewMultiReporter(reporters...)

	// test case runner?
	for suite := range loader {
		for _, testCase := range suite.Cases {

			result := TestResult{
				Suite: suite,
				Case:  testCase,
			}

			if testCase.Ignore != "" {
				result.Skipped = true
				result.SkippedMsg = testCase.Ignore
				reporter.Report(result)
				continue
			}

			rememberedMap := make(map[string]interface{})
			start := time.Now()
			for _, c := range testCase.Calls {
				addAll(c.Args, rememberedMap)
				terr := call(suite, testCase, c, rememberedMap)
				if terr != nil {
					result.Error = terr
					break
				}
			}

			result.Duration = time.Since(start)

			reporter.Report(result)
		}
	}

	reporter.Flush()
}

func addAll(src, target map[string]interface{}) {
	for key, val := range src {
		target[key] = val
	}
}

func call(testSuite TestSuite, testCase TestCase, call Call, rememberMap map[string]interface{}) *TError {
	debug.Printf("Starting call: %s - %s", testSuite.Name, testCase.Name)
	terr := &TError{}

	on := call.On

	dat := []byte(on.Body)
	if on.BodyFile != "" {
		uri, err := toAbsPath(testSuite.Dir, on.BodyFile)
		if err != nil {
			terr.Cause = err
			return terr
		}

		if d, err := ioutil.ReadFile(uri); err == nil {
			dat = d
		} else {
			terr.Cause = fmt.Errorf("Can't read body file: %s", err.Error())
			return terr
		}
	}

	req, err := populateRequest(on, string(dat), rememberMap)
	if err != nil {
		terr.Cause = err
		return terr
	}

	printRequestInfo(req, dat)

	client := &http.Client{}

	resp, err := client.Do(req)

	if err != nil {
		debug.Print("Error when sending request", err)
		terr.Cause = err
		return terr
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		debug.Print("Error reading response")
		terr.Cause = err
		return terr
	}

	testResp := Response{http: *resp, body: body}
	terr.Resp = testResp

	info.Println(strings.Repeat("-", 50))
	info.Println(testResp.ToString())
	info.Println("")

	exps, err := expectations(call, testSuite.Dir)
	if err != nil {
		terr.Cause = err
		return terr
	}

	for _, exp := range exps {
		checkErr := exp.check(testResp)
		if checkErr != nil {
			terr.Cause = checkErr
			return terr
		}
	}

	m, err := testResp.parseBody()
	if err != nil {
		debug.Print("Can't parse response body to Map for [remember]")
		terr.Cause = err
		return terr
	}

	err = rememberBody(m, call.Remember.Body, rememberMap)
	debug.Print("Remember: ", rememberMap)
	if err != nil {
		debug.Print("Error remember")
		terr.Cause = err
		return terr
	}

	rememberHeaders(testResp.http.Header, call.Remember.Headers, rememberMap)

	return nil
}

func populateRequest(on On, body string, rememberMap map[string]interface{}) (*http.Request, error) {

	urlStr, err := urlPrefix(populateRememberedVars(on.URL, rememberMap))
	if err != nil {
		return nil, errors.New("Cannot create request. Invalid url: " + on.URL)
	}

	body = populateRememberedVars(body, rememberMap)
	dat := []byte(body)

	req, err := http.NewRequest(on.Method, urlStr, bytes.NewBuffer(dat))
	if err != nil {
		return nil, err
	}

	for key, value := range on.Headers {
		req.Header.Add(key, populateRememberedVars(value, rememberMap))
	}

	q := req.URL.Query()
	for key, value := range on.Params {
		q.Add(key, populateRememberedVars(value, rememberMap))
	}
	req.URL.RawQuery = q.Encode()

	return req, nil
}

func urlPrefix(p string) (string, error) {
	if strings.HasPrefix(p, "http://") || strings.HasPrefix(p, "https://") {
		return p, nil
	}

	return concatURL(hostFlag, p)
}

func concatURL(base string, p string) (string, error) {
	baseURL, err := url.ParseRequestURI(base)
	if err != nil {
		return "", err
	}
	return baseURL.Scheme + "://" + baseURL.Host + path.Join(baseURL.Path, p), nil
}

func populateRememberedVars(str string, rememberMap map[string]interface{}) string {
	res := str
	for varName, val := range rememberMap {
		placeholder := "{" + varName + "}"
		res = strings.Replace(res, placeholder, toString(val), -1)
	}
	return res
}

// toString returns value suitable to insert as an argument
// if value if a float where decimal part is zero - convert to int
func toString(rw interface{}) string {
	var sv interface{} = rw
	if fv, ok := rw.(float64); ok {
		_, frac := math.Modf(fv)
		if frac == 0 {
			sv = int(fv)
		}
	}

	return fmt.Sprintf("%v", sv)
}

func expectations(call Call, srcDir string) ([]ResponseExpectation, error) {
	var exps []ResponseExpectation
	if call.Expect.StatusCode != 0 {
		exps = append(exps, StatusCodeExpectation{statusCode: call.Expect.StatusCode})
	}

	if call.Expect.hasSchema() {
		var (
			schemeURI string
			err       error
		)

		if call.Expect.BodySchemaFile != "" {
			schemeURI, err = toAbsPath(srcDir, call.Expect.BodySchemaFile)
			if err != nil {
				return nil, err
			}
			schemeURI = "file:///" + schemeURI
		}

		if call.Expect.BodySchemaURI != "" {
			isHTTP := strings.HasPrefix(call.Expect.BodySchemaURI, "http://")
			isHTTPS := strings.HasPrefix(call.Expect.BodySchemaURI, "https://")
			if !(isHTTP || isHTTPS) {
				schemeURI = hostFlag + call.Expect.BodySchemaURI
			} else {
				schemeURI = call.Expect.BodySchemaURI
			}
		}
		exps = append(exps, BodySchemaExpectation{schemaURI: schemeURI})
	}

	if len(call.Expect.Body) > 0 {
		exps = append(exps, BodyExpectation{pathExpectations: call.Expect.Body})
	}

	if len(call.Expect.Absent) > 0 {
		exps = append(exps, AbsentExpectation{paths: call.Expect.Absent})
	}

	if len(call.Expect.Headers) > 0 {
		for k, v := range call.Expect.Headers {
			exps = append(exps, HeaderExpectation{Name: k, Value: v})
		}
	}

	if call.Expect.ContentType != "" {
		exps = append(exps, ContentTypeExpectation{call.Expect.ContentType})
	}

	// and so on
	return exps, nil
}

func toAbsPath(srcDir string, assetPath string) (string, error) {
	if filepath.IsAbs(assetPath) {
		// ignore srcDir
		return assetPath, nil
	}

	uri, err := filepath.Abs(filepath.Join(suiteDir, srcDir, assetPath))
	if err != nil {
		return "", errors.New("Invalid file path: " + assetPath)
	}

	return filepath.ToSlash(uri), nil
}

func rememberBody(body interface{}, remember map[string]string, rememberedMap map[string]interface{}) (err error) {

	for varName, pathLine := range remember {

		if rememberVar, err := GetByPath(body, pathLine); err == nil {
			rememberedMap[varName] = rememberVar
		} else {
			strErr := fmt.Sprintf("Remembered value not found, path: %v", pathLine)
			err = errors.New(strErr)
		}
		//fmt.Printf("v: %v\n", getByPath(bodyMap, b...))
	}

	return err
}

func rememberHeaders(header http.Header, remember map[string]string, rememberedMap map[string]interface{}) {
	for valueName, headerName := range remember {
		value := header.Get(headerName)
		if value == "" {
			continue
		}

		rememberedMap[valueName] = value
	}
}

func printRequestInfo(req *http.Request, body []byte) {
	info.Println()
	info.Printf("%s %s %s\n", req.Method, req.URL.String(), req.Proto)

	if len(req.Header) > 0 {
		info.Println()
	}

	for k, v := range req.Header {
		info.Printf("%s: %s", k, strings.Join(v, " "))
	}
	info.Println()

	if len(body) > 0 {
		info.Printf(string(body))
	}
}
<|endoftext|>