diff --git a/completed.go b/completed.go index 2b5f14f..c68e07f 100644 --- a/completed.go +++ b/completed.go @@ -32,7 +32,7 @@ func CompletedList(c *cli.Context) error { } for _, item := range completed.Items { - result, err := Eval(ex, item, client.Store.Projects, client.Store.Labels) + result, err := Eval(ex, item, client.Store.Projects) if err != nil { return err } diff --git a/filter_eval.go b/filter_eval.go index 34e75c0..5aac780 100644 --- a/filter_eval.go +++ b/filter_eval.go @@ -10,13 +10,13 @@ import ( var priorityRegex = regexp.MustCompile("^p([1-4])$") -func Eval(e Expression, item todoist.AbstractItem, projects todoist.Projects, labels todoist.Labels) (result bool, err error) { +func Eval(e Expression, item todoist.AbstractItem, projects todoist.Projects) (result bool, err error) { result = false switch e.(type) { case BoolInfixOpExpr: e := e.(BoolInfixOpExpr) - lr, err := Eval(e.left, item, projects, labels) - rr, err := Eval(e.right, item, projects, labels) + lr, err := Eval(e.left, item, projects) + rr, err := Eval(e.right, item, projects) if err != nil { return false, nil } @@ -31,7 +31,7 @@ func Eval(e Expression, item todoist.AbstractItem, projects todoist.Projects, la return EvalProject(e, item.GetProjectID(), projects), err case LabelExpr: e := e.(LabelExpr) - return EvalLabel(e, item.GetLabelNames(), labels), err + return EvalLabel(e, item.GetLabelNames()), err case StringExpr: switch item.(type) { case *todoist.Item: @@ -46,7 +46,7 @@ func Eval(e Expression, item todoist.AbstractItem, projects todoist.Projects, la return EvalDate(e, item.DateTime()), err case NotOpExpr: e := e.(NotOpExpr) - r, err := Eval(e.expr, item, projects, labels) + r, err := Eval(e.expr, item, projects) if err != nil { return false, nil } @@ -109,7 +109,7 @@ func EvalProject(e ProjectExpr, projectID string, projects todoist.Projects) boo return false } -func EvalLabel(e LabelExpr, labelNames []string, labels todoist.Labels) bool { +func EvalLabel(e LabelExpr, labelNames []string) bool { if e.name == "" { return len(labelNames) == 0 } diff --git a/filter_eval_test.go b/filter_eval_test.go index 690aa7b..6b836df 100644 --- a/filter_eval_test.go +++ b/filter_eval_test.go @@ -19,21 +19,23 @@ func due(s string) *todoist.Due { date := t.Format(todoist.RFC3339DateTime) return &todoist.Due{ Date: date, + // these tests will break in other timezones + TimeZone: "Asia/Tokyo", } } func testFilterEval(t *testing.T, f string, item todoist.Item, expect bool) { - actual, _ := Eval(Filter(f), &item, todoist.Projects{}, todoist.Labels{}) + actual, _ := Eval(Filter(f), &item, todoist.Projects{}) assert.Equal(t, expect, actual, "they should be equal") } func testFilterEvalWithProject(t *testing.T, f string, item todoist.Item, projects todoist.Projects, expect bool) { - actual, _ := Eval(Filter(f), &item, projects, todoist.Labels{}) + actual, _ := Eval(Filter(f), &item, projects) assert.Equal(t, expect, actual, "they should be equal") } -func testFilterEvalWithLabel(t *testing.T, f string, item todoist.Item, labels todoist.Labels, expect bool) { - actual, _ := Eval(Filter(f), &item, todoist.Projects{}, labels) +func testFilterEvalWithLabel(t *testing.T, f string, item todoist.Item, expect bool) { + actual, _ := Eval(Filter(f), &item, todoist.Projects{}) assert.Equal(t, expect, actual, "they should be equal") } @@ -47,26 +49,12 @@ func TestPriorityEval(t *testing.T) { } func TestLabelEval(t *testing.T) { - labels := todoist.Labels{ - todoist.Label{ - HaveID: todoist.HaveID{ID: "1"}, - Name: "must", - }, - todoist.Label{ - HaveID: todoist.HaveID{ID: "2"}, - Name: "icebox", - }, todoist.Label{ - HaveID: todoist.HaveID{ID: "3"}, - Name: "another", - }, - } - item1 := todoist.Item{} - item1.LabelNames = []string{"1", "2"} + item1.LabelNames = []string{"must", "icebox"} - // testFilterEvalWithLabel(t, "@must", item1, labels, true) - // testFilterEvalWithLabel(t, "@icebox", item1, labels, true) - testFilterEvalWithLabel(t, "@another", item1, labels, false) + testFilterEvalWithLabel(t, "@must", item1, true) + testFilterEvalWithLabel(t, "@icebox", item1, true) + testFilterEvalWithLabel(t, "@another", item1, false) } func TestProjectEval(t *testing.T) { @@ -114,16 +102,16 @@ func TestDueOnEval(t *testing.T) { timeNow := time.Date(2017, time.October, 2, 1, 0, 0, 0, testTimeZone) // JST: Mon 2 Oct 2017 00:00:00 setNow(timeNow) - // testFilterEval(t, "today", todoist.Item{Due: due("Sun 1 Oct 2017 15:00:00 +0000")}, true) // JST: Mon 2 Oct 2017 00:00:00 - // testFilterEval(t, "today", todoist.Item{Due: due("Mon 2 Oct 2017 14:59:59 +0000")}, true) // JST: Mon 2 Oct 2017 23:59:59 + testFilterEval(t, "today", todoist.Item{Due: due("Sun 1 Oct 2017 15:00:00 +0000")}, true) // JST: Mon 2 Oct 2017 00:00:00 + testFilterEval(t, "today", todoist.Item{Due: due("Mon 2 Oct 2017 14:59:59 +0000")}, true) // JST: Mon 2 Oct 2017 23:59:59 testFilterEval(t, "today", todoist.Item{Due: due("Mon 2 Oct 2017 15:00:00 +0000")}, false) // JST: Tue 3 Oct 2017 00:00:00 - // testFilterEval(t, "yesterday", todoist.Item{Due: due("Sun 1 Oct 2017 14:59:59 +0000")}, true) // JST: Sun 1 Oct 2017 23:59:59 - // testFilterEval(t, "yesterday", todoist.Item{Due: due("Sat 30 Sep 2017 15:00:00 +0000")}, true) // JST: Sun 1 Oct 2017 00:00:00 - // testFilterEval(t, "yesterday", todoist.Item{Due: due("Sat 30 Sep 2017 14:59:59 +0000")}, false) // JST: Sat 30 Sept 2017 23:59:59 - // testFilterEval(t, "tomorrow", todoist.Item{Due: due("Mon 2 Oct 2017 15:00:00 +0000")}, true) // JST: Tue 3 Oct 2017 00:00:00 - // testFilterEval(t, "tomorrow", todoist.Item{Due: due("Tue 3 Oct 2017 14:59:59 +0000")}, true) // JST: Tue 3 Oct 2017 23:59:59 - testFilterEval(t, "tomorrow", todoist.Item{Due: due("Tue 3 Oct 2017 15:00:00 +0000")}, false) // JST: Wed 4 Oct 2017 00:00:00 + testFilterEval(t, "yesterday", todoist.Item{Due: due("Sun 1 Oct 2017 14:59:59 +0000")}, true) // JST: Sun 1 Oct 2017 23:59:59 + testFilterEval(t, "yesterday", todoist.Item{Due: due("Sat 30 Sep 2017 15:00:00 +0000")}, true) // JST: Sun 1 Oct 2017 00:00:00 + testFilterEval(t, "yesterday", todoist.Item{Due: due("Sat 30 Sep 2017 14:59:59 +0000")}, false) // JST: Sat 30 Sept 2017 23:59:59 + testFilterEval(t, "tomorrow", todoist.Item{Due: due("Mon 2 Oct 2017 15:00:00 +0000")}, true) // JST: Tue 3 Oct 2017 00:00:00 + testFilterEval(t, "tomorrow", todoist.Item{Due: due("Tue 3 Oct 2017 14:59:59 +0000")}, true) // JST: Tue 3 Oct 2017 23:59:59 + testFilterEval(t, "tomorrow", todoist.Item{Due: due("Tue 3 Oct 2017 15:00:00 +0000")}, false) // JST: Wed 4 Oct 2017 00:00:00 testFilterEval(t, "10/2/2017", todoist.Item{Due: due("Mon 2 Oct 2017 01:00:00 +0000")}, true) // JST: Mon 2 Oct 2017 10:00:00 testFilterEval(t, "10/2/2017 10:00", todoist.Item{Due: due("Mon 2 Oct 2017 01:00:00 +0000")}, false) // JST: Mon 2 Oct 2017 10:00:00 @@ -140,10 +128,10 @@ func TestDueBeforeEval(t *testing.T) { timeNow := time.Date(2017, time.October, 2, 1, 0, 0, 0, testTimeZone) // JST: Mon 2 Oct 2017 00:00:00 setNow(timeNow) - testFilterEval(t, "due before: 10/2/2017", todoist.Item{Due: due("Sun 1 Oct 2017 15:00:00 +0000")}, false) // JST: Mon 2 Oct 2017 00:00:00 - // testFilterEval(t, "due before: 10/2/2017", todoist.Item{Due: due("Sun 1 Oct 2017 14:59:59 +0000")}, true) // JST: Sun 1 Oct 2017 23:59:59 + testFilterEval(t, "due before: 10/2/2017", todoist.Item{Due: due("Sun 1 Oct 2017 15:00:00 +0000")}, false) // JST: Mon 2 Oct 2017 00:00:00 + testFilterEval(t, "due before: 10/2/2017", todoist.Item{Due: due("Sun 1 Oct 2017 14:59:59 +0000")}, true) // JST: Sun 1 Oct 2017 23:59:59 testFilterEval(t, "due before: 10/2/2017 13:00", todoist.Item{Due: due("Mon 2 Oct 2017 4:00:00 +0000")}, false) // JST: Mon 2 Oct 2017 13:00:00 - // testFilterEval(t, "due before: 10/2/2017 13:00", todoist.Item{Due: due("Mon 2 Oct 2017 3:59:00 +0000")}, true) // JST: Mon 2 Oct 2017 12:59:00 + testFilterEval(t, "due before: 10/2/2017 13:00", todoist.Item{Due: due("Mon 2 Oct 2017 3:59:00 +0000")}, true) // JST: Mon 2 Oct 2017 12:59:00 testFilterEval(t, "due before: 10/2/2017 13:00", todoist.Item{Due: nil}, false) // JST: Mon 2 Oct 2017 12:59:00 } @@ -152,22 +140,22 @@ func TestOverDueEval(t *testing.T) { timeNow := time.Date(2017, time.October, 2, 12, 0, 0, 0, testTimeZone) // JST: Mon 2 Oct 2017 12:00:00 setNow(timeNow) - // testFilterEval(t, "over due", todoist.Item{Due: due("Mon 2 Oct 2017 2:59:00 +0000")}, true) // JST: Mon 2 Oct 2017 11:59:00 - // testFilterEval(t, "over due", todoist.Item{Due: due("Mon 2 Oct 2017 3:00:00 +0000")}, false) // JST: Mon 2 Oct 2017 12:00:00 - // testFilterEval(t, "od", todoist.Item{Due: due("Mon 2 Oct 2017 2:59:00 +0000")}, true) // JST: Mon 2 Oct 2017 11:59:00 - // testFilterEval(t, "od", todoist.Item{Due: due("Mon 2 Oct 2017 3:00:00 +0000")}, false) // JST: Mon 2 Oct 2017 12:00:00 + testFilterEval(t, "over due", todoist.Item{Due: due("Mon 2 Oct 2017 2:59:00 +0000")}, true) // JST: Mon 2 Oct 2017 11:59:00 + testFilterEval(t, "over due", todoist.Item{Due: due("Mon 2 Oct 2017 3:00:00 +0000")}, false) // JST: Mon 2 Oct 2017 12:00:00 + testFilterEval(t, "od", todoist.Item{Due: due("Mon 2 Oct 2017 2:59:00 +0000")}, true) // JST: Mon 2 Oct 2017 11:59:00 + testFilterEval(t, "od", todoist.Item{Due: due("Mon 2 Oct 2017 3:00:00 +0000")}, false) // JST: Mon 2 Oct 2017 12:00:00 - // testFilterEval(t, "od", todoist.Item{Due: nil}, false) // JST: Mon 2 Oct 2017 12:00:00 + testFilterEval(t, "od", todoist.Item{Due: nil}, false) // JST: Mon 2 Oct 2017 12:00:00 } func TestDueAfterEval(t *testing.T) { timeNow := time.Date(2017, time.October, 2, 1, 0, 0, 0, testTimeZone) // JST: Mon 2 Oct 2017 00:00:00 setNow(timeNow) - //testFilterEval(t, "due after: 10/2/2017", todoist.Item{Due: due("Mon 2 Oct 2017 14:59:59 +0000")}, false) // JST: Mon 2 Oct 2017 23:59:59 - //testFilterEval(t, "due after: 10/2/2017", todoist.Item{Due: due("Mon 2 Oct 2017 15:00:00 +0000")}, true) // JST: Tue 3 Oct 2017 00:00:00 - //testFilterEval(t, "due after: 10/2/2017 13:00", todoist.Item{Due: due("Mon 2 Oct 2017 4:00:00 +0000")}, false) // JST: Mon 2 Oct 2017 13:00:00 - //testFilterEval(t, "due after: 10/2/2017 13:00", todoist.Item{Due: due("Mon 2 Oct 2017 4:01:00 +0000")}, true) // JST: Mon 2 Oct 2017 13:01:00 - // - //testFilterEval(t, "due after: 10/2/2017 13:00", todoist.Item{Due: nil}, false) // JST: Mon 2 Oct 2017 13:01:00 + testFilterEval(t, "due after: 10/2/2017", todoist.Item{Due: due("Mon 2 Oct 2017 14:59:59 +0000")}, false) // JST: Mon 2 Oct 2017 23:59:59 + testFilterEval(t, "due after: 10/2/2017", todoist.Item{Due: due("Mon 2 Oct 2017 15:00:00 +0000")}, true) // JST: Tue 3 Oct 2017 00:00:00 + testFilterEval(t, "due after: 10/2/2017 13:00", todoist.Item{Due: due("Mon 2 Oct 2017 4:00:00 +0000")}, false) // JST: Mon 2 Oct 2017 13:00:00 + testFilterEval(t, "due after: 10/2/2017 13:00", todoist.Item{Due: due("Mon 2 Oct 2017 4:01:00 +0000")}, true) // JST: Mon 2 Oct 2017 13:01:00 + + testFilterEval(t, "due after: 10/2/2017 13:00", todoist.Item{Due: nil}, false) // JST: Mon 2 Oct 2017 13:01:00 } diff --git a/filter_parser.go b/filter_parser.go index 92247d3..8ce9e69 100644 --- a/filter_parser.go +++ b/filter_parser.go @@ -9,7 +9,6 @@ import __yyfmt__ "fmt" import ( "fmt" - "os" "strconv" "strings" @@ -74,7 +73,7 @@ var timezone = func() *time.Location { return now().Location() } -//line filter_parser.y:73 +//line filter_parser.y:72 type yySymType struct { yys int token Token @@ -126,13 +125,14 @@ var yyToknames = [...]string{ "':'", "'/'", } + var yyStatenames = [...]string{} const yyEofCode = 1 const yyErrCode = 2 const yyInitialStackSize = 16 -//line filter_parser.y:295 +//line filter_parser.y:294 type Lexer struct { scanner.Scanner @@ -233,12 +233,14 @@ func (l *Lexer) Error(e string) { func Filter(f string) (e Expression) { l := new(Lexer) l.Init(strings.NewReader(f)) + // important to exclude scanner.ScanFloats because afternoon times in am/pm format trigger float parsing + l.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.SkipComments yyParse(l) return l.result } //line yacctab:1 -var yyExca = [...]int{ +var yyExca = [...]int8{ -1, 1, 1, -1, -2, 0, @@ -248,8 +250,7 @@ const yyPrivate = 57344 const yyLast = 72 -var yyAct = [...]int{ - +var yyAct = [...]int8{ 13, 3, 21, 22, 60, 24, 25, 26, 12, 2, 46, 17, 18, 16, 44, 46, 14, 15, 32, 33, 8, 61, 9, 20, 52, 51, 36, 28, 27, 45, @@ -259,8 +260,8 @@ var yyAct = [...]int{ 31, 30, 29, 7, 6, 5, 4, 11, 10, 19, 23, 1, } -var yyPact = [...]int{ +var yyPact = [...]int16{ -3, -1000, 10, -1000, 58, 57, 56, -1000, -3, -3, -1000, -1000, 35, -1000, 7, -1000, 22, 38, -1000, 54, -1000, 8, 53, -1000, -1000, -1000, -1000, -3, -3, -1000, @@ -269,29 +270,29 @@ var yyPact = [...]int{ -1000, 36, 36, -1000, -23, -1000, -5, -1000, -1000, -1000, 46, 45, -1000, -1000, } -var yyPgo = [...]int{ +var yyPgo = [...]int8{ 0, 71, 9, 0, 70, 69, 68, 67, 66, 65, 64, 63, 23, } -var yyR1 = [...]int{ +var yyR1 = [...]int8{ 0, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 9, 8, 10, 11, 7, 7, 6, 6, 3, 3, 3, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 12, 12, 12, } -var yyR2 = [...]int{ +var yyR2 = [...]int8{ 0, 0, 1, 3, 3, 1, 2, 2, 2, 1, 3, 2, 1, 1, 4, 4, 1, 2, 1, 1, 2, 2, 3, 2, 1, 2, 1, 1, 5, 3, 3, 1, 1, 1, 1, 2, 2, 3, 3, 5, 2, } -var yyChk = [...]int{ +var yyChk = [...]int16{ -1000, -1, -2, 4, -8, -9, -10, -11, 23, 25, -6, -7, 11, -3, 19, 20, 16, 14, 15, -5, -12, 5, 6, -4, 8, 9, 10, 22, 21, 4, @@ -300,8 +301,8 @@ var yyChk = [...]int{ 24, 26, 26, 17, 5, 5, 5, 5, -3, -3, 27, 26, 5, 5, } -var yyDef = [...]int{ +var yyDef = [...]int8{ 1, -2, 2, 5, 0, 0, 0, 9, 0, 0, 12, 13, 0, 16, 18, 19, 0, 0, 24, 26, 27, 0, 0, 31, 32, 33, 34, 0, 0, 6, @@ -310,8 +311,8 @@ var yyDef = [...]int{ 10, 0, 0, 22, 37, 30, 38, 29, 14, 15, 0, 0, 28, 39, } -var yyTok1 = [...]int{ +var yyTok1 = [...]int8{ 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, @@ -326,12 +327,13 @@ var yyTok1 = [...]int{ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 22, } -var yyTok2 = [...]int{ +var yyTok2 = [...]int8{ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, } -var yyTok3 = [...]int{ + +var yyTok3 = [...]int8{ 0, } @@ -413,9 +415,9 @@ func yyErrorMessage(state, lookAhead int) string { expected := make([]int, 0, 4) // Look for shiftable tokens. - base := yyPact[state] + base := int(yyPact[state]) for tok := TOKSTART; tok-1 < len(yyToknames); tok++ { - if n := base + tok; n >= 0 && n < yyLast && yyChk[yyAct[n]] == tok { + if n := base + tok; n >= 0 && n < yyLast && int(yyChk[int(yyAct[n])]) == tok { if len(expected) == cap(expected) { return res } @@ -425,13 +427,13 @@ func yyErrorMessage(state, lookAhead int) string { if yyDef[state] == -2 { i := 0 - for yyExca[i] != -1 || yyExca[i+1] != state { + for yyExca[i] != -1 || int(yyExca[i+1]) != state { i += 2 } // Look for tokens that we accept or reduce. for i += 2; yyExca[i] >= 0; i += 2 { - tok := yyExca[i] + tok := int(yyExca[i]) if tok < TOKSTART || yyExca[i+1] == 0 { continue } @@ -462,30 +464,30 @@ func yylex1(lex yyLexer, lval *yySymType) (char, token int) { token = 0 char = lex.Lex(lval) if char <= 0 { - token = yyTok1[0] + token = int(yyTok1[0]) goto out } if char < len(yyTok1) { - token = yyTok1[char] + token = int(yyTok1[char]) goto out } if char >= yyPrivate { if char < yyPrivate+len(yyTok2) { - token = yyTok2[char-yyPrivate] + token = int(yyTok2[char-yyPrivate]) goto out } } for i := 0; i < len(yyTok3); i += 2 { - token = yyTok3[i+0] + token = int(yyTok3[i+0]) if token == char { - token = yyTok3[i+1] + token = int(yyTok3[i+1]) goto out } } out: if token == 0 { - token = yyTok2[1] /* unknown char */ + token = int(yyTok2[1]) /* unknown char */ } if yyDebug >= 3 { __yyfmt__.Printf("lex %s(%d)\n", yyTokname(token), uint(char)) @@ -540,7 +542,7 @@ yystack: yyS[yyp].yys = yystate yynewstate: - yyn = yyPact[yystate] + yyn = int(yyPact[yystate]) if yyn <= yyFlag { goto yydefault /* simple state */ } @@ -551,8 +553,8 @@ yynewstate: if yyn < 0 || yyn >= yyLast { goto yydefault } - yyn = yyAct[yyn] - if yyChk[yyn] == yytoken { /* valid shift */ + yyn = int(yyAct[yyn]) + if int(yyChk[yyn]) == yytoken { /* valid shift */ yyrcvr.char = -1 yytoken = -1 yyVAL = yyrcvr.lval @@ -565,7 +567,7 @@ yynewstate: yydefault: /* default state action */ - yyn = yyDef[yystate] + yyn = int(yyDef[yystate]) if yyn == -2 { if yyrcvr.char < 0 { yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) @@ -574,18 +576,18 @@ yydefault: /* look through exception table */ xi := 0 for { - if yyExca[xi+0] == -1 && yyExca[xi+1] == yystate { + if yyExca[xi+0] == -1 && int(yyExca[xi+1]) == yystate { break } xi += 2 } for xi += 2; ; xi += 2 { - yyn = yyExca[xi+0] + yyn = int(yyExca[xi+0]) if yyn < 0 || yyn == yytoken { break } } - yyn = yyExca[xi+1] + yyn = int(yyExca[xi+1]) if yyn < 0 { goto ret0 } @@ -607,10 +609,10 @@ yydefault: /* find a state where "error" is a legal shift action */ for yyp >= 0 { - yyn = yyPact[yyS[yyp].yys] + yyErrCode + yyn = int(yyPact[yyS[yyp].yys]) + yyErrCode if yyn >= 0 && yyn < yyLast { - yystate = yyAct[yyn] /* simulate a shift of "error" */ - if yyChk[yystate] == yyErrCode { + yystate = int(yyAct[yyn]) /* simulate a shift of "error" */ + if int(yyChk[yystate]) == yyErrCode { goto yystack } } @@ -646,7 +648,7 @@ yydefault: yypt := yyp _ = yypt // guard against "declared and not used" - yyp -= yyR2[yyn] + yyp -= int(yyR2[yyn]) // yyp is now the index of $0. Perform the default action. Iff the // reduced production is ε, $1 is possibly out of range. if yyp+1 >= len(yyS) { @@ -657,16 +659,16 @@ yydefault: yyVAL = yyS[yyp+1] /* consult goto table to find next state */ - yyn = yyR1[yyn] - yyg := yyPgo[yyn] + yyn = int(yyR1[yyn]) + yyg := int(yyPgo[yyn]) yyj := yyg + yyS[yyp].yys + 1 if yyj >= yyLast { - yystate = yyAct[yyg] + yystate = int(yyAct[yyg]) } else { - yystate = yyAct[yyj] - if yyChk[yystate] != -yyn { - yystate = yyAct[yyg] + yystate = int(yyAct[yyj]) + if int(yyChk[yystate]) != -yyn { + yystate = int(yyAct[yyg]) } } // dummy call; replaced with literal code @@ -674,86 +676,86 @@ yydefault: case 1: yyDollar = yyS[yypt-0 : yypt+1] -//line filter_parser.y:95 +//line filter_parser.y:94 { yyVAL.expr = VoidExpr{} } case 2: yyDollar = yyS[yypt-1 : yypt+1] -//line filter_parser.y:99 +//line filter_parser.y:98 { yyVAL.expr = yyDollar[1].expr yylex.(*Lexer).result = yyVAL.expr } case 3: yyDollar = yyS[yypt-3 : yypt+1] -//line filter_parser.y:106 +//line filter_parser.y:105 { yyVAL.expr = BoolInfixOpExpr{left: yyDollar[1].expr, operator: '|', right: yyDollar[3].expr} } case 4: yyDollar = yyS[yypt-3 : yypt+1] -//line filter_parser.y:110 +//line filter_parser.y:109 { yyVAL.expr = BoolInfixOpExpr{left: yyDollar[1].expr, operator: '&', right: yyDollar[3].expr} } case 5: yyDollar = yyS[yypt-1 : yypt+1] -//line filter_parser.y:114 +//line filter_parser.y:113 { yyVAL.expr = StringExpr{literal: yyDollar[1].token.literal} } case 6: yyDollar = yyS[yypt-2 : yypt+1] -//line filter_parser.y:118 +//line filter_parser.y:117 { yyVAL.expr = ProjectExpr{isAll: false, name: yyDollar[2].token.literal} } case 7: yyDollar = yyS[yypt-2 : yypt+1] -//line filter_parser.y:122 +//line filter_parser.y:121 { yyVAL.expr = ProjectExpr{isAll: true, name: yyDollar[2].token.literal} } case 8: yyDollar = yyS[yypt-2 : yypt+1] -//line filter_parser.y:126 +//line filter_parser.y:125 { yyVAL.expr = LabelExpr{name: yyDollar[2].token.literal} } case 9: yyDollar = yyS[yypt-1 : yypt+1] -//line filter_parser.y:130 +//line filter_parser.y:129 { yyVAL.expr = LabelExpr{name: ""} } case 10: yyDollar = yyS[yypt-3 : yypt+1] -//line filter_parser.y:134 +//line filter_parser.y:133 { yyVAL.expr = yyDollar[2].expr } case 11: yyDollar = yyS[yypt-2 : yypt+1] -//line filter_parser.y:138 +//line filter_parser.y:137 { yyVAL.expr = NotOpExpr{expr: yyDollar[2].expr} } case 12: yyDollar = yyS[yypt-1 : yypt+1] -//line filter_parser.y:142 +//line filter_parser.y:141 { yyVAL.expr = DateExpr{allDay: false, datetime: now(), operation: DUE_BEFORE} } case 13: yyDollar = yyS[yypt-1 : yypt+1] -//line filter_parser.y:146 +//line filter_parser.y:145 { yyVAL.expr = DateExpr{operation: NO_DUE_DATE} } case 14: yyDollar = yyS[yypt-4 : yypt+1] -//line filter_parser.y:150 +//line filter_parser.y:149 { e := yyDollar[4].expr.(DateExpr) e.operation = DUE_BEFORE @@ -761,7 +763,7 @@ yydefault: } case 15: yyDollar = yyS[yypt-4 : yypt+1] -//line filter_parser.y:156 +//line filter_parser.y:155 { e := yyDollar[4].expr.(DateExpr) e.operation = DUE_AFTER @@ -769,55 +771,55 @@ yydefault: } case 17: yyDollar = yyS[yypt-2 : yypt+1] -//line filter_parser.y:165 +//line filter_parser.y:164 { yyVAL.expr = yyDollar[1].token } case 18: yyDollar = yyS[yypt-1 : yypt+1] -//line filter_parser.y:171 +//line filter_parser.y:170 { yyVAL.expr = yyDollar[1].token } case 19: yyDollar = yyS[yypt-1 : yypt+1] -//line filter_parser.y:177 +//line filter_parser.y:176 { yyVAL.expr = yyDollar[1].token } case 20: yyDollar = yyS[yypt-2 : yypt+1] -//line filter_parser.y:183 +//line filter_parser.y:182 { yyVAL.expr = yyDollar[1].token } case 21: yyDollar = yyS[yypt-2 : yypt+1] -//line filter_parser.y:189 +//line filter_parser.y:188 { yyVAL.expr = yyDollar[1].token } case 22: yyDollar = yyS[yypt-3 : yypt+1] -//line filter_parser.y:193 +//line filter_parser.y:192 { yyVAL.expr = yyDollar[1].token } case 23: yyDollar = yyS[yypt-2 : yypt+1] -//line filter_parser.y:199 +//line filter_parser.y:198 { yyVAL.expr = yyDollar[1].token } case 24: yyDollar = yyS[yypt-1 : yypt+1] -//line filter_parser.y:203 +//line filter_parser.y:202 { yyVAL.expr = yyDollar[1].token } case 25: yyDollar = yyS[yypt-2 : yypt+1] -//line filter_parser.y:209 +//line filter_parser.y:208 { date := yyDollar[1].expr.(time.Time) time := yyDollar[2].expr.(time.Duration) @@ -825,13 +827,13 @@ yydefault: } case 26: yyDollar = yyS[yypt-1 : yypt+1] -//line filter_parser.y:215 +//line filter_parser.y:214 { yyVAL.expr = DateExpr{allDay: true, datetime: yyDollar[1].expr.(time.Time)} } case 27: yyDollar = yyS[yypt-1 : yypt+1] -//line filter_parser.y:219 +//line filter_parser.y:218 { nd := now().Sub(today()) d := yyDollar[1].expr.(time.Duration) @@ -842,25 +844,25 @@ yydefault: } case 28: yyDollar = yyS[yypt-5 : yypt+1] -//line filter_parser.y:230 +//line filter_parser.y:229 { yyVAL.expr = time.Date(atoi(yyDollar[5].token.literal), time.Month(atoi(yyDollar[1].token.literal)), atoi(yyDollar[3].token.literal), 0, 0, 0, 0, timezone()) } case 29: yyDollar = yyS[yypt-3 : yypt+1] -//line filter_parser.y:234 +//line filter_parser.y:233 { yyVAL.expr = time.Date(atoi(yyDollar[3].token.literal), MonthIdentHash[strings.ToLower(yyDollar[1].token.literal)], atoi(yyDollar[2].token.literal), 0, 0, 0, 0, timezone()) } case 30: yyDollar = yyS[yypt-3 : yypt+1] -//line filter_parser.y:238 +//line filter_parser.y:237 { yyVAL.expr = time.Date(atoi(yyDollar[3].token.literal), MonthIdentHash[strings.ToLower(yyDollar[2].token.literal)], atoi(yyDollar[1].token.literal), 0, 0, 0, 0, timezone()) } case 31: yyDollar = yyS[yypt-1 : yypt+1] -//line filter_parser.y:242 +//line filter_parser.y:241 { tod := today() date := yyDollar[1].expr.(time.Time) @@ -871,55 +873,55 @@ yydefault: } case 32: yyDollar = yyS[yypt-1 : yypt+1] -//line filter_parser.y:251 +//line filter_parser.y:250 { yyVAL.expr = today() } case 33: yyDollar = yyS[yypt-1 : yypt+1] -//line filter_parser.y:255 +//line filter_parser.y:254 { yyVAL.expr = today().AddDate(0, 0, 1) } case 34: yyDollar = yyS[yypt-1 : yypt+1] -//line filter_parser.y:259 +//line filter_parser.y:258 { yyVAL.expr = today().AddDate(0, 0, -1) } case 35: yyDollar = yyS[yypt-2 : yypt+1] -//line filter_parser.y:265 +//line filter_parser.y:264 { yyVAL.expr = time.Date(today().Year(), MonthIdentHash[strings.ToLower(yyDollar[1].token.literal)], atoi(yyDollar[2].token.literal), 0, 0, 0, 0, timezone()) } case 36: yyDollar = yyS[yypt-2 : yypt+1] -//line filter_parser.y:269 +//line filter_parser.y:268 { yyVAL.expr = time.Date(today().Year(), MonthIdentHash[strings.ToLower(yyDollar[2].token.literal)], atoi(yyDollar[1].token.literal), 0, 0, 0, 0, timezone()) } case 37: yyDollar = yyS[yypt-3 : yypt+1] -//line filter_parser.y:273 +//line filter_parser.y:272 { yyVAL.expr = time.Date(now().Year(), time.Month(atoi(yyDollar[3].token.literal)), atoi(yyDollar[1].token.literal), 0, 0, 0, 0, timezone()) } case 38: yyDollar = yyS[yypt-3 : yypt+1] -//line filter_parser.y:279 +//line filter_parser.y:278 { yyVAL.expr = time.Duration(int64(time.Hour)*int64(atoi(yyDollar[1].token.literal)) + int64(time.Minute)*int64(atoi(yyDollar[3].token.literal))) } case 39: yyDollar = yyS[yypt-5 : yypt+1] -//line filter_parser.y:283 +//line filter_parser.y:282 { yyVAL.expr = time.Duration(int64(time.Hour)*int64(atoi(yyDollar[1].token.literal)) + int64(time.Minute)*int64(atoi(yyDollar[3].token.literal)) + int64(time.Second)*int64(atoi(yyDollar[5].token.literal))) } case 40: yyDollar = yyS[yypt-2 : yypt+1] -//line filter_parser.y:287 +//line filter_parser.y:286 { hour := atoi(yyDollar[1].token.literal) if TwelveClockIdentHash[yyDollar[2].token.literal] { diff --git a/filter_parser.y b/filter_parser.y index f7de158..fe7d956 100644 --- a/filter_parser.y +++ b/filter_parser.y @@ -393,6 +393,8 @@ func (l *Lexer) Error(e string) { func Filter(f string) (e Expression) { l := new(Lexer) l.Init(strings.NewReader(f)) + // important to exclude scanner.ScanFloats because afternoon times in am/pm format trigger float parsing + l.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.SkipComments yyParse(l) return l.result } diff --git a/filter_parser_test.go b/filter_parser_test.go index 2a91cb2..86313a3 100644 --- a/filter_parser_test.go +++ b/filter_parser_test.go @@ -112,21 +112,21 @@ func TestDateTimeFilter(t *testing.T) { DateExpr{operation: DUE_ON, datetime: time.Date(timeNow.Year(), timeNow.Month(), timeNow.Day(), 16, 10, 3, 0, testTimeZone), allDay: false}, Filter("16:10:03"), "they should be equal") - //assert.Equal(t, - // DateExpr{operation: DUE_ON, datetime: time.Date(timeNow.Year(), timeNow.Month(), timeNow.Day(), 15, 0, 0, 0, testTimeZone), allDay: false}, - // Filter("3pm"), "they should be equal") - // - //assert.Equal(t, - // DateExpr{operation: DUE_ON, datetime: time.Date(timeNow.Year(), timeNow.Month(), timeNow.Day(), 7, 0, 0, 0, testTimeZone), allDay: false}, - // Filter("7am"), "they should be equal") - // - //assert.Equal(t, - // DateExpr{operation: DUE_ON, datetime: time.Date(2020, time.February, 10, 15, 0, 0, 0, testTimeZone), allDay: false}, - // Filter("10 Feb 2020 3pm"), "they should be equal") - // - //assert.Equal(t, - // DateExpr{operation: DUE_ON, datetime: time.Date(timeNow.Year(), timeNow.Month(), timeNow.Day(), 7, 0, 0, 0, testTimeZone), allDay: false}, - // Filter("7am"), "they should be equal") + assert.Equal(t, + DateExpr{operation: DUE_ON, datetime: time.Date(timeNow.Year(), timeNow.Month(), timeNow.Day(), 15, 0, 0, 0, testTimeZone), allDay: false}, + Filter("3pm"), "they should be equal") + + assert.Equal(t, + DateExpr{operation: DUE_ON, datetime: time.Date(timeNow.Year(), timeNow.Month(), timeNow.Day(), 7, 0, 0, 0, testTimeZone), allDay: false}, + Filter("7am"), "they should be equal") + + assert.Equal(t, + DateExpr{operation: DUE_ON, datetime: time.Date(2020, time.February, 10, 15, 0, 0, 0, testTimeZone), allDay: false}, + Filter("10 Feb 2020 3pm"), "they should be equal") + + assert.Equal(t, + DateExpr{operation: DUE_ON, datetime: time.Date(timeNow.Year(), timeNow.Month(), timeNow.Day(), 7, 0, 0, 0, testTimeZone), allDay: false}, + Filter("7am"), "they should be equal") } func TestSpecialDateTimeFilter(t *testing.T) { diff --git a/lib/item.go b/lib/item.go index bc807d2..99594aa 100644 --- a/lib/item.go +++ b/lib/item.go @@ -7,9 +7,7 @@ import ( "time" ) -var ( - linkRegex = regexp.MustCompile(`\[(.*?)\]\((.*?)\)`) -) +var linkRegex = regexp.MustCompile(`\[(.*?)\]\((.*?)\)`) const ( RFC3339Date = "2006-01-02" @@ -95,20 +93,28 @@ func (a Items) At(i int) IDCarrier { return a[i] } func (item Item) DateTime() time.Time { var date string + // TODO: it would be more correct to get the timezone from Store.User + location := time.Local if item.Due == nil { date = "" } else { + if item.Due.TimeZone != "" { + dueTz, err := time.LoadLocation(item.Due.TimeZone) + if err == nil { + location = dueTz + } + } date = item.Due.Date } - //2020-03-03T14:00:00 - //2020-01-17T23:00:00Z - t, err := time.ParseInLocation(RFC3339DateTimeWithTimeZone, date, time.Local) + // 2020-03-03T14:00:00 + // 2020-01-17T23:00:00Z + t, err := time.ParseInLocation(RFC3339DateTimeWithTimeZone, date, location) if err != nil { - t, err = time.ParseInLocation(RFC3339DateTime, date, time.Local) + t, err = time.ParseInLocation(RFC3339DateTime, date, location) } if err != nil { - t, _ = time.ParseInLocation(RFC3339Date, date, time.Local) + t, _ = time.ParseInLocation(RFC3339Date, date, location) } return t } diff --git a/list.go b/list.go index 3aac08f..cbc0560 100644 --- a/list.go +++ b/list.go @@ -56,7 +56,7 @@ func List(c *cli.Context) error { } traverseItems(rootItem, func(item *todoist.Item, depth int) { - r, err := Eval(ex, item, client.Store.Projects, client.Store.Labels) + r, err := Eval(ex, item, client.Store.Projects) if err != nil { return }