savestate.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. package itashi
  2. import (
  3. "bytes"
  4. "fmt"
  5. "log"
  6. "os"
  7. "strconv"
  8. "strings"
  9. "text/template"
  10. "time"
  11. tea "github.com/charmbracelet/bubbletea"
  12. )
  13. const SHELF_LINE_DELIM = "\n---- ---- ---- ----\n"
  14. const SHELF_COL_DELIM = " "
  15. const TIME_FORMAT = "2006-01-02T15:04:05 -07:00:00"
  16. const FS_SAVE_LOCATION = "./todo.ta"
  17. const SHELF_TEMPLATE = "{{.Id}} {{.Title}} {{.Desc}} {{.Due}} {{.Done}} {{.Priority}}"
  18. type Task struct {
  19. Id int
  20. Title string
  21. Desc string
  22. Due time.Time
  23. Done bool
  24. Priority int
  25. }
  26. func GetDefualtSave() string {
  27. return fmt.Sprintf("%s/.config/itashi/todo.ta", os.Getenv("HOME"))
  28. }
  29. // lets make Task implement the tea.Model interface
  30. func (t Task) Init() tea.Cmd {
  31. return nil
  32. }
  33. func (t Task) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
  34. return t, nil
  35. }
  36. func (t Task) View() string {
  37. return ""
  38. }
  39. type TaskShelf interface {
  40. // Modify the due date of an existing task
  41. ModifyDue(id int, due time.Time)
  42. // Modify the description field of an existing task
  43. ModifyDesc(id int, desc string)
  44. // Modify the priority of an existing task
  45. ModifyPriority(id int, pri int)
  46. // modify the title of an existing task
  47. ModifyTitle(id int, title string)
  48. // delete an existing task from the shelf
  49. DeleteTask(id int)
  50. // Mark a task as complete
  51. MarkDone(id int)
  52. // hopefully you dont need to call this! ;)
  53. ResetDone(id int)
  54. // Add a task to the shelf
  55. AddTask(title string, desc string, priority int, due time.Time)
  56. // Retrieve all tasks in the shelf
  57. GetAll() []Task
  58. // Render a task to a task template
  59. RenderTask(task Task) string
  60. }
  61. /*
  62. Retrieve all the tasks from the designated TaskShelf
  63. */
  64. func GetTaskList(taskio TaskShelf) []Task {
  65. return taskio.GetAll()
  66. }
  67. /*
  68. Grab all of the names of the tasks from the TaskShelf
  69. :param t: a list of Task structs
  70. :returns: A list of the task names
  71. */
  72. func GetTaskNames(t []Task) []string {
  73. var taskn []string
  74. for i := range t {
  75. taskn = append(taskn, t[i].Title)
  76. }
  77. return taskn
  78. }
  79. type FilesystemShelf struct {
  80. SaveLocation string
  81. Template *template.Template
  82. TaskTempl *template.Template
  83. Tasks []Task
  84. }
  85. func (t *FilesystemShelf) RenderTask(task Task) string {
  86. var bw bytes.Buffer
  87. err := t.TaskTempl.Execute(&bw, task)
  88. if err != nil {
  89. log.Fatal("Had a problem rendering this task.", task, err)
  90. }
  91. return bw.String()
  92. }
  93. /*
  94. Create a new filesystem shelf struct to reflect the filesystem shelf
  95. :param save: the save location to store the shelf in
  96. :returns: a pointer to a FilesystemShelf struct
  97. */
  98. func NewFilesystemShelf(save string) *FilesystemShelf {
  99. tmpl, err := template.New("task").Parse(SHELF_TEMPLATE)
  100. if err != nil {
  101. log.Fatal("Could not parse the shelf template! ", err)
  102. }
  103. tasktmpl, err := template.New("task").Parse(TASK_ITEM)
  104. if err != nil {
  105. log.Fatal("Couldnt parse task template. ", err)
  106. }
  107. shelf := &FilesystemShelf{
  108. SaveLocation: save,
  109. Template: tmpl,
  110. TaskTempl: tasktmpl,
  111. Tasks: []Task{},
  112. }
  113. shelf.Tasks = shelf.GetAll()
  114. return shelf
  115. }
  116. // Retrieve all the tasks from the filesystem shelf
  117. func (f *FilesystemShelf) GetAll() []Task {
  118. b, err := os.ReadFile(f.SaveLocation)
  119. if err != nil {
  120. log.Fatal(err)
  121. }
  122. return parseFilesystemShelf(b)
  123. }
  124. /*
  125. Add a task to the filesystem shelf
  126. :param title: the title to give the task
  127. :param desc: the description to give the task
  128. :param priority: the priority to give the task
  129. :param due: the due date for the task
  130. :returns: Nothing
  131. */
  132. func (f *FilesystemShelf) AddTask(title string, desc string, priority int, due time.Time) {
  133. var inc int
  134. inc = 0
  135. for i := range f.Tasks {
  136. if f.Tasks[i].Id > inc {
  137. inc = f.Tasks[i].Id
  138. }
  139. }
  140. inc += 1
  141. task := Task{
  142. Id: inc,
  143. Title: title,
  144. Desc: desc,
  145. Due: due,
  146. Done: false,
  147. Priority: priority,
  148. }
  149. f.Tasks = append(f.Tasks, task)
  150. err := os.WriteFile(f.SaveLocation, marshalTaskToShelf(f.Tasks, f.Template), os.ModePerm)
  151. if err != nil {
  152. log.Fatal("Need to fix later, error writing to fs ", err)
  153. }
  154. }
  155. // Boiler plate so i can implement later
  156. func (f *FilesystemShelf) ModifyDue(id int, due time.Time) {}
  157. func (f *FilesystemShelf) ModifyDesc(id int, desc string) {}
  158. func (f *FilesystemShelf) ModifyPriority(id int, pri int) {}
  159. func (f *FilesystemShelf) ModifyTitle(id int, title string) {}
  160. func (f *FilesystemShelf) DeleteTask(id int) {
  161. replTasks := []Task{}
  162. for i := range f.Tasks {
  163. if f.Tasks[i].Id == id {
  164. continue
  165. }
  166. replTasks = append(replTasks, f.Tasks[i])
  167. }
  168. os.WriteFile(f.SaveLocation, marshalTaskToShelf(replTasks, f.Template), os.ModePerm)
  169. }
  170. /*
  171. Mark task as done and write the shelf to disk. since the Tasks within FilesystemShelf are
  172. values and not pointers, we need to copy the entirety of the shelf over to a new set
  173. and write it, as opposed to just modifying the pointer and then writing.
  174. :param id: the ID of the task to mark as done
  175. :returns: Nothing
  176. */
  177. func (f *FilesystemShelf) MarkDone(id int) {
  178. replTasks := []Task{}
  179. for i := range f.Tasks {
  180. if f.Tasks[i].Id == id {
  181. doneTask := Task{
  182. Id: f.Tasks[i].Id,
  183. Title: f.Tasks[i].Title,
  184. Desc: f.Tasks[i].Desc,
  185. Due: f.Tasks[i].Due,
  186. Done: true,
  187. Priority: f.Tasks[i].Priority,
  188. }
  189. replTasks = append(replTasks, doneTask)
  190. continue
  191. }
  192. replTasks = append(replTasks, f.Tasks[i])
  193. }
  194. os.WriteFile(f.SaveLocation, marshalTaskToShelf(replTasks, f.Template), os.ModePerm)
  195. }
  196. func (f *FilesystemShelf) ResetDone(id int) {}
  197. // private function for parsing the byte stream from the filesystem
  198. func parseFilesystemShelf(data []byte) []Task {
  199. var filestring string
  200. filestring = string(data)
  201. items := strings.Split(filestring, SHELF_LINE_DELIM)
  202. var shelf []Task
  203. for i := range items {
  204. sect := strings.Split(items[i], SHELF_COL_DELIM)
  205. if len(sect) < 6 {
  206. continue
  207. }
  208. var id int
  209. var due time.Time
  210. var done bool
  211. var pri int
  212. var err error
  213. id, err = strconv.Atoi(sect[0])
  214. due, err = time.Parse(TIME_FORMAT, sect[3])
  215. done, err = strconv.ParseBool(sect[4])
  216. pri, err = strconv.Atoi(sect[5])
  217. if err != nil {
  218. log.Fatal("Couldnt parse from filesystem shelf", err)
  219. }
  220. shelf = append(shelf, Task{
  221. Id: id,
  222. Title: sect[1],
  223. Desc: sect[2],
  224. Due: due,
  225. Done: done,
  226. Priority: pri,
  227. })
  228. }
  229. return shelf
  230. }
  231. /*
  232. Helper function to marshal the tasks to the custom format
  233. :param tasks: an array of Task structs
  234. :returns: a byte array
  235. */
  236. func marshalTaskToShelf(tasks []Task, templ *template.Template) []byte {
  237. var bw bytes.Buffer
  238. for i := range tasks {
  239. err := templ.Execute(&bw, tasks[i])
  240. if err != nil {
  241. log.Fatal("Error parsing data into template: ", err)
  242. }
  243. // dynamically allocating, no need for the read delim
  244. _, err = bw.Write([]byte(SHELF_LINE_DELIM))
  245. if err != nil {
  246. log.Fatal("Error parsing data into template: ", err)
  247. }
  248. }
  249. return bw.Bytes()
  250. }