aboutsummaryrefslogblamecommitdiffstats
path: root/datamaps/config.go
blob: 590e07d48977e7c4c77ba235c3341d365820f98e (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
















                                     



                                                                                     














































                                                                                                     
                                    

























































































































































                                                                                      
                                                    















































                                                                                                            
package datamaps

import (
	"log"
	"os"
	"path/filepath"
)

const (
	configDirName = "datamaps"
	dbName        = "datamaps.db"
)

// Help text.
const Usage = `
usage: datamaps COMMAND [OPTIONS]

datamaps will NOT create configuration files automatically. Ensure this is handled by
calling: "datamaps setup". This will create the database file required to run the
application.

-Managing datamap files-

Command: datamap

All datamap related actions. Before datamaps can do much, it must know about a datamap.
A datamap starts life as a CSV file with the format:

cell_key,template_sheet,cell_reference,type
key1,sheet1,A10,TEXT
key2,sheet1,A11,NUMBER
...

Options:
	--import PATH		Import a datamap the csv datamap at PATH
	--datamapname NAME	Name for imported datamap
`

// mocking funcs in go https://stackoverflow.com/questions/19167970/mock-functions-in-go
// we only need the func signature to create the type. This is pretty weird, we're want to mock
// os.UserHomeDir so that we can set it to something like /tmp in our tests. Here we are creating
// two types: getUserConfigDir to represent the os.UserConfigDir function, dbPathChecker as a wrapper
// which which we can assign methods to that holds the value of the func os.UserConfigDir and the
// method, check(), which does the work, using the passed in func to determine the user $HOME/.config
// directory.
// Which is a lot of work for what it is, but it does make this testable and serves as an example
// of how things could be done in Go.

// getUserConfigDir allows replaces os.UserConfigDir
// for testing purposes.
type getUserConfigDir func() (string, error)

// DBPathChecker contains the func used to create the user config dir.
type DBPathChecker struct {
	userConfig getUserConfigDir
}

// NewDBPathChecker creates a DBPathChecker using whatever
// func you want as the argument, as long as it matches the
// type os.UserConfigDir. This makes it convenient for testing
// and was done as an experiment here to practice mocking in Go.
func NewDBPathChecker(h getUserConfigDir) *DBPathChecker {
	return &DBPathChecker{userConfig: h}
}

// Check returns true if the necessary config files (including
// the database) are in place - false if not
func (db *DBPathChecker) Check() bool {
	println("In Check() method")
	userConfig, err := db.userConfig()
	if err != nil {
		log.Fatal(err)
	}
	dbPath := filepath.Join(userConfig, "datamaps.db")
	if _, err := os.Stat(dbPath); os.IsNotExist(err) {
		return false
	}
	return true
}

// SetUp creates the config directory and requisite files
func SetUp() (string, error) {
	dir, err := os.UserConfigDir()
	if err != nil {
		return "", err
	}
	// check if config folder exists
	configPath := filepath.Join(dir, configDirName)
	dbPath := filepath.Join(configPath, dbName)
	if _, err := os.Stat(configPath); os.IsNotExist(err) {
		log.Println("Config directory does not exist.")
		log.Printf("Creating config directory %s.\n", configPath)
		if err := os.Mkdir(filepath.Join(dir, "datamaps"), 0700); err != nil {
			return "", err
		}
	} else {
		log.Println("Config directory found.")
	}
	if _, err := os.Stat(dbPath); os.IsNotExist(err) {
		log.Println("Database does not exist.")
		_, err = os.Create(dbPath)
		if err != nil {
			return "", err
		}
		log.Printf("Creating database file at %s.\n", dbPath)
		_, err := setupDB(dbPath)
		if err != nil {
			return "", err
		}
	} else {
		log.Println("Database file found.")
	}
	return dir, nil
}

func userConfigDir() (string, error) {
	dir, err := os.UserConfigDir()
	if err != nil {
		return "", err
	}

	configPath := filepath.Join(dir, configDirName)

	return configPath, nil
}

func defaultDMPath() (string, error) {
	dir, err := os.UserHomeDir()
	if err != nil {
		return "", err
	}

	return filepath.Join(dir, "Documents", "datamaps"), nil
}

func defaultXLSXPath() (string, error) {
	dir, err := os.UserHomeDir()
	if err != nil {
		return "", err
	}

	return filepath.Join(dir, "Documents", "datamaps", "import"), nil
}

// Options for the whole CLI application.
type Options struct {
	// Command is the main CLI sub-command (e.g. "datamap" handles all datamap
	// operations and the flags that follow pertain only to that operation.
	Command string

	// DBPath is the path to the database file.
	DBPath string

	// DMPath is the path to a datamap file.
	DMPath string

	// DMNane is the name of a datamap, whether setting or querying.
	DMName string

	// XLSXPath is the path to a directory containing ".xlsx" files for
	// importing.
	XLSXPath string

	// ReturnName is the name of a Return, whether setting or querying.
	ReturnName string

	// DMOverwrite is currently not used.
	DMOverwrite bool

	// DMInitial is currently not used.
	DMInitial bool

	// MasterOutPutPath is where the master.xlsx file is to be saved
	MasterOutPutPath string
}

func defaultOptions() *Options {
	dbpath, err := userConfigDir()
	if err != nil {
		log.Fatalf("Unable to get user config directory %v", err)
	}

	dmPath, err := defaultDMPath()
	if err != nil {
		log.Fatalf("unable to get default datamaps directory path: %v", err)
	}

	xlsxPath, err := defaultXLSXPath()
	if err != nil {
		log.Fatalf("unable to get default XLSX directory path: %v", err)
	}

	homeDir, err := os.UserHomeDir()
	if err != nil {
		log.Fatal("unable to get user home directory")
	}

	return &Options{
		Command:          "help",
		DBPath:           filepath.Join(dbpath, dbName),
		DMPath:           dmPath,
		DMName:           "Unnamed Datamap",
		XLSXPath:         xlsxPath,
		ReturnName:       "Unnamed Return",
		DMOverwrite:      false,
		DMInitial:        false,
		MasterOutPutPath: filepath.Join(homeDir, "Desktop"),
	}
}

// nextString get the next string in a slice.
func nextString(args []string, i *int, message string) string {
	if len(args) > *i+1 {
		*i++
	} else {
		log.Fatal(message)
	}

	return args[*i]
}

func processOptions(opts *Options, allArgs []string) {
	if len(allArgs) == 0 {
		allArgs = append(allArgs, "checkdb")
	}
	switch allArgs[0] {
	case "import":
		opts.Command = "import"
	case "help":
		opts.Command = "help"
	case "datamap":
		opts.Command = "datamap"
	case "setup":
		opts.Command = "setup"
	case "server":
		opts.Command = "server"
	case "createmaster":
		opts.Command = "createmaster"
	default:
		log.Fatal("No relevant command provided.")
	}

	restArgs := allArgs[1:]

	for i := 0; i < len(allArgs[1:]); i++ {
		arg := restArgs[i]
		switch arg {
		case "--xlsxpath":
			opts.XLSXPath = nextString(restArgs, &i, "xlsx directory path required")
		case "--returnname":
			opts.ReturnName = nextString(restArgs, &i, "return name required")
		case "--import":
			opts.DMPath = nextString(restArgs, &i, "import path required")
		case "--datamapname":
			opts.DMName = nextString(restArgs, &i, "datamap name required")
		case "--overwrite":
			opts.DMOverwrite = true
		case "--initial":
			opts.DMInitial = true
		case "--masteroutputdir":
			opts.MasterOutPutPath = nextString(restArgs, &i, "master output directory required")
		}
	}
}

//ParseOptions for CLI.
func ParseOptions() *Options {
	opts := defaultOptions()
	processOptions(opts, os.Args[1:])

	return opts
}