133 lines
5.1 KiB
Python
Executable file
133 lines
5.1 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import click
|
|
import genanki
|
|
import markdown
|
|
import pathlib
|
|
import random
|
|
import re
|
|
|
|
RE_DIRNAME = re.compile(r'^(.*)\.([0-9]+)\.([0-9]+)$')
|
|
RE_FILENAME = re.compile(r'^(.*)\.([0-9]+)\.(md|html)$')
|
|
|
|
class MyNote(genanki.Note):
|
|
pass
|
|
|
|
def load_notes(
|
|
*,
|
|
path: pathlib.Path,
|
|
model: genanki.Model,
|
|
) -> list[genanki.Note]:
|
|
files_loaded = []
|
|
final_files: dict[str, list[tuple[pathlib.Path, int, bool]]] = {}
|
|
for dirpath, _, filenames in path.walk():
|
|
files_loaded += [dirpath / x for x in filenames]
|
|
for f in files_loaded:
|
|
match = RE_FILENAME.match(f.name)
|
|
if match:
|
|
final_files.setdefault(match.group(1), []).append(
|
|
(f, int(match.group(2)), match.group(3) == 'html'),
|
|
)
|
|
notes: list[genanki.Note] = []
|
|
for v in final_files.values():
|
|
note_files: list[tuple[pathlib.Path, bool]] = [(v_[0], v_[2]) for v_ in sorted(v, key=lambda x: x[1])]
|
|
note_str: list[str] = []
|
|
for f, is_html in note_files:
|
|
with f.open() as fp:
|
|
if is_html:
|
|
html_str = fp.read()
|
|
else:
|
|
html_str = markdown.markdown(fp.read())
|
|
note_str += [html_str]
|
|
notes += [MyNote(model=model, fields=note_str, guid=genanki.guid_for(note_str[0]))]
|
|
return notes
|
|
|
|
@click.command()
|
|
@click.option('--name', '-n', 'name', type=str, default=None)
|
|
@click.option('--model-name', '-m', 'model_name', type=str, default='Simple Model')
|
|
@click.option('--template-name', '-t', 'template_name', type=str, default='Card 1')
|
|
@click.option('--field', '-f', 'fields', type=str, default=['Question', 'Answer'], multiple=True)
|
|
@click.option('--qfmt', '-q', 'question_format', type=str, default='{{Question}}')
|
|
@click.option('--afmt', '-a', 'answer_format', type=str, default='{{FrontSide}}<hr id="answer">{{Answer}}')
|
|
@click.option('--model-id', 'model_id', type=int, default=None)
|
|
@click.option('--deck-id', 'deck_id', type=int, default=None)
|
|
@click.option('--destructive/--no-destructive', 'destructive', type=bool, default=False, help='Perform write operations, which may end in data loss')
|
|
@click.option('--autogenerate/--no-autogenerate', 'autogenerate', type=bool, default=False, help='Generate missing ids, should probably be used with --destructive, to always use the same ones.')
|
|
@click.option('--debug/--no-debug', 'debug', type=bool, default=False, help="Debug mode, don't write files")
|
|
@click.option('--multi', 'multi', type=click.Path(path_type=pathlib.Path))
|
|
@click.argument('paths', type=click.Path(path_type=pathlib.Path, dir_okay=True), nargs=-1)
|
|
def main(
|
|
*,
|
|
name: str | None,
|
|
model_name: str,
|
|
template_name: str,
|
|
fields: list[str],
|
|
question_format: str,
|
|
answer_format: str,
|
|
model_id: int | None,
|
|
deck_id: int | None,
|
|
destructive: bool,
|
|
autogenerate: bool,
|
|
debug: bool,
|
|
multi: pathlib.Path | None,
|
|
paths: list[pathlib.Path],
|
|
):
|
|
decks = []
|
|
for path in paths:
|
|
match = RE_DIRNAME.match(path.name)
|
|
if match:
|
|
model_name = model_name or match.group(1)
|
|
name = name or match.group(1)
|
|
model_id = model_id or int(match.group(2))
|
|
deck_id = deck_id or int(match.group(3))
|
|
else:
|
|
model_name = model_name or path.name
|
|
name = name or path.name
|
|
if model_id is None:
|
|
if autogenerate:
|
|
model_id = random.randrange(1 << 30, 1 << 31)
|
|
else:
|
|
raise click.ClickException('model_id could not be derived from directory name and is not explicitly set, or autogenerate enabled')
|
|
if deck_id is None:
|
|
if autogenerate:
|
|
deck_id = random.randrange(1 << 30, 1 << 31)
|
|
else:
|
|
raise click.ClickException('deck_id could not be derived from directory name and is not explicitly set, or autogenerate enabled')
|
|
if destructive:
|
|
new_path = path.parent / f'{path.name}.{model_id}.{deck_id}'
|
|
if not debug:
|
|
path.rename(new_path)
|
|
path = new_path
|
|
model = genanki.Model(
|
|
model_id,
|
|
model_name,
|
|
fields=[dict(name=x) for x in fields],
|
|
templates=[
|
|
{
|
|
'name': template_name,
|
|
'qfmt': question_format,
|
|
'afmt': answer_format,
|
|
},
|
|
],
|
|
)
|
|
notes: list[genanki.Note] = load_notes(path=path, model=model)
|
|
deck = genanki.Deck(
|
|
deck_id,
|
|
name,
|
|
)
|
|
for n in notes:
|
|
deck.add_note(n)
|
|
click.echo(f'added {len(notes)} notes')
|
|
decks += [deck]
|
|
if not debug:
|
|
genanki.Package(deck).write_to_file(path.parent / (path.name + '.apkg'))
|
|
if not debug and multi:
|
|
if multi.suffix != '.apkg':
|
|
click.echo('WARNING: destination file does not have extension ".apkg"')
|
|
genanki.Package(decks).write_to_file(multi)
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|