pymdownx-mahjong 1.0.0__py3-none-any.whl → 1.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ from .extension import MahjongExtension, makeExtension
4
4
  from .inline import INLINE_TILE_PATTERN, MahjongInlineProcessor
5
5
  from .parser import Hand, MahjongParser, Meld, Tile
6
6
  from .renderer import MahjongRenderer
7
- from .superfences import superfences_formatter, superfences_validator
7
+ from .superfences import configure_superfences, superfences_formatter, superfences_validator
8
8
 
9
9
  __all__ = [
10
10
  "MahjongExtension",
@@ -18,6 +18,7 @@ __all__ = [
18
18
  "Hand",
19
19
  "superfences_formatter",
20
20
  "superfences_validator",
21
+ "configure_superfences",
21
22
  ]
22
23
 
23
- __version__ = "1.0.0"
24
+ __version__ = "1.2.0"
@@ -0,0 +1,3 @@
1
+ Original assets by https://github.com/FluffyStuff/riichi-mahjong-tiles, available in the public domain.
2
+
3
+ Some assets have been recolored, tweaks, minified, or otherwise modified.
@@ -23,7 +23,7 @@
23
23
  --mahjong-error-border: #dc2626;
24
24
  --mahjong-error-color: #fca5a5;
25
25
  --mahjong-tile-bg: #1e1e1e;
26
- --mahjong-tile-border: #9a9a8a;
26
+ --mahjong-tile-border: #47473e;
27
27
  --mahjong-tile-shadow: rgba(0, 0, 0, 0.3);
28
28
  }
29
29
 
@@ -48,7 +48,7 @@
48
48
  display: contents;
49
49
  }
50
50
 
51
- /* Main container */
51
+ /* NOTE Main container */
52
52
  .mahjong-hand {
53
53
  display: block;
54
54
  margin: 1em 0;
@@ -58,24 +58,24 @@
58
58
  border-radius: 4px;
59
59
  }
60
60
 
61
- /* Override MkDocs Material theme figure margins */
61
+ /* NOTE Override MkDocs Material/Zensical theme figure margins */
62
62
  .md-typeset .mahjong-hand {
63
63
  margin-left: 1rem;
64
64
  }
65
65
 
66
- /* Hand row - contains left (tiles/melds), draw, and right (dora) sections */
66
+ /* NOTE Hand row - contains left (tiles/melds), draw, and right (dora) sections */
67
67
  .mahjong-hand-row {
68
68
  display: flex;
69
69
  flex-wrap: nowrap;
70
70
  align-items: flex-end;
71
71
  }
72
72
 
73
- /* Left section - closed tiles and melds */
73
+ /* NOTE Left section - closed tiles and melds */
74
74
  .mahjong-hand-left {
75
75
  flex: 0 0 auto;
76
76
  }
77
77
 
78
- /* Draw tile section - separated from main hand */
78
+ /* NOTE Draw tile section */
79
79
  .mahjong-hand-draw {
80
80
  flex: 0 0 auto;
81
81
  margin-left: calc(var(--mahjong-meld-gap) * 0.75);
@@ -96,13 +96,13 @@
96
96
  pointer-events: none;
97
97
  }
98
98
 
99
- /* Melds section - after draw tile */
99
+ /* NOTE Melds section - after draw tile */
100
100
  .mahjong-hand-melds {
101
101
  flex: 0 0 auto;
102
102
  margin-left: calc(var(--mahjong-meld-gap) * 0.75);
103
103
  }
104
104
 
105
- /* Tiles container - flexbox layout */
105
+ /* NOTE Tiles container - flexbox layout */
106
106
  .mahjong-tiles {
107
107
  display: flex;
108
108
  flex-wrap: wrap;
@@ -110,7 +110,7 @@
110
110
  gap: var(--mahjong-tile-gap);
111
111
  }
112
112
 
113
- /* Individual tile - styled like physical riichi tiles */
113
+ /* NOTE Individual tile - styled like physical riichi tiles */
114
114
  .mahjong-tile {
115
115
  display: inline-flex;
116
116
  align-items: center;
@@ -122,9 +122,6 @@
122
122
  background: var(--mahjong-tile-bg);
123
123
  border: 2px solid var(--mahjong-tile-border);
124
124
  border-radius: 6px;
125
- box-shadow:
126
- 1px 2px 3px var(--mahjong-tile-shadow),
127
- inset 0 1px 0 rgba(255, 255, 255, 0.3);
128
125
  padding: 2px;
129
126
  box-sizing: border-box;
130
127
  position: relative;
@@ -144,6 +141,8 @@
144
141
  /* Rotated tile (called from another player) */
145
142
  .mahjong-tile-rotated {
146
143
  transform: rotate(90deg);
144
+ will-change: transform;
145
+ backface-visibility: hidden;
147
146
  margin-left: calc(var(--mahjong-tile-gap) * 6);
148
147
  margin-right: calc(var(--mahjong-tile-gap) * 6);
149
148
  position: relative;
@@ -19,6 +19,15 @@ if TYPE_CHECKING:
19
19
  from markdown.blockparser import BlockParser
20
20
 
21
21
 
22
+ def _to_bool(value: Any) -> bool:
23
+ """Convert a value to boolean, handling string 'true'/'false'."""
24
+ if isinstance(value, bool):
25
+ return value
26
+ if isinstance(value, str):
27
+ return value.lower() in ("true", "1", "yes")
28
+ return bool(value)
29
+
30
+
22
31
  class MahjongBlockProcessor(BlockProcessor):
23
32
  """Block processor that handles ```mahjong fenced code blocks.
24
33
 
@@ -43,10 +52,9 @@ class MahjongBlockProcessor(BlockProcessor):
43
52
  self.mj_parser = MahjongParser()
44
53
  self.renderer = MahjongRenderer(
45
54
  theme=config.get("theme", "light"),
46
- css_class=config.get("css_class", "mahjong-hand"),
47
- show_labels=config.get("show_labels", True),
48
- inline_svg=config.get("inline_svg", True),
55
+ inline_svg=_to_bool(config.get("inline_svg", True)),
49
56
  assets_path=config.get("assets_path"),
57
+ closed_kan_style=config.get("closed_kan_style", "outer"),
50
58
  )
51
59
 
52
60
  def test(self, parent: etree.Element, block: str) -> bool:
@@ -158,13 +166,13 @@ class MahjongExtension(markdown.Extension):
158
166
  **kwargs: Configuration options
159
167
  """
160
168
  # Define configuration options with defaults
169
+ # Note: Use strings for boolean defaults for YAML compatibility
161
170
  self.config = {
162
171
  "theme": ["auto", "Color theme: 'light', 'dark', or 'auto'"],
163
- "css_class": ["mahjong-hand", "CSS class for container"],
164
- "show_labels": [True, "Show tile names as title attributes"],
165
- "inline_svg": [True, "Inline SVG content vs img tags"],
172
+ "inline_svg": ["true", "Inline SVG content vs img tags"],
166
173
  "assets_path": ["", "Custom path to SVG assets"],
167
- "enable_inline": [True, "Enable inline tile syntax (:1m:)"],
174
+ "enable_inline": ["true", "Enable inline tile syntax (:1m:)"],
175
+ "closed_kan_style": ["outer", "Closed kan style: 'outer' or 'inner'"],
168
176
  }
169
177
  super().__init__(**kwargs)
170
178
 
@@ -184,7 +192,7 @@ class MahjongExtension(markdown.Extension):
184
192
 
185
193
  # Register inline processor if enabled
186
194
  # Priority 76 to run before pymdownx.emoji (which uses 75)
187
- if config.get("enable_inline", True):
195
+ if _to_bool(config.get("enable_inline", True)):
188
196
  inline_processor = MahjongInlineProcessor(INLINE_TILE_PATTERN, md, config)
189
197
  md.inlinePatterns.register(inline_processor, "mahjong_inline", 76)
190
198
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import TYPE_CHECKING, Any
5
+ from typing import TYPE_CHECKING, Any, Final
6
6
 
7
7
  from markdown.inlinepatterns import InlineProcessor
8
8
 
@@ -17,7 +17,16 @@ if TYPE_CHECKING:
17
17
  # Pattern matches :123m:, :1z:, :0m: (red dora), etc.
18
18
  # Must be valid MPSZ: one or more groups of digits followed by m/p/s/z
19
19
  # Examples: :1m:, :123p:, :5z:, :0s:, :123m456p:
20
- INLINE_TILE_PATTERN = r":([0-9]+[mpsz])+:"
20
+ INLINE_TILE_PATTERN: Final[str] = r":([0-9]+[mpsz])+:"
21
+
22
+
23
+ def _to_bool(value: Any) -> bool:
24
+ """Convert a value to boolean, handling string 'true'/'false'."""
25
+ if isinstance(value, bool):
26
+ return value
27
+ if isinstance(value, str):
28
+ return value.lower() in ("true", "1", "yes")
29
+ return bool(value)
21
30
 
22
31
 
23
32
  class MahjongInlineProcessor(InlineProcessor):
@@ -40,10 +49,9 @@ class MahjongInlineProcessor(InlineProcessor):
40
49
  self.parser = MahjongParser()
41
50
  self.renderer = MahjongRenderer(
42
51
  theme=config.get("theme", "auto"),
43
- css_class="mahjong-inline",
44
- show_labels=config.get("show_labels", True),
45
- inline_svg=config.get("inline_svg", True),
52
+ inline_svg=_to_bool(config.get("inline_svg", True)),
46
53
  assets_path=config.get("assets_path"),
54
+ css_class="mahjong-inline",
47
55
  )
48
56
 
49
57
  def handleMatch(self, m: re.Match, data: str) -> tuple[str | None, int | None, int | None]:
@@ -3,8 +3,10 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import re
6
+ from collections import Counter
6
7
  from dataclasses import dataclass, field
7
8
  from enum import Enum
9
+ from typing import Final, Pattern
8
10
 
9
11
  from .tiles import TileInfo, get_tile_info, is_valid_tile
10
12
 
@@ -127,11 +129,11 @@ class MahjongParser:
127
129
  """
128
130
 
129
131
  # Pattern for a group of numbers followed by a suit
130
- TILE_GROUP_PATTERN = re.compile(r"([0-9]+)([mpsz])")
132
+ TILE_GROUP_PATTERN: Final[Pattern[str]] = re.compile(r"([0-9]+)([mpsz])")
131
133
 
132
134
  # Pattern for melds: (tiles<) or [tiles] with optional source marker inside brackets
133
135
  # For added kan, use + to mark the added tile: (111+1m<)
134
- MELD_PATTERN = re.compile(r"(\[|\()([0-9]+)(\+)?([0-9])?([mpsz])([<^>])?(\]|\))")
136
+ MELD_PATTERN: Final[Pattern[str]] = re.compile(r"(\[|\()([0-9]+)(\+)?([0-9])?([mpsz])([<^>])?(\]|\))")
135
137
 
136
138
  def __init__(self) -> None:
137
139
  self.errors: list[str] = []
@@ -163,6 +165,9 @@ class MahjongParser:
163
165
  # Parse closed tiles
164
166
  hand.closed_tiles = self._parse_tiles(closed_part)
165
167
 
168
+ # Validate tile counts (max 4 of each tile type)
169
+ self._validate_tile_counts(hand)
170
+
166
171
  if self.errors:
167
172
  raise ParseError("; ".join(self.errors))
168
173
 
@@ -336,6 +341,32 @@ class MahjongParser:
336
341
 
337
342
  return numbers[1] == numbers[0] + 1 and numbers[2] == numbers[1] + 1
338
343
 
344
+ def _validate_tile_counts(self, hand: Hand) -> None:
345
+ """Validate that no tile appears more than 4 times.
346
+
347
+ In Mahjong, there are exactly 4 copies of each tile type.
348
+ Having more than 4 of the same tile is invalid.
349
+
350
+ Args:
351
+ hand: The Hand object to validate
352
+ """
353
+ # Count all tiles including melds
354
+ # Note: Red 5 (0) and regular 5 are different tiles, so we count them separately
355
+ all_tiles = list(hand.closed_tiles)
356
+ for meld in hand.melds:
357
+ all_tiles.extend(meld.tiles)
358
+ if hand.draw_tile:
359
+ all_tiles.append(hand.draw_tile)
360
+
361
+ counts = Counter((t.suit, t.number) for t in all_tiles)
362
+
363
+ for (suit, number), count in counts.items():
364
+ if count > 4:
365
+ tile_notation = f"{number}{suit}"
366
+ self.errors.append(
367
+ f"Invalid tile count: {tile_notation} appears {count} times (max 4)"
368
+ )
369
+
339
370
 
340
371
  def parse_hand(notation: str) -> Hand:
341
372
  """Convenience function to parse a hand notation.
@@ -2,23 +2,47 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import functools
5
6
  import html
6
7
  import importlib.resources
7
8
  import re
8
9
  from pathlib import Path
10
+ from typing import Final, Pattern
9
11
 
10
12
  from .parser import Hand, Meld, MeldType, Tile
11
13
  from .tiles import TileInfo, get_special_tile
12
14
 
13
15
  # Pre-compiled regex patterns for SVG processing
14
- _RE_XML_DECL = re.compile(r"<\?xml[^?]*\?>")
15
- _RE_SODIPODI_SELF = re.compile(r"<sodipodi:namedview[^>]*/>")
16
- _RE_SODIPODI_FULL = re.compile(r"<sodipodi:namedview[^>]*>.*?</sodipodi:namedview>", re.DOTALL)
17
- _RE_METADATA = re.compile(r"<metadata[^>]*>.*?</metadata>", re.DOTALL)
18
- _RE_WIDTH = re.compile(r'width="[^"]*"')
19
- _RE_HEIGHT = re.compile(r'height="[^"]*"')
16
+ _RE_XML_DECL: Final[Pattern[str]] = re.compile(r"<\?xml[^?]*\?>")
17
+ _RE_SODIPODI_SELF: Final[Pattern[str]] = re.compile(r"<sodipodi:namedview[^>]*/>")
18
+ _RE_SODIPODI_FULL: Final[Pattern[str]] = re.compile(r"<sodipodi:namedview[^>]*>.*?</sodipodi:namedview>", re.DOTALL)
19
+ _RE_METADATA: Final[Pattern[str]] = re.compile(r"<metadata[^>]*>.*?</metadata>", re.DOTALL)
20
+ _RE_WIDTH: Final[Pattern[str]] = re.compile(r'width="[^"]*"')
21
+ _RE_HEIGHT: Final[Pattern[str]] = re.compile(r'height="[^"]*"')
20
22
  # Pattern to find IDs in SVGs
21
- _RE_ID = re.compile(r'id="([^"]+)"')
23
+ _RE_ID: Final[Pattern[str]] = re.compile(r'id="([^"]+)"')
24
+
25
+
26
+ @functools.lru_cache(maxsize=128)
27
+ def _load_svg_from_package(asset_name: str, theme: str) -> str:
28
+ """Load SVG content from package resources with caching.
29
+
30
+ This is a module-level cached function to avoid repeated file I/O
31
+ for the same tile assets across multiple renderer instances.
32
+
33
+ Args:
34
+ asset_name: Name of the asset (e.g., '1m', 'back')
35
+ theme: Theme to load ('light' or 'dark')
36
+
37
+ Returns:
38
+ Raw SVG content string
39
+
40
+ Raises:
41
+ FileNotFoundError: If the asset doesn't exist
42
+ """
43
+ assets = importlib.resources.files("pymdownx_mahjong") / "assets" / theme
44
+ svg_file = assets / f"{asset_name}.svg"
45
+ return svg_file.read_text(encoding="utf-8")
22
46
 
23
47
 
24
48
  class MahjongRenderer:
@@ -26,9 +50,8 @@ class MahjongRenderer:
26
50
 
27
51
  Configuration options:
28
52
  theme: 'light', 'dark', or 'auto'
29
- css_class: CSS class for the container
30
- show_labels: Whether to show tile names as titles
31
- inline_svg: Whether to inline SVG or use img tags
53
+ closed_kan_style: 'outer' (default) or 'inner'
54
+ inline_svg: Whether to inline SVG content (vs using img tags)
32
55
  """
33
56
 
34
57
  DEFAULT_TILE_WIDTH = 45
@@ -39,19 +62,21 @@ class MahjongRenderer:
39
62
  def __init__(
40
63
  self,
41
64
  theme: str = "light",
42
- css_class: str = "mahjong-hand",
43
- show_labels: bool = True,
44
65
  inline_svg: bool = True,
45
66
  assets_path: str | Path | None = None,
67
+ closed_kan_style: str = "outer",
68
+ css_class: str = "mahjong-hand",
46
69
  ) -> None:
47
70
  """Initialize the renderer.
48
71
 
49
72
  Args:
50
73
  theme: Color theme ('light', 'dark', or 'auto')
51
- css_class: CSS class for the container element
52
- show_labels: Show tile names as title attributes
53
74
  inline_svg: Inline SVG content vs img tags
54
75
  assets_path: Custom path to SVG assets
76
+ closed_kan_style: Style for closed kan back tiles:
77
+ 'outer' (default) - back tiles on edges (back, front, front, back)
78
+ 'inner' - back tiles in middle (front, back, back, front)
79
+ css_class: CSS class for container (internal use)
55
80
  """
56
81
  self.theme = theme
57
82
  self.tile_width = self.DEFAULT_TILE_WIDTH
@@ -59,10 +84,9 @@ class MahjongRenderer:
59
84
  self.tile_gap = self.DEFAULT_TILE_GAP
60
85
  self.meld_gap = self.DEFAULT_MELD_GAP
61
86
  self.css_class = css_class
62
- self.show_labels = show_labels
63
87
  self.inline_svg = inline_svg
64
88
  self.assets_path = Path(assets_path) if assets_path else None
65
- self._svg_cache: dict[tuple[str, str], str] = {}
89
+ self.closed_kan_style = closed_kan_style
66
90
  self._svg_id_counter = 0
67
91
 
68
92
  def render(
@@ -186,7 +210,7 @@ class MahjongRenderer:
186
210
  classes.append("mahjong-tile-added")
187
211
 
188
212
  class_str = " ".join(classes)
189
- title_attr = f' title="{info.display_name}"' if self.show_labels else ""
213
+ title_attr = f' title="{info.display_name}"'
190
214
 
191
215
  if self.inline_svg:
192
216
  svg_content = self._get_themed_svg_content(info)
@@ -248,9 +272,18 @@ class MahjongRenderer:
248
272
  parts.append(self._render_tile(meld.tiles[2]))
249
273
  else:
250
274
  for i, tile in enumerate(meld.tiles):
251
- # For closed kan, show back tiles for middle two
252
- if meld.meld_type == MeldType.KAN_CLOSED and i in (1, 2):
253
- parts.append(self._render_back_tile())
275
+ # For closed kan, show back tiles based on style setting
276
+ if meld.meld_type == MeldType.KAN_CLOSED:
277
+ if self.closed_kan_style == "inner":
278
+ # 'inner': back tiles in middle: front, back, back, front
279
+ is_back = i in (1, 2)
280
+ else:
281
+ # Default 'outer': back tiles on edges: back, front, front, back
282
+ is_back = i in (0, 3)
283
+ if is_back:
284
+ parts.append(self._render_back_tile())
285
+ else:
286
+ parts.append(self._render_tile(tile))
254
287
  else:
255
288
  parts.append(self._render_tile(tile))
256
289
 
@@ -290,17 +323,12 @@ class MahjongRenderer:
290
323
  SVG content string with unique IDs
291
324
  """
292
325
  theme = theme or (self.theme if self.theme != "auto" else "light")
293
- cache_key = (theme, info.asset_name)
294
326
 
295
- # Cache raw loaded SVG content (before ID uniquification)
296
- if cache_key not in self._svg_cache:
297
- svg_content = self._load_svg(info, theme)
298
- # Process but don't add unique IDs yet - cache the base processed version
299
- svg_content = self._process_svg(svg_content, unique_prefix=None)
300
- self._svg_cache[cache_key] = svg_content
327
+ # Load and process SVG (package assets use module-level LRU cache)
328
+ svg_content = self._load_svg(info, theme)
329
+ svg_content = self._process_svg(svg_content)
301
330
 
302
- # Get cached SVG and make IDs unique for this instance
303
- svg_content = self._svg_cache[cache_key]
331
+ # Make IDs unique for this instance
304
332
  self._svg_id_counter += 1
305
333
  return self._make_ids_unique(svg_content, f"mj{self._svg_id_counter}_")
306
334
 
@@ -342,32 +370,28 @@ class MahjongRenderer:
342
370
  """
343
371
  theme = theme or (self.theme if self.theme != "auto" else "light")
344
372
 
345
- # Try custom assets path first
373
+ # Try custom assets path first (not cached since it's user-specific)
346
374
  if self.assets_path:
347
375
  svg_path = self.assets_path / theme / f"{info.asset_name}.svg"
348
376
  if svg_path.exists():
349
377
  return svg_path.read_text(encoding="utf-8")
350
378
 
351
- # Fall back to package assets
379
+ # Fall back to package assets (uses module-level LRU cache)
352
380
  try:
353
- assets = importlib.resources.files("pymdownx_mahjong") / "assets" / theme
354
- svg_file = assets / f"{info.asset_name}.svg"
355
- return svg_file.read_text(encoding="utf-8")
381
+ return _load_svg_from_package(info.asset_name, theme)
356
382
  except (FileNotFoundError, TypeError):
357
383
  # Return a placeholder SVG
358
384
  return self._placeholder_svg(info)
359
385
 
360
- def _process_svg(self, svg_content: str, unique_prefix: str | None = None) -> str:
386
+ def _process_svg(self, svg_content: str) -> str:
361
387
  """Process SVG content for inline use.
362
388
 
363
389
  - Removes XML declaration
364
390
  - Removes unnecessary metadata
365
391
  - Adds sizing attributes
366
- - Makes IDs unique to avoid conflicts when multiple SVGs are on the same page
367
392
 
368
393
  Args:
369
394
  svg_content: Raw SVG content
370
- unique_prefix: Optional prefix to make IDs unique
371
395
 
372
396
  Returns:
373
397
  Processed SVG content
@@ -384,10 +408,6 @@ class MahjongRenderer:
384
408
  svg_content = _RE_WIDTH.sub(f'width="{self.tile_width}"', svg_content, count=1)
385
409
  svg_content = _RE_HEIGHT.sub(f'height="{self.tile_height}"', svg_content, count=1)
386
410
 
387
- # Make IDs unique if prefix is provided
388
- if unique_prefix:
389
- svg_content = self._make_ids_unique(svg_content, unique_prefix)
390
-
391
411
  return svg_content.strip()
392
412
 
393
413
  def _make_ids_unique(self, svg_content: str, prefix: str) -> str:
@@ -17,20 +17,32 @@ class _SuperfencesState:
17
17
  """Encapsulates global state for superfences integration.
18
18
 
19
19
  Uses lazy initialization to create parser/renderer on first use.
20
+ Allows configuration via configure() method.
20
21
  """
21
22
 
22
23
  def __init__(self) -> None:
23
24
  self._renderer: MahjongRenderer | None = None
24
25
  self._parser: MahjongParser | None = None
26
+ self._config: dict[str, Any] = {}
27
+
28
+ def configure(self, **kwargs: Any) -> None:
29
+ """Configure the superfences state.
30
+
31
+ Args:
32
+ **kwargs: Configuration options (theme, closed_kan_style, etc.)
33
+ """
34
+ self._config.update(kwargs)
35
+ # Reset renderer to apply new config
36
+ self._renderer = None
25
37
 
26
38
  @property
27
39
  def renderer(self) -> MahjongRenderer:
28
40
  """Get or create the renderer instance."""
29
41
  if self._renderer is None:
30
42
  self._renderer = MahjongRenderer(
31
- theme="auto",
32
- show_labels=True,
33
- inline_svg=True,
43
+ theme=self._config.get("theme", "auto"),
44
+ inline_svg=self._config.get("inline_svg", True),
45
+ closed_kan_style=self._config.get("closed_kan_style", "outer"),
34
46
  )
35
47
  return self._renderer
36
48
 
@@ -46,6 +58,24 @@ class _SuperfencesState:
46
58
  _state = _SuperfencesState()
47
59
 
48
60
 
61
+ def configure_superfences(**kwargs: Any) -> None:
62
+ """Configure the superfences integration.
63
+
64
+ Call this before using superfences to set options like closed_kan_style.
65
+
66
+ Example:
67
+ from pymdownx_mahjong import configure_superfences
68
+ configure_superfences(closed_kan_style='outer')
69
+
70
+ Args:
71
+ **kwargs: Configuration options
72
+ - theme: 'light', 'dark', or 'auto'
73
+ - closed_kan_style: 'outer' or 'inner'
74
+ - inline_svg: bool
75
+ """
76
+ _state.configure(**kwargs)
77
+
78
+
49
79
  def superfences_validator(
50
80
  language: str,
51
81
  inputs: dict[str, str],
@@ -65,6 +95,14 @@ def superfences_validator(
65
95
  Returns:
66
96
  True if this is a valid mahjong fence
67
97
  """
98
+ # Try to get config from the markdown instance's extension
99
+ if hasattr(md, 'registeredExtensions'):
100
+ for ext in md.registeredExtensions:
101
+ if hasattr(ext, 'config') and 'closed_kan_style' in ext.config:
102
+ config = {key: ext.getConfig(key) for key in ext.config}
103
+ _state.configure(**config)
104
+ break
105
+
68
106
  return language == "mahjong"
69
107
 
70
108
 
pymdownx_mahjong/tiles.py CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import NamedTuple
5
+ from typing import Final, NamedTuple
6
6
 
7
7
 
8
8
  class TileInfo(NamedTuple):
@@ -12,7 +12,7 @@ class TileInfo(NamedTuple):
12
12
  display_name: str
13
13
 
14
14
 
15
- TILE_DATABASE: dict[tuple[str, int], TileInfo] = {
15
+ TILE_DATABASE: Final[dict[tuple[str, int], TileInfo]] = {
16
16
  # Manzu
17
17
  ("m", 1): TileInfo("1m", "1 Man"),
18
18
  ("m", 2): TileInfo("2m", "2 Man"),
@@ -57,7 +57,7 @@ TILE_DATABASE: dict[tuple[str, int], TileInfo] = {
57
57
  ("z", 7): TileInfo("7z", "Red Dragon"),
58
58
  }
59
59
 
60
- SPECIAL_TILES: dict[str, TileInfo] = {
60
+ SPECIAL_TILES: Final[dict[str, TileInfo]] = {
61
61
  "back": TileInfo("back", "Face Down"),
62
62
  "blank": TileInfo("blank", "Blank"),
63
63
  "front": TileInfo("front", "Front"),
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pymdownx-mahjong
3
- Version: 1.0.0
3
+ Version: 1.2.0
4
4
  Summary: Python Markdown extension to render and stylize Mahjong tiles.
5
5
  Project-URL: Homepage, https://github.com/tylernguyen/pymdownx-mahjong
6
- Project-URL: Documentation, https://github.com/tylernguyen/pymdownx-mahjong
6
+ Project-URL: Documentation, https://tylernguyen.github.io/pymdownx-mahjong/
7
7
  Project-URL: Repository, https://github.com/tylernguyen/pymdownx-mahjong
8
8
  Project-URL: Issues, https://github.com/tylernguyen/pymdownx-mahjong/issues
9
9
  Author: Tyler Nguyen
@@ -35,9 +35,15 @@ Requires-Dist: mkdocs-material>=9.0; extra == 'mkdocs'
35
35
  Requires-Dist: mkdocs>=1.4; extra == 'mkdocs'
36
36
  Description-Content-Type: text/markdown
37
37
 
38
+ ![PyPI - Version](https://img.shields.io/pypi/v/pymdownx-mahjong)
39
+
38
40
  # PyMdown Mahjong
39
41
 
40
- Extensions for [Python Markdown](https://python-markdown.github.io) that aids writing Mahjong content
42
+ [Python Markdown](https://python-markdown.github.io) extension to render and stylize Mahjong tiles. Designed for use with [MkDocs](https://github.com/mkdocs/mkdocs) and [Zensical](https://github.com/zensical/zensical).
43
+
44
+ ## Demo and Documentation
45
+
46
+ Demo and documentation can be found at [https://tylernguyen.github.io/pymdownx-mahjong/](https://tylernguyen.github.io/pymdownx-mahjong/).
41
47
 
42
48
  ## License
43
49
 
@@ -1,11 +1,12 @@
1
- pymdownx_mahjong/__init__.py,sha256=3w2615_eJnY6sN3k_hBgEJue3dRFY13KhVZmHBHLPNk,644
2
- pymdownx_mahjong/extension.py,sha256=aCRMkMQ8c0eenpcyR5eyLdN_OND9JpYFeRxodOeJyGo,6688
3
- pymdownx_mahjong/inline.py,sha256=2yQ9mM1qLFIin_2UBI_Kml8DOAN6J2jsgGri1ImHc4g,2429
4
- pymdownx_mahjong/parser.py,sha256=gw4_ZQUHviWHAr0DmnTVFoKUID_dElCqxpMgl7m9cMA,11217
5
- pymdownx_mahjong/renderer.py,sha256=xFwzjVQbqFrMnXmaKMwa3-9tXbTKgXmpkIuEK3VMhmg,16814
6
- pymdownx_mahjong/superfences.py,sha256=LXzG6P2EB8dU9A_5ncFbTJEA4Jy6RLwjjYXZS2wSjhs,3099
7
- pymdownx_mahjong/tiles.py,sha256=BD4XlnOfH0Z3_xMhyLDAvxi6tPzMf5j4okPSsw1nzh4,2562
1
+ pymdownx_mahjong/__init__.py,sha256=TTW8ssjfwCA9CCZLi2_ISzBMdiiIC_0ukY2yYGMDbvc,696
2
+ pymdownx_mahjong/extension.py,sha256=HC-S_EUtSOyHHwJ72aDDsfyUGqxVUIwwycKpeap9YqY,6939
3
+ pymdownx_mahjong/inline.py,sha256=Q0_wxP4mR3DOOa3pH4d8EDmffOc9VV0eiyPIKFC5P50,2667
4
+ pymdownx_mahjong/parser.py,sha256=I8wDlZO6oPMhTCqC0NFmsZJOQZCCCXyc78mk3vmmwcQ,12396
5
+ pymdownx_mahjong/renderer.py,sha256=wl_wJSntg-7YGIfD6aVgdI0rokOpr4Qz2oFVWG49BQQ,17522
6
+ pymdownx_mahjong/superfences.py,sha256=7rsTfhSlYZE25uwEJTfzmEDWUSblqvX0jMGnstwh92c,4493
7
+ pymdownx_mahjong/tiles.py,sha256=ltA1xJeS9fVcZQyUwaIjyl1mHEZ0Kr_QUKy1jhCapDM,2583
8
8
  pymdownx_mahjong/utils.py,sha256=aHKX4wDCGM_rvaOwFIbd9qVJHpzzUjXWMviLxdwZrdo,2637
9
+ pymdownx_mahjong/assets/README.md,sha256=HDNhZZlt8ffTER1fVNcTayUnFqOmB3tXoocT8iZgCz0,179
9
10
  pymdownx_mahjong/assets/dark/0m.svg,sha256=wTa3OxyZziCtsk5hqLpLJig-1IbQPo_XrMrox5wZFzU,18773
10
11
  pymdownx_mahjong/assets/dark/0p.svg,sha256=Ge4V_vOYTJtqSOI89YmxP451WsNOL3Pv_A8-apdYrI8,23308
11
12
  pymdownx_mahjong/assets/dark/0s.svg,sha256=mErmM8konoHN5XlNwuffYEJNczrawQo1800kQlAg1VA,32913
@@ -86,9 +87,9 @@ pymdownx_mahjong/assets/light/9s.svg,sha256=WjNpR3IUty3ccnEwq9CpcSbLr3A-ta0yJqWx
86
87
  pymdownx_mahjong/assets/light/back.svg,sha256=HPAJfchslm_1uZQzpY8FUNF2s8hmbARGkhxNMlfnkQk,11245
87
88
  pymdownx_mahjong/assets/light/blank.svg,sha256=uKGioQLfrCZsSbMr66iDXv4w9lKHEqGym1l098-q5DM,8499
88
89
  pymdownx_mahjong/assets/light/front.svg,sha256=63TB5953_-10TDRd7K_kJnHoqt-6wd05MEGV2AcNsks,11906
89
- pymdownx_mahjong/css/mahjong.css,sha256=z-AuTffSmu4_Rrh8VnoYjx8ZHL2DV-8ATbNKdoTg4Bc,9336
90
- pymdownx_mahjong-1.0.0.dist-info/METADATA,sha256=fS6tItQex0QDQakUjZa6n9skBTrhgIR8NTiBQGIOx7c,1871
91
- pymdownx_mahjong-1.0.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
92
- pymdownx_mahjong-1.0.0.dist-info/entry_points.txt,sha256=dEty6XpsYCWl45PeRYHiH9ORxwSZA64LQU8gWF1CuyU,72
93
- pymdownx_mahjong-1.0.0.dist-info/licenses/LICENSE,sha256=Mnpx_G3eVz7AX5uTHGPaZWHJYiJYu4Y2_l01PsksHdg,19921
94
- pymdownx_mahjong-1.0.0.dist-info/RECORD,,
90
+ pymdownx_mahjong/css/mahjong.css,sha256=OpO9RR-UPFAbPF6Q8Wuz0Ak_Pg9rOrOsMAtnP0oj5LE,9307
91
+ pymdownx_mahjong-1.2.0.dist-info/METADATA,sha256=S4NGo2aiURkBQXW-Z_oll-1cMjq4W8grUkzjLe1SP6Y,2222
92
+ pymdownx_mahjong-1.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
93
+ pymdownx_mahjong-1.2.0.dist-info/entry_points.txt,sha256=dEty6XpsYCWl45PeRYHiH9ORxwSZA64LQU8gWF1CuyU,72
94
+ pymdownx_mahjong-1.2.0.dist-info/licenses/LICENSE,sha256=Mnpx_G3eVz7AX5uTHGPaZWHJYiJYu4Y2_l01PsksHdg,19921
95
+ pymdownx_mahjong-1.2.0.dist-info/RECORD,,