savestate.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  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) string
  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. // Clean the shelf of all completed tasks
  61. Clean() int
  62. }
  63. /*
  64. Retrieve all the tasks from the designated TaskShelf
  65. */
  66. func GetTaskList(taskio TaskShelf) []Task {
  67. return taskio.GetAll()
  68. }
  69. /*
  70. Grab all of the names of the tasks from the TaskShelf
  71. :param t: a list of Task structs
  72. :returns: A list of the task names
  73. */
  74. func GetTaskNames(t []Task) []string {
  75. var taskn []string
  76. for i := range t {
  77. taskn = append(taskn, t[i].Title)
  78. }
  79. return taskn
  80. }
  81. type FilesystemShelf struct {
  82. SaveLocation string
  83. Template *template.Template
  84. TaskTempl *template.Template
  85. Tasks []Task
  86. }
  87. func (t *FilesystemShelf) RenderTask(task Task) string {
  88. var bw bytes.Buffer
  89. err := t.TaskTempl.Execute(&bw, task)
  90. if err != nil {
  91. log.Fatal("Had a problem rendering this task.", task, err)
  92. }
  93. return bw.String()
  94. }
  95. /*
  96. Create a new filesystem shelf struct to reflect the filesystem shelf
  97. :param save: the save location to store the shelf in
  98. :returns: a pointer to a FilesystemShelf struct
  99. */
  100. func NewFilesystemShelf(save string) *FilesystemShelf {
  101. tmpl, err := template.New("task").Parse(SHELF_TEMPLATE)
  102. if err != nil {
  103. log.Fatal("Could not parse the shelf template! ", err)
  104. }
  105. tasktmpl, err := template.New("task").Parse(TASK_ITEM)
  106. if err != nil {
  107. log.Fatal("Couldnt parse task template. ", err)
  108. }
  109. shelf := &FilesystemShelf{
  110. SaveLocation: save,
  111. Template: tmpl,
  112. TaskTempl: tasktmpl,
  113. Tasks: []Task{},
  114. }
  115. shelf.Tasks = shelf.GetAll()
  116. return shelf
  117. }
  118. // Retrieve all the tasks from the filesystem shelf
  119. func (f *FilesystemShelf) GetAll() []Task {
  120. b, err := os.ReadFile(f.SaveLocation)
  121. if err != nil {
  122. log.Fatal(err)
  123. }
  124. return parseFilesystemShelf(b)
  125. }
  126. /*
  127. Add a task to the filesystem shelf
  128. :param title: the title to give the task
  129. :param desc: the description to give the task
  130. :param priority: the priority to give the task
  131. :param due: the due date for the task
  132. :returns: Nothing
  133. */
  134. func (f *FilesystemShelf) AddTask(title string, desc string, priority int, due time.Time) {
  135. var inc int
  136. inc = 0
  137. for i := range f.Tasks {
  138. if f.Tasks[i].Id > inc {
  139. inc = f.Tasks[i].Id
  140. }
  141. }
  142. inc += 1
  143. task := Task{
  144. Id: inc,
  145. Title: title,
  146. Desc: desc,
  147. Due: due,
  148. Done: false,
  149. Priority: priority,
  150. }
  151. f.Tasks = append(f.Tasks, task)
  152. err := os.WriteFile(f.SaveLocation, marshalTaskToShelf(f.Tasks, f.Template), os.ModePerm)
  153. if err != nil {
  154. log.Fatal("Need to fix later, error writing to fs ", err)
  155. }
  156. }
  157. // Boiler plate so i can implement later
  158. func (f *FilesystemShelf) ModifyDue(id int, due time.Time) {}
  159. func (f *FilesystemShelf) ModifyDesc(id int, desc string) {}
  160. func (f *FilesystemShelf) ModifyPriority(id int, pri int) {}
  161. func (f *FilesystemShelf) ModifyTitle(id int, title string) {}
  162. func (f *FilesystemShelf) DeleteTask(id int) {
  163. replTasks := []Task{}
  164. for i := range f.Tasks {
  165. if f.Tasks[i].Id == id {
  166. continue
  167. }
  168. replTasks = append(replTasks, f.Tasks[i])
  169. }
  170. os.WriteFile(f.SaveLocation, marshalTaskToShelf(replTasks, f.Template), os.ModePerm)
  171. }
  172. // Clean the filesystem shelf of all completed tasks
  173. func (f *FilesystemShelf) Clean() int {
  174. replTasks := []Task{}
  175. var cleaned int
  176. cleaned = 0
  177. for i := range f.Tasks {
  178. if f.Tasks[i].Done {
  179. cleaned += 1
  180. continue
  181. }
  182. replTasks = append(replTasks, f.Tasks[i])
  183. }
  184. os.WriteFile(f.SaveLocation, marshalTaskToShelf(replTasks, f.Template), os.ModePerm)
  185. return cleaned
  186. }
  187. /*
  188. Mark task as done and write the shelf to disk. since the Tasks within FilesystemShelf are
  189. values and not pointers, we need to copy the entirety of the shelf over to a new set
  190. and write it, as opposed to just modifying the pointer and then writing.
  191. :param id: the ID of the task to mark as done
  192. :returns: Nothing
  193. */
  194. func (f *FilesystemShelf) MarkDone(id int) string {
  195. replTasks := []Task{}
  196. var taskName string
  197. for i := range f.Tasks {
  198. if f.Tasks[i].Id == id {
  199. replTasks = append(replTasks, Task{
  200. Id: f.Tasks[i].Id,
  201. Title: f.Tasks[i].Title,
  202. Desc: f.Tasks[i].Desc,
  203. Due: f.Tasks[i].Due,
  204. Done: true,
  205. Priority: f.Tasks[i].Priority,
  206. })
  207. taskName = f.Tasks[i].Title
  208. continue
  209. }
  210. replTasks = append(replTasks, f.Tasks[i])
  211. }
  212. os.WriteFile(f.SaveLocation, marshalTaskToShelf(replTasks, f.Template), os.ModePerm)
  213. return taskName
  214. }
  215. func (f *FilesystemShelf) ResetDone(id int) {}
  216. // private function for parsing the byte stream from the filesystem
  217. func parseFilesystemShelf(data []byte) []Task {
  218. var filestring string
  219. filestring = string(data)
  220. items := strings.Split(filestring, SHELF_LINE_DELIM)
  221. var shelf []Task
  222. for i := range items {
  223. sect := strings.Split(items[i], SHELF_COL_DELIM)
  224. if len(sect) < 6 {
  225. continue
  226. }
  227. var id int
  228. var due time.Time
  229. var done bool
  230. var pri int
  231. var err error
  232. id, err = strconv.Atoi(sect[0])
  233. due, err = time.Parse(TIME_FORMAT, sect[3])
  234. done, err = strconv.ParseBool(sect[4])
  235. pri, err = strconv.Atoi(sect[5])
  236. if err != nil {
  237. log.Fatal("Couldnt parse from filesystem shelf", err)
  238. }
  239. shelf = append(shelf, Task{
  240. Id: id,
  241. Title: sect[1],
  242. Desc: sect[2],
  243. Due: due,
  244. Done: done,
  245. Priority: pri,
  246. })
  247. }
  248. return shelf
  249. }
  250. /*
  251. Helper function to marshal the tasks to the custom format
  252. :param tasks: an array of Task structs
  253. :returns: a byte array
  254. */
  255. func marshalTaskToShelf(tasks []Task, templ *template.Template) []byte {
  256. var bw bytes.Buffer
  257. for i := range tasks {
  258. err := templ.Execute(&bw, tasks[i])
  259. if err != nil {
  260. log.Fatal("Error parsing data into template: ", err)
  261. }
  262. // dynamically allocating, no need for the read delim
  263. _, err = bw.Write([]byte(SHELF_LINE_DELIM))
  264. if err != nil {
  265. log.Fatal("Error parsing data into template: ", err)
  266. }
  267. }
  268. return bw.Bytes()
  269. }