Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/things/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func preAdd(d *Deps, kind string, p addParams, flags TagFlags) error {
func resolveAddTarget(d *Deps, list, heading string) (db.Target, createdDest) {
if list == "" {
if heading != "" {
fmt.Fprintf(d.errOut(), "warning: --heading %q needs --list or --project; Things will ignore it and put the to-do in the Inbox\n", heading)
fmt.Fprintf(d.errOut(), "warning: %s\n", headingNeedsList("--heading", heading, "--list or --project", "put the to-do in the Inbox"))
}
return db.Target{}, createdDest{checked: true}
}
Expand All @@ -102,7 +102,7 @@ func resolveAddTarget(d *Deps, list, heading string) (db.Target, createdDest) {
fmt.Fprintf(d.errOut(), "warning: %s\n", noTarget("project or area", list, false, "it will put the to-do in the Inbox"))
return target, createdDest{checked: true}
case heading != "" && !headingFound:
fmt.Fprintf(d.errOut(), "warning: %s\n", noHeading(list, heading, "add the to-do there without a heading"))
fmt.Fprintf(d.errOut(), "warning: %s\n", noHeading(listName(list, target), heading, "add the to-do there without a heading"))
}
noteTarget(d, list, "lists", target)
return target, addDest(target)
Expand Down
4 changes: 2 additions & 2 deletions cmd/things/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ func (c *EditCmd) checkMove(d *Deps, database *db.DB, task *model.Task) bool {
return !inList || task.HeadingUUID != target.Heading
case target.UUID != "":
if heading != "" {
fmt.Fprintf(d.errOut(), "warning: %s\n", noHeading(list, heading, "move the to-do there without a heading"))
fmt.Fprintf(d.errOut(), "warning: %s\n", noHeading(listName(list, target), heading, "move the to-do there without a heading"))
}
return task.HeadingUUID != "" || !inList
case c.Heading == nil:
Expand All @@ -219,7 +219,7 @@ func (c *EditCmd) checkMove(d *Deps, database *db.DB, task *model.Task) bool {
// The unknown list's warning has said the to-do stays put.
return false
case task.ProjectUUID == "":
fmt.Fprintf(d.errOut(), "warning: --heading %q needs --list: the to-do is not in a project, so Things will leave it where it is\n", heading)
fmt.Fprintf(d.errOut(), "warning: %s\n", headingNeedsList("--heading", heading, "--list or --list-id, as the to-do is not in a project", "leave the to-do where it is"))
return false
}
t, f, err := database.AddTarget(task.ProjectUUID, heading)
Expand Down
8 changes: 4 additions & 4 deletions cmd/things/edit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -635,17 +635,17 @@ func TestEditWarnsOnUnresolvedMove(t *testing.T) {
{"unknownHeadingIDWithList", []string{"edit", "one-1", "--list", "Tools", "--heading-id", "nope"}, `no heading with id "nope"; it will ignore --heading-id`, false},
{"unknownHeading", []string{"edit", "one-1", "--list", "Tools", "--heading", "Later"}, `"Tools" has no heading "Later"; Things will move the to-do there without a heading`, false},
{"paddedHeading", []string{"edit", "one-1", "--list", "Tools", "--heading", " Setup "}, `"Tools" has no heading " Setup "`, false},
{"unknownHeadingInListID", []string{"edit", "one-1", "--list-id", "proj-1", "--heading", "Later"}, `"proj-1" has no heading "Later"`, false},
{"unknownHeadingInListID", []string{"edit", "one-1", "--list-id", "proj-1", "--heading", "Later"}, `"Tools" has no heading "Later"`, false},
{"knownHeadingInListID", []string{"edit", "one-1", "--list-id", "proj-1", "--heading", "Setup"}, "", false},
{"unknownHeadingInListUUID", []string{"edit", "one-1", "--list", "proj-1", "--heading", "Later"}, `"proj-1" has no heading "Later"`, false},
{"headingInAreaUUID", []string{"edit", "one-1", "--list", "area-1", "--heading", "Setup"}, `"area-1" has no heading "Setup"`, false},
{"unknownHeadingInListUUID", []string{"edit", "one-1", "--list", "proj-1", "--heading", "Later"}, `"Tools" has no heading "Later"`, false},
{"headingInAreaUUID", []string{"edit", "one-1", "--list", "area-1", "--heading", "Setup"}, `"Personal" has no heading "Setup"`, false},
{"headingOnlyKnown", []string{"edit", "tool-1", "--heading", "SETUP"}, "", false},
// Of two headings that differ only in case, Things files the to-do
// under the one with the lower uuid, whichever case was sent.
{"caseTwinHeading", []string{"edit", "twin-todo", "--heading", "SETUP"}, "", false},
{"caseTwinListAndHeading", []string{"edit", "twin-todo", "--list", "Tools", "--heading", "SETUP"}, "", false},
{"headingOnlyUnknown", []string{"edit", "tool-1", "--heading", "Later"}, `"Tools" has no heading "Later"; Things will leave the to-do where it is`, true},
{"headingOnlyNoProject", []string{"edit", "one-1", "--heading", "Setup"}, `--heading "Setup" needs --list: the to-do is not in a project, so Things will leave it where it is`, true},
{"headingOnlyNoProject", []string{"edit", "one-1", "--heading", "Setup"}, `--heading "Setup" needs --list or --list-id, as the to-do is not in a project; Things will ignore it and leave the to-do where it is`, true},
{"headingID", []string{"edit", "one-1", "--list", "Tools", "--heading-id", "head-1"}, "", false},
// Measured in Things 3: an empty heading alone clears the heading,
// and an empty list unfiles the to-do into Anytime. Both are sent
Expand Down
4 changes: 2 additions & 2 deletions cmd/things/importdest.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ func (r *destResolver) taskDest(c importCreate) createdDest {
return createdDest{checked: true}
case list == "":
if heading != "" {
r.warn(c, "heading %q needs a list; Things will ignore it and put the to-do in the Inbox", heading)
r.warn(c, "%s", headingNeedsList("heading", heading, "list or list-id", "put the to-do in the Inbox"))
}
return createdDest{checked: true}
case !byID && to.listInPayload:
Expand Down Expand Up @@ -225,7 +225,7 @@ func (r *destResolver) taskDest(c importCreate) createdDest {
// lowest-uuid twin of one the list already has.
dest.anyHeading = true
case heading != "" && !res.headingFound:
r.warn(c, "%s", noHeading(list, heading, "add the to-do there without a heading"))
r.warn(c, "%s", noHeading(listName(list, res.target), heading, "add the to-do there without a heading"))
}
return dest
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/things/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func projectArea(d *Deps, area, fallback string) (target db.Target, read bool) {
case err != nil:
return db.Target{}, false
case t.UUID == "":
fmt.Fprintf(d.errOut(), "warning: %s\n", noArea(area, false, fallback))
fmt.Fprintf(d.errOut(), "warning: %s\n", noTarget("area", area, false, fallback))
}
return t, true
}
Expand Down Expand Up @@ -147,7 +147,7 @@ func (c *ProjectEditCmd) checkOwn(d *Deps, database *db.DB, task *model.Task) bo
return moves
}

// checkAreaID warns when Things has no area with the uuid --area-id gives,
// checkAreaID warns when Things finds no area with the uuid --area-id gives,
// and reports whether the project may move. Checked in Things 3: an unknown
// area-id leaves the project where it is.
func (c *ProjectEditCmd) checkAreaID(d *Deps, database *db.DB, task *model.Task) bool {
Expand All @@ -166,6 +166,6 @@ func (c *ProjectEditCmd) checkAreaID(d *Deps, database *db.DB, task *model.Task)
case t.ByUUID:
return true
}
fmt.Fprintf(d.errOut(), "warning: %s\n", noArea(*c.AreaID, true, "the project will stay where it is"))
fmt.Fprintf(d.errOut(), "warning: %s\n", noTarget("area", *c.AreaID, true, "the project will stay where it is"))
return false
}
36 changes: 25 additions & 11 deletions cmd/things/targetmsg.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,44 @@
package main

import "fmt"
import (
"fmt"

"github.com/ryanlewis/things-cli/internal/db"
)

// The warnings add, edit, project and import give when Things cannot find
// the list, area or heading an item was sent to. Agents read this wording,
// so each command builds it here rather than by hand. (The "--heading needs
// --list" warnings differ per command and stay with add and edit.) Where the
// rules differ between commands, the caller decides; these only say it.
// so each command builds it here rather than by hand. Where the rules differ
// between commands, the caller decides; these only say it.

// noTarget is the warning for ref, sent by uuid when byID and by title
// otherwise, when Things finds no noun ("project or area", or "area") for
// it. next says what Things does instead.
// it. next says what Things does instead. It says "finds no" rather than
// "has no": the check is of how Things matches the name, and an item can
// exist that Things will still not match, such as one by its uuid sent as a
// title.
func noTarget(noun, ref string, byID bool, next string) string {
if byID {
return fmt.Sprintf("Things finds no %s with id %q; %s", noun, ref, next)
}
return fmt.Sprintf("Things finds no %s called %q; %s", noun, ref, next)
}

// noArea is project's warning for an area Things cannot find, as noTarget.
// It says "has no area" where import's says "finds no area".
func noArea(ref string, byID bool, next string) string {
if byID {
return fmt.Sprintf("Things has no area with id %q; %s", ref, next)
// headingNeedsList is the warning for a heading sent with no list to file it
// in. name is the flag or field ("--heading", "heading"), needs what would
// give it a list, and next what Things does with the to-do instead.
func headingNeedsList(name, heading, needs, next string) string {
return fmt.Sprintf("%s %q needs %s; Things will ignore it and %s", name, heading, needs, next)
}

// listName is how a warning names the list ref led to: by its title when
// ref was the list's uuid, since that is the name the user knows it by, and
// as given otherwise.
func listName(ref string, t db.Target) string {
if t.ByUUID && t.Title != "" {
return t.Title
}
return fmt.Sprintf("Things has no area called %q; %s", ref, next)
return ref
}

// noHeading is the warning for a heading title list does not have. next is
Expand Down
20 changes: 13 additions & 7 deletions cmd/things/targetmsg_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ func TestTargetWarningsExact(t *testing.T) {
`Things finds no project or area called "Nowhere"; it will put the to-do in the Inbox`},
{"addUnknownHeading", []string{"add", "Buy oat milk", "--list", "Tools", "--heading", "Later"},
`"Tools" has no heading "Later"; Things will add the to-do there without a heading`},
{"addUnknownHeadingInListUUID", []string{"add", "Buy oat milk", "--list", "proj-1", "--heading", "Later"},
`"Tools" has no heading "Later"; Things will add the to-do there without a heading`},
{"addHeadingWithoutList", []string{"add", "Buy oat milk", "--heading", "Setup"},
`--heading "Setup" needs --list or --project; Things will ignore it and put the to-do in the Inbox`},
{"editUnknownList", []string{"edit", "one-1", "--list", "Nowhere"},
Expand All @@ -34,21 +36,21 @@ func TestTargetWarningsExact(t *testing.T) {
{"editUnknownHeading", []string{"edit", "one-1", "--list", "Tools", "--heading", "Later"},
`"Tools" has no heading "Later"; Things will move the to-do there without a heading`},
{"editUnknownHeadingInListID", []string{"edit", "one-1", "--list-id", "proj-1", "--heading", "Later"},
`"proj-1" has no heading "Later"; Things will move the to-do there without a heading`},
`"Tools" has no heading "Later"; Things will move the to-do there without a heading`},
{"editUnknownOwnHeading", []string{"edit", "tool-1", "--heading", "Later"},
`"Tools" has no heading "Later"; Things will leave the to-do where it is`},
{"editHeadingNoProject", []string{"edit", "one-1", "--heading", "Setup"},
`--heading "Setup" needs --list: the to-do is not in a project, so Things will leave it where it is`},
`--heading "Setup" needs --list or --list-id, as the to-do is not in a project; Things will ignore it and leave the to-do where it is`},
{"editUnknownHeadingID", []string{"edit", "tool-1", "--heading-id", "nope"},
`Things has no heading with id "nope"; the to-do will stay where it is`},
{"editUnknownHeadingIDWithList", []string{"edit", "one-1", "--list", "Tools", "--heading-id", "nope"},
`Things has no heading with id "nope"; it will ignore --heading-id`},
{"projectAddUnknownArea", []string{"project", "add", "Launch", "--area", "Nowhere"},
`Things has no area called "Nowhere"; it will create the project with no area`},
`Things finds no area called "Nowhere"; it will create the project with no area`},
{"projectEditUnknownArea", []string{"project", "edit", "garden-1", "--area", "Nowhere"},
`Things has no area called "Nowhere"; the project will stay where it is`},
`Things finds no area called "Nowhere"; the project will stay where it is`},
{"projectEditUnknownAreaID", []string{"project", "edit", "garden-1", "--area-id", "nope"},
`Things has no area with id "nope"; the project will stay where it is`},
`Things finds no area with id "nope"; the project will stay where it is`},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
Expand Down Expand Up @@ -86,6 +88,8 @@ func TestImportTargetWarningsExact(t *testing.T) {
{"type":"to-do","attributes":{"title":"c","list":"proj-1"}},
{"type":"to-do","attributes":{"title":"d","list":"Tools","heading":"Setup"}},
{"type":"to-do","attributes":{"title":"e","list":"Tools","heading-id":"nope"}},
{"type":"to-do","attributes":{"title":"f","heading":"Setup"}},
{"type":"to-do","attributes":{"title":"g","list-id":"proj-1","heading":"Later"}},
{"type":"project","attributes":{"title":"Shed","area":"Nowhere"}},
{"type":"project","attributes":{"title":"Barn","area-id":"nope"}}
]`
Expand All @@ -99,8 +103,10 @@ func TestImportTargetWarningsExact(t *testing.T) {
`[2]: list "proj-1" is an id, and Things matches list by title only; it will put the to-do in the Inbox (use list-id)`,
`[3]: "Tools" has no heading "Setup"; Things will add the to-do there without a heading`,
`[4]: Things has no heading with id "nope"; it will ignore heading-id and heading`,
`[5]: Things finds no area called "Nowhere"; it will create the project in no area`,
`[6]: Things finds no area with id "nope"; it will create the project in no area`,
`[5]: heading "Setup" needs list or list-id; Things will ignore it and put the to-do in the Inbox`,
`[6]: "Tools" has no heading "Later"; Things will add the to-do there without a heading`,
`[7]: Things finds no area called "Nowhere"; it will create the project in no area`,
`[8]: Things finds no area with id "nope"; it will create the project in no area`,
} {
if !strings.Contains(stderr, "warning: "+want+"\n") {
t.Errorf("stderr = %q, want the line %q", stderr, "warning: "+want)
Expand Down
8 changes: 4 additions & 4 deletions docs/content/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,7 @@ nothing matches it creates the project with no area. `project add` warns on
stderr when that will happen, then sends the project anyway:

```
warning: Things has no area called "Nowhere"; it will create the project with no area
warning: Things finds no area called "Nowhere"; it will create the project with no area
```

Each line of `--todos`, like each `--checklist` item, may be at most 4000
Expand Down Expand Up @@ -649,7 +649,7 @@ warning: Things has no heading with id "abc123"; the to-do will stay where it is
warning: Things has no heading with id "abc123"; it will ignore --heading-id
warning: "Tools" has no heading "Later"; Things will move the to-do there without a heading
warning: "Tools" has no heading "Later"; Things will leave the to-do where it is
warning: --heading "Later" needs --list: the to-do is not in a project, so Things will leave it where it is
warning: --heading "Later" needs --list or --list-id, as the to-do is not in a project; Things will ignore it and leave the to-do where it is
```

When the move files the to-do somewhere new, `edit` notes the same things
Expand Down Expand Up @@ -798,8 +798,8 @@ one Things will use, as `project add` does, unless the project is already
there. A move to the area the project is already in counts as no change.

```
warning: Things has no area called "Nowhere"; the project will stay where it is
warning: Things has no area with id "abc123"; the project will stay where it is
warning: Things finds no area called "Nowhere"; the project will stay where it is
warning: Things finds no area with id "abc123"; the project will stay where it is
```

## Tags must already exist
Expand Down
Loading