Merge the list/task-capture marker split (V-520)
This commit is contained in:
@@ -34,6 +34,46 @@ var listTags = []struct{ word, list string }{
|
||||
{"хозяйство", "хозяйство"},
|
||||
}
|
||||
|
||||
// taskListTags — the list names that belong to task capture, not here. One
|
||||
// dictionary form each, read the same way listTags are (Vikunja #520).
|
||||
//
|
||||
// "добавь в список" is a marker on both sides: task capture has it in
|
||||
// task_phrases.json and listCapturePrefixes has it below. ListGrammars is wired
|
||||
// before TaskCaptureGrammar, so the list claimed every one of them, and
|
||||
// takeListTag does not recognise "дел" as a list name — so "добавь в список дел
|
||||
// хлеб" filed a grocery item called "дел хлеб". The bare marker stays a grocery
|
||||
// item, because an unnamed list already defaults to покупки and the task side
|
||||
// always names its list. A named task list refuses here and falls through.
|
||||
var taskListTags = []string{"дело", "задача", "task", "todo", "todos"}
|
||||
|
||||
// namesTaskList reports whether the remainder after a list marker names a task
|
||||
// list rather than one of the standing lists.
|
||||
func namesTaskList(rest string) bool {
|
||||
fields := strings.Fields(rest)
|
||||
// The list noun and the prepositions around it are skipped, so the three
|
||||
// callers can ask this before their own trimming: the remove path leaves
|
||||
// "из списка дел хлеб" and the capture path leaves "список дел хлеб".
|
||||
for len(fields) > 0 {
|
||||
head := strings.ToLower(strings.Trim(fields[0], listTrimCut))
|
||||
if morph.SameWord(head, "список") || head == "list" || head == "of" || head == "the" ||
|
||||
head == "из" || head == "со" || head == "в" {
|
||||
fields = fields[1:]
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if len(fields) == 0 {
|
||||
return false
|
||||
}
|
||||
head := strings.ToLower(strings.Trim(fields[0], listTrimCut))
|
||||
for _, w := range taskListTags {
|
||||
if morph.SameWord(head, w) || head == w {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var listTagsEN = []struct{ word, list string }{
|
||||
{"shopping", "покупки"},
|
||||
{"groceries", "покупки"},
|
||||
@@ -121,6 +161,10 @@ func ParseListCapture(text string) (ListCapture, bool) {
|
||||
if !ok {
|
||||
return ListCapture{}, false
|
||||
}
|
||||
// A named task list is task capture's, not the grocery list's (#520).
|
||||
if namesTaskList(rest) {
|
||||
return ListCapture{}, false
|
||||
}
|
||||
list, rest := takeListTag(rest)
|
||||
rest = strings.Trim(rest, listTrimCut)
|
||||
if rest == "" {
|
||||
@@ -135,6 +179,9 @@ func ParseListQuery(text string) (string, bool) {
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
if namesTaskList(rest) {
|
||||
return "", false
|
||||
}
|
||||
list, _ := takeListTag(rest)
|
||||
return list, true
|
||||
}
|
||||
@@ -162,6 +209,9 @@ func ParseListRemove(text string) (ListCapture, bool) {
|
||||
if !ok {
|
||||
return ListCapture{}, false
|
||||
}
|
||||
if namesTaskList(rest) {
|
||||
return ListCapture{}, false
|
||||
}
|
||||
list, rest := takeListTag(rest)
|
||||
rest = strings.Trim(rest, listTrimCut)
|
||||
for _, lead := range []string{"из списка ", "со списка ", "из ", "from the list "} {
|
||||
|
||||
@@ -108,3 +108,41 @@ func TestListTagIsAWordNotAPrefix(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestNamedTaskListIsNotAGroceryItem — "добавь в список" is a marker on both
|
||||
// sides, and ListGrammars is wired before TaskCaptureGrammar, so the list used
|
||||
// to claim every one of them (Vikunja #520). takeListTag does not know "дел" as
|
||||
// a list name, so the item filed was "дел хлеб". The bare marker stays a
|
||||
// grocery item; a named task list falls through to task capture.
|
||||
func TestNamedTaskListIsNotAGroceryItem(t *testing.T) {
|
||||
for _, in := range []string{
|
||||
"добавь в список дел хлеб",
|
||||
"добавь в список задач позвонить врачу",
|
||||
"запиши в список дел купить молоко",
|
||||
"add to the list of todos milk",
|
||||
} {
|
||||
if got, ok := ParseListCapture(in); ok {
|
||||
t.Errorf("ParseListCapture(%q) claimed it as list %q item %q, want a pass to task capture",
|
||||
in, got.List, got.Item)
|
||||
}
|
||||
}
|
||||
// The bare form is still the grocery list, and the tagged forms are untouched.
|
||||
for _, tc := range []struct{ in, list, item string }{
|
||||
{"добавь в список хлеб", "покупки", "хлеб"},
|
||||
{"добавь в список покупок молоко", "покупки", "молоко"},
|
||||
{"добавь в список аптеку витамины", "аптека", "витамины"},
|
||||
} {
|
||||
got, ok := ParseListCapture(tc.in)
|
||||
if !ok || got.List != tc.list || got.Item != tc.item {
|
||||
t.Errorf("ParseListCapture(%q) = (%q, %q, %v), want (%q, %q, true)",
|
||||
tc.in, got.List, got.Item, ok, tc.list, tc.item)
|
||||
}
|
||||
}
|
||||
// A read-back and a cross-off name the same lists and must split the same way.
|
||||
if _, ok := ParseListQuery("покажи список дел"); ok {
|
||||
t.Error(`ParseListQuery("покажи список дел") claimed a task list`)
|
||||
}
|
||||
if got, ok := ParseListRemove("вычеркни из списка дел хлеб"); ok {
|
||||
t.Errorf("ParseListRemove claimed a task list: %q / %q", got.List, got.Item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,19 +17,23 @@
|
||||
"",
|
||||
"list_nouns are the nouns that make a question be about the list.",
|
||||
"list_shortcut_nouns are the subset that stand alone as a whole utterance",
|
||||
"('задачи'), which the longer nouns do not ('дел')."
|
||||
"('задачи'), which the longer nouns do not ('дел').",
|
||||
"",
|
||||
"The bare 'добавь в список' and 'запиши в список' are deliberately absent",
|
||||
"(Vikunja #520). They are markers on the grocery list too, in list.go, and an",
|
||||
"unnamed list already means покупки there, so the bare form is a grocery item",
|
||||
"and the task side always names its list. The list declines a named task list",
|
||||
"instead of shadowing these by grammar order."
|
||||
],
|
||||
"capture_prefixes": [
|
||||
"добавь в задачи",
|
||||
"добавь в тудушки",
|
||||
"добавь в список задач",
|
||||
"добавь в список дел",
|
||||
"добавь в список",
|
||||
"добавь задачу",
|
||||
"запиши в задачи",
|
||||
"запиши в список дел",
|
||||
"запиши в список задач",
|
||||
"запиши в список",
|
||||
"запиши задачу",
|
||||
"новая задача",
|
||||
"поставь задачу",
|
||||
|
||||
Reference in New Issue
Block a user