f
This commit is contained in:
16
mytest.py
16
mytest.py
@@ -1,16 +0,0 @@
|
||||
from tarot import Tarot, letter, number, kaballah
|
||||
from temporal import ThalemaClock
|
||||
from datetime import datetime
|
||||
from utils import Personality, MBTIType
|
||||
|
||||
from tarot.ui import display_cards,display_cube
|
||||
from tarot.deck import Deck
|
||||
|
||||
# Get some cards
|
||||
deck = Deck()
|
||||
cards = Tarot.deck.card.filter(suit="cups",type="ace")
|
||||
|
||||
print(cards)
|
||||
# Display using default deck
|
||||
display_cards(cards)
|
||||
)
|
||||
@@ -15,8 +15,8 @@ Usage:
|
||||
direction = kaballah.Cube.direction("North", "East")
|
||||
"""
|
||||
|
||||
from .tree import Tree
|
||||
from .cube import Cube
|
||||
from .tree import Tree
|
||||
|
||||
# Export classes for fluent access
|
||||
__all__ = ["Tree", "Cube"]
|
||||
|
||||
@@ -6,22 +6,22 @@ including Sephira, Paths, and Tree of Life structures.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List, Optional, Tuple, Any
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from utils.attributes import (
|
||||
Element,
|
||||
ElementType,
|
||||
Planet,
|
||||
Color,
|
||||
Colorscale,
|
||||
Perfume,
|
||||
ElementType,
|
||||
God,
|
||||
Perfume,
|
||||
Planet,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Sephera:
|
||||
"""Represents a Sephira on the Tree of Life."""
|
||||
|
||||
number: int
|
||||
name: str
|
||||
hebrew_name: str
|
||||
@@ -29,21 +29,22 @@ class Sephera:
|
||||
archangel: str
|
||||
order_of_angels: str
|
||||
mundane_chakra: str
|
||||
element: Optional['ElementType'] = None
|
||||
element: Optional["ElementType"] = None
|
||||
planetary_ruler: Optional[str] = None
|
||||
tarot_trump: Optional[str] = None
|
||||
colorscale: Optional['Colorscale'] = None
|
||||
colorscale: Optional["Colorscale"] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class PeriodicTable:
|
||||
"""Represents a Sephirothic position in Kabbalah with cross-correspondences."""
|
||||
|
||||
number: int
|
||||
name: str
|
||||
sephera: Optional[Sephera]
|
||||
element: Optional['ElementType'] = None
|
||||
planet: Optional['Planet'] = None
|
||||
color: Optional['Color'] = None
|
||||
element: Optional["ElementType"] = None
|
||||
planet: Optional["Planet"] = None
|
||||
color: Optional["Color"] = None
|
||||
tarot_trump: Optional[str] = None
|
||||
hebrew_letter: Optional[str] = None
|
||||
divine_name: Optional[str] = None
|
||||
@@ -55,6 +56,7 @@ class PeriodicTable:
|
||||
@dataclass
|
||||
class TreeOfLife:
|
||||
"""Represents the Tree of Life structure."""
|
||||
|
||||
sephiroth: Dict[int, str]
|
||||
paths: Dict[Tuple[int, int], str]
|
||||
|
||||
@@ -62,6 +64,7 @@ class TreeOfLife:
|
||||
@dataclass
|
||||
class Correspondences:
|
||||
"""Represents Kabbalistic correspondences."""
|
||||
|
||||
number: int
|
||||
sephira: str
|
||||
element: Optional[str]
|
||||
@@ -76,18 +79,19 @@ class Correspondences:
|
||||
@dataclass
|
||||
class Path:
|
||||
"""Represents one of the 22 Paths on the Tree of Life with full correspondences."""
|
||||
|
||||
number: int # 11-32
|
||||
hebrew_letter: str # Hebrew letter name (Aleph through Tau)
|
||||
transliteration: str # English transliteration
|
||||
tarot_trump: str # Major Arcana card (0-XXI)
|
||||
sephera_from: Optional['Sephera'] = None # Lower Sephira
|
||||
sephera_to: Optional['Sephera'] = None # Upper Sephira
|
||||
element: Optional['ElementType'] = None # Element (Air, Fire, Water, Earth)
|
||||
planet: Optional['Planet'] = None # Planetary ruler
|
||||
sephera_from: Optional["Sephera"] = None # Lower Sephira
|
||||
sephera_to: Optional["Sephera"] = None # Upper Sephira
|
||||
element: Optional["ElementType"] = None # Element (Air, Fire, Water, Earth)
|
||||
planet: Optional["Planet"] = None # Planetary ruler
|
||||
zodiac_sign: Optional[str] = None # Zodiac sign (12 paths only)
|
||||
colorscale: Optional['Colorscale'] = None # Golden Dawn color scales
|
||||
perfumes: List['Perfume'] = field(default_factory=list)
|
||||
gods: Dict[str, List['God']] = field(default_factory=dict)
|
||||
colorscale: Optional["Colorscale"] = None # Golden Dawn color scales
|
||||
perfumes: List["Perfume"] = field(default_factory=list)
|
||||
gods: Dict[str, List["God"]] = field(default_factory=dict)
|
||||
keywords: List[str] = field(default_factory=list)
|
||||
description: str = ""
|
||||
|
||||
@@ -108,23 +112,23 @@ class Path:
|
||||
"""Check if this path has zodiac correspondence."""
|
||||
return self.zodiac_sign is not None
|
||||
|
||||
def add_god(self, god: 'God') -> None:
|
||||
def add_god(self, god: "God") -> None:
|
||||
"""Attach a god to this path grouped by culture."""
|
||||
culture_key = god.culture_key()
|
||||
culture_bucket = self.gods.setdefault(culture_key, [])
|
||||
if god not in culture_bucket:
|
||||
culture_bucket.append(god)
|
||||
|
||||
def add_perfume(self, perfume: 'Perfume') -> None:
|
||||
def add_perfume(self, perfume: "Perfume") -> None:
|
||||
"""Attach a perfume correspondence if it is not already present."""
|
||||
if perfume not in self.perfumes:
|
||||
self.perfumes.append(perfume)
|
||||
|
||||
def get_gods(self, culture: Optional[str] = None) -> List['God']:
|
||||
def get_gods(self, culture: Optional[str] = None) -> List["God"]:
|
||||
"""Return all gods for this path, optionally filtered by culture."""
|
||||
if culture:
|
||||
return list(self.gods.get(culture.lower(), []))
|
||||
merged: List['God'] = []
|
||||
merged: List["God"] = []
|
||||
for values in self.gods.values():
|
||||
merged.extend(values)
|
||||
return merged
|
||||
@@ -148,7 +152,7 @@ class Path:
|
||||
|
||||
# Element
|
||||
if self.element:
|
||||
element_name = self.element.name if hasattr(self.element, 'name') else str(self.element)
|
||||
element_name = self.element.name if hasattr(self.element, "name") else str(self.element)
|
||||
lines.append(f"element: {element_name}")
|
||||
|
||||
# Planet
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Cube namespace - access Cube of Space walls and areas."""
|
||||
|
||||
from .cube import Cube
|
||||
from .attributes import CubeOfSpace, Wall, WallDirection
|
||||
from .cube import Cube
|
||||
|
||||
__all__ = ["Cube", "CubeOfSpace", "Wall", "WallDirection"]
|
||||
|
||||
@@ -17,6 +17,7 @@ class WallDirection:
|
||||
Each wall has 5 directions: North, South, East, West, Center.
|
||||
Each direction has a Hebrew letter and zodiac correspondence.
|
||||
"""
|
||||
|
||||
name: str # "North", "South", "East", "West", "Center"
|
||||
letter: str # Hebrew letter (e.g., "Aleph", "Bet", etc.)
|
||||
zodiac: Optional[str] = None # Zodiac sign if applicable
|
||||
@@ -51,6 +52,7 @@ class Wall:
|
||||
Opposite walls: North↔South, East↔West, Above↔Below.
|
||||
Each direction has a Hebrew letter and zodiac correspondence.
|
||||
"""
|
||||
|
||||
name: str # "North", "South", "East", "West", "Above", "Below"
|
||||
side: str # Alias for name, used for filtering (e.g., "north", "south")
|
||||
opposite: str # Opposite wall name (e.g., "South" for North wall)
|
||||
@@ -82,9 +84,7 @@ class Wall:
|
||||
|
||||
# Validate side matches name (case-insensitive)
|
||||
if self.side.capitalize() != self.name:
|
||||
raise ValueError(
|
||||
f"Wall side '{self.side}' must match name '{self.name}'"
|
||||
)
|
||||
raise ValueError(f"Wall side '{self.side}' must match name '{self.name}'")
|
||||
|
||||
# Validate opposite wall
|
||||
expected_opposite = self.OPPOSITE_WALLS.get(self.name)
|
||||
@@ -177,6 +177,7 @@ class CubeOfSpace:
|
||||
- Opposite walls: North↔South, East↔West, Above↔Below
|
||||
- Total: 30 positions plus central core
|
||||
"""
|
||||
|
||||
walls: Dict[str, Wall] = field(default_factory=dict)
|
||||
center: Optional[WallDirection] = None # Central core position
|
||||
|
||||
@@ -392,9 +393,7 @@ class CubeOfSpace:
|
||||
"""Validate that all 6 walls are present."""
|
||||
required_walls = {"North", "South", "East", "West", "Above", "Below"}
|
||||
if set(self.walls.keys()) != required_walls:
|
||||
raise ValueError(
|
||||
f"CubeOfSpace must have all 6 walls, got: {set(self.walls.keys())}"
|
||||
)
|
||||
raise ValueError(f"CubeOfSpace must have all 6 walls, got: {set(self.walls.keys())}")
|
||||
|
||||
@classmethod
|
||||
def create_default(cls) -> "CubeOfSpace":
|
||||
@@ -416,7 +415,7 @@ class CubeOfSpace:
|
||||
"above": {"name": "North", "letter": "Bet", "zodiac": None},
|
||||
"below": {"name": "South", "letter": "Gimel", "zodiac": None},
|
||||
"east": {"name": "East", "letter": "Daleth", "zodiac": "Aries"},
|
||||
"west": {"name": "West", "letter": "He", "zodiac": "Pisces"}
|
||||
"west": {"name": "West", "letter": "He", "zodiac": "Pisces"},
|
||||
}
|
||||
|
||||
for wall_name, wall_data in cls._WALL_DEFINITIONS.items():
|
||||
|
||||
@@ -17,7 +17,10 @@ Usage:
|
||||
wall.direction("East") # Get specific direction
|
||||
"""
|
||||
|
||||
from typing import Optional, Any
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from kaballah.cube.attributes import CubeOfSpace
|
||||
|
||||
|
||||
class CubeMeta(type):
|
||||
@@ -29,7 +32,7 @@ class CubeMeta(type):
|
||||
if cls._cube is None:
|
||||
return "Cube of Space (not initialized)"
|
||||
|
||||
walls = cls._cube.walls if hasattr(cls._cube, 'walls') else {}
|
||||
walls = cls._cube.walls if hasattr(cls._cube, "walls") else {}
|
||||
lines = [
|
||||
"Cube of Space",
|
||||
"=" * 60,
|
||||
@@ -42,8 +45,8 @@ class CubeMeta(type):
|
||||
for wall_name in ["North", "South", "East", "West", "Above", "Below"]:
|
||||
wall = walls.get(wall_name)
|
||||
if wall:
|
||||
element = f" [{wall.element}]" if hasattr(wall, 'element') else ""
|
||||
areas = len(wall.directions) if hasattr(wall, 'directions') else 0
|
||||
element = f" [{wall.element}]" if hasattr(wall, "element") else ""
|
||||
areas = len(wall.directions) if hasattr(wall, "directions") else 0
|
||||
lines.append(f" {wall_name}{element}: {areas} areas")
|
||||
|
||||
return "\n".join(lines)
|
||||
@@ -53,7 +56,7 @@ class CubeMeta(type):
|
||||
cls._ensure_initialized()
|
||||
if cls._cube is None:
|
||||
return "Cube(not initialized)"
|
||||
walls = cls._cube.walls if hasattr(cls._cube, 'walls') else {}
|
||||
walls = cls._cube.walls if hasattr(cls._cube, "walls") else {}
|
||||
return f"Cube(walls={len(walls)})"
|
||||
|
||||
|
||||
@@ -68,7 +71,7 @@ class DirectionAccessor:
|
||||
|
||||
def all(self) -> list:
|
||||
"""Get all directions in this wall."""
|
||||
if self._wall is None or not hasattr(self._wall, 'directions'):
|
||||
if self._wall is None or not hasattr(self._wall, "directions"):
|
||||
return []
|
||||
return list(self._wall.directions.values())
|
||||
|
||||
@@ -89,10 +92,7 @@ class DirectionAccessor:
|
||||
|
||||
# Filter by direction name if provided
|
||||
if direction_name:
|
||||
all_dirs = [
|
||||
d for d in all_dirs
|
||||
if d.name.lower() == direction_name.lower()
|
||||
]
|
||||
all_dirs = [d for d in all_dirs if d.name.lower() == direction_name.lower()]
|
||||
|
||||
# Apply other filters
|
||||
if kwargs:
|
||||
@@ -126,7 +126,7 @@ class DirectionAccessor:
|
||||
"""Get specific direction by name."""
|
||||
if direction_name is None:
|
||||
return self.all()
|
||||
if self._wall is None or not hasattr(self._wall, 'directions'):
|
||||
if self._wall is None or not hasattr(self._wall, "directions"):
|
||||
return None
|
||||
return self._wall.directions.get(direction_name.capitalize())
|
||||
|
||||
@@ -152,7 +152,7 @@ class WallWrapper:
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
"""Delegate attribute access to the wrapped wall."""
|
||||
if name in ('_wall', '_direction_accessor'):
|
||||
if name in ("_wall", "_direction_accessor"):
|
||||
return object.__getattribute__(self, name)
|
||||
return getattr(self._wall, name)
|
||||
|
||||
@@ -343,6 +343,7 @@ class Cube(metaclass=CubeMeta):
|
||||
# Use a descriptor to make wall work like a property on the class
|
||||
class WallProperty:
|
||||
"""Descriptor that returns wall accessor when accessed."""
|
||||
|
||||
def __get__(self, obj: Any, objtype: Optional[type] = None) -> "WallAccessor":
|
||||
if objtype is None:
|
||||
objtype = type(obj)
|
||||
|
||||
@@ -13,12 +13,12 @@ Usage:
|
||||
print(Tree()) # Display Tree structure
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Union, overload
|
||||
from typing import TYPE_CHECKING, Dict, Optional, Union, overload
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tarot.attributes import Sephera, Path
|
||||
from tarot.attributes import Path, Sephera
|
||||
from tarot.card.data import CardDataLoader
|
||||
from utils.query import QueryResult, Query
|
||||
from utils.query import Query
|
||||
|
||||
|
||||
class TreeMeta(type):
|
||||
@@ -28,8 +28,8 @@ class TreeMeta(type):
|
||||
"""Return readable representation when Tree is converted to string."""
|
||||
# Access Tree class attributes through type.__getattribute__
|
||||
Tree._ensure_initialized()
|
||||
sepheras = type.__getattribute__(cls, '_sepheras')
|
||||
paths = type.__getattribute__(cls, '_paths')
|
||||
sepheras = type.__getattribute__(cls, "_sepheras")
|
||||
paths = type.__getattribute__(cls, "_paths")
|
||||
lines = [
|
||||
"Tree of Life",
|
||||
"=" * 60,
|
||||
@@ -49,8 +49,8 @@ class TreeMeta(type):
|
||||
def __repr__(cls) -> str:
|
||||
"""Return object representation."""
|
||||
Tree._ensure_initialized()
|
||||
sepheras = type.__getattribute__(cls, '_sepheras')
|
||||
paths = type.__getattribute__(cls, '_paths')
|
||||
sepheras = type.__getattribute__(cls, "_sepheras")
|
||||
paths = type.__getattribute__(cls, "_paths")
|
||||
return f"Tree(sepheras={len(sepheras)}, paths={len(paths)})"
|
||||
|
||||
|
||||
@@ -65,10 +65,10 @@ class Tree(metaclass=TreeMeta):
|
||||
print(Tree()) # Displays tree structure
|
||||
"""
|
||||
|
||||
_sepheras: Dict[int, 'Sephera'] = {} # type: ignore
|
||||
_paths: Dict[int, 'Path'] = {} # type: ignore
|
||||
_sepheras: Dict[int, "Sephera"] = {} # type: ignore
|
||||
_paths: Dict[int, "Path"] = {} # type: ignore
|
||||
_initialized: bool = False
|
||||
_loader: Optional['CardDataLoader'] = None # type: ignore
|
||||
_loader: Optional["CardDataLoader"] = None # type: ignore
|
||||
|
||||
@classmethod
|
||||
def _ensure_initialized(cls) -> None:
|
||||
@@ -77,6 +77,7 @@ class Tree(metaclass=TreeMeta):
|
||||
return
|
||||
|
||||
from tarot.card.data import CardDataLoader
|
||||
|
||||
cls._loader = CardDataLoader()
|
||||
cls._sepheras = cls._loader._sephera
|
||||
cls._paths = cls._loader._paths
|
||||
@@ -84,16 +85,16 @@ class Tree(metaclass=TreeMeta):
|
||||
|
||||
@classmethod
|
||||
@overload
|
||||
def sephera(cls, number: int) -> Optional['Sephera']:
|
||||
...
|
||||
def sephera(cls, number: int) -> Optional["Sephera"]: ...
|
||||
|
||||
@classmethod
|
||||
@overload
|
||||
def sephera(cls, number: None = ...) -> Dict[int, 'Sephera']:
|
||||
...
|
||||
def sephera(cls, number: None = ...) -> Dict[int, "Sephera"]: ...
|
||||
|
||||
@classmethod
|
||||
def sephera(cls, number: Optional[int] = None) -> Union[Optional['Sephera'], Dict[int, 'Sephera']]:
|
||||
def sephera(
|
||||
cls, number: Optional[int] = None
|
||||
) -> Union[Optional["Sephera"], Dict[int, "Sephera"]]:
|
||||
"""Return a Sephira or all Sephiroth."""
|
||||
cls._ensure_initialized()
|
||||
if number is None:
|
||||
@@ -102,16 +103,14 @@ class Tree(metaclass=TreeMeta):
|
||||
|
||||
@classmethod
|
||||
@overload
|
||||
def path(cls, number: int) -> Optional['Path']:
|
||||
...
|
||||
def path(cls, number: int) -> Optional["Path"]: ...
|
||||
|
||||
@classmethod
|
||||
@overload
|
||||
def path(cls, number: None = ...) -> Dict[int, 'Path']:
|
||||
...
|
||||
def path(cls, number: None = ...) -> Dict[int, "Path"]: ...
|
||||
|
||||
@classmethod
|
||||
def path(cls, number: Optional[int] = None) -> Union[Optional['Path'], Dict[int, 'Path']]:
|
||||
def path(cls, number: Optional[int] = None) -> Union[Optional["Path"], Dict[int, "Path"]]:
|
||||
"""Return a Path or all Paths."""
|
||||
cls._ensure_initialized()
|
||||
if number is None:
|
||||
@@ -119,7 +118,7 @@ class Tree(metaclass=TreeMeta):
|
||||
return cls._paths.get(number)
|
||||
|
||||
@classmethod
|
||||
def filter(cls, expression: str) -> 'Query':
|
||||
def filter(cls, expression: str) -> "Query":
|
||||
"""
|
||||
Filter Sephiroth by attribute:value expression.
|
||||
|
||||
@@ -131,6 +130,7 @@ class Tree(metaclass=TreeMeta):
|
||||
Returns a Query object for chaining.
|
||||
"""
|
||||
from tarot.query import Query
|
||||
|
||||
cls._ensure_initialized()
|
||||
# Create a query from all Sephiroth
|
||||
return Query(cls._sepheras).filter(expression)
|
||||
|
||||
@@ -18,10 +18,9 @@ Usage:
|
||||
letter.paths('aleph') # Get Hebrew letter with Tarot correspondences
|
||||
"""
|
||||
|
||||
from .iChing import hexagram, trigram
|
||||
from .letter import letter
|
||||
from .iChing import trigram, hexagram
|
||||
from .words import word
|
||||
from .paths import letters
|
||||
from .words import word
|
||||
|
||||
__all__ = ["letter", "trigram", "hexagram", "word", "letters"]
|
||||
|
||||
|
||||
@@ -6,19 +6,19 @@ including Alphabets, Enochian letters, and Double Letter Trumps.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List, Optional, Tuple, Any
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from utils.attributes import (
|
||||
Element,
|
||||
ElementType,
|
||||
Planet,
|
||||
Meaning,
|
||||
Planet,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Letter:
|
||||
"""Represents a letter with its attributes."""
|
||||
|
||||
character: str
|
||||
position: int
|
||||
name: str
|
||||
@@ -27,6 +27,7 @@ class Letter:
|
||||
@dataclass
|
||||
class EnglishAlphabet:
|
||||
"""English alphabet with Tarot/Kabbalistic correspondence."""
|
||||
|
||||
letter: str
|
||||
position: int
|
||||
sound: str
|
||||
@@ -41,6 +42,7 @@ class EnglishAlphabet:
|
||||
@dataclass
|
||||
class GreekAlphabet:
|
||||
"""Greek alphabet with Tarot/Kabbalistic correspondence."""
|
||||
|
||||
letter: str
|
||||
position: int
|
||||
transliteration: str
|
||||
@@ -53,6 +55,7 @@ class GreekAlphabet:
|
||||
@dataclass
|
||||
class HebrewAlphabet:
|
||||
"""Hebrew alphabet with Tarot/Kabbalistic correspondence."""
|
||||
|
||||
letter: str
|
||||
position: int
|
||||
transliteration: str
|
||||
@@ -66,17 +69,18 @@ class HebrewAlphabet:
|
||||
@dataclass
|
||||
class DoublLetterTrump:
|
||||
"""Represents a Double Letter Trump (Yodh through Tau, 3-21 of Major Arcana)."""
|
||||
|
||||
number: int # 3-21 (19 double letter trumps)
|
||||
name: str # Full name (e.g., "The Empress")
|
||||
hebrew_letter_1: str # First Hebrew letter (e.g., "Gimel")
|
||||
hebrew_letter_2: Optional[str] = None # Second Hebrew letter if applicable
|
||||
planet: Optional['Planet'] = None # Associated planet
|
||||
planet: Optional["Planet"] = None # Associated planet
|
||||
tarot_trump: Optional[str] = None # e.g., "III - The Empress"
|
||||
astrological_sign: Optional[str] = None # Zodiac sign if any
|
||||
element: Optional['ElementType'] = None # Associated element
|
||||
element: Optional["ElementType"] = None # Associated element
|
||||
number_value: Optional[int] = None # Numerological value
|
||||
keywords: List[str] = field(default_factory=list)
|
||||
meaning: Optional['Meaning'] = None # Upright and reversed meanings
|
||||
meaning: Optional["Meaning"] = None # Upright and reversed meanings
|
||||
description: str = ""
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
@@ -87,6 +91,7 @@ class DoublLetterTrump:
|
||||
@dataclass(frozen=True)
|
||||
class EnochianLetter:
|
||||
"""Represents an Enochian letter with its properties."""
|
||||
|
||||
name: str # Enochian letter name
|
||||
letter: str # The letter itself
|
||||
hebrew_equivalent: Optional[str] = None
|
||||
@@ -100,6 +105,7 @@ class EnochianLetter:
|
||||
@dataclass(frozen=True)
|
||||
class EnochianSpirit:
|
||||
"""Represents an Enochian spirit or intelligence."""
|
||||
|
||||
name: str # Spirit name
|
||||
rank: str # e.g., "King", "Prince", "Duke", "Intelligence"
|
||||
element: Optional[str] = None
|
||||
@@ -117,15 +123,18 @@ class EnochianArchetype:
|
||||
Provides a 4x4 grid with positions that can be filled with different
|
||||
visual representations (colors, images, symbols, etc.).
|
||||
"""
|
||||
|
||||
name: str # e.g., "Tablet of Air Archetype"
|
||||
tablet_name: str # Reference to parent tablet
|
||||
grid: Dict[Tuple[int, int], 'EnochianGridPosition'] = field(default_factory=dict) # 4x4 grid
|
||||
grid: Dict[Tuple[int, int], "EnochianGridPosition"] = field(default_factory=dict) # 4x4 grid
|
||||
row_correspondences: List[Dict[str, Any]] = field(default_factory=list) # Row meanings (4 rows)
|
||||
col_correspondences: List[Dict[str, Any]] = field(default_factory=list) # Column meanings (4 cols)
|
||||
col_correspondences: List[Dict[str, Any]] = field(
|
||||
default_factory=list
|
||||
) # Column meanings (4 cols)
|
||||
keywords: List[str] = field(default_factory=list)
|
||||
description: str = ""
|
||||
|
||||
def get_position(self, row: int, col: int) -> Optional['EnochianGridPosition']:
|
||||
def get_position(self, row: int, col: int) -> Optional["EnochianGridPosition"]:
|
||||
"""Get the grid position at (row, col)."""
|
||||
if not 0 <= row < 4 or not 0 <= col < 4:
|
||||
return None
|
||||
@@ -154,6 +163,7 @@ class EnochianGridPosition:
|
||||
- Directional letters (N, S, E, W)
|
||||
- Archetypal correspondences (Tarot, element, etc.)
|
||||
"""
|
||||
|
||||
row: int # Grid row (0-3)
|
||||
col: int # Grid column (0-3)
|
||||
center_letter: str # The main letter at this position
|
||||
@@ -195,12 +205,15 @@ class EnochianTablet:
|
||||
- Each tablet is 12x12 (144 squares) containing Enochian letters
|
||||
- Archetypal form with 4x4 grid for user customization
|
||||
"""
|
||||
|
||||
name: str # e.g., "Tablet of Earth", "Tablet of Air", etc.
|
||||
number: int # Tablet identifier (1-5)
|
||||
element: Optional[str] = None # Earth, Water, Air, Fire, or Aethyr/Union
|
||||
rulers: List[str] = field(default_factory=list) # Names of spirits ruling this tablet
|
||||
archangels: List[str] = field(default_factory=list) # Associated archangels
|
||||
letters: Dict[Tuple[int, int], str] = field(default_factory=dict) # Grid of letters (row, col) -> letter
|
||||
letters: Dict[Tuple[int, int], str] = field(
|
||||
default_factory=dict
|
||||
) # Grid of letters (row, col) -> letter
|
||||
planetary_hours: List[str] = field(default_factory=list) # Associated hours
|
||||
keywords: List[str] = field(default_factory=list)
|
||||
description: str = ""
|
||||
@@ -218,8 +231,7 @@ class EnochianTablet:
|
||||
def __post_init__(self) -> None:
|
||||
if self.name not in self.VALID_TABLETS:
|
||||
raise ValueError(
|
||||
f"Invalid tablet '{self.name}'. "
|
||||
f"Valid tablets: {', '.join(self.VALID_TABLETS)}"
|
||||
f"Invalid tablet '{self.name}'. " f"Valid tablets: {', '.join(self.VALID_TABLETS)}"
|
||||
)
|
||||
# Tablet of Union uses 0, elemental tablets use 1-5
|
||||
valid_range = (0, 0) if "Union" in self.name else (1, 5)
|
||||
|
||||
@@ -10,13 +10,12 @@ Usage:
|
||||
creative = hexagram.hexagram(1)
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING, Dict, Optional
|
||||
from typing import TYPE_CHECKING, Dict
|
||||
|
||||
from utils.query import CollectionAccessor
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tarot.card.data import CardDataLoader
|
||||
from tarot.attributes import Trigram, Hexagram
|
||||
from tarot.attributes import Hexagram, Trigram
|
||||
|
||||
|
||||
def _line_diagram_from_binary(binary: str) -> str:
|
||||
@@ -36,7 +35,7 @@ class _Trigram:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._initialized: bool = False
|
||||
self._trigrams: Dict[str, 'Trigram'] = {}
|
||||
self._trigrams: Dict[str, "Trigram"] = {}
|
||||
self.trigram = CollectionAccessor(self._get_trigrams)
|
||||
|
||||
def _ensure_initialized(self) -> None:
|
||||
@@ -56,14 +55,78 @@ class _Trigram:
|
||||
from tarot.attributes import Trigram
|
||||
|
||||
trigram_specs = [
|
||||
{"name": "Qian", "chinese": "乾", "pinyin": "Qián", "element": "Heaven", "attribute": "Creative", "binary": "111", "description": "Pure yang drive that initiates action."},
|
||||
{"name": "Dui", "chinese": "兑", "pinyin": "Duì", "element": "Lake", "attribute": "Joyous", "binary": "011", "description": "Open delight that invites community."},
|
||||
{"name": "Li", "chinese": "离", "pinyin": "Lí", "element": "Fire", "attribute": "Clinging", "binary": "101", "description": "Radiant clarity that adheres to insight."},
|
||||
{"name": "Zhen", "chinese": "震", "pinyin": "Zhèn", "element": "Thunder", "attribute": "Arousing", "binary": "001", "description": "Sudden awakening that shakes stagnation."},
|
||||
{"name": "Xun", "chinese": "巽", "pinyin": "Xùn", "element": "Wind", "attribute": "Gentle", "binary": "110", "description": "Penetrating influence that persuades subtly."},
|
||||
{"name": "Kan", "chinese": "坎", "pinyin": "Kǎn", "element": "Water", "attribute": "Abysmal", "binary": "010", "description": "Depth, risk, and sincere feeling."},
|
||||
{"name": "Gen", "chinese": "艮", "pinyin": "Gèn", "element": "Mountain", "attribute": "Stillness", "binary": "100", "description": "Grounded rest that establishes boundaries."},
|
||||
{"name": "Kun", "chinese": "坤", "pinyin": "Kūn", "element": "Earth", "attribute": "Receptive", "binary": "000", "description": "Vast receptivity that nurtures form."},
|
||||
{
|
||||
"name": "Qian",
|
||||
"chinese": "乾",
|
||||
"pinyin": "Qián",
|
||||
"element": "Heaven",
|
||||
"attribute": "Creative",
|
||||
"binary": "111",
|
||||
"description": "Pure yang drive that initiates action.",
|
||||
},
|
||||
{
|
||||
"name": "Dui",
|
||||
"chinese": "兑",
|
||||
"pinyin": "Duì",
|
||||
"element": "Lake",
|
||||
"attribute": "Joyous",
|
||||
"binary": "011",
|
||||
"description": "Open delight that invites community.",
|
||||
},
|
||||
{
|
||||
"name": "Li",
|
||||
"chinese": "离",
|
||||
"pinyin": "Lí",
|
||||
"element": "Fire",
|
||||
"attribute": "Clinging",
|
||||
"binary": "101",
|
||||
"description": "Radiant clarity that adheres to insight.",
|
||||
},
|
||||
{
|
||||
"name": "Zhen",
|
||||
"chinese": "震",
|
||||
"pinyin": "Zhèn",
|
||||
"element": "Thunder",
|
||||
"attribute": "Arousing",
|
||||
"binary": "001",
|
||||
"description": "Sudden awakening that shakes stagnation.",
|
||||
},
|
||||
{
|
||||
"name": "Xun",
|
||||
"chinese": "巽",
|
||||
"pinyin": "Xùn",
|
||||
"element": "Wind",
|
||||
"attribute": "Gentle",
|
||||
"binary": "110",
|
||||
"description": "Penetrating influence that persuades subtly.",
|
||||
},
|
||||
{
|
||||
"name": "Kan",
|
||||
"chinese": "坎",
|
||||
"pinyin": "Kǎn",
|
||||
"element": "Water",
|
||||
"attribute": "Abysmal",
|
||||
"binary": "010",
|
||||
"description": "Depth, risk, and sincere feeling.",
|
||||
},
|
||||
{
|
||||
"name": "Gen",
|
||||
"chinese": "艮",
|
||||
"pinyin": "Gèn",
|
||||
"element": "Mountain",
|
||||
"attribute": "Stillness",
|
||||
"binary": "100",
|
||||
"description": "Grounded rest that establishes boundaries.",
|
||||
},
|
||||
{
|
||||
"name": "Kun",
|
||||
"chinese": "坤",
|
||||
"pinyin": "Kūn",
|
||||
"element": "Earth",
|
||||
"attribute": "Receptive",
|
||||
"binary": "000",
|
||||
"description": "Vast receptivity that nurtures form.",
|
||||
},
|
||||
]
|
||||
self._trigrams = {}
|
||||
for spec in trigram_specs:
|
||||
@@ -88,7 +151,7 @@ class _Hexagram:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._initialized: bool = False
|
||||
self._hexagrams: Dict[int, 'Hexagram'] = {}
|
||||
self._hexagrams: Dict[int, "Hexagram"] = {}
|
||||
self.hexagram = CollectionAccessor(self._get_hexagrams)
|
||||
|
||||
def _ensure_initialized(self) -> None:
|
||||
@@ -115,70 +178,710 @@ class _Hexagram:
|
||||
loader = CardDataLoader()
|
||||
|
||||
hex_specs = [
|
||||
{"number": 1, "name": "Creative Force", "chinese": "乾", "pinyin": "Qián", "judgement": "Initiative succeeds when anchored in integrity.", "image": "Heaven above and below mirrors unstoppable drive.", "upper": "Qian", "lower": "Qian", "keywords": "Leadership|Momentum|Clarity"},
|
||||
{"number": 2, "name": "Receptive Field", "chinese": "坤", "pinyin": "Kūn", "judgement": "Grounded support flourishes through patience.", "image": "Earth layered upon earth offers fertile space.", "upper": "Kun", "lower": "Kun", "keywords": "Nurture|Support|Yielding"},
|
||||
{"number": 3, "name": "Sprouting", "chinese": "屯", "pinyin": "Zhūn", "judgement": "Challenges at the start need perseverance.", "image": "Water over thunder shows storms that germinate seeds.", "upper": "Kan", "lower": "Zhen", "keywords": "Beginnings|Struggle|Resolve"},
|
||||
{"number": 4, "name": "Youthful Insight", "chinese": "蒙", "pinyin": "Méng", "judgement": "Ignorance yields to steady guidance.", "image": "Mountain above water signals learning via restraint.", "upper": "Gen", "lower": "Kan", "keywords": "Study|Mentorship|Humility"},
|
||||
{"number": 5, "name": "Waiting", "chinese": "需", "pinyin": "Xū", "judgement": "Hold position until nourishment arrives.", "image": "Water above heaven depicts clouds gathering provision.", "upper": "Kan", "lower": "Qian", "keywords": "Patience|Faith|Preparation"},
|
||||
{"number": 6, "name": "Conflict", "chinese": "訟", "pinyin": "Sòng", "judgement": "Clarity and fairness prevent escalation.", "image": "Heaven above water shows tension seeking balance.", "upper": "Qian", "lower": "Kan", "keywords": "Debate|Justice|Boundaries"},
|
||||
{"number": 7, "name": "Collective Force", "chinese": "師", "pinyin": "Shī", "judgement": "Coordinated effort requires disciplined leadership.", "image": "Earth over water mirrors troops marshaling supplies.", "upper": "Kun", "lower": "Kan", "keywords": "Discipline|Leadership|Community"},
|
||||
{"number": 8, "name": "Union", "chinese": "比", "pinyin": "Bǐ", "judgement": "Shared values attract loyal allies.", "image": "Water over earth highlights bonds formed through empathy.", "upper": "Kan", "lower": "Kun", "keywords": "Alliance|Affinity|Trust"},
|
||||
{"number": 9, "name": "Small Accumulating", "chinese": "小畜", "pinyin": "Xiǎo Chù", "judgement": "Gentle restraint nurtures gradual gains.", "image": "Wind over heaven indicates tender guidance on great power.", "upper": "Xun", "lower": "Qian", "keywords": "Restraint|Cultivation|Care"},
|
||||
{"number": 10, "name": "Treading", "chinese": "履", "pinyin": "Lǚ", "judgement": "Walk with awareness when near power.", "image": "Heaven over lake shows respect between ranks.", "upper": "Qian", "lower": "Dui", "keywords": "Conduct|Respect|Sensitivity"},
|
||||
{"number": 11, "name": "Peace", "chinese": "泰", "pinyin": "Tài", "judgement": "Harmony thrives when resources circulate freely.", "image": "Earth over heaven signals prosperity descending.", "upper": "Kun", "lower": "Qian", "keywords": "Harmony|Prosperity|Flourish"},
|
||||
{"number": 12, "name": "Standstill", "chinese": "否", "pinyin": "Pǐ", "judgement": "When channels close, conserve strength.", "image": "Heaven over earth reveals blocked exchange.", "upper": "Qian", "lower": "Kun", "keywords": "Stagnation|Reflection|Pause"},
|
||||
{"number": 13, "name": "Fellowship", "chinese": "同人", "pinyin": "Tóng Rén", "judgement": "Shared purpose unites distant hearts.", "image": "Heaven over fire shows clarity within community.", "upper": "Qian", "lower": "Li", "keywords": "Community|Shared Vision|Openness"},
|
||||
{"number": 14, "name": "Great Possession", "chinese": "大有", "pinyin": "Dà Yǒu", "judgement": "Generosity cements lasting influence.", "image": "Fire over heaven reflects radiance sustained by ethics.", "upper": "Li", "lower": "Qian", "keywords": "Wealth|Stewardship|Confidence"},
|
||||
{"number": 15, "name": "Modesty", "chinese": "謙", "pinyin": "Qiān", "judgement": "Balance is found by lowering the proud.", "image": "Earth over mountain reveals humility safeguarding strength.", "upper": "Kun", "lower": "Gen", "keywords": "Humility|Balance|Service"},
|
||||
{"number": 16, "name": "Enthusiasm", "chinese": "豫", "pinyin": "Yù", "judgement": "Inspired music rallies the people.", "image": "Thunder over earth depicts drums stirring hearts.", "upper": "Zhen", "lower": "Kun", "keywords": "Inspiration|Celebration|Momentum"},
|
||||
{"number": 17, "name": "Following", "chinese": "隨", "pinyin": "Suí", "judgement": "Adapt willingly to timely leadership.", "image": "Lake over thunder points to joyful allegiance.", "upper": "Dui", "lower": "Zhen", "keywords": "Adaptation|Loyalty|Flow"},
|
||||
{"number": 18, "name": "Repairing", "chinese": "蠱", "pinyin": "Gǔ", "judgement": "Address decay with responsibility and care.", "image": "Mountain over wind shows correction of lineages.", "upper": "Gen", "lower": "Xun", "keywords": "Restoration|Accountability|Healing"},
|
||||
{"number": 19, "name": "Approach", "chinese": "臨", "pinyin": "Lín", "judgement": "Leaders draw near to listen sincerely.", "image": "Earth over lake signifies compassion visiting the people.", "upper": "Kun", "lower": "Dui", "keywords": "Empathy|Guidance|Presence"},
|
||||
{"number": 20, "name": "Contemplation", "chinese": "觀", "pinyin": "Guān", "judgement": "Observation inspires ethical alignment.", "image": "Wind over earth is the elevated view of the sage.", "upper": "Xun", "lower": "Kun", "keywords": "Perspective|Ritual|Vision"},
|
||||
{"number": 21, "name": "Biting Through", "chinese": "噬嗑", "pinyin": "Shì Kè", "judgement": "Decisive action cuts through obstruction.", "image": "Fire over thunder shows justice enforced with clarity.", "upper": "Li", "lower": "Zhen", "keywords": "Decision|Justice|Resolve"},
|
||||
{"number": 22, "name": "Grace", "chinese": "賁", "pinyin": "Bì", "judgement": "Beauty adorns substance when humility remains.", "image": "Mountain over fire highlights poise and restraint.", "upper": "Gen", "lower": "Li", "keywords": "Aesthetics|Poise|Form"},
|
||||
{"number": 23, "name": "Splitting Apart", "chinese": "剝", "pinyin": "Bō", "judgement": "When decay spreads, strip away excess.", "image": "Mountain over earth signals outer shells falling.", "upper": "Gen", "lower": "Kun", "keywords": "Decline|Release|Truth"},
|
||||
{"number": 24, "name": "Return", "chinese": "復", "pinyin": "Fù", "judgement": "Cycles renew when rest follows completion.", "image": "Earth over thunder marks the turning of the year.", "upper": "Kun", "lower": "Zhen", "keywords": "Renewal|Rhythm|Faith"},
|
||||
{"number": 25, "name": "Innocence", "chinese": "無妄", "pinyin": "Wú Wàng", "judgement": "Sincerity triumphs over scheming.", "image": "Heaven over thunder shows spontaneous virtue.", "upper": "Qian", "lower": "Zhen", "keywords": "Authenticity|Spontaneity|Trust"},
|
||||
{"number": 26, "name": "Great Taming", "chinese": "大畜", "pinyin": "Dà Chù", "judgement": "Conserve strength until action serves wisdom.", "image": "Mountain over heaven portrays restraint harnessing power.", "upper": "Gen", "lower": "Qian", "keywords": "Discipline|Reserve|Mastery"},
|
||||
{"number": 27, "name": "Nourishment", "chinese": "頤", "pinyin": "Yí", "judgement": "Words and food alike must be chosen with care.", "image": "Mountain over thunder emphasizes mindful sustenance.", "upper": "Gen", "lower": "Zhen", "keywords": "Nutrition|Speech|Mindfulness"},
|
||||
{"number": 28, "name": "Great Exceeding", "chinese": "大過", "pinyin": "Dà Guò", "judgement": "Bearing heavy loads demands flexibility.", "image": "Lake over wind shows a beam bending before it breaks.", "upper": "Dui", "lower": "Xun", "keywords": "Weight|Adaptability|Responsibility"},
|
||||
{"number": 29, "name": "The Abyss", "chinese": "坎", "pinyin": "Kǎn", "judgement": "Repeated trials teach sincere caution.", "image": "Water over water is the perilous gorge.", "upper": "Kan", "lower": "Kan", "keywords": "Trial|Honesty|Depth"},
|
||||
{"number": 30, "name": "Radiance", "chinese": "離", "pinyin": "Lí", "judgement": "Clarity is maintained by tending the flame.", "image": "Fire over fire represents brilliance sustained through care.", "upper": "Li", "lower": "Li", "keywords": "Illumination|Culture|Attention"},
|
||||
{"number": 31, "name": "Influence", "chinese": "咸", "pinyin": "Xián", "judgement": "Sincere attraction arises from mutual respect.", "image": "Lake over mountain highlights responsive hearts.", "upper": "Dui", "lower": "Gen", "keywords": "Attraction|Mutuality|Sensitivity"},
|
||||
{"number": 32, "name": "Duration", "chinese": "恒", "pinyin": "Héng", "judgement": "Commitment endures when balanced.", "image": "Thunder over wind speaks of constancy amid change.", "upper": "Zhen", "lower": "Xun", "keywords": "Commitment|Consistency|Rhythm"},
|
||||
{"number": 33, "name": "Retreat", "chinese": "遯", "pinyin": "Dùn", "judgement": "Strategic withdrawal preserves integrity.", "image": "Heaven over mountain shows noble retreat.", "upper": "Qian", "lower": "Gen", "keywords": "Withdrawal|Strategy|Self-care"},
|
||||
{"number": 34, "name": "Great Power", "chinese": "大壯", "pinyin": "Dà Zhuàng", "judgement": "Strength must remain aligned with virtue.", "image": "Thunder over heaven affirms action matched with purpose.", "upper": "Zhen", "lower": "Qian", "keywords": "Power|Ethics|Momentum"},
|
||||
{"number": 35, "name": "Progress", "chinese": "晉", "pinyin": "Jìn", "judgement": "Advancement arrives through clarity and loyalty.", "image": "Fire over earth depicts dawn spreading across the plain.", "upper": "Li", "lower": "Kun", "keywords": "Advancement|Visibility|Service"},
|
||||
{"number": 36, "name": "Darkening Light", "chinese": "明夷", "pinyin": "Míng Yí", "judgement": "Protect the inner light when circumstances grow harsh.", "image": "Earth over fire shows brilliance concealed for safety.", "upper": "Kun", "lower": "Li", "keywords": "Protection|Subtlety|Endurance"},
|
||||
{"number": 37, "name": "Family", "chinese": "家人", "pinyin": "Jiā Rén", "judgement": "Clear roles nourish household harmony.", "image": "Wind over fire indicates rituals ordering the home.", "upper": "Xun", "lower": "Li", "keywords": "Home|Roles|Care"},
|
||||
{"number": 38, "name": "Opposition", "chinese": "睽", "pinyin": "Kuí", "judgement": "Recognize difference without hostility.", "image": "Fire over lake reflects contrast seeking balance.", "upper": "Li", "lower": "Dui", "keywords": "Contrast|Perspective|Tolerance"},
|
||||
{"number": 39, "name": "Obstruction", "chinese": "蹇", "pinyin": "Jiǎn", "judgement": "Turn hindrance into training.", "image": "Water over mountain shows difficult ascent.", "upper": "Kan", "lower": "Gen", "keywords": "Obstacle|Effort|Learning"},
|
||||
{"number": 40, "name": "Deliverance", "chinese": "解", "pinyin": "Xiè", "judgement": "Relief comes when knots are untied.", "image": "Thunder over water portrays release after storm.", "upper": "Zhen", "lower": "Kan", "keywords": "Release|Solution|Breath"},
|
||||
{"number": 41, "name": "Decrease", "chinese": "損", "pinyin": "Sǔn", "judgement": "Voluntary simplicity restores balance.", "image": "Mountain over lake shows graceful sharing of resources.", "upper": "Gen", "lower": "Dui", "keywords": "Simplicity|Offering|Balance"},
|
||||
{"number": 42, "name": "Increase", "chinese": "益", "pinyin": "Yì", "judgement": "Blessings multiply when shared.", "image": "Wind over thunder reveals generous expansion.", "upper": "Xun", "lower": "Zhen", "keywords": "Growth|Generosity|Opportunity"},
|
||||
{"number": 43, "name": "Breakthrough", "chinese": "夬", "pinyin": "Guài", "judgement": "Speak truth boldly to clear corruption.", "image": "Lake over heaven highlights decisive proclamation.", "upper": "Dui", "lower": "Qian", "keywords": "Resolution|Declaration|Courage"},
|
||||
{"number": 44, "name": "Encounter", "chinese": "姤", "pinyin": "Gòu", "judgement": "Unexpected influence requires discernment.", "image": "Heaven over wind shows potent visitors arriving.", "upper": "Qian", "lower": "Xun", "keywords": "Encounter|Discernment|Temptation"},
|
||||
{"number": 45, "name": "Gathering", "chinese": "萃", "pinyin": "Cuì", "judgement": "Unity grows when motive is sincere.", "image": "Lake over earth signifies assembly around shared cause.", "upper": "Dui", "lower": "Kun", "keywords": "Assembly|Devotion|Focus"},
|
||||
{"number": 46, "name": "Ascending", "chinese": "升", "pinyin": "Shēng", "judgement": "Slow steady progress pierces obstacles.", "image": "Earth over wind shows roots pushing upward.", "upper": "Kun", "lower": "Xun", "keywords": "Growth|Perseverance|Aspiration"},
|
||||
{"number": 47, "name": "Oppression", "chinese": "困", "pinyin": "Kùn", "judgement": "Constraints refine inner resolve.", "image": "Lake over water indicates fatigue relieved only by integrity.", "upper": "Dui", "lower": "Kan", "keywords": "Constraint|Endurance|Faith"},
|
||||
{"number": 48, "name": "The Well", "chinese": "井", "pinyin": "Jǐng", "judgement": "Communal resources must be maintained.", "image": "Water over wind depicts a well drawing fresh insight.", "upper": "Kan", "lower": "Xun", "keywords": "Resource|Maintenance|Depth"},
|
||||
{"number": 49, "name": "Revolution", "chinese": "革", "pinyin": "Gé", "judgement": "Change succeeds when timing and virtue align.", "image": "Lake over fire indicates shedding the old skin.", "upper": "Dui", "lower": "Li", "keywords": "Change|Timing|Renewal"},
|
||||
{"number": 50, "name": "The Vessel", "chinese": "鼎", "pinyin": "Dǐng", "judgement": "Elevated service transforms the culture.", "image": "Fire over wind depicts the cauldron that refines offerings.", "upper": "Li", "lower": "Xun", "keywords": "Service|Transformation|Heritage"},
|
||||
{"number": 51, "name": "Arousing Thunder", "chinese": "震", "pinyin": "Zhèn", "judgement": "Shock awakens the heart to reverence.", "image": "Thunder over thunder doubles the drumbeat of alertness.", "upper": "Zhen", "lower": "Zhen", "keywords": "Shock|Awakening|Movement"},
|
||||
{"number": 52, "name": "Still Mountain", "chinese": "艮", "pinyin": "Gèn", "judgement": "Cultivate stillness to master desire.", "image": "Mountain over mountain shows unmoving focus.", "upper": "Gen", "lower": "Gen", "keywords": "Stillness|Meditation|Boundaries"},
|
||||
{"number": 53, "name": "Gradual Development", "chinese": "漸", "pinyin": "Jiàn", "judgement": "Lasting progress resembles a tree growing rings.", "image": "Wind over mountain displays slow maturation.", "upper": "Xun", "lower": "Gen", "keywords": "Patience|Evolution|Commitment"},
|
||||
{"number": 54, "name": "Marrying Maiden", "chinese": "歸妹", "pinyin": "Guī Mèi", "judgement": "Adjust expectations when circumstances limit rank.", "image": "Thunder over lake spotlights unequal partnerships.", "upper": "Zhen", "lower": "Dui", "keywords": "Transition|Adaptation|Protocol"},
|
||||
{"number": 55, "name": "Abundance", "chinese": "豐", "pinyin": "Fēng", "judgement": "Radiant success must be handled with balance.", "image": "Thunder over fire illuminates the hall at noon.", "upper": "Zhen", "lower": "Li", "keywords": "Splendor|Responsibility|Timing"},
|
||||
{"number": 56, "name": "The Wanderer", "chinese": "旅", "pinyin": "Lǚ", "judgement": "Travel lightly and guard reputation.", "image": "Fire over mountain marks a traveler tending the campfire.", "upper": "Li", "lower": "Gen", "keywords": "Travel|Restraint|Awareness"},
|
||||
{"number": 57, "name": "Gentle Wind", "chinese": "巽", "pinyin": "Xùn", "judgement": "Persistent influence accomplishes what force cannot.", "image": "Wind over wind indicates subtle penetration.", "upper": "Xun", "lower": "Xun", "keywords": "Penetration|Diplomacy|Subtlety"},
|
||||
{"number": 58, "name": "Joyous Lake", "chinese": "兌", "pinyin": "Duì", "judgement": "Openhearted dialogue dissolves resentment.", "image": "Lake over lake celebrates shared delight.", "upper": "Dui", "lower": "Dui", "keywords": "Joy|Conversation|Trust"},
|
||||
{"number": 59, "name": "Dispersion", "chinese": "渙", "pinyin": "Huàn", "judgement": "Loosen rigid structures so spirit can move.", "image": "Wind over water shows breath dispersing fear.", "upper": "Xun", "lower": "Kan", "keywords": "Dissolve|Freedom|Relief"},
|
||||
{"number": 60, "name": "Limitation", "chinese": "節", "pinyin": "Jié", "judgement": "Clear boundaries enable real freedom.", "image": "Water over lake portrays calibrated vessels.", "upper": "Kan", "lower": "Dui", "keywords": "Boundaries|Measure|Discipline"},
|
||||
{"number": 61, "name": "Inner Truth", "chinese": "中孚", "pinyin": "Zhōng Fú", "judgement": "Trustworthiness unites disparate groups.", "image": "Wind over lake depicts resonance within the heart.", "upper": "Xun", "lower": "Dui", "keywords": "Sincerity|Empathy|Alignment"},
|
||||
{"number": 62, "name": "Small Exceeding", "chinese": "小過", "pinyin": "Xiǎo Guò", "judgement": "Attend to details when stakes are delicate.", "image": "Thunder over mountain reveals careful movement.", "upper": "Zhen", "lower": "Gen", "keywords": "Detail|Caution|Adjustment"},
|
||||
{"number": 63, "name": "After Completion", "chinese": "既濟", "pinyin": "Jì Jì", "judgement": "Success endures only if vigilance continues.", "image": "Water over fire displays balance maintained through work.", "upper": "Kan", "lower": "Li", "keywords": "Completion|Maintenance|Balance"},
|
||||
{"number": 64, "name": "Before Completion", "chinese": "未濟", "pinyin": "Wèi Jì", "judgement": "Stay attentive as outcomes crystallize.", "image": "Fire over water illustrates the final push before harmony.", "upper": "Li", "lower": "Kan", "keywords": "Transition|Focus|Preparation"},
|
||||
{
|
||||
"number": 1,
|
||||
"name": "Creative Force",
|
||||
"chinese": "乾",
|
||||
"pinyin": "Qián",
|
||||
"judgement": "Initiative succeeds when anchored in integrity.",
|
||||
"image": "Heaven above and below mirrors unstoppable drive.",
|
||||
"upper": "Qian",
|
||||
"lower": "Qian",
|
||||
"keywords": "Leadership|Momentum|Clarity",
|
||||
},
|
||||
{
|
||||
"number": 2,
|
||||
"name": "Receptive Field",
|
||||
"chinese": "坤",
|
||||
"pinyin": "Kūn",
|
||||
"judgement": "Grounded support flourishes through patience.",
|
||||
"image": "Earth layered upon earth offers fertile space.",
|
||||
"upper": "Kun",
|
||||
"lower": "Kun",
|
||||
"keywords": "Nurture|Support|Yielding",
|
||||
},
|
||||
{
|
||||
"number": 3,
|
||||
"name": "Sprouting",
|
||||
"chinese": "屯",
|
||||
"pinyin": "Zhūn",
|
||||
"judgement": "Challenges at the start need perseverance.",
|
||||
"image": "Water over thunder shows storms that germinate seeds.",
|
||||
"upper": "Kan",
|
||||
"lower": "Zhen",
|
||||
"keywords": "Beginnings|Struggle|Resolve",
|
||||
},
|
||||
{
|
||||
"number": 4,
|
||||
"name": "Youthful Insight",
|
||||
"chinese": "蒙",
|
||||
"pinyin": "Méng",
|
||||
"judgement": "Ignorance yields to steady guidance.",
|
||||
"image": "Mountain above water signals learning via restraint.",
|
||||
"upper": "Gen",
|
||||
"lower": "Kan",
|
||||
"keywords": "Study|Mentorship|Humility",
|
||||
},
|
||||
{
|
||||
"number": 5,
|
||||
"name": "Waiting",
|
||||
"chinese": "需",
|
||||
"pinyin": "Xū",
|
||||
"judgement": "Hold position until nourishment arrives.",
|
||||
"image": "Water above heaven depicts clouds gathering provision.",
|
||||
"upper": "Kan",
|
||||
"lower": "Qian",
|
||||
"keywords": "Patience|Faith|Preparation",
|
||||
},
|
||||
{
|
||||
"number": 6,
|
||||
"name": "Conflict",
|
||||
"chinese": "訟",
|
||||
"pinyin": "Sòng",
|
||||
"judgement": "Clarity and fairness prevent escalation.",
|
||||
"image": "Heaven above water shows tension seeking balance.",
|
||||
"upper": "Qian",
|
||||
"lower": "Kan",
|
||||
"keywords": "Debate|Justice|Boundaries",
|
||||
},
|
||||
{
|
||||
"number": 7,
|
||||
"name": "Collective Force",
|
||||
"chinese": "師",
|
||||
"pinyin": "Shī",
|
||||
"judgement": "Coordinated effort requires disciplined leadership.",
|
||||
"image": "Earth over water mirrors troops marshaling supplies.",
|
||||
"upper": "Kun",
|
||||
"lower": "Kan",
|
||||
"keywords": "Discipline|Leadership|Community",
|
||||
},
|
||||
{
|
||||
"number": 8,
|
||||
"name": "Union",
|
||||
"chinese": "比",
|
||||
"pinyin": "Bǐ",
|
||||
"judgement": "Shared values attract loyal allies.",
|
||||
"image": "Water over earth highlights bonds formed through empathy.",
|
||||
"upper": "Kan",
|
||||
"lower": "Kun",
|
||||
"keywords": "Alliance|Affinity|Trust",
|
||||
},
|
||||
{
|
||||
"number": 9,
|
||||
"name": "Small Accumulating",
|
||||
"chinese": "小畜",
|
||||
"pinyin": "Xiǎo Chù",
|
||||
"judgement": "Gentle restraint nurtures gradual gains.",
|
||||
"image": "Wind over heaven indicates tender guidance on great power.",
|
||||
"upper": "Xun",
|
||||
"lower": "Qian",
|
||||
"keywords": "Restraint|Cultivation|Care",
|
||||
},
|
||||
{
|
||||
"number": 10,
|
||||
"name": "Treading",
|
||||
"chinese": "履",
|
||||
"pinyin": "Lǚ",
|
||||
"judgement": "Walk with awareness when near power.",
|
||||
"image": "Heaven over lake shows respect between ranks.",
|
||||
"upper": "Qian",
|
||||
"lower": "Dui",
|
||||
"keywords": "Conduct|Respect|Sensitivity",
|
||||
},
|
||||
{
|
||||
"number": 11,
|
||||
"name": "Peace",
|
||||
"chinese": "泰",
|
||||
"pinyin": "Tài",
|
||||
"judgement": "Harmony thrives when resources circulate freely.",
|
||||
"image": "Earth over heaven signals prosperity descending.",
|
||||
"upper": "Kun",
|
||||
"lower": "Qian",
|
||||
"keywords": "Harmony|Prosperity|Flourish",
|
||||
},
|
||||
{
|
||||
"number": 12,
|
||||
"name": "Standstill",
|
||||
"chinese": "否",
|
||||
"pinyin": "Pǐ",
|
||||
"judgement": "When channels close, conserve strength.",
|
||||
"image": "Heaven over earth reveals blocked exchange.",
|
||||
"upper": "Qian",
|
||||
"lower": "Kun",
|
||||
"keywords": "Stagnation|Reflection|Pause",
|
||||
},
|
||||
{
|
||||
"number": 13,
|
||||
"name": "Fellowship",
|
||||
"chinese": "同人",
|
||||
"pinyin": "Tóng Rén",
|
||||
"judgement": "Shared purpose unites distant hearts.",
|
||||
"image": "Heaven over fire shows clarity within community.",
|
||||
"upper": "Qian",
|
||||
"lower": "Li",
|
||||
"keywords": "Community|Shared Vision|Openness",
|
||||
},
|
||||
{
|
||||
"number": 14,
|
||||
"name": "Great Possession",
|
||||
"chinese": "大有",
|
||||
"pinyin": "Dà Yǒu",
|
||||
"judgement": "Generosity cements lasting influence.",
|
||||
"image": "Fire over heaven reflects radiance sustained by ethics.",
|
||||
"upper": "Li",
|
||||
"lower": "Qian",
|
||||
"keywords": "Wealth|Stewardship|Confidence",
|
||||
},
|
||||
{
|
||||
"number": 15,
|
||||
"name": "Modesty",
|
||||
"chinese": "謙",
|
||||
"pinyin": "Qiān",
|
||||
"judgement": "Balance is found by lowering the proud.",
|
||||
"image": "Earth over mountain reveals humility safeguarding strength.",
|
||||
"upper": "Kun",
|
||||
"lower": "Gen",
|
||||
"keywords": "Humility|Balance|Service",
|
||||
},
|
||||
{
|
||||
"number": 16,
|
||||
"name": "Enthusiasm",
|
||||
"chinese": "豫",
|
||||
"pinyin": "Yù",
|
||||
"judgement": "Inspired music rallies the people.",
|
||||
"image": "Thunder over earth depicts drums stirring hearts.",
|
||||
"upper": "Zhen",
|
||||
"lower": "Kun",
|
||||
"keywords": "Inspiration|Celebration|Momentum",
|
||||
},
|
||||
{
|
||||
"number": 17,
|
||||
"name": "Following",
|
||||
"chinese": "隨",
|
||||
"pinyin": "Suí",
|
||||
"judgement": "Adapt willingly to timely leadership.",
|
||||
"image": "Lake over thunder points to joyful allegiance.",
|
||||
"upper": "Dui",
|
||||
"lower": "Zhen",
|
||||
"keywords": "Adaptation|Loyalty|Flow",
|
||||
},
|
||||
{
|
||||
"number": 18,
|
||||
"name": "Repairing",
|
||||
"chinese": "蠱",
|
||||
"pinyin": "Gǔ",
|
||||
"judgement": "Address decay with responsibility and care.",
|
||||
"image": "Mountain over wind shows correction of lineages.",
|
||||
"upper": "Gen",
|
||||
"lower": "Xun",
|
||||
"keywords": "Restoration|Accountability|Healing",
|
||||
},
|
||||
{
|
||||
"number": 19,
|
||||
"name": "Approach",
|
||||
"chinese": "臨",
|
||||
"pinyin": "Lín",
|
||||
"judgement": "Leaders draw near to listen sincerely.",
|
||||
"image": "Earth over lake signifies compassion visiting the people.",
|
||||
"upper": "Kun",
|
||||
"lower": "Dui",
|
||||
"keywords": "Empathy|Guidance|Presence",
|
||||
},
|
||||
{
|
||||
"number": 20,
|
||||
"name": "Contemplation",
|
||||
"chinese": "觀",
|
||||
"pinyin": "Guān",
|
||||
"judgement": "Observation inspires ethical alignment.",
|
||||
"image": "Wind over earth is the elevated view of the sage.",
|
||||
"upper": "Xun",
|
||||
"lower": "Kun",
|
||||
"keywords": "Perspective|Ritual|Vision",
|
||||
},
|
||||
{
|
||||
"number": 21,
|
||||
"name": "Biting Through",
|
||||
"chinese": "噬嗑",
|
||||
"pinyin": "Shì Kè",
|
||||
"judgement": "Decisive action cuts through obstruction.",
|
||||
"image": "Fire over thunder shows justice enforced with clarity.",
|
||||
"upper": "Li",
|
||||
"lower": "Zhen",
|
||||
"keywords": "Decision|Justice|Resolve",
|
||||
},
|
||||
{
|
||||
"number": 22,
|
||||
"name": "Grace",
|
||||
"chinese": "賁",
|
||||
"pinyin": "Bì",
|
||||
"judgement": "Beauty adorns substance when humility remains.",
|
||||
"image": "Mountain over fire highlights poise and restraint.",
|
||||
"upper": "Gen",
|
||||
"lower": "Li",
|
||||
"keywords": "Aesthetics|Poise|Form",
|
||||
},
|
||||
{
|
||||
"number": 23,
|
||||
"name": "Splitting Apart",
|
||||
"chinese": "剝",
|
||||
"pinyin": "Bō",
|
||||
"judgement": "When decay spreads, strip away excess.",
|
||||
"image": "Mountain over earth signals outer shells falling.",
|
||||
"upper": "Gen",
|
||||
"lower": "Kun",
|
||||
"keywords": "Decline|Release|Truth",
|
||||
},
|
||||
{
|
||||
"number": 24,
|
||||
"name": "Return",
|
||||
"chinese": "復",
|
||||
"pinyin": "Fù",
|
||||
"judgement": "Cycles renew when rest follows completion.",
|
||||
"image": "Earth over thunder marks the turning of the year.",
|
||||
"upper": "Kun",
|
||||
"lower": "Zhen",
|
||||
"keywords": "Renewal|Rhythm|Faith",
|
||||
},
|
||||
{
|
||||
"number": 25,
|
||||
"name": "Innocence",
|
||||
"chinese": "無妄",
|
||||
"pinyin": "Wú Wàng",
|
||||
"judgement": "Sincerity triumphs over scheming.",
|
||||
"image": "Heaven over thunder shows spontaneous virtue.",
|
||||
"upper": "Qian",
|
||||
"lower": "Zhen",
|
||||
"keywords": "Authenticity|Spontaneity|Trust",
|
||||
},
|
||||
{
|
||||
"number": 26,
|
||||
"name": "Great Taming",
|
||||
"chinese": "大畜",
|
||||
"pinyin": "Dà Chù",
|
||||
"judgement": "Conserve strength until action serves wisdom.",
|
||||
"image": "Mountain over heaven portrays restraint harnessing power.",
|
||||
"upper": "Gen",
|
||||
"lower": "Qian",
|
||||
"keywords": "Discipline|Reserve|Mastery",
|
||||
},
|
||||
{
|
||||
"number": 27,
|
||||
"name": "Nourishment",
|
||||
"chinese": "頤",
|
||||
"pinyin": "Yí",
|
||||
"judgement": "Words and food alike must be chosen with care.",
|
||||
"image": "Mountain over thunder emphasizes mindful sustenance.",
|
||||
"upper": "Gen",
|
||||
"lower": "Zhen",
|
||||
"keywords": "Nutrition|Speech|Mindfulness",
|
||||
},
|
||||
{
|
||||
"number": 28,
|
||||
"name": "Great Exceeding",
|
||||
"chinese": "大過",
|
||||
"pinyin": "Dà Guò",
|
||||
"judgement": "Bearing heavy loads demands flexibility.",
|
||||
"image": "Lake over wind shows a beam bending before it breaks.",
|
||||
"upper": "Dui",
|
||||
"lower": "Xun",
|
||||
"keywords": "Weight|Adaptability|Responsibility",
|
||||
},
|
||||
{
|
||||
"number": 29,
|
||||
"name": "The Abyss",
|
||||
"chinese": "坎",
|
||||
"pinyin": "Kǎn",
|
||||
"judgement": "Repeated trials teach sincere caution.",
|
||||
"image": "Water over water is the perilous gorge.",
|
||||
"upper": "Kan",
|
||||
"lower": "Kan",
|
||||
"keywords": "Trial|Honesty|Depth",
|
||||
},
|
||||
{
|
||||
"number": 30,
|
||||
"name": "Radiance",
|
||||
"chinese": "離",
|
||||
"pinyin": "Lí",
|
||||
"judgement": "Clarity is maintained by tending the flame.",
|
||||
"image": "Fire over fire represents brilliance sustained through care.",
|
||||
"upper": "Li",
|
||||
"lower": "Li",
|
||||
"keywords": "Illumination|Culture|Attention",
|
||||
},
|
||||
{
|
||||
"number": 31,
|
||||
"name": "Influence",
|
||||
"chinese": "咸",
|
||||
"pinyin": "Xián",
|
||||
"judgement": "Sincere attraction arises from mutual respect.",
|
||||
"image": "Lake over mountain highlights responsive hearts.",
|
||||
"upper": "Dui",
|
||||
"lower": "Gen",
|
||||
"keywords": "Attraction|Mutuality|Sensitivity",
|
||||
},
|
||||
{
|
||||
"number": 32,
|
||||
"name": "Duration",
|
||||
"chinese": "恒",
|
||||
"pinyin": "Héng",
|
||||
"judgement": "Commitment endures when balanced.",
|
||||
"image": "Thunder over wind speaks of constancy amid change.",
|
||||
"upper": "Zhen",
|
||||
"lower": "Xun",
|
||||
"keywords": "Commitment|Consistency|Rhythm",
|
||||
},
|
||||
{
|
||||
"number": 33,
|
||||
"name": "Retreat",
|
||||
"chinese": "遯",
|
||||
"pinyin": "Dùn",
|
||||
"judgement": "Strategic withdrawal preserves integrity.",
|
||||
"image": "Heaven over mountain shows noble retreat.",
|
||||
"upper": "Qian",
|
||||
"lower": "Gen",
|
||||
"keywords": "Withdrawal|Strategy|Self-care",
|
||||
},
|
||||
{
|
||||
"number": 34,
|
||||
"name": "Great Power",
|
||||
"chinese": "大壯",
|
||||
"pinyin": "Dà Zhuàng",
|
||||
"judgement": "Strength must remain aligned with virtue.",
|
||||
"image": "Thunder over heaven affirms action matched with purpose.",
|
||||
"upper": "Zhen",
|
||||
"lower": "Qian",
|
||||
"keywords": "Power|Ethics|Momentum",
|
||||
},
|
||||
{
|
||||
"number": 35,
|
||||
"name": "Progress",
|
||||
"chinese": "晉",
|
||||
"pinyin": "Jìn",
|
||||
"judgement": "Advancement arrives through clarity and loyalty.",
|
||||
"image": "Fire over earth depicts dawn spreading across the plain.",
|
||||
"upper": "Li",
|
||||
"lower": "Kun",
|
||||
"keywords": "Advancement|Visibility|Service",
|
||||
},
|
||||
{
|
||||
"number": 36,
|
||||
"name": "Darkening Light",
|
||||
"chinese": "明夷",
|
||||
"pinyin": "Míng Yí",
|
||||
"judgement": "Protect the inner light when circumstances grow harsh.",
|
||||
"image": "Earth over fire shows brilliance concealed for safety.",
|
||||
"upper": "Kun",
|
||||
"lower": "Li",
|
||||
"keywords": "Protection|Subtlety|Endurance",
|
||||
},
|
||||
{
|
||||
"number": 37,
|
||||
"name": "Family",
|
||||
"chinese": "家人",
|
||||
"pinyin": "Jiā Rén",
|
||||
"judgement": "Clear roles nourish household harmony.",
|
||||
"image": "Wind over fire indicates rituals ordering the home.",
|
||||
"upper": "Xun",
|
||||
"lower": "Li",
|
||||
"keywords": "Home|Roles|Care",
|
||||
},
|
||||
{
|
||||
"number": 38,
|
||||
"name": "Opposition",
|
||||
"chinese": "睽",
|
||||
"pinyin": "Kuí",
|
||||
"judgement": "Recognize difference without hostility.",
|
||||
"image": "Fire over lake reflects contrast seeking balance.",
|
||||
"upper": "Li",
|
||||
"lower": "Dui",
|
||||
"keywords": "Contrast|Perspective|Tolerance",
|
||||
},
|
||||
{
|
||||
"number": 39,
|
||||
"name": "Obstruction",
|
||||
"chinese": "蹇",
|
||||
"pinyin": "Jiǎn",
|
||||
"judgement": "Turn hindrance into training.",
|
||||
"image": "Water over mountain shows difficult ascent.",
|
||||
"upper": "Kan",
|
||||
"lower": "Gen",
|
||||
"keywords": "Obstacle|Effort|Learning",
|
||||
},
|
||||
{
|
||||
"number": 40,
|
||||
"name": "Deliverance",
|
||||
"chinese": "解",
|
||||
"pinyin": "Xiè",
|
||||
"judgement": "Relief comes when knots are untied.",
|
||||
"image": "Thunder over water portrays release after storm.",
|
||||
"upper": "Zhen",
|
||||
"lower": "Kan",
|
||||
"keywords": "Release|Solution|Breath",
|
||||
},
|
||||
{
|
||||
"number": 41,
|
||||
"name": "Decrease",
|
||||
"chinese": "損",
|
||||
"pinyin": "Sǔn",
|
||||
"judgement": "Voluntary simplicity restores balance.",
|
||||
"image": "Mountain over lake shows graceful sharing of resources.",
|
||||
"upper": "Gen",
|
||||
"lower": "Dui",
|
||||
"keywords": "Simplicity|Offering|Balance",
|
||||
},
|
||||
{
|
||||
"number": 42,
|
||||
"name": "Increase",
|
||||
"chinese": "益",
|
||||
"pinyin": "Yì",
|
||||
"judgement": "Blessings multiply when shared.",
|
||||
"image": "Wind over thunder reveals generous expansion.",
|
||||
"upper": "Xun",
|
||||
"lower": "Zhen",
|
||||
"keywords": "Growth|Generosity|Opportunity",
|
||||
},
|
||||
{
|
||||
"number": 43,
|
||||
"name": "Breakthrough",
|
||||
"chinese": "夬",
|
||||
"pinyin": "Guài",
|
||||
"judgement": "Speak truth boldly to clear corruption.",
|
||||
"image": "Lake over heaven highlights decisive proclamation.",
|
||||
"upper": "Dui",
|
||||
"lower": "Qian",
|
||||
"keywords": "Resolution|Declaration|Courage",
|
||||
},
|
||||
{
|
||||
"number": 44,
|
||||
"name": "Encounter",
|
||||
"chinese": "姤",
|
||||
"pinyin": "Gòu",
|
||||
"judgement": "Unexpected influence requires discernment.",
|
||||
"image": "Heaven over wind shows potent visitors arriving.",
|
||||
"upper": "Qian",
|
||||
"lower": "Xun",
|
||||
"keywords": "Encounter|Discernment|Temptation",
|
||||
},
|
||||
{
|
||||
"number": 45,
|
||||
"name": "Gathering",
|
||||
"chinese": "萃",
|
||||
"pinyin": "Cuì",
|
||||
"judgement": "Unity grows when motive is sincere.",
|
||||
"image": "Lake over earth signifies assembly around shared cause.",
|
||||
"upper": "Dui",
|
||||
"lower": "Kun",
|
||||
"keywords": "Assembly|Devotion|Focus",
|
||||
},
|
||||
{
|
||||
"number": 46,
|
||||
"name": "Ascending",
|
||||
"chinese": "升",
|
||||
"pinyin": "Shēng",
|
||||
"judgement": "Slow steady progress pierces obstacles.",
|
||||
"image": "Earth over wind shows roots pushing upward.",
|
||||
"upper": "Kun",
|
||||
"lower": "Xun",
|
||||
"keywords": "Growth|Perseverance|Aspiration",
|
||||
},
|
||||
{
|
||||
"number": 47,
|
||||
"name": "Oppression",
|
||||
"chinese": "困",
|
||||
"pinyin": "Kùn",
|
||||
"judgement": "Constraints refine inner resolve.",
|
||||
"image": "Lake over water indicates fatigue relieved only by integrity.",
|
||||
"upper": "Dui",
|
||||
"lower": "Kan",
|
||||
"keywords": "Constraint|Endurance|Faith",
|
||||
},
|
||||
{
|
||||
"number": 48,
|
||||
"name": "The Well",
|
||||
"chinese": "井",
|
||||
"pinyin": "Jǐng",
|
||||
"judgement": "Communal resources must be maintained.",
|
||||
"image": "Water over wind depicts a well drawing fresh insight.",
|
||||
"upper": "Kan",
|
||||
"lower": "Xun",
|
||||
"keywords": "Resource|Maintenance|Depth",
|
||||
},
|
||||
{
|
||||
"number": 49,
|
||||
"name": "Revolution",
|
||||
"chinese": "革",
|
||||
"pinyin": "Gé",
|
||||
"judgement": "Change succeeds when timing and virtue align.",
|
||||
"image": "Lake over fire indicates shedding the old skin.",
|
||||
"upper": "Dui",
|
||||
"lower": "Li",
|
||||
"keywords": "Change|Timing|Renewal",
|
||||
},
|
||||
{
|
||||
"number": 50,
|
||||
"name": "The Vessel",
|
||||
"chinese": "鼎",
|
||||
"pinyin": "Dǐng",
|
||||
"judgement": "Elevated service transforms the culture.",
|
||||
"image": "Fire over wind depicts the cauldron that refines offerings.",
|
||||
"upper": "Li",
|
||||
"lower": "Xun",
|
||||
"keywords": "Service|Transformation|Heritage",
|
||||
},
|
||||
{
|
||||
"number": 51,
|
||||
"name": "Arousing Thunder",
|
||||
"chinese": "震",
|
||||
"pinyin": "Zhèn",
|
||||
"judgement": "Shock awakens the heart to reverence.",
|
||||
"image": "Thunder over thunder doubles the drumbeat of alertness.",
|
||||
"upper": "Zhen",
|
||||
"lower": "Zhen",
|
||||
"keywords": "Shock|Awakening|Movement",
|
||||
},
|
||||
{
|
||||
"number": 52,
|
||||
"name": "Still Mountain",
|
||||
"chinese": "艮",
|
||||
"pinyin": "Gèn",
|
||||
"judgement": "Cultivate stillness to master desire.",
|
||||
"image": "Mountain over mountain shows unmoving focus.",
|
||||
"upper": "Gen",
|
||||
"lower": "Gen",
|
||||
"keywords": "Stillness|Meditation|Boundaries",
|
||||
},
|
||||
{
|
||||
"number": 53,
|
||||
"name": "Gradual Development",
|
||||
"chinese": "漸",
|
||||
"pinyin": "Jiàn",
|
||||
"judgement": "Lasting progress resembles a tree growing rings.",
|
||||
"image": "Wind over mountain displays slow maturation.",
|
||||
"upper": "Xun",
|
||||
"lower": "Gen",
|
||||
"keywords": "Patience|Evolution|Commitment",
|
||||
},
|
||||
{
|
||||
"number": 54,
|
||||
"name": "Marrying Maiden",
|
||||
"chinese": "歸妹",
|
||||
"pinyin": "Guī Mèi",
|
||||
"judgement": "Adjust expectations when circumstances limit rank.",
|
||||
"image": "Thunder over lake spotlights unequal partnerships.",
|
||||
"upper": "Zhen",
|
||||
"lower": "Dui",
|
||||
"keywords": "Transition|Adaptation|Protocol",
|
||||
},
|
||||
{
|
||||
"number": 55,
|
||||
"name": "Abundance",
|
||||
"chinese": "豐",
|
||||
"pinyin": "Fēng",
|
||||
"judgement": "Radiant success must be handled with balance.",
|
||||
"image": "Thunder over fire illuminates the hall at noon.",
|
||||
"upper": "Zhen",
|
||||
"lower": "Li",
|
||||
"keywords": "Splendor|Responsibility|Timing",
|
||||
},
|
||||
{
|
||||
"number": 56,
|
||||
"name": "The Wanderer",
|
||||
"chinese": "旅",
|
||||
"pinyin": "Lǚ",
|
||||
"judgement": "Travel lightly and guard reputation.",
|
||||
"image": "Fire over mountain marks a traveler tending the campfire.",
|
||||
"upper": "Li",
|
||||
"lower": "Gen",
|
||||
"keywords": "Travel|Restraint|Awareness",
|
||||
},
|
||||
{
|
||||
"number": 57,
|
||||
"name": "Gentle Wind",
|
||||
"chinese": "巽",
|
||||
"pinyin": "Xùn",
|
||||
"judgement": "Persistent influence accomplishes what force cannot.",
|
||||
"image": "Wind over wind indicates subtle penetration.",
|
||||
"upper": "Xun",
|
||||
"lower": "Xun",
|
||||
"keywords": "Penetration|Diplomacy|Subtlety",
|
||||
},
|
||||
{
|
||||
"number": 58,
|
||||
"name": "Joyous Lake",
|
||||
"chinese": "兌",
|
||||
"pinyin": "Duì",
|
||||
"judgement": "Openhearted dialogue dissolves resentment.",
|
||||
"image": "Lake over lake celebrates shared delight.",
|
||||
"upper": "Dui",
|
||||
"lower": "Dui",
|
||||
"keywords": "Joy|Conversation|Trust",
|
||||
},
|
||||
{
|
||||
"number": 59,
|
||||
"name": "Dispersion",
|
||||
"chinese": "渙",
|
||||
"pinyin": "Huàn",
|
||||
"judgement": "Loosen rigid structures so spirit can move.",
|
||||
"image": "Wind over water shows breath dispersing fear.",
|
||||
"upper": "Xun",
|
||||
"lower": "Kan",
|
||||
"keywords": "Dissolve|Freedom|Relief",
|
||||
},
|
||||
{
|
||||
"number": 60,
|
||||
"name": "Limitation",
|
||||
"chinese": "節",
|
||||
"pinyin": "Jié",
|
||||
"judgement": "Clear boundaries enable real freedom.",
|
||||
"image": "Water over lake portrays calibrated vessels.",
|
||||
"upper": "Kan",
|
||||
"lower": "Dui",
|
||||
"keywords": "Boundaries|Measure|Discipline",
|
||||
},
|
||||
{
|
||||
"number": 61,
|
||||
"name": "Inner Truth",
|
||||
"chinese": "中孚",
|
||||
"pinyin": "Zhōng Fú",
|
||||
"judgement": "Trustworthiness unites disparate groups.",
|
||||
"image": "Wind over lake depicts resonance within the heart.",
|
||||
"upper": "Xun",
|
||||
"lower": "Dui",
|
||||
"keywords": "Sincerity|Empathy|Alignment",
|
||||
},
|
||||
{
|
||||
"number": 62,
|
||||
"name": "Small Exceeding",
|
||||
"chinese": "小過",
|
||||
"pinyin": "Xiǎo Guò",
|
||||
"judgement": "Attend to details when stakes are delicate.",
|
||||
"image": "Thunder over mountain reveals careful movement.",
|
||||
"upper": "Zhen",
|
||||
"lower": "Gen",
|
||||
"keywords": "Detail|Caution|Adjustment",
|
||||
},
|
||||
{
|
||||
"number": 63,
|
||||
"name": "After Completion",
|
||||
"chinese": "既濟",
|
||||
"pinyin": "Jì Jì",
|
||||
"judgement": "Success endures only if vigilance continues.",
|
||||
"image": "Water over fire displays balance maintained through work.",
|
||||
"upper": "Kan",
|
||||
"lower": "Li",
|
||||
"keywords": "Completion|Maintenance|Balance",
|
||||
},
|
||||
{
|
||||
"number": 64,
|
||||
"name": "Before Completion",
|
||||
"chinese": "未濟",
|
||||
"pinyin": "Wèi Jì",
|
||||
"judgement": "Stay attentive as outcomes crystallize.",
|
||||
"image": "Fire over water illustrates the final push before harmony.",
|
||||
"upper": "Li",
|
||||
"lower": "Kan",
|
||||
"keywords": "Transition|Focus|Preparation",
|
||||
},
|
||||
]
|
||||
planet_cycle = ["Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Earth"]
|
||||
self._hexagrams = {}
|
||||
|
||||
@@ -14,6 +14,7 @@ from utils.attributes import Number, Planet
|
||||
@dataclass
|
||||
class Trigram:
|
||||
"""Represents one of the eight I Ching trigrams."""
|
||||
|
||||
name: str
|
||||
chinese_name: str
|
||||
pinyin: str
|
||||
@@ -27,6 +28,7 @@ class Trigram:
|
||||
@dataclass
|
||||
class Hexagram:
|
||||
"""Represents an I Ching hexagram with Tarot correspondence."""
|
||||
|
||||
number: int
|
||||
name: str
|
||||
chinese_name: str
|
||||
|
||||
@@ -13,7 +13,7 @@ class Letter:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._initialized: bool = False
|
||||
self._loader: 'CardDataLoader | None' = None
|
||||
self._loader: "CardDataLoader | None" = None
|
||||
self.alphabet = CollectionAccessor(self._get_alphabets)
|
||||
self.cipher = CollectionAccessor(self._get_ciphers)
|
||||
self.letter = CollectionAccessor(self._get_letters)
|
||||
@@ -26,10 +26,11 @@ class Letter:
|
||||
return
|
||||
|
||||
from tarot.card.data import CardDataLoader
|
||||
|
||||
self._loader = CardDataLoader()
|
||||
self._initialized = True
|
||||
|
||||
def _require_loader(self) -> 'CardDataLoader':
|
||||
def _require_loader(self) -> "CardDataLoader":
|
||||
self._ensure_initialized()
|
||||
assert self._loader is not None, "Loader not initialized"
|
||||
return self._loader
|
||||
@@ -54,7 +55,7 @@ class Letter:
|
||||
loader = self._require_loader()
|
||||
return loader._periodic_table.copy()
|
||||
|
||||
def word(self, text: str, *, alphabet: str = 'english'):
|
||||
def word(self, text: str, *, alphabet: str = "english"):
|
||||
"""
|
||||
Start a fluent cipher request for the given text.
|
||||
|
||||
|
||||
@@ -17,13 +17,14 @@ Each letter has attributes like:
|
||||
- Musical Note
|
||||
"""
|
||||
|
||||
from typing import List, Optional, Dict, Union, TYPE_CHECKING
|
||||
from dataclasses import dataclass, field
|
||||
from utils.filter import universal_filter, get_filterable_fields, format_results
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Union
|
||||
|
||||
from utils.filter import format_results, get_filterable_fields, universal_filter
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from utils.query import CollectionAccessor
|
||||
from tarot.attributes import Path
|
||||
from utils.query import CollectionAccessor
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -34,7 +35,8 @@ class TarotLetter:
|
||||
Wraps Path objects from CardDataLoader to provide a letter-focused interface
|
||||
while maintaining a single source of truth.
|
||||
"""
|
||||
path: 'Path' # Reference to the actual Path object from CardDataLoader
|
||||
|
||||
path: "Path" # Reference to the actual Path object from CardDataLoader
|
||||
letter_type: str # "Mother", "Double", or "Simple" (derived from path)
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
@@ -149,7 +151,7 @@ class LetterAccessor:
|
||||
|
||||
def by_type(self, letter_type: str) -> List[TarotLetter]:
|
||||
"""Filter by type: 'Mother', 'Double', or 'Simple'."""
|
||||
return [l for l in self._letters.values() if l.letter_type == letter_type]
|
||||
return [letter for letter in self._letters.values() if letter.letter_type == letter_type]
|
||||
|
||||
def by_zodiac(self, zodiac: str) -> Optional[TarotLetter]:
|
||||
"""Get letter by zodiac sign."""
|
||||
@@ -160,11 +162,15 @@ class LetterAccessor:
|
||||
|
||||
def by_planet(self, planet: str) -> List[TarotLetter]:
|
||||
"""Get letters by planet."""
|
||||
return [l for l in self._letters.values() if l.planet and planet.lower() in l.planet.lower()]
|
||||
return [
|
||||
letter
|
||||
for letter in self._letters.values()
|
||||
if letter.planet and planet.lower() in letter.planet.lower()
|
||||
]
|
||||
|
||||
def by_trump(self, trump: str) -> Optional[TarotLetter]:
|
||||
"""Get letter by tarot trump."""
|
||||
return next((l for l in self._letters.values() if l.trump == trump), None)
|
||||
return next((letter for letter in self._letters.values() if letter.trump == trump), None)
|
||||
|
||||
def get_filterable_fields(self) -> List[str]:
|
||||
"""
|
||||
@@ -265,12 +271,13 @@ class IChing:
|
||||
all_hex = hexagrams.list()
|
||||
"""
|
||||
|
||||
trigram: 'CollectionAccessor'
|
||||
hexagram: 'CollectionAccessor'
|
||||
trigram: "CollectionAccessor"
|
||||
hexagram: "CollectionAccessor"
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize iChing accessor with trigram and hexagram collections."""
|
||||
from tarot.letter import iChing as iching_module
|
||||
|
||||
self.trigram = iching_module.trigram.trigram
|
||||
self.hexagram = iching_module.hexagram.hexagram
|
||||
|
||||
@@ -286,7 +293,7 @@ class IChing:
|
||||
class LettersRegistry:
|
||||
"""Registry and accessor for all Hebrew letters with Tarot correspondences."""
|
||||
|
||||
_instance: Optional['LettersRegistry'] = None
|
||||
_instance: Optional["LettersRegistry"] = None
|
||||
_letters: Dict[str, TarotLetter] = {}
|
||||
_initialized: bool = False
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ if TYPE_CHECKING:
|
||||
class _Word:
|
||||
"""Fluent accessor for word analysis and cipher operations."""
|
||||
|
||||
_loader: 'CardDataLoader | None' = None
|
||||
_loader: "CardDataLoader | None" = None
|
||||
_initialized: bool = False
|
||||
|
||||
@classmethod
|
||||
@@ -19,11 +19,12 @@ class _Word:
|
||||
return
|
||||
|
||||
from tarot.card.data import CardDataLoader
|
||||
|
||||
cls._loader = CardDataLoader()
|
||||
cls._initialized = True
|
||||
|
||||
@classmethod
|
||||
def word(cls, text: str, *, alphabet: str = 'english'):
|
||||
def word(cls, text: str, *, alphabet: str = "english"):
|
||||
"""
|
||||
Start a fluent cipher request for the given text.
|
||||
|
||||
|
||||
@@ -14,6 +14,6 @@ Usage:
|
||||
colors = number.color()
|
||||
"""
|
||||
|
||||
from .number import number, calculate_digital_root
|
||||
from .number import calculate_digital_root, number
|
||||
|
||||
__all__ = ["number", "calculate_digital_root"]
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
"""Numbers loader - access to numerology and number correspondences."""
|
||||
|
||||
from typing import Dict, Optional, Union, overload
|
||||
from typing import TYPE_CHECKING, Dict, Optional, Union, overload
|
||||
|
||||
from utils.filter import universal_filter
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from utils.attributes import Color, Number
|
||||
|
||||
|
||||
def calculate_digital_root(value: int) -> int:
|
||||
"""
|
||||
@@ -46,8 +50,8 @@ class Numbers:
|
||||
"""
|
||||
|
||||
# These are populated on first access from CardDataLoader
|
||||
_numbers: Dict[int, 'Number'] = {} # type: ignore
|
||||
_colors: Dict[int, 'Color'] = {} # type: ignore
|
||||
_numbers: Dict[int, "Number"] = {} # type: ignore
|
||||
_colors: Dict[int, "Color"] = {} # type: ignore
|
||||
_initialized: bool = False
|
||||
|
||||
@classmethod
|
||||
@@ -57,6 +61,7 @@ class Numbers:
|
||||
return
|
||||
|
||||
from tarot.card.data import CardDataLoader
|
||||
|
||||
loader = CardDataLoader()
|
||||
cls._numbers = loader.number()
|
||||
cls._colors = loader.color()
|
||||
@@ -64,16 +69,14 @@ class Numbers:
|
||||
|
||||
@classmethod
|
||||
@overload
|
||||
def number(cls, value: int) -> Optional['Number']:
|
||||
...
|
||||
def number(cls, value: int) -> Optional["Number"]: ...
|
||||
|
||||
@classmethod
|
||||
@overload
|
||||
def number(cls, value: None = ...) -> Dict[int, 'Number']:
|
||||
...
|
||||
def number(cls, value: None = ...) -> Dict[int, "Number"]: ...
|
||||
|
||||
@classmethod
|
||||
def number(cls, value: Optional[int] = None) -> Union[Optional['Number'], Dict[int, 'Number']]:
|
||||
def number(cls, value: Optional[int] = None) -> Union[Optional["Number"], Dict[int, "Number"]]:
|
||||
"""Return an individual Number or the full numerology table."""
|
||||
cls._ensure_initialized()
|
||||
if value is None:
|
||||
@@ -82,16 +85,16 @@ class Numbers:
|
||||
|
||||
@classmethod
|
||||
@overload
|
||||
def color(cls, sephera_number: int) -> Optional['Color']:
|
||||
...
|
||||
def color(cls, sephera_number: int) -> Optional["Color"]: ...
|
||||
|
||||
@classmethod
|
||||
@overload
|
||||
def color(cls, sephera_number: None = ...) -> Dict[int, 'Color']:
|
||||
...
|
||||
def color(cls, sephera_number: None = ...) -> Dict[int, "Color"]: ...
|
||||
|
||||
@classmethod
|
||||
def color(cls, sephera_number: Optional[int] = None) -> Union[Optional['Color'], Dict[int, 'Color']]:
|
||||
def color(
|
||||
cls, sephera_number: Optional[int] = None
|
||||
) -> Union[Optional["Color"], Dict[int, "Color"]]:
|
||||
"""Return a single color correspondence or the entire map."""
|
||||
cls._ensure_initialized()
|
||||
if sephera_number is None:
|
||||
@@ -99,13 +102,13 @@ class Numbers:
|
||||
return cls._colors.get(sephera_number)
|
||||
|
||||
@classmethod
|
||||
def color_by_number(cls, number: int) -> Optional['Color']:
|
||||
def color_by_number(cls, number: int) -> Optional["Color"]:
|
||||
"""Get a Color by mapping a number through digital root."""
|
||||
root = calculate_digital_root(number)
|
||||
return cls.color(root)
|
||||
|
||||
@classmethod
|
||||
def number_by_digital_root(cls, value: int) -> Optional['Number']:
|
||||
def number_by_digital_root(cls, value: int) -> Optional["Number"]:
|
||||
"""Get a Number object using digital root calculation."""
|
||||
root = calculate_digital_root(value)
|
||||
return cls.number(root)
|
||||
@@ -153,6 +156,7 @@ class Numbers:
|
||||
print(Numbers.display_filter_numbers(element="Fire"))
|
||||
"""
|
||||
from utils.filter import format_results
|
||||
|
||||
results = cls.filter_numbers(**kwargs)
|
||||
return format_results(results)
|
||||
|
||||
@@ -194,5 +198,6 @@ class Numbers:
|
||||
print(Numbers.display_filter_colors(element="Water"))
|
||||
"""
|
||||
from utils.filter import format_results
|
||||
|
||||
results = cls.filter_colors(**kwargs)
|
||||
return format_results(results)
|
||||
|
||||
@@ -29,7 +29,7 @@ class _Number:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._initialized: bool = False
|
||||
self._loader: 'CardDataLoader | None' = None
|
||||
self._loader: "CardDataLoader | None" = None
|
||||
self.number = CollectionAccessor(self._get_numbers)
|
||||
self.color = CollectionAccessor(self._get_colors)
|
||||
self.cipher = CollectionAccessor(self._get_ciphers)
|
||||
@@ -40,10 +40,11 @@ class _Number:
|
||||
return
|
||||
|
||||
from tarot.card.data import CardDataLoader
|
||||
|
||||
self._loader = CardDataLoader()
|
||||
self._initialized = True
|
||||
|
||||
def _require_loader(self) -> 'CardDataLoader':
|
||||
def _require_loader(self) -> "CardDataLoader":
|
||||
self._ensure_initialized()
|
||||
assert self._loader is not None, "Loader not initialized"
|
||||
return self._loader
|
||||
|
||||
@@ -23,44 +23,76 @@ Usage:
|
||||
card = Tarot.deck.card(3)
|
||||
"""
|
||||
|
||||
from .deck import Deck, Card, MajorCard, MinorCard, DLT
|
||||
from .attributes import (
|
||||
Month, Day, Weekday, Hour, ClockHour, Zodiac, Suit, Meaning, Letter,
|
||||
Sephera, PeriodicTable, Degree, AstrologicalInfluence,
|
||||
TreeOfLife, Correspondences, CardImage, DoublLetterTrump,
|
||||
EnglishAlphabet, GreekAlphabet, HebrewAlphabet,
|
||||
Trigram, Hexagram,
|
||||
EnochianTablet, EnochianGridPosition, EnochianArchetype, Path,
|
||||
)
|
||||
import kaballah
|
||||
from kaballah import Cube, Tree
|
||||
from kaballah.cube.attributes import CubeOfSpace, Wall, WallDirection
|
||||
|
||||
# Import from namespace folders
|
||||
from letter import hexagram, letter, trigram
|
||||
from number import calculate_digital_root, number
|
||||
from temporal import PlanetPosition, ThalemaClock
|
||||
from temporal import Zodiac as AstrologyZodiac
|
||||
|
||||
# Import shared attributes from utils
|
||||
from utils.attributes import (
|
||||
Note, Element, ElementType, Number, Color, Colorscale,
|
||||
Planet, God, Cipher, CipherResult, Perfume,
|
||||
Cipher,
|
||||
CipherResult,
|
||||
Color,
|
||||
Colorscale,
|
||||
Element,
|
||||
ElementType,
|
||||
God,
|
||||
Note,
|
||||
Number,
|
||||
Perfume,
|
||||
Planet,
|
||||
)
|
||||
|
||||
from .attributes import (
|
||||
AstrologicalInfluence,
|
||||
CardImage,
|
||||
ClockHour,
|
||||
Correspondences,
|
||||
Day,
|
||||
Degree,
|
||||
DoublLetterTrump,
|
||||
EnglishAlphabet,
|
||||
EnochianArchetype,
|
||||
EnochianGridPosition,
|
||||
EnochianTablet,
|
||||
GreekAlphabet,
|
||||
HebrewAlphabet,
|
||||
Hexagram,
|
||||
Hour,
|
||||
Letter,
|
||||
Meaning,
|
||||
Month,
|
||||
Path,
|
||||
PeriodicTable,
|
||||
Sephera,
|
||||
Suit,
|
||||
TreeOfLife,
|
||||
Trigram,
|
||||
Weekday,
|
||||
Zodiac,
|
||||
)
|
||||
from kaballah.cube.attributes import CubeOfSpace, WallDirection, Wall
|
||||
from .card.data import CardDataLoader, calculate_digital_root
|
||||
from .tarot_api import Tarot
|
||||
|
||||
# Import from card module (includes details, loader, and image_loader)
|
||||
from .card import (
|
||||
CardAccessor,
|
||||
CardDetailsRegistry,
|
||||
ImageDeckLoader,
|
||||
filter_cards_by_keywords,
|
||||
get_card_info,
|
||||
get_cards_by_suit,
|
||||
load_card_details,
|
||||
load_deck_details,
|
||||
get_cards_by_suit,
|
||||
filter_cards_by_keywords,
|
||||
print_card_details,
|
||||
get_card_info,
|
||||
ImageDeckLoader,
|
||||
load_deck_images,
|
||||
print_card_details,
|
||||
)
|
||||
|
||||
# Import from namespace folders
|
||||
from letter import letter, trigram, hexagram
|
||||
from number import number, calculate_digital_root
|
||||
import kaballah
|
||||
from kaballah import Tree, Cube
|
||||
from temporal import ThalemaClock, Zodiac as AstrologyZodiac, PlanetPosition
|
||||
from .card.data import CardDataLoader
|
||||
from .deck import DLT, Card, Deck, MajorCard, MinorCard
|
||||
from .tarot_api import Tarot
|
||||
|
||||
|
||||
def display(obj):
|
||||
@@ -76,7 +108,8 @@ def display(obj):
|
||||
display(num) # Shows all attributes nicely formatted
|
||||
"""
|
||||
from dataclasses import fields
|
||||
if hasattr(obj, '__dataclass_fields__'):
|
||||
|
||||
if hasattr(obj, "__dataclass_fields__"):
|
||||
# It's a dataclass - show all fields
|
||||
print(f"{obj.__class__.__name__}:")
|
||||
for field in fields(obj):
|
||||
@@ -96,12 +129,10 @@ __all__ = [
|
||||
"Tarot",
|
||||
"trigram",
|
||||
"hexagram",
|
||||
|
||||
# Temporal and astrological
|
||||
"ThalemaClock",
|
||||
"AstrologyZodiac",
|
||||
"PlanetPosition",
|
||||
|
||||
# Card details and loading
|
||||
"CardDetailsRegistry",
|
||||
"load_card_details",
|
||||
@@ -110,24 +141,20 @@ __all__ = [
|
||||
"filter_cards_by_keywords",
|
||||
"print_card_details",
|
||||
"get_card_info",
|
||||
|
||||
# Image loading
|
||||
"ImageDeckLoader",
|
||||
"load_deck_images",
|
||||
|
||||
# Utilities
|
||||
"display",
|
||||
"CardAccessor",
|
||||
"Tree",
|
||||
"Cube",
|
||||
|
||||
# Deck classes
|
||||
"Deck",
|
||||
"Card",
|
||||
"MajorCard",
|
||||
"MinorCard",
|
||||
"DLT",
|
||||
|
||||
# Calendar/attribute classes
|
||||
"Month",
|
||||
"Day",
|
||||
@@ -142,7 +169,6 @@ __all__ = [
|
||||
"CubeOfSpace",
|
||||
"WallDirection",
|
||||
"Wall",
|
||||
|
||||
# Sepheric classes
|
||||
"Sephera",
|
||||
"PeriodicTable",
|
||||
@@ -157,12 +183,10 @@ __all__ = [
|
||||
"EnochianTablet",
|
||||
"EnochianGridPosition",
|
||||
"EnochianArchetype",
|
||||
|
||||
# Alphabet classes
|
||||
"EnglishAlphabet",
|
||||
"GreekAlphabet",
|
||||
"HebrewAlphabet",
|
||||
|
||||
# Number and color classes
|
||||
"Number",
|
||||
"Color",
|
||||
@@ -172,7 +196,6 @@ __all__ = [
|
||||
"Hexagram",
|
||||
"Cipher",
|
||||
"CipherResult",
|
||||
|
||||
# Data loader and functions
|
||||
"CardDataLoader",
|
||||
"calculate_digital_root",
|
||||
|
||||
@@ -8,54 +8,54 @@ attribute classes for cards.
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
# Re-export shared attributes from utils
|
||||
from utils.attributes import (
|
||||
Element,
|
||||
ElementType,
|
||||
Number,
|
||||
Color,
|
||||
Colorscale,
|
||||
Planet,
|
||||
God,
|
||||
Cipher,
|
||||
CipherResult,
|
||||
Perfume,
|
||||
Note,
|
||||
Meaning,
|
||||
)
|
||||
|
||||
# Re-export attributes from other modules for convenience/backward compatibility
|
||||
from kaballah.attributes import (
|
||||
Sephera,
|
||||
PeriodicTable,
|
||||
TreeOfLife,
|
||||
Correspondences,
|
||||
Path,
|
||||
PeriodicTable,
|
||||
Sephera,
|
||||
TreeOfLife,
|
||||
)
|
||||
from letter.attributes import (
|
||||
Letter,
|
||||
EnglishAlphabet,
|
||||
GreekAlphabet,
|
||||
HebrewAlphabet,
|
||||
DoublLetterTrump,
|
||||
EnglishAlphabet,
|
||||
EnochianArchetype,
|
||||
EnochianGridPosition,
|
||||
EnochianLetter,
|
||||
EnochianSpirit,
|
||||
EnochianTablet,
|
||||
EnochianGridPosition,
|
||||
EnochianArchetype,
|
||||
GreekAlphabet,
|
||||
HebrewAlphabet,
|
||||
Letter,
|
||||
)
|
||||
from letter.iChing_attributes import (
|
||||
Trigram,
|
||||
Hexagram,
|
||||
Trigram,
|
||||
)
|
||||
from temporal.attributes import (
|
||||
AstrologicalInfluence,
|
||||
ClockHour,
|
||||
Degree,
|
||||
Hour,
|
||||
Month,
|
||||
Weekday,
|
||||
Hour,
|
||||
ClockHour,
|
||||
Zodiac,
|
||||
Degree,
|
||||
AstrologicalInfluence,
|
||||
)
|
||||
|
||||
# Re-export shared attributes from utils
|
||||
from utils.attributes import (
|
||||
Cipher,
|
||||
CipherResult,
|
||||
Color,
|
||||
Colorscale,
|
||||
Element,
|
||||
ElementType,
|
||||
God,
|
||||
Meaning,
|
||||
Note,
|
||||
Number,
|
||||
Perfume,
|
||||
Planet,
|
||||
)
|
||||
|
||||
# Alias Day to Weekday for backward compatibility (Day in this context was Day of Week)
|
||||
@@ -113,8 +113,9 @@ __all__ = [
|
||||
@dataclass
|
||||
class Suit:
|
||||
"""Represents a tarot suit."""
|
||||
|
||||
name: str
|
||||
element: 'ElementType'
|
||||
element: "ElementType"
|
||||
tarot_correspondence: str
|
||||
number: int
|
||||
|
||||
@@ -122,8 +123,8 @@ class Suit:
|
||||
@dataclass
|
||||
class CardImage:
|
||||
"""Represents an image associated with a card."""
|
||||
|
||||
filename: str
|
||||
artist: str
|
||||
deck_name: str
|
||||
url: Optional[str] = None
|
||||
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
from .card import CardAccessor
|
||||
from .details import CardDetailsRegistry
|
||||
from .image_loader import ImageDeckLoader, load_deck_images
|
||||
from .loader import (
|
||||
filter_cards_by_keywords,
|
||||
get_card_info,
|
||||
get_cards_by_suit,
|
||||
load_card_details,
|
||||
load_deck_details,
|
||||
get_cards_by_suit,
|
||||
filter_cards_by_keywords,
|
||||
print_card_details,
|
||||
get_card_info,
|
||||
)
|
||||
from .image_loader import ImageDeckLoader, load_deck_images
|
||||
|
||||
__all__ = [
|
||||
"CardAccessor",
|
||||
|
||||
@@ -13,9 +13,13 @@ Usage:
|
||||
cards = Deck.card.filter(arcana="Minor", suit="Wands", pip=5) # 5 of Wands
|
||||
"""
|
||||
|
||||
from typing import List, Optional
|
||||
from utils.filter import universal_filter, format_results
|
||||
from utils.object_formatting import is_nested_object, get_object_attributes, format_value
|
||||
from typing import TYPE_CHECKING, List, Optional
|
||||
|
||||
from utils.filter import format_results, universal_filter
|
||||
from utils.object_formatting import format_value, get_object_attributes, is_nested_object
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tarot.deck import Card, Deck
|
||||
|
||||
|
||||
class CardList(list):
|
||||
@@ -32,7 +36,7 @@ class CardList(list):
|
||||
return self.__str__()
|
||||
|
||||
|
||||
def _format_cards(cards: List['Card']) -> str:
|
||||
def _format_cards(cards: List["Card"]) -> str:
|
||||
"""
|
||||
Format a list of cards for user-friendly display.
|
||||
|
||||
@@ -42,12 +46,10 @@ def _format_cards(cards: List['Card']) -> str:
|
||||
Returns:
|
||||
Formatted string with each card separated by blank lines
|
||||
"""
|
||||
from utils.object_formatting import is_nested_object, get_object_attributes, format_value
|
||||
|
||||
lines = []
|
||||
for card in cards:
|
||||
card_num = getattr(card, 'number', '?')
|
||||
card_name = getattr(card, 'name', 'Unknown')
|
||||
card_num = getattr(card, "number", "?")
|
||||
card_name = getattr(card, "name", "Unknown")
|
||||
lines.append(f"--- {card_num}: {card_name} ---")
|
||||
|
||||
# Format all attributes with proper nesting
|
||||
@@ -78,17 +80,18 @@ class CardAccessor:
|
||||
Tarot.deck.card.display_filter(arcana="Major") # Display Major Arcana
|
||||
"""
|
||||
|
||||
_deck: Optional['Deck'] = None
|
||||
_deck: Optional["Deck"] = None
|
||||
_initialized: bool = False
|
||||
|
||||
def _ensure_initialized(self) -> None:
|
||||
"""Lazy-load the Deck on first access."""
|
||||
if not self._initialized:
|
||||
from tarot.deck import Deck as DeckClass
|
||||
|
||||
CardAccessor._deck = DeckClass()
|
||||
CardAccessor._initialized = True
|
||||
|
||||
def __call__(self, number: int) -> Optional['Card']:
|
||||
def __call__(self, number: int) -> Optional["Card"]:
|
||||
"""Get a card by number."""
|
||||
self._ensure_initialized()
|
||||
if self._deck is None:
|
||||
@@ -177,7 +180,9 @@ class CardAccessor:
|
||||
fool = next((c for c in major_arcana if c.number == 0), None)
|
||||
world = next((c for c in major_arcana if c.number == 21), None)
|
||||
if fool and world:
|
||||
lines.append(f" Special Pair: {fool.name} ({fool.number}) - {world.name} ({world.number})")
|
||||
lines.append(
|
||||
f" Special Pair: {fool.name} ({fool.number}) - {world.name} ({world.number})"
|
||||
)
|
||||
|
||||
double_letter_trumps = [c for c in major_arcana if 3 <= c.number <= 21]
|
||||
lines.append(f" Double Letter Trumps ({len(double_letter_trumps)} cards): Cards 3-21")
|
||||
@@ -189,34 +194,34 @@ class CardAccessor:
|
||||
lines.append("")
|
||||
|
||||
# Aces
|
||||
aces = [c for c in minor_arcana if hasattr(c, 'pip') and c.pip == 1]
|
||||
aces = [c for c in minor_arcana if hasattr(c, "pip") and c.pip == 1]
|
||||
if aces:
|
||||
lines.append(f" ACES ({len(aces)} cards - The Root Powers):")
|
||||
for ace in aces:
|
||||
suit_name = ace.suit.name if hasattr(ace.suit, 'name') else str(ace.suit)
|
||||
suit_name = ace.suit.name if hasattr(ace.suit, "name") else str(ace.suit)
|
||||
lines.append(f" Ace of {suit_name}")
|
||||
lines.append("")
|
||||
|
||||
# Pips (2-10)
|
||||
pips = [c for c in minor_arcana if hasattr(c, 'pip') and 2 <= c.pip <= 10]
|
||||
pips = [c for c in minor_arcana if hasattr(c, "pip") and 2 <= c.pip <= 10]
|
||||
if pips:
|
||||
lines.append(f" PIPS ({len(pips)} cards - 2-10 of each suit):")
|
||||
# Group by suit
|
||||
suits_dict = {}
|
||||
for pip in pips:
|
||||
suit_name = pip.suit.name if hasattr(pip.suit, 'name') else str(pip.suit)
|
||||
suit_name = pip.suit.name if hasattr(pip.suit, "name") else str(pip.suit)
|
||||
if suit_name not in suits_dict:
|
||||
suits_dict[suit_name] = []
|
||||
suits_dict[suit_name].append(pip)
|
||||
|
||||
for suit_name in ['Cups', 'Pentacles', 'Swords', 'Wands']:
|
||||
for suit_name in ["Cups", "Pentacles", "Swords", "Wands"]:
|
||||
if suit_name in suits_dict:
|
||||
pip_nums = sorted([p.pip for p in suits_dict[suit_name]])
|
||||
lines.append(f" {suit_name}: {', '.join(str(n) for n in pip_nums)}")
|
||||
lines.append("")
|
||||
|
||||
# Court Cards
|
||||
courts = [c for c in minor_arcana if hasattr(c, 'court_rank') and c.court_rank]
|
||||
courts = [c for c in minor_arcana if hasattr(c, "court_rank") and c.court_rank]
|
||||
if courts:
|
||||
lines.append(f" COURT CARDS ({len(courts)} cards - 4 ranks × 4 suits):")
|
||||
# Get unique ranks and their order
|
||||
@@ -227,15 +232,16 @@ class CardAccessor:
|
||||
# Group by suit
|
||||
suits_dict = {}
|
||||
for court in courts:
|
||||
suit_name = court.suit.name if hasattr(court.suit, 'name') else str(court.suit)
|
||||
suit_name = court.suit.name if hasattr(court.suit, "name") else str(court.suit)
|
||||
if suit_name not in suits_dict:
|
||||
suits_dict[suit_name] = []
|
||||
suits_dict[suit_name].append(court)
|
||||
|
||||
for suit_name in ['Cups', 'Pentacles', 'Swords', 'Wands']:
|
||||
for suit_name in ["Cups", "Pentacles", "Swords", "Wands"]:
|
||||
if suit_name in suits_dict:
|
||||
suit_courts = sorted(suits_dict[suit_name],
|
||||
key=lambda c: rank_order.get(c.court_rank, 99))
|
||||
suit_courts = sorted(
|
||||
suits_dict[suit_name], key=lambda c: rank_order.get(c.court_rank, 99)
|
||||
)
|
||||
court_names = [c.court_rank for c in suit_courts]
|
||||
lines.append(f" {suit_name}: {', '.join(court_names)}")
|
||||
lines.append("")
|
||||
@@ -244,45 +250,44 @@ class CardAccessor:
|
||||
lines.append("SUIT CORRESPONDENCES:")
|
||||
suits_info = {}
|
||||
for card in minor_arcana:
|
||||
if hasattr(card, 'suit') and card.suit:
|
||||
suit_name = card.suit.name if hasattr(card.suit, 'name') else str(card.suit)
|
||||
if hasattr(card, "suit") and card.suit:
|
||||
suit_name = card.suit.name if hasattr(card.suit, "name") else str(card.suit)
|
||||
if suit_name not in suits_info:
|
||||
# Extract element info
|
||||
element_name = "Unknown"
|
||||
if hasattr(card.suit, 'element') and card.suit.element:
|
||||
if hasattr(card.suit.element, 'name'):
|
||||
if hasattr(card.suit, "element") and card.suit.element:
|
||||
if hasattr(card.suit.element, "name"):
|
||||
element_name = card.suit.element.name
|
||||
else:
|
||||
element_name = str(card.suit.element)
|
||||
|
||||
# Extract zodiac signs
|
||||
zodiac_signs = []
|
||||
if hasattr(card.suit, 'element') and card.suit.element:
|
||||
if hasattr(card.suit.element, 'zodiac_signs'):
|
||||
if hasattr(card.suit, "element") and card.suit.element:
|
||||
if hasattr(card.suit.element, "zodiac_signs"):
|
||||
zodiac_signs = card.suit.element.zodiac_signs
|
||||
|
||||
# Extract keywords
|
||||
keywords = []
|
||||
if hasattr(card.suit, 'element') and card.suit.element:
|
||||
if hasattr(card.suit.element, 'keywords'):
|
||||
if hasattr(card.suit, "element") and card.suit.element:
|
||||
if hasattr(card.suit.element, "keywords"):
|
||||
keywords = card.suit.element.keywords
|
||||
|
||||
suits_info[suit_name] = {
|
||||
'element': element_name,
|
||||
'zodiac': zodiac_signs,
|
||||
'keywords': keywords
|
||||
"element": element_name,
|
||||
"zodiac": zodiac_signs,
|
||||
"keywords": keywords,
|
||||
}
|
||||
|
||||
for suit_name in ['Cups', 'Pentacles', 'Swords', 'Wands']:
|
||||
for suit_name in ["Cups", "Pentacles", "Swords", "Wands"]:
|
||||
if suit_name in suits_info:
|
||||
info = suits_info[suit_name]
|
||||
lines.append(f" {suit_name} ({info['element']}):")
|
||||
if info['zodiac']:
|
||||
if info["zodiac"]:
|
||||
lines.append(f" Zodiac: {', '.join(info['zodiac'])}")
|
||||
if info['keywords']:
|
||||
if info["keywords"]:
|
||||
lines.append(f" Keywords: {', '.join(info['keywords'])}")
|
||||
|
||||
|
||||
lines.append("")
|
||||
lines.append(f"Total: {len(self._deck.cards)} cards")
|
||||
|
||||
@@ -315,7 +320,7 @@ class CardAccessor:
|
||||
print(Tarot.deck.card.spread('three card'))
|
||||
print(Tarot.deck.card.spread('tree of life'))
|
||||
"""
|
||||
from tarot.card.spread import Spread, draw_spread, SpreadReading
|
||||
from tarot.card.spread import Spread, SpreadReading, draw_spread
|
||||
|
||||
# Initialize deck if needed
|
||||
self._ensure_initialized()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -64,19 +64,9 @@ class CardDetailsRegistry:
|
||||
if key == 0:
|
||||
return "o"
|
||||
|
||||
val = [
|
||||
1000, 900, 500, 400,
|
||||
100, 90, 50, 40,
|
||||
10, 9, 5, 4,
|
||||
1
|
||||
]
|
||||
syms = [
|
||||
"M", "CM", "D", "CD",
|
||||
"C", "XC", "L", "XL",
|
||||
"X", "IX", "V", "IV",
|
||||
"I"
|
||||
]
|
||||
roman_num = ''
|
||||
val = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
|
||||
syms = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
|
||||
roman_num = ""
|
||||
i = 0
|
||||
while key > 0:
|
||||
for _ in range(key // val[i]):
|
||||
@@ -120,7 +110,7 @@ class CardDetailsRegistry:
|
||||
"o": {
|
||||
"explanation": {
|
||||
"summary": "The Fool represents new beginnings, innocence, and spontaneity. This card signifies a fresh start or embarking on a new journey with optimism and faith.",
|
||||
"waite": "The Fool, Mate, or Unwise Man. Court de Gebelin places it at the head of the whole series as the zero or negative which is presupposed by numeration, and as this is a simpler so also it is a better arrangement. It has been abandoned because in later times the cards have been attributed to the letters of the Hebrew alphabet, and there has been apparently some difficulty about allocating the zero symbol satisfactorily in a sequence of letters all of which signify numbers. In the present reference of the card to the letter Shin, which corresponds to 200, the difficulty or the unreason remains. The truth is that the real arrangement of the cards has never transpired. The Fool carries a wallet; he is looking over his shoulder and does not know that he is on the brink of a precipice; but a dog or other animal--some call it a tiger--is attacking him from behind, and he is hurried to his destruction unawares."
|
||||
"waite": "The Fool, Mate, or Unwise Man. Court de Gebelin places it at the head of the whole series as the zero or negative which is presupposed by numeration, and as this is a simpler so also it is a better arrangement. It has been abandoned because in later times the cards have been attributed to the letters of the Hebrew alphabet, and there has been apparently some difficulty about allocating the zero symbol satisfactorily in a sequence of letters all of which signify numbers. In the present reference of the card to the letter Shin, which corresponds to 200, the difficulty or the unreason remains. The truth is that the real arrangement of the cards has never transpired. The Fool carries a wallet; he is looking over his shoulder and does not know that he is on the brink of a precipice; but a dog or other animal--some call it a tiger--is attacking him from behind, and he is hurried to his destruction unawares.",
|
||||
},
|
||||
"interpretation": "Beginning of the Great Work, innocence; a fool for love; divine madness. Reason is transcended. Take the leap. Gain or loss through foolish actions.",
|
||||
"keywords": ["new beginnings", "innocence", "faith", "spontaneity", "potential"],
|
||||
@@ -130,137 +120,261 @@ class CardDetailsRegistry:
|
||||
"I": {
|
||||
"explanation": {
|
||||
"summary": "The Magician embodies manifestation, resourcefulness, and personal power. This card shows mastery of skills and the ability to turn ideas into reality.",
|
||||
"waite": "The Magus, Magician, or juggler, the caster of the dice and mountebank, in the world of vulgar trickery. This is the colportage interpretation, and it has the same correspondence with the real symbolical meaning that the use of the Tarot in fortune-telling has with its mystic construction according to the secret science of symbolism. I should add that many independent students of the subject, following their own lights, have produced individual sequences of meaning in respect of the Trumps Major, and their lights are sometimes suggestive, but they are not the true lights."
|
||||
"waite": "The Magus, Magician, or juggler, the caster of the dice and mountebank, in the world of vulgar trickery. This is the colportage interpretation, and it has the same correspondence with the real symbolical meaning that the use of the Tarot in fortune-telling has with its mystic construction according to the secret science of symbolism. I should add that many independent students of the subject, following their own lights, have produced individual sequences of meaning in respect of the Trumps Major, and their lights are sometimes suggestive, but they are not the true lights.",
|
||||
},
|
||||
"interpretation": "Communication; Conscious Will; the process of continuous creation; ambiguity; deceptionl Things may not be as they appear. Concentration, meditation; mind used to direct the Will. Manipulation; crafty maneuverings.",
|
||||
"keywords": ["manifestation", "resourcefulness", "power", "inspired action", "concentration"],
|
||||
"reversed_keywords": ["manipulation", "poor planning", "untapped talents", "lack of direction"],
|
||||
"keywords": [
|
||||
"manifestation",
|
||||
"resourcefulness",
|
||||
"power",
|
||||
"inspired action",
|
||||
"concentration",
|
||||
],
|
||||
"reversed_keywords": [
|
||||
"manipulation",
|
||||
"poor planning",
|
||||
"untapped talents",
|
||||
"lack of direction",
|
||||
],
|
||||
"guidance": "Focus your energy and intention on what you want to manifest. You have the tools and talents you need.",
|
||||
},
|
||||
"II": {
|
||||
"explanation": {
|
||||
"summary": "The High Priestess represents intuition, sacred knowledge, and the subconscious mind. She embodies mystery and inner wisdom.",
|
||||
"waite": "The High Priestess, the Pope Joan, or Female Pontiff; early expositors have sought to term this card the Mother, or Pope's Wife, which is opposed to the symbolism. It is sometimes held to represent the Divine Law and the Gnosis, in which case the Priestess corresponds to the idea of the Shekinah. She is the Secret Tradition and the higher sense of the instituted Mysteries."
|
||||
"waite": "The High Priestess, the Pope Joan, or Female Pontiff; early expositors have sought to term this card the Mother, or Pope's Wife, which is opposed to the symbolism. It is sometimes held to represent the Divine Law and the Gnosis, in which case the Priestess corresponds to the idea of the Shekinah. She is the Secret Tradition and the higher sense of the instituted Mysteries.",
|
||||
},
|
||||
"interpretation": "Symbol of highest initiation; link between the archetypal and Formative Worlds. An initiatrixl Wooing by enchantment. possibility. The Idea behind the Form. Fluctuationl Time may not be right for a decision concerning mundane matters.",
|
||||
"keywords": ["intuition", "sacred knowledge", "divine feminine", "the subconscious", "mystery"],
|
||||
"reversed_keywords": ["hidden information", "silence", "disconnection from intuition", "superficiality"],
|
||||
"keywords": [
|
||||
"intuition",
|
||||
"sacred knowledge",
|
||||
"divine feminine",
|
||||
"the subconscious",
|
||||
"mystery",
|
||||
],
|
||||
"reversed_keywords": [
|
||||
"hidden information",
|
||||
"silence",
|
||||
"disconnection from intuition",
|
||||
"superficiality",
|
||||
],
|
||||
"guidance": "Listen to your inner voice. The answers you seek lie within. Trust the wisdom of your intuition.",
|
||||
},
|
||||
"III": {
|
||||
"explanation": {
|
||||
"summary": "The Empress symbolizes abundance, fertility, and nurturing energy. She represents creativity, sensuality, and the power of manifestation through nurturing.",
|
||||
"waite": "The Empress, who is sometimes represented with full face, while her correspondence, the Emperor, is in profile. As there has been some tendency to ascribe a symbolical significance to this distinction, it seems desirable to say that it carries no inner meaning. The Empress has been connected with the ideas of universal fecundity and in a general sense with activity."
|
||||
"waite": "The Empress, who is sometimes represented with full face, while her correspondence, the Emperor, is in profile. As there has been some tendency to ascribe a symbolical significance to this distinction, it seems desirable to say that it carries no inner meaning. The Empress has been connected with the ideas of universal fecundity and in a general sense with activity.",
|
||||
},
|
||||
"interpretation": "The Holy Grail. love unites the Will. Love; beauty; friendship; success; passive balance. The feminine point of view. The door is open. Disregard the details and concentrate on the big picture.",
|
||||
"keywords": ["abundance", "fertility", "femininity", "beauty", "nature", "creativity"],
|
||||
"reversed_keywords": ["dependency", "creative block", "neediness", "underdevelopment"],
|
||||
"keywords": [
|
||||
"abundance",
|
||||
"fertility",
|
||||
"femininity",
|
||||
"beauty",
|
||||
"nature",
|
||||
"creativity",
|
||||
],
|
||||
"reversed_keywords": [
|
||||
"dependency",
|
||||
"creative block",
|
||||
"neediness",
|
||||
"underdevelopment",
|
||||
],
|
||||
"guidance": "Nurture yourself and others. Allow yourself to enjoy the fruits of your labor and appreciate beauty.",
|
||||
},
|
||||
"IV": {
|
||||
"explanation": {
|
||||
"summary": "The Emperor represents authority, leadership, and established power. He embodies structure, discipline, and protection through strength and control.",
|
||||
"waite": "The Emperor, by imputation the spouse of the former. He is occasionally represented as wearing, in addition to his personal insignia, the stars or ribbons of some order of chivalry. I mention this to shew that the cards are a medley of old and new emblems."
|
||||
"waite": "The Emperor, by imputation the spouse of the former. He is occasionally represented as wearing, in addition to his personal insignia, the stars or ribbons of some order of chivalry. I mention this to shew that the cards are a medley of old and new emblems.",
|
||||
},
|
||||
"interpretation": "Creative wisdom radiating upon the organized man and woman. Domination after conquest; quarrelsomeness; paternal love; ambition. Thought ruled by creative, masculine, fiery energy. Stubbornness; war; authority; energy in its most temporal form. Swift immpermaent action over confidence.",
|
||||
"keywords": ["authority", "leadership", "power", "structure", "protection", "discipline"],
|
||||
"reversed_keywords": ["weakness", "ineffectual leadership", "lack of discipline", "tyranny"],
|
||||
"keywords": [
|
||||
"authority",
|
||||
"leadership",
|
||||
"power",
|
||||
"structure",
|
||||
"protection",
|
||||
"discipline",
|
||||
],
|
||||
"reversed_keywords": [
|
||||
"weakness",
|
||||
"ineffectual leadership",
|
||||
"lack of discipline",
|
||||
"tyranny",
|
||||
],
|
||||
"guidance": "Step into your power with confidence. Establish clear boundaries and structure. Lead by example.",
|
||||
},
|
||||
"V": {
|
||||
"explanation": {
|
||||
"summary": "The Hierophant represents tradition, conventional wisdom, and spiritual authority. This card embodies education, ceremony, and moral values.",
|
||||
"waite": "The High Priest or Hierophant, called also Spiritual Father, and more commonly and obviously the Pope. It seems even to have been named the Abbot, and then its correspondence, the High Priestess, was the Abbess or Mother of the Convent. Both are arbitrary names. The insignia of the figures are papal, and in such case the High Priestess is and can be only the Church, to whom Pope and priests are married by the spiritual rite of ordination."
|
||||
"waite": "The High Priest or Hierophant, called also Spiritual Father, and more commonly and obviously the Pope. It seems even to have been named the Abbot, and then its correspondence, the High Priestess, was the Abbess or Mother of the Convent. Both are arbitrary names. The insignia of the figures are papal, and in such case the High Priestess is and can be only the Church, to whom Pope and priests are married by the spiritual rite of ordination.",
|
||||
},
|
||||
"interpretation": "The Holy Guardian Angel. The uniting of t hat which is above with that which is below. Love is indicated, but the nature of that love is not yet to be revealed. Inspiration; teaching; organization; discipline; strength; endurance; toil; help from superiors.",
|
||||
"keywords": ["tradition", "spirituality", "wisdom", "ritual", "morality", "ethics"],
|
||||
"reversed_keywords": ["rebellion", "unconventionality", "questioning authority", "dogmatism"],
|
||||
"reversed_keywords": [
|
||||
"rebellion",
|
||||
"unconventionality",
|
||||
"questioning authority",
|
||||
"dogmatism",
|
||||
],
|
||||
"guidance": "Seek guidance from established wisdom. Respect traditions while finding your own spiritual path.",
|
||||
},
|
||||
"VI": {
|
||||
"explanation": {
|
||||
"summary": "The Lovers represents relationships, values alignment, and the union of opposites. It signifies choice, intimacy, and deep connection.",
|
||||
"waite": "The Lovers or Marriage. This symbol has undergone many variations, as might be expected from its subject. In the eighteenth century form, by which it first became known to the world of archæological research, it is really a card of married life, shewing father and mother, with their child placed between them; and the pagan Cupid above, in the act of flying his shaft, is, of course, a misapplied emblem."
|
||||
"waite": "The Lovers or Marriage. This symbol has undergone many variations, as might be expected from its subject. In the eighteenth century form, by which it first became known to the world of archæological research, it is really a card of married life, shewing father and mother, with their child placed between them; and the pagan Cupid above, in the act of flying his shaft, is, of course, a misapplied emblem.",
|
||||
},
|
||||
"interpretation": "Intuition. Be open to your own inner voice. A well-intended, arranged marriage. An artificial union. The need to make a choice with awareness of consequences union; analysis followed by synthesis; indecision; instability; superficiality.",
|
||||
"keywords": ["relationships", "love", "union", "values", "choice", "alignment"],
|
||||
"reversed_keywords": ["disharmony", "misalignment", "conflict", "communication breakdown"],
|
||||
"reversed_keywords": [
|
||||
"disharmony",
|
||||
"misalignment",
|
||||
"conflict",
|
||||
"communication breakdown",
|
||||
],
|
||||
"guidance": "Choose with your heart aligned with your values. Deep connection requires vulnerability and honesty.",
|
||||
},
|
||||
"VII": {
|
||||
"explanation": {
|
||||
"summary": "The Chariot embodies willpower, determination, and control through focused intention. It represents triumph through discipline and forward momentum.",
|
||||
"waite": "The Chariot. This is represented in some extant codices as being drawn by two sphinxes, and the device is in consonance with the symbolism, but it must not be supposed that such was its original form; the variation was invented to support a particular historical hypothesis. In the eighteenth century white horses were yoked to the car. As regards its usual name, the lesser stands for the greater; it is really the King in his triumph."
|
||||
"waite": "The Chariot. This is represented in some extant codices as being drawn by two sphinxes, and the device is in consonance with the symbolism, but it must not be supposed that such was its original form; the variation was invented to support a particular historical hypothesis. In the eighteenth century white horses were yoked to the car. As regards its usual name, the lesser stands for the greater; it is really the King in his triumph.",
|
||||
},
|
||||
"interpretation": "Light in the darkness. The burden you carry may be the Holy Grail. Faithfulness; hope; obedience; a protective relationship; firm, even violent adherance to dogma or tradition. Glory; riches; englightened civilization; victory; triumph; chain of command.",
|
||||
"keywords": ["determination", "willpower", "control", "momentum", "victory", "focus"],
|
||||
"keywords": [
|
||||
"determination",
|
||||
"willpower",
|
||||
"control",
|
||||
"momentum",
|
||||
"victory",
|
||||
"focus",
|
||||
],
|
||||
"reversed_keywords": ["lack of control", "haste", "resistance", "moving backward"],
|
||||
"guidance": "Take the reins of your life. Move forward with determination and clear direction. You have the power.",
|
||||
},
|
||||
"VIII": {
|
||||
"explanation": {
|
||||
"summary": "Strength represents inner power, courage, and compassion. It shows mastery through gentleness and the ability to face challenges with calm confidence.",
|
||||
"waite": "Fortitude. This is one of the cardinal virtues, of which I shall speak later. The female figure is usually represented as closing the mouth of a lion. In the earlier form which is printed by Court de Gebelin, she is obviously opening it. The first alternative is better symbolically, but either is an instance of strength in its conventional understanding, and conveys the idea of mastery."
|
||||
"waite": "Fortitude. This is one of the cardinal virtues, of which I shall speak later. The female figure is usually represented as closing the mouth of a lion. In the earlier form which is printed by Court de Gebelin, she is obviously opening it. The first alternative is better symbolically, but either is an instance of strength in its conventional understanding, and conveys the idea of mastery.",
|
||||
},
|
||||
"interpretation": "Equilibrium; karmic law; the dance of life; all possibilities. The woman satisfied. Balance; weigh each thought against its opposite. Lawsuits; treaties. Pause and look before you leap.",
|
||||
"keywords": ["strength", "courage", "patience", "compassion", "control", "confidence"],
|
||||
"reversed_keywords": ["weakness", "self-doubt", "lack of composure", "poor control"],
|
||||
"keywords": [
|
||||
"strength",
|
||||
"courage",
|
||||
"patience",
|
||||
"compassion",
|
||||
"control",
|
||||
"confidence",
|
||||
],
|
||||
"reversed_keywords": [
|
||||
"weakness",
|
||||
"self-doubt",
|
||||
"lack of composure",
|
||||
"poor control",
|
||||
],
|
||||
"guidance": "True strength comes from within. Face challenges with courage and compassion for yourself and others.",
|
||||
},
|
||||
"IX": {
|
||||
"explanation": {
|
||||
"summary": "The Hermit represents introspection, spiritual seeking, and inner guidance. This card embodies solitude, wisdom gained through reflection, and self-discovery.",
|
||||
"waite": "The Hermit, as he is termed in common parlance, stands next on the list; he is also the Capuchin, and in more philosophical language the Sage. He is said to be in search of that Truth which is located far off in the sequence, and of justice which has preceded him on the way. But this is a card of attainment, as we shall see later, rather than a card of quest."
|
||||
"waite": "The Hermit, as he is termed in common parlance, stands next on the list; he is also the Capuchin, and in more philosophical language the Sage. He is said to be in search of that Truth which is located far off in the sequence, and of justice which has preceded him on the way. But this is a card of attainment, as we shall see later, rather than a card of quest.",
|
||||
},
|
||||
"interpretation": "Divine seed of all things. By silence comes inspiration and wisdom. Wandering alone; temporary solitude; creative contemplation; a virgin. Retirement from involvement in current events.",
|
||||
"keywords": ["introspection", "spiritual seeking", "inner light", "wisdom", "solitude", "truth"],
|
||||
"reversed_keywords": ["loneliness", "isolation", "lost", "paranoia", "disconnection"],
|
||||
"keywords": [
|
||||
"introspection",
|
||||
"spiritual seeking",
|
||||
"inner light",
|
||||
"wisdom",
|
||||
"solitude",
|
||||
"truth",
|
||||
],
|
||||
"reversed_keywords": [
|
||||
"loneliness",
|
||||
"isolation",
|
||||
"lost",
|
||||
"paranoia",
|
||||
"disconnection",
|
||||
],
|
||||
"guidance": "Take time for introspection and self-discovery. Your inner light guides your path. Seek solitude for wisdom.",
|
||||
},
|
||||
"X": {
|
||||
"explanation": {
|
||||
"summary": "The Wheel of Fortune represents cycles, destiny, and the turning points of life. It embodies luck, karma, and the natural ebb and flow of existence.",
|
||||
"waite": "The Wheel of Fortune. There is a current Manual of Cartomancy which has obtained a considerable vogue in England, and amidst a great scattermeal of curious things to no purpose has intersected a few serious subjects. In its last and largest edition it treats in one section of the Tarot; which--if I interpret the author rightly--it regards from beginning to end as the Wheel of Fortune."
|
||||
"waite": "The Wheel of Fortune. There is a current Manual of Cartomancy which has obtained a considerable vogue in England, and amidst a great scattermeal of curious things to no purpose has intersected a few serious subjects. In its last and largest edition it treats in one section of the Tarot; which--if I interpret the author rightly--it regards from beginning to end as the Wheel of Fortune.",
|
||||
},
|
||||
"interpretation": "Continual change. In the midst of revolving phenomena, reaach joyously the motionless center. Carefree love; wanton pleasure; amusement; fun; change of fortune, usually good.",
|
||||
"keywords": ["fate", "destiny", "cycles", "fortune", "karma", "turning point"],
|
||||
"reversed_keywords": ["bad luck", "resistance to change", "broken cycles", "misfortune"],
|
||||
"reversed_keywords": [
|
||||
"bad luck",
|
||||
"resistance to change",
|
||||
"broken cycles",
|
||||
"misfortune",
|
||||
],
|
||||
"guidance": "Trust in the cycles of life. What goes up must come down. Embrace change as part of your journey.",
|
||||
},
|
||||
"XI": {
|
||||
"explanation": {
|
||||
"summary": "Justice represents fairness, truth, and balance. It embodies accountability, clear judgment, and the consequences of actions both past and present.",
|
||||
"waite": "Justice. That the Tarot, though it is of all reasonable antiquity, is not of time immemorial, is shewn by this card, which could have been presented in a much more archaic manner. Those, however, who have gifts of discernment in matters of this kind will not need to be told that age is in no sense of the essence of the consideration."
|
||||
"waite": "Justice. That the Tarot, though it is of all reasonable antiquity, is not of time immemorial, is shewn by this card, which could have been presented in a much more archaic manner. Those, however, who have gifts of discernment in matters of this kind will not need to be told that age is in no sense of the essence of the consideration.",
|
||||
},
|
||||
"interpretation": "Understanding; the Will of New Aeon; passion; sense smitten with ecstasy. let love devour all. Energy independent of reason. Strength; courage; utilization of magical power.",
|
||||
"keywords": ["justice", "fairness", "truth", "cause and effect", "balance", "accountability"],
|
||||
"keywords": [
|
||||
"justice",
|
||||
"fairness",
|
||||
"truth",
|
||||
"cause and effect",
|
||||
"balance",
|
||||
"accountability",
|
||||
],
|
||||
"reversed_keywords": ["injustice", "bias", "lack of accountability", "dishonesty"],
|
||||
"guidance": "Seek the truth and act with fairness. Take responsibility for your actions. Balance is key.",
|
||||
},
|
||||
"XII": {
|
||||
"explanation": {
|
||||
"summary": "The Hanged Man represents suspension, letting go, and seeing things from a new perspective. It embodies surrender, pause, and gaining wisdom through sacrifice.",
|
||||
"waite": "The Hanged Man. This is the symbol which is supposed to represent Prudence, and Éliphas Lévi says, in his most shallow and plausible manner, that it is the adept bound by his engagements. The figure of a man is suspended head-downwards from a gibbet, to which he is attached by a rope about one of his ankles."
|
||||
"waite": "The Hanged Man. This is the symbol which is supposed to represent Prudence, and Éliphas Lévi says, in his most shallow and plausible manner, that it is the adept bound by his engagements. The figure of a man is suspended head-downwards from a gibbet, to which he is attached by a rope about one of his ankles.",
|
||||
},
|
||||
"interpretation": "Redemption, sacrifice, annihilation in the beloved; martyrdom; loss; torment; suspension; death; suffering.",
|
||||
"keywords": ["suspension", "restriction", "letting go", "new perspective", "surrender", "pause"],
|
||||
"reversed_keywords": ["resistance", "stalling", "unwillingness to change", "impatience"],
|
||||
"keywords": [
|
||||
"suspension",
|
||||
"restriction",
|
||||
"letting go",
|
||||
"new perspective",
|
||||
"surrender",
|
||||
"pause",
|
||||
],
|
||||
"reversed_keywords": [
|
||||
"resistance",
|
||||
"stalling",
|
||||
"unwillingness to change",
|
||||
"impatience",
|
||||
],
|
||||
"guidance": "Pause and reflect. What are you holding onto? Surrender control and trust the process.",
|
||||
},
|
||||
"XIII": {
|
||||
"explanation": {
|
||||
"summary": "Death represents transformation, endings, and new beginnings. This card embodies major life transitions, the release of the old, and inevitable change.",
|
||||
"waite": "Death. The method of presentation is almost invariable, and embodies a bourgeois form of symbolism. The scene is the field of life, and amidst ordinary rank vegetation there are living arms and heads protruding from the ground. One of the heads is crowned, and a skeleton with a great scythe is in the act of mowing it."
|
||||
"waite": "Death. The method of presentation is almost invariable, and embodies a bourgeois form of symbolism. The scene is the field of life, and amidst ordinary rank vegetation there are living arms and heads protruding from the ground. One of the heads is crowned, and a skeleton with a great scythe is in the act of mowing it.",
|
||||
},
|
||||
"interpretation": "End of cycle; transformation; raw sexuality. Sex is death. Stress becomes intolerable. Any change is welcome. Time; age; unexpected change; death.",
|
||||
"keywords": ["transformation", "transition", "endings", "beginnings", "change", "acceptance"],
|
||||
"reversed_keywords": ["resistance to change", "stagnation", "missed opportunity", "delay"],
|
||||
"keywords": [
|
||||
"transformation",
|
||||
"transition",
|
||||
"endings",
|
||||
"beginnings",
|
||||
"change",
|
||||
"acceptance",
|
||||
],
|
||||
"reversed_keywords": [
|
||||
"resistance to change",
|
||||
"stagnation",
|
||||
"missed opportunity",
|
||||
"delay",
|
||||
],
|
||||
"guidance": "Release what no longer serves you. Transformation is inevitable. Trust in the cycle of death and rebirth.",
|
||||
},
|
||||
"XIV": {
|
||||
"explanation": {
|
||||
"summary": "Temperance represents balance, moderation, and harmony. It embodies blending of opposites, inner peace through balance, and finding your rhythm.",
|
||||
"waite": "Temperance. The winged figure of a female--who, in opposition to all doctrine concerning the hierarchy of angels, is usually allocated to this order of ministering spirits--is pouring liquid from one pitcher to another. In his last work on the Tarot, Dr. Papus abandons the traditional form and depicts a woman wearing an Egyptian head-dress."
|
||||
"waite": "Temperance. The winged figure of a female--who, in opposition to all doctrine concerning the hierarchy of angels, is usually allocated to this order of ministering spirits--is pouring liquid from one pitcher to another. In his last work on the Tarot, Dr. Papus abandons the traditional form and depicts a woman wearing an Egyptian head-dress.",
|
||||
},
|
||||
"interpretation": "Transmutation through union of opposites. A perfect marriage exalts and transforms each partner. The scientific method. Success follows complex maneuvers.",
|
||||
"keywords": ["balance", "moderation", "harmony", "patience", "timing", "peace"],
|
||||
@@ -270,47 +384,84 @@ class CardDetailsRegistry:
|
||||
"XV": {
|
||||
"explanation": {
|
||||
"summary": "The Devil represents bondage, materialism, and shadow aspects of self. It embodies addictions, illusions, and the consequences of giving away personal power.",
|
||||
"waite": "The Devil. In the eighteenth century this card seems to have been rather a symbol of merely animal impudicity. Except for a fantastic head-dress, the chief figure is entirely naked; it has bat-like wings, and the hands and feet are represented by the claws of a bird."
|
||||
"waite": "The Devil. In the eighteenth century this card seems to have been rather a symbol of merely animal impudicity. Except for a fantastic head-dress, the chief figure is entirely naked; it has bat-like wings, and the hands and feet are represented by the claws of a bird.",
|
||||
},
|
||||
"interpretation": "Thou hast no right but to do thy will. Obession; temptation; ecstasy found in every phenomenon; creative action, yet sublimely careless of result; unscrupulous ambition; strength.",
|
||||
"keywords": ["bondage", "materialism", "playfulness", "shadow self", "sexuality", "excess"],
|
||||
"keywords": [
|
||||
"bondage",
|
||||
"materialism",
|
||||
"playfulness",
|
||||
"shadow self",
|
||||
"sexuality",
|
||||
"excess",
|
||||
],
|
||||
"reversed_keywords": ["freedom", "detachment", "reclaiming power", "breaking free"],
|
||||
"guidance": "Examine what binds you. Acknowledge your shadow. You hold the key to your own freedom.",
|
||||
},
|
||||
"XVI": {
|
||||
"explanation": {
|
||||
"summary": "The Tower represents sudden disruption, revelation, and breakthrough through crisis. It embodies sudden change, truth revealed, and necessary destruction.",
|
||||
"waite": "The Tower struck by Lightning. Its alternative titles are: Castle of Plutus, God's House and the Tower of Babel. In the last case, the figures falling therefrom are held to be Nimrod and his minister. It is assuredly a card of confusion, and the design corresponds, broadly speaking, to any of the designations except Maison Dieu."
|
||||
"waite": "The Tower struck by Lightning. Its alternative titles are: Castle of Plutus, God's House and the Tower of Babel. In the last case, the figures falling therefrom are held to be Nimrod and his minister. It is assuredly a card of confusion, and the design corresponds, broadly speaking, to any of the designations except Maison Dieu.",
|
||||
},
|
||||
"interpretation": "Escape from the prison of organized life; renunciation of love; quarreling. Plans are destroyed. War; danger; sudden death.",
|
||||
"keywords": ["sudden change", "upheaval", "revelation", "breakdown", "breakthrough", "chaos"],
|
||||
"reversed_keywords": ["resistance to change", "averted crisis", "delay", "stagnation"],
|
||||
"keywords": [
|
||||
"sudden change",
|
||||
"upheaval",
|
||||
"revelation",
|
||||
"breakdown",
|
||||
"breakthrough",
|
||||
"chaos",
|
||||
],
|
||||
"reversed_keywords": [
|
||||
"resistance to change",
|
||||
"averted crisis",
|
||||
"delay",
|
||||
"stagnation",
|
||||
],
|
||||
"guidance": "Crisis brings clarity. Though change is sudden and jarring, it clears away the false and brings truth.",
|
||||
},
|
||||
"XVII": {
|
||||
"explanation": {
|
||||
"summary": "The Star represents hope, guidance, and inspiration. It embodies clarity of purpose, spiritual insight, and the light that guides your path forward.",
|
||||
"waite": "The Star, Dog-Star, or Sirius, also called fantastically the Star of the Magi. Grouped about it are seven minor luminaries, and beneath it is a naked female figure, with her left knee upon the earth and her right foot upon the water. She is in the act of pouring fluids from two vessels."
|
||||
"waite": "The Star, Dog-Star, or Sirius, also called fantastically the Star of the Magi. Grouped about it are seven minor luminaries, and beneath it is a naked female figure, with her left knee upon the earth and her right foot upon the water. She is in the act of pouring fluids from two vessels.",
|
||||
},
|
||||
"interpretation": "Clairvoyance; visions; drams; hope; love; yearning; realization of inexhaustible possibilities; dreaminess; unexpected help; renewal.",
|
||||
"keywords": ["hope", "faith", "inspiration", "vision", "guidance", "spirituality"],
|
||||
"reversed_keywords": ["hopelessness", "despair", "lack of direction", "lost", "obscured"],
|
||||
"reversed_keywords": [
|
||||
"hopelessness",
|
||||
"despair",
|
||||
"lack of direction",
|
||||
"lost",
|
||||
"obscured",
|
||||
],
|
||||
"guidance": "Let your inner light shine. Trust in your vision. Hope and guidance light your path forward.",
|
||||
},
|
||||
"XVIII": {
|
||||
"explanation": {
|
||||
"summary": "The Moon represents illusion, intuition, and the subconscious mind. It embodies mystery, dreams, and navigating by inner knowing rather than sight.",
|
||||
"waite": "The Moon. Some eighteenth-century cards shew the luminary on its waning side; in the debased edition of Etteilla, it is the moon at night in her plenitude, set in a heaven of stars; of recent years the moon is shewn on the side of her increase. In nearly all presentations she is shining brightly and shedding the moisture of fertilizing dew in great drops."
|
||||
"waite": "The Moon. Some eighteenth-century cards shew the luminary on its waning side; in the debased edition of Etteilla, it is the moon at night in her plenitude, set in a heaven of stars; of recent years the moon is shewn on the side of her increase. In nearly all presentations she is shining brightly and shedding the moisture of fertilizing dew in great drops.",
|
||||
},
|
||||
"interpretation": "The Dark night of the soul; deception; falsehood; illusion; madness; the threshold of significant change.",
|
||||
"keywords": ["illusion", "intuition", "uncertainty", "subconscious", "dreams", "mystery"],
|
||||
"reversed_keywords": ["clarity", "truth revealed", "release from illusion", "awakening"],
|
||||
"keywords": [
|
||||
"illusion",
|
||||
"intuition",
|
||||
"uncertainty",
|
||||
"subconscious",
|
||||
"dreams",
|
||||
"mystery",
|
||||
],
|
||||
"reversed_keywords": [
|
||||
"clarity",
|
||||
"truth revealed",
|
||||
"release from illusion",
|
||||
"awakening",
|
||||
],
|
||||
"guidance": "Trust your intuition to navigate mystery. What appears illusory contains deeper truths worth exploring.",
|
||||
},
|
||||
"XIX": {
|
||||
"explanation": {
|
||||
"summary": "The Sun represents joy, clarity, and vitality. It embodies success, positive energy, and the radiance of authentic self-expression.",
|
||||
"waite": "The Sun. The luminary is distinguished in older cards by chief rays that are waved and salient alternately and by secondary salient rays. It appears to shed its influence on earth not only by light and heat, but--like the moon--by drops of dew."
|
||||
"waite": "The Sun. The luminary is distinguished in older cards by chief rays that are waved and salient alternately and by secondary salient rays. It appears to shed its influence on earth not only by light and heat, but--like the moon--by drops of dew.",
|
||||
},
|
||||
"interpretation": "Lord of the New Aeon. Spiritual emancipation. Pleasure; shamelessness; vanity; frankness. Freedom brings sanity. Glory; riches; enlightened civilization.",
|
||||
"keywords": ["success", "joy", "clarity", "vitality", "warmth", "authenticity"],
|
||||
@@ -320,29 +471,47 @@ class CardDetailsRegistry:
|
||||
"XX": {
|
||||
"explanation": {
|
||||
"summary": "Judgement represents awakening, calling, and significant decisions. It embodies reckoning, rebirth, and responding to a higher calling.",
|
||||
"waite": "The Last judgment. I have spoken of this symbol already, the form of which is essentially invariable, even in the Etteilla set. An angel sounds his trumpet per sepulchra regionum, and the dead arise. It matters little that Etteilla omits the angel, or that Dr. Papus substitutes a ridiculous figure."
|
||||
"waite": "The Last judgment. I have spoken of this symbol already, the form of which is essentially invariable, even in the Etteilla set. An angel sounds his trumpet per sepulchra regionum, and the dead arise. It matters little that Etteilla omits the angel, or that Dr. Papus substitutes a ridiculous figure.",
|
||||
},
|
||||
"interpretation": "Let every act be an act of Worship; let every act be an act of Love. Final decision; judgement. Learn from the past. Prepare for the future.",
|
||||
"keywords": ["awakening", "calling", "judgment", "rebirth", "evaluation", "absolution"],
|
||||
"reversed_keywords": ["doubt", "self-doubt", "harsh judgment", "reluctance to change"],
|
||||
"keywords": [
|
||||
"awakening",
|
||||
"calling",
|
||||
"judgment",
|
||||
"rebirth",
|
||||
"evaluation",
|
||||
"absolution",
|
||||
],
|
||||
"reversed_keywords": [
|
||||
"doubt",
|
||||
"self-doubt",
|
||||
"harsh judgment",
|
||||
"reluctance to change",
|
||||
],
|
||||
"guidance": "Answer your higher calling. Evaluate with compassion. A significant awakening or decision awaits.",
|
||||
},
|
||||
"XXI": {
|
||||
"explanation": {
|
||||
"summary": "The World represents completion, wholeness, and fulfillment. It embodies the end of a cycle, achievement of goals, and a sense of unity.",
|
||||
"waite": "The World, the Universe, or Time. The four living creatures of the Apocalypse and Ezekiel's vision, attributed to the evangelists in Christian symbolism, are grouped about an elliptic garland, as if it were a chain of flowers intended to symbolize all sensible things; within this garland there is the figure of a woman, whom the wind has girt about the loins with a light scarf, and this is all her vesture."
|
||||
"waite": "The World, the Universe, or Time. The four living creatures of the Apocalypse and Ezekiel's vision, attributed to the evangelists in Christian symbolism, are grouped about an elliptic garland, as if it were a chain of flowers intended to symbolize all sensible things; within this garland there is the figure of a woman, whom the wind has girt about the loins with a light scarf, and this is all her vesture.",
|
||||
},
|
||||
"interpretation": "Completion of the Greatk Work; patience; perseverance; stubbornness; serious meditation. Work accomplished.",
|
||||
"keywords": ["completion", "fulfillment", "wholeness", "travel", "unity", "achievement"],
|
||||
"keywords": [
|
||||
"completion",
|
||||
"fulfillment",
|
||||
"wholeness",
|
||||
"travel",
|
||||
"unity",
|
||||
"achievement",
|
||||
],
|
||||
"reversed_keywords": ["incomplete", "blocked", "separation", "seeking closure"],
|
||||
"guidance": "A significant cycle completes. You have achieved wholeness. Yet every ending is a new beginning.",
|
||||
},
|
||||
|
||||
# Minor Arcana - Swords
|
||||
"Ace of Swords": {
|
||||
"explanation": {
|
||||
"summary": "A hand issues from a cloud, grasping as word, the point of which is encircled by a crown.",
|
||||
"waite": "A hand issues from a cloud, grasping as word, the point of which is encircled by a crown. Divinatory Meanings: Triumph, the excessive degree in everything, conquest, triumph of force. It is a card of great force, in love as well as in hatred. The crown may carry a much higher significance than comes usually within the sphere of fortune-telling. Reversed: The same, but the results are disastrous; another account says--conception, childbirth, augmentation, multiplicity."
|
||||
"waite": "A hand issues from a cloud, grasping as word, the point of which is encircled by a crown. Divinatory Meanings: Triumph, the excessive degree in everything, conquest, triumph of force. It is a card of great force, in love as well as in hatred. The crown may carry a much higher significance than comes usually within the sphere of fortune-telling. Reversed: The same, but the results are disastrous; another account says--conception, childbirth, augmentation, multiplicity.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -352,7 +521,7 @@ class CardDetailsRegistry:
|
||||
"Two of Swords": {
|
||||
"explanation": {
|
||||
"summary": "A hoodwinked female figure balances two swords upon her shoulders.",
|
||||
"waite": "A hoodwinked female figure balances two swords upon her shoulders. Divinatory Meanings: Conformity and the equipoise which it suggests, courage, friendship, concord in a state of arms; another reading gives tenderness, affection, intimacy. The suggestion of harmony and other favourable readings must be considered in a qualified manner, as Swords generally are not symbolical of beneficent forces in human affairs. Reversed: Imposture, falsehood, duplicity, disloyalty."
|
||||
"waite": "A hoodwinked female figure balances two swords upon her shoulders. Divinatory Meanings: Conformity and the equipoise which it suggests, courage, friendship, concord in a state of arms; another reading gives tenderness, affection, intimacy. The suggestion of harmony and other favourable readings must be considered in a qualified manner, as Swords generally are not symbolical of beneficent forces in human affairs. Reversed: Imposture, falsehood, duplicity, disloyalty.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -362,7 +531,7 @@ class CardDetailsRegistry:
|
||||
"Three of Swords": {
|
||||
"explanation": {
|
||||
"summary": "Three swords piercing a heart; cloud and rain behind.",
|
||||
"waite": "Three swords piercing a heart; cloud and rain behind. Divinatory Meanings: Removal, absence, delay, division, rupture, dispersion, and all that the design signifies naturally, being too simple and obvious to call for specific enumeration. Reversed: Mental alienation, error, loss, distraction, disorder, confusion."
|
||||
"waite": "Three swords piercing a heart; cloud and rain behind. Divinatory Meanings: Removal, absence, delay, division, rupture, dispersion, and all that the design signifies naturally, being too simple and obvious to call for specific enumeration. Reversed: Mental alienation, error, loss, distraction, disorder, confusion.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -372,7 +541,7 @@ class CardDetailsRegistry:
|
||||
"Four of Swords": {
|
||||
"explanation": {
|
||||
"summary": "The effigy of a knight in the attitude of prayer, at full length upon his tomb.",
|
||||
"waite": "The effigy of a knight in the attitude of prayer, at full length upon his tomb. Divinatory Meanings: Vigilance, retreat, solitude, hermit's repose, exile, tomb and coffin. It is these last that have suggested the design. Reversed: Wise administration, circumspection, economy, avarice, precaution, testament."
|
||||
"waite": "The effigy of a knight in the attitude of prayer, at full length upon his tomb. Divinatory Meanings: Vigilance, retreat, solitude, hermit's repose, exile, tomb and coffin. It is these last that have suggested the design. Reversed: Wise administration, circumspection, economy, avarice, precaution, testament.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -382,7 +551,7 @@ class CardDetailsRegistry:
|
||||
"Five of Swords": {
|
||||
"explanation": {
|
||||
"summary": "A disdainful man looks after two retreating and dejected figures.",
|
||||
"waite": "A disdainful man looks after two retreating and dejected figures. Their swords lie upon the ground. He carries two others on his left shoulder, and a third sword is in his right hand, point to earth. He is the master in possession of the field. Divinatory Meanings: Degradation, destruction, revocation, infamy, dishonour, loss, with the variants and analogues of these. Reversed: The same; burial and obsequies."
|
||||
"waite": "A disdainful man looks after two retreating and dejected figures. Their swords lie upon the ground. He carries two others on his left shoulder, and a third sword is in his right hand, point to earth. He is the master in possession of the field. Divinatory Meanings: Degradation, destruction, revocation, infamy, dishonour, loss, with the variants and analogues of these. Reversed: The same; burial and obsequies.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -392,7 +561,7 @@ class CardDetailsRegistry:
|
||||
"Six of Swords": {
|
||||
"explanation": {
|
||||
"summary": "A ferryman carrying passengers in his punt to the further shore.",
|
||||
"waite": "A ferryman carrying passengers in his punt to the further shore. The course is smooth, and seeing that the freight is light, it may be noted that the work is not beyond his strength. Divinatory Meanings: journey by water, route, way, envoy, commissionary, expedient. Reversed: Declaration, confession, publicity; one account says that it is a proposal of love."
|
||||
"waite": "A ferryman carrying passengers in his punt to the further shore. The course is smooth, and seeing that the freight is light, it may be noted that the work is not beyond his strength. Divinatory Meanings: journey by water, route, way, envoy, commissionary, expedient. Reversed: Declaration, confession, publicity; one account says that it is a proposal of love.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -402,7 +571,7 @@ class CardDetailsRegistry:
|
||||
"Seven of Swords": {
|
||||
"explanation": {
|
||||
"summary": "A man in the act of carrying away five swords rapidly; the two others of the card remain stuck in the ground.",
|
||||
"waite": "A man in the act of carrying away five swords rapidly; the two others of the card remain stuck in the ground. A camp is close at hand. Divinatory Meanings: Design, attempt, wish, hope, confidence; also quarrelling, a plan that may fail, annoyance. The design is uncertain in its import, because the significations are widely at variance with each other. Reversed: Good advice, counsel, instruction, slander, babbling."
|
||||
"waite": "A man in the act of carrying away five swords rapidly; the two others of the card remain stuck in the ground. A camp is close at hand. Divinatory Meanings: Design, attempt, wish, hope, confidence; also quarrelling, a plan that may fail, annoyance. The design is uncertain in its import, because the significations are widely at variance with each other. Reversed: Good advice, counsel, instruction, slander, babbling.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -412,7 +581,7 @@ class CardDetailsRegistry:
|
||||
"Eight of Swords": {
|
||||
"explanation": {
|
||||
"summary": "A woman, bound and hoodwinked, with the swords of the card about her.",
|
||||
"waite": "A woman, bound and hoodwinked, with the swords of the card about her. Yet it is rather a card of temporary durance than of irretrievable bondage. Divinatory Meanings: Bad news, violent chagrin, crisis, censure, power in trammels, conflict, calumny; also sickness. Reversed: Disquiet, difficulty, opposition, accident, treachery; what is unforeseen; fatality."
|
||||
"waite": "A woman, bound and hoodwinked, with the swords of the card about her. Yet it is rather a card of temporary durance than of irretrievable bondage. Divinatory Meanings: Bad news, violent chagrin, crisis, censure, power in trammels, conflict, calumny; also sickness. Reversed: Disquiet, difficulty, opposition, accident, treachery; what is unforeseen; fatality.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -422,7 +591,7 @@ class CardDetailsRegistry:
|
||||
"Nine of Swords": {
|
||||
"explanation": {
|
||||
"summary": "One seated on her couch in lamentation, with the swords over her.",
|
||||
"waite": "One seated on her couch in lamentation, with the swords over her. She is as one who knows no sorrow which is like unto hers. It is a card of utter desolation. Divinatory Meanings: Death, failure, miscarriage, delay, deception, disappointment, despair. Reversed: Imprisonment, suspicion, doubt, reasonable fear, shame."
|
||||
"waite": "One seated on her couch in lamentation, with the swords over her. She is as one who knows no sorrow which is like unto hers. It is a card of utter desolation. Divinatory Meanings: Death, failure, miscarriage, delay, deception, disappointment, despair. Reversed: Imprisonment, suspicion, doubt, reasonable fear, shame.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -432,7 +601,7 @@ class CardDetailsRegistry:
|
||||
"Ten of Swords": {
|
||||
"explanation": {
|
||||
"summary": "A prostrate figure, pierced by all the swords belonging to the card.",
|
||||
"waite": "A prostrate figure, pierced by all the swords belonging to the card. Divinatory Meanings: Whatsoever is intimated by the design; also pain, affliction, tears, sadness, desolation. It is not especially a card of violent death. Reversed: Advantage, profit, success, favour, but none of these are permanent; also power and authority."
|
||||
"waite": "A prostrate figure, pierced by all the swords belonging to the card. Divinatory Meanings: Whatsoever is intimated by the design; also pain, affliction, tears, sadness, desolation. It is not especially a card of violent death. Reversed: Advantage, profit, success, favour, but none of these are permanent; also power and authority.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -442,7 +611,7 @@ class CardDetailsRegistry:
|
||||
"Page of Swords": {
|
||||
"explanation": {
|
||||
"summary": "A lithe, active figure holds a sword upright in both hands, while in the act of swift walking.",
|
||||
"waite": "A lithe, active figure holds a sword upright in both hands, while in the act of swift walking. He is passing over rugged land, and about his way the clouds are collocated wildly. He is alert and lithe, looking this way and that, as if an expected enemy might appear at any moment. Divinatory Meanings: Authority, overseeing, secret service, vigilance, spying, examination, and the qualities thereto belonging. Reversed: More evil side of these qualities; what is unforeseen, unprepared state; sickness is also intimated."
|
||||
"waite": "A lithe, active figure holds a sword upright in both hands, while in the act of swift walking. He is passing over rugged land, and about his way the clouds are collocated wildly. He is alert and lithe, looking this way and that, as if an expected enemy might appear at any moment. Divinatory Meanings: Authority, overseeing, secret service, vigilance, spying, examination, and the qualities thereto belonging. Reversed: More evil side of these qualities; what is unforeseen, unprepared state; sickness is also intimated.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -452,7 +621,7 @@ class CardDetailsRegistry:
|
||||
"Knight of Swords": {
|
||||
"explanation": {
|
||||
"summary": "He is riding in full course, as if scattering his enemies.",
|
||||
"waite": "He is riding in full course, as if scattering his enemies. In the design he is really a prototypical hero of romantic chivalry. He might almost be Galahad, whose sword is swift and sure because he is clean of heart. Divinatory Meanings: Skill, bravery, capacity, defence, address, enmity, wrath, war, destruction, opposition, resistance, ruin. There is therefore a sense in which the card signifies death, but it carries this meaning only in its proximity to other cards of fatality. Reversed: Imprudence, incapacity, extravagance."
|
||||
"waite": "He is riding in full course, as if scattering his enemies. In the design he is really a prototypical hero of romantic chivalry. He might almost be Galahad, whose sword is swift and sure because he is clean of heart. Divinatory Meanings: Skill, bravery, capacity, defence, address, enmity, wrath, war, destruction, opposition, resistance, ruin. There is therefore a sense in which the card signifies death, but it carries this meaning only in its proximity to other cards of fatality. Reversed: Imprudence, incapacity, extravagance.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -462,7 +631,7 @@ class CardDetailsRegistry:
|
||||
"Queen of Swords": {
|
||||
"explanation": {
|
||||
"summary": "Her right hand raises the weapon vertically and the hilt rests on an arm of her royal chair the left hand is extended, the arm raised her countenance is severe but chastened; it suggests familiarity with sorrow.",
|
||||
"waite": "Her right hand raises the weapon vertically and the hilt rests on an arm of her royal chair the left hand is extended, the arm raised her countenance is severe but chastened; it suggests familiarity with sorrow. It does not represent mercy, and, her sword notwithstanding, she is scarcely a symbol of power. Divinatory Meanings: Widowhood, female sadness and embarrassment, absence, sterility, mourning, privation, separation. Reversed: Malice, bigotry, artifice, prudery, bale, deceit."
|
||||
"waite": "Her right hand raises the weapon vertically and the hilt rests on an arm of her royal chair the left hand is extended, the arm raised her countenance is severe but chastened; it suggests familiarity with sorrow. It does not represent mercy, and, her sword notwithstanding, she is scarcely a symbol of power. Divinatory Meanings: Widowhood, female sadness and embarrassment, absence, sterility, mourning, privation, separation. Reversed: Malice, bigotry, artifice, prudery, bale, deceit.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -472,7 +641,7 @@ class CardDetailsRegistry:
|
||||
"King of Swords": {
|
||||
"explanation": {
|
||||
"summary": "Whatsoever arises out of the idea of judgment and all its connexions-power, command, authority, militant intelligence, law, offices of the crown, and so forth.",
|
||||
"waite": "Whatsoever arises out of the idea of judgment and all its connexions-power, command, authority, militant intelligence, law, offices of the crown, and so forth."
|
||||
"waite": "Whatsoever arises out of the idea of judgment and all its connexions-power, command, authority, militant intelligence, law, offices of the crown, and so forth.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -483,7 +652,7 @@ class CardDetailsRegistry:
|
||||
"Ace of Cups": {
|
||||
"explanation": {
|
||||
"summary": "The waters are beneath, and thereon are water-lilies; the hand issues from the cloud, holding in its palm the cup, from which four streams are pouring; a dove, bearing in its bill a cross-marked Host, descends to place the Wafer in the Cup; the dew of water is falling on all sides.",
|
||||
"waite": "The waters are beneath, and thereon are water-lilies; the hand issues from the cloud, holding in its palm the cup, from which four streams are pouring; a dove, bearing in its bill a cross-marked Host, descends to place the Wafer in the Cup; the dew of water is falling on all sides. It is an intimation of that which may lie behind the Lesser Arcana. Divinatory Meanings: House of the true heart, joy, content, abode, nourishment, abundance, fertility; Holy Table, felicity hereof. Reversed: House of the false heart, mutation, instability, revolution."
|
||||
"waite": "The waters are beneath, and thereon are water-lilies; the hand issues from the cloud, holding in its palm the cup, from which four streams are pouring; a dove, bearing in its bill a cross-marked Host, descends to place the Wafer in the Cup; the dew of water is falling on all sides. It is an intimation of that which may lie behind the Lesser Arcana. Divinatory Meanings: House of the true heart, joy, content, abode, nourishment, abundance, fertility; Holy Table, felicity hereof. Reversed: House of the false heart, mutation, instability, revolution.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -493,7 +662,7 @@ class CardDetailsRegistry:
|
||||
"Two of Cups": {
|
||||
"explanation": {
|
||||
"summary": "A youth and maiden are pledging one another, and above their cups rises the Caduceus of Hermes, between the great wings of which there appears a lion's head.",
|
||||
"waite": "A youth and maiden are pledging one another, and above their cups rises the Caduceus of Hermes, between the great wings of which there appears a lion's head. It is a variant of a sign which is found in a few old examples of this card. Some curious emblematical meanings are attached to it, but they do not concern us in this place. Divinatory Meanings: Love, passion, friendship, affinity, union, concord, sympathy, the interrelation of the sexes, and--as a suggestion apart from all offices of divination--that desire which is not in Nature, but by which Nature is sanctified."
|
||||
"waite": "A youth and maiden are pledging one another, and above their cups rises the Caduceus of Hermes, between the great wings of which there appears a lion's head. It is a variant of a sign which is found in a few old examples of this card. Some curious emblematical meanings are attached to it, but they do not concern us in this place. Divinatory Meanings: Love, passion, friendship, affinity, union, concord, sympathy, the interrelation of the sexes, and--as a suggestion apart from all offices of divination--that desire which is not in Nature, but by which Nature is sanctified.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -503,7 +672,7 @@ class CardDetailsRegistry:
|
||||
"Three of Cups": {
|
||||
"explanation": {
|
||||
"summary": "Maidens in a garden-ground with cups uplifted, as if pledging one another.",
|
||||
"waite": "Maidens in a garden-ground with cups uplifted, as if pledging one another. Divinatory Meanings: The conclusion of any matter in plenty, perfection and merriment; happy issue, victory, fulfilment, solace, healing, Reversed: Expedition, dispatch, achievement, end. It signifies also the side of excess in physical enjoyment, and the pleasures of the senses."
|
||||
"waite": "Maidens in a garden-ground with cups uplifted, as if pledging one another. Divinatory Meanings: The conclusion of any matter in plenty, perfection and merriment; happy issue, victory, fulfilment, solace, healing, Reversed: Expedition, dispatch, achievement, end. It signifies also the side of excess in physical enjoyment, and the pleasures of the senses.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -513,7 +682,7 @@ class CardDetailsRegistry:
|
||||
"Four of Cups": {
|
||||
"explanation": {
|
||||
"summary": "A young man is seated under a tree and contemplates three cups set on the grass before him; an arm issuing from a cloud offers him another cup.",
|
||||
"waite": "A young man is seated under a tree and contemplates three cups set on the grass before him; an arm issuing from a cloud offers him another cup. His expression notwithstanding is one of discontent with his environment. Divinatory Meanings: Weariness, disgust, aversion, imaginary vexations, as if the wine of this world had caused satiety only; another wine, as if a fairy gift, is now offered the wastrel, but he sees no consolation therein. This is also a card of blended pleasure. Reversed: Novelty, presage, new instruction, new relations."
|
||||
"waite": "A young man is seated under a tree and contemplates three cups set on the grass before him; an arm issuing from a cloud offers him another cup. His expression notwithstanding is one of discontent with his environment. Divinatory Meanings: Weariness, disgust, aversion, imaginary vexations, as if the wine of this world had caused satiety only; another wine, as if a fairy gift, is now offered the wastrel, but he sees no consolation therein. This is also a card of blended pleasure. Reversed: Novelty, presage, new instruction, new relations.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -523,7 +692,7 @@ class CardDetailsRegistry:
|
||||
"Five of Cups": {
|
||||
"explanation": {
|
||||
"summary": "A dark, cloaked figure, looking sideways at three prone cups two others stand upright behind him; a bridge is in the background, leading to a small keep or holding.",
|
||||
"waite": "A dark, cloaked figure, looking sideways at three prone cups two others stand upright behind him; a bridge is in the background, leading to a small keep or holding. Divinatory Meanings: It is a card of loss, but something remains over; three have been taken, but two are left; it is a card of inheritance, patrimony, transmission, but not corresponding to expectations; with some interpreters it is a card of marriage, but not without bitterness or frustration. Reversed: News, alliances, affinity, consanguinity, ancestry, return, false projects."
|
||||
"waite": "A dark, cloaked figure, looking sideways at three prone cups two others stand upright behind him; a bridge is in the background, leading to a small keep or holding. Divinatory Meanings: It is a card of loss, but something remains over; three have been taken, but two are left; it is a card of inheritance, patrimony, transmission, but not corresponding to expectations; with some interpreters it is a card of marriage, but not without bitterness or frustration. Reversed: News, alliances, affinity, consanguinity, ancestry, return, false projects.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -533,7 +702,7 @@ class CardDetailsRegistry:
|
||||
"Six of Cups": {
|
||||
"explanation": {
|
||||
"summary": "Children in an old garden, their cups filled with flowers.",
|
||||
"waite": "Children in an old garden, their cups filled with flowers. Divinatory Meanings: A card of the past and of memories, looking back, as--for example--on childhood; happiness, enjoyment, but coming rather from the past; things that have vanished. Another reading reverses this, giving new relations, new knowledge, new environment, and then the children are disporting in an unfamiliar precinct. Reversed: The future, renewal, that which will come to pass presently."
|
||||
"waite": "Children in an old garden, their cups filled with flowers. Divinatory Meanings: A card of the past and of memories, looking back, as--for example--on childhood; happiness, enjoyment, but coming rather from the past; things that have vanished. Another reading reverses this, giving new relations, new knowledge, new environment, and then the children are disporting in an unfamiliar precinct. Reversed: The future, renewal, that which will come to pass presently.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -543,7 +712,7 @@ class CardDetailsRegistry:
|
||||
"Seven of Cups": {
|
||||
"explanation": {
|
||||
"summary": "Strange chalices of vision, but the images are more especially those of the fantastic spirit.",
|
||||
"waite": "Strange chalices of vision, but the images are more especially those of the fantastic spirit. Divinatory Meanings: Fairy favours, images of reflection, sentiment, imagination, things seen in the glass of contemplation; some attainment in these degrees, but nothing permanent or substantial is suggested. Reversed: Desire, will, determination, project."
|
||||
"waite": "Strange chalices of vision, but the images are more especially those of the fantastic spirit. Divinatory Meanings: Fairy favours, images of reflection, sentiment, imagination, things seen in the glass of contemplation; some attainment in these degrees, but nothing permanent or substantial is suggested. Reversed: Desire, will, determination, project.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -553,7 +722,7 @@ class CardDetailsRegistry:
|
||||
"Eight of Cups": {
|
||||
"explanation": {
|
||||
"summary": "A man of dejected aspect is deserting the cups of his felicity, enterprise, undertaking or previous concern.",
|
||||
"waite": "A man of dejected aspect is deserting the cups of his felicity, enterprise, undertaking or previous concern. Divinatory Meanings: The card speaks for itself on the surface, but other readings are entirely antithetical--giving joy, mildness, timidity, honour, modesty. In practice, it is usually found that the card shews the decline of a matter, or that a matter which has been thought to be important is really of slight consequence--either for good or evil. Reversed: Great joy, happiness, feasting."
|
||||
"waite": "A man of dejected aspect is deserting the cups of his felicity, enterprise, undertaking or previous concern. Divinatory Meanings: The card speaks for itself on the surface, but other readings are entirely antithetical--giving joy, mildness, timidity, honour, modesty. In practice, it is usually found that the card shews the decline of a matter, or that a matter which has been thought to be important is really of slight consequence--either for good or evil. Reversed: Great joy, happiness, feasting.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -563,7 +732,7 @@ class CardDetailsRegistry:
|
||||
"Nine of Cups": {
|
||||
"explanation": {
|
||||
"summary": "A goodly personage has feasted to his heart's content, and abundant refreshment of wine is on the arched counter behind him, seeming to indicate that the future is also assured.",
|
||||
"waite": "A goodly personage has feasted to his heart's content, and abundant refreshment of wine is on the arched counter behind him, seeming to indicate that the future is also assured. The picture offers the material side only, but there are other aspects. Divinatory Meanings: Concord, contentment, physical bien-être; also victory, success, advantage; satisfaction for the Querent or person for whom the consultation is made. Reversed: Truth, loyalty, liberty; but the readings vary and include mistakes, imperfections, etc."
|
||||
"waite": "A goodly personage has feasted to his heart's content, and abundant refreshment of wine is on the arched counter behind him, seeming to indicate that the future is also assured. The picture offers the material side only, but there are other aspects. Divinatory Meanings: Concord, contentment, physical bien-être; also victory, success, advantage; satisfaction for the Querent or person for whom the consultation is made. Reversed: Truth, loyalty, liberty; but the readings vary and include mistakes, imperfections, etc.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -573,7 +742,7 @@ class CardDetailsRegistry:
|
||||
"Ten of Cups": {
|
||||
"explanation": {
|
||||
"summary": "Appearance of Cups in a rainbow; it is contemplated in wonder and ecstacy by a man and woman below, evidently husband and wife.",
|
||||
"waite": "Appearance of Cups in a rainbow; it is contemplated in wonder and ecstacy by a man and woman below, evidently husband and wife. His right arm is about her; his left is raised upward; she raises her right arm. The two children dancing near them have not observed the prodigy but are happy after their own manner. There is a home-scene beyond. Divinatory Meanings: Contentment, repose of the entire heart; the perfection of that state; also perfection of human love and friendship; if with several picture-cards, a person who is taking charge of the Querent's interests; also the town, village or country inhabited by the Querent. Reversed: Repose of the false heart, indignation, violence."
|
||||
"waite": "Appearance of Cups in a rainbow; it is contemplated in wonder and ecstacy by a man and woman below, evidently husband and wife. His right arm is about her; his left is raised upward; she raises her right arm. The two children dancing near them have not observed the prodigy but are happy after their own manner. There is a home-scene beyond. Divinatory Meanings: Contentment, repose of the entire heart; the perfection of that state; also perfection of human love and friendship; if with several picture-cards, a person who is taking charge of the Querent's interests; also the town, village or country inhabited by the Querent. Reversed: Repose of the false heart, indignation, violence.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -583,7 +752,7 @@ class CardDetailsRegistry:
|
||||
"Page of Cups": {
|
||||
"explanation": {
|
||||
"summary": "A fair, pleasing, somewhat effeminate page, of studious and intent aspect, contemplates a fish rising from a cup to look at him.",
|
||||
"waite": "A fair, pleasing, somewhat effeminate page, of studious and intent aspect, contemplates a fish rising from a cup to look at him. It is the pictures of the mind taking form. Divinatory Meanings: Fair young man, one impelled to render service and with whom the Querent will be connected; a studious youth; news, message; application, reflection, meditation; also these things directed to business. Reversed: Taste, inclination, attachment, seduction, deception, artifice."
|
||||
"waite": "A fair, pleasing, somewhat effeminate page, of studious and intent aspect, contemplates a fish rising from a cup to look at him. It is the pictures of the mind taking form. Divinatory Meanings: Fair young man, one impelled to render service and with whom the Querent will be connected; a studious youth; news, message; application, reflection, meditation; also these things directed to business. Reversed: Taste, inclination, attachment, seduction, deception, artifice.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -593,7 +762,7 @@ class CardDetailsRegistry:
|
||||
"Knight of Cups": {
|
||||
"explanation": {
|
||||
"summary": "Graceful, but not warlike; riding quietly, wearing a winged helmet, referring to those higher graces of the imagination which sometimes characterize this card.",
|
||||
"waite": "Graceful, but not warlike; riding quietly, wearing a winged helmet, referring to those higher graces of the imagination which sometimes characterize this card. He too is a dreamer, but the images of the side of sense haunt him in his vision. Divinatory Meanings: Arrival, approach--sometimes that of a messenger; advances, proposition, demeanour, invitation, incitement. Reversed: Trickery, artifice, subtlety, swindling, duplicity, fraud."
|
||||
"waite": "Graceful, but not warlike; riding quietly, wearing a winged helmet, referring to those higher graces of the imagination which sometimes characterize this card. He too is a dreamer, but the images of the side of sense haunt him in his vision. Divinatory Meanings: Arrival, approach--sometimes that of a messenger; advances, proposition, demeanour, invitation, incitement. Reversed: Trickery, artifice, subtlety, swindling, duplicity, fraud.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -603,7 +772,7 @@ class CardDetailsRegistry:
|
||||
"Queen of Cups": {
|
||||
"explanation": {
|
||||
"summary": "Beautiful, fair, dreamy--as one who sees visions in a cup.",
|
||||
"waite": "Beautiful, fair, dreamy--as one who sees visions in a cup. This is, however, only one of her aspects; she sees, but she also acts, and her activity feeds her dream. Divinatory Meanings: Good, fair woman; honest, devoted woman, who will do service to the Querent; loving intelligence, and hence the gift of vision; success, happiness, pleasure; also wisdom, virtue; a perfect spouse and a good mother. Reversed: The accounts vary; good woman; otherwise, distinguished woman but one not to be trusted; perverse woman; vice, dishonour, depravity."
|
||||
"waite": "Beautiful, fair, dreamy--as one who sees visions in a cup. This is, however, only one of her aspects; she sees, but she also acts, and her activity feeds her dream. Divinatory Meanings: Good, fair woman; honest, devoted woman, who will do service to the Querent; loving intelligence, and hence the gift of vision; success, happiness, pleasure; also wisdom, virtue; a perfect spouse and a good mother. Reversed: The accounts vary; good woman; otherwise, distinguished woman but one not to be trusted; perverse woman; vice, dishonour, depravity.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -613,7 +782,7 @@ class CardDetailsRegistry:
|
||||
"King of Cups": {
|
||||
"explanation": {
|
||||
"summary": "Fair man, man of business, law, or divinity; responsible, disposed to oblige the Querent; also equity, art and science, including those who profess science, law and art; creative intelligence.",
|
||||
"waite": "Fair man, man of business, law, or divinity; responsible, disposed to oblige the Querent; also equity, art and science, including those who profess science, law and art; creative intelligence."
|
||||
"waite": "Fair man, man of business, law, or divinity; responsible, disposed to oblige the Querent; also equity, art and science, including those who profess science, law and art; creative intelligence.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -624,7 +793,7 @@ class CardDetailsRegistry:
|
||||
"Ace of Pentacles": {
|
||||
"explanation": {
|
||||
"summary": "A hand--issuing, as usual, from a cloud--holds up a pentacle.",
|
||||
"waite": "A hand--issuing, as usual, from a cloud--holds up a pentacle. Divinatory Meanings: Perfect contentment, felicity, ecstasy; also speedy intelligence; gold. Reversed: The evil side of wealth, bad intelligence; also great riches. In any case it shews prosperity, comfortable material conditions, but whether these are of advantage to the possessor will depend on whether the card is reversed or not."
|
||||
"waite": "A hand--issuing, as usual, from a cloud--holds up a pentacle. Divinatory Meanings: Perfect contentment, felicity, ecstasy; also speedy intelligence; gold. Reversed: The evil side of wealth, bad intelligence; also great riches. In any case it shews prosperity, comfortable material conditions, but whether these are of advantage to the possessor will depend on whether the card is reversed or not.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -634,7 +803,7 @@ class CardDetailsRegistry:
|
||||
"Two of Pentacles": {
|
||||
"explanation": {
|
||||
"summary": "A young man, in the act of dancing, has a pentacle in either hand, and they are joined by that endless cord which is like the number 8 reversed.",
|
||||
"waite": "A young man, in the act of dancing, has a pentacle in either hand, and they are joined by that endless cord which is like the number 8 reversed. Divinatory Meanings: On the one hand it is represented as a card of gaiety, recreation and its connexions, which is the subject of the design; but it is read also as news and messages in writing, as obstacles, agitation, trouble, embroilment. Reversed: Enforced gaiety, simulated enjoyment, literal sense, handwriting, composition, letters of exchange."
|
||||
"waite": "A young man, in the act of dancing, has a pentacle in either hand, and they are joined by that endless cord which is like the number 8 reversed. Divinatory Meanings: On the one hand it is represented as a card of gaiety, recreation and its connexions, which is the subject of the design; but it is read also as news and messages in writing, as obstacles, agitation, trouble, embroilment. Reversed: Enforced gaiety, simulated enjoyment, literal sense, handwriting, composition, letters of exchange.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -644,7 +813,7 @@ class CardDetailsRegistry:
|
||||
"Three of Pentacles": {
|
||||
"explanation": {
|
||||
"summary": "A sculptor at his work in a monastery.",
|
||||
"waite": "A sculptor at his work in a monastery. Compare the design which illustrates the Eight of Pentacles. The apprentice or amateur therein has received his reward and is now at work in earnest. Divinatory Meanings: Métier, trade, skilled labour; usually, however, regarded as a card of nobility, aristocracy, renown, glory. Reversed: Mediocrity, in work and otherwise, puerility, pettiness, weakness."
|
||||
"waite": "A sculptor at his work in a monastery. Compare the design which illustrates the Eight of Pentacles. The apprentice or amateur therein has received his reward and is now at work in earnest. Divinatory Meanings: Métier, trade, skilled labour; usually, however, regarded as a card of nobility, aristocracy, renown, glory. Reversed: Mediocrity, in work and otherwise, puerility, pettiness, weakness.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -654,7 +823,7 @@ class CardDetailsRegistry:
|
||||
"Four of Pentacles": {
|
||||
"explanation": {
|
||||
"summary": "A crowned figure, having a pentacle over his crown, clasps another with hands and arms; two pentacles are under his feet.",
|
||||
"waite": "A crowned figure, having a pentacle over his crown, clasps another with hands and arms; two pentacles are under his feet. He holds to that which he has. Divinatory Meanings: The surety of possessions, cleaving to that which one has, gift, legacy, inheritance. Reversed: Suspense, delay, opposition."
|
||||
"waite": "A crowned figure, having a pentacle over his crown, clasps another with hands and arms; two pentacles are under his feet. He holds to that which he has. Divinatory Meanings: The surety of possessions, cleaving to that which one has, gift, legacy, inheritance. Reversed: Suspense, delay, opposition.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -664,7 +833,7 @@ class CardDetailsRegistry:
|
||||
"Five of Pentacles": {
|
||||
"explanation": {
|
||||
"summary": "Two mendicants in a snow-storm pass a lighted casement.",
|
||||
"waite": "Two mendicants in a snow-storm pass a lighted casement. Divinatory Meanings: The card foretells material trouble above all, whether in the form illustrated--that is, destitution--or otherwise. For some cartomancists, it is a card of love and lovers-wife, husband, friend, mistress; also concordance, affinities. These alternatives cannot be harmonized. Reversed: Disorder, chaos, ruin, discord, profligacy."
|
||||
"waite": "Two mendicants in a snow-storm pass a lighted casement. Divinatory Meanings: The card foretells material trouble above all, whether in the form illustrated--that is, destitution--or otherwise. For some cartomancists, it is a card of love and lovers-wife, husband, friend, mistress; also concordance, affinities. These alternatives cannot be harmonized. Reversed: Disorder, chaos, ruin, discord, profligacy.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -674,7 +843,7 @@ class CardDetailsRegistry:
|
||||
"Six of Pentacles": {
|
||||
"explanation": {
|
||||
"summary": "A person in the guise of a merchant weighs money in a pair of scales and distributes it to the needy and distressed.",
|
||||
"waite": "A person in the guise of a merchant weighs money in a pair of scales and distributes it to the needy and distressed. It is a testimony to his own success in life, as well as to his goodness of heart. Divinatory Meanings: Presents, gifts, gratification another account says attention, vigilance now is the accepted time, present prosperity, etc. Reversed: Desire, cupidity, envy, jealousy, illusion."
|
||||
"waite": "A person in the guise of a merchant weighs money in a pair of scales and distributes it to the needy and distressed. It is a testimony to his own success in life, as well as to his goodness of heart. Divinatory Meanings: Presents, gifts, gratification another account says attention, vigilance now is the accepted time, present prosperity, etc. Reversed: Desire, cupidity, envy, jealousy, illusion.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -684,7 +853,7 @@ class CardDetailsRegistry:
|
||||
"Seven of Pentacles": {
|
||||
"explanation": {
|
||||
"summary": "A young man, leaning on his staff, looks intently at seven pentacles attached to a clump of greenery on his right; one would say that these were his treasures and that his heart was there.",
|
||||
"waite": "A young man, leaning on his staff, looks intently at seven pentacles attached to a clump of greenery on his right; one would say that these were his treasures and that his heart was there. Divinatory Meanings: These are exceedingly contradictory; in the main, it is a card of money, business, barter; but one reading gives altercation, quarrels--and another innocence, ingenuity, purgation. Reversed: Cause for anxiety regarding money which it may be proposed to lend."
|
||||
"waite": "A young man, leaning on his staff, looks intently at seven pentacles attached to a clump of greenery on his right; one would say that these were his treasures and that his heart was there. Divinatory Meanings: These are exceedingly contradictory; in the main, it is a card of money, business, barter; but one reading gives altercation, quarrels--and another innocence, ingenuity, purgation. Reversed: Cause for anxiety regarding money which it may be proposed to lend.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -694,7 +863,7 @@ class CardDetailsRegistry:
|
||||
"Eight of Pentacles": {
|
||||
"explanation": {
|
||||
"summary": "An artist in stone at his work, which he exhibits in the form of trophies.",
|
||||
"waite": "An artist in stone at his work, which he exhibits in the form of trophies. Divinatory Meanings: Work, employment, commission, craftsmanship, skill in craft and business, perhaps in the preparatory stage. Reversed: Voided ambition, vanity, cupidity, exaction, usury. It may also signify the possession of skill, in the sense of the ingenious mind turned to cunning and intrigue."
|
||||
"waite": "An artist in stone at his work, which he exhibits in the form of trophies. Divinatory Meanings: Work, employment, commission, craftsmanship, skill in craft and business, perhaps in the preparatory stage. Reversed: Voided ambition, vanity, cupidity, exaction, usury. It may also signify the possession of skill, in the sense of the ingenious mind turned to cunning and intrigue.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -704,7 +873,7 @@ class CardDetailsRegistry:
|
||||
"Nine of Pentacles": {
|
||||
"explanation": {
|
||||
"summary": "A woman, with a bird upon her wrist, stands amidst a great abundance of grapevines in the garden of a manorial house.",
|
||||
"waite": "A woman, with a bird upon her wrist, stands amidst a great abundance of grapevines in the garden of a manorial house. It is a wide domain, suggesting plenty in all things. Possibly it is her own possession and testifies to material well-being. Divinatory Meanings: Prudence, safety, success, accomplishment, certitude, discernment. Reversed: Roguery, deception, voided project, bad faith."
|
||||
"waite": "A woman, with a bird upon her wrist, stands amidst a great abundance of grapevines in the garden of a manorial house. It is a wide domain, suggesting plenty in all things. Possibly it is her own possession and testifies to material well-being. Divinatory Meanings: Prudence, safety, success, accomplishment, certitude, discernment. Reversed: Roguery, deception, voided project, bad faith.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -714,7 +883,7 @@ class CardDetailsRegistry:
|
||||
"Ten of Pentacles": {
|
||||
"explanation": {
|
||||
"summary": "A man and woman beneath an archway which gives entrance to a house and domain.",
|
||||
"waite": "A man and woman beneath an archway which gives entrance to a house and domain. They are accompanied by a child, who looks curiously at two dogs accosting an ancient personage seated in the foreground. The child's hand is on one of them. Divinatory Meanings: Gain, riches; family matters, archives, extraction, the abode of a family. Reversed: Chance, fatality, loss, robbery, games of hazard; sometimes gift, dowry, pension."
|
||||
"waite": "A man and woman beneath an archway which gives entrance to a house and domain. They are accompanied by a child, who looks curiously at two dogs accosting an ancient personage seated in the foreground. The child's hand is on one of them. Divinatory Meanings: Gain, riches; family matters, archives, extraction, the abode of a family. Reversed: Chance, fatality, loss, robbery, games of hazard; sometimes gift, dowry, pension.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -724,7 +893,7 @@ class CardDetailsRegistry:
|
||||
"Page of Pentacles": {
|
||||
"explanation": {
|
||||
"summary": "A youthful figure, looking intently at the pentacle which hovers over his raised hands.",
|
||||
"waite": "A youthful figure, looking intently at the pentacle which hovers over his raised hands. He moves slowly, insensible of that which is about him. Divinatory Meanings: Application, study, scholarship, reflection another reading says news, messages and the bringer thereof; also rule, management. Reversed: Prodigality, dissipation, liberality, luxury; unfavourable news."
|
||||
"waite": "A youthful figure, looking intently at the pentacle which hovers over his raised hands. He moves slowly, insensible of that which is about him. Divinatory Meanings: Application, study, scholarship, reflection another reading says news, messages and the bringer thereof; also rule, management. Reversed: Prodigality, dissipation, liberality, luxury; unfavourable news.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -734,7 +903,7 @@ class CardDetailsRegistry:
|
||||
"Knight of Pentacles": {
|
||||
"explanation": {
|
||||
"summary": "He rides a slow, enduring, heavy horse, to which his own aspect corresponds.",
|
||||
"waite": "He rides a slow, enduring, heavy horse, to which his own aspect corresponds. He exhibits his symbol, but does not look therein. Divinatory Meanings: Utility, serviceableness, interest, responsibility, rectitude-all on the normal and external plane. Reversed: inertia, idleness, repose of that kind, stagnation; also placidity, discouragement, carelessness."
|
||||
"waite": "He rides a slow, enduring, heavy horse, to which his own aspect corresponds. He exhibits his symbol, but does not look therein. Divinatory Meanings: Utility, serviceableness, interest, responsibility, rectitude-all on the normal and external plane. Reversed: inertia, idleness, repose of that kind, stagnation; also placidity, discouragement, carelessness.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -744,7 +913,7 @@ class CardDetailsRegistry:
|
||||
"Queen of Pentacles": {
|
||||
"explanation": {
|
||||
"summary": "The face suggests that of a dark woman, whose qualities might be summed up in the idea of greatness of soul; she has also the serious cast of intelligence; she contemplates her symbol and may see worlds therein.",
|
||||
"waite": "The face suggests that of a dark woman, whose qualities might be summed up in the idea of greatness of soul; she has also the serious cast of intelligence; she contemplates her symbol and may see worlds therein. Divinatory Meanings: Opulence, generosity, magnificence, security, liberty. Reversed: Evil, suspicion, suspense, fear, mistrust."
|
||||
"waite": "The face suggests that of a dark woman, whose qualities might be summed up in the idea of greatness of soul; she has also the serious cast of intelligence; she contemplates her symbol and may see worlds therein. Divinatory Meanings: Opulence, generosity, magnificence, security, liberty. Reversed: Evil, suspicion, suspense, fear, mistrust.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -754,7 +923,7 @@ class CardDetailsRegistry:
|
||||
"King of Pentacles": {
|
||||
"explanation": {
|
||||
"summary": "Valour, realizing intelligence, business and normal intellectual aptitude, sometimes mathematical gifts and attainments of this kind; success in these paths.",
|
||||
"waite": "Valour, realizing intelligence, business and normal intellectual aptitude, sometimes mathematical gifts and attainments of this kind; success in these paths."
|
||||
"waite": "Valour, realizing intelligence, business and normal intellectual aptitude, sometimes mathematical gifts and attainments of this kind; success in these paths.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -765,7 +934,7 @@ class CardDetailsRegistry:
|
||||
"Ace of Wands": {
|
||||
"explanation": {
|
||||
"summary": "Creation, invention, enterprise, the powers which result in these; principle, beginning, source; birth, family, origin, and in a sense the virility which is behind them; the starting point of enterprises; according to another account, money, fortune, inheritance.",
|
||||
"waite": "Creation, invention, enterprise, the powers which result in these; principle, beginning, source; birth, family, origin, and in a sense the virility which is behind them; the starting point of enterprises; according to another account, money, fortune, inheritance."
|
||||
"waite": "Creation, invention, enterprise, the powers which result in these; principle, beginning, source; birth, family, origin, and in a sense the virility which is behind them; the starting point of enterprises; according to another account, money, fortune, inheritance.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -775,7 +944,7 @@ class CardDetailsRegistry:
|
||||
"Two of Wands": {
|
||||
"explanation": {
|
||||
"summary": "Between the alternative readings there is no marriage possible; on the one hand, riches, fortune, magnificence; on the other, physical suffering, disease, chagrin, sadness, mortification.",
|
||||
"waite": "Between the alternative readings there is no marriage possible; on the one hand, riches, fortune, magnificence; on the other, physical suffering, disease, chagrin, sadness, mortification. The design gives one suggestion; here is a lord overlooking his dominion and alternately contemplating a globe; it looks like the malady, the mortification, the sadness of Alexander amidst the grandeur of this world's wealth."
|
||||
"waite": "Between the alternative readings there is no marriage possible; on the one hand, riches, fortune, magnificence; on the other, physical suffering, disease, chagrin, sadness, mortification. The design gives one suggestion; here is a lord overlooking his dominion and alternately contemplating a globe; it looks like the malady, the mortification, the sadness of Alexander amidst the grandeur of this world's wealth.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -785,7 +954,7 @@ class CardDetailsRegistry:
|
||||
"Three of Wands": {
|
||||
"explanation": {
|
||||
"summary": "He symbolizes established strength, enterprise, effort, trade, commerce, discovery; those are his ships, bearing his merchandise, which are sailing over the sea.",
|
||||
"waite": "He symbolizes established strength, enterprise, effort, trade, commerce, discovery; those are his ships, bearing his merchandise, which are sailing over the sea. The card also signifies able co-operation in business, as if the successful merchant prince were looking from his side towards yours with a view to help you."
|
||||
"waite": "He symbolizes established strength, enterprise, effort, trade, commerce, discovery; those are his ships, bearing his merchandise, which are sailing over the sea. The card also signifies able co-operation in business, as if the successful merchant prince were looking from his side towards yours with a view to help you.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -795,7 +964,7 @@ class CardDetailsRegistry:
|
||||
"Four of Wands": {
|
||||
"explanation": {
|
||||
"summary": "They are for once almost on the surface--country life, haven of refuge, a species of domestic harvest-home, repose, concord, harmony, prosperity, peace, and the perfected work of these.",
|
||||
"waite": "They are for once almost on the surface--country life, haven of refuge, a species of domestic harvest-home, repose, concord, harmony, prosperity, peace, and the perfected work of these."
|
||||
"waite": "They are for once almost on the surface--country life, haven of refuge, a species of domestic harvest-home, repose, concord, harmony, prosperity, peace, and the perfected work of these.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -805,7 +974,7 @@ class CardDetailsRegistry:
|
||||
"Five of Wands": {
|
||||
"explanation": {
|
||||
"summary": "Imitation, as, for example, sham fight, but also the strenuous competition and struggle of the search after riches and fortune.",
|
||||
"waite": "Imitation, as, for example, sham fight, but also the strenuous competition and struggle of the search after riches and fortune. In this sense it connects with the battle of life. Hence some attributions say that it is a card of gold, gain, opulence."
|
||||
"waite": "Imitation, as, for example, sham fight, but also the strenuous competition and struggle of the search after riches and fortune. In this sense it connects with the battle of life. Hence some attributions say that it is a card of gold, gain, opulence.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -815,7 +984,7 @@ class CardDetailsRegistry:
|
||||
"Six of Wands": {
|
||||
"explanation": {
|
||||
"summary": "The card has been so designed that it can cover several significations; on the surface, it is a victor triumphing, but it is also great news, such as might be carried in state by the King's courier; it is expectation crowned with its own desire, the crown of hope, and so forth.",
|
||||
"waite": "The card has been so designed that it can cover several significations; on the surface, it is a victor triumphing, but it is also great news, such as might be carried in state by the King's courier; it is expectation crowned with its own desire, the crown of hope, and so forth."
|
||||
"waite": "The card has been so designed that it can cover several significations; on the surface, it is a victor triumphing, but it is also great news, such as might be carried in state by the King's courier; it is expectation crowned with its own desire, the crown of hope, and so forth.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -825,7 +994,7 @@ class CardDetailsRegistry:
|
||||
"Seven of Wands": {
|
||||
"explanation": {
|
||||
"summary": "It is a card of valour, for, on the surface, six are attacking one, who has, however, the vantage position.",
|
||||
"waite": "It is a card of valour, for, on the surface, six are attacking one, who has, however, the vantage position. On the intellectual plane, it signifies discussion, wordy strife; in business--negotiations, war of trade, barter, competition. It is further a card of success, for the combatant is on the top and his enemies may be unable to reach him."
|
||||
"waite": "It is a card of valour, for, on the surface, six are attacking one, who has, however, the vantage position. On the intellectual plane, it signifies discussion, wordy strife; in business--negotiations, war of trade, barter, competition. It is further a card of success, for the combatant is on the top and his enemies may be unable to reach him.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -835,7 +1004,7 @@ class CardDetailsRegistry:
|
||||
"Eight of Wands": {
|
||||
"explanation": {
|
||||
"summary": "Activity in undertakings, the path of such activity, swiftness, as that of an express messenger; great haste, great hope, speed towards an end which promises assured felicity; generally, that which is on the move; also the arrows of love.",
|
||||
"waite": "Activity in undertakings, the path of such activity, swiftness, as that of an express messenger; great haste, great hope, speed towards an end which promises assured felicity; generally, that which is on the move; also the arrows of love."
|
||||
"waite": "Activity in undertakings, the path of such activity, swiftness, as that of an express messenger; great haste, great hope, speed towards an end which promises assured felicity; generally, that which is on the move; also the arrows of love.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -845,7 +1014,7 @@ class CardDetailsRegistry:
|
||||
"Nine of Wands": {
|
||||
"explanation": {
|
||||
"summary": "The card signifies strength in opposition.",
|
||||
"waite": "The card signifies strength in opposition. If attacked, the person will meet an onslaught boldly; and his build shews, that he may prove a formidable antagonist. With this main significance there are all its possible adjuncts--delay, suspension, adjournment."
|
||||
"waite": "The card signifies strength in opposition. If attacked, the person will meet an onslaught boldly; and his build shews, that he may prove a formidable antagonist. With this main significance there are all its possible adjuncts--delay, suspension, adjournment.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -855,7 +1024,7 @@ class CardDetailsRegistry:
|
||||
"Ten of Wands": {
|
||||
"explanation": {
|
||||
"summary": "A card of many significances, and some of the readings cannot be harmonized.",
|
||||
"waite": "A card of many significances, and some of the readings cannot be harmonized. I set aside that which connects it with honour and good faith. The chief meaning is oppression simply, but it is also fortune, gain, any kind of success, and then it is the oppression of these things. It is also a card of false-seeming, disguise, perfidy. The place which the figure is approaching may suffer from the rods that he carries. Success is stultified if the Nine of Swords follows, and if it is a question of a lawsuit, there will be certain loss."
|
||||
"waite": "A card of many significances, and some of the readings cannot be harmonized. I set aside that which connects it with honour and good faith. The chief meaning is oppression simply, but it is also fortune, gain, any kind of success, and then it is the oppression of these things. It is also a card of false-seeming, disguise, perfidy. The place which the figure is approaching may suffer from the rods that he carries. Success is stultified if the Nine of Swords follows, and if it is a question of a lawsuit, there will be certain loss.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -865,7 +1034,7 @@ class CardDetailsRegistry:
|
||||
"Page of Wands": {
|
||||
"explanation": {
|
||||
"summary": "Dark young man, faithful, a lover, an envoy, a postman.",
|
||||
"waite": "Dark young man, faithful, a lover, an envoy, a postman. Beside a man, he will bear favourable testimony concerning him. A dangerous rival, if followed by the Page of Cups. Has the chief qualities of his suit. He may signify family intelligence."
|
||||
"waite": "Dark young man, faithful, a lover, an envoy, a postman. Beside a man, he will bear favourable testimony concerning him. A dangerous rival, if followed by the Page of Cups. Has the chief qualities of his suit. He may signify family intelligence.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -875,7 +1044,7 @@ class CardDetailsRegistry:
|
||||
"Knight of Wands": {
|
||||
"explanation": {
|
||||
"summary": "Departure, absence, flight, emigration.",
|
||||
"waite": "Departure, absence, flight, emigration. A dark young man, friendly. Change of residence."
|
||||
"waite": "Departure, absence, flight, emigration. A dark young man, friendly. Change of residence.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -885,7 +1054,7 @@ class CardDetailsRegistry:
|
||||
"Queen of Wands": {
|
||||
"explanation": {
|
||||
"summary": "A dark woman, countrywoman, friendly, chaste, loving, honourable.",
|
||||
"waite": "A dark woman, countrywoman, friendly, chaste, loving, honourable. If the card beside her signifies a man, she is well disposed towards him; if a woman, she is interested in the Querent. Also, love of money, or a certain success in business."
|
||||
"waite": "A dark woman, countrywoman, friendly, chaste, loving, honourable. If the card beside her signifies a man, she is well disposed towards him; if a woman, she is interested in the Querent. Also, love of money, or a certain success in business.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -895,7 +1064,7 @@ class CardDetailsRegistry:
|
||||
"King of Wands": {
|
||||
"explanation": {
|
||||
"summary": "Dark man, friendly, countryman, generally married, honest and conscientious.",
|
||||
"waite": "Dark man, friendly, countryman, generally married, honest and conscientious. The card always signifies honesty, and may mean news concerning an unexpected heritage to fall in before very long."
|
||||
"waite": "Dark man, friendly, countryman, generally married, honest and conscientious. The card always signifies honesty, and may mean news concerning an unexpected heritage to fall in before very long.",
|
||||
},
|
||||
"interpretation": "",
|
||||
"keywords": [],
|
||||
@@ -942,11 +1111,12 @@ class CardDetailsRegistry:
|
||||
Dictionary of card details for that suit
|
||||
"""
|
||||
return {
|
||||
name: details for name, details in self._details.items()
|
||||
name: details
|
||||
for name, details in self._details.items()
|
||||
if suit_name.lower() in name.lower()
|
||||
}
|
||||
|
||||
def _get_registry_key_for_card(self, card: 'Card') -> Optional[str]:
|
||||
def _get_registry_key_for_card(self, card: "Card") -> Optional[str]:
|
||||
"""
|
||||
Get the registry key for a card based on deck position (1-78).
|
||||
|
||||
@@ -961,7 +1131,7 @@ class CardDetailsRegistry:
|
||||
"""
|
||||
return self._position_map.get(card.number)
|
||||
|
||||
def load_into_card(self, card: 'Card') -> bool:
|
||||
def load_into_card(self, card: "Card") -> bool:
|
||||
"""
|
||||
Load details from registry into a Card object using its position.
|
||||
|
||||
|
||||
@@ -14,10 +14,9 @@ Usage:
|
||||
print(f"Loaded {count} card images")
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple, TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tarot.deck import Card, Deck
|
||||
@@ -27,10 +26,12 @@ class ImageDeckLoader:
|
||||
"""Loader for matching Tarot card images to deck cards."""
|
||||
|
||||
# Supported image extensions
|
||||
SUPPORTED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'}
|
||||
SUPPORTED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"}
|
||||
|
||||
# Regex patterns for file matching
|
||||
NUMBERED_PATTERN = re.compile(r'^(\d+)(?:_(.+))?\.(?:jpg|jpeg|png|gif|bmp|webp)$', re.IGNORECASE)
|
||||
NUMBERED_PATTERN = re.compile(
|
||||
r"^(\d+)(?:_(.+))?\.(?:jpg|jpeg|png|gif|bmp|webp)$", re.IGNORECASE
|
||||
)
|
||||
|
||||
def __init__(self, deck_folder: str) -> None:
|
||||
"""
|
||||
@@ -51,15 +52,17 @@ class ImageDeckLoader:
|
||||
raise ValueError(f"Deck path is not a directory: {deck_folder}")
|
||||
|
||||
self.image_files = self._scan_folder()
|
||||
self.card_mapping: Dict[int, Tuple[str, bool]] = {} # card_number -> (path, has_custom_name)
|
||||
self.card_mapping: Dict[int, Tuple[str, bool]] = (
|
||||
{}
|
||||
) # card_number -> (path, has_custom_name)
|
||||
self._build_mapping()
|
||||
|
||||
def _scan_folder(self) -> List[Path]:
|
||||
"""Scan folder for image files."""
|
||||
images = []
|
||||
for ext in self.SUPPORTED_EXTENSIONS:
|
||||
images.extend(self.deck_folder.glob(f'*{ext}'))
|
||||
images.extend(self.deck_folder.glob(f'*{ext.upper()}'))
|
||||
images.extend(self.deck_folder.glob(f"*{ext}"))
|
||||
images.extend(self.deck_folder.glob(f"*{ext.upper()}"))
|
||||
|
||||
# Sort by filename for consistent ordering
|
||||
return sorted(images)
|
||||
@@ -124,10 +127,10 @@ class ImageDeckLoader:
|
||||
normalized = name.lower()
|
||||
|
||||
# Replace special characters with spaces
|
||||
normalized = re.sub(r'[^\w\s]', ' ', normalized)
|
||||
normalized = re.sub(r"[^\w\s]", " ", normalized)
|
||||
|
||||
# Collapse multiple spaces
|
||||
normalized = re.sub(r'\s+', ' ', normalized).strip()
|
||||
normalized = re.sub(r"\s+", " ", normalized).strip()
|
||||
|
||||
return normalized
|
||||
|
||||
@@ -174,7 +177,7 @@ class ImageDeckLoader:
|
||||
|
||||
return best_match
|
||||
|
||||
def get_image_path(self, card: 'Card') -> Optional[str]:
|
||||
def get_image_path(self, card: "Card") -> Optional[str]:
|
||||
"""
|
||||
Get the image path for a specific card.
|
||||
|
||||
@@ -247,14 +250,14 @@ class ImageDeckLoader:
|
||||
parsed_num, _, _ = self._parse_filename(image_path.name)
|
||||
if parsed_num == card_number and custom_name:
|
||||
# Convert underscore-separated name to title case
|
||||
name_words = custom_name.split('_')
|
||||
return ' '.join(word.capitalize() for word in name_words)
|
||||
name_words = custom_name.split("_")
|
||||
return " ".join(word.capitalize() for word in name_words)
|
||||
|
||||
return None
|
||||
|
||||
def load_into_deck(self, deck: 'Deck',
|
||||
override_names: bool = True,
|
||||
verbose: bool = False) -> int:
|
||||
def load_into_deck(
|
||||
self, deck: "Deck", override_names: bool = True, verbose: bool = False
|
||||
) -> int:
|
||||
"""
|
||||
Load image paths into all cards in a deck.
|
||||
|
||||
@@ -305,20 +308,19 @@ class ImageDeckLoader:
|
||||
custom_named = sum(1 for _, has_custom in self.card_mapping.values() if has_custom)
|
||||
|
||||
return {
|
||||
'deck_folder': str(self.deck_folder),
|
||||
'total_image_files': total_images,
|
||||
'total_image_filenames': len(set(f.name for f in self.image_files)),
|
||||
'mapped_card_numbers': mapped_cards,
|
||||
'cards_with_custom_names': custom_named,
|
||||
'cards_with_generic_numbers': mapped_cards - custom_named,
|
||||
'image_extensions_found': list(set(f.suffix.lower() for f in self.image_files)),
|
||||
"deck_folder": str(self.deck_folder),
|
||||
"total_image_files": total_images,
|
||||
"total_image_filenames": len(set(f.name for f in self.image_files)),
|
||||
"mapped_card_numbers": mapped_cards,
|
||||
"cards_with_custom_names": custom_named,
|
||||
"cards_with_generic_numbers": mapped_cards - custom_named,
|
||||
"image_extensions_found": list(set(f.suffix.lower() for f in self.image_files)),
|
||||
}
|
||||
|
||||
|
||||
def load_deck_images(deck: 'Deck',
|
||||
deck_folder: str,
|
||||
override_names: bool = True,
|
||||
verbose: bool = False) -> int:
|
||||
def load_deck_images(
|
||||
deck: "Deck", deck_folder: str, override_names: bool = True, verbose: bool = False
|
||||
) -> int:
|
||||
"""
|
||||
Convenience function to load deck images.
|
||||
|
||||
|
||||
@@ -24,10 +24,7 @@ if TYPE_CHECKING:
|
||||
from tarot.deck import Deck
|
||||
|
||||
|
||||
def load_card_details(
|
||||
card: 'Card',
|
||||
registry: Optional['CardDetailsRegistry'] = None
|
||||
) -> bool:
|
||||
def load_card_details(card: "Card", registry: Optional["CardDetailsRegistry"] = None) -> bool:
|
||||
"""
|
||||
Load details for a single card from the registry.
|
||||
|
||||
@@ -49,15 +46,14 @@ def load_card_details(
|
||||
"""
|
||||
if registry is None:
|
||||
from tarot.card.details import CardDetailsRegistry
|
||||
|
||||
registry = CardDetailsRegistry()
|
||||
|
||||
return registry.load_into_card(card)
|
||||
|
||||
|
||||
def load_deck_details(
|
||||
deck: 'Deck',
|
||||
registry: Optional['CardDetailsRegistry'] = None,
|
||||
verbose: bool = False
|
||||
deck: "Deck", registry: Optional["CardDetailsRegistry"] = None, verbose: bool = False
|
||||
) -> int:
|
||||
"""
|
||||
Load details for all cards in a deck.
|
||||
@@ -78,6 +74,7 @@ def load_deck_details(
|
||||
"""
|
||||
if registry is None:
|
||||
from tarot.card.details import CardDetailsRegistry
|
||||
|
||||
registry = CardDetailsRegistry()
|
||||
|
||||
loaded_count = 0
|
||||
@@ -102,10 +99,7 @@ def load_deck_details(
|
||||
return loaded_count
|
||||
|
||||
|
||||
def get_cards_by_suit(
|
||||
deck: 'Deck',
|
||||
suit_name: str
|
||||
) -> List['Card']:
|
||||
def get_cards_by_suit(deck: "Deck", suit_name: str) -> List["Card"]:
|
||||
"""
|
||||
Get all cards from a specific suit in the deck.
|
||||
|
||||
@@ -124,19 +118,19 @@ def get_cards_by_suit(
|
||||
>>> print(len(swords)) # Should be 14
|
||||
14
|
||||
"""
|
||||
if hasattr(deck, 'suit') and callable(deck.suit):
|
||||
if hasattr(deck, "suit") and callable(deck.suit):
|
||||
# Deck has a suit method, use it
|
||||
return deck.suit(suit_name)
|
||||
|
||||
# Fallback: filter cards manually
|
||||
return [card for card in deck.cards if hasattr(card, 'suit') and
|
||||
card.suit and card.suit.name == suit_name]
|
||||
return [
|
||||
card
|
||||
for card in deck.cards
|
||||
if hasattr(card, "suit") and card.suit and card.suit.name == suit_name
|
||||
]
|
||||
|
||||
|
||||
def filter_cards_by_keywords(
|
||||
cards: List['Card'],
|
||||
keyword: str
|
||||
) -> List['Card']:
|
||||
def filter_cards_by_keywords(cards: List["Card"], keyword: str) -> List["Card"]:
|
||||
"""
|
||||
Filter a list of cards by keyword.
|
||||
|
||||
@@ -155,13 +149,15 @@ def filter_cards_by_keywords(
|
||||
"""
|
||||
keyword_lower = keyword.lower()
|
||||
return [
|
||||
card for card in cards
|
||||
if hasattr(card, 'keywords') and card.keywords and
|
||||
any(keyword_lower in kw.lower() for kw in card.keywords)
|
||||
card
|
||||
for card in cards
|
||||
if hasattr(card, "keywords")
|
||||
and card.keywords
|
||||
and any(keyword_lower in kw.lower() for kw in card.keywords)
|
||||
]
|
||||
|
||||
|
||||
def print_card_details(card: 'Card', include_reversed: bool = False) -> None:
|
||||
def print_card_details(card: "Card", include_reversed: bool = False) -> None:
|
||||
"""
|
||||
Pretty print card details to console.
|
||||
|
||||
@@ -181,24 +177,24 @@ def print_card_details(card: 'Card', include_reversed: bool = False) -> None:
|
||||
|
||||
# Define attributes to print with their formatting
|
||||
attributes = {
|
||||
'explanation': ('Explanation', False),
|
||||
'interpretation': ('Interpretation', False),
|
||||
'guidance': ('Guidance', False),
|
||||
"explanation": ("Explanation", False),
|
||||
"interpretation": ("Interpretation", False),
|
||||
"guidance": ("Guidance", False),
|
||||
}
|
||||
|
||||
# Add reversed attributes only if requested
|
||||
if include_reversed:
|
||||
attributes['reversed_interpretation'] = ('Reversed Interpretation', False)
|
||||
attributes["reversed_interpretation"] = ("Reversed Interpretation", False)
|
||||
|
||||
# List attributes (joined with commas)
|
||||
list_attributes = {
|
||||
'keywords': 'Keywords',
|
||||
'reversed_keywords': ('Reversed Keywords', include_reversed),
|
||||
"keywords": "Keywords",
|
||||
"reversed_keywords": ("Reversed Keywords", include_reversed),
|
||||
}
|
||||
|
||||
# Numeric attributes
|
||||
numeric_attributes = {
|
||||
'numerology': 'Numerology',
|
||||
"numerology": "Numerology",
|
||||
}
|
||||
|
||||
# Print text attributes
|
||||
@@ -206,7 +202,7 @@ def print_card_details(card: 'Card', include_reversed: bool = False) -> None:
|
||||
if hasattr(card, attr_name):
|
||||
value = getattr(card, attr_name)
|
||||
if value:
|
||||
if attr_name == 'explanation' and isinstance(value, dict):
|
||||
if attr_name == "explanation" and isinstance(value, dict):
|
||||
print(f"\n{display_name}:")
|
||||
if "summary" in value:
|
||||
print(f"Summary: {value['summary']}")
|
||||
@@ -244,8 +240,7 @@ def print_card_details(card: 'Card', include_reversed: bool = False) -> None:
|
||||
|
||||
|
||||
def get_card_info(
|
||||
card_name: str,
|
||||
registry: Optional['CardDetailsRegistry'] = None
|
||||
card_name: str, registry: Optional["CardDetailsRegistry"] = None
|
||||
) -> Optional[dict]:
|
||||
"""
|
||||
Get card information by card name.
|
||||
@@ -265,6 +260,7 @@ def get_card_info(
|
||||
"""
|
||||
if registry is None:
|
||||
from tarot.card.details import CardDetailsRegistry
|
||||
|
||||
registry = CardDetailsRegistry()
|
||||
|
||||
return registry.get(card_name)
|
||||
|
||||
@@ -18,9 +18,9 @@ Usage:
|
||||
reading = draw_spread(spread) # Returns list of (position, card) tuples
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Optional, TYPE_CHECKING
|
||||
from dataclasses import dataclass
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tarot.card import Card
|
||||
@@ -29,6 +29,7 @@ if TYPE_CHECKING:
|
||||
@dataclass
|
||||
class SpreadPosition:
|
||||
"""Represents a position in a Tarot spread."""
|
||||
|
||||
number: int
|
||||
name: str
|
||||
meaning: str
|
||||
@@ -44,8 +45,9 @@ class SpreadPosition:
|
||||
@dataclass
|
||||
class DrawnCard:
|
||||
"""Represents a card drawn for a spread position."""
|
||||
|
||||
position: SpreadPosition
|
||||
card: 'Card'
|
||||
card: "Card"
|
||||
is_reversed: bool
|
||||
|
||||
def __str__(self) -> str:
|
||||
@@ -54,9 +56,11 @@ class DrawnCard:
|
||||
if self.is_reversed:
|
||||
card_name += " (Reversed)"
|
||||
|
||||
return f"{self.position.number}. {self.position.name}\n" \
|
||||
f" └─ {card_name}\n" \
|
||||
f" └─ Position: {self.position.meaning}"
|
||||
return (
|
||||
f"{self.position.number}. {self.position.name}\n"
|
||||
f" └─ {card_name}\n"
|
||||
f" └─ Position: {self.position.meaning}"
|
||||
)
|
||||
|
||||
|
||||
class Spread:
|
||||
@@ -64,97 +68,102 @@ class Spread:
|
||||
|
||||
# Define all available spreads
|
||||
SPREADS: Dict[str, Dict] = {
|
||||
'three card': {
|
||||
'name': '3-Card Spread',
|
||||
'description': 'Simple 3-card spread for past, present, future or situation, action, outcome',
|
||||
'positions': [
|
||||
SpreadPosition(1, 'First Position', 'Past, Foundation, or Situation'),
|
||||
SpreadPosition(2, 'Second Position', 'Present, Action, or Influence'),
|
||||
SpreadPosition(3, 'Third Position', 'Future, Outcome, or Advice'),
|
||||
]
|
||||
"three card": {
|
||||
"name": "3-Card Spread",
|
||||
"description": (
|
||||
"Simple 3-card spread for past, present, future "
|
||||
"or situation, action, outcome"
|
||||
),
|
||||
"positions": [
|
||||
SpreadPosition(1, "First Position", "Past, Foundation, or Situation"),
|
||||
SpreadPosition(2, "Second Position", "Present, Action, or Influence"),
|
||||
SpreadPosition(3, "Third Position", "Future, Outcome, or Advice"),
|
||||
],
|
||||
},
|
||||
'golden dawn': {
|
||||
'name': 'Golden Dawn 3-Card',
|
||||
'description': 'Three card spread used in Golden Dawn tradition',
|
||||
'positions': [
|
||||
SpreadPosition(1, 'Supernal Triangle', 'Spiritual/Divine aspect'),
|
||||
SpreadPosition(2, 'Pillar of Severity', 'Challenging/Active force'),
|
||||
SpreadPosition(3, 'Pillar of Mercy', 'Supportive/Passive force'),
|
||||
]
|
||||
"golden dawn": {
|
||||
"name": "Golden Dawn 3-Card",
|
||||
"description": "Three card spread used in Golden Dawn tradition",
|
||||
"positions": [
|
||||
SpreadPosition(1, "Supernal Triangle", "Spiritual/Divine aspect"),
|
||||
SpreadPosition(2, "Pillar of Severity", "Challenging/Active force"),
|
||||
SpreadPosition(3, "Pillar of Mercy", "Supportive/Passive force"),
|
||||
],
|
||||
},
|
||||
'celtic cross': {
|
||||
'name': 'Celtic Cross',
|
||||
'description': 'Classic 10-card spread for in-depth reading',
|
||||
'positions': [
|
||||
SpreadPosition(1, 'The Significator', 'The main situation or person'),
|
||||
SpreadPosition(2, 'The Cross', 'The challenge or heart of the matter'),
|
||||
SpreadPosition(3, 'Crowning Influence', 'Conscious hopes/ideals'),
|
||||
SpreadPosition(4, 'Beneath the Cross', 'Unconscious or hidden aspects'),
|
||||
SpreadPosition(5, 'Behind', 'Past influences'),
|
||||
SpreadPosition(6, 'Before', 'Future influences'),
|
||||
SpreadPosition(7, 'Self/Attitude', 'How the querent sees themselves'),
|
||||
SpreadPosition(8, 'Others/Environment', 'External factors/opinions'),
|
||||
SpreadPosition(9, 'Hopes and Fears', 'What the querent hopes for or fears'),
|
||||
SpreadPosition(10, 'Outcome', 'Final outcome or resolution'),
|
||||
]
|
||||
"celtic cross": {
|
||||
"name": "Celtic Cross",
|
||||
"description": "Classic 10-card spread for in-depth reading",
|
||||
"positions": [
|
||||
SpreadPosition(1, "The Significator", "The main situation or person"),
|
||||
SpreadPosition(2, "The Cross", "The challenge or heart of the matter"),
|
||||
SpreadPosition(3, "Crowning Influence", "Conscious hopes/ideals"),
|
||||
SpreadPosition(4, "Beneath the Cross", "Unconscious or hidden aspects"),
|
||||
SpreadPosition(5, "Behind", "Past influences"),
|
||||
SpreadPosition(6, "Before", "Future influences"),
|
||||
SpreadPosition(7, "Self/Attitude", "How the querent sees themselves"),
|
||||
SpreadPosition(8, "Others/Environment", "External factors/opinions"),
|
||||
SpreadPosition(9, "Hopes and Fears", "What the querent hopes for or fears"),
|
||||
SpreadPosition(10, "Outcome", "Final outcome or resolution"),
|
||||
],
|
||||
},
|
||||
'horseshoe': {
|
||||
'name': 'Horseshoe',
|
||||
'description': '7-card spread in horseshoe formation for past, present, future insight',
|
||||
'positions': [
|
||||
SpreadPosition(1, 'Distant Past', 'Ancient influences and foundations'),
|
||||
SpreadPosition(2, 'Recent Past', 'Recent events and circumstances'),
|
||||
SpreadPosition(3, 'Present Situation', 'Current state of affairs'),
|
||||
SpreadPosition(4, 'Immediate Future', 'Near-term developments'),
|
||||
SpreadPosition(5, 'Distant Future', 'Long-term outcome'),
|
||||
SpreadPosition(6, 'Inner Influence', 'Self/thoughts/emotions'),
|
||||
SpreadPosition(7, 'Outer Influence', 'External forces and environment'),
|
||||
]
|
||||
"horseshoe": {
|
||||
"name": "Horseshoe",
|
||||
"description": "7-card spread in horseshoe formation for past, present, future insight",
|
||||
"positions": [
|
||||
SpreadPosition(1, "Distant Past", "Ancient influences and foundations"),
|
||||
SpreadPosition(2, "Recent Past", "Recent events and circumstances"),
|
||||
SpreadPosition(3, "Present Situation", "Current state of affairs"),
|
||||
SpreadPosition(4, "Immediate Future", "Near-term developments"),
|
||||
SpreadPosition(5, "Distant Future", "Long-term outcome"),
|
||||
SpreadPosition(6, "Inner Influence", "Self/thoughts/emotions"),
|
||||
SpreadPosition(7, "Outer Influence", "External forces and environment"),
|
||||
],
|
||||
},
|
||||
'pentagram': {
|
||||
'name': 'Pentagram',
|
||||
'description': '5-card spread based on Earth element pentagram',
|
||||
'positions': [
|
||||
SpreadPosition(1, 'Spirit', 'Core essence or spiritual truth'),
|
||||
SpreadPosition(2, 'Fire', 'Action and willpower'),
|
||||
SpreadPosition(3, 'Water', 'Emotions and intuition'),
|
||||
SpreadPosition(4, 'Air', 'Intellect and communication'),
|
||||
SpreadPosition(5, 'Earth', 'Physical manifestation and grounding'),
|
||||
]
|
||||
"pentagram": {
|
||||
"name": "Pentagram",
|
||||
"description": "5-card spread based on Earth element pentagram",
|
||||
"positions": [
|
||||
SpreadPosition(1, "Spirit", "Core essence or spiritual truth"),
|
||||
SpreadPosition(2, "Fire", "Action and willpower"),
|
||||
SpreadPosition(3, "Water", "Emotions and intuition"),
|
||||
SpreadPosition(4, "Air", "Intellect and communication"),
|
||||
SpreadPosition(5, "Earth", "Physical manifestation and grounding"),
|
||||
],
|
||||
},
|
||||
'tree of life': {
|
||||
'name': 'Tree of Life',
|
||||
'description': '10-card spread mapping Sephiroth on the Tree of Life',
|
||||
'positions': [
|
||||
SpreadPosition(1, 'Kether (Crown)', 'Divine will and unity'),
|
||||
SpreadPosition(2, 'Chokmah (Wisdom)', 'Creative force and impulse'),
|
||||
SpreadPosition(3, 'Binah (Understanding)', 'Form and structure'),
|
||||
SpreadPosition(4, 'Chesed (Mercy)', 'Expansion and abundance'),
|
||||
SpreadPosition(5, 'Gevurah (Severity)', 'Reduction and discipline'),
|
||||
SpreadPosition(6, 'Tiphareth (Beauty)', 'Core self and integration'),
|
||||
SpreadPosition(7, 'Netzach (Victory)', 'Desire and passion'),
|
||||
SpreadPosition(8, 'Hod (Splendor)', 'Intellect and communication'),
|
||||
SpreadPosition(9, 'Yesod (Foundation)', 'Subconscious and dreams'),
|
||||
SpreadPosition(10, 'Malkuth (Kingdom)', 'Manifestation and physical reality'),
|
||||
]
|
||||
"tree of life": {
|
||||
"name": "Tree of Life",
|
||||
"description": "10-card spread mapping Sephiroth on the Tree of Life",
|
||||
"positions": [
|
||||
SpreadPosition(1, "Kether (Crown)", "Divine will and unity"),
|
||||
SpreadPosition(2, "Chokmah (Wisdom)", "Creative force and impulse"),
|
||||
SpreadPosition(3, "Binah (Understanding)", "Form and structure"),
|
||||
SpreadPosition(4, "Chesed (Mercy)", "Expansion and abundance"),
|
||||
SpreadPosition(5, "Gevurah (Severity)", "Reduction and discipline"),
|
||||
SpreadPosition(6, "Tiphareth (Beauty)", "Core self and integration"),
|
||||
SpreadPosition(7, "Netzach (Victory)", "Desire and passion"),
|
||||
SpreadPosition(8, "Hod (Splendor)", "Intellect and communication"),
|
||||
SpreadPosition(9, "Yesod (Foundation)", "Subconscious and dreams"),
|
||||
SpreadPosition(10, "Malkuth (Kingdom)", "Manifestation and physical reality"),
|
||||
],
|
||||
},
|
||||
'relationship': {
|
||||
'name': 'Relationship',
|
||||
'description': '5-card spread for relationship insight',
|
||||
'positions': [
|
||||
SpreadPosition(1, 'You', 'Your position, feelings, or role'),
|
||||
SpreadPosition(2, 'Them', 'Their position, feelings, or perspective'),
|
||||
SpreadPosition(3, 'The Relationship', 'The dynamic and connection'),
|
||||
SpreadPosition(4, 'Challenge', 'Current challenge or friction point'),
|
||||
SpreadPosition(5, 'Outcome', 'Where the relationship is heading'),
|
||||
]
|
||||
"relationship": {
|
||||
"name": "Relationship",
|
||||
"description": "5-card spread for relationship insight",
|
||||
"positions": [
|
||||
SpreadPosition(1, "You", "Your position, feelings, or role"),
|
||||
SpreadPosition(2, "Them", "Their position, feelings, or perspective"),
|
||||
SpreadPosition(3, "The Relationship", "The dynamic and connection"),
|
||||
SpreadPosition(4, "Challenge", "Current challenge or friction point"),
|
||||
SpreadPosition(5, "Outcome", "Where the relationship is heading"),
|
||||
],
|
||||
},
|
||||
'yes or no': {
|
||||
'name': 'Yes or No',
|
||||
'description': '1-card spread for simple yes/no answers',
|
||||
'positions': [
|
||||
SpreadPosition(1, 'Answer', 'Major Arcana = Yes, Minor Arcana = No, Court Cards = Maybe'),
|
||||
]
|
||||
"yes or no": {
|
||||
"name": "Yes or No",
|
||||
"description": "1-card spread for simple yes/no answers",
|
||||
"positions": [
|
||||
SpreadPosition(
|
||||
1, "Answer", "Major Arcana = Yes, Minor Arcana = No, Court Cards = Maybe"
|
||||
),
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -169,43 +178,41 @@ class Spread:
|
||||
ValueError: If spread name not found
|
||||
"""
|
||||
# Normalize name (case-insensitive, allow underscores or spaces)
|
||||
normalized_name = spread_name.lower().replace('_', ' ')
|
||||
normalized_name = spread_name.lower().replace("_", " ")
|
||||
|
||||
# Find matching spread
|
||||
spread_data = None
|
||||
for key, data in self.SPREADS.items():
|
||||
if key == normalized_name or data['name'].lower() == normalized_name:
|
||||
if key == normalized_name or data["name"].lower() == normalized_name:
|
||||
spread_data = data
|
||||
break
|
||||
|
||||
if not spread_data:
|
||||
available = ', '.join(f"'{k}'" for k in self.SPREADS.keys())
|
||||
raise ValueError(
|
||||
f"Spread '{spread_name}' not found. Available spreads: {available}"
|
||||
)
|
||||
available = ", ".join(f"'{k}'" for k in self.SPREADS.keys())
|
||||
raise ValueError(f"Spread '{spread_name}' not found. Available spreads: {available}")
|
||||
|
||||
self.name = spread_data['name']
|
||||
self.description = spread_data['description']
|
||||
self.positions: List[SpreadPosition] = spread_data['positions']
|
||||
self.name = spread_data["name"]
|
||||
self.description = spread_data["description"]
|
||||
self.positions: List[SpreadPosition] = spread_data["positions"]
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Return formatted spread information."""
|
||||
lines = [
|
||||
f"═══════════════════════════════════════════",
|
||||
"═══════════════════════════════════════════",
|
||||
f" {self.name}",
|
||||
f"═══════════════════════════════════════════",
|
||||
f"",
|
||||
"═══════════════════════════════════════════",
|
||||
"",
|
||||
f"{self.description}",
|
||||
f"",
|
||||
"",
|
||||
f"Positions ({len(self.positions)} cards):",
|
||||
f"",
|
||||
"",
|
||||
]
|
||||
|
||||
for pos in self.positions:
|
||||
lines.append(f" {pos}")
|
||||
|
||||
lines.append(f"")
|
||||
lines.append(f"═══════════════════════════════════════════")
|
||||
lines.append("")
|
||||
lines.append("═══════════════════════════════════════════")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
@@ -215,11 +222,7 @@ class Spread:
|
||||
@classmethod
|
||||
def available_spreads(cls) -> str:
|
||||
"""Return list of all available spreads."""
|
||||
lines = [
|
||||
"Available Tarot Spreads:",
|
||||
"═" * 50,
|
||||
""
|
||||
]
|
||||
lines = ["Available Tarot Spreads:", "═" * 50, ""]
|
||||
|
||||
for key, data in cls.SPREADS.items():
|
||||
lines.append(f" • {data['name']}")
|
||||
@@ -254,20 +257,18 @@ def draw_spread(spread: Spread, deck: Optional[List] = None) -> List[DrawnCard]:
|
||||
Raises:
|
||||
ValueError: If spread has more positions than cards in the deck
|
||||
"""
|
||||
import random
|
||||
|
||||
# Load deck if not provided
|
||||
if deck is None:
|
||||
from tarot.deck import Deck
|
||||
|
||||
deck_instance = Deck()
|
||||
deck = deck_instance.cards
|
||||
|
||||
# Validate that we have enough cards to draw from without duplicates
|
||||
num_positions = len(spread.positions)
|
||||
if num_positions > len(deck):
|
||||
raise ValueError(
|
||||
f"Cannot draw {num_positions} unique cards from deck of {len(deck)} cards"
|
||||
)
|
||||
raise ValueError(f"Cannot draw {num_positions} unique cards from deck of {len(deck)} cards")
|
||||
|
||||
# Draw unique cards using random.sample (no replacements)
|
||||
drawn_deck = random.sample(deck, num_positions)
|
||||
@@ -298,14 +299,14 @@ class SpreadReading:
|
||||
def __str__(self) -> str:
|
||||
"""Return formatted reading with all cards and interpretations."""
|
||||
lines = [
|
||||
f"╔═══════════════════════════════════════════╗",
|
||||
"╔═══════════════════════════════════════════╗",
|
||||
f"║ {self.spread.name:40}║",
|
||||
f"╚═══════════════════════════════════════════╝",
|
||||
f"",
|
||||
"╚═══════════════════════════════════════════╝",
|
||||
"",
|
||||
f"{self.spread.description}",
|
||||
f"",
|
||||
f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
||||
f"",
|
||||
"",
|
||||
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
||||
"",
|
||||
]
|
||||
|
||||
for drawn in self.drawn_cards:
|
||||
@@ -319,16 +320,16 @@ class SpreadReading:
|
||||
lines.append(f" Meaning: {drawn.position.meaning}")
|
||||
|
||||
# Add card details if available
|
||||
if hasattr(card, 'number'):
|
||||
if hasattr(card, "number"):
|
||||
lines.append(f" Card #: {card.number}")
|
||||
if hasattr(card, 'arcana'):
|
||||
if hasattr(card, "arcana"):
|
||||
lines.append(f" Arcana: {card.arcana}")
|
||||
if hasattr(card, 'suit') and card.suit:
|
||||
if hasattr(card, "suit") and card.suit:
|
||||
lines.append(f" Suit: {card.suit.name}")
|
||||
|
||||
lines.append("")
|
||||
|
||||
lines.append(f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||
lines.append("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
@@ -6,16 +6,16 @@ MinorCard, and related classes for representing individual cards.
|
||||
"""
|
||||
|
||||
from .deck import (
|
||||
DLT,
|
||||
AceCard,
|
||||
Card,
|
||||
CardQuery,
|
||||
CourtCard,
|
||||
Deck,
|
||||
MajorCard,
|
||||
MinorCard,
|
||||
PipCard,
|
||||
AceCard,
|
||||
CourtCard,
|
||||
CardQuery,
|
||||
TemporalQuery,
|
||||
DLT,
|
||||
Deck,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
|
||||
@@ -5,19 +5,27 @@ This module defines the Deck class for managing Tarot cards and the Card,
|
||||
MajorCard, and MinorCard classes for representing individual cards.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional, Tuple, TYPE_CHECKING, Dict
|
||||
import random
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
|
||||
|
||||
from ..attributes import (
|
||||
Meaning, CardImage, Suit, Zodiac, Element, Path,
|
||||
Planet, Sephera, Color, PeriodicTable, ElementType, DoublLetterTrump
|
||||
CardImage,
|
||||
Color,
|
||||
Element,
|
||||
ElementType,
|
||||
Meaning,
|
||||
Path,
|
||||
PeriodicTable,
|
||||
Planet,
|
||||
Sephera,
|
||||
Suit,
|
||||
)
|
||||
from ..constants import (
|
||||
COURT_RANKS,
|
||||
MAJOR_ARCANA_NAMES,
|
||||
PIP_INDEX_TO_NUMBER,
|
||||
MINOR_RANK_NAMES,
|
||||
PIP_INDEX_TO_NUMBER,
|
||||
PIP_ORDER,
|
||||
SUITS_FIRST,
|
||||
SUITS_LAST,
|
||||
@@ -35,6 +43,7 @@ def _get_card_data():
|
||||
global _card_data
|
||||
if _card_data is None:
|
||||
from ..card.data import CardDataLoader
|
||||
|
||||
_card_data = CardDataLoader()
|
||||
return _card_data
|
||||
|
||||
@@ -42,6 +51,7 @@ def _get_card_data():
|
||||
@dataclass
|
||||
class Card:
|
||||
"""Base class representing a Tarot card."""
|
||||
|
||||
number: int
|
||||
name: str
|
||||
meaning: Meaning
|
||||
@@ -85,10 +95,17 @@ class Card:
|
||||
return CardDetailsRegistry.key_to_roman(self.number)
|
||||
|
||||
# For Minor Arcana, return the pip number as a formatted string
|
||||
if hasattr(self, 'pip') and self.pip > 0:
|
||||
if hasattr(self, "pip") and self.pip > 0:
|
||||
pip_names = {
|
||||
2: "Two", 3: "Three", 4: "Four", 5: "Five",
|
||||
6: "Six", 7: "Seven", 8: "Eight", 9: "Nine", 10: "Ten"
|
||||
2: "Two",
|
||||
3: "Three",
|
||||
4: "Four",
|
||||
5: "Five",
|
||||
6: "Six",
|
||||
7: "Seven",
|
||||
8: "Eight",
|
||||
9: "Nine",
|
||||
10: "Ten",
|
||||
}
|
||||
return pip_names.get(self.pip, str(self.pip))
|
||||
|
||||
@@ -111,20 +128,26 @@ class Card:
|
||||
@dataclass
|
||||
class MajorCard(Card):
|
||||
"""Represents a Major Arcana card."""
|
||||
|
||||
kabbalistic_number: Optional[int] = None
|
||||
tarot_letter: Optional[str] = None
|
||||
tree_of_life_path: Optional[int] = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
# Kabbalistic number should be 0-21, but deck position can be anywhere
|
||||
if self.kabbalistic_number is not None and (self.kabbalistic_number < 0 or self.kabbalistic_number > 21):
|
||||
raise ValueError(f"Major Arcana kabbalistic number must be 0-21, got {self.kabbalistic_number}")
|
||||
if self.kabbalistic_number is not None and (
|
||||
self.kabbalistic_number < 0 or self.kabbalistic_number > 21
|
||||
):
|
||||
raise ValueError(
|
||||
f"Major Arcana kabbalistic number must be 0-21, got {self.kabbalistic_number}"
|
||||
)
|
||||
self.arcana = "Major"
|
||||
|
||||
|
||||
@dataclass
|
||||
class MinorCard(Card):
|
||||
"""Represents a Minor Arcana card - either Pip or Court card."""
|
||||
|
||||
suit: Suit = None # type: ignore
|
||||
astrological_influence: Optional[str] = None
|
||||
element: Optional[Element] = None
|
||||
@@ -142,6 +165,7 @@ class PipCard(MinorCard):
|
||||
Pip cards represent numbered forces in their suit, from Two
|
||||
through its full development (10).
|
||||
"""
|
||||
|
||||
pip: int = 0
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
@@ -158,6 +182,7 @@ class AceCard(MinorCard):
|
||||
for all other cards within that suit. Aces have pip=1 but are not
|
||||
technically pip cards.
|
||||
"""
|
||||
|
||||
pip: int = 1
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
@@ -183,7 +208,7 @@ class CourtCard(MinorCard):
|
||||
COURT_RANKS = {"Knight": 12, "Prince": 11, "Princess": 13, "Queen": 14}
|
||||
court_rank: str = ""
|
||||
associated_element: Optional[ElementType] = None
|
||||
hebrew_letter_path: Optional['Path'] = None
|
||||
hebrew_letter_path: Optional["Path"] = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.court_rank not in self.COURT_RANKS:
|
||||
@@ -194,12 +219,12 @@ class CourtCard(MinorCard):
|
||||
super().__post_init__()
|
||||
|
||||
|
||||
|
||||
class CardQuery:
|
||||
"""Helper class for fluent card queries: deck.number(3).minor.wands"""
|
||||
|
||||
def __init__(self, deck: 'Deck', number: Optional[int] = None,
|
||||
arcana: Optional[str] = None) -> None:
|
||||
def __init__(
|
||||
self, deck: "Deck", number: Optional[int] = None, arcana: Optional[str] = None
|
||||
) -> None:
|
||||
self.deck = deck
|
||||
self.number = number
|
||||
self.arcana = arcana
|
||||
@@ -209,8 +234,11 @@ class CardQuery:
|
||||
cards = self.deck.cards
|
||||
|
||||
if self.number is not None:
|
||||
cards = [c for c in cards if c.number == self.number or
|
||||
(hasattr(c, 'pip') and c.pip == self.number)]
|
||||
cards = [
|
||||
c
|
||||
for c in cards
|
||||
if c.number == self.number or (hasattr(c, "pip") and c.pip == self.number)
|
||||
]
|
||||
|
||||
if self.arcana is not None:
|
||||
cards = [c for c in cards if c.arcana == self.arcana]
|
||||
@@ -223,33 +251,45 @@ class CardQuery:
|
||||
return [c for c in self._filter_cards() if c.arcana == "Major"]
|
||||
|
||||
@property
|
||||
def minor(self) -> 'CardQuery':
|
||||
def minor(self) -> "CardQuery":
|
||||
"""Filter to Minor Arcana, return new CardQuery for suit chaining."""
|
||||
return CardQuery(self.deck, self.number, "Minor")
|
||||
|
||||
@property
|
||||
def cups(self) -> List[Card]:
|
||||
"""Get cards in Cups suit."""
|
||||
return [c for c in self._filter_cards() if hasattr(c, 'suit') and
|
||||
c.suit and c.suit.name == "Cups"]
|
||||
return [
|
||||
c
|
||||
for c in self._filter_cards()
|
||||
if hasattr(c, "suit") and c.suit and c.suit.name == "Cups"
|
||||
]
|
||||
|
||||
@property
|
||||
def swords(self) -> List[Card]:
|
||||
"""Get cards in Swords suit."""
|
||||
return [c for c in self._filter_cards() if hasattr(c, 'suit') and
|
||||
c.suit and c.suit.name == "Swords"]
|
||||
return [
|
||||
c
|
||||
for c in self._filter_cards()
|
||||
if hasattr(c, "suit") and c.suit and c.suit.name == "Swords"
|
||||
]
|
||||
|
||||
@property
|
||||
def wands(self) -> List[Card]:
|
||||
"""Get cards in Wands suit."""
|
||||
return [c for c in self._filter_cards() if hasattr(c, 'suit') and
|
||||
c.suit and c.suit.name == "Wands"]
|
||||
return [
|
||||
c
|
||||
for c in self._filter_cards()
|
||||
if hasattr(c, "suit") and c.suit and c.suit.name == "Wands"
|
||||
]
|
||||
|
||||
@property
|
||||
def pentacles(self) -> List[Card]:
|
||||
"""Get cards in Pentacles suit."""
|
||||
return [c for c in self._filter_cards() if hasattr(c, 'suit') and
|
||||
c.suit and c.suit.name == "Pentacles"]
|
||||
return [
|
||||
c
|
||||
for c in self._filter_cards()
|
||||
if hasattr(c, "suit") and c.suit and c.suit.name == "Pentacles"
|
||||
]
|
||||
|
||||
def __iter__(self):
|
||||
"""Allow iteration over filtered cards."""
|
||||
@@ -272,8 +312,13 @@ class CardQuery:
|
||||
class TemporalQuery:
|
||||
"""Helper class for fluent temporal queries: loader.month(5).day(23).hour(15)"""
|
||||
|
||||
def __init__(self, loader: 'CardDataLoader', month_num: Optional[int] = None,
|
||||
day_num: Optional[int] = None, hour_num: Optional[int] = None) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
loader: "CardDataLoader",
|
||||
month_num: Optional[int] = None,
|
||||
day_num: Optional[int] = None,
|
||||
hour_num: Optional[int] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Initialize temporal query builder.
|
||||
|
||||
@@ -288,24 +333,27 @@ class TemporalQuery:
|
||||
self.day_num = day_num
|
||||
self.hour_num = hour_num
|
||||
|
||||
def month(self, num: int) -> 'TemporalQuery':
|
||||
def month(self, num: int) -> "TemporalQuery":
|
||||
"""Set month (1-12) and return new query for chaining."""
|
||||
return TemporalQuery(self.loader, month_num=num,
|
||||
day_num=self.day_num, hour_num=self.hour_num)
|
||||
return TemporalQuery(
|
||||
self.loader, month_num=num, day_num=self.day_num, hour_num=self.hour_num
|
||||
)
|
||||
|
||||
def day(self, num: int) -> 'TemporalQuery':
|
||||
def day(self, num: int) -> "TemporalQuery":
|
||||
"""Set day (1-31) and return new query for chaining."""
|
||||
if self.month_num is None:
|
||||
raise ValueError("Must set month before day")
|
||||
return TemporalQuery(self.loader, month_num=self.month_num,
|
||||
day_num=num, hour_num=self.hour_num)
|
||||
return TemporalQuery(
|
||||
self.loader, month_num=self.month_num, day_num=num, hour_num=self.hour_num
|
||||
)
|
||||
|
||||
def hour(self, num: int) -> 'TemporalQuery':
|
||||
def hour(self, num: int) -> "TemporalQuery":
|
||||
"""Set hour (0-23) and return new query for chaining."""
|
||||
if self.month_num is None or self.day_num is None:
|
||||
raise ValueError("Must set month and day before hour")
|
||||
return TemporalQuery(self.loader, month_num=self.month_num,
|
||||
day_num=self.day_num, hour_num=num)
|
||||
return TemporalQuery(
|
||||
self.loader, month_num=self.month_num, day_num=self.day_num, hour_num=num
|
||||
)
|
||||
|
||||
def weekday(self) -> Optional[str]:
|
||||
"""Get weekday name for current month/day combination using Zeller's congruence."""
|
||||
@@ -389,19 +437,20 @@ class DLT:
|
||||
raise ValueError(f"DLT number must be 3-21, got {trump_number}")
|
||||
|
||||
self.trump_number = trump_number
|
||||
self._loader: Optional['CardDataLoader'] = None
|
||||
self._loader: Optional["CardDataLoader"] = None
|
||||
self._deck: Optional[Deck] = None
|
||||
|
||||
@property
|
||||
def loader(self) -> 'CardDataLoader':
|
||||
def loader(self) -> "CardDataLoader":
|
||||
"""Lazy-load CardDataLoader on first access."""
|
||||
if self._loader is None:
|
||||
from ..card.data import CardDataLoader
|
||||
|
||||
self._loader = CardDataLoader()
|
||||
return self._loader
|
||||
|
||||
@property
|
||||
def deck(self) -> 'Deck':
|
||||
def deck(self) -> "Deck":
|
||||
"""Lazy-load Deck on first access."""
|
||||
if self._deck is None:
|
||||
self._deck = Deck()
|
||||
@@ -486,7 +535,7 @@ class Deck:
|
||||
# Get Hebrew letters (Paths) for court cards
|
||||
yod_path = card_data.path(20) # Yod
|
||||
vav_path = card_data.path(16) # Vav
|
||||
he_path = card_data.path(15) # He (Heh)
|
||||
he_path = card_data.path(15) # He (Heh)
|
||||
|
||||
if not yod_path or not vav_path or not he_path:
|
||||
raise RuntimeError("Failed to load Hebrew letter/path data from CardDataLoader")
|
||||
@@ -507,12 +556,16 @@ class Deck:
|
||||
"Fire": fire_element,
|
||||
}
|
||||
|
||||
def _suit_specs(suit_defs: List[Tuple[str, str, int]]) -> List[Tuple[str, ElementType, int]]:
|
||||
def _suit_specs(
|
||||
suit_defs: List[Tuple[str, str, int]],
|
||||
) -> List[Tuple[str, ElementType, int]]:
|
||||
specs: List[Tuple[str, ElementType, int]] = []
|
||||
for suit_name, element_key, suit_num in suit_defs:
|
||||
element_obj = element_lookup.get(element_key)
|
||||
if element_obj is None:
|
||||
raise RuntimeError(f"Failed to resolve element '{element_key}' for suit '{suit_name}'")
|
||||
raise RuntimeError(
|
||||
f"Failed to resolve element '{element_key}' for suit '{suit_name}'"
|
||||
)
|
||||
specs.append((suit_name, element_obj, suit_num))
|
||||
return specs
|
||||
|
||||
@@ -538,11 +591,10 @@ class Deck:
|
||||
number=card_number,
|
||||
name=name,
|
||||
meaning=Meaning(
|
||||
upright=f"{name} upright meaning",
|
||||
reversed=f"{name} reversed meaning"
|
||||
upright=f"{name} upright meaning", reversed=f"{name} reversed meaning"
|
||||
),
|
||||
arcana="Major",
|
||||
kabbalistic_number=i
|
||||
kabbalistic_number=i,
|
||||
)
|
||||
self.cards.append(card)
|
||||
card_number += 1
|
||||
@@ -561,12 +613,12 @@ class Deck:
|
||||
# Load detailed explanations and keywords from registry
|
||||
try:
|
||||
from ..card.loader import load_deck_details
|
||||
|
||||
load_deck_details(self)
|
||||
except ImportError:
|
||||
# Handle case where loader might not be available or circular import issues
|
||||
pass
|
||||
|
||||
|
||||
def _add_minor_cards_for_suit(
|
||||
self,
|
||||
suit_name: str,
|
||||
@@ -625,7 +677,6 @@ class Deck:
|
||||
|
||||
return card_number
|
||||
|
||||
|
||||
def shuffle(self) -> None:
|
||||
"""Shuffle the deck."""
|
||||
random.shuffle(self.cards)
|
||||
@@ -644,7 +695,9 @@ class Deck:
|
||||
raise ValueError("Must draw at least 1 card")
|
||||
|
||||
if num_cards > len(self.cards):
|
||||
raise ValueError(f"Cannot draw {num_cards} cards from deck with {len(self.cards)} cards")
|
||||
raise ValueError(
|
||||
f"Cannot draw {num_cards} cards from deck with {len(self.cards)} cards"
|
||||
)
|
||||
|
||||
drawn = []
|
||||
for _ in range(num_cards):
|
||||
@@ -680,8 +733,7 @@ class Deck:
|
||||
Usage:
|
||||
deck.suit("Wands")
|
||||
"""
|
||||
return [c for c in self.cards if hasattr(c, 'suit') and
|
||||
c.suit and c.suit.name == suit_name]
|
||||
return [c for c in self.cards if hasattr(c, "suit") and c.suit and c.suit.name == suit_name]
|
||||
|
||||
@property
|
||||
def major(self) -> List[Card]:
|
||||
|
||||
@@ -25,15 +25,18 @@ Usage:
|
||||
area = Tarot.cube.area('North', 'center')
|
||||
"""
|
||||
|
||||
from typing import Dict, Optional, Union, overload, TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Dict, Optional, Union, overload
|
||||
|
||||
from .card import CardAccessor
|
||||
from kaballah import Tree, Cube
|
||||
from kaballah import Cube, Tree
|
||||
from letter import letters
|
||||
|
||||
from .card import CardAccessor
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from utils.attributes import Planet, God
|
||||
from utils.attributes import God, Planet
|
||||
|
||||
from .attributes import Hexagram
|
||||
from .card.data import CardDataLoader
|
||||
|
||||
|
||||
class DeckAccessor:
|
||||
@@ -44,16 +47,20 @@ class DeckAccessor:
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Return a nice summary of the deck accessor."""
|
||||
return "Tarot Deck Accessor\n\nAccess methods:\n Tarot.deck.card(3) - Get card by number\n Tarot.deck.card.filter(...) - Filter cards\n Tarot.deck.card.display() - Display all cards\n Tarot.deck.card.spread(...) - Draw a spread"
|
||||
return (
|
||||
"Tarot Deck Accessor\n\n"
|
||||
"Access methods:\n"
|
||||
" Tarot.deck.card(3) - Get card by number\n"
|
||||
" Tarot.deck.card.filter(...) - Filter cards\n"
|
||||
" Tarot.deck.card.display() - Display all cards\n"
|
||||
" Tarot.deck.card.spread(...) - Draw a spread"
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Return a nice representation of the deck accessor."""
|
||||
return self.__str__()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Tarot:
|
||||
"""
|
||||
Unified accessor for Tarot correspondences and data.
|
||||
@@ -75,7 +82,7 @@ class Tarot:
|
||||
tree = Tree
|
||||
cube = Cube
|
||||
|
||||
_loader: Optional['CardDataLoader'] = None # type: ignore
|
||||
_loader: Optional["CardDataLoader"] = None # type: ignore
|
||||
_initialized: bool = False
|
||||
|
||||
@classmethod
|
||||
@@ -85,35 +92,34 @@ class Tarot:
|
||||
return
|
||||
|
||||
from .card.data import CardDataLoader
|
||||
|
||||
cls._loader = CardDataLoader()
|
||||
cls._initialized = True
|
||||
|
||||
@classmethod
|
||||
@overload
|
||||
def planet(cls, name: str) -> Optional['Planet']:
|
||||
...
|
||||
def planet(cls, name: str) -> Optional["Planet"]: ...
|
||||
|
||||
@classmethod
|
||||
@overload
|
||||
def planet(cls, name: None = ...) -> Dict[str, 'Planet']:
|
||||
...
|
||||
def planet(cls, name: None = ...) -> Dict[str, "Planet"]: ...
|
||||
|
||||
@classmethod
|
||||
def planet(cls, name: Optional[str] = None) -> Union[Optional['Planet'], Dict[str, 'Planet']]:
|
||||
def planet(cls, name: Optional[str] = None) -> Union[Optional["Planet"], Dict[str, "Planet"]]:
|
||||
"""Return a planet entry or all planets."""
|
||||
cls._ensure_initialized()
|
||||
return cls._loader.planet(name) # type: ignore
|
||||
|
||||
@classmethod
|
||||
def god(cls, name: Optional[str] = None) -> Union[Optional['God'], Dict[str, 'God']]:
|
||||
def god(cls, name: Optional[str] = None) -> Union[Optional["God"], Dict[str, "God"]]:
|
||||
"""Return a god entry or all gods."""
|
||||
cls._ensure_initialized()
|
||||
return cls._loader.god(name) # type: ignore
|
||||
|
||||
@classmethod
|
||||
def hexagram(cls, number: Optional[int] = None) -> Union[Optional['Hexagram'], Dict[int, 'Hexagram']]:
|
||||
def hexagram(
|
||||
cls, number: Optional[int] = None
|
||||
) -> Union[Optional["Hexagram"], Dict[int, "Hexagram"]]:
|
||||
"""Return a hexagram or all hexagrams."""
|
||||
cls._ensure_initialized()
|
||||
return cls._loader.hexagram(number) # type: ignore
|
||||
|
||||
|
||||
|
||||
301
src/tarot/ui.py
301
src/tarot/ui.py
@@ -6,20 +6,20 @@ supporting multiple decks and automatic image resolution.
|
||||
"""
|
||||
|
||||
import os
|
||||
import io
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, filedialog
|
||||
from pathlib import Path
|
||||
from tkinter import filedialog, ttk
|
||||
from typing import List, Optional
|
||||
|
||||
try:
|
||||
from PIL import Image, ImageTk, ImageGrab, ImageDraw, ImageFont
|
||||
from PIL import Image, ImageDraw, ImageFont, ImageTk
|
||||
|
||||
HAS_PILLOW = True
|
||||
except ImportError:
|
||||
HAS_PILLOW = False
|
||||
|
||||
from tarot.deck import Card
|
||||
from tarot.card.image_loader import ImageDeckLoader
|
||||
from tarot.deck import Card
|
||||
|
||||
|
||||
class CardDisplay:
|
||||
@@ -66,7 +66,7 @@ class CardDisplay:
|
||||
return
|
||||
|
||||
# Import spread classes here to avoid circular imports if any
|
||||
from tarot.card.spread import SpreadPosition, DrawnCard, SpreadReading
|
||||
from tarot.card.spread import DrawnCard, SpreadPosition, SpreadReading
|
||||
|
||||
# Create a dummy spread class since Spread requires a valid name
|
||||
class SimpleSpread:
|
||||
@@ -81,19 +81,11 @@ class CardDisplay:
|
||||
|
||||
for i, card in enumerate(cards, 1):
|
||||
# Create a generic position
|
||||
pos = SpreadPosition(
|
||||
number=i,
|
||||
name=f"Card {i}",
|
||||
meaning="Display Card"
|
||||
)
|
||||
pos = SpreadPosition(number=i, name=f"Card {i}", meaning="Display Card")
|
||||
positions.append(pos)
|
||||
|
||||
# Create drawn card
|
||||
drawn = DrawnCard(
|
||||
position=pos,
|
||||
card=card,
|
||||
is_reversed=False
|
||||
)
|
||||
drawn = DrawnCard(position=pos, card=card, is_reversed=False)
|
||||
drawn_cards.append(drawn)
|
||||
|
||||
# Create a synthetic spread
|
||||
@@ -101,7 +93,7 @@ class CardDisplay:
|
||||
spread.positions = positions
|
||||
|
||||
# Create reading
|
||||
reading = SpreadReading(spread, drawn_cards) # type: ignore
|
||||
reading = SpreadReading(spread, drawn_cards) # type: ignore
|
||||
|
||||
# Use SpreadDisplay
|
||||
display = SpreadDisplay(reading, self.deck_name)
|
||||
@@ -109,8 +101,6 @@ class CardDisplay:
|
||||
display.run()
|
||||
|
||||
|
||||
|
||||
|
||||
class CubeDisplay:
|
||||
"""
|
||||
Displays the Cube of Space with navigation.
|
||||
@@ -124,6 +114,12 @@ class CubeDisplay:
|
||||
"Above": {"Right": "East", "Left": "West", "Up": "South", "Down": "North"},
|
||||
"Below": {"Right": "East", "Left": "West", "Up": "North", "Down": "South"},
|
||||
}
|
||||
# Zoom bounds used when UI is not initialized (headless tests)
|
||||
MIN_ZOOM = 0.5
|
||||
MAX_ZOOM = 3.0
|
||||
# Zoom bounds for the live UI (when canvas/root exist)
|
||||
UI_MIN_ZOOM = 0.1
|
||||
UI_MAX_ZOOM = 50.0
|
||||
|
||||
def __init__(self, cube, deck_name: str = "default"):
|
||||
self.cube = cube
|
||||
@@ -153,9 +149,9 @@ class CubeDisplay:
|
||||
self.root.bind("<plus>", lambda e: self._zoom(1.1))
|
||||
self.root.bind("<equal>", lambda e: self._zoom(1.1)) # Often same key as plus
|
||||
self.root.bind("<minus>", lambda e: self._zoom(0.9))
|
||||
self.root.bind("<underscore>", lambda e: self._zoom(0.9)) # Shift+minus
|
||||
self.root.bind("<KP_Add>", lambda e: self._zoom(1.1)) # Numpad +
|
||||
self.root.bind("<KP_Subtract>", lambda e: self._zoom(0.9)) # Numpad -
|
||||
self.root.bind("<underscore>", lambda e: self._zoom(0.9)) # Shift+minus
|
||||
self.root.bind("<KP_Add>", lambda e: self._zoom(1.1)) # Numpad +
|
||||
self.root.bind("<KP_Subtract>", lambda e: self._zoom(0.9)) # Numpad -
|
||||
|
||||
# Bind WASD for panning
|
||||
self.root.bind("w", lambda e: self._pan_key("up"))
|
||||
@@ -181,7 +177,9 @@ class CubeDisplay:
|
||||
|
||||
# Content Frame (inside canvas)
|
||||
self.content_frame = ttk.Frame(self.canvas)
|
||||
self.canvas_window = self.canvas.create_window((0, 0), window=self.content_frame, anchor="center")
|
||||
self.canvas_window = self.canvas.create_window(
|
||||
(0, 0), window=self.content_frame, anchor="center"
|
||||
)
|
||||
|
||||
# Overlay Controls
|
||||
# Navigation Frame (Bottom Center)
|
||||
@@ -194,8 +192,12 @@ class CubeDisplay:
|
||||
|
||||
# Populate Zoom Frame
|
||||
ttk.Label(zoom_frame, text="Zoom:").pack(side=tk.LEFT, padx=5)
|
||||
ttk.Button(zoom_frame, text="+", width=3, command=lambda: self._zoom(1.22)).pack(side=tk.LEFT)
|
||||
ttk.Button(zoom_frame, text="-", width=3, command=lambda: self._zoom(0.82)).pack(side=tk.LEFT)
|
||||
ttk.Button(zoom_frame, text="+", width=3, command=lambda: self._zoom(1.22)).pack(
|
||||
side=tk.LEFT
|
||||
)
|
||||
ttk.Button(zoom_frame, text="-", width=3, command=lambda: self._zoom(0.82)).pack(
|
||||
side=tk.LEFT
|
||||
)
|
||||
|
||||
# Populate Navigation Frame
|
||||
dir_frame = ttk.Frame(nav_frame)
|
||||
@@ -205,8 +207,12 @@ class CubeDisplay:
|
||||
|
||||
mid_nav = ttk.Frame(dir_frame)
|
||||
mid_nav.pack(side=tk.TOP)
|
||||
ttk.Button(mid_nav, text="Left", command=lambda: self._navigate("Left")).pack(side=tk.LEFT, padx=5)
|
||||
ttk.Button(mid_nav, text="Right", command=lambda: self._navigate("Right")).pack(side=tk.LEFT, padx=5)
|
||||
ttk.Button(mid_nav, text="Left", command=lambda: self._navigate("Left")).pack(
|
||||
side=tk.LEFT, padx=5
|
||||
)
|
||||
ttk.Button(mid_nav, text="Right", command=lambda: self._navigate("Right")).pack(
|
||||
side=tk.LEFT, padx=5
|
||||
)
|
||||
|
||||
ttk.Button(dir_frame, text="Down", command=lambda: self._navigate("Down")).pack(side=tk.TOP)
|
||||
|
||||
@@ -221,7 +227,7 @@ class CubeDisplay:
|
||||
screen_height = self.root.winfo_screenheight()
|
||||
x = (screen_width // 2) - (width // 2)
|
||||
y = (screen_height // 2) - (height // 2)
|
||||
self.root.geometry(f'{width}x{height}+{x}+{y}')
|
||||
self.root.geometry(f"{width}x{height}+{x}+{y}")
|
||||
|
||||
# Ensure window has focus for keyboard events
|
||||
self.root.focus_force()
|
||||
@@ -238,17 +244,27 @@ class CubeDisplay:
|
||||
|
||||
def _pan_key(self, direction):
|
||||
"""Pan the canvas using keys."""
|
||||
if direction == 'up':
|
||||
if direction == "up":
|
||||
self.canvas.yview_scroll(-1, "units")
|
||||
elif direction == 'down':
|
||||
elif direction == "down":
|
||||
self.canvas.yview_scroll(1, "units")
|
||||
elif direction == 'left':
|
||||
elif direction == "left":
|
||||
self.canvas.xview_scroll(-1, "units")
|
||||
elif direction == 'right':
|
||||
elif direction == "right":
|
||||
self.canvas.xview_scroll(1, "units")
|
||||
|
||||
def _zoom(self, factor):
|
||||
"""Adjust zoom level and redraw, keeping the view centered."""
|
||||
# If UI not initialized (no canvas), just update zoom level and return.
|
||||
if not getattr(self, "canvas", None):
|
||||
old_zoom = self.zoom_level
|
||||
self.zoom_level *= factor
|
||||
# Clamp zoom level to configured bounds
|
||||
self.zoom_level = max(self.MIN_ZOOM, min(self.zoom_level, self.MAX_ZOOM))
|
||||
# _update_display is safe and will no-op if content isn't initialized
|
||||
self._update_display()
|
||||
return
|
||||
|
||||
# 1. Capture current state
|
||||
canvas_width = self.canvas.winfo_width()
|
||||
canvas_height = self.canvas.winfo_height()
|
||||
@@ -262,7 +278,8 @@ class CubeDisplay:
|
||||
if not bbox:
|
||||
# Should not happen if initialized
|
||||
self.zoom_level *= factor
|
||||
self.zoom_level = max(0.1, min(self.zoom_level, 50.0))
|
||||
# Clamp to UI bounds when canvas exists
|
||||
self.zoom_level = max(self.UI_MIN_ZOOM, min(self.zoom_level, self.UI_MAX_ZOOM))
|
||||
self._update_display()
|
||||
return
|
||||
|
||||
@@ -276,8 +293,12 @@ class CubeDisplay:
|
||||
# 2. Update zoom level
|
||||
old_zoom = self.zoom_level
|
||||
self.zoom_level *= factor
|
||||
# Clamp zoom level
|
||||
self.zoom_level = max(0.1, min(self.zoom_level, 50.0))
|
||||
# Clamp zoom level (UI uses broader bounds when initialized)
|
||||
if getattr(self, "canvas", None):
|
||||
min_z, max_z = self.UI_MIN_ZOOM, self.UI_MAX_ZOOM
|
||||
else:
|
||||
min_z, max_z = self.MIN_ZOOM, self.MAX_ZOOM
|
||||
self.zoom_level = max(min_z, min(self.zoom_level, max_z))
|
||||
|
||||
# Calculate effective factor in case of clamping
|
||||
effective_factor = self.zoom_level / old_zoom if old_zoom > 0 else factor
|
||||
@@ -335,8 +356,11 @@ class CubeDisplay:
|
||||
widget.destroy()
|
||||
|
||||
# Title
|
||||
ttk.Label(self.content_frame, text=f"Wall: {self.current_wall_name}",
|
||||
font=("Helvetica", 16, "bold")).pack(pady=(0, 20))
|
||||
ttk.Label(
|
||||
self.content_frame,
|
||||
text=f"Wall: {self.current_wall_name}",
|
||||
font=("Helvetica", 16, "bold"),
|
||||
).pack(pady=(0, 20))
|
||||
|
||||
# Grid for directions
|
||||
grid_frame = ttk.Frame(self.content_frame)
|
||||
@@ -351,7 +375,7 @@ class CubeDisplay:
|
||||
"West": (1, 0),
|
||||
"Center": (1, 1),
|
||||
"East": (1, 2),
|
||||
"South": (2, 1)
|
||||
"South": (2, 1),
|
||||
}
|
||||
|
||||
# Calculate sizes based on zoom
|
||||
@@ -365,7 +389,9 @@ class CubeDisplay:
|
||||
for dir_name, (row, col) in layout.items():
|
||||
direction = wall.direction(dir_name)
|
||||
|
||||
cell_frame = ttk.Frame(grid_frame, borderwidth=1, relief="solid", width=cell_width, height=cell_height)
|
||||
cell_frame = ttk.Frame(
|
||||
grid_frame, borderwidth=1, relief="solid", width=cell_width, height=cell_height
|
||||
)
|
||||
cell_frame.grid(row=row, column=col, padx=5, pady=5)
|
||||
cell_frame.grid_propagate(False)
|
||||
|
||||
@@ -385,9 +411,11 @@ class CubeDisplay:
|
||||
pil_img = Image.open(img_path)
|
||||
# Resize for grid
|
||||
base_height = img_height
|
||||
h_percent = (base_height / float(pil_img.size[1]))
|
||||
h_percent = base_height / float(pil_img.size[1])
|
||||
w_size = int((float(pil_img.size[0]) * float(h_percent)))
|
||||
pil_img = pil_img.resize((w_size, base_height), Image.Resampling.LANCZOS)
|
||||
pil_img = pil_img.resize(
|
||||
(w_size, base_height), Image.Resampling.LANCZOS
|
||||
)
|
||||
|
||||
tk_img = ImageTk.PhotoImage(pil_img)
|
||||
self.root.images.append(tk_img)
|
||||
@@ -414,10 +442,10 @@ class CubeDisplay:
|
||||
content_height = self.content_frame.winfo_reqheight()
|
||||
|
||||
if canvas_width > content_width and canvas_height > content_height:
|
||||
self.canvas.coords(self.canvas_window, canvas_width/2, canvas_height/2)
|
||||
self.canvas.coords(self.canvas_window, canvas_width / 2, canvas_height / 2)
|
||||
else:
|
||||
# Reset to top-left or center of scroll region
|
||||
self.canvas.coords(self.canvas_window, content_width/2, content_height/2)
|
||||
# Reset to top-left or center of scroll region
|
||||
self.canvas.coords(self.canvas_window, content_width / 2, content_height / 2)
|
||||
|
||||
# Bind panning events to all content widgets
|
||||
self._bind_recursive(self.content_frame)
|
||||
@@ -491,6 +519,7 @@ def display_cube(cube=None, deck_name: str = "default"):
|
||||
"""
|
||||
if cube is None:
|
||||
from tarot.tarot_api import Tarot
|
||||
|
||||
cube = Tarot.cube
|
||||
|
||||
display = CubeDisplay(cube, deck_name)
|
||||
@@ -506,54 +535,48 @@ class SpreadDisplay:
|
||||
# Coordinates are relative grid units (approx card width/height)
|
||||
# Using 1.02 spacing for tight layout
|
||||
LAYOUTS = {
|
||||
'Celtic Cross': {
|
||||
1: {'pos': (0, 0)},
|
||||
2: {'pos': (0, 0), 'rotate': 90, 'z': 10}, # Top layer
|
||||
3: {'pos': (0, -1.02)},
|
||||
4: {'pos': (0, 1.02)},
|
||||
5: {'pos': (-1.02, 0)},
|
||||
6: {'pos': (1.02, 0)},
|
||||
7: {'pos': (2.1, 1.53)}, # 1.5 * 1.02
|
||||
8: {'pos': (2.1, 0.51)}, # 0.5 * 1.02
|
||||
9: {'pos': (2.1, -0.51)},
|
||||
10: {'pos': (2.1, -1.53)}
|
||||
"Celtic Cross": {
|
||||
1: {"pos": (0, 0)},
|
||||
2: {"pos": (0, 0), "rotate": 90, "z": 10}, # Top layer
|
||||
3: {"pos": (0, -1.02)},
|
||||
4: {"pos": (0, 1.02)},
|
||||
5: {"pos": (-1.02, 0)},
|
||||
6: {"pos": (1.02, 0)},
|
||||
7: {"pos": (2.1, 1.53)}, # 1.5 * 1.02
|
||||
8: {"pos": (2.1, 0.51)}, # 0.5 * 1.02
|
||||
9: {"pos": (2.1, -0.51)},
|
||||
10: {"pos": (2.1, -1.53)},
|
||||
},
|
||||
'3-Card Spread': {
|
||||
1: {'pos': (-1.02, 0)},
|
||||
2: {'pos': (0, 0)},
|
||||
3: {'pos': (1.02, 0)}
|
||||
"3-Card Spread": {1: {"pos": (-1.02, 0)}, 2: {"pos": (0, 0)}, 3: {"pos": (1.02, 0)}},
|
||||
"Golden Dawn 3-Card": {
|
||||
1: {"pos": (0, -1.02)},
|
||||
2: {"pos": (-1.02, 0.8)},
|
||||
3: {"pos": (1.02, 0.8)},
|
||||
},
|
||||
'Golden Dawn 3-Card': {
|
||||
1: {'pos': (0, -1.02)},
|
||||
2: {'pos': (-1.02, 0.8)},
|
||||
3: {'pos': (1.02, 0.8)}
|
||||
"Horseshoe": {
|
||||
1: {"pos": (-3.06, 1.02)},
|
||||
2: {"pos": (-2.04, 0)},
|
||||
3: {"pos": (-1.02, -0.51)},
|
||||
4: {"pos": (0, -1.02)},
|
||||
5: {"pos": (1.02, -0.51)},
|
||||
6: {"pos": (2.04, 0)},
|
||||
7: {"pos": (3.06, 1.02)},
|
||||
},
|
||||
'Horseshoe': {
|
||||
1: {'pos': (-3.06, 1.02)},
|
||||
2: {'pos': (-2.04, 0)},
|
||||
3: {'pos': (-1.02, -0.51)},
|
||||
4: {'pos': (0, -1.02)},
|
||||
5: {'pos': (1.02, -0.51)},
|
||||
6: {'pos': (2.04, 0)},
|
||||
7: {'pos': (3.06, 1.02)}
|
||||
"Pentagram": {
|
||||
1: {"pos": (0, -1.5)}, # Spirit (Top)
|
||||
2: {"pos": (1.5, -0.4)}, # Fire (Right Top)
|
||||
3: {"pos": (1.0, 1.5)}, # Water (Right Bottom)
|
||||
4: {"pos": (-1.0, 1.5)}, # Air (Left Bottom)
|
||||
5: {"pos": (-1.5, -0.4)}, # Earth (Left Top)
|
||||
},
|
||||
'Pentagram': {
|
||||
1: {'pos': (0, -1.5)}, # Spirit (Top)
|
||||
2: {'pos': (1.5, -0.4)}, # Fire (Right Top)
|
||||
3: {'pos': (1.0, 1.5)}, # Water (Right Bottom)
|
||||
4: {'pos': (-1.0, 1.5)}, # Air (Left Bottom)
|
||||
5: {'pos': (-1.5, -0.4)} # Earth (Left Top)
|
||||
"Relationship": {
|
||||
1: {"pos": (-1.5, 0)}, # You
|
||||
2: {"pos": (1.5, 0)}, # Them
|
||||
3: {"pos": (0, -1.02)}, # Relationship (Center Top)
|
||||
4: {"pos": (0, 0.5)}, # Challenge (Center Bottom)
|
||||
5: {"pos": (0, 2.0)}, # Outcome (Bottom)
|
||||
},
|
||||
'Relationship': {
|
||||
1: {'pos': (-1.5, 0)}, # You
|
||||
2: {'pos': (1.5, 0)}, # Them
|
||||
3: {'pos': (0, -1.02)}, # Relationship (Center Top)
|
||||
4: {'pos': (0, 0.5)}, # Challenge (Center Bottom)
|
||||
5: {'pos': (0, 2.0)} # Outcome (Bottom)
|
||||
},
|
||||
'Yes or No': {
|
||||
1: {'pos': (0, 0)}
|
||||
}
|
||||
"Yes or No": {1: {"pos": (0, 0)}},
|
||||
}
|
||||
|
||||
def __init__(self, reading, deck_name="default"):
|
||||
@@ -566,7 +589,7 @@ class SpreadDisplay:
|
||||
self.show_text = True
|
||||
self.show_top_card = True
|
||||
self.drag_data = {"x": 0, "y": 0}
|
||||
self._tk_images = [] # Keep references
|
||||
self._tk_images = [] # Keep references
|
||||
|
||||
# Setup UI
|
||||
self._setup_ui()
|
||||
@@ -593,15 +616,25 @@ class SpreadDisplay:
|
||||
toolbar = ttk.Frame(self.root)
|
||||
toolbar.pack(side=tk.TOP, fill=tk.X)
|
||||
|
||||
ttk.Button(toolbar, text="Zoom In (+)", command=lambda: self._zoom(1.2)).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(toolbar, text="Zoom Out (-)", command=lambda: self._zoom(0.8)).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(toolbar, text="Zoom In (+)", command=lambda: self._zoom(1.2)).pack(
|
||||
side=tk.LEFT, padx=2
|
||||
)
|
||||
ttk.Button(toolbar, text="Zoom Out (-)", command=lambda: self._zoom(0.8)).pack(
|
||||
side=tk.LEFT, padx=2
|
||||
)
|
||||
ttk.Button(toolbar, text="Reset View", command=self._reset_view).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(toolbar, text="Toggle Text", command=self._toggle_text).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(toolbar, text="Export PNG", command=self._export_image).pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(toolbar, text="Toggle Text", command=self._toggle_text).pack(
|
||||
side=tk.LEFT, padx=2
|
||||
)
|
||||
ttk.Button(toolbar, text="Export PNG", command=self._export_image).pack(
|
||||
side=tk.LEFT, padx=2
|
||||
)
|
||||
|
||||
# Only show toggle top card if relevant
|
||||
if self.reading.spread.name == 'Celtic Cross':
|
||||
ttk.Button(toolbar, text="Toggle Cross", command=self._toggle_top_card).pack(side=tk.LEFT, padx=2)
|
||||
if self.reading.spread.name == "Celtic Cross":
|
||||
ttk.Button(toolbar, text="Toggle Cross", command=self._toggle_top_card).pack(
|
||||
side=tk.LEFT, padx=2
|
||||
)
|
||||
|
||||
# Canvas
|
||||
self.canvas = tk.Canvas(self.root, bg="#2c3e50")
|
||||
@@ -636,8 +669,8 @@ class SpreadDisplay:
|
||||
# Center of virtual space (arbitrary large number to allow scrolling)
|
||||
cx, cy = 2000, 2000
|
||||
|
||||
min_x, min_y = float('inf'), float('inf')
|
||||
max_x, max_y = float('-inf'), float('-inf')
|
||||
min_x, min_y = float("inf"), float("inf")
|
||||
max_x, max_y = float("-inf"), float("-inf")
|
||||
|
||||
# Sort cards by z-index (default 0)
|
||||
cards_to_draw = []
|
||||
@@ -646,7 +679,7 @@ class SpreadDisplay:
|
||||
if not pos_data:
|
||||
continue
|
||||
|
||||
z_index = pos_data.get('z', 0)
|
||||
z_index = pos_data.get("z", 0)
|
||||
|
||||
# Skip if top card is hidden
|
||||
if z_index > 0 and not self.show_top_card:
|
||||
@@ -658,8 +691,8 @@ class SpreadDisplay:
|
||||
cards_to_draw.sort(key=lambda x: x[2])
|
||||
|
||||
for drawn, pos_data, _ in cards_to_draw:
|
||||
rel_x, rel_y = pos_data['pos']
|
||||
rotation = pos_data.get('rotate', 0)
|
||||
rel_x, rel_y = pos_data["pos"]
|
||||
rotation = pos_data.get("rotate", 0)
|
||||
|
||||
# Calculate position
|
||||
x = cx + (rel_x * unit_x)
|
||||
@@ -672,7 +705,7 @@ class SpreadDisplay:
|
||||
|
||||
# If rotated 90 deg, width and height swap for bounding box
|
||||
if abs(rotation % 180) == 90:
|
||||
half_w, half_h = half_h, half_w
|
||||
half_w, half_h = half_h, half_w
|
||||
|
||||
min_x = min(min_x, x - half_w)
|
||||
max_x = max(max_x, x + half_w)
|
||||
@@ -684,7 +717,9 @@ class SpreadDisplay:
|
||||
|
||||
# Set scroll region
|
||||
padding = 50 * self.zoom_level
|
||||
self.canvas.configure(scrollregion=(min_x - padding, min_y - padding, max_x + padding, max_y + padding))
|
||||
self.canvas.configure(
|
||||
scrollregion=(min_x - padding, min_y - padding, max_x + padding, max_y + padding)
|
||||
)
|
||||
|
||||
def _draw_card(self, drawn, x, y, w, h, layout_rotation):
|
||||
card_name = drawn.card.name
|
||||
@@ -701,8 +736,10 @@ class SpreadDisplay:
|
||||
|
||||
if not pil_image:
|
||||
# Draw placeholder
|
||||
self.canvas.create_rectangle(x-w/2, y-h/2, x+w/2, y+h/2, fill="white", outline="black")
|
||||
self.canvas.create_text(x, y, text=card_name, width=w-10)
|
||||
self.canvas.create_rectangle(
|
||||
x - w / 2, y - h / 2, x + w / 2, y + h / 2, fill="white", outline="black"
|
||||
)
|
||||
self.canvas.create_text(x, y, text=card_name, width=w - 10)
|
||||
else:
|
||||
# Total rotation
|
||||
rotation = layout_rotation
|
||||
@@ -728,7 +765,7 @@ class SpreadDisplay:
|
||||
|
||||
# Text Overlay
|
||||
# Calculate visual dimensions
|
||||
is_vertical = (layout_rotation % 180 == 0)
|
||||
is_vertical = layout_rotation % 180 == 0
|
||||
vis_w = w if is_vertical else h
|
||||
vis_h = h if is_vertical else w
|
||||
|
||||
@@ -739,35 +776,36 @@ class SpreadDisplay:
|
||||
# Height needed: approx 3 lines of text
|
||||
text_h = font_size * 3.5
|
||||
|
||||
bg_x1 = x - vis_w/2
|
||||
bg_y1 = y + vis_h/2 - text_h
|
||||
bg_x2 = x + vis_w/2
|
||||
bg_y2 = y + vis_h/2
|
||||
bg_x1 = x - vis_w / 2
|
||||
bg_y1 = y + vis_h / 2 - text_h
|
||||
bg_x2 = x + vis_w / 2
|
||||
bg_y2 = y + vis_h / 2
|
||||
|
||||
# Draw semi-transparent-ish background (stipple works on some platforms, otherwise solid)
|
||||
self.canvas.create_rectangle(
|
||||
bg_x1, bg_y1, bg_x2, bg_y2,
|
||||
fill="#000000", stipple="gray75", outline=""
|
||||
bg_x1, bg_y1, bg_x2, bg_y2, fill="#000000", stipple="gray75", outline=""
|
||||
)
|
||||
|
||||
# Position Name
|
||||
self.canvas.create_text(
|
||||
x, bg_y1 + font_size,
|
||||
x,
|
||||
bg_y1 + font_size,
|
||||
text=f"{drawn.position.number}. {drawn.position.name}",
|
||||
fill="white",
|
||||
font=("Arial", font_size, "bold"),
|
||||
width=vis_w - 4,
|
||||
justify="center"
|
||||
justify="center",
|
||||
)
|
||||
|
||||
# Meaning (shortened)
|
||||
self.canvas.create_text(
|
||||
x, bg_y1 + font_size * 2.2,
|
||||
x,
|
||||
bg_y1 + font_size * 2.2,
|
||||
text=drawn.position.meaning,
|
||||
fill="#ecf0f1",
|
||||
font=("Arial", int(font_size * 0.8)),
|
||||
width=vis_w - 4,
|
||||
justify="center"
|
||||
justify="center",
|
||||
)
|
||||
|
||||
def _draw_grid_fallback(self):
|
||||
@@ -777,15 +815,15 @@ class SpreadDisplay:
|
||||
card_height = 150 * self.zoom_level
|
||||
padding = 20 * self.zoom_level
|
||||
|
||||
min_x, min_y = float('inf'), float('inf')
|
||||
max_x, max_y = float('-inf'), float('-inf')
|
||||
min_x, min_y = float("inf"), float("inf")
|
||||
max_x, max_y = float("-inf"), float("-inf")
|
||||
|
||||
cols = 5
|
||||
for i, drawn in enumerate(self.reading.drawn_cards):
|
||||
row = i // cols
|
||||
col = i % cols
|
||||
|
||||
x = cx + (col - cols/2) * (card_width + padding)
|
||||
x = cx + (col - cols / 2) * (card_width + padding)
|
||||
y = cy + (row * 1.5) * (card_height + padding)
|
||||
|
||||
self._draw_card(drawn, x, y, card_width, card_height, 0)
|
||||
@@ -799,16 +837,18 @@ class SpreadDisplay:
|
||||
min_y = min(min_y, y - half_h)
|
||||
max_y = max(max_y, y + half_h)
|
||||
|
||||
if min_x == float('inf'): # No cards
|
||||
min_x, min_y, max_x, max_y = cx, cy, cx, cy
|
||||
if min_x == float("inf"): # No cards
|
||||
min_x, min_y, max_x, max_y = cx, cy, cx, cy
|
||||
|
||||
scroll_padding = 50 * self.zoom_level
|
||||
self.canvas.configure(scrollregion=(
|
||||
min_x - scroll_padding,
|
||||
min_y - scroll_padding,
|
||||
max_x + scroll_padding,
|
||||
max_y + scroll_padding
|
||||
))
|
||||
self.canvas.configure(
|
||||
scrollregion=(
|
||||
min_x - scroll_padding,
|
||||
min_y - scroll_padding,
|
||||
max_x + scroll_padding,
|
||||
max_y + scroll_padding,
|
||||
)
|
||||
)
|
||||
|
||||
def _zoom(self, factor):
|
||||
self.zoom_level *= factor
|
||||
@@ -962,7 +1002,10 @@ class SpreadDisplay:
|
||||
if pil_image.mode in ("RGBA", "PA"):
|
||||
# Create white background for transparency
|
||||
background = Image.new("RGB", pil_image.size, "white")
|
||||
background.paste(pil_image, mask=pil_image.split()[-1] if pil_image.mode == "RGBA" else None)
|
||||
background.paste(
|
||||
pil_image,
|
||||
mask=pil_image.split()[-1] if pil_image.mode == "RGBA" else None,
|
||||
)
|
||||
pil_image = background
|
||||
elif pil_image.mode != "RGB":
|
||||
pil_image = pil_image.convert("RGB")
|
||||
@@ -994,7 +1037,9 @@ class SpreadDisplay:
|
||||
except Exception as e:
|
||||
print(f" Debug: Paste failed for {drawn.card.name} at ({x}, {y}): {e}")
|
||||
print(f" Image mode: {pil_image.mode}, size: {pil_image.size}")
|
||||
print(f" Canvas size: {img.size}, position: ({int(x - pw / 2)}, {int(y - ph / 2)})")
|
||||
print(
|
||||
f" Canvas size: {img.size}, position: ({int(x - pw / 2)}, {int(y - ph / 2)})"
|
||||
)
|
||||
|
||||
if self.show_text:
|
||||
text_font = font
|
||||
@@ -1008,7 +1053,9 @@ class SpreadDisplay:
|
||||
by2 = int(y + card_height / 2)
|
||||
draw.rectangle([bx1, by1, bx2, by2], fill=(0, 0, 0, 180))
|
||||
draw.text((bx1 + 4, by1 + 4), label, fill="white", font=text_font)
|
||||
draw.text((bx1 + 4, by1 + 4 + text_h * 1.4), meaning, fill="#ecf0f1", font=text_font)
|
||||
draw.text(
|
||||
(bx1 + 4, by1 + 4 + text_h * 1.4), meaning, fill="#ecf0f1", font=text_font
|
||||
)
|
||||
|
||||
return img.convert("RGB")
|
||||
|
||||
|
||||
@@ -28,26 +28,26 @@ Access Patterns:
|
||||
print(clock) # Shows planetary positions
|
||||
"""
|
||||
|
||||
from .temporal import Year, Month, Day, Hour, Week
|
||||
from .astrology import PlanetPosition, ThalemaClock, Zodiac
|
||||
from .calendar import Calendar
|
||||
from .coordinates import Season, SolarEvent, TemporalCoordinates
|
||||
from .temporal import Day, Hour, Month, Week, Year
|
||||
from .time import TimeUtil
|
||||
from .coordinates import TemporalCoordinates, Season, SolarEvent
|
||||
from .astrology import ThalemaClock, Zodiac, PlanetPosition
|
||||
|
||||
__all__ = [
|
||||
# Temporal classes
|
||||
'Year',
|
||||
'Month',
|
||||
'Day',
|
||||
'Hour',
|
||||
'Week',
|
||||
'Calendar',
|
||||
'TimeUtil',
|
||||
'TemporalCoordinates',
|
||||
'Season',
|
||||
'SolarEvent',
|
||||
"Year",
|
||||
"Month",
|
||||
"Day",
|
||||
"Hour",
|
||||
"Week",
|
||||
"Calendar",
|
||||
"TimeUtil",
|
||||
"TemporalCoordinates",
|
||||
"Season",
|
||||
"SolarEvent",
|
||||
# Astrological classes
|
||||
'ThalemaClock',
|
||||
'Zodiac',
|
||||
'PlanetPosition',
|
||||
"ThalemaClock",
|
||||
"Zodiac",
|
||||
"PlanetPosition",
|
||||
]
|
||||
|
||||
@@ -20,12 +20,13 @@ Usage:
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Dict, Optional, List
|
||||
from enum import Enum
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
|
||||
class Zodiac(Enum):
|
||||
"""Zodiac signs with degree ranges (0-360°)."""
|
||||
|
||||
ARIES = ("♈", 0, 30)
|
||||
TAURUS = ("♉", 30, 60)
|
||||
GEMINI = ("♊", 60, 90)
|
||||
@@ -63,6 +64,7 @@ class Zodiac(Enum):
|
||||
@dataclass
|
||||
class PlanetPosition:
|
||||
"""Represents a planet's position with degree and zodiac."""
|
||||
|
||||
planet_name: str
|
||||
planet_symbol: str
|
||||
zodiac: Zodiac
|
||||
@@ -219,7 +221,10 @@ class ThalemaClock:
|
||||
return result
|
||||
|
||||
def display_compact(self) -> str:
|
||||
"""Display in compact format like: ☉︎ 25°Scorpio : ☾︎ 23°Libra : ☿︎ 2°Sagittarius : ♀︎ 13°Scorpio : ♂︎ 9°Sagittarius"""
|
||||
"""Display in compact format.
|
||||
|
||||
Example: ☉︎ 25°Scorpio : ☾︎ 23°Libra : ☿︎ 2°Sagittarius : ♀︎ 13°Scorpio : ♂︎ 9°Sagittarius
|
||||
"""
|
||||
return self.display_format()
|
||||
|
||||
def display_verbose(self) -> str:
|
||||
@@ -228,7 +233,9 @@ class ThalemaClock:
|
||||
for planet_name in ["Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn"]:
|
||||
if planet_name in self.positions:
|
||||
pos = self.positions[planet_name]
|
||||
lines.append(f"{planet_name:9} {pos.zodiac.name:11} {pos.degree_in_sign:5.1f}° {pos}")
|
||||
lines.append(
|
||||
f"{planet_name:9} {pos.zodiac.name:11} {pos.degree_in_sign:5.1f}° {pos}"
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
@@ -6,12 +6,13 @@ including Zodiac, Time cycles, and Astrological influences.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
|
||||
|
||||
@dataclass
|
||||
class Month:
|
||||
"""Represents a calendar month."""
|
||||
|
||||
number: int
|
||||
name: str
|
||||
zodiac_start: str
|
||||
@@ -21,6 +22,7 @@ class Month:
|
||||
@dataclass
|
||||
class Weekday:
|
||||
"""Represents weekday/weekend archetypes with planetary ties."""
|
||||
|
||||
number: int
|
||||
name: str
|
||||
planetary_correspondence: str
|
||||
@@ -35,6 +37,7 @@ class Weekday:
|
||||
@dataclass
|
||||
class Hour:
|
||||
"""Represents an hour with planetary correspondence."""
|
||||
|
||||
number: int
|
||||
name: str
|
||||
planetary_hours: List[str] = field(default_factory=list)
|
||||
@@ -43,6 +46,7 @@ class Hour:
|
||||
@dataclass
|
||||
class ClockHour:
|
||||
"""Represents a clock hour with both 24-hour and 12-hour phases."""
|
||||
|
||||
hour_24: int
|
||||
hour_12: int
|
||||
period: str # AM or PM
|
||||
@@ -63,6 +67,7 @@ class ClockHour:
|
||||
@dataclass
|
||||
class Zodiac:
|
||||
"""Represents a zodiac sign."""
|
||||
|
||||
name: str
|
||||
symbol: str
|
||||
element: str
|
||||
@@ -73,6 +78,7 @@ class Zodiac:
|
||||
@dataclass
|
||||
class Degree:
|
||||
"""Represents an astrological degree."""
|
||||
|
||||
number: int
|
||||
constellation: str
|
||||
ruling_planet: str
|
||||
@@ -82,6 +88,7 @@ class Degree:
|
||||
@dataclass
|
||||
class AstrologicalInfluence:
|
||||
"""Represents astrological influences."""
|
||||
|
||||
planet: str
|
||||
sign: str
|
||||
house: str
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
This module provides calendar-related operations and utilities.
|
||||
"""
|
||||
|
||||
from datetime import datetime, date
|
||||
from typing import Optional, Dict, Any
|
||||
from .temporal import Year, Month, Day, Week, Hour
|
||||
from datetime import date, datetime
|
||||
from typing import Any, Dict
|
||||
|
||||
from .temporal import Day, Hour, Month, Week, Year
|
||||
|
||||
|
||||
class Calendar:
|
||||
@@ -20,12 +21,12 @@ class Calendar:
|
||||
"""
|
||||
now = datetime.now()
|
||||
return {
|
||||
'year': Year(now.year),
|
||||
'month': Month(now.month),
|
||||
'day': Day(now.day),
|
||||
'hour': Hour(now.hour, now.minute, now.second),
|
||||
'week': Calendar.get_week(now.year, now.month, now.day),
|
||||
'datetime': now,
|
||||
"year": Year(now.year),
|
||||
"month": Month(now.month),
|
||||
"day": Day(now.day),
|
||||
"hour": Hour(now.hour, now.minute, now.second),
|
||||
"week": Calendar.get_week(now.year, now.month, now.day),
|
||||
"datetime": now,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -6,15 +6,16 @@ and other astronomical/calendrical coordinates.
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Optional, Dict, Any
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Season(Enum):
|
||||
"""The four seasons of the year."""
|
||||
SPRING = "Spring" # Vernal Equinox (Mar 20/21)
|
||||
SUMMER = "Summer" # Summer Solstice (Jun 20/21)
|
||||
AUTUMN = "Autumn" # Autumnal Equinox (Sep 22/23)
|
||||
WINTER = "Winter" # Winter Solstice (Dec 21/22)
|
||||
|
||||
SPRING = "Spring" # Vernal Equinox (Mar 20/21)
|
||||
SUMMER = "Summer" # Summer Solstice (Jun 20/21)
|
||||
AUTUMN = "Autumn" # Autumnal Equinox (Sep 22/23)
|
||||
WINTER = "Winter" # Winter Solstice (Dec 21/22)
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -26,8 +27,9 @@ class SolarEvent:
|
||||
date: The approximate date of the event
|
||||
season: The associated season
|
||||
"""
|
||||
|
||||
event_type: str # "solstice" or "equinox"
|
||||
date: tuple # (month, day)
|
||||
date: tuple # (month, day)
|
||||
season: Season
|
||||
|
||||
def __str__(self) -> str:
|
||||
@@ -118,4 +120,4 @@ class TemporalCoordinates:
|
||||
Day of year
|
||||
"""
|
||||
days_in_months = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
||||
return sum(days_in_months[:month-1]) + day
|
||||
return sum(days_in_months[: month - 1]) + day
|
||||
|
||||
@@ -4,12 +4,12 @@ This module provides the core temporal domain classes used throughout the system
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, List
|
||||
|
||||
|
||||
@dataclass
|
||||
class Year:
|
||||
"""Represents a year in the Gregorian calendar."""
|
||||
|
||||
value: int
|
||||
|
||||
def __str__(self) -> str:
|
||||
@@ -22,6 +22,7 @@ class Year:
|
||||
@dataclass
|
||||
class Month:
|
||||
"""Represents a month (1-12)."""
|
||||
|
||||
value: int
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
@@ -32,8 +33,18 @@ class Month:
|
||||
def name(self) -> str:
|
||||
"""Get the month name."""
|
||||
names = [
|
||||
"January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
]
|
||||
return names[self.value - 1]
|
||||
|
||||
@@ -47,6 +58,7 @@ class Month:
|
||||
@dataclass
|
||||
class Week:
|
||||
"""Represents a week in the calendar."""
|
||||
|
||||
number: int # 1-53
|
||||
year: int
|
||||
|
||||
@@ -64,6 +76,7 @@ class Week:
|
||||
@dataclass
|
||||
class Day:
|
||||
"""Represents a day of the month (1-31)."""
|
||||
|
||||
value: int
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
@@ -85,6 +98,7 @@ class Day:
|
||||
@dataclass
|
||||
class Hour:
|
||||
"""Represents an hour in 24-hour format (0-23)."""
|
||||
|
||||
value: int
|
||||
minute: int = 0
|
||||
second: int = 0
|
||||
|
||||
@@ -4,7 +4,7 @@ This module handles time-related operations and conversions.
|
||||
"""
|
||||
|
||||
from datetime import datetime, time
|
||||
from typing import Dict, Any, Optional
|
||||
from typing import Dict
|
||||
|
||||
|
||||
class TimeUtil:
|
||||
@@ -35,9 +35,9 @@ class TimeUtil:
|
||||
secs = remaining % 60
|
||||
|
||||
return {
|
||||
'hours': hours,
|
||||
'minutes': minutes,
|
||||
'seconds': secs,
|
||||
"hours": hours,
|
||||
"minutes": minutes,
|
||||
"seconds": secs,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
"""Utility modules for the Tarot project."""
|
||||
|
||||
from .attributes import (
|
||||
Cipher,
|
||||
CipherResult,
|
||||
Color,
|
||||
Colorscale,
|
||||
Element,
|
||||
ElementType,
|
||||
God,
|
||||
Note,
|
||||
Number,
|
||||
Perfume,
|
||||
Planet,
|
||||
)
|
||||
from .filter import (
|
||||
universal_filter,
|
||||
get_filterable_fields,
|
||||
describe_filter_fields,
|
||||
filter_by,
|
||||
format_results,
|
||||
get_filter_autocomplete,
|
||||
describe_filter_fields,
|
||||
)
|
||||
from .attributes import (
|
||||
Note,
|
||||
Element,
|
||||
ElementType,
|
||||
Number,
|
||||
Color,
|
||||
Colorscale,
|
||||
Planet,
|
||||
God,
|
||||
Perfume,
|
||||
Cipher,
|
||||
CipherResult,
|
||||
get_filterable_fields,
|
||||
universal_filter,
|
||||
)
|
||||
from .misc import (
|
||||
Personality,
|
||||
MBTIType,
|
||||
Personality,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
|
||||
@@ -7,12 +7,13 @@ exclusively to any single namespace.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, List, Optional, Sequence, Set, Tuple
|
||||
from typing import Dict, List, Optional, Sequence, Set, Tuple
|
||||
|
||||
|
||||
@dataclass
|
||||
class Meaning:
|
||||
"""Represents the meaning of a card."""
|
||||
|
||||
upright: str
|
||||
reversed: str
|
||||
|
||||
@@ -20,6 +21,7 @@ class Meaning:
|
||||
@dataclass(frozen=True)
|
||||
class Note:
|
||||
"""Represents a musical note with its properties."""
|
||||
|
||||
name: str # e.g., "C", "D", "E", "F#", "G", "A", "B"
|
||||
frequency: float # Frequency in Hz (A4 = 440 Hz)
|
||||
semitone: int # Position in chromatic scale (0-11)
|
||||
@@ -40,6 +42,7 @@ class Note:
|
||||
@dataclass
|
||||
class Element:
|
||||
"""Represents one of the four elements."""
|
||||
|
||||
name: str
|
||||
symbol: str
|
||||
color: str
|
||||
@@ -50,6 +53,7 @@ class Element:
|
||||
@dataclass
|
||||
class ElementType:
|
||||
"""Represents an elemental force (Fire, Water, Air, Earth, Spirit)."""
|
||||
|
||||
name: str
|
||||
symbol: str
|
||||
direction: str
|
||||
@@ -68,11 +72,12 @@ class ElementType:
|
||||
@dataclass
|
||||
class Number:
|
||||
"""Represents a number (1-9) with Kabbalistic attributes."""
|
||||
|
||||
value: int
|
||||
sephera: str
|
||||
element: str
|
||||
compliment: int
|
||||
color: Optional['Color'] = None
|
||||
color: Optional["Color"] = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not (1 <= self.value <= 9):
|
||||
@@ -94,6 +99,7 @@ class Colorscale:
|
||||
- Emperor Scale (Vau): Son/Form, active expression, concrete manifestation
|
||||
- Empress Scale (He final): Daughter, physical manifestation, receptivity in Assiah
|
||||
"""
|
||||
|
||||
name: str # Sephira/Path name (e.g., "Kether", "Path of Aleph")
|
||||
number: int # 1-10 for Sephiroth, 11-32 for Paths
|
||||
king_scale: str # Yod - Father principle
|
||||
@@ -109,6 +115,7 @@ class Colorscale:
|
||||
@dataclass
|
||||
class Color:
|
||||
"""Represents a color with Kabbalistic correspondences."""
|
||||
|
||||
name: str
|
||||
hex_value: str
|
||||
rgb: Tuple[int, int, int]
|
||||
@@ -133,6 +140,7 @@ class Color:
|
||||
@dataclass
|
||||
class Planet:
|
||||
"""Represents a planetary correspondence entry."""
|
||||
|
||||
name: str
|
||||
symbol: str
|
||||
element: str
|
||||
@@ -170,6 +178,7 @@ class Planet:
|
||||
@dataclass
|
||||
class God:
|
||||
"""Unified deity representation that synchronizes multiple pantheons."""
|
||||
|
||||
name: str
|
||||
culture: str
|
||||
pantheon: str
|
||||
@@ -231,7 +240,11 @@ class God:
|
||||
lines.append(f" associated_planet: {self.associated_planet.name}")
|
||||
|
||||
if self.associated_element:
|
||||
elem_name = self.associated_element.name if hasattr(self.associated_element, 'name') else str(self.associated_element)
|
||||
elem_name = (
|
||||
self.associated_element.name
|
||||
if hasattr(self.associated_element, "name")
|
||||
else str(self.associated_element)
|
||||
)
|
||||
lines.append(f" associated_element: {elem_name}")
|
||||
|
||||
if self.tarot_trumps:
|
||||
@@ -249,6 +262,7 @@ class God:
|
||||
@dataclass
|
||||
class Perfume:
|
||||
"""Represents a perfume/incense correspondence in Kabbalah."""
|
||||
|
||||
name: str
|
||||
alternative_names: List[str] = field(default_factory=list)
|
||||
scent_profile: str = "" # e.g., "Resinous", "Floral", "Spicy", "Earthy"
|
||||
@@ -362,9 +376,7 @@ class Cipher:
|
||||
expanded.append(self.pattern[idx % len(self.pattern)])
|
||||
idx += 1
|
||||
return expanded
|
||||
raise ValueError(
|
||||
"Cipher pattern length does not match alphabet and cycling is disabled"
|
||||
)
|
||||
raise ValueError("Cipher pattern length does not match alphabet and cycling is disabled")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
||||
@@ -21,11 +21,17 @@ Usage:
|
||||
fields = get_filterable_fields(TarotLetter)
|
||||
"""
|
||||
|
||||
from typing import List, Any, TypeVar, Union, Dict
|
||||
from dataclasses import is_dataclass, fields
|
||||
from utils.object_formatting import get_item_label, is_nested_object, get_object_attributes, format_value
|
||||
from dataclasses import fields, is_dataclass
|
||||
from typing import Any, Dict, List, TypeVar
|
||||
|
||||
T = TypeVar('T') # Generic type for any dataclass
|
||||
from utils.object_formatting import (
|
||||
format_value,
|
||||
get_item_label,
|
||||
get_object_attributes,
|
||||
is_nested_object,
|
||||
)
|
||||
|
||||
T = TypeVar("T") # Generic type for any dataclass
|
||||
|
||||
|
||||
def get_filterable_fields(dataclass_type) -> List[str]:
|
||||
@@ -96,10 +102,7 @@ def _matches_filter(obj: Any, key: str, value: Any) -> bool:
|
||||
for check_value in values_to_check:
|
||||
# Handle list attributes (like keywords, colors, etc.)
|
||||
if isinstance(attr_value, list):
|
||||
if any(
|
||||
str(check_value).lower() == str(item).lower()
|
||||
for item in attr_value
|
||||
):
|
||||
if any(str(check_value).lower() == str(item).lower() for item in attr_value):
|
||||
return True
|
||||
continue
|
||||
|
||||
@@ -117,8 +120,8 @@ def _matches_filter(obj: Any, key: str, value: Any) -> bool:
|
||||
|
||||
# Handle nested object comparison: if attr_value has a 'name' attribute,
|
||||
# try matching the value against that (e.g., suit="Cups" vs Suit(name="Cups"))
|
||||
if hasattr(attr_value, 'name'):
|
||||
nested_name = getattr(attr_value, 'name', None)
|
||||
if hasattr(attr_value, "name"):
|
||||
nested_name = getattr(attr_value, "name", None)
|
||||
if nested_name is not None:
|
||||
if str(nested_name).lower() == str(check_value).lower():
|
||||
return True
|
||||
@@ -184,10 +187,7 @@ def universal_filter(items: List[T], **kwargs) -> List[T]:
|
||||
# Apply field alias if it exists
|
||||
actual_key = field_aliases.get(key, key)
|
||||
|
||||
results = [
|
||||
obj for obj in results
|
||||
if _matches_filter(obj, actual_key, value)
|
||||
]
|
||||
results = [obj for obj in results if _matches_filter(obj, actual_key, value)]
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ This module contains specialized utilities that don't fit into other categories.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tarot.deck.deck import CourtCard
|
||||
@@ -14,6 +14,7 @@ if TYPE_CHECKING:
|
||||
|
||||
class MBTIType(Enum):
|
||||
"""16 MBTI personality types."""
|
||||
|
||||
ISTJ = "ISTJ"
|
||||
ISFJ = "ISFJ"
|
||||
INFJ = "INFJ"
|
||||
@@ -62,39 +63,36 @@ class Personality:
|
||||
"""
|
||||
|
||||
mbti_type: MBTIType
|
||||
court_card: Optional['CourtCard'] = None
|
||||
court_card: Optional["CourtCard"] = None
|
||||
description: str = ""
|
||||
|
||||
# Direct MBTI-to-CourtCard mapping (1-to-1 relationship)
|
||||
# Format: MBTI_TYPE -> (Rank, Suit)
|
||||
_MBTI_TO_CARD_MAPPING = {
|
||||
# KINGS (E + J) - Extraverted Judgers
|
||||
"ENTJ": ("Knight", "Wands"), # Fiery, forceful leadership
|
||||
"ENFJ": ("Knight", "Cups"), # Sensitive, mission-driven
|
||||
"ESTJ": ("Knight", "Swords"), # Practical, pragmatic
|
||||
"ENTJ": ("Knight", "Wands"), # Fiery, forceful leadership
|
||||
"ENFJ": ("Knight", "Cups"), # Sensitive, mission-driven
|
||||
"ESTJ": ("Knight", "Swords"), # Practical, pragmatic
|
||||
"ESFJ": ("Knight", "Pentacles"), # Sociable, consensus-seeking
|
||||
|
||||
# QUEENS (I + J) - Introverted Judgers
|
||||
"INTJ": ("Queen", "Wands"), # Analytical, self-motivated
|
||||
"INFJ": ("Queen", "Cups"), # Sensitive, interconnected
|
||||
"ISTJ": ("Queen", "Swords"), # Pragmatic, duty-fulfiller
|
||||
"ISFJ": ("Queen", "Pentacles"), # Caring, earth-mother type
|
||||
|
||||
"INTJ": ("Queen", "Wands"), # Analytical, self-motivated
|
||||
"INFJ": ("Queen", "Cups"), # Sensitive, interconnected
|
||||
"ISTJ": ("Queen", "Swords"), # Pragmatic, duty-fulfiller
|
||||
"ISFJ": ("Queen", "Pentacles"), # Caring, earth-mother type
|
||||
# PRINCES (E + P) - Extraverted Perceivers
|
||||
"ENTP": ("Prince", "Wands"), # Visionary, quick-study
|
||||
"ENFP": ("Prince", "Cups"), # Inspiring, intuitive
|
||||
"ESTP": ("Prince", "Swords"), # Action-oriented, risk-taker
|
||||
"ENTP": ("Prince", "Wands"), # Visionary, quick-study
|
||||
"ENFP": ("Prince", "Cups"), # Inspiring, intuitive
|
||||
"ESTP": ("Prince", "Swords"), # Action-oriented, risk-taker
|
||||
"ESFP": ("Prince", "Pentacles"), # Aesthete, sensualist
|
||||
|
||||
# PRINCESSES (I + P) - Introverted Perceivers
|
||||
"INTP": ("Princess", "Wands"), # Thinker par excellence
|
||||
"INFP": ("Princess", "Cups"), # Idealistic, devoted
|
||||
"ISTP": ("Princess", "Swords"), # Observer, mechanic
|
||||
"ISFP": ("Princess", "Pentacles"), # Aesthete, free spirit
|
||||
"INTP": ("Princess", "Wands"), # Thinker par excellence
|
||||
"INFP": ("Princess", "Cups"), # Idealistic, devoted
|
||||
"ISTP": ("Princess", "Swords"), # Observer, mechanic
|
||||
"ISFP": ("Princess", "Pentacles"), # Aesthete, free spirit
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_mbti(cls, mbti_type: str, deck: Optional[object] = None) -> 'Personality':
|
||||
def from_mbti(cls, mbti_type: str, deck: Optional[object] = None) -> "Personality":
|
||||
"""
|
||||
Create a Personality from an MBTI type string.
|
||||
|
||||
@@ -140,7 +138,7 @@ class Personality:
|
||||
from tarot import Tarot
|
||||
|
||||
# Use provided deck or default to Tarot
|
||||
d = deck if hasattr(deck, 'card') else Tarot.deck
|
||||
d = deck if hasattr(deck, "card") else Tarot.deck
|
||||
|
||||
cards = d.card.filter(type="Court", court_rank=rank, suit=suit)
|
||||
if cards:
|
||||
@@ -152,17 +150,14 @@ class Personality:
|
||||
"ENFJ": "The Protagonist - Inspiring, empathetic, leader of Cups",
|
||||
"ESTJ": "The Supervisor - Practical, decisive, leader of Swords",
|
||||
"ESFJ": "The Consul - Sociable, cooperative, leader of Pentacles",
|
||||
|
||||
"INTJ": "The Architect - Strategic, logical, sage of Wands",
|
||||
"INFJ": "The Advocate - Insightful, idealistic, sage of Cups",
|
||||
"ISTJ": "The Logistician - Practical, reliable, sage of Swords",
|
||||
"ISFJ": "The Defender - Caring, conscientious, sage of Pentacles",
|
||||
|
||||
"ENTP": "The Debater - Innovative, quick-witted, explorer of Wands",
|
||||
"ENFP": "The Campaigner - Enthusiastic, social, explorer of Cups",
|
||||
"ESTP": "The Entrepreneur - Energetic, bold, explorer of Swords",
|
||||
"ESFP": "The Entertainer - Spontaneous, outgoing, explorer of Pentacles",
|
||||
|
||||
"INTP": "The Logician - Analytical, curious, seeker of Wands",
|
||||
"INFP": "The Mediator - Idealistic, authentic, seeker of Cups",
|
||||
"ISTP": "The Virtuoso - Practical, observant, seeker of Swords",
|
||||
@@ -170,9 +165,7 @@ class Personality:
|
||||
}
|
||||
|
||||
return cls(
|
||||
mbti_type=mbti_enum,
|
||||
court_card=court_card,
|
||||
description=descriptions.get(mbti_type, "")
|
||||
mbti_type=mbti_enum, court_card=court_card, description=descriptions.get(mbti_type, "")
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
@@ -16,14 +16,13 @@ Usage:
|
||||
|
||||
from typing import Any, List, Tuple
|
||||
|
||||
|
||||
# Type checking predicates
|
||||
SCALAR_TYPES = (str, int, float, bool, list, dict, type(None))
|
||||
|
||||
|
||||
def is_dataclass(obj: Any) -> bool:
|
||||
"""Check if object is a dataclass."""
|
||||
return hasattr(obj, '__dataclass_fields__')
|
||||
return hasattr(obj, "__dataclass_fields__")
|
||||
|
||||
|
||||
def is_nested_object(obj: Any) -> bool:
|
||||
@@ -36,7 +35,7 @@ def is_nested_object(obj: Any) -> bool:
|
||||
return True
|
||||
if is_dataclass(obj):
|
||||
return True
|
||||
return hasattr(obj, '__dict__') and not isinstance(obj, SCALAR_TYPES)
|
||||
return hasattr(obj, "__dict__") and not isinstance(obj, SCALAR_TYPES)
|
||||
|
||||
|
||||
def is_scalar(obj: Any) -> bool:
|
||||
@@ -60,16 +59,16 @@ def get_item_label(item: Any, fallback: str = "item") -> str:
|
||||
Returns:
|
||||
A string suitable for display as an item label
|
||||
"""
|
||||
if hasattr(item, 'name'):
|
||||
return str(getattr(item, 'name', fallback))
|
||||
elif hasattr(item, 'transliteration'):
|
||||
return str(getattr(item, 'transliteration', fallback))
|
||||
if hasattr(item, "name"):
|
||||
return str(getattr(item, "name", fallback))
|
||||
elif hasattr(item, "transliteration"):
|
||||
return str(getattr(item, "transliteration", fallback))
|
||||
return str(item) if item is not None else fallback
|
||||
|
||||
|
||||
def get_dataclass_fields(obj: Any) -> List[str]:
|
||||
"""Get list of field names from a dataclass."""
|
||||
if hasattr(obj, '__dataclass_fields__'):
|
||||
if hasattr(obj, "__dataclass_fields__"):
|
||||
return list(obj.__dataclass_fields__.keys())
|
||||
return []
|
||||
|
||||
@@ -90,9 +89,9 @@ def get_object_attributes(obj: Any) -> List[Tuple[str, Any]]:
|
||||
for field_name in obj.__dataclass_fields__:
|
||||
value = getattr(obj, field_name, None)
|
||||
attributes.append((field_name, value))
|
||||
elif hasattr(obj, '__dict__'):
|
||||
elif hasattr(obj, "__dict__"):
|
||||
for field_name, value in obj.__dict__.items():
|
||||
if not field_name.startswith('_'):
|
||||
if not field_name.startswith("_"):
|
||||
attributes.append((field_name, value))
|
||||
|
||||
return attributes
|
||||
@@ -121,16 +120,21 @@ def format_value(value: Any, indent: int = 2) -> str:
|
||||
if is_nested_object(value):
|
||||
# Classes that have custom __str__ implementations should use them
|
||||
obj_class = type(value).__name__
|
||||
has_custom_str = (
|
||||
hasattr(value, '__str__') and
|
||||
type(value).__str__ is not object.__str__
|
||||
)
|
||||
has_custom_str = hasattr(value, "__str__") and type(value).__str__ is not object.__str__
|
||||
|
||||
if has_custom_str and obj_class in ['Path', 'Planet', 'Perfume', 'God', 'Colorscale', 'Sephera', 'ElementType']:
|
||||
if has_custom_str and obj_class in [
|
||||
"Path",
|
||||
"Planet",
|
||||
"Perfume",
|
||||
"God",
|
||||
"Colorscale",
|
||||
"Sephera",
|
||||
"ElementType",
|
||||
]:
|
||||
# Use the custom __str__ method and indent each line
|
||||
custom_output = str(value)
|
||||
lines = []
|
||||
for line in custom_output.split('\n'):
|
||||
for line in custom_output.split("\n"):
|
||||
if line.strip(): # Skip empty lines
|
||||
lines.append(f"{indent_str}{line}")
|
||||
return "\n".join(lines)
|
||||
|
||||
@@ -20,9 +20,10 @@ Usage:
|
||||
"""
|
||||
|
||||
from typing import Any, Callable, Dict, Generic, List, Optional, TypeVar, Union
|
||||
|
||||
from utils.object_formatting import format_value, get_object_attributes, is_nested_object
|
||||
|
||||
T = TypeVar('T')
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class QueryResult:
|
||||
@@ -32,18 +33,18 @@ class QueryResult:
|
||||
self.data = data
|
||||
|
||||
def __repr__(self) -> str:
|
||||
if hasattr(self.data, '__repr__'):
|
||||
if hasattr(self.data, "__repr__"):
|
||||
return repr(self.data)
|
||||
return f"{self.__class__.__name__}({self.data})"
|
||||
|
||||
def __str__(self) -> str:
|
||||
if hasattr(self.data, '__str__'):
|
||||
if hasattr(self.data, "__str__"):
|
||||
return str(self.data)
|
||||
return repr(self)
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
"""Pass through attribute access to the wrapped data."""
|
||||
if name.startswith('_'):
|
||||
if name.startswith("_"):
|
||||
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
||||
return getattr(self.data, name)
|
||||
|
||||
@@ -61,7 +62,7 @@ class Query:
|
||||
self._data = data if isinstance(data, list) else list(data.values())
|
||||
self._filters: List[Callable[[T], bool]] = []
|
||||
|
||||
def filter(self, expression: str) -> 'Query':
|
||||
def filter(self, expression: str) -> "Query":
|
||||
"""
|
||||
Filter by key:value expression.
|
||||
|
||||
@@ -74,12 +75,12 @@ class Query:
|
||||
Supports multiple filters by chaining:
|
||||
.filter('number:1').filter('name:creative')
|
||||
"""
|
||||
key, value = expression.split(':', 1) if ':' in expression else (expression, '')
|
||||
key, value = expression.split(":", 1) if ":" in expression else (expression, "")
|
||||
|
||||
def filter_func(item: T) -> bool:
|
||||
# Special handling for 'name' key
|
||||
if key == 'name':
|
||||
if hasattr(item, 'name'):
|
||||
if key == "name":
|
||||
if hasattr(item, "name"):
|
||||
value_lower = value.lower()
|
||||
item_name = str(item.name).lower()
|
||||
return value_lower == item_name or value_lower in item_name
|
||||
@@ -97,16 +98,16 @@ class Query:
|
||||
self._filters.append(filter_func)
|
||||
return self
|
||||
|
||||
def name(self, value: str) -> Optional['QueryResult']:
|
||||
def name(self, value: str) -> Optional["QueryResult"]:
|
||||
"""
|
||||
Deprecated: Use .filter('name:value') instead.
|
||||
|
||||
Find item by name (exact or partial match, case-insensitive).
|
||||
Returns QueryResult wrapping the found item, or None if not found.
|
||||
"""
|
||||
return self.filter(f'name:{value}').first()
|
||||
return self.filter(f"name:{value}").first()
|
||||
|
||||
def get(self) -> Optional['QueryResult']:
|
||||
def get(self) -> Optional["QueryResult"]:
|
||||
"""
|
||||
Get first result matching all applied filters.
|
||||
|
||||
@@ -142,7 +143,7 @@ class Query:
|
||||
"""
|
||||
return [item for item in self._data if all(f(item) for f in self._filters)]
|
||||
|
||||
def first(self) -> Optional['QueryResult']:
|
||||
def first(self) -> Optional["QueryResult"]:
|
||||
"""Alias for get() - returns first matching item."""
|
||||
return self.get()
|
||||
|
||||
@@ -205,8 +206,6 @@ class CollectionAccessor(Generic[T]):
|
||||
Returns a formatted string with each item separated by blank lines.
|
||||
Nested objects are indented and separated with their own sections.
|
||||
"""
|
||||
from utils.object_formatting import is_nested_object, get_object_attributes
|
||||
|
||||
data = self.all()
|
||||
if not data:
|
||||
return "(empty collection)"
|
||||
@@ -241,7 +240,7 @@ class CollectionAccessor(Generic[T]):
|
||||
class FilterableDict(dict):
|
||||
"""Dict subclass that provides .filter() method for dynamic querying."""
|
||||
|
||||
def filter(self, expression: str = '') -> Query:
|
||||
def filter(self, expression: str = "") -> Query:
|
||||
"""
|
||||
Filter dict values by attribute:value expression.
|
||||
|
||||
@@ -259,8 +258,6 @@ class FilterableDict(dict):
|
||||
Returns a formatted string with each item separated by blank lines.
|
||||
Nested objects are indented and separated with their own sections.
|
||||
"""
|
||||
from utils.object_formatting import is_nested_object, get_object_attributes, format_value
|
||||
|
||||
if not self:
|
||||
return "(empty collection)"
|
||||
|
||||
@@ -283,7 +280,7 @@ class FilterableDict(dict):
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def make_filterable(data: Union[Dict[Any, T], List[T]]) -> Union['FilterableDict', Query]:
|
||||
def make_filterable(data: Union[Dict[Any, T], List[T]]) -> Union["FilterableDict", Query]:
|
||||
"""
|
||||
Convert dict or list to a filterable object with .filter() support.
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import sys
|
||||
sys.path.insert(0, 'src')
|
||||
|
||||
# Test parsing logic
|
||||
test_cases = [
|
||||
('2,11,20', [2, 11, 20]),
|
||||
('1 5 10', [1, 5, 10]),
|
||||
('3, 7, 14', [3, 7, 14]),
|
||||
]
|
||||
|
||||
for test_input, expected in test_cases:
|
||||
# Simulate the parsing logic
|
||||
parts = ['display-cards'] + test_input.split()
|
||||
nums = []
|
||||
tokens = []
|
||||
for tok in parts[1:]:
|
||||
if ',' in tok:
|
||||
tokens.extend(tok.split(','))
|
||||
else:
|
||||
tokens.append(tok)
|
||||
|
||||
for tok in tokens:
|
||||
tok = tok.strip()
|
||||
if tok:
|
||||
try:
|
||||
nums.append(int(tok))
|
||||
except ValueError:
|
||||
nums.append(-1)
|
||||
|
||||
status = '✓' if nums == expected else '✗'
|
||||
print(f'{status} Input: "{test_input}" -> {nums} (expected {expected})')
|
||||
|
||||
# Test with actual cards
|
||||
from tarot.tarot_api import Tarot
|
||||
print("\n✓ Parsing logic works! Now test in REPL with:")
|
||||
print(" display-cards 2,11,20")
|
||||
print(" display-cards 1 5 10")
|
||||
@@ -1,10 +0,0 @@
|
||||
from tarot import Tarot
|
||||
from tarot.ui import display_spread
|
||||
|
||||
# Draw a spread
|
||||
print("Drawing Celtic Cross spread...")
|
||||
reading = Tarot.deck.card.spread("Celtic Cross")
|
||||
|
||||
# Display it
|
||||
print("Displaying spread...")
|
||||
display_spread(reading)
|
||||
@@ -3,19 +3,41 @@
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from src.tarot.attributes import (
|
||||
Month, Day, Weekday, Hour, ClockHour, Zodiac, Suit, Meaning, Letter, Sephera, Degree, Element,
|
||||
AstrologicalInfluence, TreeOfLife, Correspondences, CardImage,
|
||||
EnglishAlphabet, GreekAlphabet, HebrewAlphabet, Number, Color, Planet, God,
|
||||
Cipher, CipherResult,
|
||||
AstrologicalInfluence,
|
||||
CardImage,
|
||||
Cipher,
|
||||
CipherResult,
|
||||
ClockHour,
|
||||
Color,
|
||||
Correspondences,
|
||||
Day,
|
||||
Degree,
|
||||
Element,
|
||||
EnglishAlphabet,
|
||||
God,
|
||||
GreekAlphabet,
|
||||
HebrewAlphabet,
|
||||
Hour,
|
||||
Letter,
|
||||
Meaning,
|
||||
Month,
|
||||
Number,
|
||||
Planet,
|
||||
Sephera,
|
||||
Suit,
|
||||
TreeOfLife,
|
||||
Weekday,
|
||||
Zodiac,
|
||||
)
|
||||
from src.tarot.card.data import CardDataLoader, calculate_digital_root
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Basic Attribute Tests
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class TestMonth:
|
||||
def test_month_creation(self):
|
||||
month = Month(1, "January", "Capricorn", "Aquarius")
|
||||
@@ -24,10 +46,7 @@ class TestMonth:
|
||||
assert month.zodiac_start == "Capricorn"
|
||||
|
||||
def test_month_all_months(self):
|
||||
months = [
|
||||
Month(i, f"Month_{i}", "Sign_1", "Sign_2")
|
||||
for i in range(1, 13)
|
||||
]
|
||||
months = [Month(i, f"Month_{i}", "Sign_1", "Sign_2") for i in range(1, 13)]
|
||||
assert len(months) == 12
|
||||
assert months[0].number == 1
|
||||
assert months[11].number == 12
|
||||
@@ -41,10 +60,7 @@ class TestDay:
|
||||
assert day.planetary_correspondence == "Sun"
|
||||
|
||||
def test_all_weekdays(self):
|
||||
days = [
|
||||
Day(i, f"Day_{i}", f"Planet_{i}")
|
||||
for i in range(1, 8)
|
||||
]
|
||||
days = [Day(i, f"Day_{i}", f"Planet_{i}") for i in range(1, 8)]
|
||||
assert len(days) == 7
|
||||
|
||||
|
||||
@@ -99,6 +115,7 @@ class TestMeaning:
|
||||
# Sepheric Tests
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class TestSephera:
|
||||
def test_sephera_creation(self):
|
||||
sephera = Sephera(1, "Kether", "כתר", "Crown", "Metatron", "Chaioth", "Primum")
|
||||
@@ -118,6 +135,7 @@ class TestSephera:
|
||||
# Alphabet Tests
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class TestEnglishAlphabet:
|
||||
def test_english_letter_creation(self):
|
||||
letter = EnglishAlphabet("A", 1, "ay")
|
||||
@@ -189,6 +207,7 @@ class TestHebrewAlphabet:
|
||||
# Number Tests
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class TestNumber:
|
||||
def test_number_creation(self):
|
||||
num = Number(1, "Kether", "Spirit", 0) # compliment is auto-calculated
|
||||
@@ -220,6 +239,7 @@ class TestNumber:
|
||||
# Color Tests
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class TestColor:
|
||||
def test_color_creation(self):
|
||||
color = Color("Red", "#FF0000", (255, 0, 0), "Gevurah", 5, "Fire", "Briah", "Power")
|
||||
@@ -248,7 +268,9 @@ class TestColor:
|
||||
for r in [0, 128, 255]:
|
||||
for g in [0, 128, 255]:
|
||||
for b in [0, 128, 255]:
|
||||
color = Color("Test", "#000000", (r, g, b), "Sephera", 1, "Element", "Scale", "Meaning")
|
||||
color = Color(
|
||||
"Test", "#000000", (r, g, b), "Sephera", 1, "Element", "Scale", "Meaning"
|
||||
)
|
||||
assert color.rgb == (r, g, b)
|
||||
|
||||
|
||||
@@ -260,7 +282,9 @@ class TestColor:
|
||||
class TestPlanet:
|
||||
def test_planet_creation(self):
|
||||
number = Number(6, "Tiphareth", "Fire", 0)
|
||||
color = Color("Gold", "#FFD700", (255, 215, 0), "Tiphareth", 6, "Fire", "Yetzirah", "Beauty")
|
||||
color = Color(
|
||||
"Gold", "#FFD700", (255, 215, 0), "Tiphareth", 6, "Fire", "Yetzirah", "Beauty"
|
||||
)
|
||||
planet = Planet(
|
||||
name="Sun",
|
||||
symbol="☉",
|
||||
@@ -342,6 +366,7 @@ class TestGod:
|
||||
# Cipher Tests
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class TestCipher:
|
||||
def test_cipher_mapping_basic(self):
|
||||
cipher = Cipher("Test", "test", [1, 2, 3])
|
||||
@@ -374,6 +399,7 @@ class TestCipherResult:
|
||||
# Digital Root Tests
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class TestDigitalRoot:
|
||||
def test_digital_root_single_digit(self):
|
||||
"""Single digits should return themselves."""
|
||||
@@ -389,7 +415,7 @@ class TestDigitalRoot:
|
||||
|
||||
def test_digital_root_large_numbers(self):
|
||||
"""Test large numbers."""
|
||||
assert calculate_digital_root(99) == 9 # 9+9 = 18, 1+8 = 9
|
||||
assert calculate_digital_root(99) == 9 # 9+9 = 18, 1+8 = 9
|
||||
assert calculate_digital_root(100) == 1 # 1+0+0 = 1
|
||||
assert calculate_digital_root(123) == 6 # 1+2+3 = 6
|
||||
|
||||
@@ -398,7 +424,7 @@ class TestDigitalRoot:
|
||||
# Major Arcana cards 0-21
|
||||
assert calculate_digital_root(14) == 5 # Card 14 (Temperance) -> 5
|
||||
assert calculate_digital_root(21) == 3 # Card 21 (The World) -> 3
|
||||
assert calculate_digital_root(1) == 1 # Card 1 (Magician) -> 1
|
||||
assert calculate_digital_root(1) == 1 # Card 1 (Magician) -> 1
|
||||
|
||||
def test_digital_root_invalid_input(self):
|
||||
"""Test that invalid inputs raise errors."""
|
||||
@@ -413,6 +439,7 @@ class TestDigitalRoot:
|
||||
# CardDataLoader Tests
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class TestCardDataLoader:
|
||||
@pytest.fixture
|
||||
def loader(self):
|
||||
@@ -463,13 +490,15 @@ class TestCardDataLoader:
|
||||
|
||||
def test_trigram_line_diagram(self, loader):
|
||||
from letter import trigram
|
||||
|
||||
tri = trigram.trigram.name("Zhen") # Thunder
|
||||
assert tri is not None
|
||||
assert tri.data.line_diagram == "|::"
|
||||
|
||||
def test_hexagram_line_diagram(self, loader):
|
||||
from letter import hexagram
|
||||
hex_result = hexagram.hexagram.filter('number:1').first()
|
||||
|
||||
hex_result = hexagram.hexagram.filter("number:1").first()
|
||||
assert hex_result is not None
|
||||
assert hex_result.data.line_diagram == "||||||"
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import pytest
|
||||
from tarot.ui import CardDisplay
|
||||
from tarot.deck import Card
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from tarot.deck import Card
|
||||
from tarot.ui import CardDisplay
|
||||
|
||||
|
||||
def test_card_display_delegation():
|
||||
"""Test that CardDisplay delegates to SpreadDisplay correctly."""
|
||||
with patch('tarot.ui.SpreadDisplay') as MockSpreadDisplay:
|
||||
with patch("tarot.ui.SpreadDisplay") as MockSpreadDisplay:
|
||||
# Mock HAS_PILLOW to True to ensure we proceed
|
||||
with patch('tarot.ui.HAS_PILLOW', True):
|
||||
with patch("tarot.ui.HAS_PILLOW", True):
|
||||
display = CardDisplay()
|
||||
|
||||
# Create dummy card
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import pytest
|
||||
from tarot.ui import CubeDisplay
|
||||
|
||||
from tarot.tarot_api import Tarot
|
||||
from tarot.ui import CubeDisplay
|
||||
|
||||
|
||||
def test_cube_display_init():
|
||||
cube = Tarot.cube
|
||||
@@ -8,6 +10,7 @@ def test_cube_display_init():
|
||||
assert display.current_wall_name == "North"
|
||||
assert display.deck_name == "default"
|
||||
|
||||
|
||||
def test_cube_navigation():
|
||||
cube = Tarot.cube
|
||||
display = CubeDisplay(cube)
|
||||
@@ -28,13 +31,14 @@ def test_cube_navigation():
|
||||
display._navigate("Left")
|
||||
assert display.current_wall_name == "West"
|
||||
|
||||
|
||||
def test_find_card_for_direction():
|
||||
cube = Tarot.cube
|
||||
display = CubeDisplay(cube)
|
||||
|
||||
# North Wall, Center Direction -> Aleph -> The Fool
|
||||
wall = cube.wall("North")
|
||||
direction = wall.direction("Center") # Should be Aleph?
|
||||
direction = wall.direction("Center") # Should be Aleph?
|
||||
# Wait, let's check what Center of North is.
|
||||
# Actually, let's just mock a direction
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import pytest
|
||||
from tarot.ui import CubeDisplay
|
||||
|
||||
from tarot.tarot_api import Tarot
|
||||
from tarot.ui import CubeDisplay
|
||||
|
||||
|
||||
def test_cube_zoom():
|
||||
cube = Tarot.cube
|
||||
@@ -13,6 +15,7 @@ def test_cube_zoom():
|
||||
display._zoom(0.5)
|
||||
assert display.zoom_level < 1.0
|
||||
|
||||
|
||||
def test_cube_zoom_limits():
|
||||
cube = Tarot.cube
|
||||
display = CubeDisplay(cube)
|
||||
|
||||
@@ -1,60 +1,131 @@
|
||||
import pytest
|
||||
from tarot.ui import CubeDisplay
|
||||
from tarot.tarot_api import Tarot
|
||||
import tkinter as tk
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from tarot.tarot_api import Tarot
|
||||
from tarot.ui import CubeDisplay
|
||||
|
||||
|
||||
def test_zoom_limits():
|
||||
# Mock Tk root
|
||||
class MockRoot:
|
||||
def __init__(self):
|
||||
self.bindings = {}
|
||||
self.images = []
|
||||
def bind(self, key, callback): pass
|
||||
def title(self, _): pass
|
||||
def update_idletasks(self): pass
|
||||
def winfo_reqwidth(self): return 800
|
||||
def winfo_reqheight(self): return 600
|
||||
def winfo_screenwidth(self): return 1920
|
||||
def winfo_screenheight(self): return 1080
|
||||
def geometry(self, _): pass
|
||||
def mainloop(self): pass
|
||||
def focus_force(self): pass
|
||||
|
||||
def bind(self, key, callback):
|
||||
pass
|
||||
|
||||
def title(self, _):
|
||||
pass
|
||||
|
||||
def update_idletasks(self):
|
||||
pass
|
||||
|
||||
def winfo_reqwidth(self):
|
||||
return 800
|
||||
|
||||
def winfo_reqheight(self):
|
||||
return 600
|
||||
|
||||
def winfo_screenwidth(self):
|
||||
return 1920
|
||||
|
||||
def winfo_screenheight(self):
|
||||
return 1080
|
||||
|
||||
def geometry(self, _):
|
||||
pass
|
||||
|
||||
def mainloop(self):
|
||||
pass
|
||||
|
||||
def focus_force(self):
|
||||
pass
|
||||
|
||||
# Mock Frame
|
||||
class MockFrame:
|
||||
def __init__(self, master=None, **kwargs):
|
||||
self.children = []
|
||||
self.master = master
|
||||
def pack(self, **kwargs): pass
|
||||
def place(self, **kwargs): pass
|
||||
def grid(self, **kwargs): pass
|
||||
def grid_propagate(self, flag): pass
|
||||
def winfo_children(self): return self.children
|
||||
def destroy(self): pass
|
||||
def update_idletasks(self): pass
|
||||
def winfo_reqwidth(self): return 100
|
||||
def winfo_reqheight(self): return 100
|
||||
def bind(self, event, callback): pass
|
||||
|
||||
def pack(self, **kwargs):
|
||||
pass
|
||||
|
||||
def place(self, **kwargs):
|
||||
pass
|
||||
|
||||
def grid(self, **kwargs):
|
||||
pass
|
||||
|
||||
def grid_propagate(self, flag):
|
||||
pass
|
||||
|
||||
def winfo_children(self):
|
||||
return self.children
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def update_idletasks(self):
|
||||
pass
|
||||
|
||||
def winfo_reqwidth(self):
|
||||
return 100
|
||||
|
||||
def winfo_reqheight(self):
|
||||
return 100
|
||||
|
||||
def bind(self, event, callback):
|
||||
pass
|
||||
|
||||
# Mock Canvas
|
||||
class MockCanvas:
|
||||
def __init__(self, master=None, **kwargs):
|
||||
self.master = master
|
||||
def pack(self, **kwargs): pass
|
||||
def bind(self, event, callback): pass
|
||||
def create_window(self, coords, **kwargs): return 1
|
||||
def config(self, **kwargs): pass
|
||||
def bbox(self, tag): return (0,0,100,100)
|
||||
def winfo_width(self): return 800
|
||||
def winfo_height(self): return 600
|
||||
def coords(self, item, x, y): pass
|
||||
def scan_mark(self, x, y): pass
|
||||
def scan_dragto(self, x, y, gain=1): pass
|
||||
def canvasx(self, x): return x
|
||||
def canvasy(self, y): return y
|
||||
def xview_moveto(self, fraction): pass
|
||||
def yview_moveto(self, fraction): pass
|
||||
|
||||
def pack(self, **kwargs):
|
||||
pass
|
||||
|
||||
def bind(self, event, callback):
|
||||
pass
|
||||
|
||||
def create_window(self, coords, **kwargs):
|
||||
return 1
|
||||
|
||||
def config(self, **kwargs):
|
||||
pass
|
||||
|
||||
def bbox(self, tag):
|
||||
return (0, 0, 100, 100)
|
||||
|
||||
def winfo_width(self):
|
||||
return 800
|
||||
|
||||
def winfo_height(self):
|
||||
return 600
|
||||
|
||||
def coords(self, item, x, y):
|
||||
pass
|
||||
|
||||
def scan_mark(self, x, y):
|
||||
pass
|
||||
|
||||
def scan_dragto(self, x, y, gain=1):
|
||||
pass
|
||||
|
||||
def canvasx(self, x):
|
||||
return x
|
||||
|
||||
def canvasy(self, y):
|
||||
return y
|
||||
|
||||
def xview_moveto(self, fraction):
|
||||
pass
|
||||
|
||||
def yview_moveto(self, fraction):
|
||||
pass
|
||||
|
||||
# Monkey patch tk
|
||||
original_tk = tk.Tk
|
||||
@@ -67,10 +138,18 @@ def test_zoom_limits():
|
||||
class MockWidget:
|
||||
def __init__(self, master=None, **kwargs):
|
||||
self.master = master
|
||||
def pack(self, **kwargs): pass
|
||||
def place(self, **kwargs): pass
|
||||
def grid(self, **kwargs): pass
|
||||
def grid_propagate(self, flag): pass
|
||||
|
||||
def pack(self, **kwargs):
|
||||
pass
|
||||
|
||||
def place(self, **kwargs):
|
||||
pass
|
||||
|
||||
def grid(self, **kwargs):
|
||||
pass
|
||||
|
||||
def grid_propagate(self, flag):
|
||||
pass
|
||||
|
||||
try:
|
||||
tk.Tk = MockRoot
|
||||
@@ -80,20 +159,20 @@ def test_zoom_limits():
|
||||
tk.ttk.Button = MockWidget
|
||||
|
||||
# Mock Image to avoid memory issues
|
||||
with patch('PIL.Image.open') as mock_open:
|
||||
with patch("PIL.Image.open") as mock_open:
|
||||
mock_img = MagicMock()
|
||||
mock_img.size = (100, 100)
|
||||
mock_img.resize.return_value = mock_img
|
||||
mock_open.return_value = mock_img
|
||||
|
||||
with patch('PIL.ImageTk.PhotoImage') as mock_photo:
|
||||
with patch("PIL.ImageTk.PhotoImage") as mock_photo:
|
||||
|
||||
cube = Tarot.cube
|
||||
display = CubeDisplay(cube)
|
||||
display.root = MockRoot()
|
||||
display.canvas = MockCanvas()
|
||||
display.content_frame = MockFrame()
|
||||
display.canvas_window = 1 # Mock window ID
|
||||
display.canvas_window = 1 # Mock window ID
|
||||
|
||||
# Test initial zoom
|
||||
assert display.zoom_level == 1.0
|
||||
|
||||
@@ -3,8 +3,9 @@ Tests for Tarot deck and card classes.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from src.tarot.deck import Deck, Card, MajorCard, MinorCard, PipCard, AceCard, CourtCard
|
||||
from src.tarot.attributes import Meaning, Suit, CardImage
|
||||
|
||||
from src.tarot.attributes import CardImage, Meaning, Suit
|
||||
from src.tarot.deck import AceCard, Card, CourtCard, Deck, MajorCard, MinorCard, PipCard
|
||||
|
||||
|
||||
class TestCard:
|
||||
@@ -30,7 +31,7 @@ class TestMajorCard:
|
||||
name="The Magician",
|
||||
meaning=Meaning("Upright", "Reversed"),
|
||||
arcana="Major",
|
||||
kabbalistic_number=1
|
||||
kabbalistic_number=1,
|
||||
)
|
||||
assert card.number == 1
|
||||
assert card.arcana == "Major"
|
||||
@@ -42,7 +43,7 @@ class TestMajorCard:
|
||||
name="Test",
|
||||
meaning=Meaning("Up", "Rev"),
|
||||
arcana="Major",
|
||||
kabbalistic_number=-1
|
||||
kabbalistic_number=-1,
|
||||
)
|
||||
|
||||
def test_major_card_invalid_high(self):
|
||||
@@ -52,16 +53,13 @@ class TestMajorCard:
|
||||
name="Test",
|
||||
meaning=Meaning("Up", "Rev"),
|
||||
arcana="Major",
|
||||
kabbalistic_number=22
|
||||
kabbalistic_number=22,
|
||||
)
|
||||
|
||||
def test_major_card_valid_range(self):
|
||||
for i in range(22):
|
||||
card = MajorCard(
|
||||
number=i,
|
||||
name=f"Card {i}",
|
||||
meaning=Meaning("Up", "Rev"),
|
||||
arcana="Major"
|
||||
number=i, name=f"Card {i}", meaning=Meaning("Up", "Rev"), arcana="Major"
|
||||
)
|
||||
assert card.number == i
|
||||
|
||||
@@ -75,7 +73,7 @@ class TestMinorCard:
|
||||
meaning=Meaning("Upright", "Reversed"),
|
||||
arcana="Minor",
|
||||
suit=suit,
|
||||
pip=1
|
||||
pip=1,
|
||||
)
|
||||
assert card.number == 1
|
||||
assert card.suit.name == "Cups"
|
||||
@@ -90,7 +88,7 @@ class TestMinorCard:
|
||||
meaning=Meaning("Up", "Rev"),
|
||||
arcana="Minor",
|
||||
suit=suit,
|
||||
pip=0
|
||||
pip=0,
|
||||
)
|
||||
|
||||
def test_minor_card_invalid_pip_high(self):
|
||||
@@ -102,7 +100,7 @@ class TestMinorCard:
|
||||
meaning=Meaning("Up", "Rev"),
|
||||
arcana="Minor",
|
||||
suit=suit,
|
||||
pip=15
|
||||
pip=15,
|
||||
)
|
||||
|
||||
def test_minor_card_valid_pips(self):
|
||||
@@ -114,7 +112,7 @@ class TestMinorCard:
|
||||
meaning=Meaning("Up", "Rev"),
|
||||
arcana="Minor",
|
||||
suit=suit,
|
||||
pip=i
|
||||
pip=i,
|
||||
)
|
||||
assert card.pip == i
|
||||
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from tarot.ui import CardDisplay
|
||||
|
||||
|
||||
def test_card_display_init():
|
||||
display = CardDisplay("default")
|
||||
assert display.deck_name == "default"
|
||||
# Check if path resolves correctly relative to src/tarot/ui.py
|
||||
# src/tarot/ui.py -> src/tarot -> src/tarot/deck/default
|
||||
expected_suffix = os.path.join("src", "tarot", "deck", "default")
|
||||
assert str(display.deck_path).endswith(expected_suffix) or str(display.deck_path).endswith("default")
|
||||
assert str(display.deck_path).endswith(expected_suffix) or str(display.deck_path).endswith(
|
||||
"default"
|
||||
)
|
||||
|
||||
|
||||
def test_card_display_resolve_path():
|
||||
display = CardDisplay("thoth")
|
||||
assert display.deck_name == "thoth"
|
||||
assert str(display.deck_path).endswith("thoth")
|
||||
|
||||
|
||||
import os
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import pytest
|
||||
from tarot.ui import CubeDisplay
|
||||
from tarot.tarot_api import Tarot
|
||||
import tkinter as tk
|
||||
|
||||
import pytest
|
||||
|
||||
from tarot.tarot_api import Tarot
|
||||
from tarot.ui import CubeDisplay
|
||||
|
||||
|
||||
def test_recursive_binding():
|
||||
# Mock Tk root and widgets
|
||||
class MockWidget:
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import pytest
|
||||
from tarot.ui import CubeDisplay
|
||||
from tarot.tarot_api import Tarot
|
||||
import tkinter as tk
|
||||
|
||||
import pytest
|
||||
|
||||
from tarot.tarot_api import Tarot
|
||||
from tarot.ui import CubeDisplay
|
||||
|
||||
|
||||
def test_zoom_key_bindings():
|
||||
# This test verifies that the bindings are set up,
|
||||
# but cannot easily simulate key presses in headless environment.
|
||||
@@ -16,23 +19,46 @@ def test_zoom_key_bindings():
|
||||
def bind(self, key, callback):
|
||||
self.bindings[key] = callback
|
||||
|
||||
def title(self, _): pass
|
||||
def update_idletasks(self): pass
|
||||
def winfo_reqwidth(self): return 800
|
||||
def winfo_reqheight(self): return 600
|
||||
def winfo_screenwidth(self): return 1920
|
||||
def winfo_screenheight(self): return 1080
|
||||
def geometry(self, _): pass
|
||||
def mainloop(self): pass
|
||||
def focus_force(self): pass
|
||||
def title(self, _):
|
||||
pass
|
||||
|
||||
def update_idletasks(self):
|
||||
pass
|
||||
|
||||
def winfo_reqwidth(self):
|
||||
return 800
|
||||
|
||||
def winfo_reqheight(self):
|
||||
return 600
|
||||
|
||||
def winfo_screenwidth(self):
|
||||
return 1920
|
||||
|
||||
def winfo_screenheight(self):
|
||||
return 1080
|
||||
|
||||
def geometry(self, _):
|
||||
pass
|
||||
|
||||
def mainloop(self):
|
||||
pass
|
||||
|
||||
def focus_force(self):
|
||||
pass
|
||||
|
||||
# Mock Frame
|
||||
class MockFrame:
|
||||
def __init__(self, master=None, **kwargs):
|
||||
self.children = []
|
||||
def pack(self, **kwargs): pass
|
||||
def winfo_children(self): return self.children
|
||||
def destroy(self): pass
|
||||
|
||||
def pack(self, **kwargs):
|
||||
pass
|
||||
|
||||
def winfo_children(self):
|
||||
return self.children
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
# Monkey patch tk
|
||||
original_tk = tk.Tk
|
||||
@@ -58,6 +84,7 @@ def test_zoom_key_bindings():
|
||||
tk.Tk = original_tk
|
||||
tk.ttk.Frame = original_frame
|
||||
|
||||
|
||||
def test_zoom_logic_direct():
|
||||
cube = Tarot.cube
|
||||
display = CubeDisplay(cube)
|
||||
|
||||
@@ -1,51 +1,108 @@
|
||||
import pytest
|
||||
from tarot.ui import CubeDisplay
|
||||
from tarot.tarot_api import Tarot
|
||||
import tkinter as tk
|
||||
|
||||
import pytest
|
||||
|
||||
from tarot.tarot_api import Tarot
|
||||
from tarot.ui import CubeDisplay
|
||||
|
||||
|
||||
def test_canvas_structure():
|
||||
# Mock Tk root
|
||||
class MockRoot:
|
||||
def __init__(self):
|
||||
self.bindings = {}
|
||||
def bind(self, key, callback): pass
|
||||
def title(self, _): pass
|
||||
def update_idletasks(self): pass
|
||||
def winfo_reqwidth(self): return 800
|
||||
def winfo_reqheight(self): return 600
|
||||
def winfo_screenwidth(self): return 1920
|
||||
def winfo_screenheight(self): return 1080
|
||||
def geometry(self, _): pass
|
||||
def mainloop(self): pass
|
||||
def focus_force(self): pass
|
||||
|
||||
def bind(self, key, callback):
|
||||
pass
|
||||
|
||||
def title(self, _):
|
||||
pass
|
||||
|
||||
def update_idletasks(self):
|
||||
pass
|
||||
|
||||
def winfo_reqwidth(self):
|
||||
return 800
|
||||
|
||||
def winfo_reqheight(self):
|
||||
return 600
|
||||
|
||||
def winfo_screenwidth(self):
|
||||
return 1920
|
||||
|
||||
def winfo_screenheight(self):
|
||||
return 1080
|
||||
|
||||
def geometry(self, _):
|
||||
pass
|
||||
|
||||
def mainloop(self):
|
||||
pass
|
||||
|
||||
def focus_force(self):
|
||||
pass
|
||||
|
||||
# Mock Frame
|
||||
class MockFrame:
|
||||
def __init__(self, master=None, **kwargs):
|
||||
self.children = []
|
||||
self.master = master
|
||||
def pack(self, **kwargs): pass
|
||||
def place(self, **kwargs): pass
|
||||
def winfo_children(self): return self.children
|
||||
def destroy(self): pass
|
||||
def update_idletasks(self): pass
|
||||
def winfo_reqwidth(self): return 100
|
||||
def winfo_reqheight(self): return 100
|
||||
|
||||
def pack(self, **kwargs):
|
||||
pass
|
||||
|
||||
def place(self, **kwargs):
|
||||
pass
|
||||
|
||||
def winfo_children(self):
|
||||
return self.children
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def update_idletasks(self):
|
||||
pass
|
||||
|
||||
def winfo_reqwidth(self):
|
||||
return 100
|
||||
|
||||
def winfo_reqheight(self):
|
||||
return 100
|
||||
|
||||
# Mock Canvas
|
||||
class MockCanvas:
|
||||
def __init__(self, master=None, **kwargs):
|
||||
self.master = master
|
||||
def pack(self, **kwargs): pass
|
||||
def bind(self, event, callback): pass
|
||||
def create_window(self, coords, **kwargs): return 1
|
||||
def config(self, **kwargs): pass
|
||||
def bbox(self, tag): return (0,0,100,100)
|
||||
def winfo_width(self): return 800
|
||||
def winfo_height(self): return 600
|
||||
def coords(self, item, x, y): pass
|
||||
def scan_mark(self, x, y): pass
|
||||
def scan_dragto(self, x, y, gain=1): pass
|
||||
|
||||
def pack(self, **kwargs):
|
||||
pass
|
||||
|
||||
def bind(self, event, callback):
|
||||
pass
|
||||
|
||||
def create_window(self, coords, **kwargs):
|
||||
return 1
|
||||
|
||||
def config(self, **kwargs):
|
||||
pass
|
||||
|
||||
def bbox(self, tag):
|
||||
return (0, 0, 100, 100)
|
||||
|
||||
def winfo_width(self):
|
||||
return 800
|
||||
|
||||
def winfo_height(self):
|
||||
return 600
|
||||
|
||||
def coords(self, item, x, y):
|
||||
pass
|
||||
|
||||
def scan_mark(self, x, y):
|
||||
pass
|
||||
|
||||
def scan_dragto(self, x, y, gain=1):
|
||||
pass
|
||||
|
||||
# Monkey patch tk
|
||||
original_tk = tk.Tk
|
||||
|
||||
@@ -1,42 +1,84 @@
|
||||
import pytest
|
||||
from tarot.ui import CubeDisplay
|
||||
from tarot.tarot_api import Tarot
|
||||
import tkinter as tk
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from tarot.tarot_api import Tarot
|
||||
from tarot.ui import CubeDisplay
|
||||
|
||||
|
||||
def test_wasd_panning():
|
||||
# Mock Tk root
|
||||
class MockRoot:
|
||||
def __init__(self):
|
||||
self.bindings = {}
|
||||
self.images = []
|
||||
|
||||
def bind(self, key, callback):
|
||||
self.bindings[key] = callback
|
||||
def title(self, _): pass
|
||||
def update_idletasks(self): pass
|
||||
def winfo_reqwidth(self): return 800
|
||||
def winfo_reqheight(self): return 600
|
||||
def winfo_screenwidth(self): return 1920
|
||||
def winfo_screenheight(self): return 1080
|
||||
def geometry(self, _): pass
|
||||
def mainloop(self): pass
|
||||
def focus_force(self): pass
|
||||
|
||||
def title(self, _):
|
||||
pass
|
||||
|
||||
def update_idletasks(self):
|
||||
pass
|
||||
|
||||
def winfo_reqwidth(self):
|
||||
return 800
|
||||
|
||||
def winfo_reqheight(self):
|
||||
return 600
|
||||
|
||||
def winfo_screenwidth(self):
|
||||
return 1920
|
||||
|
||||
def winfo_screenheight(self):
|
||||
return 1080
|
||||
|
||||
def geometry(self, _):
|
||||
pass
|
||||
|
||||
def mainloop(self):
|
||||
pass
|
||||
|
||||
def focus_force(self):
|
||||
pass
|
||||
|
||||
# Mock Frame
|
||||
class MockFrame:
|
||||
def __init__(self, master=None, **kwargs):
|
||||
self.children = []
|
||||
self.master = master
|
||||
def pack(self, **kwargs): pass
|
||||
def place(self, **kwargs): pass
|
||||
def grid(self, **kwargs): pass
|
||||
def grid_propagate(self, flag): pass
|
||||
def winfo_children(self): return self.children
|
||||
def destroy(self): pass
|
||||
def update_idletasks(self): pass
|
||||
def winfo_reqwidth(self): return 100
|
||||
def winfo_reqheight(self): return 100
|
||||
def bind(self, event, callback): pass
|
||||
|
||||
def pack(self, **kwargs):
|
||||
pass
|
||||
|
||||
def place(self, **kwargs):
|
||||
pass
|
||||
|
||||
def grid(self, **kwargs):
|
||||
pass
|
||||
|
||||
def grid_propagate(self, flag):
|
||||
pass
|
||||
|
||||
def winfo_children(self):
|
||||
return self.children
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def update_idletasks(self):
|
||||
pass
|
||||
|
||||
def winfo_reqwidth(self):
|
||||
return 100
|
||||
|
||||
def winfo_reqheight(self):
|
||||
return 100
|
||||
|
||||
def bind(self, event, callback):
|
||||
pass
|
||||
|
||||
# Mock Canvas
|
||||
class MockCanvas:
|
||||
@@ -45,20 +87,47 @@ def test_wasd_panning():
|
||||
self.x_scrolls = []
|
||||
self.y_scrolls = []
|
||||
|
||||
def pack(self, **kwargs): pass
|
||||
def bind(self, event, callback): pass
|
||||
def create_window(self, coords, **kwargs): return 1
|
||||
def config(self, **kwargs): pass
|
||||
def bbox(self, tag): return (0,0,100,100)
|
||||
def winfo_width(self): return 800
|
||||
def winfo_height(self): return 600
|
||||
def coords(self, item, x, y): pass
|
||||
def scan_mark(self, x, y): pass
|
||||
def scan_dragto(self, x, y, gain=1): pass
|
||||
def canvasx(self, x): return x
|
||||
def canvasy(self, y): return y
|
||||
def xview_moveto(self, fraction): pass
|
||||
def yview_moveto(self, fraction): pass
|
||||
def pack(self, **kwargs):
|
||||
pass
|
||||
|
||||
def bind(self, event, callback):
|
||||
pass
|
||||
|
||||
def create_window(self, coords, **kwargs):
|
||||
return 1
|
||||
|
||||
def config(self, **kwargs):
|
||||
pass
|
||||
|
||||
def bbox(self, tag):
|
||||
return (0, 0, 100, 100)
|
||||
|
||||
def winfo_width(self):
|
||||
return 800
|
||||
|
||||
def winfo_height(self):
|
||||
return 600
|
||||
|
||||
def coords(self, item, x, y):
|
||||
pass
|
||||
|
||||
def scan_mark(self, x, y):
|
||||
pass
|
||||
|
||||
def scan_dragto(self, x, y, gain=1):
|
||||
pass
|
||||
|
||||
def canvasx(self, x):
|
||||
return x
|
||||
|
||||
def canvasy(self, y):
|
||||
return y
|
||||
|
||||
def xview_moveto(self, fraction):
|
||||
pass
|
||||
|
||||
def yview_moveto(self, fraction):
|
||||
pass
|
||||
|
||||
def xview_scroll(self, number, what):
|
||||
self.x_scrolls.append((number, what))
|
||||
@@ -77,10 +146,18 @@ def test_wasd_panning():
|
||||
class MockWidget:
|
||||
def __init__(self, master=None, **kwargs):
|
||||
self.master = master
|
||||
def pack(self, **kwargs): pass
|
||||
def place(self, **kwargs): pass
|
||||
def grid(self, **kwargs): pass
|
||||
def grid_propagate(self, flag): pass
|
||||
|
||||
def pack(self, **kwargs):
|
||||
pass
|
||||
|
||||
def place(self, **kwargs):
|
||||
pass
|
||||
|
||||
def grid(self, **kwargs):
|
||||
pass
|
||||
|
||||
def grid_propagate(self, flag):
|
||||
pass
|
||||
|
||||
try:
|
||||
tk.Tk = MockRoot
|
||||
@@ -90,13 +167,13 @@ def test_wasd_panning():
|
||||
tk.ttk.Button = MockWidget
|
||||
|
||||
# Mock Image to avoid memory issues
|
||||
with patch('PIL.Image.open') as mock_open:
|
||||
with patch("PIL.Image.open") as mock_open:
|
||||
mock_img = MagicMock()
|
||||
mock_img.size = (100, 100)
|
||||
mock_img.resize.return_value = mock_img
|
||||
mock_open.return_value = mock_img
|
||||
|
||||
with patch('PIL.ImageTk.PhotoImage') as mock_photo:
|
||||
with patch("PIL.ImageTk.PhotoImage") as mock_photo:
|
||||
|
||||
cube = Tarot.cube
|
||||
display = CubeDisplay(cube)
|
||||
|
||||
@@ -8,8 +8,8 @@ References:
|
||||
- Weekday planetary rulers
|
||||
"""
|
||||
|
||||
from datetime import datetime, date, timedelta, timezone
|
||||
from typing import Dict, List, Optional
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Dict, Optional
|
||||
|
||||
|
||||
# Planetary symbols for weekdays (Sun=0, Mon=1, ..., Sat=6)
|
||||
|
||||
Reference in New Issue
Block a user