mirror of
https://github.com/kuhyx/testsAndMisc-archive.git
synced 2026-07-04 14:23:04 +02:00
feat: anki generation feature
This commit is contained in:
parent
510440e02d
commit
1411e685c2
499
python_pkg/word_frequency/anki_generator.py
Normal file
499
python_pkg/word_frequency/anki_generator.py
Normal file
@ -0,0 +1,499 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Anki flashcard generator from vocabulary curve analysis.
|
||||||
|
|
||||||
|
Generates Anki-compatible flashcard decks from the vocabulary needed to
|
||||||
|
understand excerpts of a given length.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
# Generate flashcards for a 20-word excerpt
|
||||||
|
python -m python_pkg.word_frequency.anki_generator --file text.txt --length 20
|
||||||
|
|
||||||
|
# Specify source language (auto-detected by default)
|
||||||
|
python -m python_pkg.word_frequency.anki_generator --file text.txt --length 20 --from pl
|
||||||
|
|
||||||
|
# Custom output file
|
||||||
|
python -m python_pkg.word_frequency.anki_generator --file text.txt --length 20 --output polish_vocab.txt
|
||||||
|
|
||||||
|
# Include example sentences/context
|
||||||
|
python -m python_pkg.word_frequency.anki_generator --file text.txt --length 20 --include-context
|
||||||
|
|
||||||
|
Output:
|
||||||
|
Creates a semicolon-separated text file that can be imported into Anki.
|
||||||
|
Format: word;translation;frequency_rank;example_context (optional)
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from collections import Counter
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TYPE_CHECKING, NamedTuple
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
try:
|
||||||
|
from python_pkg.word_frequency.translator import (
|
||||||
|
detect_language,
|
||||||
|
translate_words_batch,
|
||||||
|
)
|
||||||
|
from python_pkg.word_frequency.analyzer import read_file, analyze_text
|
||||||
|
except ImportError:
|
||||||
|
from translator import detect_language, translate_words_batch
|
||||||
|
from analyzer import read_file, analyze_text
|
||||||
|
|
||||||
|
|
||||||
|
# Path to C vocabulary_curve executable
|
||||||
|
C_EXECUTABLE = Path(__file__).parent.parent.parent / "C" / "vocabulary_curve" / "vocabulary_curve"
|
||||||
|
|
||||||
|
|
||||||
|
class VocabWord(NamedTuple):
|
||||||
|
"""A vocabulary word with its metadata."""
|
||||||
|
|
||||||
|
word: str
|
||||||
|
rank: int
|
||||||
|
translation: str
|
||||||
|
context: str
|
||||||
|
|
||||||
|
|
||||||
|
def run_vocabulary_curve(filepath: Path, max_length: int) -> str:
|
||||||
|
"""Run the C vocabulary_curve executable.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filepath: Path to the text file.
|
||||||
|
max_length: Maximum excerpt length.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Output from the executable.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
FileNotFoundError: If executable not found.
|
||||||
|
subprocess.CalledProcessError: If execution fails.
|
||||||
|
"""
|
||||||
|
if not C_EXECUTABLE.exists():
|
||||||
|
raise FileNotFoundError(
|
||||||
|
f"C executable not found at {C_EXECUTABLE}. "
|
||||||
|
"Please compile it first: cd C/vocabulary_curve && make"
|
||||||
|
)
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
[str(C_EXECUTABLE), str(filepath), str(max_length)],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=120,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
return result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def parse_vocabulary_curve_output(output: str, target_length: int) -> tuple[str, list[tuple[str, int]]]:
|
||||||
|
"""Parse output from vocabulary_curve to get words needed.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
output: Raw output from vocabulary_curve.
|
||||||
|
target_length: The target excerpt length.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (excerpt_text, list of (word, rank) tuples).
|
||||||
|
"""
|
||||||
|
lines = output.split("\n")
|
||||||
|
excerpt = ""
|
||||||
|
words: list[tuple[str, int]] = []
|
||||||
|
|
||||||
|
# Find the line for the target length
|
||||||
|
i = 0
|
||||||
|
while i < len(lines):
|
||||||
|
line = lines[i]
|
||||||
|
if line.strip().startswith(f"[Length {target_length}]"):
|
||||||
|
# Found our target length, now get excerpt and words
|
||||||
|
i += 1
|
||||||
|
# Find excerpt line
|
||||||
|
while i < len(lines) and not lines[i].strip().startswith("Excerpt:"):
|
||||||
|
i += 1
|
||||||
|
if i < len(lines):
|
||||||
|
excerpt_line = lines[i].strip()
|
||||||
|
if '"' in excerpt_line:
|
||||||
|
start = excerpt_line.index('"') + 1
|
||||||
|
end = excerpt_line.rindex('"')
|
||||||
|
excerpt = excerpt_line[start:end]
|
||||||
|
|
||||||
|
# Find words line
|
||||||
|
i += 1
|
||||||
|
while i < len(lines) and not lines[i].strip().startswith("Words:"):
|
||||||
|
i += 1
|
||||||
|
if i < len(lines):
|
||||||
|
words_line = lines[i].strip()
|
||||||
|
if words_line.startswith("Words:"):
|
||||||
|
words_part = words_line[6:].strip()
|
||||||
|
# Parse "word(#rank), word2(#rank2), ..."
|
||||||
|
pattern = r"(\S+)\(#(\d+)\)"
|
||||||
|
matches = re.findall(pattern, words_part)
|
||||||
|
words = [(w, int(r)) for w, r in matches]
|
||||||
|
break
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return excerpt, words
|
||||||
|
|
||||||
|
|
||||||
|
def get_top_n_words(text: str, n: int) -> list[tuple[str, int]]:
|
||||||
|
"""Get the top N most frequent words from text.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: The source text.
|
||||||
|
n: Number of top words to return.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of (word, rank) tuples, ranked 1 to n.
|
||||||
|
"""
|
||||||
|
word_counts = analyze_text(text)
|
||||||
|
sorted_words = sorted(word_counts.items(), key=lambda x: (-x[1], x[0]))
|
||||||
|
return [(word, rank + 1) for rank, (word, _) in enumerate(sorted_words[:n])]
|
||||||
|
|
||||||
|
|
||||||
|
def find_word_contexts(
|
||||||
|
text: str,
|
||||||
|
words: list[str],
|
||||||
|
context_words: int = 5,
|
||||||
|
) -> dict[str, str]:
|
||||||
|
"""Find example contexts for each word in the text.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: The source text.
|
||||||
|
words: List of words to find contexts for.
|
||||||
|
context_words: Number of words of context on each side.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict mapping word to example context.
|
||||||
|
"""
|
||||||
|
# Extract all words preserving positions
|
||||||
|
all_words = re.findall(r"\b[\w]+\b", text, re.UNICODE)
|
||||||
|
all_words_lower = [w.lower() for w in all_words]
|
||||||
|
|
||||||
|
contexts: dict[str, str] = {}
|
||||||
|
words_lower = {w.lower() for w in words}
|
||||||
|
|
||||||
|
for target in words_lower:
|
||||||
|
# Find first occurrence
|
||||||
|
for i, word in enumerate(all_words_lower):
|
||||||
|
if word == target:
|
||||||
|
start = max(0, i - context_words)
|
||||||
|
end = min(len(all_words), i + context_words + 1)
|
||||||
|
context = " ".join(all_words[start:end])
|
||||||
|
contexts[target] = f"...{context}..."
|
||||||
|
break
|
||||||
|
|
||||||
|
return contexts
|
||||||
|
|
||||||
|
|
||||||
|
def generate_anki_deck(
|
||||||
|
words_with_ranks: list[tuple[str, int]],
|
||||||
|
source_lang: str,
|
||||||
|
target_lang: str = "en",
|
||||||
|
contexts: dict[str, str] | None = None,
|
||||||
|
deck_name: str = "Vocabulary",
|
||||||
|
include_context: bool = False,
|
||||||
|
no_translate: bool = False,
|
||||||
|
) -> str:
|
||||||
|
"""Generate Anki-compatible deck content.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
words_with_ranks: List of (word, rank) tuples.
|
||||||
|
source_lang: Source language code.
|
||||||
|
target_lang: Target language code (default: en).
|
||||||
|
contexts: Optional dict of word -> context.
|
||||||
|
deck_name: Name for the deck.
|
||||||
|
include_context: Whether to include context in cards.
|
||||||
|
no_translate: If True, skip translation (use placeholder).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Semicolon-separated content ready for Anki import.
|
||||||
|
"""
|
||||||
|
lines: list[str] = []
|
||||||
|
|
||||||
|
# Add Anki headers
|
||||||
|
lines.append(f"#separator:semicolon")
|
||||||
|
lines.append(f"#html:true")
|
||||||
|
lines.append(f"#deck:{deck_name}")
|
||||||
|
lines.append(f"#tags:vocabulary {source_lang}")
|
||||||
|
if include_context:
|
||||||
|
lines.append("#columns:Front;Back;Rank;Context")
|
||||||
|
else:
|
||||||
|
lines.append("#columns:Front;Back;Rank")
|
||||||
|
lines.append("") # Empty line before data
|
||||||
|
|
||||||
|
# Get translations (or skip if no_translate)
|
||||||
|
words = [w for w, _ in words_with_ranks]
|
||||||
|
if no_translate:
|
||||||
|
trans_lookup = {w.lower(): "[TODO]" for w in words}
|
||||||
|
else:
|
||||||
|
translations = translate_words_batch(words, source_lang, target_lang)
|
||||||
|
# Build translation lookup
|
||||||
|
trans_lookup = {}
|
||||||
|
for result in translations:
|
||||||
|
if result.success:
|
||||||
|
trans_lookup[result.source_word.lower()] = result.translated_word
|
||||||
|
else:
|
||||||
|
trans_lookup[result.source_word.lower()] = f"[{result.source_word}]"
|
||||||
|
|
||||||
|
# Generate cards
|
||||||
|
for word, rank in words_with_ranks:
|
||||||
|
translation = trans_lookup.get(word.lower(), f"[{word}]")
|
||||||
|
|
||||||
|
# Escape semicolons in fields
|
||||||
|
word_escaped = word.replace(";", ",")
|
||||||
|
translation_escaped = translation.replace(";", ",")
|
||||||
|
|
||||||
|
if include_context and contexts:
|
||||||
|
context = contexts.get(word.lower(), "")
|
||||||
|
# Highlight the word in context
|
||||||
|
if context:
|
||||||
|
context_escaped = context.replace(";", ",")
|
||||||
|
# Make target word bold in context
|
||||||
|
pattern = re.compile(re.escape(word), re.IGNORECASE)
|
||||||
|
context_escaped = pattern.sub(f"<b>{word}</b>", context_escaped)
|
||||||
|
else:
|
||||||
|
context_escaped = ""
|
||||||
|
lines.append(f"{word_escaped};{translation_escaped};#{rank};{context_escaped}")
|
||||||
|
else:
|
||||||
|
lines.append(f"{word_escaped};{translation_escaped};#{rank}")
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_flashcards(
|
||||||
|
filepath: str | Path,
|
||||||
|
excerpt_length: int,
|
||||||
|
source_lang: str | None = None,
|
||||||
|
target_lang: str = "en",
|
||||||
|
include_context: bool = False,
|
||||||
|
deck_name: str | None = None,
|
||||||
|
all_vocab: bool = True,
|
||||||
|
no_translate: bool = False,
|
||||||
|
) -> tuple[str, str, int, int]:
|
||||||
|
"""Generate Anki flashcards for vocabulary needed for an excerpt length.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filepath: Path to the source text file.
|
||||||
|
excerpt_length: Target excerpt length.
|
||||||
|
source_lang: Source language (auto-detected if None).
|
||||||
|
target_lang: Target language for translations.
|
||||||
|
include_context: Whether to include example contexts.
|
||||||
|
deck_name: Optional deck name.
|
||||||
|
all_vocab: If True, include ALL words from rank 1 to max rank needed.
|
||||||
|
If False, only include words that appear in the excerpt.
|
||||||
|
no_translate: If True, skip translation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (anki_content, excerpt, num_words, max_rank).
|
||||||
|
"""
|
||||||
|
filepath = Path(filepath)
|
||||||
|
|
||||||
|
# Read the text
|
||||||
|
text = read_file(filepath)
|
||||||
|
|
||||||
|
# Auto-detect language if not provided
|
||||||
|
if source_lang is None:
|
||||||
|
source_lang = detect_language(text)
|
||||||
|
if source_lang is None:
|
||||||
|
source_lang = "auto"
|
||||||
|
|
||||||
|
# Run vocabulary curve analysis
|
||||||
|
output = run_vocabulary_curve(filepath, excerpt_length)
|
||||||
|
|
||||||
|
# Parse the output
|
||||||
|
excerpt, excerpt_words = parse_vocabulary_curve_output(output, excerpt_length)
|
||||||
|
|
||||||
|
if not excerpt_words:
|
||||||
|
raise ValueError(f"No words found for excerpt length {excerpt_length}")
|
||||||
|
|
||||||
|
# Find max rank needed
|
||||||
|
max_rank = max(rank for _, rank in excerpt_words)
|
||||||
|
|
||||||
|
# Get ALL words up to max_rank if requested
|
||||||
|
if all_vocab:
|
||||||
|
words_with_ranks = get_top_n_words(text, max_rank)
|
||||||
|
else:
|
||||||
|
words_with_ranks = excerpt_words
|
||||||
|
|
||||||
|
# Get contexts if requested
|
||||||
|
contexts = None
|
||||||
|
if include_context:
|
||||||
|
words = [w for w, _ in words_with_ranks]
|
||||||
|
contexts = find_word_contexts(text, words)
|
||||||
|
|
||||||
|
# Generate deck name
|
||||||
|
if deck_name is None:
|
||||||
|
deck_name = f"{filepath.stem}_vocab_{excerpt_length}"
|
||||||
|
|
||||||
|
# Generate Anki content
|
||||||
|
anki_content = generate_anki_deck(
|
||||||
|
words_with_ranks,
|
||||||
|
source_lang,
|
||||||
|
target_lang,
|
||||||
|
contexts,
|
||||||
|
deck_name,
|
||||||
|
include_context,
|
||||||
|
no_translate,
|
||||||
|
)
|
||||||
|
|
||||||
|
return anki_content, excerpt, len(words_with_ranks), max_rank
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: Sequence[str] | None = None) -> int:
|
||||||
|
"""Main entry point.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
argv: Command line arguments.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Exit code.
|
||||||
|
"""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Generate Anki flashcards from vocabulary analysis.",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog=__doc__,
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--file",
|
||||||
|
"-f",
|
||||||
|
type=str,
|
||||||
|
required=True,
|
||||||
|
help="Path to the text file to analyze",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--length",
|
||||||
|
"-l",
|
||||||
|
type=int,
|
||||||
|
required=True,
|
||||||
|
help="Target excerpt length (how many words you want to understand)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--from",
|
||||||
|
"-F",
|
||||||
|
dest="source_lang",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="Source language code (e.g., 'pl', 'la', 'de'). Auto-detected if not specified.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--to",
|
||||||
|
"-T",
|
||||||
|
dest="target_lang",
|
||||||
|
type=str,
|
||||||
|
default="en",
|
||||||
|
help="Target language code for translations (default: 'en')",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--output",
|
||||||
|
"-o",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="Output file path (default: <filename>_anki_<length>.txt)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--include-context",
|
||||||
|
"-c",
|
||||||
|
action="store_true",
|
||||||
|
help="Include example context sentences in flashcards",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--deck-name",
|
||||||
|
"-d",
|
||||||
|
type=str,
|
||||||
|
default=None,
|
||||||
|
help="Name for the Anki deck (default: auto-generated)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--quiet",
|
||||||
|
"-q",
|
||||||
|
action="store_true",
|
||||||
|
help="Only output the file path, no status messages",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--excerpt-words-only",
|
||||||
|
"-e",
|
||||||
|
action="store_true",
|
||||||
|
help="Only include words that appear in the excerpt (default: include ALL words up to max rank)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-translate",
|
||||||
|
"-n",
|
||||||
|
action="store_true",
|
||||||
|
help="Skip translation (output words without translations)",
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
try:
|
||||||
|
filepath = Path(args.file)
|
||||||
|
if not filepath.exists():
|
||||||
|
print(f"Error: File not found: {args.file}", file=sys.stderr) # noqa: T201
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if not args.quiet:
|
||||||
|
print(f"Analyzing {filepath.name}...") # noqa: T201
|
||||||
|
print(f"Finding vocabulary for {args.length}-word excerpt...") # noqa: T201
|
||||||
|
|
||||||
|
# Generate flashcards
|
||||||
|
anki_content, excerpt, num_words, max_rank = generate_flashcards(
|
||||||
|
filepath,
|
||||||
|
args.length,
|
||||||
|
source_lang=args.source_lang,
|
||||||
|
target_lang=args.target_lang,
|
||||||
|
include_context=args.include_context,
|
||||||
|
deck_name=args.deck_name,
|
||||||
|
all_vocab=not args.excerpt_words_only,
|
||||||
|
no_translate=args.no_translate,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Determine output path
|
||||||
|
if args.output:
|
||||||
|
output_path = Path(args.output)
|
||||||
|
else:
|
||||||
|
output_path = filepath.parent / f"{filepath.stem}_anki_{args.length}.txt"
|
||||||
|
|
||||||
|
# Write output
|
||||||
|
output_path.write_text(anki_content, encoding="utf-8")
|
||||||
|
|
||||||
|
if not args.quiet:
|
||||||
|
print("") # noqa: T201
|
||||||
|
print("=" * 60) # noqa: T201
|
||||||
|
print("FLASHCARD GENERATION COMPLETE") # noqa: T201
|
||||||
|
print("=" * 60) # noqa: T201
|
||||||
|
print(f"Excerpt to understand ({args.length} words):") # noqa: T201
|
||||||
|
print(f' "{excerpt}"') # noqa: T201
|
||||||
|
print("") # noqa: T201
|
||||||
|
print(f"Max word rank needed: #{max_rank}") # noqa: T201
|
||||||
|
if args.excerpt_words_only:
|
||||||
|
print(f"Flashcards: {num_words} (excerpt words only)") # noqa: T201
|
||||||
|
else:
|
||||||
|
print(f"Flashcards: {num_words} (ALL words rank #1 to #{max_rank})") # noqa: T201
|
||||||
|
print(f"Output file: {output_path}") # noqa: T201
|
||||||
|
print("") # noqa: T201
|
||||||
|
print("To import into Anki:") # noqa: T201
|
||||||
|
print(" 1. Open Anki") # noqa: T201
|
||||||
|
print(" 2. File -> Import") # noqa: T201
|
||||||
|
print(f" 3. Select: {output_path}") # noqa: T201
|
||||||
|
print(" 4. Click Import") # noqa: T201
|
||||||
|
else:
|
||||||
|
print(output_path) # noqa: T201
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
print(f"Error: {e}", file=sys.stderr) # noqa: T201
|
||||||
|
return 1
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Error running vocabulary_curve: {e}", file=sys.stderr) # noqa: T201
|
||||||
|
return 1
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"Error: {e}", file=sys.stderr) # noqa: T201
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@ -0,0 +1,198 @@
|
|||||||
|
#separator:semicolon
|
||||||
|
#html:true
|
||||||
|
#deck:polish_pan_tadeusz_vocab_10
|
||||||
|
#tags:vocabulary pl
|
||||||
|
#columns:Front;Back;Rank;Context
|
||||||
|
|
||||||
|
i;and;#1;...modam<b>i</b> Początek sporu o Kusego <b>i</b> Sokoła Żale Wojsk<b>i</b>ego Ostatn<b>i</b> Woźny...
|
||||||
|
w;In;#2;...Po<b>w</b>rót panicza Spotkanie się pier<b>w</b>sze <b>w</b> pokoiku drugie u stołu <b>w</b>ażna...
|
||||||
|
się;myself;#3;...pierwsza Gospodarstwo Powrót panicza Spotkanie <b>się</b> pierwsze w pokoiku drugie u...
|
||||||
|
z;With;#4;...co gród <b>z</b>amkowy Nowogród<b>z</b>ki ochranias<b>z</b> <b>z</b> jego wiernym ludem Jak mnie...
|
||||||
|
na;on;#5;...Pan Tadeusz czyli ostatni zajazd <b>na</b> Litwie ISBN 978 83 288...
|
||||||
|
nie;NO;#6;...co pod strzechą zmieścić się <b>nie</b> może Widać że okolica obfita...
|
||||||
|
jak;How;#7;...Litwo Ojczyzno moja ty jesteś <b>jak</b> zdrowie Ile cię trzeba cenić...
|
||||||
|
do;down;#8;...wiernym ludem Jak mnie dziecko <b>do</b> zdrowia powróciłaś cudem Gdy od...
|
||||||
|
a;and;#9;...Gdzie p<b>a</b>nieńskim rumieńcem dzięcielin<b>a</b> p<b>a</b>ł<b>a</b> <b>a</b> wszystko przep<b>a</b>s<b>a</b>ne j<b>a</b>kby wstęgą miedzą...
|
||||||
|
że;That;#10;...daleka pobielane ściany Tym bielsze <b>że</b> odbite od ciemnej zieleni Topoli...
|
||||||
|
to;this;#11;...w Petersburgu mieszkała przed laty <b>to</b> nie był ochmistrzyni pokój Fortepiano...
|
||||||
|
o;about;#12;...u st<b>o</b>łu Ważna Sędzieg<b>o</b> nauka <b>o</b> grzeczn<b>o</b>ści P<b>o</b>dk<b>o</b>m<b>o</b>rzeg<b>o</b> uwagi p<b>o</b>lityczne nad...
|
||||||
|
za;for;#13;...do Twych świątyń progu Iść <b>za</b> wrócone życie podziękować Bogu Tak...
|
||||||
|
po;after;#14;...Widzę i opisuję bo tęsknię <b>po</b> tobie Panno święta co Jasnej...
|
||||||
|
już;Already;#15;...Moskali Siekąc wrogów a Praga <b>już</b> się wkoło pali Nawet stary...
|
||||||
|
tak;Yes;#16;...za wrócone życie podziękować Bogu <b>tak</b> nas powrócisz cudem na Ojczyzny...
|
||||||
|
co;What;#17;...tęsknię po tobie Panno święta <b>co</b> Jasnej bronisz Częstochowy I w...
|
||||||
|
od;From;#18;...do zdrowia powróciłaś cudem Gdy <b>od</b> płaczącej matki p<b>od</b> Twoją opiekę...
|
||||||
|
lecz;but;#19;...Stał dwór szlachecki z drzewa <b>lecz</b> podmurowany Świeciły się z daleka...
|
||||||
|
bo;because;#20;...całej ozdobie Widzę i opisuję <b>bo</b> tęsknię po tobie Panno święta...
|
||||||
|
gdy;When;#21;...dziecko do zdrowia powróciłaś cudem <b>gdy</b> od płaczącej matki pod Twoją...
|
||||||
|
ja;I;#22;...z talerzem ogórki Rzekł Muszę <b>ja</b> wam służyć moje panny córki...
|
||||||
|
pan;you;#23;...Adam Mickiewicz <b>pan</b> Tadeusz czyli ostatni zajazd na...
|
||||||
|
jest;Is;#24;...myśli wkrótce sprawić ci wesele <b>jest</b> z czego wybrać u nas...
|
||||||
|
ale;But;#25;...stało wody pełne naczynie blaszane <b>ale</b> nigdzie nie widać było ogrodniczki...
|
||||||
|
był;was;#26;...niebo miecz oburącz trzyma Takim <b>był</b> gdy przysięgał na stopniach ołtarzów...
|
||||||
|
nim;him;#27;...trzech mocarzów Albo sam na <b>nim</b> padnie Dalej w polskiej szacie...
|
||||||
|
rzekł;he said;#28;...młodszej przysunąwszy z talerzem ogórki <b>rzekł</b> Muszę ja wam służyć moje...
|
||||||
|
go;him;#29;...od ciemnej zieleni Topoli co <b>go</b> bronią od wiatrów jesieni Dom...
|
||||||
|
tylko;Just;#30;...Ile cię trzeba cenić ten <b>tylko</b> się dowie Kto cię stracił...
|
||||||
|
jako;as;#31;...chciwie ściany starodawne Ogląda czule <b>jako</b> swe znajome dawne Też same...
|
||||||
|
mnie;me;#32;...z jego wiernym ludem Jak <b>mnie</b> dziecko do zdrowia powróciłaś cudem...
|
||||||
|
mu;him;#33;...Wyszedł zmieszany i czuł że <b>mu</b> serce biło Głośno i sam...
|
||||||
|
tu;here;#34;...same portrety na ścianach wisiały <b>tu</b> Kościuszko w czamarce krakowskiej z...
|
||||||
|
on;he;#35;...teraz za domem urządzał wieczerzę <b>on</b> pana zastępuje i <b>on</b> w...
|
||||||
|
czy;Whether;#36;...Głośno i sam nie wiedział <b>czy</b> go miało śmieszyć To dziwaczne...
|
||||||
|
ten;this;#37;...zdrowie Ile cię trzeba cenić <b>ten</b> tylko się dowie Kto cię...
|
||||||
|
hrabia;count;#38;...panem Hrabią sporu I pan <b>hrabia</b> ma jutro sam zjechać do...
|
||||||
|
sędzia;judge;#39;...i obrok i siano Bo <b>sędzia</b> nigdy nie chciał według nowej...
|
||||||
|
tam;there;#40;...żniwo oglądają Pod lasem i <b>tam</b> pewnie na młodzież czekają Pójdziemy...
|
||||||
|
pod;under;#41;...cudem Gdy od płaczącej matki <b>pod</b> Twoją opiekę Ofiarowany martwą <b>pod</b>niosłem...
|
||||||
|
aż;until;#42;...Wonnymi powiewami kwiatów oddychając Oblicze <b>aż</b> na krzaki fijołkowe skłonił Oczyma...
|
||||||
|
dla;For;#43;...zbiera się na sądy graniczne <b>dla</b> skończenia dawnego z panem Hrabią...
|
||||||
|
u;at;#44;...się pierwsze w pokoik<b>u</b> dr<b>u</b>gie <b>u</b> stoł<b>u</b> Ważna Sędziego na<b>u</b>ka o...
|
||||||
|
nad;over;#45;...o grzeczności Podkomorzego uwagi polityczne <b>nad</b> modami Początek sporu o Kusego...
|
||||||
|
więc;So;#46;...nie bywa od mężczyzn widziana <b>więc</b> choć świadka nie miała założyła...
|
||||||
|
ich;their;#47;...stodoły Cieszą się z niezwyczajnej <b>ich</b> lekkości woły Właśnie z lasu...
|
||||||
|
tadeusz;tadeusz;#48;...Adam Mickiewicz Pan <b>tadeusz</b> czyli ostatni zajazd na Litwie...
|
||||||
|
tym;this;#49;...się z daleka pobielane ściany <b>tym</b> bielsze że odbite od ciemnej...
|
||||||
|
przed;Before;#50;...grusze siedzą Śród takich pól <b>przed</b> laty nad brzegiem ruczaju Na...
|
||||||
|
jeszcze;still;#51;...było ogrodniczki Tylko co wyszła <b>jeszcze</b> kołyszą się drzwiczki Świeżo trącone...
|
||||||
|
sam;alone;#52;...z Polski trzech mocarzów Albo <b>sam</b> na nim padnie Dalej w...
|
||||||
|
przy;by;#53;...I stodołę miał wielką i <b>przy</b> niej trzy stogi Użątku co...
|
||||||
|
przez;By;#54;...na błonie I wionęła ogrodem <b>przez</b> płotki <b>przez</b> kwiaty I po...
|
||||||
|
ze;That;#55;...pan Sędzia każe U niego <b>ze</b> dniem kończą pracę gospodar<b>ze</b> Pan...
|
||||||
|
bez;without;#56;...ślad widać nóżki Na piasku <b>bez</b> trzewika była i pończoszki Na...
|
||||||
|
gdzie;Where;#57;...rozmaitem Wyzłacanych pszenicą posrebrzanych żytem <b>gdzie</b> bursztynowy świerzop gryka jak śnieg...
|
||||||
|
jej;her;#58;...parkanie Stała młoda dziewczyna Białe <b>jej</b> ubranie Wysmukłą postać tylko aż...
|
||||||
|
kto;Who;#59;...cenić ten tylko się dowie <b>kto</b> cię stracił Dziś piękność twą...
|
||||||
|
ku;to;#60;...ze skoszonej łąki Wszystko bieży <b>ku</b> studni której ramię z drzewa...
|
||||||
|
wszyscy;all;#61;...łacinie Mężczyznom dano wódkę wtenczas <b>wszyscy</b> siedli I chołodziec litewski milcząc...
|
||||||
|
wojski;troops;#62;...Słudzy czekają nim się pan <b>wojski</b> ubierze Który teraz za domem...
|
||||||
|
było;was;#63;...blaszane Ale nigdzie nie widać <b>było</b> ogrodniczki Tylko co wyszła jeszcze...
|
||||||
|
choć;though;#64;...bywa od mężczyzn widziana Więc <b>choć</b> świadka nie miała założyła ręce...
|
||||||
|
potem;then;#65;...Naprzód dzieci małe Z dozorcą <b>potem</b> Sędzia szedł z Podkomorzyną Obok...
|
||||||
|
mi;me;#66;...urzędów Przynajmniej tom skorzystał że <b>mi</b> w moim domu Nikt nigdy...
|
||||||
|
miał;had;#67;...lecz zewsząd chędogi I stodołę <b>miał</b> wielką i przy niej trzy...
|
||||||
|
teraz;Now;#68;...się pan Wojski ubierze Który <b>teraz</b> za domem urządzał wieczerzę On...
|
||||||
|
dziś;today;#69;...się dowie Kto cię stracił <b>dziś</b> piękność twą w całej ozdobie...
|
||||||
|
jego;his;#70;...gród zamkowy Nowogródzki ochraniasz z <b>jego</b> wiernym ludem Jak mnie dziecko...
|
||||||
|
by;by;#71;...dziecinną radością pociągnął za sznurek <b>by</b> stary Dąbrowskiego usłyszeć mazurek Biegał...
|
||||||
|
ją;I;#72;...że niecierpliwa młodzież teraźniejsza Że <b>ją</b> nudzi rzecz długa choć najwymowniejsza...
|
||||||
|
oczy;Eyes;#73;...i czyje były odgadywał Przypadkiem <b>oczy</b> podniósł i tuż na parkanie...
|
||||||
|
domu;home;#74;...ogrodowych grządek Że w tym <b>domu</b> dostatek mieszka i porządek Brama...
|
||||||
|
niech;let;#75;...kłócić się o nie Więc <b>niech</b> jaśnie wielmożny Podkomorzy raczy Odwołać...
|
||||||
|
może;Maybe;#76;...pod strzechą zmieścić się nie <b>może</b> Widać że okolica obfita we...
|
||||||
|
kiedy;When;#77;...dziś nagodził Do domu właśnie <b>kiedy</b> mamy panien wiele Stryjaszek myśli...
|
||||||
|
jeśli;If;#78;...pewnie na młodzież czekają Pójdziemy <b>jeśli</b> zechcesz i wkrótce spotkamy Stryjaszka...
|
||||||
|
ma;has;#79;...Hrabią sporu I pan Hrabia <b>ma</b> jutro sam zjechać do dworu...
|
||||||
|
który;which;#80;...nim się pan Wojski ubierze <b>który</b> teraz za domem urządzał wieczerzę...
|
||||||
|
nas;us;#81;...wrócone życie podziękować Bogu Tak <b>nas</b> powrócisz cudem na Ojczyzny łono...
|
||||||
|
nawet;even;#82;...Praga już się wkoło pali <b>nawet</b> stary stojący zegar kurantowy W...
|
||||||
|
znowu;again;#83;...ciekawymi po drożynach gonił I <b>znowu</b> je na drobnych śladach zatrzymywał...
|
||||||
|
jakby;as if;#84;...dzięcielina pała A wszystko przepasane <b>jakby</b> wstęgą miedzą Zieloną na niej...
|
||||||
|
wszystko;All;#85;...panieńskim rumieńcem dzięcielina pała A <b>wszystko</b> przepasane jakby wstęgą miedzą Zieloną...
|
||||||
|
raz;once;#86;...studni której ramię z drzewa <b>raz</b> w<b>raz</b> skrzypi i napój w...
|
||||||
|
szlachta;gentry;#87;...śmieli otwierać On rzekł Wielmożni <b>szlachta</b> bracia dobrodzieje Forum myśliwskim tylko...
|
||||||
|
lub;or;#88;...że się nam zdawał małpą <b>lub</b> papugą W wielkiej peruce którą...
|
||||||
|
tej;this one;#89;...zdumione źrenice Po ścianach w <b>tej</b> komnacie mieszkanie kobiéce Któż by...
|
||||||
|
we;in;#90;...brzegiem ruczaju Na pagórku niewielkim <b>we</b> brzozowym gaju Stał dwór szlachecki...
|
||||||
|
cóż;Well;#91;...były pod lasem zwaliska Po <b>cóż</b> te przenosiny Pan Wojski się...
|
||||||
|
sobie;yourself;#92;...ukłonem Chciała usieść na miejscu <b>sobie</b> zostawionem Trudno było bo krzeseł...
|
||||||
|
też;Too;#93;...czule jako swe znajome dawne <b>też</b> same widzi sprzęty <b>też</b> same...
|
||||||
|
albo;or;#94;...wypędzi z Polski trzech mocarzów <b>albo</b> sam na nim padnie Dalej...
|
||||||
|
gerwazy;gerwazy;#95;...Krzyknąć nim głos Hrabiego usłyszał <b>gerwazy</b> Szlachcic to był służący dawnych...
|
||||||
|
ręce;hands;#96;...choć świadka nie miała założyła <b>ręce</b> Na piersiach przydawając zasłony sukience...
|
||||||
|
są;are;#97;...upodobał mury Tłumacząc że gotyckiej <b>są</b> architektury Choć Sędzia z dokumentów...
|
||||||
|
te;these;#98;...pod lasem zwaliska Po cóż <b>te</b> przenosiny Pan Wojski się krzywił...
|
||||||
|
będzie;will be;#99;...rana wiedział Że u wieczerzy <b>będzie</b> z mnóstwem gości siedział Pan...
|
||||||
|
między;between;#100;...po kądzieli A resztę rozdzielono <b>między</b> wierzycieli Zamku żaden wziąć nie...
|
||||||
|
mój;my;#101;...dzieje tego dnia powiadał Dobrze <b>mój</b> Tadeuszu bo tak nazywano Młodzieńca...
|
||||||
|
była;was;#102;...nóżki Na piasku bez trzewika <b>była</b> i pończoszki Na piasku drobnym...
|
||||||
|
głowy;head;#103;...nie przerywał Ale częstym skinieniem <b>głowy</b> potakiwał Sędzia milczał on jeszcze...
|
||||||
|
telimena;telimena;#104;...sąd pańskiej cioci Choć pani <b>telimena</b> mieszkała w stolicy I bawi...
|
||||||
|
bardzo;Very;#105;...oko nie zobaczy Bo biegła <b>bardzo</b> szybko suwała się raczéj Jako...
|
||||||
|
panie;sir;#106;...pani Telimena i panny i <b>panie</b> Słowem zrobim na urząd wielkie...
|
||||||
|
razem;Together;#107;...z łąk i z pastwisk <b>razem</b> wracało do dworu Tu owiec...
|
||||||
|
tadeusza;tadeusz;#108;...Otarł prędko jak kochał pana <b>tadeusza</b> W ślad gospodarza wszystko ze...
|
||||||
|
głowę;head;#109;...drobne strączki białe Dziwnie ozdabiał <b>głowę</b> bo od słońca blasku Świecił...
|
||||||
|
je;eats;#110;...miły Niestare były rączki co <b>je</b> tak rzuciły Tuż i sukienka...
|
||||||
|
klucznik;steward;#111;...Podobny do zdarzenia dzisiejszej obławy <b>klucznik</b> mówił że tylko znał jednego...
|
||||||
|
nic;nothing;#112;...Okna bez szyb lecz latem <b>nic</b> to nie zawadzi Bliskość piw<b>nic</b>...
|
||||||
|
robak;worm;#113;...Jegomość Nic a nic odpowiedział <b>robak</b> obojętnie Widać było że słuchał...
|
||||||
|
ręką;hand;#114;...miejscach swoich stali Jeden drugiemu <b>ręką</b> dawał znak milczenia A wszyscy...
|
||||||
|
ziemi;earth;#115;...kogoś co zaledwie dotykał się <b>ziemi</b> Podróżny długo w oknie stał...
|
||||||
|
ci;you;#116;...wiele Stryjaszek myśli wkrótce sprawić <b>ci</b> wesele Jest z czego wybrać...
|
||||||
|
dwa;two;#117;...taił inne ważniejsze przyczyny O <b>dwa</b> tysiące kroków zamek stał za...
|
||||||
|
gdyby;if;#118;...jedno pozostało Puste miejsce jak <b>gdyby</b> na kogoś czekało Stryj nieraz...
|
||||||
|
podkomorzy;chamberlain;#119;...jutro sam zjechać do dworu <b>podkomorzy</b> już zjechał z żoną i...
|
||||||
|
ty;you;#120;...i Europy Litwo Ojczyzno moja <b>ty</b> jesteś jak zdrowie Ile cię...
|
||||||
|
zamku;castle;#121;...tylu tak szanownych gości W <b>zamku</b> sień wielka jeszcze dobrze zachowana...
|
||||||
|
wszystkie;all;#122;...mógłby spojrzeć bez wstydu królewic <b>wszystkie</b> zacnie zrodzone każda młoda ładna...
|
||||||
|
krzyknął;he shouted;#123;...kształtu jeśli równie chwytny Chwytny <b>krzyknął</b> pan Rejent mój pies faworytny...
|
||||||
|
rzecz;thing;#124;...pustym oczy swe osadzał Dziwna <b>rzecz</b> miejsca wkoło są siedzeniem dziewic...
|
||||||
|
rękę;hand;#125;...synowcem witania Dał mu poważnie <b>rękę</b> do pocałowania I w skroń...
|
||||||
|
siebie;myself;#126;...naród Bo już sam wewnątrz <b>siebie</b> czuł choroby zaród Krzyczano na...
|
||||||
|
widać;can be seen;#127;...strzechą zmieścić się nie może <b>widać</b> że okolica obfita we zboże...
|
||||||
|
ani;or;#128;...Grzeczność nie jest nauką łatwą <b>ani</b> małą Niełatwą bo nie na...
|
||||||
|
które;which;#129;...świętą Bo nawet wozy w <b>które</b> już składać zaczęto Kopę żyta...
|
||||||
|
ledwie;hardly;#130;...oczyma rodziców którzy te pogonie <b>ledwie</b> raczyli widzieć cóż kłócić się...
|
||||||
|
ni;ni;#131;...co chce wytłumaczył Bernardyn odpowiedzieć <b>ni</b> spojrzeć <b>ni</b>e raczył Kaptur tylko...
|
||||||
|
tymczasem;meanwhile;#132;...powrócisz cudem na Ojczyzny łono <b>tymczasem</b> przenoś moją duszę utęsknioną Do...
|
||||||
|
wtem;suddenly;#133;...chwyciła suknie biegła do zwierciadła <b>wtem</b> ujrzała młodzieńca i z rąk...
|
||||||
|
zaraz;In a second;#134;...Ofiarowany martwą podniosłem powiekę I <b>zaraz</b> mogłem pieszo do Twych świątyń...
|
||||||
|
jeden;one;#135;...uciekły I dwie twarze w <b>jeden</b> się rumieniec oblekły Tadeusz by...
|
||||||
|
was;mustache;#136;...mu zawiążesz wierz mi kląć <b>was</b> kiedyś będzie Zakopać taki talent...
|
||||||
|
tego;this;#137;...nabadał Na samym końcu dzieje <b>tego</b> dnia powiadał Dobrze mój Tadeuszu...
|
||||||
|
zawsze;Always;#138;...gości obejrzał porządkiem Bo choć <b>zawsze</b> i płynnie mówił i z...
|
||||||
|
nich;them;#139;...drobnych śladach zatrzymywał Myślał o <b>nich</b> i czyje były odgadywał Przypadkiem...
|
||||||
|
nikt;nobody;#140;...pół kroku Tak każe przyzwoitość <b>nikt</b> tam nie rozprawiał O porządku...
|
||||||
|
wielki;great;#141;...Po której miał przyjść wkrótce <b>wielki</b> post niewola Pamiętam chociaż byłem...
|
||||||
|
coraz;increasingly;#142;...z Rejentem wzmogła się uparta <b>coraz</b> głośniejsza kłótnia o kusego charta...
|
||||||
|
czas;time;#143;...robotnik kiedy znijdzie z nieba <b>czas</b> i ziemianinowi ustępować z pola...
|
||||||
|
długo;long;#144;...zaledwie dotykał się ziemi Podróżny <b>długo</b> w oknie stał patrząc dumając...
|
||||||
|
nigdy;Never;#145;...zwykła z rana W takim <b>nigdy</b> nie bywa od mężczyzn widziana...
|
||||||
|
pana;sir;#146;...za domem urządzał wieczerzę On <b>pana</b> zastępuje i on w niebytności...
|
||||||
|
chciał;he wanted;#147;...młodzieniec oczy zmrużył i przysłonił <b>chciał</b> coś mówić przepraszać tylko się...
|
||||||
|
dotąd;so far;#148;...wojewoda Niesiołowski stary Który ma <b>dotąd</b> pierwsze na świecie ogary I...
|
||||||
|
ksiądz;priest;#149;...Pańskim pisano Zakonie I każdy <b>ksiądz</b> toż samo gada na ambonie...
|
||||||
|
przecież;yet;#150;...a pan może zyska Bo <b>przecież</b> o ten zamek dziś toczy...
|
||||||
|
ryków;roars;#151;...Moskal był to pan kapitan <b>ryków</b> Stary żołnierz stał w bliskiej...
|
||||||
|
tyle;so much;#152;...jego proszę Pana Boga Jeślim <b>tyle</b> na jego nie korzystał dworze...
|
||||||
|
wszystkich;everyone;#153;...przechodniom ogłasza Że gościnna i <b>wszystkich</b> w gościnę zaprasza Właśnie dwukonną...
|
||||||
|
zaś;and;#154;...był majstrem z Wilna nie <b>zaś</b> Gotem Dość że Hrabia chciał...
|
||||||
|
mam;I have;#155;...polityka nudzi jeżeli z Warszawy <b>mam</b> list to rzecz zakonna to...
|
||||||
|
strony;pages;#156;...Gdy tak były zajęte stołu <b>strony</b> obie Tadeusz przyglądał się nieznanej...
|
||||||
|
sędziego;judge;#157;...pokoiku drugie u stołu Ważna <b>sędziego</b> nauka o grzeczności Podkomorzego uwagi...
|
||||||
|
bóg;God;#158;...moc ta tłuszcza Bo Pan <b>bóg</b> kiedy karę na naród przypuszcza...
|
||||||
|
drzwi;door;#159;...bramę We dworze pusto bo <b>drzwi</b> od ganku zamknięto Zaszczepkami i...
|
||||||
|
gości;guests;#160;...ganek zajechał któryś z nowych <b>gości</b> Już konie w stajnią wzięto...
|
||||||
|
trzeba;it's necessary to;#161;...jesteś jak zdrowie Ile cię <b>trzeba</b> cenić ten tylko się dowie...
|
||||||
|
właśnie;Exactly;#162;...i wszystkich w gościnę zaprasza <b>właśnie</b> dwukonną bryką wjechał młody panek...
|
||||||
|
góry;mountains;#163;...niemałą chętkę do swawoli Z <b>góry</b> już robił projekt że sobie...
|
||||||
|
niby;kind of;#164;...różowymi wstęgi Pośród nich brylant <b>niby</b> zakryty od oczu Świecił się...
|
||||||
|
niej;her;#165;...jakby wstęgą miedzą Zieloną na <b>niej</b> z rzadka ciche grusze siedzą...
|
||||||
|
szlachty;nobility;#166;...myśliwi Widząc że w tylu <b>szlachty</b> w tylu panów gronie Mają...
|
||||||
|
wtenczas;then;#167;...po łacinie Mężczyznom dano wódkę <b>wtenczas</b> wszyscy siedli I chołodziec litewski...
|
||||||
|
ów;that;#168;...byle nie w nędzy Jak <b>ów</b> Wespazyjanus nie wąchał pieniędzy I...
|
||||||
|
dobrze;All right;#169;...zapewne należne do dworu Uprawne <b>dobrze</b> na kształt ogrodowych grządek Że...
|
||||||
|
każdy;everyone;#170;...i dam nie ustawiał A <b>każdy</b> mimowolnie porządku pilnował Bo Sędzia...
|
||||||
|
litwie;Lithuania;#171;...Tadeusz czyli ostatni zajazd na <b>litwie</b> ISBN 978 83 288 2495...
|
||||||
|
pierwszy;first;#172;...pamiętam czasy kiedy do ojczyzny <b>pierwszy</b> raz zawitała moda francuszczyzny Gdy...
|
||||||
|
stał;steel;#173;...pagórku niewielkim we brzozowym gaju <b>stał</b> dwór szlachecki z drzewa lecz...
|
||||||
|
stąd;hence;#174;...Objaśniają wrodzone wdzięki i przymioty <b>stąd</b> droga do afektów i <b>stąd</b>...
|
||||||
|
asesor;assessor;#175;...utrzymywał że on zająca pochwycił <b>asesor</b> zaś dowodził na złość Rejentowi...
|
||||||
|
dwóch;two;#176;...Stolnik ja pani Kuchmistrz i <b>dwóch</b> kuchcików wszyscy trzej pijani Proboszcz...
|
||||||
|
której;which one;#177;...ta prędka zmieszana rozmowa W <b>której</b> lat kilku dzieje chciano zamknąć...
|
||||||
|
my;we;#178;...i rzekł Dziś nowym zwyczajem <b>my</b> na naukę młodzież do stolicy...
|
||||||
|
serce;heart;#179;...zmieszany i czuł że mu <b>serce</b> biło Głośno i sam nie...
|
||||||
|
wkoło;around;#180;...wrogów a Praga już się <b>wkoło</b> pali Nawet stary stojący zegar...
|
||||||
|
wnet;soon;#181;...okiennic szpary I zgasło I <b>wnet</b> sierpy gromadnie dzwoniące We zbożach...
|
||||||
|
która;which;#182;...progiem Stanęła Podczaszyca dwukolna dryndulka <b>która</b> się po francusku zwała karyjulka...
|
||||||
|
prawda;True;#183;...by nie zdradzić swego roztargnienia <b>prawda</b> rzekł mój Rejencie <b>prawda</b> bez...
|
||||||
|
przerwał;interrupted;#184;...końcu z Bonapartą Tu Ryków <b>przerwał</b> i jadł wtem z potrawą...
|
||||||
|
tych;these;#185;...przenoś moją duszę utęsknioną Do <b>tych</b> pagórków leśnych do <b>tych</b> łąk...
|
||||||
|
zosia;Zosia;#186;...poznać Prawda bardzo młodzi Szczególnie <b>zosia</b> mała lecz to nic nie...
|
||||||
|
chociaż;though;#187;...koryta rozlewa Sędzia choć utrudzony <b>chociaż</b> w gronie gości Nie chybił...
|
||||||
|
cię;you;#188;...ty jesteś jak zdrowie Ile <b>cię</b> trzeba cenić ten tylko się...
|
||||||
|
koniec;end;#189;...Maleski z Mickiewiczem a na <b>koniec</b> Hrabia Z Soplicą i czytając...
|
||||||
|
których;which;#190;...zabawia przez rozmowy grzeczne Z <b>których</b> by wychowanie poznano stołeczne To...
|
||||||
|
okiem;eye;#191;...końca doczekał nareszcie Wbiega i <b>okiem</b> chciwie ściany starodawne Ogląda czule...
|
||||||
|
rejent;notary;#192;...kusego charta Którego posiadaniem pan <b>rejent</b> się szczycił I utrzymywał że...
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
#separator:semicolon
|
||||||
|
#html:true
|
||||||
|
#deck:polish_pan_tadeusz_vocab_30
|
||||||
|
#tags:vocabulary pl
|
||||||
|
#columns:Front;Back;Rank;Context
|
||||||
|
|
||||||
|
i;and;#1;...modam<b>i</b> Początek sporu o Kusego <b>i</b> Sokoła Żale Wojsk<b>i</b>ego Ostatn<b>i</b> Woźny...
|
||||||
|
się;myself;#3;...pierwsza Gospodarstwo Powrót panicza Spotkanie <b>się</b> pierwsze w pokoiku drugie u...
|
||||||
|
z;With;#4;...co gród <b>z</b>amkowy Nowogród<b>z</b>ki ochranias<b>z</b> <b>z</b> jego wiernym ludem Jak mnie...
|
||||||
|
za;for;#14;...do Twych świątyń progu Iść <b>za</b> wrócone życie podziękować Bogu Tak...
|
||||||
|
nim;him;#29;...trzech mocarzów Albo sam na <b>nim</b> padnie Dalej w polskiej szacie...
|
||||||
|
mu;him;#34;...Wyszedł zmieszany i czuł że <b>mu</b> serce biło Głośno i sam...
|
||||||
|
ten;this;#39;...zdrowie Ile cię trzeba cenić <b>ten</b> tylko się dowie Kto cię...
|
||||||
|
sędzia;judge;#42;...i obrok i siano Bo <b>sędzia</b> nigdy nie chciał według nowej...
|
||||||
|
przy;by;#54;...I stodołę miał wielką i <b>przy</b> niej trzy stogi Użątku co...
|
||||||
|
podkomorzy;chamberlain;#120;...jutro sam zjechać do dworu <b>podkomorzy</b> już zjechał z żoną i...
|
||||||
|
stał;steel;#172;...pagórku niewielkim we brzozowym gaju <b>stał</b> dwór szlachecki z drzewa lecz...
|
||||||
|
tuż;next door;#205;...rączki co je tak rzuciły <b>tuż</b> i sukienka biała świeżo z...
|
||||||
|
należy;should;#351;...i z urzędu ten zaszczyt <b>należy</b> Idąc kłaniał się damom starcom...
|
||||||
|
miejsce;place;#370;...stanęli kołem Podkomorzy najwyższe brał <b>miejsce</b> za stołem Z wieku mu...
|
||||||
|
kwestarz;collection;#589;...i młodzieży Przy nim stał <b>kwestarz</b> Sędzia tuż przy bernardynie Bernardyn...
|
||||||
|
stołem;table;#666;...Podkomorzy najwyższe brał miejsce za <b>stołem</b> Z wieku mu i z...
|
||||||
|
idąc;walking;#667;...z urzędu ten zaszczyt należy <b>idąc</b> kłaniał się damom starcom i...
|
||||||
|
wieku;century;#735;...dozwalał by chybiano względu Dla <b>wieku</b> urodzenia rozumu urzędu Tym ładem...
|
||||||
|
urzędu;office;#967;...względu Dla wieku urodzenia rozumu <b>urzędu</b> Tym ładem mawiał domy i...
|
||||||
|
brał;he took;#1139;...i stanęli kołem Podkomorzy najwyższe <b>brał</b> miejsce za stołem Z wieku...
|
||||||
|
kłaniał;he bowed;#1140;...urzędu ten zaszczyt należy Idąc <b>kłaniał</b> się damom starcom i młodzieży...
|
||||||
|
młodzieży;youth;#1141;...kłaniał się damom starcom i <b>młodzieży</b> Przy nim stał kwestarz Sędzia...
|
||||||
|
damom;ladies;#1355;...zaszczyt należy Idąc kłaniał się <b>damom</b> starcom i młodzieży Przy nim...
|
||||||
|
kołem;wheel;#1671;...weszli w porządku i stanęli <b>kołem</b> Podkomorzy najwyższe brał miejsce za...
|
||||||
|
najwyższe;highest;#1672;...porządku i stanęli kołem Podkomorzy <b>najwyższe</b> brał miejsce za stołem Z...
|
||||||
|
zaszczyt;honor;#2110;...mu i z urzędu ten <b>zaszczyt</b> należy Idąc kłaniał się damom...
|
||||||
|
starcom;old men;#2111;...należy Idąc kłaniał się damom <b>starcom</b> i młodzieży Przy nim stał...
|
||||||
380
python_pkg/word_frequency/tests/test_anki_generator.py
Normal file
380
python_pkg/word_frequency/tests/test_anki_generator.py
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Tests for the Anki flashcard generator."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
try:
|
||||||
|
from python_pkg.word_frequency.anki_generator import (
|
||||||
|
find_word_contexts,
|
||||||
|
generate_anki_deck,
|
||||||
|
generate_flashcards,
|
||||||
|
get_top_n_words,
|
||||||
|
main,
|
||||||
|
parse_vocabulary_curve_output,
|
||||||
|
)
|
||||||
|
except ImportError:
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))
|
||||||
|
from python_pkg.word_frequency.anki_generator import (
|
||||||
|
find_word_contexts,
|
||||||
|
generate_anki_deck,
|
||||||
|
generate_flashcards,
|
||||||
|
get_top_n_words,
|
||||||
|
main,
|
||||||
|
parse_vocabulary_curve_output,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Test fixtures
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_vocabulary_output() -> str:
|
||||||
|
"""Sample output from vocabulary_curve."""
|
||||||
|
return """======================================================================
|
||||||
|
VOCABULARY LEARNING CURVE
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
Total words in text: 100
|
||||||
|
Unique words: 50
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
[Length 1] Vocab needed: 1 (+1)
|
||||||
|
Excerpt: "the"
|
||||||
|
Words: the(#1)
|
||||||
|
|
||||||
|
[Length 2] Vocab needed: 2 (+1)
|
||||||
|
Excerpt: "the dog"
|
||||||
|
Words: the(#1), dog(#2)
|
||||||
|
|
||||||
|
[Length 3] Vocab needed: 5 (+3)
|
||||||
|
Excerpt: "the quick fox"
|
||||||
|
Words: the(#1), quick(#3), fox(#5)
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def sample_text_file(tmp_path: Path) -> Path:
|
||||||
|
"""Create a sample text file."""
|
||||||
|
text = """The quick brown fox jumps over the lazy dog.
|
||||||
|
The fox was very quick and the dog was very lazy.
|
||||||
|
Quick foxes and lazy dogs are common in stories."""
|
||||||
|
filepath = tmp_path / "sample.txt"
|
||||||
|
filepath.write_text(text, encoding="utf-8")
|
||||||
|
return filepath
|
||||||
|
|
||||||
|
|
||||||
|
# Tests for parse_vocabulary_curve_output
|
||||||
|
|
||||||
|
|
||||||
|
class TestParseVocabularyCurveOutput:
|
||||||
|
"""Tests for parsing vocabulary_curve output."""
|
||||||
|
|
||||||
|
def test_parse_length_1(self, sample_vocabulary_output: str) -> None:
|
||||||
|
"""Test parsing output for length 1."""
|
||||||
|
excerpt, words = parse_vocabulary_curve_output(sample_vocabulary_output, 1)
|
||||||
|
assert excerpt == "the"
|
||||||
|
assert words == [("the", 1)]
|
||||||
|
|
||||||
|
def test_parse_length_2(self, sample_vocabulary_output: str) -> None:
|
||||||
|
"""Test parsing output for length 2."""
|
||||||
|
excerpt, words = parse_vocabulary_curve_output(sample_vocabulary_output, 2)
|
||||||
|
assert excerpt == "the dog"
|
||||||
|
assert words == [("the", 1), ("dog", 2)]
|
||||||
|
|
||||||
|
def test_parse_length_3(self, sample_vocabulary_output: str) -> None:
|
||||||
|
"""Test parsing output for length 3."""
|
||||||
|
excerpt, words = parse_vocabulary_curve_output(sample_vocabulary_output, 3)
|
||||||
|
assert excerpt == "the quick fox"
|
||||||
|
assert len(words) == 3
|
||||||
|
assert ("the", 1) in words
|
||||||
|
assert ("quick", 3) in words
|
||||||
|
assert ("fox", 5) in words
|
||||||
|
|
||||||
|
def test_parse_nonexistent_length(self, sample_vocabulary_output: str) -> None:
|
||||||
|
"""Test parsing output for non-existent length."""
|
||||||
|
excerpt, words = parse_vocabulary_curve_output(sample_vocabulary_output, 100)
|
||||||
|
assert excerpt == ""
|
||||||
|
assert words == []
|
||||||
|
|
||||||
|
|
||||||
|
# Tests for find_word_contexts
|
||||||
|
|
||||||
|
|
||||||
|
class TestFindWordContexts:
|
||||||
|
"""Tests for finding word contexts."""
|
||||||
|
|
||||||
|
def test_find_single_word_context(self) -> None:
|
||||||
|
"""Test finding context for a single word."""
|
||||||
|
text = "The quick brown fox jumps over the lazy dog"
|
||||||
|
contexts = find_word_contexts(text, ["fox"], context_words=2)
|
||||||
|
assert "fox" in contexts
|
||||||
|
assert "fox" in contexts["fox"].lower()
|
||||||
|
|
||||||
|
def test_find_multiple_word_contexts(self) -> None:
|
||||||
|
"""Test finding contexts for multiple words."""
|
||||||
|
text = "The quick brown fox jumps over the lazy dog"
|
||||||
|
contexts = find_word_contexts(text, ["fox", "dog"], context_words=2)
|
||||||
|
assert len(contexts) == 2
|
||||||
|
assert "fox" in contexts
|
||||||
|
assert "dog" in contexts
|
||||||
|
|
||||||
|
def test_word_not_found(self) -> None:
|
||||||
|
"""Test when word is not in text."""
|
||||||
|
text = "The quick brown fox"
|
||||||
|
contexts = find_word_contexts(text, ["elephant"], context_words=2)
|
||||||
|
assert "elephant" not in contexts
|
||||||
|
|
||||||
|
|
||||||
|
# Tests for generate_anki_deck
|
||||||
|
|
||||||
|
|
||||||
|
class TestGenerateAnkiDeck:
|
||||||
|
"""Tests for generating Anki deck content."""
|
||||||
|
|
||||||
|
def test_generates_valid_header(self) -> None:
|
||||||
|
"""Test that output contains valid Anki headers."""
|
||||||
|
with patch(
|
||||||
|
"python_pkg.word_frequency.anki_generator.translate_words_batch"
|
||||||
|
) as mock_translate:
|
||||||
|
mock_translate.return_value = [
|
||||||
|
MagicMock(success=True, source_word="hello", translated_word="hola")
|
||||||
|
]
|
||||||
|
result = generate_anki_deck(
|
||||||
|
[("hello", 1)],
|
||||||
|
source_lang="en",
|
||||||
|
target_lang="es",
|
||||||
|
deck_name="TestDeck",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "#separator:semicolon" in result
|
||||||
|
assert "#deck:TestDeck" in result
|
||||||
|
assert "#html:true" in result
|
||||||
|
|
||||||
|
def test_generates_flashcard_content(self) -> None:
|
||||||
|
"""Test that output contains flashcard data."""
|
||||||
|
with patch(
|
||||||
|
"python_pkg.word_frequency.anki_generator.translate_words_batch"
|
||||||
|
) as mock_translate:
|
||||||
|
mock_translate.return_value = [
|
||||||
|
MagicMock(success=True, source_word="hello", translated_word="hola"),
|
||||||
|
MagicMock(success=True, source_word="world", translated_word="mundo"),
|
||||||
|
]
|
||||||
|
result = generate_anki_deck(
|
||||||
|
[("hello", 1), ("world", 2)],
|
||||||
|
source_lang="en",
|
||||||
|
target_lang="es",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check that words and translations are present
|
||||||
|
assert "hello" in result
|
||||||
|
assert "hola" in result
|
||||||
|
assert "world" in result
|
||||||
|
assert "mundo" in result
|
||||||
|
|
||||||
|
def test_includes_rank(self) -> None:
|
||||||
|
"""Test that rank is included in output."""
|
||||||
|
with patch(
|
||||||
|
"python_pkg.word_frequency.anki_generator.translate_words_batch"
|
||||||
|
) as mock_translate:
|
||||||
|
mock_translate.return_value = [
|
||||||
|
MagicMock(success=True, source_word="test", translated_word="prueba")
|
||||||
|
]
|
||||||
|
result = generate_anki_deck(
|
||||||
|
[("test", 42)],
|
||||||
|
source_lang="en",
|
||||||
|
target_lang="es",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "#42" in result
|
||||||
|
|
||||||
|
def test_escapes_semicolons(self) -> None:
|
||||||
|
"""Test that semicolons in words are escaped."""
|
||||||
|
with patch(
|
||||||
|
"python_pkg.word_frequency.anki_generator.translate_words_batch"
|
||||||
|
) as mock_translate:
|
||||||
|
mock_translate.return_value = [
|
||||||
|
MagicMock(
|
||||||
|
success=True, source_word="test;word", translated_word="translation"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
result = generate_anki_deck(
|
||||||
|
[("test;word", 1)],
|
||||||
|
source_lang="en",
|
||||||
|
target_lang="es",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Semicolons should be replaced with commas
|
||||||
|
assert "test,word" in result
|
||||||
|
|
||||||
|
def test_includes_context_when_requested(self) -> None:
|
||||||
|
"""Test that context is included when requested."""
|
||||||
|
with patch(
|
||||||
|
"python_pkg.word_frequency.anki_generator.translate_words_batch"
|
||||||
|
) as mock_translate:
|
||||||
|
mock_translate.return_value = [
|
||||||
|
MagicMock(success=True, source_word="hello", translated_word="hola")
|
||||||
|
]
|
||||||
|
contexts = {"hello": "...say hello to..."}
|
||||||
|
result = generate_anki_deck(
|
||||||
|
[("hello", 1)],
|
||||||
|
source_lang="en",
|
||||||
|
target_lang="es",
|
||||||
|
contexts=contexts,
|
||||||
|
include_context=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "Context" in result
|
||||||
|
assert "say" in result
|
||||||
|
|
||||||
|
def test_no_translate_flag(self) -> None:
|
||||||
|
"""Test that no_translate skips translation."""
|
||||||
|
result = generate_anki_deck(
|
||||||
|
[("hello", 1), ("world", 2)],
|
||||||
|
source_lang="en",
|
||||||
|
target_lang="es",
|
||||||
|
no_translate=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Should have [TODO] placeholders
|
||||||
|
assert "[TODO]" in result
|
||||||
|
assert "hello" in result
|
||||||
|
assert "world" in result
|
||||||
|
|
||||||
|
|
||||||
|
# Tests for get_top_n_words
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetTopNWords:
|
||||||
|
"""Tests for getting top N words."""
|
||||||
|
|
||||||
|
def test_get_top_5_words(self) -> None:
|
||||||
|
"""Test getting top 5 words from text."""
|
||||||
|
text = "the cat sat on the mat the cat meowed"
|
||||||
|
words = get_top_n_words(text, 5)
|
||||||
|
assert len(words) == 5
|
||||||
|
# 'the' appears 3x, 'cat' appears 2x
|
||||||
|
assert words[0][0] == "the"
|
||||||
|
assert words[0][1] == 1
|
||||||
|
assert words[1][0] == "cat"
|
||||||
|
assert words[1][1] == 2
|
||||||
|
|
||||||
|
def test_ranks_are_sequential(self) -> None:
|
||||||
|
"""Test that ranks are 1-based and sequential."""
|
||||||
|
text = "one two three four five six seven eight"
|
||||||
|
words = get_top_n_words(text, 8)
|
||||||
|
ranks = [r for _, r in words]
|
||||||
|
assert ranks == [1, 2, 3, 4, 5, 6, 7, 8]
|
||||||
|
|
||||||
|
|
||||||
|
# Tests for main function
|
||||||
|
|
||||||
|
|
||||||
|
class TestMain:
|
||||||
|
"""Tests for the main CLI function."""
|
||||||
|
|
||||||
|
def test_missing_file_returns_error(self) -> None:
|
||||||
|
"""Test that missing file returns error code."""
|
||||||
|
result = main(["--file", "nonexistent.txt", "--length", "10"])
|
||||||
|
assert result == 1
|
||||||
|
|
||||||
|
def test_help_flag(self, capsys: pytest.CaptureFixture[str]) -> None:
|
||||||
|
"""Test that --help works."""
|
||||||
|
with pytest.raises(SystemExit) as exc_info:
|
||||||
|
main(["--help"])
|
||||||
|
assert exc_info.value.code == 0
|
||||||
|
|
||||||
|
|
||||||
|
# Integration tests
|
||||||
|
|
||||||
|
|
||||||
|
class TestIntegration:
|
||||||
|
"""Integration tests (require C executable)."""
|
||||||
|
|
||||||
|
def test_generate_flashcards_creates_output(
|
||||||
|
self, sample_text_file: Path, tmp_path: Path
|
||||||
|
) -> None:
|
||||||
|
"""Test that generate_flashcards produces output file."""
|
||||||
|
from python_pkg.word_frequency.anki_generator import C_EXECUTABLE
|
||||||
|
|
||||||
|
if not C_EXECUTABLE.exists():
|
||||||
|
pytest.skip("C executable not found")
|
||||||
|
|
||||||
|
output_file = tmp_path / "output.txt"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"python_pkg.word_frequency.anki_generator.translate_words_batch"
|
||||||
|
) as mock_translate:
|
||||||
|
# Mock translation to avoid network calls
|
||||||
|
def mock_translate_fn(
|
||||||
|
words: list[str], from_lang: str, to_lang: str
|
||||||
|
) -> list[MagicMock]:
|
||||||
|
return [
|
||||||
|
MagicMock(success=True, source_word=w, translated_word=f"[{w}]")
|
||||||
|
for w in words
|
||||||
|
]
|
||||||
|
|
||||||
|
mock_translate.side_effect = mock_translate_fn
|
||||||
|
|
||||||
|
result = main(
|
||||||
|
[
|
||||||
|
"--file",
|
||||||
|
str(sample_text_file),
|
||||||
|
"--length",
|
||||||
|
"5",
|
||||||
|
"--output",
|
||||||
|
str(output_file),
|
||||||
|
"--quiet",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == 0
|
||||||
|
assert output_file.exists()
|
||||||
|
|
||||||
|
content = output_file.read_text()
|
||||||
|
assert "#separator:semicolon" in content
|
||||||
|
|
||||||
|
def test_cli_with_sample_file(
|
||||||
|
self, sample_text_file: Path, tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
||||||
|
) -> None:
|
||||||
|
"""Test CLI with actual file."""
|
||||||
|
from python_pkg.word_frequency.anki_generator import C_EXECUTABLE
|
||||||
|
|
||||||
|
if not C_EXECUTABLE.exists():
|
||||||
|
pytest.skip("C executable not found")
|
||||||
|
|
||||||
|
output_file = tmp_path / "anki_output.txt"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"python_pkg.word_frequency.anki_generator.translate_words_batch"
|
||||||
|
) as mock_translate:
|
||||||
|
mock_translate.return_value = [
|
||||||
|
MagicMock(success=True, source_word="the", translated_word="le")
|
||||||
|
]
|
||||||
|
|
||||||
|
result = main(
|
||||||
|
[
|
||||||
|
"--file",
|
||||||
|
str(sample_text_file),
|
||||||
|
"--length",
|
||||||
|
"1",
|
||||||
|
"--output",
|
||||||
|
str(output_file),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == 0
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert "FLASHCARD GENERATION COMPLETE" in captured.out
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
pytest.main([__file__, "-v"])
|
||||||
Loading…
Reference in New Issue
Block a user