Compare commits
9 Commits
32cb827d26
...
v4
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e5999676d | |||
| 1099f4ab97 | |||
| ec9df87d68 | |||
| e2d27367fe | |||
| f332c1708a | |||
| daffdf4441 | |||
| bfb1bb3433 | |||
| 676ef83e50 | |||
| adbc2cf364 |
@@ -6,6 +6,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -13,19 +14,19 @@ import (
|
|||||||
|
|
||||||
// Task represents a parsed todo item.
|
// Task represents a parsed todo item.
|
||||||
type Task struct {
|
type Task struct {
|
||||||
Raw string `json:"raw"`
|
Raw string `json:"raw"`
|
||||||
Completed bool `json:"completed"`
|
Completed bool `json:"completed"`
|
||||||
CompletionDate *time.Time `json:"completion_date,omitempty"`
|
CompletionDate *time.Time `json:"completion_date,omitempty"`
|
||||||
Priority *rune `json:"priority,omitempty"` // 'A'..'Z'
|
Priority *rune `json:"priority,omitempty"` // 'A'..'Z'
|
||||||
CreationDate *time.Time `json:"creation_date,omitempty"`
|
CreationDate *time.Time `json:"creation_date,omitempty"`
|
||||||
DueDate *time.Time `json:"due_date,omitempty"` // parsed from due:YYYY-MM-DD
|
DueDate *time.Time `json:"due_date,omitempty"` // parsed from due:YYYY-MM-DD
|
||||||
Description string `json:"description"` // description with projects/contexts/metadata removed
|
Description string `json:"description"` // description with projects/contexts/metadata removed
|
||||||
Projects []string `json:"projects"`
|
Projects []string `json:"projects"`
|
||||||
Contexts []string `json:"contexts"`
|
Contexts []string `json:"contexts"`
|
||||||
Metadata map[string]string `json:"metadata"`
|
Metadata map[string][]string `json:"metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t Task) ToRemind() string {
|
func (t Task) ToRemind(indent int) string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
sb.WriteString("REM TODO ")
|
sb.WriteString("REM TODO ")
|
||||||
if t.DueDate == nil {
|
if t.DueDate == nil {
|
||||||
@@ -36,13 +37,19 @@ func (t Task) ToRemind() string {
|
|||||||
sb.WriteString(" ++5 INFO \"Calendar: TODO.TX\" ")
|
sb.WriteString(" ++5 INFO \"Calendar: TODO.TX\" ")
|
||||||
|
|
||||||
if len(t.Metadata) > 0 {
|
if len(t.Metadata) > 0 {
|
||||||
for k, v := range t.Metadata {
|
for k, values := range t.Metadata {
|
||||||
if k == "due" {
|
if k == "due" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// k must have the first letter uppercase for Remind
|
// k must have the first letter uppercase for Remind
|
||||||
k = strings.ToUpper(k[:1]) + k[1:]
|
kUpper := strings.ToUpper(k[:1]) + k[1:]
|
||||||
sb.WriteString(fmt.Sprintf("INFO \"%s: %s\" ", k, v))
|
if len(values) == 1 {
|
||||||
|
sb.WriteString(fmt.Sprintf("INFO \"%s: %s\" ", kUpper, values[0]))
|
||||||
|
} else {
|
||||||
|
for i, v := range values {
|
||||||
|
sb.WriteString(fmt.Sprintf("INFO \"%s%d: %s\" ", kUpper, i+1, v))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,14 +87,23 @@ func (t Task) ToRemind() string {
|
|||||||
sb.WriteString(" (%b)")
|
sb.WriteString(" (%b)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
headingSpaces := strings.Repeat(" ", indent)
|
||||||
|
|
||||||
if len(t.Metadata) > 0 {
|
if len(t.Metadata) > 0 {
|
||||||
for k := range t.Metadata {
|
for k, values := range t.Metadata {
|
||||||
if k == "due" {
|
if k == "due" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// uppercase first letter for Remind
|
// uppercase first letter for Remind
|
||||||
k = strings.ToUpper(k[:1]) + k[1:]
|
kUpper := strings.ToUpper(k[:1]) + k[1:]
|
||||||
sb.WriteString(fmt.Sprintf("%%_%s: %%<%s>", k, k))
|
if len(values) == 1 {
|
||||||
|
sb.WriteString(fmt.Sprintf("%%_%s%s: %%<%s>", headingSpaces, kUpper, kUpper))
|
||||||
|
} else {
|
||||||
|
for i := range values {
|
||||||
|
kNumbered := fmt.Sprintf("%s%d", kUpper, i+1)
|
||||||
|
sb.WriteString(fmt.Sprintf("%%_%s%s: %%<%s>", headingSpaces, kNumbered, kNumbered))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,12 +120,10 @@ func (t Task) PriorityAsRemind() int {
|
|||||||
if p < 'A' || p > 'Z' {
|
if p < 'A' || p > 'Z' {
|
||||||
return 5000
|
return 5000
|
||||||
}
|
}
|
||||||
step := 9999 / 25 // 399
|
pInt := int(p) - 65 // 'A' = 65
|
||||||
val := 9999 - int(p-'A')*step
|
value := 9999 - (float64(pInt) * (float64(9999) / float64(25)))
|
||||||
if p == 'Z' {
|
value = math.Round(value)
|
||||||
return 0
|
return int(value)
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t Task) MarshalJSON() ([]byte, error) {
|
func (t Task) MarshalJSON() ([]byte, error) {
|
||||||
@@ -182,7 +196,7 @@ func ParseReader(r io.Reader) ([]Task, []error) {
|
|||||||
func ParseLine(line string) (Task, error) {
|
func ParseLine(line string) (Task, error) {
|
||||||
t := Task{
|
t := Task{
|
||||||
Raw: line,
|
Raw: line,
|
||||||
Metadata: make(map[string]string),
|
Metadata: make(map[string][]string),
|
||||||
}
|
}
|
||||||
|
|
||||||
working := strings.TrimSpace(line)
|
working := strings.TrimSpace(line)
|
||||||
@@ -263,14 +277,14 @@ func ParseLine(line string) (Task, error) {
|
|||||||
}
|
}
|
||||||
val += " " + toks[i]
|
val += " " + toks[i]
|
||||||
}
|
}
|
||||||
t.Metadata["location"] = val
|
t.Metadata["location"] = append(t.Metadata["location"], val)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !strings.ContainsAny(tok, " \t") && strings.Contains(tok, ":") {
|
if !strings.ContainsAny(tok, " \t") && strings.Contains(tok, ":") {
|
||||||
parts := strings.SplitN(tok, ":", 2)
|
parts := strings.SplitN(tok, ":", 2)
|
||||||
k, v := parts[0], parts[1]
|
k, v := parts[0], parts[1]
|
||||||
if k != "" && v != "" && !isProtocolKey(k) {
|
if k != "" && v != "" && !isProtocolKey(k) {
|
||||||
t.Metadata[k] = v
|
t.Metadata[k] = append(t.Metadata[k], v)
|
||||||
if k == "due" && dateRe.MatchString(v) {
|
if k == "due" && dateRe.MatchString(v) {
|
||||||
if dt, err := time.Parse(dateLayout, v); err == nil {
|
if dt, err := time.Parse(dateLayout, v); err == nil {
|
||||||
t.DueDate = &dt
|
t.DueDate = &dt
|
||||||
@@ -278,6 +292,7 @@ func ParseLine(line string) (Task, error) {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
descParts = append(descParts, tok)
|
descParts = append(descParts, tok)
|
||||||
}
|
}
|
||||||
|
|||||||
6
main.go
6
main.go
@@ -14,7 +14,8 @@ var (
|
|||||||
inputFile string
|
inputFile string
|
||||||
outputFile string
|
outputFile string
|
||||||
debug bool
|
debug bool
|
||||||
version = "1"
|
indent int
|
||||||
|
version = "v3.0.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -59,7 +60,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, t := range tasks {
|
for _, t := range tasks {
|
||||||
rem := t.ToRemind()
|
rem := t.ToRemind(indent)
|
||||||
if rem == "" {
|
if rem == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -71,6 +72,7 @@ func main() {
|
|||||||
rootCmd.Flags().StringVarP(&inputFile, "input", "i", "", "Input file (default: stdin)")
|
rootCmd.Flags().StringVarP(&inputFile, "input", "i", "", "Input file (default: stdin)")
|
||||||
rootCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output file (default: stdout)")
|
rootCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output file (default: stdout)")
|
||||||
rootCmd.Flags().BoolVar(&debug, "debug", false, "Print intermediate JSON to stderr")
|
rootCmd.Flags().BoolVar(&debug, "debug", false, "Print intermediate JSON to stderr")
|
||||||
|
rootCmd.Flags().IntVar(&indent, "indent", 0, "Number of spaces to indent lines (from second line onward)")
|
||||||
rootCmd.Version = version
|
rootCmd.Version = version
|
||||||
rootCmd.Flags().BoolP("version", "v", false, "Show version and exit")
|
rootCmd.Flags().BoolP("version", "v", false, "Show version and exit")
|
||||||
rootCmd.SetVersionTemplate(fmt.Sprintf("todotxt2remind version %s\n", version))
|
rootCmd.SetVersionTemplate(fmt.Sprintf("todotxt2remind version %s\n", version))
|
||||||
|
|||||||
Reference in New Issue
Block a user