package itashi import ( "bytes" "fmt" "log" "os" "strconv" "strings" "text/template" "time" tea "github.com/charmbracelet/bubbletea" ) const SHELF_LINE_DELIM = "\n---- ---- ---- ----\n" const SHELF_COL_DELIM = " " const TIME_FORMAT = "2006-01-02T15:04:05 -07:00:00" const FS_SAVE_LOCATION = "./todo.ta" const SHELF_TEMPLATE = "{{.Id}} {{.Title}} {{.Desc}} {{.Due}} {{.Done}} {{.Priority}}" type Task struct { Id int Title string Desc string Due time.Time Done bool Priority int } func GetDefualtSave() string { return fmt.Sprintf("%s/.config/itashi/todo.ta", os.Getenv("HOME")) } // lets make Task implement the tea.Model interface func (t Task) Init() tea.Cmd { return nil } func (t Task) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return t, nil } func (t Task) View() string { return "" } type TaskShelf interface { // Modify the due date of an existing task ModifyDue(id int, due time.Time) // Modify the description field of an existing task ModifyDesc(id int, desc string) // Modify the priority of an existing task ModifyPriority(id int, pri int) // modify the title of an existing task ModifyTitle(id int, title string) // delete an existing task from the shelf DeleteTask(id int) // Mark a task as complete MarkDone(id int) // hopefully you dont need to call this! ;) ResetDone(id int) // Add a task to the shelf AddTask(t Task) // Retrieve all tasks in the shelf GetAll() []Task // Render a task to a task template RenderTask(task Task) string } /* Retrieve all the tasks from the designated TaskShelf */ func GetTaskList(taskio TaskShelf) []Task { return taskio.GetAll() } /* Grab all of the names of the tasks from the TaskShelf :param t: a list of Task structs :returns: A list of the task names */ func GetTaskNames(t []Task) []string { var taskn []string for i := range t { taskn = append(taskn, t[i].Title) } return taskn } type FilesystemShelf struct { SaveLocation string Template *template.Template TaskTempl *template.Template Tasks []Task } func (t *FilesystemShelf) RenderTask(task Task) string { var bw bytes.Buffer err := t.TaskTempl.Execute(&bw, task) if err != nil { log.Fatal("Had a problem rendering this task.", task, err) } return bw.String() } /* Create a new filesystem shelf struct to reflect the filesystem shelf :param save: the save location to store the shelf in :returns: a pointer to a FilesystemShelf struct */ func NewFilesystemShelf(save string) *FilesystemShelf { tmpl, err := template.New("task").Parse(SHELF_TEMPLATE) if err != nil { log.Fatal("Could not parse the shelf template! ", err) } tasktmpl, err := template.New("task").Parse(TASK_ITEM) if err != nil { log.Fatal("Couldnt parse task template. ", err) } shelf := &FilesystemShelf{ SaveLocation: save, Template: tmpl, TaskTempl: tasktmpl, Tasks: []Task{}, } shelf.Tasks = shelf.GetAll() return shelf } // Retrieve all the tasks from the filesystem shelf func (f *FilesystemShelf) GetAll() []Task { b, err := os.ReadFile(f.SaveLocation) if err != nil { log.Fatal(err) } return parseFilesystemShelf(b) } /* Add a task to the filesystem shelf :param t: the Task struct to add */ func (f *FilesystemShelf) AddTask(t Task) { f.Tasks = append(f.Tasks, t) err := os.WriteFile(f.SaveLocation, marshalTaskToShelf(f.Tasks, f.Template), os.ModePerm) if err != nil { log.Fatal("Need to fix later, error writing to fs ", err) } } // Boiler plate so i can implement later func (f *FilesystemShelf) ModifyDue(id int, due time.Time) {} func (f *FilesystemShelf) ModifyDesc(id int, desc string) {} func (f *FilesystemShelf) ModifyPriority(id int, pri int) {} func (f *FilesystemShelf) ModifyTitle(id int, title string) {} func (f *FilesystemShelf) DeleteTask(id int) {} func (f *FilesystemShelf) MarkDone(id int) {} func (f *FilesystemShelf) ResetDone(id int) {} // private function for parsing the byte stream from the filesystem func parseFilesystemShelf(data []byte) []Task { var filestring string filestring = string(data) items := strings.Split(filestring, SHELF_LINE_DELIM) var shelf []Task for i := range items { sect := strings.Split(items[i], SHELF_COL_DELIM) if len(sect) < 6 { continue } var id int var due time.Time var done bool var pri int var err error id, err = strconv.Atoi(sect[0]) due, err = time.Parse(TIME_FORMAT, sect[3]) done, err = strconv.ParseBool(sect[4]) pri, err = strconv.Atoi(sect[5]) if err != nil { log.Fatal("Couldnt parse from filesystem shelf", err) } shelf = append(shelf, Task{ Id: id, Title: sect[1], Desc: sect[2], Due: due, Done: done, Priority: pri, }) } return shelf } /* Helper function to marshal the tasks to the custom format :param tasks: an array of Task structs :returns: a byte array */ func marshalTaskToShelf(tasks []Task, templ *template.Template) []byte { var bw bytes.Buffer for i := range tasks { err := templ.Execute(&bw, tasks[i]) if err != nil { log.Fatal("Error parsing data into template: ", err) } // dynamically allocating, no need for the read delim _, err = bw.Write([]byte(SHELF_LINE_DELIM)) if err != nil { log.Fatal("Error parsing data into template: ", err) } } return bw.Bytes() }