Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Search for entries with no tags or stars with -not -starred and -not -tagged #1663

Merged
merged 2 commits into from
Jan 28, 2023
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
59 changes: 54 additions & 5 deletions jrnl/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,43 @@ def _split_lines(self, text: str, width: int) -> list[str]:
return text


class IgnoreNoneAppendAction(argparse._AppendAction):
"""
Pass -not without a following string and avoid appending
a None value to the excluded list
"""

def __call__(self, parser, namespace, values, option_string=None):
if values is not None:
super().__call__(parser, namespace, values, option_string)


def parse_not_arg(
args: list[str], parsed_args: argparse.Namespace, parser: argparse.ArgumentParser
) -> argparse.Namespace:
"""
It's possible to use -not as a precursor to -starred and -tagged
to reverse their behaviour, however this requires some extra logic
to parse, and to ensure we still do not allow passing an empty -not
"""

parsed_args.exclude_starred = False
parsed_args.exclude_tagged = False

if "-not-starred" in "".join(args):
parsed_args.starred = False
parsed_args.exclude_starred = True
if "-not-tagged" in "".join(args):
parsed_args.tagged = False
parsed_args.exclude_tagged = True
if "-not" in args and not any(
[parsed_args.exclude_starred, parsed_args.exclude_tagged, parsed_args.excluded]
):
parser.error("argument -not: expected 1 argument")

return parsed_args


def parse_args(args: list[str] = []) -> argparse.Namespace:
"""
Argument parsing that is doable before the config is available.
Expand Down Expand Up @@ -237,6 +274,12 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
action="store_true",
help="Show only starred entries (marked with *)",
)
reading.add_argument(
"-tagged",
dest="tagged",
action="store_true",
help="Show only entries that have at least one tag",
)
reading.add_argument(
"-n",
dest="limit",
Expand All @@ -249,11 +292,15 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
reading.add_argument(
"-not",
dest="excluded",
nargs=1,
nargs="?",
default=[],
metavar="TAG",
action="extend",
help="Exclude entries with this tag",
metavar="TAG/FLAG",
action=IgnoreNoneAppendAction,
help=(
"If passed a string, will exclude entries with that tag. "
"Can be also used before -starred or -tagged flags, to exclude "
"starred or tagged entries respectively."
),
)

search_options_msg = """ These help you do various tasks with the selected entries from your search.
Expand Down Expand Up @@ -388,5 +435,7 @@ def parse_args(args: list[str] = []) -> argparse.Namespace:
# Handle '-123' as a shortcut for '-n 123'
num = re.compile(r"^-(\d+)$")
args = [num.sub(r"-n \1", arg) for arg in args]
parsed_args = parser.parse_intermixed_args(args)
parsed_args = parse_not_arg(args, parsed_args, parser)

return parser.parse_intermixed_args(args)
return parsed_args
9 changes: 9 additions & 0 deletions jrnl/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ def _is_write_mode(args: "Namespace", config: dict, **kwargs) -> bool:
args.edit,
args.change_time,
args.excluded,
args.exclude_starred,
args.exclude_tagged,
args.export,
args.end_date,
args.today_in_history,
Expand All @@ -101,6 +103,7 @@ def _is_write_mode(args: "Namespace", config: dict, **kwargs) -> bool:
args.starred,
args.start_date,
args.strict,
args.tagged,
args.tags,
)
)
Expand Down Expand Up @@ -270,7 +273,10 @@ def _has_search_args(args: "Namespace") -> bool:
args.end_date,
args.strict,
args.starred,
args.tagged,
args.excluded,
args.exclude_starred,
args.exclude_tagged,
args.contains,
args.limit,
)
Expand All @@ -296,7 +302,10 @@ def _filter_journal_entries(args: "Namespace", journal: Journal, **kwargs) -> No
end_date=args.end_date,
strict=args.strict,
starred=args.starred,
tagged=args.tagged,
exclude=args.excluded,
exclude_starred=args.exclude_starred,
exclude_tagged=args.exclude_tagged,
contains=args.contains,
)
journal.limit(args.limit)
Expand Down
30 changes: 17 additions & 13 deletions jrnl/journals/Journal.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,16 +229,19 @@ def tags(self) -> list[Tag]:

def filter(
self,
tags: list = [],
month: str | int | None = None,
day: str | int | None = None,
year: str | None = None,
start_date: str | None = None,
end_date: str | None = None,
starred: bool = False,
strict: bool = False,
contains: bool = None,
exclude: list = [],
tags=[],
month=None,
day=None,
year=None,
start_date=None,
end_date=None,
starred=False,
tagged=False,
exclude_starred=False,
exclude_tagged=False,
strict=False,
contains=None,
exclude=[],
):
"""Removes all entries from the journal that don't match the filter.

Expand All @@ -259,7 +262,7 @@ def filter(
start_date = time.parse(start_date)

# If strict mode is on, all tags have to be present in entry
tagged = self.search_tags.issubset if strict else self.search_tags.intersection
has_tags = self.search_tags.issubset if strict else self.search_tags.intersection

def excluded(tags):
return 0 < len([tag for tag in tags if tag in excluded_tags])
Expand All @@ -275,8 +278,9 @@ def excluded(tags):
result = [
entry
for entry in self.entries
if (not tags or tagged(entry.tags))
and (not starred or entry.starred)
if (not tags or has_tags(entry.tags))
and (not (starred or exclude_starred) or entry.starred == starred)
and (not (tagged or exclude_tagged) or bool(entry.tags) == tagged)
and (not month or entry.date.month == compare_d.month)
and (not day or entry.date.day == compare_d.day)
and (not year or entry.date.year == compare_d.year)
Expand Down
44 changes: 44 additions & 0 deletions tests/bdd/features/search.feature
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,50 @@ Feature: Searching in a journal
| basic_folder.yaml |
| basic_dayone.yaml |


Scenario: Searching for unstarred entries
Given we use the config "<config_file>"
And we use the password "test" if prompted
When we run "jrnl -not -starred"
Then we should get no error
And the output should contain "2 entries found"

Examples: configs
| config_file |
| basic_onefile.yaml |
| basic_folder.yaml |
| basic_dayone.yaml |

Scenario: Searching for tagged entries
Given we use the config "<config_file>"
And we use the password "test" if prompted
When we run "jrnl -tagged"
Then we should get no error
And the output should contain "3 entries found"

Examples: configs
| config_file |
| basic_onefile.yaml |
| basic_folder.yaml |
| basic_dayone.yaml |

Scenario: Searching for untagged entries
Given we use the config "empty_folder.yaml"
When we run "jrnl Tagged entry. This one has a @tag."
Then we should get no error
When we run "jrnl Untagged entry. This one has no tag."
Then we should get no error
When we run "jrnl -not -tagged"
Then we should get no error
And the output should contain "1 entry found"
And the output should contain "This one has no tag"

Examples: configs
| config_file |
| basic_onefile.yaml |
| basic_folder.yaml |
| basic_dayone.yaml |

Scenario Outline: Searching for dates
Given we use the config "<config_file>"
When we run "jrnl -on 2020-08-31 --short"
Expand Down
3 changes: 3 additions & 0 deletions tests/unit/test_parse_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ def expected_args(**kwargs):
"change_time": None,
"edit": False,
"end_date": None,
"exclude_starred": False,
"exclude_tagged": False,
"today_in_history": False,
"month": None,
"day": None,
Expand All @@ -38,6 +40,7 @@ def expected_args(**kwargs):
"starred": False,
"start_date": None,
"strict": False,
"tagged": False,
"tags": False,
"text": [],
"config_override": [],
Expand Down