go/beryl.go

241 lines
4.7 KiB
Go

package beryl
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"regexp"
"strings"
)
type Task struct {
Title string
Tags map[string]string
Completed bool
Comment string
SubTasks []*Task
}
func ParseTask(input string) (*Task, error) {
var err error
task := Task{}
text := strings.Trim(input, "\t")
if text[0:6] == "- [ ] " {
task.Completed = false
}
if text[0:6] == "- [x] " {
task.Completed = true
}
task.Title = text[6:]
r := regexp.MustCompile(`((\w+):(\w+))`)
rSpace := regexp.MustCompile(` ((\w+):(\w+))`)
tags := r.FindAllString(task.Title, -1)
task.Title = rSpace.ReplaceAllString(task.Title, "")
task.Tags = make(map[string]string)
for _, element := range tags {
splits := strings.Split(element, ":")
task.Tags[splits[0]] = splits[1]
}
return &task, err
}
func Tabs(num int) string {
return strings.Repeat("\t", num)
}
func CountTabs(input string) int {
// this will also count any tabs that are in the task body itself
// ideally this would only count consecutave tabs before the first non-tab charicter
// for char, idx := range input {
//
// }
return strings.Count(input, "\t")
}
func IsTask(input string, numIndent int) bool {
noCheck := Tabs(numIndent) + "- [ ] "
check := Tabs(numIndent) + "- [x] "
hasUncheck := input[0:len(noCheck)] == noCheck
hasCheck := input[0:len(check)] == check
return hasUncheck || hasCheck
}
// -1 error, 0 task, 1 comment, 2 subtask
func Identify(input string, numIndent int) int {
actualIndent := CountTabs(input)
if IsTask(input, actualIndent) {
return 0
}
// if IsTask(input, numIndent+1) {
// return 0
// }
//
// // cant check negatively tabbed tasks
// if numIndent > 0 {
// if IsTask(input, numIndent-1) {
// return 0
// }
// }
tabs := Tabs(numIndent + 1)
if input[0:len(tabs)] == tabs {
return 1
}
return -1
}
func Parse(file *os.File) ([]*Task, error) {
var rootTask = Task{}
lastTasks := stack[*Task]{}
lastTasks.Push(&rootTask)
currentIndent := 0
scanner := bufio.NewScanner(file)
for scanner.Scan() {
// we want whitespace to get indent level
// text := strings.Trim(scanner.Text(), "\t\n\r ") // Read line
text := scanner.Text()
// Ignore blank
if text == "" {
continue
}
//determine current index
//update index as needed
diff := CountTabs(text) - currentIndent
ident := Identify(text, currentIndent)
switch ident {
case 0:
task, err := ParseTask(text)
if err != nil {
return nil, err
}
if diff < 0 {
absDiff := 0 - diff
for i := 0; i < absDiff+1; i++ {
lastTasks.Pop()
}
// if there is one or more in stack
lastTasks.Push(task)
// if len(lastTasks) >= 1 {
// add current as subtask of it
lastTasks.SecondToLast().SubTasks = append(lastTasks.SecondToLast().SubTasks, task)
currentIndent = currentIndent - absDiff
} else if diff == 0 {
// if there is one or more in stack
// if len(lastTasks) >= 1 {
// add current as subtask of it
if len(lastTasks) != 1 {
_ = lastTasks.Pop()
}
lastTasks.Push(task)
lastTasks.SecondToLast().SubTasks = append(lastTasks.SecondToLast().SubTasks, task)
} else if diff > 0 {
lastTasks.Push(task)
lastTasks.SecondToLast().SubTasks = append(lastTasks.SecondToLast().SubTasks, task)
currentIndent += 1
}
case 1:
comment := strings.Trim(text, "\t\n ")
targetTask := lastTasks.Last()
if targetTask.Comment != "" {
targetTask.Comment += "\n"
}
targetTask.Comment += comment
default:
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
return lastTasks[0].SubTasks, nil
}
func LoadFromFilename(filename string) ([]*Task, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()
return Parse(file)
}
func String(tasklist []*Task) (text string) {
for _, task := range tasklist {
text += fmt.Sprintf("%s\n", task.String())
}
return text
}
func (task Task) String() (text string) {
// checkbox
text += `- [`
if task.Completed {
text += `x`
} else {
text += ` `
}
text += `] `
// title
text += task.Title + ` `
// tags
// it seems like keys can get output in a different order
// thats not ideal, but is good enough for now
for key, value := range task.Tags {
text += fmt.Sprintf("%s:%s ", key, value)
}
text = strings.TrimSuffix(text, ` `)
if task.Comment != `` {
text += "\n\t"
text += strings.Replace(task.Comment, "\n", "\n\t", -1)
}
subtasktxt := ""
for _, subtask := range task.SubTasks {
subtasktxt += "\n"
subtasktxt += subtask.String()
}
text += strings.Replace(subtasktxt, "\n", "\n\t", -1)
return
}
func WriteToFilename(tasklist []*Task, filename string) error {
return ioutil.WriteFile(filename, []byte(String(tasklist)), 0640)
}