From 2fb4545a7895c2c91e4b41633c8a0558c39244bf Mon Sep 17 00:00:00 2001 From: Brian Buller Date: Wed, 28 Mar 2018 08:34:01 -0500 Subject: [PATCH] Initial Commit --- .gitignore | 2 + app.go | 123 ++++++++++++++++++++++++++++++++++++++++++ helpers.go | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 25 +++++++++ model_app.go | 18 +++++++ model_list.go | 102 +++++++++++++++++++++++++++++++++++ model_task.go | 83 ++++++++++++++++++++++++++++ 7 files changed, 499 insertions(+) create mode 100644 .gitignore create mode 100644 app.go create mode 100644 helpers.go create mode 100644 main.go create mode 100644 model_app.go create mode 100644 model_list.go create mode 100644 model_task.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ee3b815 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Ignore the binary +gask diff --git a/app.go b/app.go new file mode 100644 index 0000000..61bc82c --- /dev/null +++ b/app.go @@ -0,0 +1,123 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "strings" + + "git.bullercodeworks.com/brian/gask" + userConfig "github.com/br0xen/user-config" +) + +// App holds everything we need to handle all application logic +type App struct { + Parms []Parameter + db *gask.GaskDB + cfg *userConfig.Config +} + +// Parameter is a cli parameter and it's description +type Parameter struct { + Flag string + Desc []string +} + +// NewApp creates the App object, which contains all application logic +func NewApp() *App { + var err error + + // Create the App + app := App{} + + // First we get the config + app.cfg, err = userConfig.NewConfig(AppName) + if err != nil { + fmt.Println("Creating new config") + app.cfg.Save() + } + wikifile := app.cfg.Get("wikifile") + // Try to open the db + if wikifile != "" { + // We've got a value, is it valid? + app.db, err = gask.NewGaskDB(wikifile) + for err != nil { + wikifile = "" + fmt.Println("Unable to open todos wiki file") + } + } else { + // No value for wikifile + wikifile = app.askForPath() + } + app.db, err = gask.NewGaskDB(wikifile) + if err != nil { + wikifile = "" + } + app.cfg.Set("wikifile", wikifile) + app.cfg.Save() + if wikifile == "" { + fmt.Println("Unable to open todos wiki file") + os.Exit(1) + } + err = app.CreateCliDb() + if err != nil { + fmt.Println("Unable to build CLI DB: " + err.Error()) + os.Exit(1) + } + + app.Parms = []Parameter{ + Parameter{Flag: "add", Desc: []string{"Add a task to an existing list"}}, + Parameter{Flag: "addlist", Desc: []string{"Add a list to the database"}}, + Parameter{Flag: "done", Desc: []string{"Mark a task done"}}, + Parameter{Flag: "help", Desc: []string{"Print this message"}}, + Parameter{Flag: "lists", Desc: []string{"Print Lists"}}, + Parameter{Flag: "ls", Desc: []string{"Print Tasks"}}, + } + + return &app +} + +func (a *App) Execute(args []string) error { + if len(args) == 0 { + args = append(args, "ls") + } + var opts []string + for i := range a.Parms { + opts = append(opts, a.Parms[i].Flag) + } + fnd := matchString(args[0], opts) + switch fnd { + + // Task cases + case "add": + return a.AddTask(args[1:]) + case "done": + return a.CompleteTask(args[1:]) + case "ls": // List tasks + return a.PrintTasks(args[1:]) + + // List cases + case "lists": // List lists + return a.PrintLists(args[1:]) + case "addlist": + return a.AddList(args[1:]) + case "help": + return a.PrintHelp(args[1:]) + } + return nil +} + +func (a *App) PrintHelp(args []string) error { + return nil +} + +// askForPath asks the user to enter the path to their todo wiki file +func (a *App) askForPath() string { + fmt.Println("Please specify the full path to the wiki file that contains your todo lists:") + fmt.Println("(Leave blank to exit)") + fmt.Print("> ") + reader := bufio.NewReader(os.Stdin) + wikifile, _ := reader.ReadString('\n') + wikifile = strings.TrimSpace(wikifile) + return wikifile +} diff --git a/helpers.go b/helpers.go new file mode 100644 index 0000000..8d24ee0 --- /dev/null +++ b/helpers.go @@ -0,0 +1,146 @@ +package main + +import ( + "errors" + "math" + "strings" + + "git.bullercodeworks.com/brian/gask" +) + +// matchString gets the closest match of 'ndl' in 'hay' +func matchString(ndl string, hay []string) string { + var nextParms []string + for i := range ndl { + for _, p := range hay { + if p[i] == ndl[i] { + nextParms = append(nextParms, p) + } + } + // If we get here and there is only one string left, return it + hay = nextParms + if len(nextParms) == 1 { + break + } + // Otherwise, loop + nextParms = []string{} + } + if len(hay) == 0 { + return "" + } + return hay[0] +} + +// matchStrings returns _all_ strings in hay that match ndl +func matchStrings(ndl string, hay []string) []string { + var nextParms []string + for i := range ndl { + for _, p := range hay { + if p[i] == ndl[i] { + nextParms = append(nextParms, p) + } + } + // If we get here and there is only one string left, return it + hay = nextParms + if len(nextParms) == 1 { + break + } + // Otherwise, loop + nextParms = []string{} + } + if len(hay) == 0 { + return []string{} + } + return hay +} + +func indexToAlpha(id int) string { + // We want 0 to be 'a' + id = id + 1 + + var ret string + d, r := id/26, id%26 + if r == 0 { + d, r = d-1, 26 + } + + if d > 26 { + return indexToAlpha(d) + string('a'+r-1) + } + + if d > 0 { + ret = string('a' + d - 1) + } + return ret + string('a'+r-1) +} + +func alphaToIndex(id string) int { + var j, ret int + for i := len(id) - 1; i >= 0; i-- { + ret += int(id[i]-'a'+1) * int(math.Pow(26, float64(j))) + j++ + } + ret-- + return ret +} + +// taskToAppString takes a task ID and a the task and returns the string ready to be output +func taskToAppString(stId string, gt *gask.GaskTask) string { + var ret string + pct := gt.GetPctComplete() + str := strings.Repeat(" ", gt.Depth) // Task depth + str += stId // Task ID + if pct == 1 { // % Complete + str += "[X]" + } else if pct > 0.67 { + ret += "[O]" + } else if pct > 0.34 { + ret += "[o]" + } else if pct > 0 { + ret += "[.]" + } else { + ret += "[ ]" + } + ret += gt.Description // Description + //tskCnt++ + //for _, st := range gt.Subtasks { + + //} + return ret +} + +// getListTaskId returns the task id for the given task in the given list +func getListTaskId(lst *gask.GaskList, tsk *gask.GaskTask) (int, error) { + var tskCnt int + for i := 0; i < lst.Tasks.Length(); i++ { + lt := lst.Tasks.Get(i) + if lt == tsk { + return tskCnt, nil + } + tskCnt++ + if lt.GetSubtaskCount() > 0 { + if stCnt, err := getSubtaskId(lt, tsk); err != nil { + return (tskCnt + stCnt), nil + } + tskCnt += lt.GetSubtaskCount() - 1 + } + } + return -1, errors.New("Couldn't find task") +} + +func getSubtaskId(tsk *gask.GaskTask, sub *gask.GaskTask) (int, error) { + var tskCnt int + for _, st := range tsk.Subtasks { + if &st == sub { + return tskCnt, nil + } + tskCnt++ + if st.GetSubtaskCount() > 0 { + if stCnt, err := getSubtaskId(&st, sub); err != nil { + return (tskCnt + stCnt), nil + } + tskCnt += st.GetSubtaskCount() - 1 + } + } + return -1, errors.New("Couldn't find subtask") +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..3655086 --- /dev/null +++ b/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "fmt" + "os" +) + +const AppName = "gask" + +var app *App + +func init() { + app = NewApp() +} + +func main() { + if app.db == nil { + os.Exit(1) + } + var err error + if err = app.Execute(os.Args[1:]); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/model_app.go b/model_app.go new file mode 100644 index 0000000..c211ded --- /dev/null +++ b/model_app.go @@ -0,0 +1,18 @@ +package main + +import "errors" + +type CliDB struct { + Lists []CliList +} + +// CreateCliDb builds the CLI App's DB +func (a *App) CreateCliDb() error { + if app.db == nil { + return errors.New("No GaskDB loaded") + } + + //var cliDb *CliDB + + return nil +} diff --git a/model_list.go b/model_list.go new file mode 100644 index 0000000..3c7165c --- /dev/null +++ b/model_list.go @@ -0,0 +1,102 @@ +package main + +import ( + "errors" + "fmt" + "unicode" + + "git.bullercodeworks.com/brian/gask" +) + +type CliList struct { + Id string // The CLI alpha-id of this list + Tasks []CliTask // The lists tasks + + dbList *gask.GaskList // The DB Backing for this list +} + +func NewCliList(gl *gask.GaskList) *CliList { + ret := new(CliList) + for i := 0; i < gl.Tasks.Length(); i++ { + ret.Tasks = append(ret.Tasks, *NewCliTask(gl.Tasks.Get(i))) + } + ret.dbList = gl + return ret +} + +func (cl *CliList) SetId(id string) { + cl.Id = id +} + +// GetList takes the id argument and returns the list for the id +// e.g. 'b10' would return list 'b' +func (a *App) GetList(id string) (*gask.GaskList, error) { + var lId string + for _, r := range id { + if !unicode.IsLetter(r) { + break + } + lId = lId + string(r) + } + // lId should now be the alpha-id of the list + if len(lId) == 0 { + return nil, errors.New("Invalid list id given: " + id) + } + idx := alphaToIndex(id) + if a.db.Lists.Length() < idx { + return nil, errors.New("Invalid list id given: " + id) + } + return a.db.Lists.Get(idx), nil +} + +// AddList adds a new list +func (a *App) AddList(args []string) error { + //a.db.AddList + return nil +} + +// PrintLists prints all list names with their alpha-ids +func (a *App) PrintLists(args []string) error { + if a.db.Lists.Length() == 0 { + return errors.New("No lists found") + } + matchedLists := a.db.Lists + if len(args) > 0 { + // Try to match the first argument to a list name + var allLists []string + for i := 0; i < a.db.Lists.Length(); i++ { + allLists = append(allLists, a.db.Lists.Get(i).Name) + } + mtch := matchStrings(args[0], allLists) + if len(mtch) > 0 { + // We matched a list, report the id and name + + } else { + } + } + if matchedLists.Length() == 0 { + return errors.New("No lists matched the search criteria") + } + for i := 0; i < matchedLists.Length(); i++ { + fmt.Println("(" + indexToAlpha(i+1) + ") " + a.db.Lists.Get(i).Name) + } + return nil +} + +// PrintList prints the lId-th list in the Todos file +func (a *App) PrintList(lId string) error { + listId := 0 + for i := range lId { + listId += int(lId[i]) - int('a') + } + if listId < 0 || listId >= a.db.Lists.Length() { + return errors.New("Invalid list id") + } + + lst := a.db.Lists.Get(listId) + fmt.Println(lId, lst.Name) + for i := 0; i < lst.Tasks.Length(); i++ { + a.PrintTask(lId, i) + } + return nil +} diff --git a/model_task.go b/model_task.go new file mode 100644 index 0000000..443bd47 --- /dev/null +++ b/model_task.go @@ -0,0 +1,83 @@ +package main + +import ( + "errors" + "fmt" + "strconv" + + "git.bullercodeworks.com/brian/gask" +) + +type CliTask struct { + Id string // This tasks full id (alpha-numeric) + ParentId string + Description string + IsDone bool + Depth int + Subtasks []CliTask // The Task's subtasks + + dbTask *gask.GaskTask // The DB Backing for this task +} + +func NewCliTask(gt *gask.GaskTask) *CliTask { + ret := new(CliTask) + ret.Description = gt.Description + ret.IsDone = gt.IsDone + ret.Depth = gt.Depth + for i := 0; i < len(gt.Subtasks); i++ { + ret.Subtasks = append(ret.Subtasks, *NewCliTask(>.Subtasks[i])) + } + ret.dbTask = gt + return ret +} + +// GetTask takes the id argument and returns the task for the id +// e.g. 'b10' would return the 11th (0-indexed) task from list 'b' +func (a *App) GetTask(id string) (*gask.GaskTask, error) { + var err error + var lst *gask.GaskList + if lst, err = a.GetList(id); err != nil { + return nil, err + } + var ret *gask.GaskTask + _ = lst + return ret, errors.New("Couldn't find task") +} + +// AddTask adds a task +func (a *App) AddTask(args []string) error { + + return nil +} + +// CompleteTasks marks a task as complete +func (a *App) CompleteTask(args []string) error { + return nil +} + +// PrintTasks lists tasks in the db +func (a *App) PrintTasks(args []string) error { + var err error + if len(args) > 0 { + if err = a.PrintList(args[0]); err != nil { + return err + } + } else { + // Just list everything + for lstId := 0; lstId < a.db.Lists.Length(); lstId++ { + var tskCnt int + lstAlphaId := indexToAlpha(lstId) + fmt.Println("@" + lstAlphaId + " " + a.db.Lists.Get(lstId).Name) + for i := 0; i < a.db.Lists.Get(lstId).Tasks.Length(); i++ { + tskId := "@" + lstAlphaId + strconv.Itoa(tskCnt) + fmt.Println(taskToAppString(tskId, a.db.Lists.Get(lstId).Tasks.Get(i))) + } + } + } + return nil +} + +// printTask prints the tId-th task in list lId with all of it's detail +func (a *App) PrintTask(lId string, tId int) error { + return nil +}