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")
|
direction = kaballah.Cube.direction("North", "East")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .tree import Tree
|
|
||||||
from .cube import Cube
|
from .cube import Cube
|
||||||
|
from .tree import Tree
|
||||||
|
|
||||||
# Export classes for fluent access
|
# Export classes for fluent access
|
||||||
__all__ = ["Tree", "Cube"]
|
__all__ = ["Tree", "Cube"]
|
||||||
|
|||||||
@@ -6,22 +6,22 @@ including Sephira, Paths, and Tree of Life structures.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Dict, List, Optional, Tuple, Any
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from utils.attributes import (
|
from utils.attributes import (
|
||||||
Element,
|
|
||||||
ElementType,
|
|
||||||
Planet,
|
|
||||||
Color,
|
Color,
|
||||||
Colorscale,
|
Colorscale,
|
||||||
Perfume,
|
ElementType,
|
||||||
God,
|
God,
|
||||||
|
Perfume,
|
||||||
|
Planet,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Sephera:
|
class Sephera:
|
||||||
"""Represents a Sephira on the Tree of Life."""
|
"""Represents a Sephira on the Tree of Life."""
|
||||||
|
|
||||||
number: int
|
number: int
|
||||||
name: str
|
name: str
|
||||||
hebrew_name: str
|
hebrew_name: str
|
||||||
@@ -29,21 +29,22 @@ class Sephera:
|
|||||||
archangel: str
|
archangel: str
|
||||||
order_of_angels: str
|
order_of_angels: str
|
||||||
mundane_chakra: str
|
mundane_chakra: str
|
||||||
element: Optional['ElementType'] = None
|
element: Optional["ElementType"] = None
|
||||||
planetary_ruler: Optional[str] = None
|
planetary_ruler: Optional[str] = None
|
||||||
tarot_trump: Optional[str] = None
|
tarot_trump: Optional[str] = None
|
||||||
colorscale: Optional['Colorscale'] = None
|
colorscale: Optional["Colorscale"] = None
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PeriodicTable:
|
class PeriodicTable:
|
||||||
"""Represents a Sephirothic position in Kabbalah with cross-correspondences."""
|
"""Represents a Sephirothic position in Kabbalah with cross-correspondences."""
|
||||||
|
|
||||||
number: int
|
number: int
|
||||||
name: str
|
name: str
|
||||||
sephera: Optional[Sephera]
|
sephera: Optional[Sephera]
|
||||||
element: Optional['ElementType'] = None
|
element: Optional["ElementType"] = None
|
||||||
planet: Optional['Planet'] = None
|
planet: Optional["Planet"] = None
|
||||||
color: Optional['Color'] = None
|
color: Optional["Color"] = None
|
||||||
tarot_trump: Optional[str] = None
|
tarot_trump: Optional[str] = None
|
||||||
hebrew_letter: Optional[str] = None
|
hebrew_letter: Optional[str] = None
|
||||||
divine_name: Optional[str] = None
|
divine_name: Optional[str] = None
|
||||||
@@ -55,6 +56,7 @@ class PeriodicTable:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class TreeOfLife:
|
class TreeOfLife:
|
||||||
"""Represents the Tree of Life structure."""
|
"""Represents the Tree of Life structure."""
|
||||||
|
|
||||||
sephiroth: Dict[int, str]
|
sephiroth: Dict[int, str]
|
||||||
paths: Dict[Tuple[int, int], str]
|
paths: Dict[Tuple[int, int], str]
|
||||||
|
|
||||||
@@ -62,6 +64,7 @@ class TreeOfLife:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Correspondences:
|
class Correspondences:
|
||||||
"""Represents Kabbalistic correspondences."""
|
"""Represents Kabbalistic correspondences."""
|
||||||
|
|
||||||
number: int
|
number: int
|
||||||
sephira: str
|
sephira: str
|
||||||
element: Optional[str]
|
element: Optional[str]
|
||||||
@@ -76,18 +79,19 @@ class Correspondences:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Path:
|
class Path:
|
||||||
"""Represents one of the 22 Paths on the Tree of Life with full correspondences."""
|
"""Represents one of the 22 Paths on the Tree of Life with full correspondences."""
|
||||||
|
|
||||||
number: int # 11-32
|
number: int # 11-32
|
||||||
hebrew_letter: str # Hebrew letter name (Aleph through Tau)
|
hebrew_letter: str # Hebrew letter name (Aleph through Tau)
|
||||||
transliteration: str # English transliteration
|
transliteration: str # English transliteration
|
||||||
tarot_trump: str # Major Arcana card (0-XXI)
|
tarot_trump: str # Major Arcana card (0-XXI)
|
||||||
sephera_from: Optional['Sephera'] = None # Lower Sephira
|
sephera_from: Optional["Sephera"] = None # Lower Sephira
|
||||||
sephera_to: Optional['Sephera'] = None # Upper Sephira
|
sephera_to: Optional["Sephera"] = None # Upper Sephira
|
||||||
element: Optional['ElementType'] = None # Element (Air, Fire, Water, Earth)
|
element: Optional["ElementType"] = None # Element (Air, Fire, Water, Earth)
|
||||||
planet: Optional['Planet'] = None # Planetary ruler
|
planet: Optional["Planet"] = None # Planetary ruler
|
||||||
zodiac_sign: Optional[str] = None # Zodiac sign (12 paths only)
|
zodiac_sign: Optional[str] = None # Zodiac sign (12 paths only)
|
||||||
colorscale: Optional['Colorscale'] = None # Golden Dawn color scales
|
colorscale: Optional["Colorscale"] = None # Golden Dawn color scales
|
||||||
perfumes: List['Perfume'] = field(default_factory=list)
|
perfumes: List["Perfume"] = field(default_factory=list)
|
||||||
gods: Dict[str, List['God']] = field(default_factory=dict)
|
gods: Dict[str, List["God"]] = field(default_factory=dict)
|
||||||
keywords: List[str] = field(default_factory=list)
|
keywords: List[str] = field(default_factory=list)
|
||||||
description: str = ""
|
description: str = ""
|
||||||
|
|
||||||
@@ -108,23 +112,23 @@ class Path:
|
|||||||
"""Check if this path has zodiac correspondence."""
|
"""Check if this path has zodiac correspondence."""
|
||||||
return self.zodiac_sign is not None
|
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."""
|
"""Attach a god to this path grouped by culture."""
|
||||||
culture_key = god.culture_key()
|
culture_key = god.culture_key()
|
||||||
culture_bucket = self.gods.setdefault(culture_key, [])
|
culture_bucket = self.gods.setdefault(culture_key, [])
|
||||||
if god not in culture_bucket:
|
if god not in culture_bucket:
|
||||||
culture_bucket.append(god)
|
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."""
|
"""Attach a perfume correspondence if it is not already present."""
|
||||||
if perfume not in self.perfumes:
|
if perfume not in self.perfumes:
|
||||||
self.perfumes.append(perfume)
|
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."""
|
"""Return all gods for this path, optionally filtered by culture."""
|
||||||
if culture:
|
if culture:
|
||||||
return list(self.gods.get(culture.lower(), []))
|
return list(self.gods.get(culture.lower(), []))
|
||||||
merged: List['God'] = []
|
merged: List["God"] = []
|
||||||
for values in self.gods.values():
|
for values in self.gods.values():
|
||||||
merged.extend(values)
|
merged.extend(values)
|
||||||
return merged
|
return merged
|
||||||
@@ -148,7 +152,7 @@ class Path:
|
|||||||
|
|
||||||
# Element
|
# Element
|
||||||
if self.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}")
|
lines.append(f"element: {element_name}")
|
||||||
|
|
||||||
# Planet
|
# Planet
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Cube namespace - access Cube of Space walls and areas."""
|
"""Cube namespace - access Cube of Space walls and areas."""
|
||||||
|
|
||||||
from .cube import Cube
|
|
||||||
from .attributes import CubeOfSpace, Wall, WallDirection
|
from .attributes import CubeOfSpace, Wall, WallDirection
|
||||||
|
from .cube import Cube
|
||||||
|
|
||||||
__all__ = ["Cube", "CubeOfSpace", "Wall", "WallDirection"]
|
__all__ = ["Cube", "CubeOfSpace", "Wall", "WallDirection"]
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class WallDirection:
|
|||||||
Each wall has 5 directions: North, South, East, West, Center.
|
Each wall has 5 directions: North, South, East, West, Center.
|
||||||
Each direction has a Hebrew letter and zodiac correspondence.
|
Each direction has a Hebrew letter and zodiac correspondence.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str # "North", "South", "East", "West", "Center"
|
name: str # "North", "South", "East", "West", "Center"
|
||||||
letter: str # Hebrew letter (e.g., "Aleph", "Bet", etc.)
|
letter: str # Hebrew letter (e.g., "Aleph", "Bet", etc.)
|
||||||
zodiac: Optional[str] = None # Zodiac sign if applicable
|
zodiac: Optional[str] = None # Zodiac sign if applicable
|
||||||
@@ -51,6 +52,7 @@ class Wall:
|
|||||||
Opposite walls: North↔South, East↔West, Above↔Below.
|
Opposite walls: North↔South, East↔West, Above↔Below.
|
||||||
Each direction has a Hebrew letter and zodiac correspondence.
|
Each direction has a Hebrew letter and zodiac correspondence.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str # "North", "South", "East", "West", "Above", "Below"
|
name: str # "North", "South", "East", "West", "Above", "Below"
|
||||||
side: str # Alias for name, used for filtering (e.g., "north", "south")
|
side: str # Alias for name, used for filtering (e.g., "north", "south")
|
||||||
opposite: str # Opposite wall name (e.g., "South" for North wall)
|
opposite: str # Opposite wall name (e.g., "South" for North wall)
|
||||||
@@ -82,9 +84,7 @@ class Wall:
|
|||||||
|
|
||||||
# Validate side matches name (case-insensitive)
|
# Validate side matches name (case-insensitive)
|
||||||
if self.side.capitalize() != self.name:
|
if self.side.capitalize() != self.name:
|
||||||
raise ValueError(
|
raise ValueError(f"Wall side '{self.side}' must match name '{self.name}'")
|
||||||
f"Wall side '{self.side}' must match name '{self.name}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Validate opposite wall
|
# Validate opposite wall
|
||||||
expected_opposite = self.OPPOSITE_WALLS.get(self.name)
|
expected_opposite = self.OPPOSITE_WALLS.get(self.name)
|
||||||
@@ -177,6 +177,7 @@ class CubeOfSpace:
|
|||||||
- Opposite walls: North↔South, East↔West, Above↔Below
|
- Opposite walls: North↔South, East↔West, Above↔Below
|
||||||
- Total: 30 positions plus central core
|
- Total: 30 positions plus central core
|
||||||
"""
|
"""
|
||||||
|
|
||||||
walls: Dict[str, Wall] = field(default_factory=dict)
|
walls: Dict[str, Wall] = field(default_factory=dict)
|
||||||
center: Optional[WallDirection] = None # Central core position
|
center: Optional[WallDirection] = None # Central core position
|
||||||
|
|
||||||
@@ -392,9 +393,7 @@ class CubeOfSpace:
|
|||||||
"""Validate that all 6 walls are present."""
|
"""Validate that all 6 walls are present."""
|
||||||
required_walls = {"North", "South", "East", "West", "Above", "Below"}
|
required_walls = {"North", "South", "East", "West", "Above", "Below"}
|
||||||
if set(self.walls.keys()) != required_walls:
|
if set(self.walls.keys()) != required_walls:
|
||||||
raise ValueError(
|
raise ValueError(f"CubeOfSpace must have all 6 walls, got: {set(self.walls.keys())}")
|
||||||
f"CubeOfSpace must have all 6 walls, got: {set(self.walls.keys())}"
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_default(cls) -> "CubeOfSpace":
|
def create_default(cls) -> "CubeOfSpace":
|
||||||
@@ -416,7 +415,7 @@ class CubeOfSpace:
|
|||||||
"above": {"name": "North", "letter": "Bet", "zodiac": None},
|
"above": {"name": "North", "letter": "Bet", "zodiac": None},
|
||||||
"below": {"name": "South", "letter": "Gimel", "zodiac": None},
|
"below": {"name": "South", "letter": "Gimel", "zodiac": None},
|
||||||
"east": {"name": "East", "letter": "Daleth", "zodiac": "Aries"},
|
"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():
|
for wall_name, wall_data in cls._WALL_DEFINITIONS.items():
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ Usage:
|
|||||||
wall.direction("East") # Get specific direction
|
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):
|
class CubeMeta(type):
|
||||||
@@ -29,7 +32,7 @@ class CubeMeta(type):
|
|||||||
if cls._cube is None:
|
if cls._cube is None:
|
||||||
return "Cube of Space (not initialized)"
|
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 = [
|
lines = [
|
||||||
"Cube of Space",
|
"Cube of Space",
|
||||||
"=" * 60,
|
"=" * 60,
|
||||||
@@ -42,8 +45,8 @@ class CubeMeta(type):
|
|||||||
for wall_name in ["North", "South", "East", "West", "Above", "Below"]:
|
for wall_name in ["North", "South", "East", "West", "Above", "Below"]:
|
||||||
wall = walls.get(wall_name)
|
wall = walls.get(wall_name)
|
||||||
if wall:
|
if wall:
|
||||||
element = f" [{wall.element}]" if hasattr(wall, 'element') else ""
|
element = f" [{wall.element}]" if hasattr(wall, "element") else ""
|
||||||
areas = len(wall.directions) if hasattr(wall, 'directions') else 0
|
areas = len(wall.directions) if hasattr(wall, "directions") else 0
|
||||||
lines.append(f" {wall_name}{element}: {areas} areas")
|
lines.append(f" {wall_name}{element}: {areas} areas")
|
||||||
|
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
@@ -53,7 +56,7 @@ class CubeMeta(type):
|
|||||||
cls._ensure_initialized()
|
cls._ensure_initialized()
|
||||||
if cls._cube is None:
|
if cls._cube is None:
|
||||||
return "Cube(not initialized)"
|
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)})"
|
return f"Cube(walls={len(walls)})"
|
||||||
|
|
||||||
|
|
||||||
@@ -68,7 +71,7 @@ class DirectionAccessor:
|
|||||||
|
|
||||||
def all(self) -> list:
|
def all(self) -> list:
|
||||||
"""Get all directions in this wall."""
|
"""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 []
|
||||||
return list(self._wall.directions.values())
|
return list(self._wall.directions.values())
|
||||||
|
|
||||||
@@ -89,10 +92,7 @@ class DirectionAccessor:
|
|||||||
|
|
||||||
# Filter by direction name if provided
|
# Filter by direction name if provided
|
||||||
if direction_name:
|
if direction_name:
|
||||||
all_dirs = [
|
all_dirs = [d for d in all_dirs if d.name.lower() == direction_name.lower()]
|
||||||
d for d in all_dirs
|
|
||||||
if d.name.lower() == direction_name.lower()
|
|
||||||
]
|
|
||||||
|
|
||||||
# Apply other filters
|
# Apply other filters
|
||||||
if kwargs:
|
if kwargs:
|
||||||
@@ -126,7 +126,7 @@ class DirectionAccessor:
|
|||||||
"""Get specific direction by name."""
|
"""Get specific direction by name."""
|
||||||
if direction_name is None:
|
if direction_name is None:
|
||||||
return self.all()
|
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 None
|
||||||
return self._wall.directions.get(direction_name.capitalize())
|
return self._wall.directions.get(direction_name.capitalize())
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ class WallWrapper:
|
|||||||
|
|
||||||
def __getattr__(self, name: str) -> Any:
|
def __getattr__(self, name: str) -> Any:
|
||||||
"""Delegate attribute access to the wrapped wall."""
|
"""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 object.__getattribute__(self, name)
|
||||||
return getattr(self._wall, 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
|
# Use a descriptor to make wall work like a property on the class
|
||||||
class WallProperty:
|
class WallProperty:
|
||||||
"""Descriptor that returns wall accessor when accessed."""
|
"""Descriptor that returns wall accessor when accessed."""
|
||||||
|
|
||||||
def __get__(self, obj: Any, objtype: Optional[type] = None) -> "WallAccessor":
|
def __get__(self, obj: Any, objtype: Optional[type] = None) -> "WallAccessor":
|
||||||
if objtype is None:
|
if objtype is None:
|
||||||
objtype = type(obj)
|
objtype = type(obj)
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ Usage:
|
|||||||
print(Tree()) # Display Tree structure
|
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:
|
if TYPE_CHECKING:
|
||||||
from tarot.attributes import Sephera, Path
|
from tarot.attributes import Path, Sephera
|
||||||
from tarot.card.data import CardDataLoader
|
from tarot.card.data import CardDataLoader
|
||||||
from utils.query import QueryResult, Query
|
from utils.query import Query
|
||||||
|
|
||||||
|
|
||||||
class TreeMeta(type):
|
class TreeMeta(type):
|
||||||
@@ -28,8 +28,8 @@ class TreeMeta(type):
|
|||||||
"""Return readable representation when Tree is converted to string."""
|
"""Return readable representation when Tree is converted to string."""
|
||||||
# Access Tree class attributes through type.__getattribute__
|
# Access Tree class attributes through type.__getattribute__
|
||||||
Tree._ensure_initialized()
|
Tree._ensure_initialized()
|
||||||
sepheras = type.__getattribute__(cls, '_sepheras')
|
sepheras = type.__getattribute__(cls, "_sepheras")
|
||||||
paths = type.__getattribute__(cls, '_paths')
|
paths = type.__getattribute__(cls, "_paths")
|
||||||
lines = [
|
lines = [
|
||||||
"Tree of Life",
|
"Tree of Life",
|
||||||
"=" * 60,
|
"=" * 60,
|
||||||
@@ -49,8 +49,8 @@ class TreeMeta(type):
|
|||||||
def __repr__(cls) -> str:
|
def __repr__(cls) -> str:
|
||||||
"""Return object representation."""
|
"""Return object representation."""
|
||||||
Tree._ensure_initialized()
|
Tree._ensure_initialized()
|
||||||
sepheras = type.__getattribute__(cls, '_sepheras')
|
sepheras = type.__getattribute__(cls, "_sepheras")
|
||||||
paths = type.__getattribute__(cls, '_paths')
|
paths = type.__getattribute__(cls, "_paths")
|
||||||
return f"Tree(sepheras={len(sepheras)}, paths={len(paths)})"
|
return f"Tree(sepheras={len(sepheras)}, paths={len(paths)})"
|
||||||
|
|
||||||
|
|
||||||
@@ -65,10 +65,10 @@ class Tree(metaclass=TreeMeta):
|
|||||||
print(Tree()) # Displays tree structure
|
print(Tree()) # Displays tree structure
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_sepheras: Dict[int, 'Sephera'] = {} # type: ignore
|
_sepheras: Dict[int, "Sephera"] = {} # type: ignore
|
||||||
_paths: Dict[int, 'Path'] = {} # type: ignore
|
_paths: Dict[int, "Path"] = {} # type: ignore
|
||||||
_initialized: bool = False
|
_initialized: bool = False
|
||||||
_loader: Optional['CardDataLoader'] = None # type: ignore
|
_loader: Optional["CardDataLoader"] = None # type: ignore
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _ensure_initialized(cls) -> None:
|
def _ensure_initialized(cls) -> None:
|
||||||
@@ -77,6 +77,7 @@ class Tree(metaclass=TreeMeta):
|
|||||||
return
|
return
|
||||||
|
|
||||||
from tarot.card.data import CardDataLoader
|
from tarot.card.data import CardDataLoader
|
||||||
|
|
||||||
cls._loader = CardDataLoader()
|
cls._loader = CardDataLoader()
|
||||||
cls._sepheras = cls._loader._sephera
|
cls._sepheras = cls._loader._sephera
|
||||||
cls._paths = cls._loader._paths
|
cls._paths = cls._loader._paths
|
||||||
@@ -84,16 +85,16 @@ class Tree(metaclass=TreeMeta):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@overload
|
@overload
|
||||||
def sephera(cls, number: int) -> Optional['Sephera']:
|
def sephera(cls, number: int) -> Optional["Sephera"]: ...
|
||||||
...
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@overload
|
@overload
|
||||||
def sephera(cls, number: None = ...) -> Dict[int, 'Sephera']:
|
def sephera(cls, number: None = ...) -> Dict[int, "Sephera"]: ...
|
||||||
...
|
|
||||||
|
|
||||||
@classmethod
|
@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."""
|
"""Return a Sephira or all Sephiroth."""
|
||||||
cls._ensure_initialized()
|
cls._ensure_initialized()
|
||||||
if number is None:
|
if number is None:
|
||||||
@@ -102,16 +103,14 @@ class Tree(metaclass=TreeMeta):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@overload
|
@overload
|
||||||
def path(cls, number: int) -> Optional['Path']:
|
def path(cls, number: int) -> Optional["Path"]: ...
|
||||||
...
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@overload
|
@overload
|
||||||
def path(cls, number: None = ...) -> Dict[int, 'Path']:
|
def path(cls, number: None = ...) -> Dict[int, "Path"]: ...
|
||||||
...
|
|
||||||
|
|
||||||
@classmethod
|
@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."""
|
"""Return a Path or all Paths."""
|
||||||
cls._ensure_initialized()
|
cls._ensure_initialized()
|
||||||
if number is None:
|
if number is None:
|
||||||
@@ -119,7 +118,7 @@ class Tree(metaclass=TreeMeta):
|
|||||||
return cls._paths.get(number)
|
return cls._paths.get(number)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def filter(cls, expression: str) -> 'Query':
|
def filter(cls, expression: str) -> "Query":
|
||||||
"""
|
"""
|
||||||
Filter Sephiroth by attribute:value expression.
|
Filter Sephiroth by attribute:value expression.
|
||||||
|
|
||||||
@@ -131,6 +130,7 @@ class Tree(metaclass=TreeMeta):
|
|||||||
Returns a Query object for chaining.
|
Returns a Query object for chaining.
|
||||||
"""
|
"""
|
||||||
from tarot.query import Query
|
from tarot.query import Query
|
||||||
|
|
||||||
cls._ensure_initialized()
|
cls._ensure_initialized()
|
||||||
# Create a query from all Sephiroth
|
# Create a query from all Sephiroth
|
||||||
return Query(cls._sepheras).filter(expression)
|
return Query(cls._sepheras).filter(expression)
|
||||||
|
|||||||
@@ -18,10 +18,9 @@ Usage:
|
|||||||
letter.paths('aleph') # Get Hebrew letter with Tarot correspondences
|
letter.paths('aleph') # Get Hebrew letter with Tarot correspondences
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from .iChing import hexagram, trigram
|
||||||
from .letter import letter
|
from .letter import letter
|
||||||
from .iChing import trigram, hexagram
|
|
||||||
from .words import word
|
|
||||||
from .paths import letters
|
from .paths import letters
|
||||||
|
from .words import word
|
||||||
|
|
||||||
__all__ = ["letter", "trigram", "hexagram", "word", "letters"]
|
__all__ = ["letter", "trigram", "hexagram", "word", "letters"]
|
||||||
|
|
||||||
|
|||||||
@@ -6,19 +6,19 @@ including Alphabets, Enochian letters, and Double Letter Trumps.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
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 (
|
from utils.attributes import (
|
||||||
Element,
|
|
||||||
ElementType,
|
ElementType,
|
||||||
Planet,
|
|
||||||
Meaning,
|
Meaning,
|
||||||
|
Planet,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Letter:
|
class Letter:
|
||||||
"""Represents a letter with its attributes."""
|
"""Represents a letter with its attributes."""
|
||||||
|
|
||||||
character: str
|
character: str
|
||||||
position: int
|
position: int
|
||||||
name: str
|
name: str
|
||||||
@@ -27,6 +27,7 @@ class Letter:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class EnglishAlphabet:
|
class EnglishAlphabet:
|
||||||
"""English alphabet with Tarot/Kabbalistic correspondence."""
|
"""English alphabet with Tarot/Kabbalistic correspondence."""
|
||||||
|
|
||||||
letter: str
|
letter: str
|
||||||
position: int
|
position: int
|
||||||
sound: str
|
sound: str
|
||||||
@@ -41,6 +42,7 @@ class EnglishAlphabet:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class GreekAlphabet:
|
class GreekAlphabet:
|
||||||
"""Greek alphabet with Tarot/Kabbalistic correspondence."""
|
"""Greek alphabet with Tarot/Kabbalistic correspondence."""
|
||||||
|
|
||||||
letter: str
|
letter: str
|
||||||
position: int
|
position: int
|
||||||
transliteration: str
|
transliteration: str
|
||||||
@@ -53,6 +55,7 @@ class GreekAlphabet:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class HebrewAlphabet:
|
class HebrewAlphabet:
|
||||||
"""Hebrew alphabet with Tarot/Kabbalistic correspondence."""
|
"""Hebrew alphabet with Tarot/Kabbalistic correspondence."""
|
||||||
|
|
||||||
letter: str
|
letter: str
|
||||||
position: int
|
position: int
|
||||||
transliteration: str
|
transliteration: str
|
||||||
@@ -66,17 +69,18 @@ class HebrewAlphabet:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class DoublLetterTrump:
|
class DoublLetterTrump:
|
||||||
"""Represents a Double Letter Trump (Yodh through Tau, 3-21 of Major Arcana)."""
|
"""Represents a Double Letter Trump (Yodh through Tau, 3-21 of Major Arcana)."""
|
||||||
|
|
||||||
number: int # 3-21 (19 double letter trumps)
|
number: int # 3-21 (19 double letter trumps)
|
||||||
name: str # Full name (e.g., "The Empress")
|
name: str # Full name (e.g., "The Empress")
|
||||||
hebrew_letter_1: str # First Hebrew letter (e.g., "Gimel")
|
hebrew_letter_1: str # First Hebrew letter (e.g., "Gimel")
|
||||||
hebrew_letter_2: Optional[str] = None # Second Hebrew letter if applicable
|
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"
|
tarot_trump: Optional[str] = None # e.g., "III - The Empress"
|
||||||
astrological_sign: Optional[str] = None # Zodiac sign if any
|
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
|
number_value: Optional[int] = None # Numerological value
|
||||||
keywords: List[str] = field(default_factory=list)
|
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 = ""
|
description: str = ""
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
@@ -87,6 +91,7 @@ class DoublLetterTrump:
|
|||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class EnochianLetter:
|
class EnochianLetter:
|
||||||
"""Represents an Enochian letter with its properties."""
|
"""Represents an Enochian letter with its properties."""
|
||||||
|
|
||||||
name: str # Enochian letter name
|
name: str # Enochian letter name
|
||||||
letter: str # The letter itself
|
letter: str # The letter itself
|
||||||
hebrew_equivalent: Optional[str] = None
|
hebrew_equivalent: Optional[str] = None
|
||||||
@@ -100,6 +105,7 @@ class EnochianLetter:
|
|||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class EnochianSpirit:
|
class EnochianSpirit:
|
||||||
"""Represents an Enochian spirit or intelligence."""
|
"""Represents an Enochian spirit or intelligence."""
|
||||||
|
|
||||||
name: str # Spirit name
|
name: str # Spirit name
|
||||||
rank: str # e.g., "King", "Prince", "Duke", "Intelligence"
|
rank: str # e.g., "King", "Prince", "Duke", "Intelligence"
|
||||||
element: Optional[str] = None
|
element: Optional[str] = None
|
||||||
@@ -117,15 +123,18 @@ class EnochianArchetype:
|
|||||||
Provides a 4x4 grid with positions that can be filled with different
|
Provides a 4x4 grid with positions that can be filled with different
|
||||||
visual representations (colors, images, symbols, etc.).
|
visual representations (colors, images, symbols, etc.).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str # e.g., "Tablet of Air Archetype"
|
name: str # e.g., "Tablet of Air Archetype"
|
||||||
tablet_name: str # Reference to parent tablet
|
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)
|
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)
|
keywords: List[str] = field(default_factory=list)
|
||||||
description: str = ""
|
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)."""
|
"""Get the grid position at (row, col)."""
|
||||||
if not 0 <= row < 4 or not 0 <= col < 4:
|
if not 0 <= row < 4 or not 0 <= col < 4:
|
||||||
return None
|
return None
|
||||||
@@ -154,6 +163,7 @@ class EnochianGridPosition:
|
|||||||
- Directional letters (N, S, E, W)
|
- Directional letters (N, S, E, W)
|
||||||
- Archetypal correspondences (Tarot, element, etc.)
|
- Archetypal correspondences (Tarot, element, etc.)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
row: int # Grid row (0-3)
|
row: int # Grid row (0-3)
|
||||||
col: int # Grid column (0-3)
|
col: int # Grid column (0-3)
|
||||||
center_letter: str # The main letter at this position
|
center_letter: str # The main letter at this position
|
||||||
@@ -195,12 +205,15 @@ class EnochianTablet:
|
|||||||
- Each tablet is 12x12 (144 squares) containing Enochian letters
|
- Each tablet is 12x12 (144 squares) containing Enochian letters
|
||||||
- Archetypal form with 4x4 grid for user customization
|
- Archetypal form with 4x4 grid for user customization
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str # e.g., "Tablet of Earth", "Tablet of Air", etc.
|
name: str # e.g., "Tablet of Earth", "Tablet of Air", etc.
|
||||||
number: int # Tablet identifier (1-5)
|
number: int # Tablet identifier (1-5)
|
||||||
element: Optional[str] = None # Earth, Water, Air, Fire, or Aethyr/Union
|
element: Optional[str] = None # Earth, Water, Air, Fire, or Aethyr/Union
|
||||||
rulers: List[str] = field(default_factory=list) # Names of spirits ruling this tablet
|
rulers: List[str] = field(default_factory=list) # Names of spirits ruling this tablet
|
||||||
archangels: List[str] = field(default_factory=list) # Associated archangels
|
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
|
planetary_hours: List[str] = field(default_factory=list) # Associated hours
|
||||||
keywords: List[str] = field(default_factory=list)
|
keywords: List[str] = field(default_factory=list)
|
||||||
description: str = ""
|
description: str = ""
|
||||||
@@ -218,8 +231,7 @@ class EnochianTablet:
|
|||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
if self.name not in self.VALID_TABLETS:
|
if self.name not in self.VALID_TABLETS:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Invalid tablet '{self.name}'. "
|
f"Invalid tablet '{self.name}'. " f"Valid tablets: {', '.join(self.VALID_TABLETS)}"
|
||||||
f"Valid tablets: {', '.join(self.VALID_TABLETS)}"
|
|
||||||
)
|
)
|
||||||
# Tablet of Union uses 0, elemental tablets use 1-5
|
# Tablet of Union uses 0, elemental tablets use 1-5
|
||||||
valid_range = (0, 0) if "Union" in self.name else (1, 5)
|
valid_range = (0, 0) if "Union" in self.name else (1, 5)
|
||||||
|
|||||||
@@ -10,13 +10,12 @@ Usage:
|
|||||||
creative = hexagram.hexagram(1)
|
creative = hexagram.hexagram(1)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Dict, Optional
|
from typing import TYPE_CHECKING, Dict
|
||||||
|
|
||||||
from utils.query import CollectionAccessor
|
from utils.query import CollectionAccessor
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from tarot.card.data import CardDataLoader
|
from tarot.attributes import Hexagram, Trigram
|
||||||
from tarot.attributes import Trigram, Hexagram
|
|
||||||
|
|
||||||
|
|
||||||
def _line_diagram_from_binary(binary: str) -> str:
|
def _line_diagram_from_binary(binary: str) -> str:
|
||||||
@@ -36,7 +35,7 @@ class _Trigram:
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._initialized: bool = False
|
self._initialized: bool = False
|
||||||
self._trigrams: Dict[str, 'Trigram'] = {}
|
self._trigrams: Dict[str, "Trigram"] = {}
|
||||||
self.trigram = CollectionAccessor(self._get_trigrams)
|
self.trigram = CollectionAccessor(self._get_trigrams)
|
||||||
|
|
||||||
def _ensure_initialized(self) -> None:
|
def _ensure_initialized(self) -> None:
|
||||||
@@ -56,14 +55,78 @@ class _Trigram:
|
|||||||
from tarot.attributes import Trigram
|
from tarot.attributes import Trigram
|
||||||
|
|
||||||
trigram_specs = [
|
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": "Qian",
|
||||||
{"name": "Li", "chinese": "离", "pinyin": "Lí", "element": "Fire", "attribute": "Clinging", "binary": "101", "description": "Radiant clarity that adheres to insight."},
|
"chinese": "乾",
|
||||||
{"name": "Zhen", "chinese": "震", "pinyin": "Zhèn", "element": "Thunder", "attribute": "Arousing", "binary": "001", "description": "Sudden awakening that shakes stagnation."},
|
"pinyin": "Qián",
|
||||||
{"name": "Xun", "chinese": "巽", "pinyin": "Xùn", "element": "Wind", "attribute": "Gentle", "binary": "110", "description": "Penetrating influence that persuades subtly."},
|
"element": "Heaven",
|
||||||
{"name": "Kan", "chinese": "坎", "pinyin": "Kǎn", "element": "Water", "attribute": "Abysmal", "binary": "010", "description": "Depth, risk, and sincere feeling."},
|
"attribute": "Creative",
|
||||||
{"name": "Gen", "chinese": "艮", "pinyin": "Gèn", "element": "Mountain", "attribute": "Stillness", "binary": "100", "description": "Grounded rest that establishes boundaries."},
|
"binary": "111",
|
||||||
{"name": "Kun", "chinese": "坤", "pinyin": "Kūn", "element": "Earth", "attribute": "Receptive", "binary": "000", "description": "Vast receptivity that nurtures form."},
|
"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 = {}
|
self._trigrams = {}
|
||||||
for spec in trigram_specs:
|
for spec in trigram_specs:
|
||||||
@@ -88,7 +151,7 @@ class _Hexagram:
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._initialized: bool = False
|
self._initialized: bool = False
|
||||||
self._hexagrams: Dict[int, 'Hexagram'] = {}
|
self._hexagrams: Dict[int, "Hexagram"] = {}
|
||||||
self.hexagram = CollectionAccessor(self._get_hexagrams)
|
self.hexagram = CollectionAccessor(self._get_hexagrams)
|
||||||
|
|
||||||
def _ensure_initialized(self) -> None:
|
def _ensure_initialized(self) -> None:
|
||||||
@@ -115,70 +178,710 @@ class _Hexagram:
|
|||||||
loader = CardDataLoader()
|
loader = CardDataLoader()
|
||||||
|
|
||||||
hex_specs = [
|
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": 1,
|
||||||
{"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"},
|
"name": "Creative Force",
|
||||||
{"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"},
|
"chinese": "乾",
|
||||||
{"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"},
|
"pinyin": "Qián",
|
||||||
{"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"},
|
"judgement": "Initiative succeeds when anchored in integrity.",
|
||||||
{"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"},
|
"image": "Heaven above and below mirrors unstoppable drive.",
|
||||||
{"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"},
|
"upper": "Qian",
|
||||||
{"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"},
|
"lower": "Qian",
|
||||||
{"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"},
|
"keywords": "Leadership|Momentum|Clarity",
|
||||||
{"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": 2,
|
||||||
{"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"},
|
"name": "Receptive Field",
|
||||||
{"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"},
|
"chinese": "坤",
|
||||||
{"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"},
|
"pinyin": "Kūn",
|
||||||
{"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"},
|
"judgement": "Grounded support flourishes through patience.",
|
||||||
{"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"},
|
"image": "Earth layered upon earth offers fertile space.",
|
||||||
{"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"},
|
"upper": "Kun",
|
||||||
{"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"},
|
"lower": "Kun",
|
||||||
{"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"},
|
"keywords": "Nurture|Support|Yielding",
|
||||||
{"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": 3,
|
||||||
{"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"},
|
"name": "Sprouting",
|
||||||
{"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"},
|
"chinese": "屯",
|
||||||
{"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"},
|
"pinyin": "Zhūn",
|
||||||
{"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"},
|
"judgement": "Challenges at the start need perseverance.",
|
||||||
{"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"},
|
"image": "Water over thunder shows storms that germinate seeds.",
|
||||||
{"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"},
|
"upper": "Kan",
|
||||||
{"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"},
|
"lower": "Zhen",
|
||||||
{"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"},
|
"keywords": "Beginnings|Struggle|Resolve",
|
||||||
{"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": 4,
|
||||||
{"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"},
|
"name": "Youthful Insight",
|
||||||
{"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"},
|
"chinese": "蒙",
|
||||||
{"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"},
|
"pinyin": "Méng",
|
||||||
{"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"},
|
"judgement": "Ignorance yields to steady guidance.",
|
||||||
{"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"},
|
"image": "Mountain above water signals learning via restraint.",
|
||||||
{"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"},
|
"upper": "Gen",
|
||||||
{"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"},
|
"lower": "Kan",
|
||||||
{"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"},
|
"keywords": "Study|Mentorship|Humility",
|
||||||
{"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": 5,
|
||||||
{"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"},
|
"name": "Waiting",
|
||||||
{"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"},
|
"chinese": "需",
|
||||||
{"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"},
|
"pinyin": "Xū",
|
||||||
{"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"},
|
"judgement": "Hold position until nourishment arrives.",
|
||||||
{"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"},
|
"image": "Water above heaven depicts clouds gathering provision.",
|
||||||
{"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"},
|
"upper": "Kan",
|
||||||
{"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"},
|
"lower": "Qian",
|
||||||
{"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"},
|
"keywords": "Patience|Faith|Preparation",
|
||||||
{"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": 6,
|
||||||
{"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"},
|
"name": "Conflict",
|
||||||
{"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"},
|
"chinese": "訟",
|
||||||
{"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"},
|
"pinyin": "Sòng",
|
||||||
{"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"},
|
"judgement": "Clarity and fairness prevent escalation.",
|
||||||
{"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"},
|
"image": "Heaven above water shows tension seeking balance.",
|
||||||
{"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"},
|
"upper": "Qian",
|
||||||
{"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"},
|
"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"]
|
planet_cycle = ["Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Earth"]
|
||||||
self._hexagrams = {}
|
self._hexagrams = {}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from utils.attributes import Number, Planet
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Trigram:
|
class Trigram:
|
||||||
"""Represents one of the eight I Ching trigrams."""
|
"""Represents one of the eight I Ching trigrams."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
chinese_name: str
|
chinese_name: str
|
||||||
pinyin: str
|
pinyin: str
|
||||||
@@ -27,6 +28,7 @@ class Trigram:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Hexagram:
|
class Hexagram:
|
||||||
"""Represents an I Ching hexagram with Tarot correspondence."""
|
"""Represents an I Ching hexagram with Tarot correspondence."""
|
||||||
|
|
||||||
number: int
|
number: int
|
||||||
name: str
|
name: str
|
||||||
chinese_name: str
|
chinese_name: str
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class Letter:
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._initialized: bool = False
|
self._initialized: bool = False
|
||||||
self._loader: 'CardDataLoader | None' = None
|
self._loader: "CardDataLoader | None" = None
|
||||||
self.alphabet = CollectionAccessor(self._get_alphabets)
|
self.alphabet = CollectionAccessor(self._get_alphabets)
|
||||||
self.cipher = CollectionAccessor(self._get_ciphers)
|
self.cipher = CollectionAccessor(self._get_ciphers)
|
||||||
self.letter = CollectionAccessor(self._get_letters)
|
self.letter = CollectionAccessor(self._get_letters)
|
||||||
@@ -26,10 +26,11 @@ class Letter:
|
|||||||
return
|
return
|
||||||
|
|
||||||
from tarot.card.data import CardDataLoader
|
from tarot.card.data import CardDataLoader
|
||||||
|
|
||||||
self._loader = CardDataLoader()
|
self._loader = CardDataLoader()
|
||||||
self._initialized = True
|
self._initialized = True
|
||||||
|
|
||||||
def _require_loader(self) -> 'CardDataLoader':
|
def _require_loader(self) -> "CardDataLoader":
|
||||||
self._ensure_initialized()
|
self._ensure_initialized()
|
||||||
assert self._loader is not None, "Loader not initialized"
|
assert self._loader is not None, "Loader not initialized"
|
||||||
return self._loader
|
return self._loader
|
||||||
@@ -54,7 +55,7 @@ class Letter:
|
|||||||
loader = self._require_loader()
|
loader = self._require_loader()
|
||||||
return loader._periodic_table.copy()
|
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.
|
Start a fluent cipher request for the given text.
|
||||||
|
|
||||||
|
|||||||
@@ -17,13 +17,14 @@ Each letter has attributes like:
|
|||||||
- Musical Note
|
- Musical Note
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List, Optional, Dict, Union, TYPE_CHECKING
|
from dataclasses import dataclass
|
||||||
from dataclasses import dataclass, field
|
from typing import TYPE_CHECKING, Dict, List, Optional, Union
|
||||||
from utils.filter import universal_filter, get_filterable_fields, format_results
|
|
||||||
|
from utils.filter import format_results, get_filterable_fields, universal_filter
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from utils.query import CollectionAccessor
|
|
||||||
from tarot.attributes import Path
|
from tarot.attributes import Path
|
||||||
|
from utils.query import CollectionAccessor
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -34,7 +35,8 @@ class TarotLetter:
|
|||||||
Wraps Path objects from CardDataLoader to provide a letter-focused interface
|
Wraps Path objects from CardDataLoader to provide a letter-focused interface
|
||||||
while maintaining a single source of truth.
|
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)
|
letter_type: str # "Mother", "Double", or "Simple" (derived from path)
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
@@ -149,7 +151,7 @@ class LetterAccessor:
|
|||||||
|
|
||||||
def by_type(self, letter_type: str) -> List[TarotLetter]:
|
def by_type(self, letter_type: str) -> List[TarotLetter]:
|
||||||
"""Filter by type: 'Mother', 'Double', or 'Simple'."""
|
"""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]:
|
def by_zodiac(self, zodiac: str) -> Optional[TarotLetter]:
|
||||||
"""Get letter by zodiac sign."""
|
"""Get letter by zodiac sign."""
|
||||||
@@ -160,11 +162,15 @@ class LetterAccessor:
|
|||||||
|
|
||||||
def by_planet(self, planet: str) -> List[TarotLetter]:
|
def by_planet(self, planet: str) -> List[TarotLetter]:
|
||||||
"""Get letters by planet."""
|
"""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]:
|
def by_trump(self, trump: str) -> Optional[TarotLetter]:
|
||||||
"""Get letter by tarot trump."""
|
"""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]:
|
def get_filterable_fields(self) -> List[str]:
|
||||||
"""
|
"""
|
||||||
@@ -265,12 +271,13 @@ class IChing:
|
|||||||
all_hex = hexagrams.list()
|
all_hex = hexagrams.list()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
trigram: 'CollectionAccessor'
|
trigram: "CollectionAccessor"
|
||||||
hexagram: 'CollectionAccessor'
|
hexagram: "CollectionAccessor"
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""Initialize iChing accessor with trigram and hexagram collections."""
|
"""Initialize iChing accessor with trigram and hexagram collections."""
|
||||||
from tarot.letter import iChing as iching_module
|
from tarot.letter import iChing as iching_module
|
||||||
|
|
||||||
self.trigram = iching_module.trigram.trigram
|
self.trigram = iching_module.trigram.trigram
|
||||||
self.hexagram = iching_module.hexagram.hexagram
|
self.hexagram = iching_module.hexagram.hexagram
|
||||||
|
|
||||||
@@ -286,7 +293,7 @@ class IChing:
|
|||||||
class LettersRegistry:
|
class LettersRegistry:
|
||||||
"""Registry and accessor for all Hebrew letters with Tarot correspondences."""
|
"""Registry and accessor for all Hebrew letters with Tarot correspondences."""
|
||||||
|
|
||||||
_instance: Optional['LettersRegistry'] = None
|
_instance: Optional["LettersRegistry"] = None
|
||||||
_letters: Dict[str, TarotLetter] = {}
|
_letters: Dict[str, TarotLetter] = {}
|
||||||
_initialized: bool = False
|
_initialized: bool = False
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ if TYPE_CHECKING:
|
|||||||
class _Word:
|
class _Word:
|
||||||
"""Fluent accessor for word analysis and cipher operations."""
|
"""Fluent accessor for word analysis and cipher operations."""
|
||||||
|
|
||||||
_loader: 'CardDataLoader | None' = None
|
_loader: "CardDataLoader | None" = None
|
||||||
_initialized: bool = False
|
_initialized: bool = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -19,11 +19,12 @@ class _Word:
|
|||||||
return
|
return
|
||||||
|
|
||||||
from tarot.card.data import CardDataLoader
|
from tarot.card.data import CardDataLoader
|
||||||
|
|
||||||
cls._loader = CardDataLoader()
|
cls._loader = CardDataLoader()
|
||||||
cls._initialized = True
|
cls._initialized = True
|
||||||
|
|
||||||
@classmethod
|
@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.
|
Start a fluent cipher request for the given text.
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,6 @@ Usage:
|
|||||||
colors = number.color()
|
colors = number.color()
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .number import number, calculate_digital_root
|
from .number import calculate_digital_root, number
|
||||||
|
|
||||||
__all__ = ["number", "calculate_digital_root"]
|
__all__ = ["number", "calculate_digital_root"]
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
"""Numbers loader - access to numerology and number correspondences."""
|
"""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
|
from utils.filter import universal_filter
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from utils.attributes import Color, Number
|
||||||
|
|
||||||
|
|
||||||
def calculate_digital_root(value: int) -> int:
|
def calculate_digital_root(value: int) -> int:
|
||||||
"""
|
"""
|
||||||
@@ -46,8 +50,8 @@ class Numbers:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# These are populated on first access from CardDataLoader
|
# These are populated on first access from CardDataLoader
|
||||||
_numbers: Dict[int, 'Number'] = {} # type: ignore
|
_numbers: Dict[int, "Number"] = {} # type: ignore
|
||||||
_colors: Dict[int, 'Color'] = {} # type: ignore
|
_colors: Dict[int, "Color"] = {} # type: ignore
|
||||||
_initialized: bool = False
|
_initialized: bool = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -57,6 +61,7 @@ class Numbers:
|
|||||||
return
|
return
|
||||||
|
|
||||||
from tarot.card.data import CardDataLoader
|
from tarot.card.data import CardDataLoader
|
||||||
|
|
||||||
loader = CardDataLoader()
|
loader = CardDataLoader()
|
||||||
cls._numbers = loader.number()
|
cls._numbers = loader.number()
|
||||||
cls._colors = loader.color()
|
cls._colors = loader.color()
|
||||||
@@ -64,16 +69,14 @@ class Numbers:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@overload
|
@overload
|
||||||
def number(cls, value: int) -> Optional['Number']:
|
def number(cls, value: int) -> Optional["Number"]: ...
|
||||||
...
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@overload
|
@overload
|
||||||
def number(cls, value: None = ...) -> Dict[int, 'Number']:
|
def number(cls, value: None = ...) -> Dict[int, "Number"]: ...
|
||||||
...
|
|
||||||
|
|
||||||
@classmethod
|
@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."""
|
"""Return an individual Number or the full numerology table."""
|
||||||
cls._ensure_initialized()
|
cls._ensure_initialized()
|
||||||
if value is None:
|
if value is None:
|
||||||
@@ -82,16 +85,16 @@ class Numbers:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@overload
|
@overload
|
||||||
def color(cls, sephera_number: int) -> Optional['Color']:
|
def color(cls, sephera_number: int) -> Optional["Color"]: ...
|
||||||
...
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@overload
|
@overload
|
||||||
def color(cls, sephera_number: None = ...) -> Dict[int, 'Color']:
|
def color(cls, sephera_number: None = ...) -> Dict[int, "Color"]: ...
|
||||||
...
|
|
||||||
|
|
||||||
@classmethod
|
@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."""
|
"""Return a single color correspondence or the entire map."""
|
||||||
cls._ensure_initialized()
|
cls._ensure_initialized()
|
||||||
if sephera_number is None:
|
if sephera_number is None:
|
||||||
@@ -99,13 +102,13 @@ class Numbers:
|
|||||||
return cls._colors.get(sephera_number)
|
return cls._colors.get(sephera_number)
|
||||||
|
|
||||||
@classmethod
|
@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."""
|
"""Get a Color by mapping a number through digital root."""
|
||||||
root = calculate_digital_root(number)
|
root = calculate_digital_root(number)
|
||||||
return cls.color(root)
|
return cls.color(root)
|
||||||
|
|
||||||
@classmethod
|
@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."""
|
"""Get a Number object using digital root calculation."""
|
||||||
root = calculate_digital_root(value)
|
root = calculate_digital_root(value)
|
||||||
return cls.number(root)
|
return cls.number(root)
|
||||||
@@ -153,6 +156,7 @@ class Numbers:
|
|||||||
print(Numbers.display_filter_numbers(element="Fire"))
|
print(Numbers.display_filter_numbers(element="Fire"))
|
||||||
"""
|
"""
|
||||||
from utils.filter import format_results
|
from utils.filter import format_results
|
||||||
|
|
||||||
results = cls.filter_numbers(**kwargs)
|
results = cls.filter_numbers(**kwargs)
|
||||||
return format_results(results)
|
return format_results(results)
|
||||||
|
|
||||||
@@ -194,5 +198,6 @@ class Numbers:
|
|||||||
print(Numbers.display_filter_colors(element="Water"))
|
print(Numbers.display_filter_colors(element="Water"))
|
||||||
"""
|
"""
|
||||||
from utils.filter import format_results
|
from utils.filter import format_results
|
||||||
|
|
||||||
results = cls.filter_colors(**kwargs)
|
results = cls.filter_colors(**kwargs)
|
||||||
return format_results(results)
|
return format_results(results)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class _Number:
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._initialized: bool = False
|
self._initialized: bool = False
|
||||||
self._loader: 'CardDataLoader | None' = None
|
self._loader: "CardDataLoader | None" = None
|
||||||
self.number = CollectionAccessor(self._get_numbers)
|
self.number = CollectionAccessor(self._get_numbers)
|
||||||
self.color = CollectionAccessor(self._get_colors)
|
self.color = CollectionAccessor(self._get_colors)
|
||||||
self.cipher = CollectionAccessor(self._get_ciphers)
|
self.cipher = CollectionAccessor(self._get_ciphers)
|
||||||
@@ -40,10 +40,11 @@ class _Number:
|
|||||||
return
|
return
|
||||||
|
|
||||||
from tarot.card.data import CardDataLoader
|
from tarot.card.data import CardDataLoader
|
||||||
|
|
||||||
self._loader = CardDataLoader()
|
self._loader = CardDataLoader()
|
||||||
self._initialized = True
|
self._initialized = True
|
||||||
|
|
||||||
def _require_loader(self) -> 'CardDataLoader':
|
def _require_loader(self) -> "CardDataLoader":
|
||||||
self._ensure_initialized()
|
self._ensure_initialized()
|
||||||
assert self._loader is not None, "Loader not initialized"
|
assert self._loader is not None, "Loader not initialized"
|
||||||
return self._loader
|
return self._loader
|
||||||
|
|||||||
@@ -23,44 +23,76 @@ Usage:
|
|||||||
card = Tarot.deck.card(3)
|
card = Tarot.deck.card(3)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .deck import Deck, Card, MajorCard, MinorCard, DLT
|
import kaballah
|
||||||
from .attributes import (
|
from kaballah import Cube, Tree
|
||||||
Month, Day, Weekday, Hour, ClockHour, Zodiac, Suit, Meaning, Letter,
|
from kaballah.cube.attributes import CubeOfSpace, Wall, WallDirection
|
||||||
Sephera, PeriodicTable, Degree, AstrologicalInfluence,
|
|
||||||
TreeOfLife, Correspondences, CardImage, DoublLetterTrump,
|
# Import from namespace folders
|
||||||
EnglishAlphabet, GreekAlphabet, HebrewAlphabet,
|
from letter import hexagram, letter, trigram
|
||||||
Trigram, Hexagram,
|
from number import calculate_digital_root, number
|
||||||
EnochianTablet, EnochianGridPosition, EnochianArchetype, Path,
|
from temporal import PlanetPosition, ThalemaClock
|
||||||
)
|
from temporal import Zodiac as AstrologyZodiac
|
||||||
|
|
||||||
# Import shared attributes from utils
|
# Import shared attributes from utils
|
||||||
from utils.attributes import (
|
from utils.attributes import (
|
||||||
Note, Element, ElementType, Number, Color, Colorscale,
|
Cipher,
|
||||||
Planet, God, Cipher, CipherResult, Perfume,
|
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)
|
# Import from card module (includes details, loader, and image_loader)
|
||||||
from .card import (
|
from .card import (
|
||||||
CardAccessor,
|
CardAccessor,
|
||||||
CardDetailsRegistry,
|
CardDetailsRegistry,
|
||||||
|
ImageDeckLoader,
|
||||||
|
filter_cards_by_keywords,
|
||||||
|
get_card_info,
|
||||||
|
get_cards_by_suit,
|
||||||
load_card_details,
|
load_card_details,
|
||||||
load_deck_details,
|
load_deck_details,
|
||||||
get_cards_by_suit,
|
|
||||||
filter_cards_by_keywords,
|
|
||||||
print_card_details,
|
|
||||||
get_card_info,
|
|
||||||
ImageDeckLoader,
|
|
||||||
load_deck_images,
|
load_deck_images,
|
||||||
|
print_card_details,
|
||||||
)
|
)
|
||||||
|
from .card.data import CardDataLoader
|
||||||
# Import from namespace folders
|
from .deck import DLT, Card, Deck, MajorCard, MinorCard
|
||||||
from letter import letter, trigram, hexagram
|
from .tarot_api import Tarot
|
||||||
from number import number, calculate_digital_root
|
|
||||||
import kaballah
|
|
||||||
from kaballah import Tree, Cube
|
|
||||||
from temporal import ThalemaClock, Zodiac as AstrologyZodiac, PlanetPosition
|
|
||||||
|
|
||||||
|
|
||||||
def display(obj):
|
def display(obj):
|
||||||
@@ -76,7 +108,8 @@ def display(obj):
|
|||||||
display(num) # Shows all attributes nicely formatted
|
display(num) # Shows all attributes nicely formatted
|
||||||
"""
|
"""
|
||||||
from dataclasses import fields
|
from dataclasses import fields
|
||||||
if hasattr(obj, '__dataclass_fields__'):
|
|
||||||
|
if hasattr(obj, "__dataclass_fields__"):
|
||||||
# It's a dataclass - show all fields
|
# It's a dataclass - show all fields
|
||||||
print(f"{obj.__class__.__name__}:")
|
print(f"{obj.__class__.__name__}:")
|
||||||
for field in fields(obj):
|
for field in fields(obj):
|
||||||
@@ -96,12 +129,10 @@ __all__ = [
|
|||||||
"Tarot",
|
"Tarot",
|
||||||
"trigram",
|
"trigram",
|
||||||
"hexagram",
|
"hexagram",
|
||||||
|
|
||||||
# Temporal and astrological
|
# Temporal and astrological
|
||||||
"ThalemaClock",
|
"ThalemaClock",
|
||||||
"AstrologyZodiac",
|
"AstrologyZodiac",
|
||||||
"PlanetPosition",
|
"PlanetPosition",
|
||||||
|
|
||||||
# Card details and loading
|
# Card details and loading
|
||||||
"CardDetailsRegistry",
|
"CardDetailsRegistry",
|
||||||
"load_card_details",
|
"load_card_details",
|
||||||
@@ -110,24 +141,20 @@ __all__ = [
|
|||||||
"filter_cards_by_keywords",
|
"filter_cards_by_keywords",
|
||||||
"print_card_details",
|
"print_card_details",
|
||||||
"get_card_info",
|
"get_card_info",
|
||||||
|
|
||||||
# Image loading
|
# Image loading
|
||||||
"ImageDeckLoader",
|
"ImageDeckLoader",
|
||||||
"load_deck_images",
|
"load_deck_images",
|
||||||
|
|
||||||
# Utilities
|
# Utilities
|
||||||
"display",
|
"display",
|
||||||
"CardAccessor",
|
"CardAccessor",
|
||||||
"Tree",
|
"Tree",
|
||||||
"Cube",
|
"Cube",
|
||||||
|
|
||||||
# Deck classes
|
# Deck classes
|
||||||
"Deck",
|
"Deck",
|
||||||
"Card",
|
"Card",
|
||||||
"MajorCard",
|
"MajorCard",
|
||||||
"MinorCard",
|
"MinorCard",
|
||||||
"DLT",
|
"DLT",
|
||||||
|
|
||||||
# Calendar/attribute classes
|
# Calendar/attribute classes
|
||||||
"Month",
|
"Month",
|
||||||
"Day",
|
"Day",
|
||||||
@@ -142,7 +169,6 @@ __all__ = [
|
|||||||
"CubeOfSpace",
|
"CubeOfSpace",
|
||||||
"WallDirection",
|
"WallDirection",
|
||||||
"Wall",
|
"Wall",
|
||||||
|
|
||||||
# Sepheric classes
|
# Sepheric classes
|
||||||
"Sephera",
|
"Sephera",
|
||||||
"PeriodicTable",
|
"PeriodicTable",
|
||||||
@@ -157,12 +183,10 @@ __all__ = [
|
|||||||
"EnochianTablet",
|
"EnochianTablet",
|
||||||
"EnochianGridPosition",
|
"EnochianGridPosition",
|
||||||
"EnochianArchetype",
|
"EnochianArchetype",
|
||||||
|
|
||||||
# Alphabet classes
|
# Alphabet classes
|
||||||
"EnglishAlphabet",
|
"EnglishAlphabet",
|
||||||
"GreekAlphabet",
|
"GreekAlphabet",
|
||||||
"HebrewAlphabet",
|
"HebrewAlphabet",
|
||||||
|
|
||||||
# Number and color classes
|
# Number and color classes
|
||||||
"Number",
|
"Number",
|
||||||
"Color",
|
"Color",
|
||||||
@@ -172,7 +196,6 @@ __all__ = [
|
|||||||
"Hexagram",
|
"Hexagram",
|
||||||
"Cipher",
|
"Cipher",
|
||||||
"CipherResult",
|
"CipherResult",
|
||||||
|
|
||||||
# Data loader and functions
|
# Data loader and functions
|
||||||
"CardDataLoader",
|
"CardDataLoader",
|
||||||
"calculate_digital_root",
|
"calculate_digital_root",
|
||||||
|
|||||||
@@ -8,54 +8,54 @@ attribute classes for cards.
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
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
|
# Re-export attributes from other modules for convenience/backward compatibility
|
||||||
from kaballah.attributes import (
|
from kaballah.attributes import (
|
||||||
Sephera,
|
|
||||||
PeriodicTable,
|
|
||||||
TreeOfLife,
|
|
||||||
Correspondences,
|
Correspondences,
|
||||||
Path,
|
Path,
|
||||||
|
PeriodicTable,
|
||||||
|
Sephera,
|
||||||
|
TreeOfLife,
|
||||||
)
|
)
|
||||||
from letter.attributes import (
|
from letter.attributes import (
|
||||||
Letter,
|
|
||||||
EnglishAlphabet,
|
|
||||||
GreekAlphabet,
|
|
||||||
HebrewAlphabet,
|
|
||||||
DoublLetterTrump,
|
DoublLetterTrump,
|
||||||
|
EnglishAlphabet,
|
||||||
|
EnochianArchetype,
|
||||||
|
EnochianGridPosition,
|
||||||
EnochianLetter,
|
EnochianLetter,
|
||||||
EnochianSpirit,
|
EnochianSpirit,
|
||||||
EnochianTablet,
|
EnochianTablet,
|
||||||
EnochianGridPosition,
|
GreekAlphabet,
|
||||||
EnochianArchetype,
|
HebrewAlphabet,
|
||||||
|
Letter,
|
||||||
)
|
)
|
||||||
from letter.iChing_attributes import (
|
from letter.iChing_attributes import (
|
||||||
Trigram,
|
|
||||||
Hexagram,
|
Hexagram,
|
||||||
|
Trigram,
|
||||||
)
|
)
|
||||||
from temporal.attributes import (
|
from temporal.attributes import (
|
||||||
|
AstrologicalInfluence,
|
||||||
|
ClockHour,
|
||||||
|
Degree,
|
||||||
|
Hour,
|
||||||
Month,
|
Month,
|
||||||
Weekday,
|
Weekday,
|
||||||
Hour,
|
|
||||||
ClockHour,
|
|
||||||
Zodiac,
|
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)
|
# Alias Day to Weekday for backward compatibility (Day in this context was Day of Week)
|
||||||
@@ -113,8 +113,9 @@ __all__ = [
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Suit:
|
class Suit:
|
||||||
"""Represents a tarot suit."""
|
"""Represents a tarot suit."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
element: 'ElementType'
|
element: "ElementType"
|
||||||
tarot_correspondence: str
|
tarot_correspondence: str
|
||||||
number: int
|
number: int
|
||||||
|
|
||||||
@@ -122,8 +123,8 @@ class Suit:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class CardImage:
|
class CardImage:
|
||||||
"""Represents an image associated with a card."""
|
"""Represents an image associated with a card."""
|
||||||
|
|
||||||
filename: str
|
filename: str
|
||||||
artist: str
|
artist: str
|
||||||
deck_name: str
|
deck_name: str
|
||||||
url: Optional[str] = None
|
url: Optional[str] = None
|
||||||
|
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
|
|
||||||
from .card import CardAccessor
|
from .card import CardAccessor
|
||||||
from .details import CardDetailsRegistry
|
from .details import CardDetailsRegistry
|
||||||
|
from .image_loader import ImageDeckLoader, load_deck_images
|
||||||
from .loader import (
|
from .loader import (
|
||||||
|
filter_cards_by_keywords,
|
||||||
|
get_card_info,
|
||||||
|
get_cards_by_suit,
|
||||||
load_card_details,
|
load_card_details,
|
||||||
load_deck_details,
|
load_deck_details,
|
||||||
get_cards_by_suit,
|
|
||||||
filter_cards_by_keywords,
|
|
||||||
print_card_details,
|
print_card_details,
|
||||||
get_card_info,
|
|
||||||
)
|
)
|
||||||
from .image_loader import ImageDeckLoader, load_deck_images
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"CardAccessor",
|
"CardAccessor",
|
||||||
|
|||||||
@@ -13,9 +13,13 @@ Usage:
|
|||||||
cards = Deck.card.filter(arcana="Minor", suit="Wands", pip=5) # 5 of Wands
|
cards = Deck.card.filter(arcana="Minor", suit="Wands", pip=5) # 5 of Wands
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List, Optional
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
from utils.filter import universal_filter, format_results
|
|
||||||
from utils.object_formatting import is_nested_object, get_object_attributes, format_value
|
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):
|
class CardList(list):
|
||||||
@@ -32,7 +36,7 @@ class CardList(list):
|
|||||||
return self.__str__()
|
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.
|
Format a list of cards for user-friendly display.
|
||||||
|
|
||||||
@@ -42,12 +46,10 @@ def _format_cards(cards: List['Card']) -> str:
|
|||||||
Returns:
|
Returns:
|
||||||
Formatted string with each card separated by blank lines
|
Formatted string with each card separated by blank lines
|
||||||
"""
|
"""
|
||||||
from utils.object_formatting import is_nested_object, get_object_attributes, format_value
|
|
||||||
|
|
||||||
lines = []
|
lines = []
|
||||||
for card in cards:
|
for card in cards:
|
||||||
card_num = getattr(card, 'number', '?')
|
card_num = getattr(card, "number", "?")
|
||||||
card_name = getattr(card, 'name', 'Unknown')
|
card_name = getattr(card, "name", "Unknown")
|
||||||
lines.append(f"--- {card_num}: {card_name} ---")
|
lines.append(f"--- {card_num}: {card_name} ---")
|
||||||
|
|
||||||
# Format all attributes with proper nesting
|
# Format all attributes with proper nesting
|
||||||
@@ -78,17 +80,18 @@ class CardAccessor:
|
|||||||
Tarot.deck.card.display_filter(arcana="Major") # Display Major Arcana
|
Tarot.deck.card.display_filter(arcana="Major") # Display Major Arcana
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_deck: Optional['Deck'] = None
|
_deck: Optional["Deck"] = None
|
||||||
_initialized: bool = False
|
_initialized: bool = False
|
||||||
|
|
||||||
def _ensure_initialized(self) -> None:
|
def _ensure_initialized(self) -> None:
|
||||||
"""Lazy-load the Deck on first access."""
|
"""Lazy-load the Deck on first access."""
|
||||||
if not self._initialized:
|
if not self._initialized:
|
||||||
from tarot.deck import Deck as DeckClass
|
from tarot.deck import Deck as DeckClass
|
||||||
|
|
||||||
CardAccessor._deck = DeckClass()
|
CardAccessor._deck = DeckClass()
|
||||||
CardAccessor._initialized = True
|
CardAccessor._initialized = True
|
||||||
|
|
||||||
def __call__(self, number: int) -> Optional['Card']:
|
def __call__(self, number: int) -> Optional["Card"]:
|
||||||
"""Get a card by number."""
|
"""Get a card by number."""
|
||||||
self._ensure_initialized()
|
self._ensure_initialized()
|
||||||
if self._deck is None:
|
if self._deck is None:
|
||||||
@@ -177,7 +180,9 @@ class CardAccessor:
|
|||||||
fool = next((c for c in major_arcana if c.number == 0), None)
|
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)
|
world = next((c for c in major_arcana if c.number == 21), None)
|
||||||
if fool and world:
|
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]
|
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")
|
lines.append(f" Double Letter Trumps ({len(double_letter_trumps)} cards): Cards 3-21")
|
||||||
@@ -189,34 +194,34 @@ class CardAccessor:
|
|||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
# Aces
|
# 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:
|
if aces:
|
||||||
lines.append(f" ACES ({len(aces)} cards - The Root Powers):")
|
lines.append(f" ACES ({len(aces)} cards - The Root Powers):")
|
||||||
for ace in aces:
|
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(f" Ace of {suit_name}")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
# Pips (2-10)
|
# 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:
|
if pips:
|
||||||
lines.append(f" PIPS ({len(pips)} cards - 2-10 of each suit):")
|
lines.append(f" PIPS ({len(pips)} cards - 2-10 of each suit):")
|
||||||
# Group by suit
|
# Group by suit
|
||||||
suits_dict = {}
|
suits_dict = {}
|
||||||
for pip in pips:
|
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:
|
if suit_name not in suits_dict:
|
||||||
suits_dict[suit_name] = []
|
suits_dict[suit_name] = []
|
||||||
suits_dict[suit_name].append(pip)
|
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:
|
if suit_name in suits_dict:
|
||||||
pip_nums = sorted([p.pip for p in suits_dict[suit_name]])
|
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(f" {suit_name}: {', '.join(str(n) for n in pip_nums)}")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
# Court Cards
|
# 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:
|
if courts:
|
||||||
lines.append(f" COURT CARDS ({len(courts)} cards - 4 ranks × 4 suits):")
|
lines.append(f" COURT CARDS ({len(courts)} cards - 4 ranks × 4 suits):")
|
||||||
# Get unique ranks and their order
|
# Get unique ranks and their order
|
||||||
@@ -227,15 +232,16 @@ class CardAccessor:
|
|||||||
# Group by suit
|
# Group by suit
|
||||||
suits_dict = {}
|
suits_dict = {}
|
||||||
for court in courts:
|
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:
|
if suit_name not in suits_dict:
|
||||||
suits_dict[suit_name] = []
|
suits_dict[suit_name] = []
|
||||||
suits_dict[suit_name].append(court)
|
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:
|
if suit_name in suits_dict:
|
||||||
suit_courts = sorted(suits_dict[suit_name],
|
suit_courts = sorted(
|
||||||
key=lambda c: rank_order.get(c.court_rank, 99))
|
suits_dict[suit_name], key=lambda c: rank_order.get(c.court_rank, 99)
|
||||||
|
)
|
||||||
court_names = [c.court_rank for c in suit_courts]
|
court_names = [c.court_rank for c in suit_courts]
|
||||||
lines.append(f" {suit_name}: {', '.join(court_names)}")
|
lines.append(f" {suit_name}: {', '.join(court_names)}")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
@@ -244,45 +250,44 @@ class CardAccessor:
|
|||||||
lines.append("SUIT CORRESPONDENCES:")
|
lines.append("SUIT CORRESPONDENCES:")
|
||||||
suits_info = {}
|
suits_info = {}
|
||||||
for card in minor_arcana:
|
for card in minor_arcana:
|
||||||
if hasattr(card, 'suit') and card.suit:
|
if hasattr(card, "suit") and card.suit:
|
||||||
suit_name = card.suit.name if hasattr(card.suit, 'name') else str(card.suit)
|
suit_name = card.suit.name if hasattr(card.suit, "name") else str(card.suit)
|
||||||
if suit_name not in suits_info:
|
if suit_name not in suits_info:
|
||||||
# Extract element info
|
# Extract element info
|
||||||
element_name = "Unknown"
|
element_name = "Unknown"
|
||||||
if hasattr(card.suit, 'element') and card.suit.element:
|
if hasattr(card.suit, "element") and card.suit.element:
|
||||||
if hasattr(card.suit.element, 'name'):
|
if hasattr(card.suit.element, "name"):
|
||||||
element_name = card.suit.element.name
|
element_name = card.suit.element.name
|
||||||
else:
|
else:
|
||||||
element_name = str(card.suit.element)
|
element_name = str(card.suit.element)
|
||||||
|
|
||||||
# Extract zodiac signs
|
# Extract zodiac signs
|
||||||
zodiac_signs = []
|
zodiac_signs = []
|
||||||
if hasattr(card.suit, 'element') and card.suit.element:
|
if hasattr(card.suit, "element") and card.suit.element:
|
||||||
if hasattr(card.suit.element, 'zodiac_signs'):
|
if hasattr(card.suit.element, "zodiac_signs"):
|
||||||
zodiac_signs = card.suit.element.zodiac_signs
|
zodiac_signs = card.suit.element.zodiac_signs
|
||||||
|
|
||||||
# Extract keywords
|
# Extract keywords
|
||||||
keywords = []
|
keywords = []
|
||||||
if hasattr(card.suit, 'element') and card.suit.element:
|
if hasattr(card.suit, "element") and card.suit.element:
|
||||||
if hasattr(card.suit.element, 'keywords'):
|
if hasattr(card.suit.element, "keywords"):
|
||||||
keywords = card.suit.element.keywords
|
keywords = card.suit.element.keywords
|
||||||
|
|
||||||
suits_info[suit_name] = {
|
suits_info[suit_name] = {
|
||||||
'element': element_name,
|
"element": element_name,
|
||||||
'zodiac': zodiac_signs,
|
"zodiac": zodiac_signs,
|
||||||
'keywords': keywords
|
"keywords": keywords,
|
||||||
}
|
}
|
||||||
|
|
||||||
for suit_name in ['Cups', 'Pentacles', 'Swords', 'Wands']:
|
for suit_name in ["Cups", "Pentacles", "Swords", "Wands"]:
|
||||||
if suit_name in suits_info:
|
if suit_name in suits_info:
|
||||||
info = suits_info[suit_name]
|
info = suits_info[suit_name]
|
||||||
lines.append(f" {suit_name} ({info['element']}):")
|
lines.append(f" {suit_name} ({info['element']}):")
|
||||||
if info['zodiac']:
|
if info["zodiac"]:
|
||||||
lines.append(f" Zodiac: {', '.join(info['zodiac'])}")
|
lines.append(f" Zodiac: {', '.join(info['zodiac'])}")
|
||||||
if info['keywords']:
|
if info["keywords"]:
|
||||||
lines.append(f" Keywords: {', '.join(info['keywords'])}")
|
lines.append(f" Keywords: {', '.join(info['keywords'])}")
|
||||||
|
|
||||||
|
|
||||||
lines.append("")
|
lines.append("")
|
||||||
lines.append(f"Total: {len(self._deck.cards)} cards")
|
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('three card'))
|
||||||
print(Tarot.deck.card.spread('tree of life'))
|
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
|
# Initialize deck if needed
|
||||||
self._ensure_initialized()
|
self._ensure_initialized()
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -64,19 +64,9 @@ class CardDetailsRegistry:
|
|||||||
if key == 0:
|
if key == 0:
|
||||||
return "o"
|
return "o"
|
||||||
|
|
||||||
val = [
|
val = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
|
||||||
1000, 900, 500, 400,
|
syms = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
|
||||||
100, 90, 50, 40,
|
roman_num = ""
|
||||||
10, 9, 5, 4,
|
|
||||||
1
|
|
||||||
]
|
|
||||||
syms = [
|
|
||||||
"M", "CM", "D", "CD",
|
|
||||||
"C", "XC", "L", "XL",
|
|
||||||
"X", "IX", "V", "IV",
|
|
||||||
"I"
|
|
||||||
]
|
|
||||||
roman_num = ''
|
|
||||||
i = 0
|
i = 0
|
||||||
while key > 0:
|
while key > 0:
|
||||||
for _ in range(key // val[i]):
|
for _ in range(key // val[i]):
|
||||||
@@ -120,7 +110,7 @@ class CardDetailsRegistry:
|
|||||||
"o": {
|
"o": {
|
||||||
"explanation": {
|
"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.",
|
"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.",
|
"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"],
|
"keywords": ["new beginnings", "innocence", "faith", "spontaneity", "potential"],
|
||||||
@@ -130,137 +120,261 @@ class CardDetailsRegistry:
|
|||||||
"I": {
|
"I": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "The Magician embodies manifestation, resourcefulness, and personal power. This card shows mastery of skills and the ability to turn ideas into reality.",
|
"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.",
|
"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"],
|
"keywords": [
|
||||||
"reversed_keywords": ["manipulation", "poor planning", "untapped talents", "lack of direction"],
|
"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.",
|
"guidance": "Focus your energy and intention on what you want to manifest. You have the tools and talents you need.",
|
||||||
},
|
},
|
||||||
"II": {
|
"II": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "The High Priestess represents intuition, sacred knowledge, and the subconscious mind. She embodies mystery and inner wisdom.",
|
"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.",
|
"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"],
|
"keywords": [
|
||||||
"reversed_keywords": ["hidden information", "silence", "disconnection from intuition", "superficiality"],
|
"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.",
|
"guidance": "Listen to your inner voice. The answers you seek lie within. Trust the wisdom of your intuition.",
|
||||||
},
|
},
|
||||||
"III": {
|
"III": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "The Empress symbolizes abundance, fertility, and nurturing energy. She represents creativity, sensuality, and the power of manifestation through nurturing.",
|
"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.",
|
"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"],
|
"keywords": [
|
||||||
"reversed_keywords": ["dependency", "creative block", "neediness", "underdevelopment"],
|
"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.",
|
"guidance": "Nurture yourself and others. Allow yourself to enjoy the fruits of your labor and appreciate beauty.",
|
||||||
},
|
},
|
||||||
"IV": {
|
"IV": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "The Emperor represents authority, leadership, and established power. He embodies structure, discipline, and protection through strength and control.",
|
"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.",
|
"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"],
|
"keywords": [
|
||||||
"reversed_keywords": ["weakness", "ineffectual leadership", "lack of discipline", "tyranny"],
|
"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.",
|
"guidance": "Step into your power with confidence. Establish clear boundaries and structure. Lead by example.",
|
||||||
},
|
},
|
||||||
"V": {
|
"V": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "The Hierophant represents tradition, conventional wisdom, and spiritual authority. This card embodies education, ceremony, and moral values.",
|
"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.",
|
"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"],
|
"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.",
|
"guidance": "Seek guidance from established wisdom. Respect traditions while finding your own spiritual path.",
|
||||||
},
|
},
|
||||||
"VI": {
|
"VI": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "The Lovers represents relationships, values alignment, and the union of opposites. It signifies choice, intimacy, and deep connection.",
|
"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.",
|
"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"],
|
"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.",
|
"guidance": "Choose with your heart aligned with your values. Deep connection requires vulnerability and honesty.",
|
||||||
},
|
},
|
||||||
"VII": {
|
"VII": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "The Chariot embodies willpower, determination, and control through focused intention. It represents triumph through discipline and forward momentum.",
|
"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.",
|
"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"],
|
"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.",
|
"guidance": "Take the reins of your life. Move forward with determination and clear direction. You have the power.",
|
||||||
},
|
},
|
||||||
"VIII": {
|
"VIII": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "Strength represents inner power, courage, and compassion. It shows mastery through gentleness and the ability to face challenges with calm confidence.",
|
"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.",
|
"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"],
|
"keywords": [
|
||||||
"reversed_keywords": ["weakness", "self-doubt", "lack of composure", "poor control"],
|
"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.",
|
"guidance": "True strength comes from within. Face challenges with courage and compassion for yourself and others.",
|
||||||
},
|
},
|
||||||
"IX": {
|
"IX": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "The Hermit represents introspection, spiritual seeking, and inner guidance. This card embodies solitude, wisdom gained through reflection, and self-discovery.",
|
"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.",
|
"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"],
|
"keywords": [
|
||||||
"reversed_keywords": ["loneliness", "isolation", "lost", "paranoia", "disconnection"],
|
"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.",
|
"guidance": "Take time for introspection and self-discovery. Your inner light guides your path. Seek solitude for wisdom.",
|
||||||
},
|
},
|
||||||
"X": {
|
"X": {
|
||||||
"explanation": {
|
"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.",
|
"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.",
|
"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"],
|
"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.",
|
"guidance": "Trust in the cycles of life. What goes up must come down. Embrace change as part of your journey.",
|
||||||
},
|
},
|
||||||
"XI": {
|
"XI": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "Justice represents fairness, truth, and balance. It embodies accountability, clear judgment, and the consequences of actions both past and present.",
|
"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.",
|
"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"],
|
"reversed_keywords": ["injustice", "bias", "lack of accountability", "dishonesty"],
|
||||||
"guidance": "Seek the truth and act with fairness. Take responsibility for your actions. Balance is key.",
|
"guidance": "Seek the truth and act with fairness. Take responsibility for your actions. Balance is key.",
|
||||||
},
|
},
|
||||||
"XII": {
|
"XII": {
|
||||||
"explanation": {
|
"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.",
|
"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.",
|
"interpretation": "Redemption, sacrifice, annihilation in the beloved; martyrdom; loss; torment; suspension; death; suffering.",
|
||||||
"keywords": ["suspension", "restriction", "letting go", "new perspective", "surrender", "pause"],
|
"keywords": [
|
||||||
"reversed_keywords": ["resistance", "stalling", "unwillingness to change", "impatience"],
|
"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.",
|
"guidance": "Pause and reflect. What are you holding onto? Surrender control and trust the process.",
|
||||||
},
|
},
|
||||||
"XIII": {
|
"XIII": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "Death represents transformation, endings, and new beginnings. This card embodies major life transitions, the release of the old, and inevitable change.",
|
"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.",
|
"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"],
|
"keywords": [
|
||||||
"reversed_keywords": ["resistance to change", "stagnation", "missed opportunity", "delay"],
|
"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.",
|
"guidance": "Release what no longer serves you. Transformation is inevitable. Trust in the cycle of death and rebirth.",
|
||||||
},
|
},
|
||||||
"XIV": {
|
"XIV": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "Temperance represents balance, moderation, and harmony. It embodies blending of opposites, inner peace through balance, and finding your rhythm.",
|
"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.",
|
"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"],
|
"keywords": ["balance", "moderation", "harmony", "patience", "timing", "peace"],
|
||||||
@@ -270,47 +384,84 @@ class CardDetailsRegistry:
|
|||||||
"XV": {
|
"XV": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "The Devil represents bondage, materialism, and shadow aspects of self. It embodies addictions, illusions, and the consequences of giving away personal power.",
|
"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.",
|
"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"],
|
"reversed_keywords": ["freedom", "detachment", "reclaiming power", "breaking free"],
|
||||||
"guidance": "Examine what binds you. Acknowledge your shadow. You hold the key to your own freedom.",
|
"guidance": "Examine what binds you. Acknowledge your shadow. You hold the key to your own freedom.",
|
||||||
},
|
},
|
||||||
"XVI": {
|
"XVI": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "The Tower represents sudden disruption, revelation, and breakthrough through crisis. It embodies sudden change, truth revealed, and necessary destruction.",
|
"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.",
|
"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"],
|
"keywords": [
|
||||||
"reversed_keywords": ["resistance to change", "averted crisis", "delay", "stagnation"],
|
"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.",
|
"guidance": "Crisis brings clarity. Though change is sudden and jarring, it clears away the false and brings truth.",
|
||||||
},
|
},
|
||||||
"XVII": {
|
"XVII": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "The Star represents hope, guidance, and inspiration. It embodies clarity of purpose, spiritual insight, and the light that guides your path forward.",
|
"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.",
|
"interpretation": "Clairvoyance; visions; drams; hope; love; yearning; realization of inexhaustible possibilities; dreaminess; unexpected help; renewal.",
|
||||||
"keywords": ["hope", "faith", "inspiration", "vision", "guidance", "spirituality"],
|
"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.",
|
"guidance": "Let your inner light shine. Trust in your vision. Hope and guidance light your path forward.",
|
||||||
},
|
},
|
||||||
"XVIII": {
|
"XVIII": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "The Moon represents illusion, intuition, and the subconscious mind. It embodies mystery, dreams, and navigating by inner knowing rather than sight.",
|
"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.",
|
"interpretation": "The Dark night of the soul; deception; falsehood; illusion; madness; the threshold of significant change.",
|
||||||
"keywords": ["illusion", "intuition", "uncertainty", "subconscious", "dreams", "mystery"],
|
"keywords": [
|
||||||
"reversed_keywords": ["clarity", "truth revealed", "release from illusion", "awakening"],
|
"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.",
|
"guidance": "Trust your intuition to navigate mystery. What appears illusory contains deeper truths worth exploring.",
|
||||||
},
|
},
|
||||||
"XIX": {
|
"XIX": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "The Sun represents joy, clarity, and vitality. It embodies success, positive energy, and the radiance of authentic self-expression.",
|
"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.",
|
"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"],
|
"keywords": ["success", "joy", "clarity", "vitality", "warmth", "authenticity"],
|
||||||
@@ -320,29 +471,47 @@ class CardDetailsRegistry:
|
|||||||
"XX": {
|
"XX": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "Judgement represents awakening, calling, and significant decisions. It embodies reckoning, rebirth, and responding to a higher calling.",
|
"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.",
|
"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"],
|
"keywords": [
|
||||||
"reversed_keywords": ["doubt", "self-doubt", "harsh judgment", "reluctance to change"],
|
"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.",
|
"guidance": "Answer your higher calling. Evaluate with compassion. A significant awakening or decision awaits.",
|
||||||
},
|
},
|
||||||
"XXI": {
|
"XXI": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "The World represents completion, wholeness, and fulfillment. It embodies the end of a cycle, achievement of goals, and a sense of unity.",
|
"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.",
|
"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"],
|
"reversed_keywords": ["incomplete", "blocked", "separation", "seeking closure"],
|
||||||
"guidance": "A significant cycle completes. You have achieved wholeness. Yet every ending is a new beginning.",
|
"guidance": "A significant cycle completes. You have achieved wholeness. Yet every ending is a new beginning.",
|
||||||
},
|
},
|
||||||
|
|
||||||
# Minor Arcana - Swords
|
# Minor Arcana - Swords
|
||||||
"Ace of Swords": {
|
"Ace of Swords": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A hand issues from a cloud, grasping as word, the point of which is encircled by a crown.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -352,7 +521,7 @@ class CardDetailsRegistry:
|
|||||||
"Two of Swords": {
|
"Two of Swords": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A hoodwinked female figure balances two swords upon her shoulders.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -362,7 +531,7 @@ class CardDetailsRegistry:
|
|||||||
"Three of Swords": {
|
"Three of Swords": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "Three swords piercing a heart; cloud and rain behind.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -372,7 +541,7 @@ class CardDetailsRegistry:
|
|||||||
"Four of Swords": {
|
"Four of Swords": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "The effigy of a knight in the attitude of prayer, at full length upon his tomb.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -382,7 +551,7 @@ class CardDetailsRegistry:
|
|||||||
"Five of Swords": {
|
"Five of Swords": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A disdainful man looks after two retreating and dejected figures.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -392,7 +561,7 @@ class CardDetailsRegistry:
|
|||||||
"Six of Swords": {
|
"Six of Swords": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A ferryman carrying passengers in his punt to the further shore.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -402,7 +571,7 @@ class CardDetailsRegistry:
|
|||||||
"Seven of Swords": {
|
"Seven of Swords": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A man in the act of carrying away five swords rapidly; the two others of the card remain stuck in the ground.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -412,7 +581,7 @@ class CardDetailsRegistry:
|
|||||||
"Eight of Swords": {
|
"Eight of Swords": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A woman, bound and hoodwinked, with the swords of the card about her.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -422,7 +591,7 @@ class CardDetailsRegistry:
|
|||||||
"Nine of Swords": {
|
"Nine of Swords": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "One seated on her couch in lamentation, with the swords over her.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -432,7 +601,7 @@ class CardDetailsRegistry:
|
|||||||
"Ten of Swords": {
|
"Ten of Swords": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A prostrate figure, pierced by all the swords belonging to the card.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -442,7 +611,7 @@ class CardDetailsRegistry:
|
|||||||
"Page of Swords": {
|
"Page of Swords": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A lithe, active figure holds a sword upright in both hands, while in the act of swift walking.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -452,7 +621,7 @@ class CardDetailsRegistry:
|
|||||||
"Knight of Swords": {
|
"Knight of Swords": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "He is riding in full course, as if scattering his enemies.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -462,7 +631,7 @@ class CardDetailsRegistry:
|
|||||||
"Queen of Swords": {
|
"Queen of Swords": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -472,7 +641,7 @@ class CardDetailsRegistry:
|
|||||||
"King of Swords": {
|
"King of Swords": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -483,7 +652,7 @@ class CardDetailsRegistry:
|
|||||||
"Ace of Cups": {
|
"Ace of Cups": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -493,7 +662,7 @@ class CardDetailsRegistry:
|
|||||||
"Two of Cups": {
|
"Two of Cups": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -503,7 +672,7 @@ class CardDetailsRegistry:
|
|||||||
"Three of Cups": {
|
"Three of Cups": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "Maidens in a garden-ground with cups uplifted, as if pledging one another.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -513,7 +682,7 @@ class CardDetailsRegistry:
|
|||||||
"Four of Cups": {
|
"Four of Cups": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -523,7 +692,7 @@ class CardDetailsRegistry:
|
|||||||
"Five of Cups": {
|
"Five of Cups": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -533,7 +702,7 @@ class CardDetailsRegistry:
|
|||||||
"Six of Cups": {
|
"Six of Cups": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "Children in an old garden, their cups filled with flowers.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -543,7 +712,7 @@ class CardDetailsRegistry:
|
|||||||
"Seven of Cups": {
|
"Seven of Cups": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "Strange chalices of vision, but the images are more especially those of the fantastic spirit.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -553,7 +722,7 @@ class CardDetailsRegistry:
|
|||||||
"Eight of Cups": {
|
"Eight of Cups": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A man of dejected aspect is deserting the cups of his felicity, enterprise, undertaking or previous concern.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -563,7 +732,7 @@ class CardDetailsRegistry:
|
|||||||
"Nine of Cups": {
|
"Nine of Cups": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -573,7 +742,7 @@ class CardDetailsRegistry:
|
|||||||
"Ten of Cups": {
|
"Ten of Cups": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -583,7 +752,7 @@ class CardDetailsRegistry:
|
|||||||
"Page of Cups": {
|
"Page of Cups": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A fair, pleasing, somewhat effeminate page, of studious and intent aspect, contemplates a fish rising from a cup to look at him.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -593,7 +762,7 @@ class CardDetailsRegistry:
|
|||||||
"Knight of Cups": {
|
"Knight of Cups": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -603,7 +772,7 @@ class CardDetailsRegistry:
|
|||||||
"Queen of Cups": {
|
"Queen of Cups": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "Beautiful, fair, dreamy--as one who sees visions in a cup.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -613,7 +782,7 @@ class CardDetailsRegistry:
|
|||||||
"King of Cups": {
|
"King of Cups": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -624,7 +793,7 @@ class CardDetailsRegistry:
|
|||||||
"Ace of Pentacles": {
|
"Ace of Pentacles": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A hand--issuing, as usual, from a cloud--holds up a pentacle.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -634,7 +803,7 @@ class CardDetailsRegistry:
|
|||||||
"Two of Pentacles": {
|
"Two of Pentacles": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -644,7 +813,7 @@ class CardDetailsRegistry:
|
|||||||
"Three of Pentacles": {
|
"Three of Pentacles": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A sculptor at his work in a monastery.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -654,7 +823,7 @@ class CardDetailsRegistry:
|
|||||||
"Four of Pentacles": {
|
"Four of Pentacles": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A crowned figure, having a pentacle over his crown, clasps another with hands and arms; two pentacles are under his feet.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -664,7 +833,7 @@ class CardDetailsRegistry:
|
|||||||
"Five of Pentacles": {
|
"Five of Pentacles": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "Two mendicants in a snow-storm pass a lighted casement.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -674,7 +843,7 @@ class CardDetailsRegistry:
|
|||||||
"Six of Pentacles": {
|
"Six of Pentacles": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -684,7 +853,7 @@ class CardDetailsRegistry:
|
|||||||
"Seven of Pentacles": {
|
"Seven of Pentacles": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -694,7 +863,7 @@ class CardDetailsRegistry:
|
|||||||
"Eight of Pentacles": {
|
"Eight of Pentacles": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "An artist in stone at his work, which he exhibits in the form of trophies.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -704,7 +873,7 @@ class CardDetailsRegistry:
|
|||||||
"Nine of Pentacles": {
|
"Nine of Pentacles": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A woman, with a bird upon her wrist, stands amidst a great abundance of grapevines in the garden of a manorial house.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -714,7 +883,7 @@ class CardDetailsRegistry:
|
|||||||
"Ten of Pentacles": {
|
"Ten of Pentacles": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A man and woman beneath an archway which gives entrance to a house and domain.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -724,7 +893,7 @@ class CardDetailsRegistry:
|
|||||||
"Page of Pentacles": {
|
"Page of Pentacles": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A youthful figure, looking intently at the pentacle which hovers over his raised hands.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -734,7 +903,7 @@ class CardDetailsRegistry:
|
|||||||
"Knight of Pentacles": {
|
"Knight of Pentacles": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "He rides a slow, enduring, heavy horse, to which his own aspect corresponds.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -744,7 +913,7 @@ class CardDetailsRegistry:
|
|||||||
"Queen of Pentacles": {
|
"Queen of Pentacles": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -754,7 +923,7 @@ class CardDetailsRegistry:
|
|||||||
"King of Pentacles": {
|
"King of Pentacles": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "Valour, realizing intelligence, business and normal intellectual aptitude, sometimes mathematical gifts and attainments of this kind; success in these paths.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -765,7 +934,7 @@ class CardDetailsRegistry:
|
|||||||
"Ace of Wands": {
|
"Ace of Wands": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -775,7 +944,7 @@ class CardDetailsRegistry:
|
|||||||
"Two of Wands": {
|
"Two of Wands": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -785,7 +954,7 @@ class CardDetailsRegistry:
|
|||||||
"Three of Wands": {
|
"Three of Wands": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "He symbolizes established strength, enterprise, effort, trade, commerce, discovery; those are his ships, bearing his merchandise, which are sailing over the sea.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -795,7 +964,7 @@ class CardDetailsRegistry:
|
|||||||
"Four of Wands": {
|
"Four of Wands": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -805,7 +974,7 @@ class CardDetailsRegistry:
|
|||||||
"Five of Wands": {
|
"Five of Wands": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "Imitation, as, for example, sham fight, but also the strenuous competition and struggle of the search after riches and fortune.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -815,7 +984,7 @@ class CardDetailsRegistry:
|
|||||||
"Six of Wands": {
|
"Six of Wands": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -825,7 +994,7 @@ class CardDetailsRegistry:
|
|||||||
"Seven of Wands": {
|
"Seven of Wands": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "It is a card of valour, for, on the surface, six are attacking one, who has, however, the vantage position.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -835,7 +1004,7 @@ class CardDetailsRegistry:
|
|||||||
"Eight of Wands": {
|
"Eight of Wands": {
|
||||||
"explanation": {
|
"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.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -845,7 +1014,7 @@ class CardDetailsRegistry:
|
|||||||
"Nine of Wands": {
|
"Nine of Wands": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "The card signifies strength in opposition.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -855,7 +1024,7 @@ class CardDetailsRegistry:
|
|||||||
"Ten of Wands": {
|
"Ten of Wands": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A card of many significances, and some of the readings cannot be harmonized.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -865,7 +1034,7 @@ class CardDetailsRegistry:
|
|||||||
"Page of Wands": {
|
"Page of Wands": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "Dark young man, faithful, a lover, an envoy, a postman.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -875,7 +1044,7 @@ class CardDetailsRegistry:
|
|||||||
"Knight of Wands": {
|
"Knight of Wands": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "Departure, absence, flight, emigration.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -885,7 +1054,7 @@ class CardDetailsRegistry:
|
|||||||
"Queen of Wands": {
|
"Queen of Wands": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "A dark woman, countrywoman, friendly, chaste, loving, honourable.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -895,7 +1064,7 @@ class CardDetailsRegistry:
|
|||||||
"King of Wands": {
|
"King of Wands": {
|
||||||
"explanation": {
|
"explanation": {
|
||||||
"summary": "Dark man, friendly, countryman, generally married, honest and conscientious.",
|
"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": "",
|
"interpretation": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -942,11 +1111,12 @@ class CardDetailsRegistry:
|
|||||||
Dictionary of card details for that suit
|
Dictionary of card details for that suit
|
||||||
"""
|
"""
|
||||||
return {
|
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()
|
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).
|
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)
|
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.
|
Load details from registry into a Card object using its position.
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,9 @@ Usage:
|
|||||||
print(f"Loaded {count} card images")
|
print(f"Loaded {count} card images")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
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:
|
if TYPE_CHECKING:
|
||||||
from tarot.deck import Card, Deck
|
from tarot.deck import Card, Deck
|
||||||
@@ -27,10 +26,12 @@ class ImageDeckLoader:
|
|||||||
"""Loader for matching Tarot card images to deck cards."""
|
"""Loader for matching Tarot card images to deck cards."""
|
||||||
|
|
||||||
# Supported image extensions
|
# Supported image extensions
|
||||||
SUPPORTED_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'}
|
SUPPORTED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp"}
|
||||||
|
|
||||||
# Regex patterns for file matching
|
# 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:
|
def __init__(self, deck_folder: str) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -51,15 +52,17 @@ class ImageDeckLoader:
|
|||||||
raise ValueError(f"Deck path is not a directory: {deck_folder}")
|
raise ValueError(f"Deck path is not a directory: {deck_folder}")
|
||||||
|
|
||||||
self.image_files = self._scan_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()
|
self._build_mapping()
|
||||||
|
|
||||||
def _scan_folder(self) -> List[Path]:
|
def _scan_folder(self) -> List[Path]:
|
||||||
"""Scan folder for image files."""
|
"""Scan folder for image files."""
|
||||||
images = []
|
images = []
|
||||||
for ext in self.SUPPORTED_EXTENSIONS:
|
for ext in self.SUPPORTED_EXTENSIONS:
|
||||||
images.extend(self.deck_folder.glob(f'*{ext}'))
|
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.upper()}"))
|
||||||
|
|
||||||
# Sort by filename for consistent ordering
|
# Sort by filename for consistent ordering
|
||||||
return sorted(images)
|
return sorted(images)
|
||||||
@@ -124,10 +127,10 @@ class ImageDeckLoader:
|
|||||||
normalized = name.lower()
|
normalized = name.lower()
|
||||||
|
|
||||||
# Replace special characters with spaces
|
# Replace special characters with spaces
|
||||||
normalized = re.sub(r'[^\w\s]', ' ', normalized)
|
normalized = re.sub(r"[^\w\s]", " ", normalized)
|
||||||
|
|
||||||
# Collapse multiple spaces
|
# Collapse multiple spaces
|
||||||
normalized = re.sub(r'\s+', ' ', normalized).strip()
|
normalized = re.sub(r"\s+", " ", normalized).strip()
|
||||||
|
|
||||||
return normalized
|
return normalized
|
||||||
|
|
||||||
@@ -174,7 +177,7 @@ class ImageDeckLoader:
|
|||||||
|
|
||||||
return best_match
|
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.
|
Get the image path for a specific card.
|
||||||
|
|
||||||
@@ -247,14 +250,14 @@ class ImageDeckLoader:
|
|||||||
parsed_num, _, _ = self._parse_filename(image_path.name)
|
parsed_num, _, _ = self._parse_filename(image_path.name)
|
||||||
if parsed_num == card_number and custom_name:
|
if parsed_num == card_number and custom_name:
|
||||||
# Convert underscore-separated name to title case
|
# Convert underscore-separated name to title case
|
||||||
name_words = custom_name.split('_')
|
name_words = custom_name.split("_")
|
||||||
return ' '.join(word.capitalize() for word in name_words)
|
return " ".join(word.capitalize() for word in name_words)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def load_into_deck(self, deck: 'Deck',
|
def load_into_deck(
|
||||||
override_names: bool = True,
|
self, deck: "Deck", override_names: bool = True, verbose: bool = False
|
||||||
verbose: bool = False) -> int:
|
) -> int:
|
||||||
"""
|
"""
|
||||||
Load image paths into all cards in a deck.
|
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)
|
custom_named = sum(1 for _, has_custom in self.card_mapping.values() if has_custom)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'deck_folder': str(self.deck_folder),
|
"deck_folder": str(self.deck_folder),
|
||||||
'total_image_files': total_images,
|
"total_image_files": total_images,
|
||||||
'total_image_filenames': len(set(f.name for f in self.image_files)),
|
"total_image_filenames": len(set(f.name for f in self.image_files)),
|
||||||
'mapped_card_numbers': mapped_cards,
|
"mapped_card_numbers": mapped_cards,
|
||||||
'cards_with_custom_names': custom_named,
|
"cards_with_custom_names": custom_named,
|
||||||
'cards_with_generic_numbers': mapped_cards - custom_named,
|
"cards_with_generic_numbers": mapped_cards - custom_named,
|
||||||
'image_extensions_found': list(set(f.suffix.lower() for f in self.image_files)),
|
"image_extensions_found": list(set(f.suffix.lower() for f in self.image_files)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def load_deck_images(deck: 'Deck',
|
def load_deck_images(
|
||||||
deck_folder: str,
|
deck: "Deck", deck_folder: str, override_names: bool = True, verbose: bool = False
|
||||||
override_names: bool = True,
|
) -> int:
|
||||||
verbose: bool = False) -> int:
|
|
||||||
"""
|
"""
|
||||||
Convenience function to load deck images.
|
Convenience function to load deck images.
|
||||||
|
|
||||||
|
|||||||
@@ -24,10 +24,7 @@ if TYPE_CHECKING:
|
|||||||
from tarot.deck import Deck
|
from tarot.deck import Deck
|
||||||
|
|
||||||
|
|
||||||
def load_card_details(
|
def load_card_details(card: "Card", registry: Optional["CardDetailsRegistry"] = None) -> bool:
|
||||||
card: 'Card',
|
|
||||||
registry: Optional['CardDetailsRegistry'] = None
|
|
||||||
) -> bool:
|
|
||||||
"""
|
"""
|
||||||
Load details for a single card from the registry.
|
Load details for a single card from the registry.
|
||||||
|
|
||||||
@@ -49,15 +46,14 @@ def load_card_details(
|
|||||||
"""
|
"""
|
||||||
if registry is None:
|
if registry is None:
|
||||||
from tarot.card.details import CardDetailsRegistry
|
from tarot.card.details import CardDetailsRegistry
|
||||||
|
|
||||||
registry = CardDetailsRegistry()
|
registry = CardDetailsRegistry()
|
||||||
|
|
||||||
return registry.load_into_card(card)
|
return registry.load_into_card(card)
|
||||||
|
|
||||||
|
|
||||||
def load_deck_details(
|
def load_deck_details(
|
||||||
deck: 'Deck',
|
deck: "Deck", registry: Optional["CardDetailsRegistry"] = None, verbose: bool = False
|
||||||
registry: Optional['CardDetailsRegistry'] = None,
|
|
||||||
verbose: bool = False
|
|
||||||
) -> int:
|
) -> int:
|
||||||
"""
|
"""
|
||||||
Load details for all cards in a deck.
|
Load details for all cards in a deck.
|
||||||
@@ -78,6 +74,7 @@ def load_deck_details(
|
|||||||
"""
|
"""
|
||||||
if registry is None:
|
if registry is None:
|
||||||
from tarot.card.details import CardDetailsRegistry
|
from tarot.card.details import CardDetailsRegistry
|
||||||
|
|
||||||
registry = CardDetailsRegistry()
|
registry = CardDetailsRegistry()
|
||||||
|
|
||||||
loaded_count = 0
|
loaded_count = 0
|
||||||
@@ -102,10 +99,7 @@ def load_deck_details(
|
|||||||
return loaded_count
|
return loaded_count
|
||||||
|
|
||||||
|
|
||||||
def get_cards_by_suit(
|
def get_cards_by_suit(deck: "Deck", suit_name: str) -> List["Card"]:
|
||||||
deck: 'Deck',
|
|
||||||
suit_name: str
|
|
||||||
) -> List['Card']:
|
|
||||||
"""
|
"""
|
||||||
Get all cards from a specific suit in the deck.
|
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
|
>>> print(len(swords)) # Should be 14
|
||||||
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
|
# Deck has a suit method, use it
|
||||||
return deck.suit(suit_name)
|
return deck.suit(suit_name)
|
||||||
|
|
||||||
# Fallback: filter cards manually
|
# Fallback: filter cards manually
|
||||||
return [card for card in deck.cards if hasattr(card, 'suit') and
|
return [
|
||||||
card.suit and card.suit.name == suit_name]
|
card
|
||||||
|
for card in deck.cards
|
||||||
|
if hasattr(card, "suit") and card.suit and card.suit.name == suit_name
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def filter_cards_by_keywords(
|
def filter_cards_by_keywords(cards: List["Card"], keyword: str) -> List["Card"]:
|
||||||
cards: List['Card'],
|
|
||||||
keyword: str
|
|
||||||
) -> List['Card']:
|
|
||||||
"""
|
"""
|
||||||
Filter a list of cards by keyword.
|
Filter a list of cards by keyword.
|
||||||
|
|
||||||
@@ -155,13 +149,15 @@ def filter_cards_by_keywords(
|
|||||||
"""
|
"""
|
||||||
keyword_lower = keyword.lower()
|
keyword_lower = keyword.lower()
|
||||||
return [
|
return [
|
||||||
card for card in cards
|
card
|
||||||
if hasattr(card, 'keywords') and card.keywords and
|
for card in cards
|
||||||
any(keyword_lower in kw.lower() for kw in card.keywords)
|
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.
|
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
|
# Define attributes to print with their formatting
|
||||||
attributes = {
|
attributes = {
|
||||||
'explanation': ('Explanation', False),
|
"explanation": ("Explanation", False),
|
||||||
'interpretation': ('Interpretation', False),
|
"interpretation": ("Interpretation", False),
|
||||||
'guidance': ('Guidance', False),
|
"guidance": ("Guidance", False),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add reversed attributes only if requested
|
# Add reversed attributes only if requested
|
||||||
if include_reversed:
|
if include_reversed:
|
||||||
attributes['reversed_interpretation'] = ('Reversed Interpretation', False)
|
attributes["reversed_interpretation"] = ("Reversed Interpretation", False)
|
||||||
|
|
||||||
# List attributes (joined with commas)
|
# List attributes (joined with commas)
|
||||||
list_attributes = {
|
list_attributes = {
|
||||||
'keywords': 'Keywords',
|
"keywords": "Keywords",
|
||||||
'reversed_keywords': ('Reversed Keywords', include_reversed),
|
"reversed_keywords": ("Reversed Keywords", include_reversed),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Numeric attributes
|
# Numeric attributes
|
||||||
numeric_attributes = {
|
numeric_attributes = {
|
||||||
'numerology': 'Numerology',
|
"numerology": "Numerology",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Print text attributes
|
# Print text attributes
|
||||||
@@ -206,7 +202,7 @@ def print_card_details(card: 'Card', include_reversed: bool = False) -> None:
|
|||||||
if hasattr(card, attr_name):
|
if hasattr(card, attr_name):
|
||||||
value = getattr(card, attr_name)
|
value = getattr(card, attr_name)
|
||||||
if value:
|
if value:
|
||||||
if attr_name == 'explanation' and isinstance(value, dict):
|
if attr_name == "explanation" and isinstance(value, dict):
|
||||||
print(f"\n{display_name}:")
|
print(f"\n{display_name}:")
|
||||||
if "summary" in value:
|
if "summary" in value:
|
||||||
print(f"Summary: {value['summary']}")
|
print(f"Summary: {value['summary']}")
|
||||||
@@ -244,8 +240,7 @@ def print_card_details(card: 'Card', include_reversed: bool = False) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def get_card_info(
|
def get_card_info(
|
||||||
card_name: str,
|
card_name: str, registry: Optional["CardDetailsRegistry"] = None
|
||||||
registry: Optional['CardDetailsRegistry'] = None
|
|
||||||
) -> Optional[dict]:
|
) -> Optional[dict]:
|
||||||
"""
|
"""
|
||||||
Get card information by card name.
|
Get card information by card name.
|
||||||
@@ -265,6 +260,7 @@ def get_card_info(
|
|||||||
"""
|
"""
|
||||||
if registry is None:
|
if registry is None:
|
||||||
from tarot.card.details import CardDetailsRegistry
|
from tarot.card.details import CardDetailsRegistry
|
||||||
|
|
||||||
registry = CardDetailsRegistry()
|
registry = CardDetailsRegistry()
|
||||||
|
|
||||||
return registry.get(card_name)
|
return registry.get(card_name)
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ Usage:
|
|||||||
reading = draw_spread(spread) # Returns list of (position, card) tuples
|
reading = draw_spread(spread) # Returns list of (position, card) tuples
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Dict, List, Optional, TYPE_CHECKING
|
|
||||||
from dataclasses import dataclass
|
|
||||||
import random
|
import random
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING, Dict, List, Optional
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from tarot.card import Card
|
from tarot.card import Card
|
||||||
@@ -29,6 +29,7 @@ if TYPE_CHECKING:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class SpreadPosition:
|
class SpreadPosition:
|
||||||
"""Represents a position in a Tarot spread."""
|
"""Represents a position in a Tarot spread."""
|
||||||
|
|
||||||
number: int
|
number: int
|
||||||
name: str
|
name: str
|
||||||
meaning: str
|
meaning: str
|
||||||
@@ -44,8 +45,9 @@ class SpreadPosition:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class DrawnCard:
|
class DrawnCard:
|
||||||
"""Represents a card drawn for a spread position."""
|
"""Represents a card drawn for a spread position."""
|
||||||
|
|
||||||
position: SpreadPosition
|
position: SpreadPosition
|
||||||
card: 'Card'
|
card: "Card"
|
||||||
is_reversed: bool
|
is_reversed: bool
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
@@ -54,9 +56,11 @@ class DrawnCard:
|
|||||||
if self.is_reversed:
|
if self.is_reversed:
|
||||||
card_name += " (Reversed)"
|
card_name += " (Reversed)"
|
||||||
|
|
||||||
return f"{self.position.number}. {self.position.name}\n" \
|
return (
|
||||||
f" └─ {card_name}\n" \
|
f"{self.position.number}. {self.position.name}\n"
|
||||||
f" └─ Position: {self.position.meaning}"
|
f" └─ {card_name}\n"
|
||||||
|
f" └─ Position: {self.position.meaning}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Spread:
|
class Spread:
|
||||||
@@ -64,97 +68,102 @@ class Spread:
|
|||||||
|
|
||||||
# Define all available spreads
|
# Define all available spreads
|
||||||
SPREADS: Dict[str, Dict] = {
|
SPREADS: Dict[str, Dict] = {
|
||||||
'three card': {
|
"three card": {
|
||||||
'name': '3-Card Spread',
|
"name": "3-Card Spread",
|
||||||
'description': 'Simple 3-card spread for past, present, future or situation, action, outcome',
|
"description": (
|
||||||
'positions': [
|
"Simple 3-card spread for past, present, future "
|
||||||
SpreadPosition(1, 'First Position', 'Past, Foundation, or Situation'),
|
"or situation, action, outcome"
|
||||||
SpreadPosition(2, 'Second Position', 'Present, Action, or Influence'),
|
),
|
||||||
SpreadPosition(3, 'Third Position', 'Future, Outcome, or Advice'),
|
"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': {
|
"golden dawn": {
|
||||||
'name': 'Golden Dawn 3-Card',
|
"name": "Golden Dawn 3-Card",
|
||||||
'description': 'Three card spread used in Golden Dawn tradition',
|
"description": "Three card spread used in Golden Dawn tradition",
|
||||||
'positions': [
|
"positions": [
|
||||||
SpreadPosition(1, 'Supernal Triangle', 'Spiritual/Divine aspect'),
|
SpreadPosition(1, "Supernal Triangle", "Spiritual/Divine aspect"),
|
||||||
SpreadPosition(2, 'Pillar of Severity', 'Challenging/Active force'),
|
SpreadPosition(2, "Pillar of Severity", "Challenging/Active force"),
|
||||||
SpreadPosition(3, 'Pillar of Mercy', 'Supportive/Passive force'),
|
SpreadPosition(3, "Pillar of Mercy", "Supportive/Passive force"),
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
'celtic cross': {
|
"celtic cross": {
|
||||||
'name': 'Celtic Cross',
|
"name": "Celtic Cross",
|
||||||
'description': 'Classic 10-card spread for in-depth reading',
|
"description": "Classic 10-card spread for in-depth reading",
|
||||||
'positions': [
|
"positions": [
|
||||||
SpreadPosition(1, 'The Significator', 'The main situation or person'),
|
SpreadPosition(1, "The Significator", "The main situation or person"),
|
||||||
SpreadPosition(2, 'The Cross', 'The challenge or heart of the matter'),
|
SpreadPosition(2, "The Cross", "The challenge or heart of the matter"),
|
||||||
SpreadPosition(3, 'Crowning Influence', 'Conscious hopes/ideals'),
|
SpreadPosition(3, "Crowning Influence", "Conscious hopes/ideals"),
|
||||||
SpreadPosition(4, 'Beneath the Cross', 'Unconscious or hidden aspects'),
|
SpreadPosition(4, "Beneath the Cross", "Unconscious or hidden aspects"),
|
||||||
SpreadPosition(5, 'Behind', 'Past influences'),
|
SpreadPosition(5, "Behind", "Past influences"),
|
||||||
SpreadPosition(6, 'Before', 'Future influences'),
|
SpreadPosition(6, "Before", "Future influences"),
|
||||||
SpreadPosition(7, 'Self/Attitude', 'How the querent sees themselves'),
|
SpreadPosition(7, "Self/Attitude", "How the querent sees themselves"),
|
||||||
SpreadPosition(8, 'Others/Environment', 'External factors/opinions'),
|
SpreadPosition(8, "Others/Environment", "External factors/opinions"),
|
||||||
SpreadPosition(9, 'Hopes and Fears', 'What the querent hopes for or fears'),
|
SpreadPosition(9, "Hopes and Fears", "What the querent hopes for or fears"),
|
||||||
SpreadPosition(10, 'Outcome', 'Final outcome or resolution'),
|
SpreadPosition(10, "Outcome", "Final outcome or resolution"),
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
'horseshoe': {
|
"horseshoe": {
|
||||||
'name': 'Horseshoe',
|
"name": "Horseshoe",
|
||||||
'description': '7-card spread in horseshoe formation for past, present, future insight',
|
"description": "7-card spread in horseshoe formation for past, present, future insight",
|
||||||
'positions': [
|
"positions": [
|
||||||
SpreadPosition(1, 'Distant Past', 'Ancient influences and foundations'),
|
SpreadPosition(1, "Distant Past", "Ancient influences and foundations"),
|
||||||
SpreadPosition(2, 'Recent Past', 'Recent events and circumstances'),
|
SpreadPosition(2, "Recent Past", "Recent events and circumstances"),
|
||||||
SpreadPosition(3, 'Present Situation', 'Current state of affairs'),
|
SpreadPosition(3, "Present Situation", "Current state of affairs"),
|
||||||
SpreadPosition(4, 'Immediate Future', 'Near-term developments'),
|
SpreadPosition(4, "Immediate Future", "Near-term developments"),
|
||||||
SpreadPosition(5, 'Distant Future', 'Long-term outcome'),
|
SpreadPosition(5, "Distant Future", "Long-term outcome"),
|
||||||
SpreadPosition(6, 'Inner Influence', 'Self/thoughts/emotions'),
|
SpreadPosition(6, "Inner Influence", "Self/thoughts/emotions"),
|
||||||
SpreadPosition(7, 'Outer Influence', 'External forces and environment'),
|
SpreadPosition(7, "Outer Influence", "External forces and environment"),
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
'pentagram': {
|
"pentagram": {
|
||||||
'name': 'Pentagram',
|
"name": "Pentagram",
|
||||||
'description': '5-card spread based on Earth element pentagram',
|
"description": "5-card spread based on Earth element pentagram",
|
||||||
'positions': [
|
"positions": [
|
||||||
SpreadPosition(1, 'Spirit', 'Core essence or spiritual truth'),
|
SpreadPosition(1, "Spirit", "Core essence or spiritual truth"),
|
||||||
SpreadPosition(2, 'Fire', 'Action and willpower'),
|
SpreadPosition(2, "Fire", "Action and willpower"),
|
||||||
SpreadPosition(3, 'Water', 'Emotions and intuition'),
|
SpreadPosition(3, "Water", "Emotions and intuition"),
|
||||||
SpreadPosition(4, 'Air', 'Intellect and communication'),
|
SpreadPosition(4, "Air", "Intellect and communication"),
|
||||||
SpreadPosition(5, 'Earth', 'Physical manifestation and grounding'),
|
SpreadPosition(5, "Earth", "Physical manifestation and grounding"),
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
'tree of life': {
|
"tree of life": {
|
||||||
'name': 'Tree of Life',
|
"name": "Tree of Life",
|
||||||
'description': '10-card spread mapping Sephiroth on the Tree of Life',
|
"description": "10-card spread mapping Sephiroth on the Tree of Life",
|
||||||
'positions': [
|
"positions": [
|
||||||
SpreadPosition(1, 'Kether (Crown)', 'Divine will and unity'),
|
SpreadPosition(1, "Kether (Crown)", "Divine will and unity"),
|
||||||
SpreadPosition(2, 'Chokmah (Wisdom)', 'Creative force and impulse'),
|
SpreadPosition(2, "Chokmah (Wisdom)", "Creative force and impulse"),
|
||||||
SpreadPosition(3, 'Binah (Understanding)', 'Form and structure'),
|
SpreadPosition(3, "Binah (Understanding)", "Form and structure"),
|
||||||
SpreadPosition(4, 'Chesed (Mercy)', 'Expansion and abundance'),
|
SpreadPosition(4, "Chesed (Mercy)", "Expansion and abundance"),
|
||||||
SpreadPosition(5, 'Gevurah (Severity)', 'Reduction and discipline'),
|
SpreadPosition(5, "Gevurah (Severity)", "Reduction and discipline"),
|
||||||
SpreadPosition(6, 'Tiphareth (Beauty)', 'Core self and integration'),
|
SpreadPosition(6, "Tiphareth (Beauty)", "Core self and integration"),
|
||||||
SpreadPosition(7, 'Netzach (Victory)', 'Desire and passion'),
|
SpreadPosition(7, "Netzach (Victory)", "Desire and passion"),
|
||||||
SpreadPosition(8, 'Hod (Splendor)', 'Intellect and communication'),
|
SpreadPosition(8, "Hod (Splendor)", "Intellect and communication"),
|
||||||
SpreadPosition(9, 'Yesod (Foundation)', 'Subconscious and dreams'),
|
SpreadPosition(9, "Yesod (Foundation)", "Subconscious and dreams"),
|
||||||
SpreadPosition(10, 'Malkuth (Kingdom)', 'Manifestation and physical reality'),
|
SpreadPosition(10, "Malkuth (Kingdom)", "Manifestation and physical reality"),
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
'relationship': {
|
"relationship": {
|
||||||
'name': 'Relationship',
|
"name": "Relationship",
|
||||||
'description': '5-card spread for relationship insight',
|
"description": "5-card spread for relationship insight",
|
||||||
'positions': [
|
"positions": [
|
||||||
SpreadPosition(1, 'You', 'Your position, feelings, or role'),
|
SpreadPosition(1, "You", "Your position, feelings, or role"),
|
||||||
SpreadPosition(2, 'Them', 'Their position, feelings, or perspective'),
|
SpreadPosition(2, "Them", "Their position, feelings, or perspective"),
|
||||||
SpreadPosition(3, 'The Relationship', 'The dynamic and connection'),
|
SpreadPosition(3, "The Relationship", "The dynamic and connection"),
|
||||||
SpreadPosition(4, 'Challenge', 'Current challenge or friction point'),
|
SpreadPosition(4, "Challenge", "Current challenge or friction point"),
|
||||||
SpreadPosition(5, 'Outcome', 'Where the relationship is heading'),
|
SpreadPosition(5, "Outcome", "Where the relationship is heading"),
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
'yes or no': {
|
"yes or no": {
|
||||||
'name': 'Yes or No',
|
"name": "Yes or No",
|
||||||
'description': '1-card spread for simple yes/no answers',
|
"description": "1-card spread for simple yes/no answers",
|
||||||
'positions': [
|
"positions": [
|
||||||
SpreadPosition(1, 'Answer', 'Major Arcana = Yes, Minor Arcana = No, Court Cards = Maybe'),
|
SpreadPosition(
|
||||||
]
|
1, "Answer", "Major Arcana = Yes, Minor Arcana = No, Court Cards = Maybe"
|
||||||
|
),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,43 +178,41 @@ class Spread:
|
|||||||
ValueError: If spread name not found
|
ValueError: If spread name not found
|
||||||
"""
|
"""
|
||||||
# Normalize name (case-insensitive, allow underscores or spaces)
|
# Normalize name (case-insensitive, allow underscores or spaces)
|
||||||
normalized_name = spread_name.lower().replace('_', ' ')
|
normalized_name = spread_name.lower().replace("_", " ")
|
||||||
|
|
||||||
# Find matching spread
|
# Find matching spread
|
||||||
spread_data = None
|
spread_data = None
|
||||||
for key, data in self.SPREADS.items():
|
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
|
spread_data = data
|
||||||
break
|
break
|
||||||
|
|
||||||
if not spread_data:
|
if not spread_data:
|
||||||
available = ', '.join(f"'{k}'" for k in self.SPREADS.keys())
|
available = ", ".join(f"'{k}'" for k in self.SPREADS.keys())
|
||||||
raise ValueError(
|
raise ValueError(f"Spread '{spread_name}' not found. Available spreads: {available}")
|
||||||
f"Spread '{spread_name}' not found. Available spreads: {available}"
|
|
||||||
)
|
|
||||||
|
|
||||||
self.name = spread_data['name']
|
self.name = spread_data["name"]
|
||||||
self.description = spread_data['description']
|
self.description = spread_data["description"]
|
||||||
self.positions: List[SpreadPosition] = spread_data['positions']
|
self.positions: List[SpreadPosition] = spread_data["positions"]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
"""Return formatted spread information."""
|
"""Return formatted spread information."""
|
||||||
lines = [
|
lines = [
|
||||||
f"═══════════════════════════════════════════",
|
"═══════════════════════════════════════════",
|
||||||
f" {self.name}",
|
f" {self.name}",
|
||||||
f"═══════════════════════════════════════════",
|
"═══════════════════════════════════════════",
|
||||||
f"",
|
"",
|
||||||
f"{self.description}",
|
f"{self.description}",
|
||||||
f"",
|
"",
|
||||||
f"Positions ({len(self.positions)} cards):",
|
f"Positions ({len(self.positions)} cards):",
|
||||||
f"",
|
"",
|
||||||
]
|
]
|
||||||
|
|
||||||
for pos in self.positions:
|
for pos in self.positions:
|
||||||
lines.append(f" {pos}")
|
lines.append(f" {pos}")
|
||||||
|
|
||||||
lines.append(f"")
|
lines.append("")
|
||||||
lines.append(f"═══════════════════════════════════════════")
|
lines.append("═══════════════════════════════════════════")
|
||||||
|
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
@@ -215,11 +222,7 @@ class Spread:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def available_spreads(cls) -> str:
|
def available_spreads(cls) -> str:
|
||||||
"""Return list of all available spreads."""
|
"""Return list of all available spreads."""
|
||||||
lines = [
|
lines = ["Available Tarot Spreads:", "═" * 50, ""]
|
||||||
"Available Tarot Spreads:",
|
|
||||||
"═" * 50,
|
|
||||||
""
|
|
||||||
]
|
|
||||||
|
|
||||||
for key, data in cls.SPREADS.items():
|
for key, data in cls.SPREADS.items():
|
||||||
lines.append(f" • {data['name']}")
|
lines.append(f" • {data['name']}")
|
||||||
@@ -254,20 +257,18 @@ def draw_spread(spread: Spread, deck: Optional[List] = None) -> List[DrawnCard]:
|
|||||||
Raises:
|
Raises:
|
||||||
ValueError: If spread has more positions than cards in the deck
|
ValueError: If spread has more positions than cards in the deck
|
||||||
"""
|
"""
|
||||||
import random
|
|
||||||
|
|
||||||
# Load deck if not provided
|
# Load deck if not provided
|
||||||
if deck is None:
|
if deck is None:
|
||||||
from tarot.deck import Deck
|
from tarot.deck import Deck
|
||||||
|
|
||||||
deck_instance = Deck()
|
deck_instance = Deck()
|
||||||
deck = deck_instance.cards
|
deck = deck_instance.cards
|
||||||
|
|
||||||
# Validate that we have enough cards to draw from without duplicates
|
# Validate that we have enough cards to draw from without duplicates
|
||||||
num_positions = len(spread.positions)
|
num_positions = len(spread.positions)
|
||||||
if num_positions > len(deck):
|
if num_positions > len(deck):
|
||||||
raise ValueError(
|
raise ValueError(f"Cannot draw {num_positions} unique cards from deck of {len(deck)} cards")
|
||||||
f"Cannot draw {num_positions} unique cards from deck of {len(deck)} cards"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Draw unique cards using random.sample (no replacements)
|
# Draw unique cards using random.sample (no replacements)
|
||||||
drawn_deck = random.sample(deck, num_positions)
|
drawn_deck = random.sample(deck, num_positions)
|
||||||
@@ -298,14 +299,14 @@ class SpreadReading:
|
|||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
"""Return formatted reading with all cards and interpretations."""
|
"""Return formatted reading with all cards and interpretations."""
|
||||||
lines = [
|
lines = [
|
||||||
f"╔═══════════════════════════════════════════╗",
|
"╔═══════════════════════════════════════════╗",
|
||||||
f"║ {self.spread.name:40}║",
|
f"║ {self.spread.name:40}║",
|
||||||
f"╚═══════════════════════════════════════════╝",
|
"╚═══════════════════════════════════════════╝",
|
||||||
f"",
|
"",
|
||||||
f"{self.spread.description}",
|
f"{self.spread.description}",
|
||||||
f"",
|
"",
|
||||||
f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
|
||||||
f"",
|
"",
|
||||||
]
|
]
|
||||||
|
|
||||||
for drawn in self.drawn_cards:
|
for drawn in self.drawn_cards:
|
||||||
@@ -319,16 +320,16 @@ class SpreadReading:
|
|||||||
lines.append(f" Meaning: {drawn.position.meaning}")
|
lines.append(f" Meaning: {drawn.position.meaning}")
|
||||||
|
|
||||||
# Add card details if available
|
# Add card details if available
|
||||||
if hasattr(card, 'number'):
|
if hasattr(card, "number"):
|
||||||
lines.append(f" Card #: {card.number}")
|
lines.append(f" Card #: {card.number}")
|
||||||
if hasattr(card, 'arcana'):
|
if hasattr(card, "arcana"):
|
||||||
lines.append(f" Arcana: {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(f" Suit: {card.suit.name}")
|
||||||
|
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
lines.append(f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
lines.append("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||||
|
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|||||||
@@ -6,16 +6,16 @@ MinorCard, and related classes for representing individual cards.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from .deck import (
|
from .deck import (
|
||||||
|
DLT,
|
||||||
|
AceCard,
|
||||||
Card,
|
Card,
|
||||||
|
CardQuery,
|
||||||
|
CourtCard,
|
||||||
|
Deck,
|
||||||
MajorCard,
|
MajorCard,
|
||||||
MinorCard,
|
MinorCard,
|
||||||
PipCard,
|
PipCard,
|
||||||
AceCard,
|
|
||||||
CourtCard,
|
|
||||||
CardQuery,
|
|
||||||
TemporalQuery,
|
TemporalQuery,
|
||||||
DLT,
|
|
||||||
Deck,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__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.
|
MajorCard, and MinorCard classes for representing individual cards.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from typing import List, Optional, Tuple, TYPE_CHECKING, Dict
|
|
||||||
import random
|
import random
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from ..attributes import (
|
from ..attributes import (
|
||||||
Meaning, CardImage, Suit, Zodiac, Element, Path,
|
CardImage,
|
||||||
Planet, Sephera, Color, PeriodicTable, ElementType, DoublLetterTrump
|
Color,
|
||||||
|
Element,
|
||||||
|
ElementType,
|
||||||
|
Meaning,
|
||||||
|
Path,
|
||||||
|
PeriodicTable,
|
||||||
|
Planet,
|
||||||
|
Sephera,
|
||||||
|
Suit,
|
||||||
)
|
)
|
||||||
from ..constants import (
|
from ..constants import (
|
||||||
COURT_RANKS,
|
COURT_RANKS,
|
||||||
MAJOR_ARCANA_NAMES,
|
MAJOR_ARCANA_NAMES,
|
||||||
PIP_INDEX_TO_NUMBER,
|
|
||||||
MINOR_RANK_NAMES,
|
MINOR_RANK_NAMES,
|
||||||
|
PIP_INDEX_TO_NUMBER,
|
||||||
PIP_ORDER,
|
PIP_ORDER,
|
||||||
SUITS_FIRST,
|
SUITS_FIRST,
|
||||||
SUITS_LAST,
|
SUITS_LAST,
|
||||||
@@ -35,6 +43,7 @@ def _get_card_data():
|
|||||||
global _card_data
|
global _card_data
|
||||||
if _card_data is None:
|
if _card_data is None:
|
||||||
from ..card.data import CardDataLoader
|
from ..card.data import CardDataLoader
|
||||||
|
|
||||||
_card_data = CardDataLoader()
|
_card_data = CardDataLoader()
|
||||||
return _card_data
|
return _card_data
|
||||||
|
|
||||||
@@ -42,6 +51,7 @@ def _get_card_data():
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Card:
|
class Card:
|
||||||
"""Base class representing a Tarot card."""
|
"""Base class representing a Tarot card."""
|
||||||
|
|
||||||
number: int
|
number: int
|
||||||
name: str
|
name: str
|
||||||
meaning: Meaning
|
meaning: Meaning
|
||||||
@@ -85,10 +95,17 @@ class Card:
|
|||||||
return CardDetailsRegistry.key_to_roman(self.number)
|
return CardDetailsRegistry.key_to_roman(self.number)
|
||||||
|
|
||||||
# For Minor Arcana, return the pip number as a formatted string
|
# 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 = {
|
pip_names = {
|
||||||
2: "Two", 3: "Three", 4: "Four", 5: "Five",
|
2: "Two",
|
||||||
6: "Six", 7: "Seven", 8: "Eight", 9: "Nine", 10: "Ten"
|
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))
|
return pip_names.get(self.pip, str(self.pip))
|
||||||
|
|
||||||
@@ -111,20 +128,26 @@ class Card:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class MajorCard(Card):
|
class MajorCard(Card):
|
||||||
"""Represents a Major Arcana card."""
|
"""Represents a Major Arcana card."""
|
||||||
|
|
||||||
kabbalistic_number: Optional[int] = None
|
kabbalistic_number: Optional[int] = None
|
||||||
tarot_letter: Optional[str] = None
|
tarot_letter: Optional[str] = None
|
||||||
tree_of_life_path: Optional[int] = None
|
tree_of_life_path: Optional[int] = None
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
# Kabbalistic number should be 0-21, but deck position can be anywhere
|
# 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):
|
if self.kabbalistic_number is not None and (
|
||||||
raise ValueError(f"Major Arcana kabbalistic number must be 0-21, got {self.kabbalistic_number}")
|
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"
|
self.arcana = "Major"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MinorCard(Card):
|
class MinorCard(Card):
|
||||||
"""Represents a Minor Arcana card - either Pip or Court card."""
|
"""Represents a Minor Arcana card - either Pip or Court card."""
|
||||||
|
|
||||||
suit: Suit = None # type: ignore
|
suit: Suit = None # type: ignore
|
||||||
astrological_influence: Optional[str] = None
|
astrological_influence: Optional[str] = None
|
||||||
element: Optional[Element] = None
|
element: Optional[Element] = None
|
||||||
@@ -142,6 +165,7 @@ class PipCard(MinorCard):
|
|||||||
Pip cards represent numbered forces in their suit, from Two
|
Pip cards represent numbered forces in their suit, from Two
|
||||||
through its full development (10).
|
through its full development (10).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pip: int = 0
|
pip: int = 0
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
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
|
for all other cards within that suit. Aces have pip=1 but are not
|
||||||
technically pip cards.
|
technically pip cards.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pip: int = 1
|
pip: int = 1
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
@@ -183,7 +208,7 @@ class CourtCard(MinorCard):
|
|||||||
COURT_RANKS = {"Knight": 12, "Prince": 11, "Princess": 13, "Queen": 14}
|
COURT_RANKS = {"Knight": 12, "Prince": 11, "Princess": 13, "Queen": 14}
|
||||||
court_rank: str = ""
|
court_rank: str = ""
|
||||||
associated_element: Optional[ElementType] = None
|
associated_element: Optional[ElementType] = None
|
||||||
hebrew_letter_path: Optional['Path'] = None
|
hebrew_letter_path: Optional["Path"] = None
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
if self.court_rank not in self.COURT_RANKS:
|
if self.court_rank not in self.COURT_RANKS:
|
||||||
@@ -194,12 +219,12 @@ class CourtCard(MinorCard):
|
|||||||
super().__post_init__()
|
super().__post_init__()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CardQuery:
|
class CardQuery:
|
||||||
"""Helper class for fluent card queries: deck.number(3).minor.wands"""
|
"""Helper class for fluent card queries: deck.number(3).minor.wands"""
|
||||||
|
|
||||||
def __init__(self, deck: 'Deck', number: Optional[int] = None,
|
def __init__(
|
||||||
arcana: Optional[str] = None) -> None:
|
self, deck: "Deck", number: Optional[int] = None, arcana: Optional[str] = None
|
||||||
|
) -> None:
|
||||||
self.deck = deck
|
self.deck = deck
|
||||||
self.number = number
|
self.number = number
|
||||||
self.arcana = arcana
|
self.arcana = arcana
|
||||||
@@ -209,8 +234,11 @@ class CardQuery:
|
|||||||
cards = self.deck.cards
|
cards = self.deck.cards
|
||||||
|
|
||||||
if self.number is not None:
|
if self.number is not None:
|
||||||
cards = [c for c in cards if c.number == self.number or
|
cards = [
|
||||||
(hasattr(c, 'pip') and c.pip == self.number)]
|
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:
|
if self.arcana is not None:
|
||||||
cards = [c for c in cards if c.arcana == self.arcana]
|
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"]
|
return [c for c in self._filter_cards() if c.arcana == "Major"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def minor(self) -> 'CardQuery':
|
def minor(self) -> "CardQuery":
|
||||||
"""Filter to Minor Arcana, return new CardQuery for suit chaining."""
|
"""Filter to Minor Arcana, return new CardQuery for suit chaining."""
|
||||||
return CardQuery(self.deck, self.number, "Minor")
|
return CardQuery(self.deck, self.number, "Minor")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cups(self) -> List[Card]:
|
def cups(self) -> List[Card]:
|
||||||
"""Get cards in Cups suit."""
|
"""Get cards in Cups suit."""
|
||||||
return [c for c in self._filter_cards() if hasattr(c, 'suit') and
|
return [
|
||||||
c.suit and c.suit.name == "Cups"]
|
c
|
||||||
|
for c in self._filter_cards()
|
||||||
|
if hasattr(c, "suit") and c.suit and c.suit.name == "Cups"
|
||||||
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def swords(self) -> List[Card]:
|
def swords(self) -> List[Card]:
|
||||||
"""Get cards in Swords suit."""
|
"""Get cards in Swords suit."""
|
||||||
return [c for c in self._filter_cards() if hasattr(c, 'suit') and
|
return [
|
||||||
c.suit and c.suit.name == "Swords"]
|
c
|
||||||
|
for c in self._filter_cards()
|
||||||
|
if hasattr(c, "suit") and c.suit and c.suit.name == "Swords"
|
||||||
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def wands(self) -> List[Card]:
|
def wands(self) -> List[Card]:
|
||||||
"""Get cards in Wands suit."""
|
"""Get cards in Wands suit."""
|
||||||
return [c for c in self._filter_cards() if hasattr(c, 'suit') and
|
return [
|
||||||
c.suit and c.suit.name == "Wands"]
|
c
|
||||||
|
for c in self._filter_cards()
|
||||||
|
if hasattr(c, "suit") and c.suit and c.suit.name == "Wands"
|
||||||
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pentacles(self) -> List[Card]:
|
def pentacles(self) -> List[Card]:
|
||||||
"""Get cards in Pentacles suit."""
|
"""Get cards in Pentacles suit."""
|
||||||
return [c for c in self._filter_cards() if hasattr(c, 'suit') and
|
return [
|
||||||
c.suit and c.suit.name == "Pentacles"]
|
c
|
||||||
|
for c in self._filter_cards()
|
||||||
|
if hasattr(c, "suit") and c.suit and c.suit.name == "Pentacles"
|
||||||
|
]
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
"""Allow iteration over filtered cards."""
|
"""Allow iteration over filtered cards."""
|
||||||
@@ -272,8 +312,13 @@ class CardQuery:
|
|||||||
class TemporalQuery:
|
class TemporalQuery:
|
||||||
"""Helper class for fluent temporal queries: loader.month(5).day(23).hour(15)"""
|
"""Helper class for fluent temporal queries: loader.month(5).day(23).hour(15)"""
|
||||||
|
|
||||||
def __init__(self, loader: 'CardDataLoader', month_num: Optional[int] = None,
|
def __init__(
|
||||||
day_num: Optional[int] = None, hour_num: Optional[int] = None) -> None:
|
self,
|
||||||
|
loader: "CardDataLoader",
|
||||||
|
month_num: Optional[int] = None,
|
||||||
|
day_num: Optional[int] = None,
|
||||||
|
hour_num: Optional[int] = None,
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Initialize temporal query builder.
|
Initialize temporal query builder.
|
||||||
|
|
||||||
@@ -288,24 +333,27 @@ class TemporalQuery:
|
|||||||
self.day_num = day_num
|
self.day_num = day_num
|
||||||
self.hour_num = hour_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."""
|
"""Set month (1-12) and return new query for chaining."""
|
||||||
return TemporalQuery(self.loader, month_num=num,
|
return TemporalQuery(
|
||||||
day_num=self.day_num, hour_num=self.hour_num)
|
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."""
|
"""Set day (1-31) and return new query for chaining."""
|
||||||
if self.month_num is None:
|
if self.month_num is None:
|
||||||
raise ValueError("Must set month before day")
|
raise ValueError("Must set month before day")
|
||||||
return TemporalQuery(self.loader, month_num=self.month_num,
|
return TemporalQuery(
|
||||||
day_num=num, hour_num=self.hour_num)
|
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."""
|
"""Set hour (0-23) and return new query for chaining."""
|
||||||
if self.month_num is None or self.day_num is None:
|
if self.month_num is None or self.day_num is None:
|
||||||
raise ValueError("Must set month and day before hour")
|
raise ValueError("Must set month and day before hour")
|
||||||
return TemporalQuery(self.loader, month_num=self.month_num,
|
return TemporalQuery(
|
||||||
day_num=self.day_num, hour_num=num)
|
self.loader, month_num=self.month_num, day_num=self.day_num, hour_num=num
|
||||||
|
)
|
||||||
|
|
||||||
def weekday(self) -> Optional[str]:
|
def weekday(self) -> Optional[str]:
|
||||||
"""Get weekday name for current month/day combination using Zeller's congruence."""
|
"""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}")
|
raise ValueError(f"DLT number must be 3-21, got {trump_number}")
|
||||||
|
|
||||||
self.trump_number = trump_number
|
self.trump_number = trump_number
|
||||||
self._loader: Optional['CardDataLoader'] = None
|
self._loader: Optional["CardDataLoader"] = None
|
||||||
self._deck: Optional[Deck] = None
|
self._deck: Optional[Deck] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def loader(self) -> 'CardDataLoader':
|
def loader(self) -> "CardDataLoader":
|
||||||
"""Lazy-load CardDataLoader on first access."""
|
"""Lazy-load CardDataLoader on first access."""
|
||||||
if self._loader is None:
|
if self._loader is None:
|
||||||
from ..card.data import CardDataLoader
|
from ..card.data import CardDataLoader
|
||||||
|
|
||||||
self._loader = CardDataLoader()
|
self._loader = CardDataLoader()
|
||||||
return self._loader
|
return self._loader
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def deck(self) -> 'Deck':
|
def deck(self) -> "Deck":
|
||||||
"""Lazy-load Deck on first access."""
|
"""Lazy-load Deck on first access."""
|
||||||
if self._deck is None:
|
if self._deck is None:
|
||||||
self._deck = Deck()
|
self._deck = Deck()
|
||||||
@@ -486,7 +535,7 @@ class Deck:
|
|||||||
# Get Hebrew letters (Paths) for court cards
|
# Get Hebrew letters (Paths) for court cards
|
||||||
yod_path = card_data.path(20) # Yod
|
yod_path = card_data.path(20) # Yod
|
||||||
vav_path = card_data.path(16) # Vav
|
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:
|
if not yod_path or not vav_path or not he_path:
|
||||||
raise RuntimeError("Failed to load Hebrew letter/path data from CardDataLoader")
|
raise RuntimeError("Failed to load Hebrew letter/path data from CardDataLoader")
|
||||||
@@ -507,12 +556,16 @@ class Deck:
|
|||||||
"Fire": fire_element,
|
"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]] = []
|
specs: List[Tuple[str, ElementType, int]] = []
|
||||||
for suit_name, element_key, suit_num in suit_defs:
|
for suit_name, element_key, suit_num in suit_defs:
|
||||||
element_obj = element_lookup.get(element_key)
|
element_obj = element_lookup.get(element_key)
|
||||||
if element_obj is None:
|
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))
|
specs.append((suit_name, element_obj, suit_num))
|
||||||
return specs
|
return specs
|
||||||
|
|
||||||
@@ -538,11 +591,10 @@ class Deck:
|
|||||||
number=card_number,
|
number=card_number,
|
||||||
name=name,
|
name=name,
|
||||||
meaning=Meaning(
|
meaning=Meaning(
|
||||||
upright=f"{name} upright meaning",
|
upright=f"{name} upright meaning", reversed=f"{name} reversed meaning"
|
||||||
reversed=f"{name} reversed meaning"
|
|
||||||
),
|
),
|
||||||
arcana="Major",
|
arcana="Major",
|
||||||
kabbalistic_number=i
|
kabbalistic_number=i,
|
||||||
)
|
)
|
||||||
self.cards.append(card)
|
self.cards.append(card)
|
||||||
card_number += 1
|
card_number += 1
|
||||||
@@ -561,12 +613,12 @@ class Deck:
|
|||||||
# Load detailed explanations and keywords from registry
|
# Load detailed explanations and keywords from registry
|
||||||
try:
|
try:
|
||||||
from ..card.loader import load_deck_details
|
from ..card.loader import load_deck_details
|
||||||
|
|
||||||
load_deck_details(self)
|
load_deck_details(self)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Handle case where loader might not be available or circular import issues
|
# Handle case where loader might not be available or circular import issues
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _add_minor_cards_for_suit(
|
def _add_minor_cards_for_suit(
|
||||||
self,
|
self,
|
||||||
suit_name: str,
|
suit_name: str,
|
||||||
@@ -625,7 +677,6 @@ class Deck:
|
|||||||
|
|
||||||
return card_number
|
return card_number
|
||||||
|
|
||||||
|
|
||||||
def shuffle(self) -> None:
|
def shuffle(self) -> None:
|
||||||
"""Shuffle the deck."""
|
"""Shuffle the deck."""
|
||||||
random.shuffle(self.cards)
|
random.shuffle(self.cards)
|
||||||
@@ -644,7 +695,9 @@ class Deck:
|
|||||||
raise ValueError("Must draw at least 1 card")
|
raise ValueError("Must draw at least 1 card")
|
||||||
|
|
||||||
if num_cards > len(self.cards):
|
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 = []
|
drawn = []
|
||||||
for _ in range(num_cards):
|
for _ in range(num_cards):
|
||||||
@@ -680,8 +733,7 @@ class Deck:
|
|||||||
Usage:
|
Usage:
|
||||||
deck.suit("Wands")
|
deck.suit("Wands")
|
||||||
"""
|
"""
|
||||||
return [c for c in self.cards if hasattr(c, 'suit') and
|
return [c for c in self.cards if hasattr(c, "suit") and c.suit and c.suit.name == suit_name]
|
||||||
c.suit and c.suit.name == suit_name]
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def major(self) -> List[Card]:
|
def major(self) -> List[Card]:
|
||||||
|
|||||||
@@ -25,15 +25,18 @@ Usage:
|
|||||||
area = Tarot.cube.area('North', 'center')
|
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 Cube, Tree
|
||||||
from kaballah import Tree, Cube
|
|
||||||
from letter import letters
|
from letter import letters
|
||||||
|
|
||||||
|
from .card import CardAccessor
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from utils.attributes import Planet, God
|
from utils.attributes import God, Planet
|
||||||
|
|
||||||
from .attributes import Hexagram
|
from .attributes import Hexagram
|
||||||
|
from .card.data import CardDataLoader
|
||||||
|
|
||||||
|
|
||||||
class DeckAccessor:
|
class DeckAccessor:
|
||||||
@@ -44,16 +47,20 @@ class DeckAccessor:
|
|||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
"""Return a nice summary of the deck accessor."""
|
"""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:
|
def __repr__(self) -> str:
|
||||||
"""Return a nice representation of the deck accessor."""
|
"""Return a nice representation of the deck accessor."""
|
||||||
return self.__str__()
|
return self.__str__()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Tarot:
|
class Tarot:
|
||||||
"""
|
"""
|
||||||
Unified accessor for Tarot correspondences and data.
|
Unified accessor for Tarot correspondences and data.
|
||||||
@@ -75,7 +82,7 @@ class Tarot:
|
|||||||
tree = Tree
|
tree = Tree
|
||||||
cube = Cube
|
cube = Cube
|
||||||
|
|
||||||
_loader: Optional['CardDataLoader'] = None # type: ignore
|
_loader: Optional["CardDataLoader"] = None # type: ignore
|
||||||
_initialized: bool = False
|
_initialized: bool = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -85,35 +92,34 @@ class Tarot:
|
|||||||
return
|
return
|
||||||
|
|
||||||
from .card.data import CardDataLoader
|
from .card.data import CardDataLoader
|
||||||
|
|
||||||
cls._loader = CardDataLoader()
|
cls._loader = CardDataLoader()
|
||||||
cls._initialized = True
|
cls._initialized = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@overload
|
@overload
|
||||||
def planet(cls, name: str) -> Optional['Planet']:
|
def planet(cls, name: str) -> Optional["Planet"]: ...
|
||||||
...
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@overload
|
@overload
|
||||||
def planet(cls, name: None = ...) -> Dict[str, 'Planet']:
|
def planet(cls, name: None = ...) -> Dict[str, "Planet"]: ...
|
||||||
...
|
|
||||||
|
|
||||||
@classmethod
|
@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."""
|
"""Return a planet entry or all planets."""
|
||||||
cls._ensure_initialized()
|
cls._ensure_initialized()
|
||||||
return cls._loader.planet(name) # type: ignore
|
return cls._loader.planet(name) # type: ignore
|
||||||
|
|
||||||
@classmethod
|
@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."""
|
"""Return a god entry or all gods."""
|
||||||
cls._ensure_initialized()
|
cls._ensure_initialized()
|
||||||
return cls._loader.god(name) # type: ignore
|
return cls._loader.god(name) # type: ignore
|
||||||
|
|
||||||
@classmethod
|
@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."""
|
"""Return a hexagram or all hexagrams."""
|
||||||
cls._ensure_initialized()
|
cls._ensure_initialized()
|
||||||
return cls._loader.hexagram(number) # type: ignore
|
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 os
|
||||||
import io
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk, filedialog
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from tkinter import filedialog, ttk
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PIL import Image, ImageTk, ImageGrab, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont, ImageTk
|
||||||
|
|
||||||
HAS_PILLOW = True
|
HAS_PILLOW = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_PILLOW = False
|
HAS_PILLOW = False
|
||||||
|
|
||||||
from tarot.deck import Card
|
|
||||||
from tarot.card.image_loader import ImageDeckLoader
|
from tarot.card.image_loader import ImageDeckLoader
|
||||||
|
from tarot.deck import Card
|
||||||
|
|
||||||
|
|
||||||
class CardDisplay:
|
class CardDisplay:
|
||||||
@@ -66,7 +66,7 @@ class CardDisplay:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Import spread classes here to avoid circular imports if any
|
# 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
|
# Create a dummy spread class since Spread requires a valid name
|
||||||
class SimpleSpread:
|
class SimpleSpread:
|
||||||
@@ -81,19 +81,11 @@ class CardDisplay:
|
|||||||
|
|
||||||
for i, card in enumerate(cards, 1):
|
for i, card in enumerate(cards, 1):
|
||||||
# Create a generic position
|
# Create a generic position
|
||||||
pos = SpreadPosition(
|
pos = SpreadPosition(number=i, name=f"Card {i}", meaning="Display Card")
|
||||||
number=i,
|
|
||||||
name=f"Card {i}",
|
|
||||||
meaning="Display Card"
|
|
||||||
)
|
|
||||||
positions.append(pos)
|
positions.append(pos)
|
||||||
|
|
||||||
# Create drawn card
|
# Create drawn card
|
||||||
drawn = DrawnCard(
|
drawn = DrawnCard(position=pos, card=card, is_reversed=False)
|
||||||
position=pos,
|
|
||||||
card=card,
|
|
||||||
is_reversed=False
|
|
||||||
)
|
|
||||||
drawn_cards.append(drawn)
|
drawn_cards.append(drawn)
|
||||||
|
|
||||||
# Create a synthetic spread
|
# Create a synthetic spread
|
||||||
@@ -101,7 +93,7 @@ class CardDisplay:
|
|||||||
spread.positions = positions
|
spread.positions = positions
|
||||||
|
|
||||||
# Create reading
|
# Create reading
|
||||||
reading = SpreadReading(spread, drawn_cards) # type: ignore
|
reading = SpreadReading(spread, drawn_cards) # type: ignore
|
||||||
|
|
||||||
# Use SpreadDisplay
|
# Use SpreadDisplay
|
||||||
display = SpreadDisplay(reading, self.deck_name)
|
display = SpreadDisplay(reading, self.deck_name)
|
||||||
@@ -109,8 +101,6 @@ class CardDisplay:
|
|||||||
display.run()
|
display.run()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CubeDisplay:
|
class CubeDisplay:
|
||||||
"""
|
"""
|
||||||
Displays the Cube of Space with navigation.
|
Displays the Cube of Space with navigation.
|
||||||
@@ -124,6 +114,12 @@ class CubeDisplay:
|
|||||||
"Above": {"Right": "East", "Left": "West", "Up": "South", "Down": "North"},
|
"Above": {"Right": "East", "Left": "West", "Up": "South", "Down": "North"},
|
||||||
"Below": {"Right": "East", "Left": "West", "Up": "North", "Down": "South"},
|
"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"):
|
def __init__(self, cube, deck_name: str = "default"):
|
||||||
self.cube = cube
|
self.cube = cube
|
||||||
@@ -153,9 +149,9 @@ class CubeDisplay:
|
|||||||
self.root.bind("<plus>", lambda e: self._zoom(1.1))
|
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("<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("<minus>", lambda e: self._zoom(0.9))
|
||||||
self.root.bind("<underscore>", lambda e: self._zoom(0.9)) # Shift+minus
|
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_Add>", lambda e: self._zoom(1.1)) # Numpad +
|
||||||
self.root.bind("<KP_Subtract>", lambda e: self._zoom(0.9)) # Numpad -
|
self.root.bind("<KP_Subtract>", lambda e: self._zoom(0.9)) # Numpad -
|
||||||
|
|
||||||
# Bind WASD for panning
|
# Bind WASD for panning
|
||||||
self.root.bind("w", lambda e: self._pan_key("up"))
|
self.root.bind("w", lambda e: self._pan_key("up"))
|
||||||
@@ -181,7 +177,9 @@ class CubeDisplay:
|
|||||||
|
|
||||||
# Content Frame (inside canvas)
|
# Content Frame (inside canvas)
|
||||||
self.content_frame = ttk.Frame(self.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
|
# Overlay Controls
|
||||||
# Navigation Frame (Bottom Center)
|
# Navigation Frame (Bottom Center)
|
||||||
@@ -194,8 +192,12 @@ class CubeDisplay:
|
|||||||
|
|
||||||
# Populate Zoom Frame
|
# Populate Zoom Frame
|
||||||
ttk.Label(zoom_frame, text="Zoom:").pack(side=tk.LEFT, padx=5)
|
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(1.22)).pack(
|
||||||
ttk.Button(zoom_frame, text="-", width=3, command=lambda: self._zoom(0.82)).pack(side=tk.LEFT)
|
side=tk.LEFT
|
||||||
|
)
|
||||||
|
ttk.Button(zoom_frame, text="-", width=3, command=lambda: self._zoom(0.82)).pack(
|
||||||
|
side=tk.LEFT
|
||||||
|
)
|
||||||
|
|
||||||
# Populate Navigation Frame
|
# Populate Navigation Frame
|
||||||
dir_frame = ttk.Frame(nav_frame)
|
dir_frame = ttk.Frame(nav_frame)
|
||||||
@@ -205,8 +207,12 @@ class CubeDisplay:
|
|||||||
|
|
||||||
mid_nav = ttk.Frame(dir_frame)
|
mid_nav = ttk.Frame(dir_frame)
|
||||||
mid_nav.pack(side=tk.TOP)
|
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="Left", command=lambda: self._navigate("Left")).pack(
|
||||||
ttk.Button(mid_nav, text="Right", command=lambda: self._navigate("Right")).pack(side=tk.LEFT, padx=5)
|
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)
|
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()
|
screen_height = self.root.winfo_screenheight()
|
||||||
x = (screen_width // 2) - (width // 2)
|
x = (screen_width // 2) - (width // 2)
|
||||||
y = (screen_height // 2) - (height // 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
|
# Ensure window has focus for keyboard events
|
||||||
self.root.focus_force()
|
self.root.focus_force()
|
||||||
@@ -238,17 +244,27 @@ class CubeDisplay:
|
|||||||
|
|
||||||
def _pan_key(self, direction):
|
def _pan_key(self, direction):
|
||||||
"""Pan the canvas using keys."""
|
"""Pan the canvas using keys."""
|
||||||
if direction == 'up':
|
if direction == "up":
|
||||||
self.canvas.yview_scroll(-1, "units")
|
self.canvas.yview_scroll(-1, "units")
|
||||||
elif direction == 'down':
|
elif direction == "down":
|
||||||
self.canvas.yview_scroll(1, "units")
|
self.canvas.yview_scroll(1, "units")
|
||||||
elif direction == 'left':
|
elif direction == "left":
|
||||||
self.canvas.xview_scroll(-1, "units")
|
self.canvas.xview_scroll(-1, "units")
|
||||||
elif direction == 'right':
|
elif direction == "right":
|
||||||
self.canvas.xview_scroll(1, "units")
|
self.canvas.xview_scroll(1, "units")
|
||||||
|
|
||||||
def _zoom(self, factor):
|
def _zoom(self, factor):
|
||||||
"""Adjust zoom level and redraw, keeping the view centered."""
|
"""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
|
# 1. Capture current state
|
||||||
canvas_width = self.canvas.winfo_width()
|
canvas_width = self.canvas.winfo_width()
|
||||||
canvas_height = self.canvas.winfo_height()
|
canvas_height = self.canvas.winfo_height()
|
||||||
@@ -262,7 +278,8 @@ class CubeDisplay:
|
|||||||
if not bbox:
|
if not bbox:
|
||||||
# Should not happen if initialized
|
# Should not happen if initialized
|
||||||
self.zoom_level *= factor
|
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()
|
self._update_display()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -276,8 +293,12 @@ class CubeDisplay:
|
|||||||
# 2. Update zoom level
|
# 2. Update zoom level
|
||||||
old_zoom = self.zoom_level
|
old_zoom = self.zoom_level
|
||||||
self.zoom_level *= factor
|
self.zoom_level *= factor
|
||||||
# Clamp zoom level
|
# Clamp zoom level (UI uses broader bounds when initialized)
|
||||||
self.zoom_level = max(0.1, min(self.zoom_level, 50.0))
|
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
|
# Calculate effective factor in case of clamping
|
||||||
effective_factor = self.zoom_level / old_zoom if old_zoom > 0 else factor
|
effective_factor = self.zoom_level / old_zoom if old_zoom > 0 else factor
|
||||||
@@ -335,8 +356,11 @@ class CubeDisplay:
|
|||||||
widget.destroy()
|
widget.destroy()
|
||||||
|
|
||||||
# Title
|
# Title
|
||||||
ttk.Label(self.content_frame, text=f"Wall: {self.current_wall_name}",
|
ttk.Label(
|
||||||
font=("Helvetica", 16, "bold")).pack(pady=(0, 20))
|
self.content_frame,
|
||||||
|
text=f"Wall: {self.current_wall_name}",
|
||||||
|
font=("Helvetica", 16, "bold"),
|
||||||
|
).pack(pady=(0, 20))
|
||||||
|
|
||||||
# Grid for directions
|
# Grid for directions
|
||||||
grid_frame = ttk.Frame(self.content_frame)
|
grid_frame = ttk.Frame(self.content_frame)
|
||||||
@@ -351,7 +375,7 @@ class CubeDisplay:
|
|||||||
"West": (1, 0),
|
"West": (1, 0),
|
||||||
"Center": (1, 1),
|
"Center": (1, 1),
|
||||||
"East": (1, 2),
|
"East": (1, 2),
|
||||||
"South": (2, 1)
|
"South": (2, 1),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Calculate sizes based on zoom
|
# Calculate sizes based on zoom
|
||||||
@@ -365,7 +389,9 @@ class CubeDisplay:
|
|||||||
for dir_name, (row, col) in layout.items():
|
for dir_name, (row, col) in layout.items():
|
||||||
direction = wall.direction(dir_name)
|
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(row=row, column=col, padx=5, pady=5)
|
||||||
cell_frame.grid_propagate(False)
|
cell_frame.grid_propagate(False)
|
||||||
|
|
||||||
@@ -385,9 +411,11 @@ class CubeDisplay:
|
|||||||
pil_img = Image.open(img_path)
|
pil_img = Image.open(img_path)
|
||||||
# Resize for grid
|
# Resize for grid
|
||||||
base_height = img_height
|
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)))
|
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)
|
tk_img = ImageTk.PhotoImage(pil_img)
|
||||||
self.root.images.append(tk_img)
|
self.root.images.append(tk_img)
|
||||||
@@ -414,10 +442,10 @@ class CubeDisplay:
|
|||||||
content_height = self.content_frame.winfo_reqheight()
|
content_height = self.content_frame.winfo_reqheight()
|
||||||
|
|
||||||
if canvas_width > content_width and canvas_height > content_height:
|
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:
|
else:
|
||||||
# Reset to top-left or center of scroll region
|
# Reset to top-left or center of scroll region
|
||||||
self.canvas.coords(self.canvas_window, content_width/2, content_height/2)
|
self.canvas.coords(self.canvas_window, content_width / 2, content_height / 2)
|
||||||
|
|
||||||
# Bind panning events to all content widgets
|
# Bind panning events to all content widgets
|
||||||
self._bind_recursive(self.content_frame)
|
self._bind_recursive(self.content_frame)
|
||||||
@@ -491,6 +519,7 @@ def display_cube(cube=None, deck_name: str = "default"):
|
|||||||
"""
|
"""
|
||||||
if cube is None:
|
if cube is None:
|
||||||
from tarot.tarot_api import Tarot
|
from tarot.tarot_api import Tarot
|
||||||
|
|
||||||
cube = Tarot.cube
|
cube = Tarot.cube
|
||||||
|
|
||||||
display = CubeDisplay(cube, deck_name)
|
display = CubeDisplay(cube, deck_name)
|
||||||
@@ -506,54 +535,48 @@ class SpreadDisplay:
|
|||||||
# Coordinates are relative grid units (approx card width/height)
|
# Coordinates are relative grid units (approx card width/height)
|
||||||
# Using 1.02 spacing for tight layout
|
# Using 1.02 spacing for tight layout
|
||||||
LAYOUTS = {
|
LAYOUTS = {
|
||||||
'Celtic Cross': {
|
"Celtic Cross": {
|
||||||
1: {'pos': (0, 0)},
|
1: {"pos": (0, 0)},
|
||||||
2: {'pos': (0, 0), 'rotate': 90, 'z': 10}, # Top layer
|
2: {"pos": (0, 0), "rotate": 90, "z": 10}, # Top layer
|
||||||
3: {'pos': (0, -1.02)},
|
3: {"pos": (0, -1.02)},
|
||||||
4: {'pos': (0, 1.02)},
|
4: {"pos": (0, 1.02)},
|
||||||
5: {'pos': (-1.02, 0)},
|
5: {"pos": (-1.02, 0)},
|
||||||
6: {'pos': (1.02, 0)},
|
6: {"pos": (1.02, 0)},
|
||||||
7: {'pos': (2.1, 1.53)}, # 1.5 * 1.02
|
7: {"pos": (2.1, 1.53)}, # 1.5 * 1.02
|
||||||
8: {'pos': (2.1, 0.51)}, # 0.5 * 1.02
|
8: {"pos": (2.1, 0.51)}, # 0.5 * 1.02
|
||||||
9: {'pos': (2.1, -0.51)},
|
9: {"pos": (2.1, -0.51)},
|
||||||
10: {'pos': (2.1, -1.53)}
|
10: {"pos": (2.1, -1.53)},
|
||||||
},
|
},
|
||||||
'3-Card Spread': {
|
"3-Card Spread": {1: {"pos": (-1.02, 0)}, 2: {"pos": (0, 0)}, 3: {"pos": (1.02, 0)}},
|
||||||
1: {'pos': (-1.02, 0)},
|
"Golden Dawn 3-Card": {
|
||||||
2: {'pos': (0, 0)},
|
1: {"pos": (0, -1.02)},
|
||||||
3: {'pos': (1.02, 0)}
|
2: {"pos": (-1.02, 0.8)},
|
||||||
|
3: {"pos": (1.02, 0.8)},
|
||||||
},
|
},
|
||||||
'Golden Dawn 3-Card': {
|
"Horseshoe": {
|
||||||
1: {'pos': (0, -1.02)},
|
1: {"pos": (-3.06, 1.02)},
|
||||||
2: {'pos': (-1.02, 0.8)},
|
2: {"pos": (-2.04, 0)},
|
||||||
3: {'pos': (1.02, 0.8)}
|
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': {
|
"Pentagram": {
|
||||||
1: {'pos': (-3.06, 1.02)},
|
1: {"pos": (0, -1.5)}, # Spirit (Top)
|
||||||
2: {'pos': (-2.04, 0)},
|
2: {"pos": (1.5, -0.4)}, # Fire (Right Top)
|
||||||
3: {'pos': (-1.02, -0.51)},
|
3: {"pos": (1.0, 1.5)}, # Water (Right Bottom)
|
||||||
4: {'pos': (0, -1.02)},
|
4: {"pos": (-1.0, 1.5)}, # Air (Left Bottom)
|
||||||
5: {'pos': (1.02, -0.51)},
|
5: {"pos": (-1.5, -0.4)}, # Earth (Left Top)
|
||||||
6: {'pos': (2.04, 0)},
|
|
||||||
7: {'pos': (3.06, 1.02)}
|
|
||||||
},
|
},
|
||||||
'Pentagram': {
|
"Relationship": {
|
||||||
1: {'pos': (0, -1.5)}, # Spirit (Top)
|
1: {"pos": (-1.5, 0)}, # You
|
||||||
2: {'pos': (1.5, -0.4)}, # Fire (Right Top)
|
2: {"pos": (1.5, 0)}, # Them
|
||||||
3: {'pos': (1.0, 1.5)}, # Water (Right Bottom)
|
3: {"pos": (0, -1.02)}, # Relationship (Center Top)
|
||||||
4: {'pos': (-1.0, 1.5)}, # Air (Left Bottom)
|
4: {"pos": (0, 0.5)}, # Challenge (Center Bottom)
|
||||||
5: {'pos': (-1.5, -0.4)} # Earth (Left Top)
|
5: {"pos": (0, 2.0)}, # Outcome (Bottom)
|
||||||
},
|
},
|
||||||
'Relationship': {
|
"Yes or No": {1: {"pos": (0, 0)}},
|
||||||
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)}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, reading, deck_name="default"):
|
def __init__(self, reading, deck_name="default"):
|
||||||
@@ -566,7 +589,7 @@ class SpreadDisplay:
|
|||||||
self.show_text = True
|
self.show_text = True
|
||||||
self.show_top_card = True
|
self.show_top_card = True
|
||||||
self.drag_data = {"x": 0, "y": 0}
|
self.drag_data = {"x": 0, "y": 0}
|
||||||
self._tk_images = [] # Keep references
|
self._tk_images = [] # Keep references
|
||||||
|
|
||||||
# Setup UI
|
# Setup UI
|
||||||
self._setup_ui()
|
self._setup_ui()
|
||||||
@@ -593,15 +616,25 @@ class SpreadDisplay:
|
|||||||
toolbar = ttk.Frame(self.root)
|
toolbar = ttk.Frame(self.root)
|
||||||
toolbar.pack(side=tk.TOP, fill=tk.X)
|
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 In (+)", command=lambda: self._zoom(1.2)).pack(
|
||||||
ttk.Button(toolbar, text="Zoom Out (-)", command=lambda: self._zoom(0.8)).pack(side=tk.LEFT, padx=2)
|
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="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="Toggle Text", command=self._toggle_text).pack(
|
||||||
ttk.Button(toolbar, text="Export PNG", command=self._export_image).pack(side=tk.LEFT, padx=2)
|
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
|
# Only show toggle top card if relevant
|
||||||
if self.reading.spread.name == 'Celtic Cross':
|
if self.reading.spread.name == "Celtic Cross":
|
||||||
ttk.Button(toolbar, text="Toggle Cross", command=self._toggle_top_card).pack(side=tk.LEFT, padx=2)
|
ttk.Button(toolbar, text="Toggle Cross", command=self._toggle_top_card).pack(
|
||||||
|
side=tk.LEFT, padx=2
|
||||||
|
)
|
||||||
|
|
||||||
# Canvas
|
# Canvas
|
||||||
self.canvas = tk.Canvas(self.root, bg="#2c3e50")
|
self.canvas = tk.Canvas(self.root, bg="#2c3e50")
|
||||||
@@ -636,8 +669,8 @@ class SpreadDisplay:
|
|||||||
# Center of virtual space (arbitrary large number to allow scrolling)
|
# Center of virtual space (arbitrary large number to allow scrolling)
|
||||||
cx, cy = 2000, 2000
|
cx, cy = 2000, 2000
|
||||||
|
|
||||||
min_x, min_y = float('inf'), float('inf')
|
min_x, min_y = float("inf"), float("inf")
|
||||||
max_x, max_y = float('-inf'), float('-inf')
|
max_x, max_y = float("-inf"), float("-inf")
|
||||||
|
|
||||||
# Sort cards by z-index (default 0)
|
# Sort cards by z-index (default 0)
|
||||||
cards_to_draw = []
|
cards_to_draw = []
|
||||||
@@ -646,7 +679,7 @@ class SpreadDisplay:
|
|||||||
if not pos_data:
|
if not pos_data:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
z_index = pos_data.get('z', 0)
|
z_index = pos_data.get("z", 0)
|
||||||
|
|
||||||
# Skip if top card is hidden
|
# Skip if top card is hidden
|
||||||
if z_index > 0 and not self.show_top_card:
|
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])
|
cards_to_draw.sort(key=lambda x: x[2])
|
||||||
|
|
||||||
for drawn, pos_data, _ in cards_to_draw:
|
for drawn, pos_data, _ in cards_to_draw:
|
||||||
rel_x, rel_y = pos_data['pos']
|
rel_x, rel_y = pos_data["pos"]
|
||||||
rotation = pos_data.get('rotate', 0)
|
rotation = pos_data.get("rotate", 0)
|
||||||
|
|
||||||
# Calculate position
|
# Calculate position
|
||||||
x = cx + (rel_x * unit_x)
|
x = cx + (rel_x * unit_x)
|
||||||
@@ -672,7 +705,7 @@ class SpreadDisplay:
|
|||||||
|
|
||||||
# If rotated 90 deg, width and height swap for bounding box
|
# If rotated 90 deg, width and height swap for bounding box
|
||||||
if abs(rotation % 180) == 90:
|
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)
|
min_x = min(min_x, x - half_w)
|
||||||
max_x = max(max_x, x + half_w)
|
max_x = max(max_x, x + half_w)
|
||||||
@@ -684,7 +717,9 @@ class SpreadDisplay:
|
|||||||
|
|
||||||
# Set scroll region
|
# Set scroll region
|
||||||
padding = 50 * self.zoom_level
|
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):
|
def _draw_card(self, drawn, x, y, w, h, layout_rotation):
|
||||||
card_name = drawn.card.name
|
card_name = drawn.card.name
|
||||||
@@ -701,8 +736,10 @@ class SpreadDisplay:
|
|||||||
|
|
||||||
if not pil_image:
|
if not pil_image:
|
||||||
# Draw placeholder
|
# 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_rectangle(
|
||||||
self.canvas.create_text(x, y, text=card_name, width=w-10)
|
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:
|
else:
|
||||||
# Total rotation
|
# Total rotation
|
||||||
rotation = layout_rotation
|
rotation = layout_rotation
|
||||||
@@ -728,7 +765,7 @@ class SpreadDisplay:
|
|||||||
|
|
||||||
# Text Overlay
|
# Text Overlay
|
||||||
# Calculate visual dimensions
|
# Calculate visual dimensions
|
||||||
is_vertical = (layout_rotation % 180 == 0)
|
is_vertical = layout_rotation % 180 == 0
|
||||||
vis_w = w if is_vertical else h
|
vis_w = w if is_vertical else h
|
||||||
vis_h = h if is_vertical else w
|
vis_h = h if is_vertical else w
|
||||||
|
|
||||||
@@ -739,35 +776,36 @@ class SpreadDisplay:
|
|||||||
# Height needed: approx 3 lines of text
|
# Height needed: approx 3 lines of text
|
||||||
text_h = font_size * 3.5
|
text_h = font_size * 3.5
|
||||||
|
|
||||||
bg_x1 = x - vis_w/2
|
bg_x1 = x - vis_w / 2
|
||||||
bg_y1 = y + vis_h/2 - text_h
|
bg_y1 = y + vis_h / 2 - text_h
|
||||||
bg_x2 = x + vis_w/2
|
bg_x2 = x + vis_w / 2
|
||||||
bg_y2 = y + vis_h/2
|
bg_y2 = y + vis_h / 2
|
||||||
|
|
||||||
# Draw semi-transparent-ish background (stipple works on some platforms, otherwise solid)
|
# Draw semi-transparent-ish background (stipple works on some platforms, otherwise solid)
|
||||||
self.canvas.create_rectangle(
|
self.canvas.create_rectangle(
|
||||||
bg_x1, bg_y1, bg_x2, bg_y2,
|
bg_x1, bg_y1, bg_x2, bg_y2, fill="#000000", stipple="gray75", outline=""
|
||||||
fill="#000000", stipple="gray75", outline=""
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Position Name
|
# Position Name
|
||||||
self.canvas.create_text(
|
self.canvas.create_text(
|
||||||
x, bg_y1 + font_size,
|
x,
|
||||||
|
bg_y1 + font_size,
|
||||||
text=f"{drawn.position.number}. {drawn.position.name}",
|
text=f"{drawn.position.number}. {drawn.position.name}",
|
||||||
fill="white",
|
fill="white",
|
||||||
font=("Arial", font_size, "bold"),
|
font=("Arial", font_size, "bold"),
|
||||||
width=vis_w - 4,
|
width=vis_w - 4,
|
||||||
justify="center"
|
justify="center",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Meaning (shortened)
|
# Meaning (shortened)
|
||||||
self.canvas.create_text(
|
self.canvas.create_text(
|
||||||
x, bg_y1 + font_size * 2.2,
|
x,
|
||||||
|
bg_y1 + font_size * 2.2,
|
||||||
text=drawn.position.meaning,
|
text=drawn.position.meaning,
|
||||||
fill="#ecf0f1",
|
fill="#ecf0f1",
|
||||||
font=("Arial", int(font_size * 0.8)),
|
font=("Arial", int(font_size * 0.8)),
|
||||||
width=vis_w - 4,
|
width=vis_w - 4,
|
||||||
justify="center"
|
justify="center",
|
||||||
)
|
)
|
||||||
|
|
||||||
def _draw_grid_fallback(self):
|
def _draw_grid_fallback(self):
|
||||||
@@ -777,15 +815,15 @@ class SpreadDisplay:
|
|||||||
card_height = 150 * self.zoom_level
|
card_height = 150 * self.zoom_level
|
||||||
padding = 20 * self.zoom_level
|
padding = 20 * self.zoom_level
|
||||||
|
|
||||||
min_x, min_y = float('inf'), float('inf')
|
min_x, min_y = float("inf"), float("inf")
|
||||||
max_x, max_y = float('-inf'), float('-inf')
|
max_x, max_y = float("-inf"), float("-inf")
|
||||||
|
|
||||||
cols = 5
|
cols = 5
|
||||||
for i, drawn in enumerate(self.reading.drawn_cards):
|
for i, drawn in enumerate(self.reading.drawn_cards):
|
||||||
row = i // cols
|
row = i // cols
|
||||||
col = 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)
|
y = cy + (row * 1.5) * (card_height + padding)
|
||||||
|
|
||||||
self._draw_card(drawn, x, y, card_width, card_height, 0)
|
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)
|
min_y = min(min_y, y - half_h)
|
||||||
max_y = max(max_y, y + half_h)
|
max_y = max(max_y, y + half_h)
|
||||||
|
|
||||||
if min_x == float('inf'): # No cards
|
if min_x == float("inf"): # No cards
|
||||||
min_x, min_y, max_x, max_y = cx, cy, cx, cy
|
min_x, min_y, max_x, max_y = cx, cy, cx, cy
|
||||||
|
|
||||||
scroll_padding = 50 * self.zoom_level
|
scroll_padding = 50 * self.zoom_level
|
||||||
self.canvas.configure(scrollregion=(
|
self.canvas.configure(
|
||||||
min_x - scroll_padding,
|
scrollregion=(
|
||||||
min_y - scroll_padding,
|
min_x - scroll_padding,
|
||||||
max_x + scroll_padding,
|
min_y - scroll_padding,
|
||||||
max_y + scroll_padding
|
max_x + scroll_padding,
|
||||||
))
|
max_y + scroll_padding,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def _zoom(self, factor):
|
def _zoom(self, factor):
|
||||||
self.zoom_level *= factor
|
self.zoom_level *= factor
|
||||||
@@ -962,7 +1002,10 @@ class SpreadDisplay:
|
|||||||
if pil_image.mode in ("RGBA", "PA"):
|
if pil_image.mode in ("RGBA", "PA"):
|
||||||
# Create white background for transparency
|
# Create white background for transparency
|
||||||
background = Image.new("RGB", pil_image.size, "white")
|
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
|
pil_image = background
|
||||||
elif pil_image.mode != "RGB":
|
elif pil_image.mode != "RGB":
|
||||||
pil_image = pil_image.convert("RGB")
|
pil_image = pil_image.convert("RGB")
|
||||||
@@ -994,7 +1037,9 @@ class SpreadDisplay:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" Debug: Paste failed for {drawn.card.name} at ({x}, {y}): {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" 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:
|
if self.show_text:
|
||||||
text_font = font
|
text_font = font
|
||||||
@@ -1008,7 +1053,9 @@ class SpreadDisplay:
|
|||||||
by2 = int(y + card_height / 2)
|
by2 = int(y + card_height / 2)
|
||||||
draw.rectangle([bx1, by1, bx2, by2], fill=(0, 0, 0, 180))
|
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), 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")
|
return img.convert("RGB")
|
||||||
|
|
||||||
|
|||||||
@@ -28,26 +28,26 @@ Access Patterns:
|
|||||||
print(clock) # Shows planetary positions
|
print(clock) # Shows planetary positions
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .temporal import Year, Month, Day, Hour, Week
|
from .astrology import PlanetPosition, ThalemaClock, Zodiac
|
||||||
from .calendar import Calendar
|
from .calendar import Calendar
|
||||||
|
from .coordinates import Season, SolarEvent, TemporalCoordinates
|
||||||
|
from .temporal import Day, Hour, Month, Week, Year
|
||||||
from .time import TimeUtil
|
from .time import TimeUtil
|
||||||
from .coordinates import TemporalCoordinates, Season, SolarEvent
|
|
||||||
from .astrology import ThalemaClock, Zodiac, PlanetPosition
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# Temporal classes
|
# Temporal classes
|
||||||
'Year',
|
"Year",
|
||||||
'Month',
|
"Month",
|
||||||
'Day',
|
"Day",
|
||||||
'Hour',
|
"Hour",
|
||||||
'Week',
|
"Week",
|
||||||
'Calendar',
|
"Calendar",
|
||||||
'TimeUtil',
|
"TimeUtil",
|
||||||
'TemporalCoordinates',
|
"TemporalCoordinates",
|
||||||
'Season',
|
"Season",
|
||||||
'SolarEvent',
|
"SolarEvent",
|
||||||
# Astrological classes
|
# Astrological classes
|
||||||
'ThalemaClock',
|
"ThalemaClock",
|
||||||
'Zodiac',
|
"Zodiac",
|
||||||
'PlanetPosition',
|
"PlanetPosition",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -20,12 +20,13 @@ Usage:
|
|||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, Optional, List
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
|
|
||||||
class Zodiac(Enum):
|
class Zodiac(Enum):
|
||||||
"""Zodiac signs with degree ranges (0-360°)."""
|
"""Zodiac signs with degree ranges (0-360°)."""
|
||||||
|
|
||||||
ARIES = ("♈", 0, 30)
|
ARIES = ("♈", 0, 30)
|
||||||
TAURUS = ("♉", 30, 60)
|
TAURUS = ("♉", 30, 60)
|
||||||
GEMINI = ("♊", 60, 90)
|
GEMINI = ("♊", 60, 90)
|
||||||
@@ -63,6 +64,7 @@ class Zodiac(Enum):
|
|||||||
@dataclass
|
@dataclass
|
||||||
class PlanetPosition:
|
class PlanetPosition:
|
||||||
"""Represents a planet's position with degree and zodiac."""
|
"""Represents a planet's position with degree and zodiac."""
|
||||||
|
|
||||||
planet_name: str
|
planet_name: str
|
||||||
planet_symbol: str
|
planet_symbol: str
|
||||||
zodiac: Zodiac
|
zodiac: Zodiac
|
||||||
@@ -219,7 +221,10 @@ class ThalemaClock:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def display_compact(self) -> str:
|
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()
|
return self.display_format()
|
||||||
|
|
||||||
def display_verbose(self) -> str:
|
def display_verbose(self) -> str:
|
||||||
@@ -228,7 +233,9 @@ class ThalemaClock:
|
|||||||
for planet_name in ["Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn"]:
|
for planet_name in ["Sun", "Moon", "Mercury", "Venus", "Mars", "Jupiter", "Saturn"]:
|
||||||
if planet_name in self.positions:
|
if planet_name in self.positions:
|
||||||
pos = self.positions[planet_name]
|
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)
|
return "\n".join(lines)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ including Zodiac, Time cycles, and Astrological influences.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import List, Optional
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Month:
|
class Month:
|
||||||
"""Represents a calendar month."""
|
"""Represents a calendar month."""
|
||||||
|
|
||||||
number: int
|
number: int
|
||||||
name: str
|
name: str
|
||||||
zodiac_start: str
|
zodiac_start: str
|
||||||
@@ -21,6 +22,7 @@ class Month:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Weekday:
|
class Weekday:
|
||||||
"""Represents weekday/weekend archetypes with planetary ties."""
|
"""Represents weekday/weekend archetypes with planetary ties."""
|
||||||
|
|
||||||
number: int
|
number: int
|
||||||
name: str
|
name: str
|
||||||
planetary_correspondence: str
|
planetary_correspondence: str
|
||||||
@@ -35,6 +37,7 @@ class Weekday:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Hour:
|
class Hour:
|
||||||
"""Represents an hour with planetary correspondence."""
|
"""Represents an hour with planetary correspondence."""
|
||||||
|
|
||||||
number: int
|
number: int
|
||||||
name: str
|
name: str
|
||||||
planetary_hours: List[str] = field(default_factory=list)
|
planetary_hours: List[str] = field(default_factory=list)
|
||||||
@@ -43,6 +46,7 @@ class Hour:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class ClockHour:
|
class ClockHour:
|
||||||
"""Represents a clock hour with both 24-hour and 12-hour phases."""
|
"""Represents a clock hour with both 24-hour and 12-hour phases."""
|
||||||
|
|
||||||
hour_24: int
|
hour_24: int
|
||||||
hour_12: int
|
hour_12: int
|
||||||
period: str # AM or PM
|
period: str # AM or PM
|
||||||
@@ -63,6 +67,7 @@ class ClockHour:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Zodiac:
|
class Zodiac:
|
||||||
"""Represents a zodiac sign."""
|
"""Represents a zodiac sign."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
symbol: str
|
symbol: str
|
||||||
element: str
|
element: str
|
||||||
@@ -73,6 +78,7 @@ class Zodiac:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Degree:
|
class Degree:
|
||||||
"""Represents an astrological degree."""
|
"""Represents an astrological degree."""
|
||||||
|
|
||||||
number: int
|
number: int
|
||||||
constellation: str
|
constellation: str
|
||||||
ruling_planet: str
|
ruling_planet: str
|
||||||
@@ -82,6 +88,7 @@ class Degree:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class AstrologicalInfluence:
|
class AstrologicalInfluence:
|
||||||
"""Represents astrological influences."""
|
"""Represents astrological influences."""
|
||||||
|
|
||||||
planet: str
|
planet: str
|
||||||
sign: str
|
sign: str
|
||||||
house: str
|
house: str
|
||||||
|
|||||||
@@ -3,9 +3,10 @@
|
|||||||
This module provides calendar-related operations and utilities.
|
This module provides calendar-related operations and utilities.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import datetime, date
|
from datetime import date, datetime
|
||||||
from typing import Optional, Dict, Any
|
from typing import Any, Dict
|
||||||
from .temporal import Year, Month, Day, Week, Hour
|
|
||||||
|
from .temporal import Day, Hour, Month, Week, Year
|
||||||
|
|
||||||
|
|
||||||
class Calendar:
|
class Calendar:
|
||||||
@@ -20,12 +21,12 @@ class Calendar:
|
|||||||
"""
|
"""
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
return {
|
return {
|
||||||
'year': Year(now.year),
|
"year": Year(now.year),
|
||||||
'month': Month(now.month),
|
"month": Month(now.month),
|
||||||
'day': Day(now.day),
|
"day": Day(now.day),
|
||||||
'hour': Hour(now.hour, now.minute, now.second),
|
"hour": Hour(now.hour, now.minute, now.second),
|
||||||
'week': Calendar.get_week(now.year, now.month, now.day),
|
"week": Calendar.get_week(now.year, now.month, now.day),
|
||||||
'datetime': now,
|
"datetime": now,
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -6,15 +6,16 @@ and other astronomical/calendrical coordinates.
|
|||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
class Season(Enum):
|
class Season(Enum):
|
||||||
"""The four seasons of the year."""
|
"""The four seasons of the year."""
|
||||||
SPRING = "Spring" # Vernal Equinox (Mar 20/21)
|
|
||||||
SUMMER = "Summer" # Summer Solstice (Jun 20/21)
|
SPRING = "Spring" # Vernal Equinox (Mar 20/21)
|
||||||
AUTUMN = "Autumn" # Autumnal Equinox (Sep 22/23)
|
SUMMER = "Summer" # Summer Solstice (Jun 20/21)
|
||||||
WINTER = "Winter" # Winter Solstice (Dec 21/22)
|
AUTUMN = "Autumn" # Autumnal Equinox (Sep 22/23)
|
||||||
|
WINTER = "Winter" # Winter Solstice (Dec 21/22)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -26,8 +27,9 @@ class SolarEvent:
|
|||||||
date: The approximate date of the event
|
date: The approximate date of the event
|
||||||
season: The associated season
|
season: The associated season
|
||||||
"""
|
"""
|
||||||
|
|
||||||
event_type: str # "solstice" or "equinox"
|
event_type: str # "solstice" or "equinox"
|
||||||
date: tuple # (month, day)
|
date: tuple # (month, day)
|
||||||
season: Season
|
season: Season
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
@@ -118,4 +120,4 @@ class TemporalCoordinates:
|
|||||||
Day of year
|
Day of year
|
||||||
"""
|
"""
|
||||||
days_in_months = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
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 dataclasses import dataclass
|
||||||
from typing import Optional, List
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Year:
|
class Year:
|
||||||
"""Represents a year in the Gregorian calendar."""
|
"""Represents a year in the Gregorian calendar."""
|
||||||
|
|
||||||
value: int
|
value: int
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
@@ -22,6 +22,7 @@ class Year:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Month:
|
class Month:
|
||||||
"""Represents a month (1-12)."""
|
"""Represents a month (1-12)."""
|
||||||
|
|
||||||
value: int
|
value: int
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
@@ -32,8 +33,18 @@ class Month:
|
|||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
"""Get the month name."""
|
"""Get the month name."""
|
||||||
names = [
|
names = [
|
||||||
"January", "February", "March", "April", "May", "June",
|
"January",
|
||||||
"July", "August", "September", "October", "November", "December"
|
"February",
|
||||||
|
"March",
|
||||||
|
"April",
|
||||||
|
"May",
|
||||||
|
"June",
|
||||||
|
"July",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"October",
|
||||||
|
"November",
|
||||||
|
"December",
|
||||||
]
|
]
|
||||||
return names[self.value - 1]
|
return names[self.value - 1]
|
||||||
|
|
||||||
@@ -47,6 +58,7 @@ class Month:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Week:
|
class Week:
|
||||||
"""Represents a week in the calendar."""
|
"""Represents a week in the calendar."""
|
||||||
|
|
||||||
number: int # 1-53
|
number: int # 1-53
|
||||||
year: int
|
year: int
|
||||||
|
|
||||||
@@ -64,6 +76,7 @@ class Week:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Day:
|
class Day:
|
||||||
"""Represents a day of the month (1-31)."""
|
"""Represents a day of the month (1-31)."""
|
||||||
|
|
||||||
value: int
|
value: int
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
@@ -85,6 +98,7 @@ class Day:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Hour:
|
class Hour:
|
||||||
"""Represents an hour in 24-hour format (0-23)."""
|
"""Represents an hour in 24-hour format (0-23)."""
|
||||||
|
|
||||||
value: int
|
value: int
|
||||||
minute: int = 0
|
minute: int = 0
|
||||||
second: int = 0
|
second: int = 0
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ This module handles time-related operations and conversions.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import datetime, time
|
from datetime import datetime, time
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict
|
||||||
|
|
||||||
|
|
||||||
class TimeUtil:
|
class TimeUtil:
|
||||||
@@ -35,9 +35,9 @@ class TimeUtil:
|
|||||||
secs = remaining % 60
|
secs = remaining % 60
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'hours': hours,
|
"hours": hours,
|
||||||
'minutes': minutes,
|
"minutes": minutes,
|
||||||
'seconds': secs,
|
"seconds": secs,
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
"""Utility modules for the Tarot project."""
|
"""Utility modules for the Tarot project."""
|
||||||
|
|
||||||
|
from .attributes import (
|
||||||
|
Cipher,
|
||||||
|
CipherResult,
|
||||||
|
Color,
|
||||||
|
Colorscale,
|
||||||
|
Element,
|
||||||
|
ElementType,
|
||||||
|
God,
|
||||||
|
Note,
|
||||||
|
Number,
|
||||||
|
Perfume,
|
||||||
|
Planet,
|
||||||
|
)
|
||||||
from .filter import (
|
from .filter import (
|
||||||
universal_filter,
|
describe_filter_fields,
|
||||||
get_filterable_fields,
|
|
||||||
filter_by,
|
filter_by,
|
||||||
format_results,
|
format_results,
|
||||||
get_filter_autocomplete,
|
get_filter_autocomplete,
|
||||||
describe_filter_fields,
|
get_filterable_fields,
|
||||||
)
|
universal_filter,
|
||||||
from .attributes import (
|
|
||||||
Note,
|
|
||||||
Element,
|
|
||||||
ElementType,
|
|
||||||
Number,
|
|
||||||
Color,
|
|
||||||
Colorscale,
|
|
||||||
Planet,
|
|
||||||
God,
|
|
||||||
Perfume,
|
|
||||||
Cipher,
|
|
||||||
CipherResult,
|
|
||||||
)
|
)
|
||||||
from .misc import (
|
from .misc import (
|
||||||
Personality,
|
|
||||||
MBTIType,
|
MBTIType,
|
||||||
|
Personality,
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|||||||
@@ -7,12 +7,13 @@ exclusively to any single namespace.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
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
|
@dataclass
|
||||||
class Meaning:
|
class Meaning:
|
||||||
"""Represents the meaning of a card."""
|
"""Represents the meaning of a card."""
|
||||||
|
|
||||||
upright: str
|
upright: str
|
||||||
reversed: str
|
reversed: str
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ class Meaning:
|
|||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Note:
|
class Note:
|
||||||
"""Represents a musical note with its properties."""
|
"""Represents a musical note with its properties."""
|
||||||
|
|
||||||
name: str # e.g., "C", "D", "E", "F#", "G", "A", "B"
|
name: str # e.g., "C", "D", "E", "F#", "G", "A", "B"
|
||||||
frequency: float # Frequency in Hz (A4 = 440 Hz)
|
frequency: float # Frequency in Hz (A4 = 440 Hz)
|
||||||
semitone: int # Position in chromatic scale (0-11)
|
semitone: int # Position in chromatic scale (0-11)
|
||||||
@@ -40,6 +42,7 @@ class Note:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Element:
|
class Element:
|
||||||
"""Represents one of the four elements."""
|
"""Represents one of the four elements."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
symbol: str
|
symbol: str
|
||||||
color: str
|
color: str
|
||||||
@@ -50,6 +53,7 @@ class Element:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class ElementType:
|
class ElementType:
|
||||||
"""Represents an elemental force (Fire, Water, Air, Earth, Spirit)."""
|
"""Represents an elemental force (Fire, Water, Air, Earth, Spirit)."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
symbol: str
|
symbol: str
|
||||||
direction: str
|
direction: str
|
||||||
@@ -68,11 +72,12 @@ class ElementType:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Number:
|
class Number:
|
||||||
"""Represents a number (1-9) with Kabbalistic attributes."""
|
"""Represents a number (1-9) with Kabbalistic attributes."""
|
||||||
|
|
||||||
value: int
|
value: int
|
||||||
sephera: str
|
sephera: str
|
||||||
element: str
|
element: str
|
||||||
compliment: int
|
compliment: int
|
||||||
color: Optional['Color'] = None
|
color: Optional["Color"] = None
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
if not (1 <= self.value <= 9):
|
if not (1 <= self.value <= 9):
|
||||||
@@ -94,6 +99,7 @@ class Colorscale:
|
|||||||
- Emperor Scale (Vau): Son/Form, active expression, concrete manifestation
|
- Emperor Scale (Vau): Son/Form, active expression, concrete manifestation
|
||||||
- Empress Scale (He final): Daughter, physical manifestation, receptivity in Assiah
|
- Empress Scale (He final): Daughter, physical manifestation, receptivity in Assiah
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name: str # Sephira/Path name (e.g., "Kether", "Path of Aleph")
|
name: str # Sephira/Path name (e.g., "Kether", "Path of Aleph")
|
||||||
number: int # 1-10 for Sephiroth, 11-32 for Paths
|
number: int # 1-10 for Sephiroth, 11-32 for Paths
|
||||||
king_scale: str # Yod - Father principle
|
king_scale: str # Yod - Father principle
|
||||||
@@ -109,6 +115,7 @@ class Colorscale:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Color:
|
class Color:
|
||||||
"""Represents a color with Kabbalistic correspondences."""
|
"""Represents a color with Kabbalistic correspondences."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
hex_value: str
|
hex_value: str
|
||||||
rgb: Tuple[int, int, int]
|
rgb: Tuple[int, int, int]
|
||||||
@@ -133,6 +140,7 @@ class Color:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Planet:
|
class Planet:
|
||||||
"""Represents a planetary correspondence entry."""
|
"""Represents a planetary correspondence entry."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
symbol: str
|
symbol: str
|
||||||
element: str
|
element: str
|
||||||
@@ -170,6 +178,7 @@ class Planet:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class God:
|
class God:
|
||||||
"""Unified deity representation that synchronizes multiple pantheons."""
|
"""Unified deity representation that synchronizes multiple pantheons."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
culture: str
|
culture: str
|
||||||
pantheon: str
|
pantheon: str
|
||||||
@@ -231,7 +240,11 @@ class God:
|
|||||||
lines.append(f" associated_planet: {self.associated_planet.name}")
|
lines.append(f" associated_planet: {self.associated_planet.name}")
|
||||||
|
|
||||||
if self.associated_element:
|
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}")
|
lines.append(f" associated_element: {elem_name}")
|
||||||
|
|
||||||
if self.tarot_trumps:
|
if self.tarot_trumps:
|
||||||
@@ -249,6 +262,7 @@ class God:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Perfume:
|
class Perfume:
|
||||||
"""Represents a perfume/incense correspondence in Kabbalah."""
|
"""Represents a perfume/incense correspondence in Kabbalah."""
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
alternative_names: List[str] = field(default_factory=list)
|
alternative_names: List[str] = field(default_factory=list)
|
||||||
scent_profile: str = "" # e.g., "Resinous", "Floral", "Spicy", "Earthy"
|
scent_profile: str = "" # e.g., "Resinous", "Floral", "Spicy", "Earthy"
|
||||||
@@ -362,9 +376,7 @@ class Cipher:
|
|||||||
expanded.append(self.pattern[idx % len(self.pattern)])
|
expanded.append(self.pattern[idx % len(self.pattern)])
|
||||||
idx += 1
|
idx += 1
|
||||||
return expanded
|
return expanded
|
||||||
raise ValueError(
|
raise ValueError("Cipher pattern length does not match alphabet and cycling is disabled")
|
||||||
"Cipher pattern length does not match alphabet and cycling is disabled"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
|
|||||||
@@ -21,11 +21,17 @@ Usage:
|
|||||||
fields = get_filterable_fields(TarotLetter)
|
fields = get_filterable_fields(TarotLetter)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import List, Any, TypeVar, Union, Dict
|
from dataclasses import fields, is_dataclass
|
||||||
from dataclasses import is_dataclass, fields
|
from typing import Any, Dict, List, TypeVar
|
||||||
from utils.object_formatting import get_item_label, is_nested_object, get_object_attributes, format_value
|
|
||||||
|
|
||||||
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]:
|
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:
|
for check_value in values_to_check:
|
||||||
# Handle list attributes (like keywords, colors, etc.)
|
# Handle list attributes (like keywords, colors, etc.)
|
||||||
if isinstance(attr_value, list):
|
if isinstance(attr_value, list):
|
||||||
if any(
|
if any(str(check_value).lower() == str(item).lower() for item in attr_value):
|
||||||
str(check_value).lower() == str(item).lower()
|
|
||||||
for item in attr_value
|
|
||||||
):
|
|
||||||
return True
|
return True
|
||||||
continue
|
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,
|
# 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"))
|
# try matching the value against that (e.g., suit="Cups" vs Suit(name="Cups"))
|
||||||
if hasattr(attr_value, 'name'):
|
if hasattr(attr_value, "name"):
|
||||||
nested_name = getattr(attr_value, 'name', None)
|
nested_name = getattr(attr_value, "name", None)
|
||||||
if nested_name is not None:
|
if nested_name is not None:
|
||||||
if str(nested_name).lower() == str(check_value).lower():
|
if str(nested_name).lower() == str(check_value).lower():
|
||||||
return True
|
return True
|
||||||
@@ -184,10 +187,7 @@ def universal_filter(items: List[T], **kwargs) -> List[T]:
|
|||||||
# Apply field alias if it exists
|
# Apply field alias if it exists
|
||||||
actual_key = field_aliases.get(key, key)
|
actual_key = field_aliases.get(key, key)
|
||||||
|
|
||||||
results = [
|
results = [obj for obj in results if _matches_filter(obj, actual_key, value)]
|
||||||
obj for obj in results
|
|
||||||
if _matches_filter(obj, actual_key, value)
|
|
||||||
]
|
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ This module contains specialized utilities that don't fit into other categories.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional, TYPE_CHECKING
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from tarot.deck.deck import CourtCard
|
from tarot.deck.deck import CourtCard
|
||||||
@@ -14,6 +14,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
class MBTIType(Enum):
|
class MBTIType(Enum):
|
||||||
"""16 MBTI personality types."""
|
"""16 MBTI personality types."""
|
||||||
|
|
||||||
ISTJ = "ISTJ"
|
ISTJ = "ISTJ"
|
||||||
ISFJ = "ISFJ"
|
ISFJ = "ISFJ"
|
||||||
INFJ = "INFJ"
|
INFJ = "INFJ"
|
||||||
@@ -62,39 +63,36 @@ class Personality:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
mbti_type: MBTIType
|
mbti_type: MBTIType
|
||||||
court_card: Optional['CourtCard'] = None
|
court_card: Optional["CourtCard"] = None
|
||||||
description: str = ""
|
description: str = ""
|
||||||
|
|
||||||
# Direct MBTI-to-CourtCard mapping (1-to-1 relationship)
|
# Direct MBTI-to-CourtCard mapping (1-to-1 relationship)
|
||||||
# Format: MBTI_TYPE -> (Rank, Suit)
|
# Format: MBTI_TYPE -> (Rank, Suit)
|
||||||
_MBTI_TO_CARD_MAPPING = {
|
_MBTI_TO_CARD_MAPPING = {
|
||||||
# KINGS (E + J) - Extraverted Judgers
|
# KINGS (E + J) - Extraverted Judgers
|
||||||
"ENTJ": ("Knight", "Wands"), # Fiery, forceful leadership
|
"ENTJ": ("Knight", "Wands"), # Fiery, forceful leadership
|
||||||
"ENFJ": ("Knight", "Cups"), # Sensitive, mission-driven
|
"ENFJ": ("Knight", "Cups"), # Sensitive, mission-driven
|
||||||
"ESTJ": ("Knight", "Swords"), # Practical, pragmatic
|
"ESTJ": ("Knight", "Swords"), # Practical, pragmatic
|
||||||
"ESFJ": ("Knight", "Pentacles"), # Sociable, consensus-seeking
|
"ESFJ": ("Knight", "Pentacles"), # Sociable, consensus-seeking
|
||||||
|
|
||||||
# QUEENS (I + J) - Introverted Judgers
|
# QUEENS (I + J) - Introverted Judgers
|
||||||
"INTJ": ("Queen", "Wands"), # Analytical, self-motivated
|
"INTJ": ("Queen", "Wands"), # Analytical, self-motivated
|
||||||
"INFJ": ("Queen", "Cups"), # Sensitive, interconnected
|
"INFJ": ("Queen", "Cups"), # Sensitive, interconnected
|
||||||
"ISTJ": ("Queen", "Swords"), # Pragmatic, duty-fulfiller
|
"ISTJ": ("Queen", "Swords"), # Pragmatic, duty-fulfiller
|
||||||
"ISFJ": ("Queen", "Pentacles"), # Caring, earth-mother type
|
"ISFJ": ("Queen", "Pentacles"), # Caring, earth-mother type
|
||||||
|
|
||||||
# PRINCES (E + P) - Extraverted Perceivers
|
# PRINCES (E + P) - Extraverted Perceivers
|
||||||
"ENTP": ("Prince", "Wands"), # Visionary, quick-study
|
"ENTP": ("Prince", "Wands"), # Visionary, quick-study
|
||||||
"ENFP": ("Prince", "Cups"), # Inspiring, intuitive
|
"ENFP": ("Prince", "Cups"), # Inspiring, intuitive
|
||||||
"ESTP": ("Prince", "Swords"), # Action-oriented, risk-taker
|
"ESTP": ("Prince", "Swords"), # Action-oriented, risk-taker
|
||||||
"ESFP": ("Prince", "Pentacles"), # Aesthete, sensualist
|
"ESFP": ("Prince", "Pentacles"), # Aesthete, sensualist
|
||||||
|
|
||||||
# PRINCESSES (I + P) - Introverted Perceivers
|
# PRINCESSES (I + P) - Introverted Perceivers
|
||||||
"INTP": ("Princess", "Wands"), # Thinker par excellence
|
"INTP": ("Princess", "Wands"), # Thinker par excellence
|
||||||
"INFP": ("Princess", "Cups"), # Idealistic, devoted
|
"INFP": ("Princess", "Cups"), # Idealistic, devoted
|
||||||
"ISTP": ("Princess", "Swords"), # Observer, mechanic
|
"ISTP": ("Princess", "Swords"), # Observer, mechanic
|
||||||
"ISFP": ("Princess", "Pentacles"), # Aesthete, free spirit
|
"ISFP": ("Princess", "Pentacles"), # Aesthete, free spirit
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@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.
|
Create a Personality from an MBTI type string.
|
||||||
|
|
||||||
@@ -140,7 +138,7 @@ class Personality:
|
|||||||
from tarot import Tarot
|
from tarot import Tarot
|
||||||
|
|
||||||
# Use provided deck or default to 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)
|
cards = d.card.filter(type="Court", court_rank=rank, suit=suit)
|
||||||
if cards:
|
if cards:
|
||||||
@@ -152,17 +150,14 @@ class Personality:
|
|||||||
"ENFJ": "The Protagonist - Inspiring, empathetic, leader of Cups",
|
"ENFJ": "The Protagonist - Inspiring, empathetic, leader of Cups",
|
||||||
"ESTJ": "The Supervisor - Practical, decisive, leader of Swords",
|
"ESTJ": "The Supervisor - Practical, decisive, leader of Swords",
|
||||||
"ESFJ": "The Consul - Sociable, cooperative, leader of Pentacles",
|
"ESFJ": "The Consul - Sociable, cooperative, leader of Pentacles",
|
||||||
|
|
||||||
"INTJ": "The Architect - Strategic, logical, sage of Wands",
|
"INTJ": "The Architect - Strategic, logical, sage of Wands",
|
||||||
"INFJ": "The Advocate - Insightful, idealistic, sage of Cups",
|
"INFJ": "The Advocate - Insightful, idealistic, sage of Cups",
|
||||||
"ISTJ": "The Logistician - Practical, reliable, sage of Swords",
|
"ISTJ": "The Logistician - Practical, reliable, sage of Swords",
|
||||||
"ISFJ": "The Defender - Caring, conscientious, sage of Pentacles",
|
"ISFJ": "The Defender - Caring, conscientious, sage of Pentacles",
|
||||||
|
|
||||||
"ENTP": "The Debater - Innovative, quick-witted, explorer of Wands",
|
"ENTP": "The Debater - Innovative, quick-witted, explorer of Wands",
|
||||||
"ENFP": "The Campaigner - Enthusiastic, social, explorer of Cups",
|
"ENFP": "The Campaigner - Enthusiastic, social, explorer of Cups",
|
||||||
"ESTP": "The Entrepreneur - Energetic, bold, explorer of Swords",
|
"ESTP": "The Entrepreneur - Energetic, bold, explorer of Swords",
|
||||||
"ESFP": "The Entertainer - Spontaneous, outgoing, explorer of Pentacles",
|
"ESFP": "The Entertainer - Spontaneous, outgoing, explorer of Pentacles",
|
||||||
|
|
||||||
"INTP": "The Logician - Analytical, curious, seeker of Wands",
|
"INTP": "The Logician - Analytical, curious, seeker of Wands",
|
||||||
"INFP": "The Mediator - Idealistic, authentic, seeker of Cups",
|
"INFP": "The Mediator - Idealistic, authentic, seeker of Cups",
|
||||||
"ISTP": "The Virtuoso - Practical, observant, seeker of Swords",
|
"ISTP": "The Virtuoso - Practical, observant, seeker of Swords",
|
||||||
@@ -170,9 +165,7 @@ class Personality:
|
|||||||
}
|
}
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
mbti_type=mbti_enum,
|
mbti_type=mbti_enum, court_card=court_card, description=descriptions.get(mbti_type, "")
|
||||||
court_card=court_card,
|
|
||||||
description=descriptions.get(mbti_type, "")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
|||||||
@@ -16,14 +16,13 @@ Usage:
|
|||||||
|
|
||||||
from typing import Any, List, Tuple
|
from typing import Any, List, Tuple
|
||||||
|
|
||||||
|
|
||||||
# Type checking predicates
|
# Type checking predicates
|
||||||
SCALAR_TYPES = (str, int, float, bool, list, dict, type(None))
|
SCALAR_TYPES = (str, int, float, bool, list, dict, type(None))
|
||||||
|
|
||||||
|
|
||||||
def is_dataclass(obj: Any) -> bool:
|
def is_dataclass(obj: Any) -> bool:
|
||||||
"""Check if object is a dataclass."""
|
"""Check if object is a dataclass."""
|
||||||
return hasattr(obj, '__dataclass_fields__')
|
return hasattr(obj, "__dataclass_fields__")
|
||||||
|
|
||||||
|
|
||||||
def is_nested_object(obj: Any) -> bool:
|
def is_nested_object(obj: Any) -> bool:
|
||||||
@@ -36,7 +35,7 @@ def is_nested_object(obj: Any) -> bool:
|
|||||||
return True
|
return True
|
||||||
if is_dataclass(obj):
|
if is_dataclass(obj):
|
||||||
return True
|
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:
|
def is_scalar(obj: Any) -> bool:
|
||||||
@@ -60,16 +59,16 @@ def get_item_label(item: Any, fallback: str = "item") -> str:
|
|||||||
Returns:
|
Returns:
|
||||||
A string suitable for display as an item label
|
A string suitable for display as an item label
|
||||||
"""
|
"""
|
||||||
if hasattr(item, 'name'):
|
if hasattr(item, "name"):
|
||||||
return str(getattr(item, 'name', fallback))
|
return str(getattr(item, "name", fallback))
|
||||||
elif hasattr(item, 'transliteration'):
|
elif hasattr(item, "transliteration"):
|
||||||
return str(getattr(item, 'transliteration', fallback))
|
return str(getattr(item, "transliteration", fallback))
|
||||||
return str(item) if item is not None else fallback
|
return str(item) if item is not None else fallback
|
||||||
|
|
||||||
|
|
||||||
def get_dataclass_fields(obj: Any) -> List[str]:
|
def get_dataclass_fields(obj: Any) -> List[str]:
|
||||||
"""Get list of field names from a dataclass."""
|
"""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 list(obj.__dataclass_fields__.keys())
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -90,9 +89,9 @@ def get_object_attributes(obj: Any) -> List[Tuple[str, Any]]:
|
|||||||
for field_name in obj.__dataclass_fields__:
|
for field_name in obj.__dataclass_fields__:
|
||||||
value = getattr(obj, field_name, None)
|
value = getattr(obj, field_name, None)
|
||||||
attributes.append((field_name, value))
|
attributes.append((field_name, value))
|
||||||
elif hasattr(obj, '__dict__'):
|
elif hasattr(obj, "__dict__"):
|
||||||
for field_name, value in obj.__dict__.items():
|
for field_name, value in obj.__dict__.items():
|
||||||
if not field_name.startswith('_'):
|
if not field_name.startswith("_"):
|
||||||
attributes.append((field_name, value))
|
attributes.append((field_name, value))
|
||||||
|
|
||||||
return attributes
|
return attributes
|
||||||
@@ -121,16 +120,21 @@ def format_value(value: Any, indent: int = 2) -> str:
|
|||||||
if is_nested_object(value):
|
if is_nested_object(value):
|
||||||
# Classes that have custom __str__ implementations should use them
|
# Classes that have custom __str__ implementations should use them
|
||||||
obj_class = type(value).__name__
|
obj_class = type(value).__name__
|
||||||
has_custom_str = (
|
has_custom_str = hasattr(value, "__str__") and type(value).__str__ is not object.__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
|
# Use the custom __str__ method and indent each line
|
||||||
custom_output = str(value)
|
custom_output = str(value)
|
||||||
lines = []
|
lines = []
|
||||||
for line in custom_output.split('\n'):
|
for line in custom_output.split("\n"):
|
||||||
if line.strip(): # Skip empty lines
|
if line.strip(): # Skip empty lines
|
||||||
lines.append(f"{indent_str}{line}")
|
lines.append(f"{indent_str}{line}")
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ Usage:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Callable, Dict, Generic, List, Optional, TypeVar, Union
|
from typing import Any, Callable, Dict, Generic, List, Optional, TypeVar, Union
|
||||||
|
|
||||||
from utils.object_formatting import format_value, get_object_attributes, is_nested_object
|
from utils.object_formatting import format_value, get_object_attributes, is_nested_object
|
||||||
|
|
||||||
T = TypeVar('T')
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
class QueryResult:
|
class QueryResult:
|
||||||
@@ -32,18 +33,18 @@ class QueryResult:
|
|||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
if hasattr(self.data, '__repr__'):
|
if hasattr(self.data, "__repr__"):
|
||||||
return repr(self.data)
|
return repr(self.data)
|
||||||
return f"{self.__class__.__name__}({self.data})"
|
return f"{self.__class__.__name__}({self.data})"
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
if hasattr(self.data, '__str__'):
|
if hasattr(self.data, "__str__"):
|
||||||
return str(self.data)
|
return str(self.data)
|
||||||
return repr(self)
|
return repr(self)
|
||||||
|
|
||||||
def __getattr__(self, name: str) -> Any:
|
def __getattr__(self, name: str) -> Any:
|
||||||
"""Pass through attribute access to the wrapped data."""
|
"""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}'")
|
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
||||||
return getattr(self.data, name)
|
return getattr(self.data, name)
|
||||||
|
|
||||||
@@ -61,7 +62,7 @@ class Query:
|
|||||||
self._data = data if isinstance(data, list) else list(data.values())
|
self._data = data if isinstance(data, list) else list(data.values())
|
||||||
self._filters: List[Callable[[T], bool]] = []
|
self._filters: List[Callable[[T], bool]] = []
|
||||||
|
|
||||||
def filter(self, expression: str) -> 'Query':
|
def filter(self, expression: str) -> "Query":
|
||||||
"""
|
"""
|
||||||
Filter by key:value expression.
|
Filter by key:value expression.
|
||||||
|
|
||||||
@@ -74,12 +75,12 @@ class Query:
|
|||||||
Supports multiple filters by chaining:
|
Supports multiple filters by chaining:
|
||||||
.filter('number:1').filter('name:creative')
|
.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:
|
def filter_func(item: T) -> bool:
|
||||||
# Special handling for 'name' key
|
# Special handling for 'name' key
|
||||||
if key == 'name':
|
if key == "name":
|
||||||
if hasattr(item, 'name'):
|
if hasattr(item, "name"):
|
||||||
value_lower = value.lower()
|
value_lower = value.lower()
|
||||||
item_name = str(item.name).lower()
|
item_name = str(item.name).lower()
|
||||||
return value_lower == item_name or value_lower in item_name
|
return value_lower == item_name or value_lower in item_name
|
||||||
@@ -97,16 +98,16 @@ class Query:
|
|||||||
self._filters.append(filter_func)
|
self._filters.append(filter_func)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def name(self, value: str) -> Optional['QueryResult']:
|
def name(self, value: str) -> Optional["QueryResult"]:
|
||||||
"""
|
"""
|
||||||
Deprecated: Use .filter('name:value') instead.
|
Deprecated: Use .filter('name:value') instead.
|
||||||
|
|
||||||
Find item by name (exact or partial match, case-insensitive).
|
Find item by name (exact or partial match, case-insensitive).
|
||||||
Returns QueryResult wrapping the found item, or None if not found.
|
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.
|
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)]
|
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."""
|
"""Alias for get() - returns first matching item."""
|
||||||
return self.get()
|
return self.get()
|
||||||
|
|
||||||
@@ -205,8 +206,6 @@ class CollectionAccessor(Generic[T]):
|
|||||||
Returns a formatted string with each item separated by blank lines.
|
Returns a formatted string with each item separated by blank lines.
|
||||||
Nested objects are indented and separated with their own sections.
|
Nested objects are indented and separated with their own sections.
|
||||||
"""
|
"""
|
||||||
from utils.object_formatting import is_nested_object, get_object_attributes
|
|
||||||
|
|
||||||
data = self.all()
|
data = self.all()
|
||||||
if not data:
|
if not data:
|
||||||
return "(empty collection)"
|
return "(empty collection)"
|
||||||
@@ -241,7 +240,7 @@ class CollectionAccessor(Generic[T]):
|
|||||||
class FilterableDict(dict):
|
class FilterableDict(dict):
|
||||||
"""Dict subclass that provides .filter() method for dynamic querying."""
|
"""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.
|
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.
|
Returns a formatted string with each item separated by blank lines.
|
||||||
Nested objects are indented and separated with their own sections.
|
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:
|
if not self:
|
||||||
return "(empty collection)"
|
return "(empty collection)"
|
||||||
|
|
||||||
@@ -283,7 +280,7 @@ class FilterableDict(dict):
|
|||||||
return "\n".join(lines)
|
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.
|
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
|
from datetime import datetime
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from src.tarot.attributes import (
|
from src.tarot.attributes import (
|
||||||
Month, Day, Weekday, Hour, ClockHour, Zodiac, Suit, Meaning, Letter, Sephera, Degree, Element,
|
AstrologicalInfluence,
|
||||||
AstrologicalInfluence, TreeOfLife, Correspondences, CardImage,
|
CardImage,
|
||||||
EnglishAlphabet, GreekAlphabet, HebrewAlphabet, Number, Color, Planet, God,
|
Cipher,
|
||||||
Cipher, CipherResult,
|
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
|
from src.tarot.card.data import CardDataLoader, calculate_digital_root
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Basic Attribute Tests
|
# Basic Attribute Tests
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
class TestMonth:
|
class TestMonth:
|
||||||
def test_month_creation(self):
|
def test_month_creation(self):
|
||||||
month = Month(1, "January", "Capricorn", "Aquarius")
|
month = Month(1, "January", "Capricorn", "Aquarius")
|
||||||
@@ -24,10 +46,7 @@ class TestMonth:
|
|||||||
assert month.zodiac_start == "Capricorn"
|
assert month.zodiac_start == "Capricorn"
|
||||||
|
|
||||||
def test_month_all_months(self):
|
def test_month_all_months(self):
|
||||||
months = [
|
months = [Month(i, f"Month_{i}", "Sign_1", "Sign_2") for i in range(1, 13)]
|
||||||
Month(i, f"Month_{i}", "Sign_1", "Sign_2")
|
|
||||||
for i in range(1, 13)
|
|
||||||
]
|
|
||||||
assert len(months) == 12
|
assert len(months) == 12
|
||||||
assert months[0].number == 1
|
assert months[0].number == 1
|
||||||
assert months[11].number == 12
|
assert months[11].number == 12
|
||||||
@@ -41,10 +60,7 @@ class TestDay:
|
|||||||
assert day.planetary_correspondence == "Sun"
|
assert day.planetary_correspondence == "Sun"
|
||||||
|
|
||||||
def test_all_weekdays(self):
|
def test_all_weekdays(self):
|
||||||
days = [
|
days = [Day(i, f"Day_{i}", f"Planet_{i}") for i in range(1, 8)]
|
||||||
Day(i, f"Day_{i}", f"Planet_{i}")
|
|
||||||
for i in range(1, 8)
|
|
||||||
]
|
|
||||||
assert len(days) == 7
|
assert len(days) == 7
|
||||||
|
|
||||||
|
|
||||||
@@ -99,6 +115,7 @@ class TestMeaning:
|
|||||||
# Sepheric Tests
|
# Sepheric Tests
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
class TestSephera:
|
class TestSephera:
|
||||||
def test_sephera_creation(self):
|
def test_sephera_creation(self):
|
||||||
sephera = Sephera(1, "Kether", "כתר", "Crown", "Metatron", "Chaioth", "Primum")
|
sephera = Sephera(1, "Kether", "כתר", "Crown", "Metatron", "Chaioth", "Primum")
|
||||||
@@ -118,6 +135,7 @@ class TestSephera:
|
|||||||
# Alphabet Tests
|
# Alphabet Tests
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
class TestEnglishAlphabet:
|
class TestEnglishAlphabet:
|
||||||
def test_english_letter_creation(self):
|
def test_english_letter_creation(self):
|
||||||
letter = EnglishAlphabet("A", 1, "ay")
|
letter = EnglishAlphabet("A", 1, "ay")
|
||||||
@@ -189,6 +207,7 @@ class TestHebrewAlphabet:
|
|||||||
# Number Tests
|
# Number Tests
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
class TestNumber:
|
class TestNumber:
|
||||||
def test_number_creation(self):
|
def test_number_creation(self):
|
||||||
num = Number(1, "Kether", "Spirit", 0) # compliment is auto-calculated
|
num = Number(1, "Kether", "Spirit", 0) # compliment is auto-calculated
|
||||||
@@ -220,6 +239,7 @@ class TestNumber:
|
|||||||
# Color Tests
|
# Color Tests
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
class TestColor:
|
class TestColor:
|
||||||
def test_color_creation(self):
|
def test_color_creation(self):
|
||||||
color = Color("Red", "#FF0000", (255, 0, 0), "Gevurah", 5, "Fire", "Briah", "Power")
|
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 r in [0, 128, 255]:
|
||||||
for g in [0, 128, 255]:
|
for g in [0, 128, 255]:
|
||||||
for b 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)
|
assert color.rgb == (r, g, b)
|
||||||
|
|
||||||
|
|
||||||
@@ -260,7 +282,9 @@ class TestColor:
|
|||||||
class TestPlanet:
|
class TestPlanet:
|
||||||
def test_planet_creation(self):
|
def test_planet_creation(self):
|
||||||
number = Number(6, "Tiphareth", "Fire", 0)
|
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(
|
planet = Planet(
|
||||||
name="Sun",
|
name="Sun",
|
||||||
symbol="☉",
|
symbol="☉",
|
||||||
@@ -342,6 +366,7 @@ class TestGod:
|
|||||||
# Cipher Tests
|
# Cipher Tests
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
class TestCipher:
|
class TestCipher:
|
||||||
def test_cipher_mapping_basic(self):
|
def test_cipher_mapping_basic(self):
|
||||||
cipher = Cipher("Test", "test", [1, 2, 3])
|
cipher = Cipher("Test", "test", [1, 2, 3])
|
||||||
@@ -374,6 +399,7 @@ class TestCipherResult:
|
|||||||
# Digital Root Tests
|
# Digital Root Tests
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
class TestDigitalRoot:
|
class TestDigitalRoot:
|
||||||
def test_digital_root_single_digit(self):
|
def test_digital_root_single_digit(self):
|
||||||
"""Single digits should return themselves."""
|
"""Single digits should return themselves."""
|
||||||
@@ -389,7 +415,7 @@ class TestDigitalRoot:
|
|||||||
|
|
||||||
def test_digital_root_large_numbers(self):
|
def test_digital_root_large_numbers(self):
|
||||||
"""Test large numbers."""
|
"""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(100) == 1 # 1+0+0 = 1
|
||||||
assert calculate_digital_root(123) == 6 # 1+2+3 = 6
|
assert calculate_digital_root(123) == 6 # 1+2+3 = 6
|
||||||
|
|
||||||
@@ -398,7 +424,7 @@ class TestDigitalRoot:
|
|||||||
# Major Arcana cards 0-21
|
# Major Arcana cards 0-21
|
||||||
assert calculate_digital_root(14) == 5 # Card 14 (Temperance) -> 5
|
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(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):
|
def test_digital_root_invalid_input(self):
|
||||||
"""Test that invalid inputs raise errors."""
|
"""Test that invalid inputs raise errors."""
|
||||||
@@ -413,6 +439,7 @@ class TestDigitalRoot:
|
|||||||
# CardDataLoader Tests
|
# CardDataLoader Tests
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
class TestCardDataLoader:
|
class TestCardDataLoader:
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def loader(self):
|
def loader(self):
|
||||||
@@ -463,13 +490,15 @@ class TestCardDataLoader:
|
|||||||
|
|
||||||
def test_trigram_line_diagram(self, loader):
|
def test_trigram_line_diagram(self, loader):
|
||||||
from letter import trigram
|
from letter import trigram
|
||||||
|
|
||||||
tri = trigram.trigram.name("Zhen") # Thunder
|
tri = trigram.trigram.name("Zhen") # Thunder
|
||||||
assert tri is not None
|
assert tri is not None
|
||||||
assert tri.data.line_diagram == "|::"
|
assert tri.data.line_diagram == "|::"
|
||||||
|
|
||||||
def test_hexagram_line_diagram(self, loader):
|
def test_hexagram_line_diagram(self, loader):
|
||||||
from letter import hexagram
|
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 is not None
|
||||||
assert hex_result.data.line_diagram == "||||||"
|
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
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from tarot.deck import Card
|
||||||
|
from tarot.ui import CardDisplay
|
||||||
|
|
||||||
|
|
||||||
def test_card_display_delegation():
|
def test_card_display_delegation():
|
||||||
"""Test that CardDisplay delegates to SpreadDisplay correctly."""
|
"""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
|
# 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()
|
display = CardDisplay()
|
||||||
|
|
||||||
# Create dummy card
|
# Create dummy card
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from tarot.ui import CubeDisplay
|
|
||||||
from tarot.tarot_api import Tarot
|
from tarot.tarot_api import Tarot
|
||||||
|
from tarot.ui import CubeDisplay
|
||||||
|
|
||||||
|
|
||||||
def test_cube_display_init():
|
def test_cube_display_init():
|
||||||
cube = Tarot.cube
|
cube = Tarot.cube
|
||||||
@@ -8,6 +10,7 @@ def test_cube_display_init():
|
|||||||
assert display.current_wall_name == "North"
|
assert display.current_wall_name == "North"
|
||||||
assert display.deck_name == "default"
|
assert display.deck_name == "default"
|
||||||
|
|
||||||
|
|
||||||
def test_cube_navigation():
|
def test_cube_navigation():
|
||||||
cube = Tarot.cube
|
cube = Tarot.cube
|
||||||
display = CubeDisplay(cube)
|
display = CubeDisplay(cube)
|
||||||
@@ -28,13 +31,14 @@ def test_cube_navigation():
|
|||||||
display._navigate("Left")
|
display._navigate("Left")
|
||||||
assert display.current_wall_name == "West"
|
assert display.current_wall_name == "West"
|
||||||
|
|
||||||
|
|
||||||
def test_find_card_for_direction():
|
def test_find_card_for_direction():
|
||||||
cube = Tarot.cube
|
cube = Tarot.cube
|
||||||
display = CubeDisplay(cube)
|
display = CubeDisplay(cube)
|
||||||
|
|
||||||
# North Wall, Center Direction -> Aleph -> The Fool
|
# North Wall, Center Direction -> Aleph -> The Fool
|
||||||
wall = cube.wall("North")
|
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.
|
# Wait, let's check what Center of North is.
|
||||||
# Actually, let's just mock a direction
|
# Actually, let's just mock a direction
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from tarot.ui import CubeDisplay
|
|
||||||
from tarot.tarot_api import Tarot
|
from tarot.tarot_api import Tarot
|
||||||
|
from tarot.ui import CubeDisplay
|
||||||
|
|
||||||
|
|
||||||
def test_cube_zoom():
|
def test_cube_zoom():
|
||||||
cube = Tarot.cube
|
cube = Tarot.cube
|
||||||
@@ -13,6 +15,7 @@ def test_cube_zoom():
|
|||||||
display._zoom(0.5)
|
display._zoom(0.5)
|
||||||
assert display.zoom_level < 1.0
|
assert display.zoom_level < 1.0
|
||||||
|
|
||||||
|
|
||||||
def test_cube_zoom_limits():
|
def test_cube_zoom_limits():
|
||||||
cube = Tarot.cube
|
cube = Tarot.cube
|
||||||
display = CubeDisplay(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
|
import tkinter as tk
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from tarot.tarot_api import Tarot
|
||||||
|
from tarot.ui import CubeDisplay
|
||||||
|
|
||||||
|
|
||||||
def test_zoom_limits():
|
def test_zoom_limits():
|
||||||
# Mock Tk root
|
# Mock Tk root
|
||||||
class MockRoot:
|
class MockRoot:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.bindings = {}
|
self.bindings = {}
|
||||||
self.images = []
|
self.images = []
|
||||||
def bind(self, key, callback): pass
|
|
||||||
def title(self, _): pass
|
def bind(self, key, callback):
|
||||||
def update_idletasks(self): pass
|
pass
|
||||||
def winfo_reqwidth(self): return 800
|
|
||||||
def winfo_reqheight(self): return 600
|
def title(self, _):
|
||||||
def winfo_screenwidth(self): return 1920
|
pass
|
||||||
def winfo_screenheight(self): return 1080
|
|
||||||
def geometry(self, _): pass
|
def update_idletasks(self):
|
||||||
def mainloop(self): pass
|
pass
|
||||||
def focus_force(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
|
# Mock Frame
|
||||||
class MockFrame:
|
class MockFrame:
|
||||||
def __init__(self, master=None, **kwargs):
|
def __init__(self, master=None, **kwargs):
|
||||||
self.children = []
|
self.children = []
|
||||||
self.master = master
|
self.master = master
|
||||||
def pack(self, **kwargs): pass
|
|
||||||
def place(self, **kwargs): pass
|
def pack(self, **kwargs):
|
||||||
def grid(self, **kwargs): pass
|
pass
|
||||||
def grid_propagate(self, flag): pass
|
|
||||||
def winfo_children(self): return self.children
|
def place(self, **kwargs):
|
||||||
def destroy(self): pass
|
pass
|
||||||
def update_idletasks(self): pass
|
|
||||||
def winfo_reqwidth(self): return 100
|
def grid(self, **kwargs):
|
||||||
def winfo_reqheight(self): return 100
|
pass
|
||||||
def bind(self, event, callback): 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
|
# Mock Canvas
|
||||||
class MockCanvas:
|
class MockCanvas:
|
||||||
def __init__(self, master=None, **kwargs):
|
def __init__(self, master=None, **kwargs):
|
||||||
self.master = master
|
self.master = master
|
||||||
def pack(self, **kwargs): pass
|
|
||||||
def bind(self, event, callback): pass
|
def pack(self, **kwargs):
|
||||||
def create_window(self, coords, **kwargs): return 1
|
pass
|
||||||
def config(self, **kwargs): pass
|
|
||||||
def bbox(self, tag): return (0,0,100,100)
|
def bind(self, event, callback):
|
||||||
def winfo_width(self): return 800
|
pass
|
||||||
def winfo_height(self): return 600
|
|
||||||
def coords(self, item, x, y): pass
|
def create_window(self, coords, **kwargs):
|
||||||
def scan_mark(self, x, y): pass
|
return 1
|
||||||
def scan_dragto(self, x, y, gain=1): pass
|
|
||||||
def canvasx(self, x): return x
|
def config(self, **kwargs):
|
||||||
def canvasy(self, y): return y
|
pass
|
||||||
def xview_moveto(self, fraction): pass
|
|
||||||
def yview_moveto(self, fraction): 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
|
# Monkey patch tk
|
||||||
original_tk = tk.Tk
|
original_tk = tk.Tk
|
||||||
@@ -67,10 +138,18 @@ def test_zoom_limits():
|
|||||||
class MockWidget:
|
class MockWidget:
|
||||||
def __init__(self, master=None, **kwargs):
|
def __init__(self, master=None, **kwargs):
|
||||||
self.master = master
|
self.master = master
|
||||||
def pack(self, **kwargs): pass
|
|
||||||
def place(self, **kwargs): pass
|
def pack(self, **kwargs):
|
||||||
def grid(self, **kwargs): pass
|
pass
|
||||||
def grid_propagate(self, flag): pass
|
|
||||||
|
def place(self, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def grid(self, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def grid_propagate(self, flag):
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tk.Tk = MockRoot
|
tk.Tk = MockRoot
|
||||||
@@ -80,20 +159,20 @@ def test_zoom_limits():
|
|||||||
tk.ttk.Button = MockWidget
|
tk.ttk.Button = MockWidget
|
||||||
|
|
||||||
# Mock Image to avoid memory issues
|
# 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 = MagicMock()
|
||||||
mock_img.size = (100, 100)
|
mock_img.size = (100, 100)
|
||||||
mock_img.resize.return_value = mock_img
|
mock_img.resize.return_value = mock_img
|
||||||
mock_open.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
|
cube = Tarot.cube
|
||||||
display = CubeDisplay(cube)
|
display = CubeDisplay(cube)
|
||||||
display.root = MockRoot()
|
display.root = MockRoot()
|
||||||
display.canvas = MockCanvas()
|
display.canvas = MockCanvas()
|
||||||
display.content_frame = MockFrame()
|
display.content_frame = MockFrame()
|
||||||
display.canvas_window = 1 # Mock window ID
|
display.canvas_window = 1 # Mock window ID
|
||||||
|
|
||||||
# Test initial zoom
|
# Test initial zoom
|
||||||
assert display.zoom_level == 1.0
|
assert display.zoom_level == 1.0
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ Tests for Tarot deck and card classes.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
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:
|
class TestCard:
|
||||||
@@ -30,7 +31,7 @@ class TestMajorCard:
|
|||||||
name="The Magician",
|
name="The Magician",
|
||||||
meaning=Meaning("Upright", "Reversed"),
|
meaning=Meaning("Upright", "Reversed"),
|
||||||
arcana="Major",
|
arcana="Major",
|
||||||
kabbalistic_number=1
|
kabbalistic_number=1,
|
||||||
)
|
)
|
||||||
assert card.number == 1
|
assert card.number == 1
|
||||||
assert card.arcana == "Major"
|
assert card.arcana == "Major"
|
||||||
@@ -42,7 +43,7 @@ class TestMajorCard:
|
|||||||
name="Test",
|
name="Test",
|
||||||
meaning=Meaning("Up", "Rev"),
|
meaning=Meaning("Up", "Rev"),
|
||||||
arcana="Major",
|
arcana="Major",
|
||||||
kabbalistic_number=-1
|
kabbalistic_number=-1,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_major_card_invalid_high(self):
|
def test_major_card_invalid_high(self):
|
||||||
@@ -52,16 +53,13 @@ class TestMajorCard:
|
|||||||
name="Test",
|
name="Test",
|
||||||
meaning=Meaning("Up", "Rev"),
|
meaning=Meaning("Up", "Rev"),
|
||||||
arcana="Major",
|
arcana="Major",
|
||||||
kabbalistic_number=22
|
kabbalistic_number=22,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_major_card_valid_range(self):
|
def test_major_card_valid_range(self):
|
||||||
for i in range(22):
|
for i in range(22):
|
||||||
card = MajorCard(
|
card = MajorCard(
|
||||||
number=i,
|
number=i, name=f"Card {i}", meaning=Meaning("Up", "Rev"), arcana="Major"
|
||||||
name=f"Card {i}",
|
|
||||||
meaning=Meaning("Up", "Rev"),
|
|
||||||
arcana="Major"
|
|
||||||
)
|
)
|
||||||
assert card.number == i
|
assert card.number == i
|
||||||
|
|
||||||
@@ -75,7 +73,7 @@ class TestMinorCard:
|
|||||||
meaning=Meaning("Upright", "Reversed"),
|
meaning=Meaning("Upright", "Reversed"),
|
||||||
arcana="Minor",
|
arcana="Minor",
|
||||||
suit=suit,
|
suit=suit,
|
||||||
pip=1
|
pip=1,
|
||||||
)
|
)
|
||||||
assert card.number == 1
|
assert card.number == 1
|
||||||
assert card.suit.name == "Cups"
|
assert card.suit.name == "Cups"
|
||||||
@@ -90,7 +88,7 @@ class TestMinorCard:
|
|||||||
meaning=Meaning("Up", "Rev"),
|
meaning=Meaning("Up", "Rev"),
|
||||||
arcana="Minor",
|
arcana="Minor",
|
||||||
suit=suit,
|
suit=suit,
|
||||||
pip=0
|
pip=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_minor_card_invalid_pip_high(self):
|
def test_minor_card_invalid_pip_high(self):
|
||||||
@@ -102,7 +100,7 @@ class TestMinorCard:
|
|||||||
meaning=Meaning("Up", "Rev"),
|
meaning=Meaning("Up", "Rev"),
|
||||||
arcana="Minor",
|
arcana="Minor",
|
||||||
suit=suit,
|
suit=suit,
|
||||||
pip=15
|
pip=15,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_minor_card_valid_pips(self):
|
def test_minor_card_valid_pips(self):
|
||||||
@@ -114,7 +112,7 @@ class TestMinorCard:
|
|||||||
meaning=Meaning("Up", "Rev"),
|
meaning=Meaning("Up", "Rev"),
|
||||||
arcana="Minor",
|
arcana="Minor",
|
||||||
suit=suit,
|
suit=suit,
|
||||||
pip=i
|
pip=i,
|
||||||
)
|
)
|
||||||
assert card.pip == i
|
assert card.pip == i
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
import pytest
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from tarot.ui import CardDisplay
|
from tarot.ui import CardDisplay
|
||||||
|
|
||||||
|
|
||||||
def test_card_display_init():
|
def test_card_display_init():
|
||||||
display = CardDisplay("default")
|
display = CardDisplay("default")
|
||||||
assert display.deck_name == "default"
|
assert display.deck_name == "default"
|
||||||
# Check if path resolves correctly relative to src/tarot/ui.py
|
# Check if path resolves correctly relative to src/tarot/ui.py
|
||||||
# src/tarot/ui.py -> src/tarot -> src/tarot/deck/default
|
# src/tarot/ui.py -> src/tarot -> src/tarot/deck/default
|
||||||
expected_suffix = os.path.join("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():
|
def test_card_display_resolve_path():
|
||||||
display = CardDisplay("thoth")
|
display = CardDisplay("thoth")
|
||||||
assert display.deck_name == "thoth"
|
assert display.deck_name == "thoth"
|
||||||
assert str(display.deck_path).endswith("thoth")
|
assert str(display.deck_path).endswith("thoth")
|
||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import pytest
|
|
||||||
from tarot.ui import CubeDisplay
|
|
||||||
from tarot.tarot_api import Tarot
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from tarot.tarot_api import Tarot
|
||||||
|
from tarot.ui import CubeDisplay
|
||||||
|
|
||||||
|
|
||||||
def test_recursive_binding():
|
def test_recursive_binding():
|
||||||
# Mock Tk root and widgets
|
# Mock Tk root and widgets
|
||||||
class MockWidget:
|
class MockWidget:
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import pytest
|
|
||||||
from tarot.ui import CubeDisplay
|
|
||||||
from tarot.tarot_api import Tarot
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from tarot.tarot_api import Tarot
|
||||||
|
from tarot.ui import CubeDisplay
|
||||||
|
|
||||||
|
|
||||||
def test_zoom_key_bindings():
|
def test_zoom_key_bindings():
|
||||||
# This test verifies that the bindings are set up,
|
# This test verifies that the bindings are set up,
|
||||||
# but cannot easily simulate key presses in headless environment.
|
# but cannot easily simulate key presses in headless environment.
|
||||||
@@ -16,23 +19,46 @@ def test_zoom_key_bindings():
|
|||||||
def bind(self, key, callback):
|
def bind(self, key, callback):
|
||||||
self.bindings[key] = callback
|
self.bindings[key] = callback
|
||||||
|
|
||||||
def title(self, _): pass
|
def title(self, _):
|
||||||
def update_idletasks(self): pass
|
pass
|
||||||
def winfo_reqwidth(self): return 800
|
|
||||||
def winfo_reqheight(self): return 600
|
def update_idletasks(self):
|
||||||
def winfo_screenwidth(self): return 1920
|
pass
|
||||||
def winfo_screenheight(self): return 1080
|
|
||||||
def geometry(self, _): pass
|
def winfo_reqwidth(self):
|
||||||
def mainloop(self): pass
|
return 800
|
||||||
def focus_force(self): pass
|
|
||||||
|
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
|
# Mock Frame
|
||||||
class MockFrame:
|
class MockFrame:
|
||||||
def __init__(self, master=None, **kwargs):
|
def __init__(self, master=None, **kwargs):
|
||||||
self.children = []
|
self.children = []
|
||||||
def pack(self, **kwargs): pass
|
|
||||||
def winfo_children(self): return self.children
|
def pack(self, **kwargs):
|
||||||
def destroy(self): pass
|
pass
|
||||||
|
|
||||||
|
def winfo_children(self):
|
||||||
|
return self.children
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
pass
|
||||||
|
|
||||||
# Monkey patch tk
|
# Monkey patch tk
|
||||||
original_tk = tk.Tk
|
original_tk = tk.Tk
|
||||||
@@ -58,6 +84,7 @@ def test_zoom_key_bindings():
|
|||||||
tk.Tk = original_tk
|
tk.Tk = original_tk
|
||||||
tk.ttk.Frame = original_frame
|
tk.ttk.Frame = original_frame
|
||||||
|
|
||||||
|
|
||||||
def test_zoom_logic_direct():
|
def test_zoom_logic_direct():
|
||||||
cube = Tarot.cube
|
cube = Tarot.cube
|
||||||
display = CubeDisplay(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 tkinter as tk
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from tarot.tarot_api import Tarot
|
||||||
|
from tarot.ui import CubeDisplay
|
||||||
|
|
||||||
|
|
||||||
def test_canvas_structure():
|
def test_canvas_structure():
|
||||||
# Mock Tk root
|
# Mock Tk root
|
||||||
class MockRoot:
|
class MockRoot:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.bindings = {}
|
self.bindings = {}
|
||||||
def bind(self, key, callback): pass
|
|
||||||
def title(self, _): pass
|
def bind(self, key, callback):
|
||||||
def update_idletasks(self): pass
|
pass
|
||||||
def winfo_reqwidth(self): return 800
|
|
||||||
def winfo_reqheight(self): return 600
|
def title(self, _):
|
||||||
def winfo_screenwidth(self): return 1920
|
pass
|
||||||
def winfo_screenheight(self): return 1080
|
|
||||||
def geometry(self, _): pass
|
def update_idletasks(self):
|
||||||
def mainloop(self): pass
|
pass
|
||||||
def focus_force(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
|
# Mock Frame
|
||||||
class MockFrame:
|
class MockFrame:
|
||||||
def __init__(self, master=None, **kwargs):
|
def __init__(self, master=None, **kwargs):
|
||||||
self.children = []
|
self.children = []
|
||||||
self.master = master
|
self.master = master
|
||||||
def pack(self, **kwargs): pass
|
|
||||||
def place(self, **kwargs): pass
|
def pack(self, **kwargs):
|
||||||
def winfo_children(self): return self.children
|
pass
|
||||||
def destroy(self): pass
|
|
||||||
def update_idletasks(self): pass
|
def place(self, **kwargs):
|
||||||
def winfo_reqwidth(self): return 100
|
pass
|
||||||
def winfo_reqheight(self): return 100
|
|
||||||
|
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
|
# Mock Canvas
|
||||||
class MockCanvas:
|
class MockCanvas:
|
||||||
def __init__(self, master=None, **kwargs):
|
def __init__(self, master=None, **kwargs):
|
||||||
self.master = master
|
self.master = master
|
||||||
def pack(self, **kwargs): pass
|
|
||||||
def bind(self, event, callback): pass
|
def pack(self, **kwargs):
|
||||||
def create_window(self, coords, **kwargs): return 1
|
pass
|
||||||
def config(self, **kwargs): pass
|
|
||||||
def bbox(self, tag): return (0,0,100,100)
|
def bind(self, event, callback):
|
||||||
def winfo_width(self): return 800
|
pass
|
||||||
def winfo_height(self): return 600
|
|
||||||
def coords(self, item, x, y): pass
|
def create_window(self, coords, **kwargs):
|
||||||
def scan_mark(self, x, y): pass
|
return 1
|
||||||
def scan_dragto(self, x, y, gain=1): pass
|
|
||||||
|
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
|
# Monkey patch tk
|
||||||
original_tk = tk.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
|
import tkinter as tk
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from tarot.tarot_api import Tarot
|
||||||
|
from tarot.ui import CubeDisplay
|
||||||
|
|
||||||
|
|
||||||
def test_wasd_panning():
|
def test_wasd_panning():
|
||||||
# Mock Tk root
|
# Mock Tk root
|
||||||
class MockRoot:
|
class MockRoot:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.bindings = {}
|
self.bindings = {}
|
||||||
self.images = []
|
self.images = []
|
||||||
|
|
||||||
def bind(self, key, callback):
|
def bind(self, key, callback):
|
||||||
self.bindings[key] = callback
|
self.bindings[key] = callback
|
||||||
def title(self, _): pass
|
|
||||||
def update_idletasks(self): pass
|
def title(self, _):
|
||||||
def winfo_reqwidth(self): return 800
|
pass
|
||||||
def winfo_reqheight(self): return 600
|
|
||||||
def winfo_screenwidth(self): return 1920
|
def update_idletasks(self):
|
||||||
def winfo_screenheight(self): return 1080
|
pass
|
||||||
def geometry(self, _): pass
|
|
||||||
def mainloop(self): pass
|
def winfo_reqwidth(self):
|
||||||
def focus_force(self): pass
|
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
|
# Mock Frame
|
||||||
class MockFrame:
|
class MockFrame:
|
||||||
def __init__(self, master=None, **kwargs):
|
def __init__(self, master=None, **kwargs):
|
||||||
self.children = []
|
self.children = []
|
||||||
self.master = master
|
self.master = master
|
||||||
def pack(self, **kwargs): pass
|
|
||||||
def place(self, **kwargs): pass
|
def pack(self, **kwargs):
|
||||||
def grid(self, **kwargs): pass
|
pass
|
||||||
def grid_propagate(self, flag): pass
|
|
||||||
def winfo_children(self): return self.children
|
def place(self, **kwargs):
|
||||||
def destroy(self): pass
|
pass
|
||||||
def update_idletasks(self): pass
|
|
||||||
def winfo_reqwidth(self): return 100
|
def grid(self, **kwargs):
|
||||||
def winfo_reqheight(self): return 100
|
pass
|
||||||
def bind(self, event, callback): 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
|
# Mock Canvas
|
||||||
class MockCanvas:
|
class MockCanvas:
|
||||||
@@ -45,20 +87,47 @@ def test_wasd_panning():
|
|||||||
self.x_scrolls = []
|
self.x_scrolls = []
|
||||||
self.y_scrolls = []
|
self.y_scrolls = []
|
||||||
|
|
||||||
def pack(self, **kwargs): pass
|
def pack(self, **kwargs):
|
||||||
def bind(self, event, callback): pass
|
pass
|
||||||
def create_window(self, coords, **kwargs): return 1
|
|
||||||
def config(self, **kwargs): pass
|
def bind(self, event, callback):
|
||||||
def bbox(self, tag): return (0,0,100,100)
|
pass
|
||||||
def winfo_width(self): return 800
|
|
||||||
def winfo_height(self): return 600
|
def create_window(self, coords, **kwargs):
|
||||||
def coords(self, item, x, y): pass
|
return 1
|
||||||
def scan_mark(self, x, y): pass
|
|
||||||
def scan_dragto(self, x, y, gain=1): pass
|
def config(self, **kwargs):
|
||||||
def canvasx(self, x): return x
|
pass
|
||||||
def canvasy(self, y): return y
|
|
||||||
def xview_moveto(self, fraction): pass
|
def bbox(self, tag):
|
||||||
def yview_moveto(self, fraction): pass
|
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):
|
def xview_scroll(self, number, what):
|
||||||
self.x_scrolls.append((number, what))
|
self.x_scrolls.append((number, what))
|
||||||
@@ -77,10 +146,18 @@ def test_wasd_panning():
|
|||||||
class MockWidget:
|
class MockWidget:
|
||||||
def __init__(self, master=None, **kwargs):
|
def __init__(self, master=None, **kwargs):
|
||||||
self.master = master
|
self.master = master
|
||||||
def pack(self, **kwargs): pass
|
|
||||||
def place(self, **kwargs): pass
|
def pack(self, **kwargs):
|
||||||
def grid(self, **kwargs): pass
|
pass
|
||||||
def grid_propagate(self, flag): pass
|
|
||||||
|
def place(self, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def grid(self, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def grid_propagate(self, flag):
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tk.Tk = MockRoot
|
tk.Tk = MockRoot
|
||||||
@@ -90,13 +167,13 @@ def test_wasd_panning():
|
|||||||
tk.ttk.Button = MockWidget
|
tk.ttk.Button = MockWidget
|
||||||
|
|
||||||
# Mock Image to avoid memory issues
|
# 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 = MagicMock()
|
||||||
mock_img.size = (100, 100)
|
mock_img.size = (100, 100)
|
||||||
mock_img.resize.return_value = mock_img
|
mock_img.resize.return_value = mock_img
|
||||||
mock_open.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
|
cube = Tarot.cube
|
||||||
display = CubeDisplay(cube)
|
display = CubeDisplay(cube)
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ References:
|
|||||||
- Weekday planetary rulers
|
- Weekday planetary rulers
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from datetime import datetime, date, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, Optional
|
||||||
|
|
||||||
|
|
||||||
# Planetary symbols for weekdays (Sun=0, Mon=1, ..., Sat=6)
|
# Planetary symbols for weekdays (Sun=0, Mon=1, ..., Sat=6)
|
||||||
|
|||||||
Reference in New Issue
Block a user