Improve Spanish Anki vocabulary tooling
This commit is contained in:
+141
-13
@@ -15,7 +15,7 @@ from .importer import (
|
||||
supported_tts_backends,
|
||||
synthesize_tts_sample,
|
||||
)
|
||||
from .words import compare_word_files, extract_words
|
||||
from .words import compare_word_files, compare_word_lists, extract_words, extract_words_from_file, lint_anki_cards_main
|
||||
from .youtube import run_youtube
|
||||
|
||||
|
||||
@@ -85,22 +85,48 @@ def build_parser(config: Config | None = None) -> argparse.ArgumentParser:
|
||||
audio.add_argument("--media-dir")
|
||||
audio.add_argument("--copy-only-new", action="store_true")
|
||||
|
||||
words = sub.add_parser("words", help="Extract frequent words from Anki.")
|
||||
words.add_argument("lang", choices=choices)
|
||||
words = sub.add_parser("words", help="Extract frequent words from Anki or from a TSV export file.")
|
||||
words.add_argument("lang", nargs="?", choices=choices)
|
||||
words.add_argument("--lang", dest="lang_option", choices=choices, help="Language code.")
|
||||
words.add_argument("--input", help="Anki TSV export file (instead of AnkiConnect).")
|
||||
group = words.add_mutually_exclusive_group()
|
||||
group.add_argument("--query")
|
||||
group.add_argument("--deck", action="append")
|
||||
words.add_argument("--field")
|
||||
words.add_argument("--field", help="Anki field name (AnkiConnect) or 1-based column index (TSV file).")
|
||||
words.add_argument("--min-freq", type=int, default=2)
|
||||
words.add_argument("--outdir")
|
||||
words.add_argument("--out")
|
||||
words.add_argument("--out", "--output", dest="out")
|
||||
words.add_argument("--full-field", action="store_true")
|
||||
words.add_argument("--spacy-model")
|
||||
words.add_argument("--include-proper-nouns", action="store_true", help="Include proper nouns (PROPN).")
|
||||
words.add_argument("--function-words", action="store_true", help="Extract function words separately.")
|
||||
words.add_argument("--debug", help="Path for debug TSV output.")
|
||||
words.add_argument("--lemma-corrections", help="Path to bad<TAB>good TSV of lemma corrections.")
|
||||
words.add_argument("--bad-lemma-file", help="Path to file listing lemmas to skip.")
|
||||
words.add_argument("--include-tags", action="store_true", help="Include tags column in parsing.")
|
||||
words.add_argument("--debug-min-freq", type=int, default=0,
|
||||
help="Minimum frequency for debug output (default: all).")
|
||||
words.add_argument("--no-clean", action="store_true",
|
||||
help="Skip Anki field text cleaning before NLP.")
|
||||
|
||||
compare = sub.add_parser("compare-words", help="Print words in source that are not in known.")
|
||||
compare.add_argument("source")
|
||||
compare.add_argument("known")
|
||||
|
||||
compare_new = sub.add_parser("compare", help="Compare deck vocabulary against a target list.")
|
||||
compare_new.add_argument("--deck-words", required=True, help="Path to deck word list.")
|
||||
compare_new.add_argument("--target-words", required=True, help="Path to target vocabulary list.")
|
||||
compare_new.add_argument("--output", help="Write missing words to this file.")
|
||||
compare_new.add_argument("--seen-output", help="Write seen words to this file.")
|
||||
compare_new.add_argument("--min-frequency", type=int, default=0,
|
||||
help="Minimum frequency for deck words to count.")
|
||||
|
||||
lint_es = sub.add_parser("lint-anki-es", help="Check Spanish Anki cards for suspicious patterns.")
|
||||
lint_es.add_argument("--input", required=True, help="Anki TSV export file.")
|
||||
lint_es.add_argument("--field", type=int, default=2, help="1-based column index (default 2).")
|
||||
lint_es.add_argument("--output", default="suspicious_cards_es.tsv", help="Output TSV path.")
|
||||
lint_es.add_argument("--verbose", action="store_true", help="Output all cards, not just suspicious ones.")
|
||||
|
||||
youtube = sub.add_parser("youtube", help="Mine a YouTube transcript.")
|
||||
youtube.add_argument("lang", choices=choices)
|
||||
youtube.add_argument("video")
|
||||
@@ -144,6 +170,19 @@ def main(argv: list[str] | None = None) -> int:
|
||||
parser = build_parser(config)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
if args.command == "words":
|
||||
lang_option = getattr(args, "lang_option", None)
|
||||
if args.lang and lang_option and args.lang != lang_option:
|
||||
print(
|
||||
f"Error: conflicting language values: {args.lang!r} and {lang_option!r}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 2
|
||||
args.lang = lang_option or args.lang
|
||||
if not args.lang:
|
||||
print("Error: words requires a language, e.g. 'saiki words es' or 'saiki words --lang es'", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
if args.command == "audio":
|
||||
result = extract_audio(config, args.lang, args.outdir, args.media_dir, args.copy_only_new, args.concat)
|
||||
print(f"Copied {result['copied']} files")
|
||||
@@ -154,14 +193,62 @@ def main(argv: list[str] | None = None) -> int:
|
||||
return 0
|
||||
|
||||
if args.command == "words":
|
||||
result = extract_words(
|
||||
config, args.lang, args.query, args.deck, args.field, args.min_freq,
|
||||
args.outdir, args.out, args.full_field, args.spacy_model,
|
||||
)
|
||||
print(f"Query: {result['query']}")
|
||||
print(f"Found {result['notes']} notes")
|
||||
print(f"Extracted {result['unique']} unique entries")
|
||||
print(f"Wrote {result['written']} entries to: {result['out']}")
|
||||
if args.input:
|
||||
# File-based extraction
|
||||
if config.language_name(args.lang) != "spanish":
|
||||
print(
|
||||
"Error: TSV file-based word extraction is currently Spanish-only.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
if args.field and not args.field.isdigit():
|
||||
print(
|
||||
f"Error: --field must be a 1-based column index (integer) "
|
||||
f"when --input is used, got: {args.field!r}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
field_index = int(args.field) if args.field else 2
|
||||
if field_index < 1:
|
||||
print(
|
||||
f"Error: --field must be a 1-based column index >= 1, got: {field_index}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
result = extract_words_from_file(
|
||||
config, args.lang, args.input,
|
||||
field_index=field_index,
|
||||
min_freq=args.min_freq,
|
||||
outdir=args.outdir,
|
||||
out=args.out,
|
||||
include_proper_nouns=args.include_proper_nouns,
|
||||
function_words=args.function_words,
|
||||
debug=args.debug,
|
||||
lemma_corrections=args.lemma_corrections,
|
||||
bad_lemma_file=args.bad_lemma_file,
|
||||
spacy_model=args.spacy_model,
|
||||
include_tags=args.include_tags,
|
||||
debug_min_freq=args.debug_min_freq,
|
||||
no_clean=args.no_clean,
|
||||
)
|
||||
print(f"Parsed {result['records']} records")
|
||||
print(f"Wrote {result['written']} content entries to: {result['out']}")
|
||||
if result.get("debug"):
|
||||
print(f"Debug output: {result['debug']}")
|
||||
if result.get("proper_nouns"):
|
||||
print(f"Proper nouns: {result['proper_nouns']}")
|
||||
if result.get("function_words"):
|
||||
print(f"Function words: {result['function_words']}")
|
||||
print(f"Suspicious tokens: {result.get('suspicious', '')}")
|
||||
else:
|
||||
result = extract_words(
|
||||
config, args.lang, args.query, args.deck, args.field, args.min_freq,
|
||||
args.outdir, args.out, args.full_field, args.spacy_model,
|
||||
)
|
||||
print(f"Query: {result['query']}")
|
||||
print(f"Found {result['notes']} notes")
|
||||
print(f"Extracted {result['unique']} unique entries")
|
||||
print(f"Wrote {result['written']} entries to: {result['out']}")
|
||||
return 0
|
||||
|
||||
if args.command == "compare-words":
|
||||
@@ -169,6 +256,47 @@ def main(argv: list[str] | None = None) -> int:
|
||||
print(line)
|
||||
return 0
|
||||
|
||||
if args.command == "compare":
|
||||
result = compare_word_lists(
|
||||
args.deck_words,
|
||||
args.target_words,
|
||||
output_path=args.output,
|
||||
min_frequency=args.min_frequency,
|
||||
seen_output_path=args.seen_output,
|
||||
)
|
||||
print(f"Total missing from deck: {result['total_missing']}")
|
||||
seen_count = len(result["seen"])
|
||||
print(f"Total seen in deck: {seen_count}")
|
||||
if result["missing"]:
|
||||
print("Missing words:")
|
||||
for w in result["missing"][:20]:
|
||||
print(f" {w}")
|
||||
if len(result["missing"]) > 20:
|
||||
print(f" ... and {len(result['missing']) - 20} more")
|
||||
if args.output:
|
||||
print(f"Missing words written to: {args.output}")
|
||||
if args.seen_output:
|
||||
print(f"Seen words written to: {args.seen_output}")
|
||||
return 0
|
||||
|
||||
if args.command == "lint-anki-es":
|
||||
if args.field < 1:
|
||||
print(
|
||||
f"Error: --field must be a 1-based column index >= 1, got: {args.field}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
result = lint_anki_cards_main(
|
||||
args.input,
|
||||
field_index=args.field,
|
||||
output_path=args.output,
|
||||
verbose=args.verbose,
|
||||
)
|
||||
print(f"Checked {result['records']} cards")
|
||||
print(f"Found {result['issues']} suspicious card(s)")
|
||||
print(f"Report written to: {result['path']}")
|
||||
return 0
|
||||
|
||||
if args.command == "youtube":
|
||||
result = run_youtube(
|
||||
config, args.lang, args.video, args.mode, args.top, args.no_stopwords,
|
||||
|
||||
Reference in New Issue
Block a user