Skip to content

Instantly share code, notes, and snippets.

@techwraith
Last active March 1, 2020 05:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save techwraith/8276a3d788f2c767954b84fef511d5a7 to your computer and use it in GitHub Desktop.
Save techwraith/8276a3d788f2c767954b84fef511d5a7 to your computer and use it in GitHub Desktop.
A go module for parsing a todo list
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)
}
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