Skip to content

Commit e055243

Browse files
committed
feat: add list-notes
This also adjusts the note review and adds the new "Show details" menu item to toggle verbose mode on and off. This has a corresponding config option "review_verbose".
1 parent 8c5dfa0 commit e055243

File tree

6 files changed

+89
-32
lines changed

6 files changed

+89
-32
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ currently recognized:
135135
- `query`: Specify default query for `apy list`, `apy review` and `apy tag`.
136136
- `review_show_cards`: Whether to show list of cards by default during note
137137
review
138+
- `review_verbose`: Whether to show note details by default during note
139+
review
138140

139141
An example configuration:
140142

src/apyanki/anki.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,11 +388,20 @@ def edit_model_css(self, model_name: str) -> None:
388388
self.col.models.save(model, templates=True)
389389
self.modified = True
390390

391-
def list_notes(self, query: str) -> None:
392-
"""List notes that match a query"""
391+
def list_note_questions(self, query: str) -> None:
392+
"""List first card questions for notes that match a query"""
393393
for note in self.find_notes(query):
394394
cards.print_question(note.n.cards()[0])
395395

396+
def list_notes(
397+
self, query: str, show_cards: bool, show_raw_fields: bool, verbose: bool
398+
) -> None:
399+
"""List notes that match a query"""
400+
for note in self.find_notes(query):
401+
note.pprint(
402+
print_raw=show_raw_fields, list_cards=show_cards, verbose=verbose
403+
)
404+
396405
def list_cards(self, query: str, verbose: bool) -> None:
397406
"""List cards that match a query"""
398407
for cid in self.col.find_cards(query):

src/apyanki/cards.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from typing import TYPE_CHECKING
66

7+
from rich.markdown import Markdown
78
from rich.text import Text
89

910
from apyanki.console import console, consolePlain
@@ -21,7 +22,7 @@ def card_pprint(card: Card, verbose: bool = True) -> None:
2122

2223
if verbose:
2324
card_type = ["new", "learning", "review", "relearning"][int(card.type)]
24-
columned = [
25+
details = [
2526
f"[yellow]nid:[/yellow] {card.nid}",
2627
f"[yellow]model:[/yellow] {card.note_type()['name']}",
2728
f"[yellow]type:[/yellow] {card_type}",
@@ -32,8 +33,8 @@ def card_pprint(card: Card, verbose: bool = True) -> None:
3233
f"[yellow]ease:[/yellow] {int(card.factor / 10)} %",
3334
"",
3435
]
35-
for line in columned:
36-
consolePlain.print(line)
36+
for detail in details:
37+
consolePlain.print(detail)
3738

3839
rendered = card.render_output()
3940
for title, field in [
@@ -47,7 +48,10 @@ def card_pprint(card: Card, verbose: bool = True) -> None:
4748
console.print(f"[blue]## {title}[/blue]\n")
4849
prepared = prepare_field_for_cli(field)
4950
prepared = prepared.replace("\n\n", "\n")
50-
console.print(prepared)
51+
if is_markdown:
52+
console.print(Markdown(prepared))
53+
else:
54+
console.print(prepared)
5155
console.print()
5256

5357

src/apyanki/cli.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,33 @@ def list_cards(query: str, verbose: bool) -> None:
397397
a.list_cards(query, verbose)
398398

399399

400+
@main.command("list-notes")
401+
@click.argument("query", required=False, nargs=-1)
402+
@click.option("-c", "--show-cards", is_flag=True, help="Print card specs")
403+
@click.option("-r", "--show-raw-fields", is_flag=True, help="Print raw field data")
404+
@click.option("-v", "--verbose", is_flag=True, help="Print note details")
405+
def list_notes(
406+
query: str, show_cards: bool, show_raw_fields: bool, verbose: bool
407+
) -> None:
408+
"""List notes that match QUERY.
409+
410+
The default QUERY is "tag:marked OR -flag:0". This default can be
411+
customized in the config file `~/.config/apy/apy.json`, e.g. with
412+
413+
\b
414+
{
415+
"query": "tag:marked OR tag:leech"
416+
}
417+
"""
418+
if query:
419+
query = " ".join(query)
420+
else:
421+
query = cfg["query"]
422+
423+
with Anki(**cfg) as a:
424+
a.list_notes(query, show_cards, show_raw_fields, verbose)
425+
426+
400427
@main.command("list-cards-table")
401428
@click.argument("query", required=False, nargs=-1)
402429
@click.option("-a", "--show-answer", is_flag=True, help="Display answer")
@@ -679,7 +706,7 @@ def tag(
679706
raise click.Abort()
680707

681708
console.print(f"The operation will be applied to {n_notes} matched notes:")
682-
a.list_notes(query)
709+
a.list_note_questions(query)
683710
console.print("")
684711

685712
if add_tags is not None:

src/apyanki/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def get_base_path() -> str | None:
5454
"profile_name": None,
5555
"query": "tag:marked OR -flag:0",
5656
"review_show_cards": False,
57+
"review_verbose": False,
5758
}
5859

5960
# Ensure that cfg has required keys

src/apyanki/note.py

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -80,41 +80,47 @@ def __repr__(self) -> str:
8080

8181
return "\n".join(lines)
8282

83-
def pprint(self, print_raw: bool = False, list_cards: bool = False) -> None:
83+
def pprint(
84+
self, print_raw: bool = False, list_cards: bool = False, verbose: bool = False
85+
) -> None:
8486
"""Print to screen"""
8587
from anki import latex
8688

8789
header = f"[green]# Note (nid: {self.n.id})[/green]"
8890
if self.suspended:
8991
header += " [red](suspended)[/red]"
90-
91-
created = strftime("%F %H:%M", localtime(self.n.id / 1000))
92-
modified = strftime("%F %H:%M", localtime(self.n.mod))
93-
columned = [
94-
f"[yellow]model:[/yellow] {self.model_name} ({len(self.n.cards())} cards)",
95-
f"[yellow]tags:[/yellow] {self.get_tag_string()}",
96-
f"[yellow]created:[/yellow] {created}",
97-
f"[yellow]modified:[/yellow] {modified}",
98-
]
99-
if self.a.n_decks > 1:
100-
columned += ["[yellow]deck:[/yellow] " + self.get_deck()]
101-
102-
if not list_cards:
103-
flagged = [
104-
cards.get_flag(c, str(c.template()["name"]))
105-
for c in self.n.cards()
106-
if c.flags > 0
92+
consolePlain.print(header + "\n")
93+
94+
if verbose:
95+
created = strftime("%F %H:%M", localtime(self.n.id / 1000))
96+
modified = strftime("%F %H:%M", localtime(self.n.mod))
97+
details = [
98+
f"[yellow]model:[/yellow] {self.model_name} ({len(self.n.cards())} cards)",
99+
f"[yellow]tags:[/yellow] {self.get_tag_string()}",
100+
f"[yellow]created:[/yellow] {created}",
101+
f"[yellow]modified:[/yellow] {modified}",
107102
]
108-
if flagged:
109-
columned += [f"[yellow]flagged:[/yellow] {', '.join(flagged)}"]
103+
if self.a.n_decks > 1:
104+
details += ["[yellow]deck:[/yellow] " + self.get_deck()]
105+
106+
if not list_cards:
107+
flagged = [
108+
cards.get_flag(c, str(c.template()["name"]))
109+
for c in self.n.cards()
110+
if c.flags > 0
111+
]
112+
if flagged:
113+
details += [f"[yellow]flagged:[/yellow] {', '.join(flagged)}"]
110114

111-
consolePlain.print(header)
112-
consolePlain.print(Columns(columned, width=37))
115+
for detail in details:
116+
consolePlain.print(detail)
113117

114118
if list_cards:
115119
self.print_cards()
116120

117-
console.print()
121+
if verbose or list_cards:
122+
console.print()
123+
118124
imgs: list[Path] = []
119125
for name, field in self.n.items():
120126
is_markdown = check_if_generated_from_markdown(field)
@@ -441,6 +447,7 @@ def review(
441447
"N": "Change model",
442448
"s": "Save and stop",
443449
"v": "Show cards",
450+
"V": "Show details",
444451
"x": "Save and stop",
445452
}
446453

@@ -471,14 +478,15 @@ def review(
471478
)
472479

473480
print_raw_fields = False
474-
refresh = True
481+
verbose = cfg["review_verbose"]
475482
show_cards = cfg["review_show_cards"]
483+
refresh = True
476484
while True:
477485
if refresh:
478486
console.clear()
479487
console.print(menu)
480488
console.print("")
481-
self.pprint(print_raw_fields, list_cards=show_cards)
489+
self.pprint(print_raw_fields, list_cards=show_cards, verbose=verbose)
482490

483491
refresh = True
484492
choice = readchar.readchar()
@@ -560,6 +568,12 @@ def review(
560568

561569
if action == "Show cards":
562570
show_cards = not show_cards
571+
cfg["review_show_cards"] = show_cards
572+
continue
573+
574+
if action == "Show details":
575+
verbose = not verbose
576+
cfg["review_verbose"] = verbose
563577
continue
564578

565579

0 commit comments

Comments
 (0)