Last active
March 1, 2020 05:17
-
-
Save techwraith/8276a3d788f2c767954b84fef511d5a7 to your computer and use it in GitHub Desktop.
A go module for parsing a todo list
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package todo | |
import ( | |
"encoding/json" | |
models "path/to/your/module/models" | |
"net/http" | |
) | |
// IndexHandler returns a todo list | |
func IndexHandler(w http.ResponseWriter, r *http.Request) { | |
params := r.URL.Query() | |
file := models.GetTodoListFile() | |
todos := models.ParseTodoList(file) | |
lists := []models.TodoList{} | |
if len(params["in_group"]) > 0 { | |
group := params["in_group"][0] | |
lists = append(lists, todos.InGroup(group)) | |
} | |
if len(params["has_been_delegated"]) > 0 { | |
choice := params["has_been_delegated"][0] | |
lists = append(lists, todos.HasBeenDelegated(choice)) | |
} | |
if len(params["delegated_to"]) > 0 { | |
delegate := params["delegated_to"][0] | |
lists = append(lists, todos.DelegatedTo(delegate)) | |
} | |
if len(params["due_on"]) > 0 { | |
due := params["due_on"][0] | |
lists = append(lists, todos.DueOn(due)) | |
} | |
if len(params["due_before"]) > 0 { | |
due := params["due_before"][0] | |
lists = append(lists, todos.DueBefore(due)) | |
} | |
if len(params["due_after"]) > 0 { | |
due := params["due_after"][0] | |
lists = append(lists, todos.DueAfter(due)) | |
} | |
if len(params["has_due_date"]) > 0 { | |
choice := params["has_due_date"][0] | |
lists = append(lists, todos.HasDueDate(choice)) | |
} | |
if len(params["is_done"]) > 0 { | |
done := params["is_done"][0] | |
lists = append(lists, todos.IsDone(done)) | |
} | |
if len(params["can_be_completed_in"]) > 0 { | |
duration := params["can_be_completed_in"][0] | |
lists = append(lists, todos.CanBeCompletedIn(duration)) | |
} | |
if len(params["has_duration"]) > 0 { | |
choice := params["has_duration"][0] | |
lists = append(lists, todos.HasDuration(choice)) | |
} | |
todos = todos.Intersection(lists) | |
js, err := json.Marshal(todos) | |
if err != nil { | |
http.Error(w, err.Error(), http.StatusInternalServerError) | |
return | |
} | |
w.Header().Set("Content-Type", "application/json") | |
w.Write(js) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package models | |
import ( | |
"encoding/json" | |
"io/ioutil" | |
"net/http" | |
"strings" | |
"time" | |
) | |
// TodoList type | |
type TodoList struct { | |
Todos []TodoListItem `json:"todo"` | |
} | |
// Intersection finds all todos in this list that are also in the passed in lists | |
func (t TodoList) Intersection(l []TodoList) TodoList { | |
list := []TodoListItem{} | |
hash := make(map[int]int) | |
for _, li := range l { | |
for _, todo := range li.Todos { | |
hash[todo.Index]++ | |
} | |
} | |
for _, todo := range t.Todos { | |
if hash[todo.Index] == len(l) { | |
list = append(list, todo) | |
} | |
} | |
return TodoList{list} | |
} | |
// InGroup method returns all tasks in a group | |
func (t TodoList) InGroup(g string) TodoList { | |
list := []TodoListItem{} | |
for _, todo := range t.Todos { | |
if todo.Group == g { | |
list = append(list, todo) | |
} | |
} | |
return TodoList{list} | |
} | |
// DelegatedTo method returns all tasks delegated to a specific person | |
func (t TodoList) DelegatedTo(d string) TodoList { | |
list := []TodoListItem{} | |
for _, todo := range t.Todos { | |
if todo.Delegate == d { | |
list = append(list, todo) | |
} | |
} | |
return TodoList{list} | |
} | |
// DueOn method returns all tasks due on a date | |
func (t TodoList) DueOn(d string) TodoList { | |
list := []TodoListItem{} | |
for _, todo := range t.Todos { | |
if fmtdate(todo.Due) == d { | |
list = append(list, todo) | |
} | |
} | |
return TodoList{list} | |
} | |
// DueBefore method returns all tasks due on or before a date | |
func (t TodoList) DueBefore(d string) TodoList { | |
list := []TodoListItem{} | |
date, _ := time.Parse(datefmt, d) | |
for _, todo := range t.Todos { | |
if todo.Due.Before(date) && !todo.Due.IsZero() { | |
list = append(list, todo) | |
} | |
} | |
return TodoList{list} | |
} | |
// DueAfter method returns all tasks due on or after a date | |
func (t TodoList) DueAfter(d string) TodoList { | |
list := []TodoListItem{} | |
date, _ := time.Parse(datefmt, d) | |
for _, todo := range t.Todos { | |
if todo.Due.After(date) && !todo.Due.IsZero() { | |
list = append(list, todo) | |
} | |
} | |
return TodoList{list} | |
} | |
// IsDone method returns all tasks that are done or returns all tasks that are not done | |
func (t TodoList) IsDone(d string) TodoList { | |
list := []TodoListItem{} | |
if d == "true" { | |
for _, todo := range t.Todos { | |
if todo.Done { | |
list = append(list, todo) | |
} | |
} | |
} else { | |
for _, todo := range t.Todos { | |
if !todo.Done { | |
list = append(list, todo) | |
} | |
} | |
} | |
return TodoList{list} | |
} | |
// CanBeCompletedIn method returns all tasks that will take less than or equal to the duration passed in | |
func (t TodoList) CanBeCompletedIn(d string) TodoList { | |
list := []TodoListItem{} | |
duration, _ := time.ParseDuration(d) | |
for _, todo := range t.Todos { | |
if todo.Duration <= duration && todo.Duration != 0 { | |
list = append(list, todo) | |
} | |
} | |
return TodoList{list} | |
} | |
// HasDuration method returns all tasks that have a duration | |
// or returns all tasks that do not have a duration | |
func (t TodoList) HasDuration(d string) TodoList { | |
list := []TodoListItem{} | |
if d == "false" { | |
for _, todo := range t.Todos { | |
if todo.Duration != 0 { | |
list = append(list, todo) | |
} | |
} | |
} else { | |
for _, todo := range t.Todos { | |
if todo.Duration > 0 { | |
list = append(list, todo) | |
} | |
} | |
} | |
return TodoList{list} | |
} | |
// HasBeenDelegated method returns all tasks that have been delegated | |
// or returns all tasks that have not been delegated | |
func (t TodoList) HasBeenDelegated(d string) TodoList { | |
list := []TodoListItem{} | |
if d == "true" { | |
for _, todo := range t.Todos { | |
if todo.Delegate != "" { | |
list = append(list, todo) | |
} | |
} | |
} else { | |
for _, todo := range t.Todos { | |
if todo.Delegate == "" { | |
list = append(list, todo) | |
} | |
} | |
} | |
return TodoList{list} | |
} | |
// HasDueDate method returns all tasks that have a due date | |
// or returns all tasks that have no due date | |
func (t TodoList) HasDueDate(d string) TodoList { | |
list := []TodoListItem{} | |
if d == "true" { | |
for _, todo := range t.Todos { | |
if !todo.Due.IsZero() { | |
list = append(list, todo) | |
} | |
} | |
} else { | |
for _, todo := range t.Todos { | |
if todo.Due.IsZero() { | |
list = append(list, todo) | |
} | |
} | |
} | |
return TodoList{list} | |
} | |
// TodoListItem type | |
type TodoListItem struct { | |
Title string `json:"title"` | |
Duration time.Duration `json:"duration,omitempty"` | |
Due time.Time `json:"due,omitempty"` | |
Done bool `json:"done"` | |
Delegate string `json:"delegate,omitempty"` | |
Group string `json:"group"` | |
Index int `json:"index"` | |
} | |
// you have to use a magic number in go for formatting date times | |
const datefmt = "2006-01-02" | |
func fmtdate(date time.Time) string { | |
if !date.IsZero() { | |
return date.Format(datefmt) | |
} | |
return "" | |
} | |
func fmtduration(dur time.Duration) string { | |
if dur != 0 { | |
return dur.String() | |
} | |
return "" | |
} | |
// getStringInBetween Returns empty string if no start string found | |
func getStringInBetween(str string, start string, end string) (result string) { | |
s := strings.Index(str, start) | |
if s == -1 { | |
return "" | |
} | |
s += len(start) | |
e := strings.Index(str, end) | |
return str[s:e] | |
} | |
/* Create a todo item | |
Sample line: - [ ] {CJ} <30m> :2019-05-20: | write Director of Platform Engineering job description | |
[ ] means that this isn't done yet | |
{CJ} means that it's been delegated to CJ | |
<30m> means that this is expected to take 30 minutes | |
:2019-05-20: means that this is due on that date | |
anything after the "|" is the todo item text | |
*/ | |
func createTodo(item string, group string, index int) TodoListItem { | |
title := "" | |
if strings.Contains(item, "| ") { | |
title = strings.Split(item, "| ")[1] | |
} else { | |
title = strings.Split(item, "] ")[1] | |
} | |
meta := strings.Split(item, "| ")[0] | |
duration := getStringInBetween(meta, " <", "> ") | |
duedate := getStringInBetween(meta, " :", ": ") | |
done := getStringInBetween(meta, " [", "] ") == "x" | |
delegate := getStringInBetween(meta, " {", "} ") | |
dur, _ := time.ParseDuration(duration) | |
date, _ := time.Parse(datefmt, duedate) | |
return TodoListItem{title, dur, date, done, delegate, group, index} | |
} | |
// GetTodoListFile gets a todo list text file (replace the URL with your todo's URL) | |
func GetTodoListFile() string { | |
const url = "https://example.com/path/to/todo.txt" | |
resp, _ := http.Get(url) | |
body, _ := ioutil.ReadAll(resp.Body) | |
return string(body) | |
} | |
// ParseTodoList takes a text file and turns it into json | |
func ParseTodoList(list string) TodoList { | |
todos := []TodoListItem{} | |
// Split the file on headers to group lists by header | |
items := strings.Split(list, "# ") | |
index := 0 | |
for i, group := range items { | |
if i == 0 { | |
continue | |
} else { | |
// Split the group on new lines to get the todos in each group | |
subitems := strings.Split(group, "\n") | |
// The first line is always the group name | |
groupName := subitems[0] | |
for n, todo := range subitems { | |
// Skip the group name and empty lines | |
if (n == 0) || (strings.Trim(todo, " ") == "") { | |
continue | |
} else { | |
index++ | |
t := createTodo(todo, groupName, index) | |
todos = append(todos, t) | |
} | |
} | |
} | |
} | |
return TodoList{todos} | |
} | |
// MarshalJSON override | |
func (d *TodoListItem) MarshalJSON() ([]byte, error) { | |
type Alias TodoListItem | |
return json.Marshal(&struct { | |
*Alias | |
Duration string `json:"duration,omitempty"` | |
Due string `json:"due,omitempty"` | |
}{ | |
Alias: (*Alias)(d), | |
Due: fmtdate(d.Due), | |
Duration: fmtduration(d.Duration), | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment