markdown_convert 1.2.49__py3-none-any.whl → 1.2.51__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,8 +4,8 @@ available to the user.
4
4
  Author: @julynx
5
5
  """
6
6
 
7
- from .modules.convert import convert, live_convert, convert_text
8
7
  from .__main__ import main
8
+ from .modules.convert import convert, convert_text, live_convert
9
9
 
10
10
  __version__ = "1.2.15"
11
11
  __all__ = ["convert", "live_convert", "convert_text", "main"]
@@ -8,7 +8,8 @@ Author: @julynx
8
8
  from sys import exit as sys_exit
9
9
 
10
10
  from argsdict import args
11
- from .modules.constants import RED, OPTIONS, OPTIONS_MODES
11
+
12
+ from .modules.constants import OPTIONS, OPTIONS_MODES, RED
12
13
  from .modules.convert import convert, live_convert
13
14
  from .modules.resources import get_css_path, get_output_path, get_usage
14
15
  from .modules.utils import color
@@ -75,7 +76,6 @@ def main():
75
76
 
76
77
  sys_exit(0)
77
78
 
78
- # pylint: disable=W0718
79
79
  except Exception as err:
80
80
  asked_for_help = "--help" in arg or "-h" in arg
81
81
  show_usage = isinstance(err, (IndexError, ValueError)) or asked_for_help
@@ -24,6 +24,17 @@
24
24
  --color-border-light: #ccc;
25
25
  /* Hyperlinks */
26
26
  --color-links: #09f;
27
+
28
+ /* Note */
29
+ --color-ad-note: oklch(70% 0.1 238.77);
30
+ /* Tip */
31
+ --color-ad-tip: oklch(70% 0.1 142.605);
32
+ /* Important */
33
+ --color-ad-important: oklch(70% 0.1 284.67);
34
+ /* Warning */
35
+ --color-ad-warning: oklch(70% 0.1 74.25);
36
+ /* Caution */
37
+ --color-ad-caution: oklch(70% 0.1 14.4);
27
38
  }
28
39
 
29
40
  /* Document */
@@ -312,9 +323,9 @@ hr:has(+ hr) {
312
323
 
313
324
  hr+hr {
314
325
  clear: both;
315
- break-after: always;
316
- page-break-after: always;
317
326
  visibility: hidden;
327
+ page-break-after: always;
328
+ break-after: always;
318
329
  }
319
330
 
320
331
  section,
@@ -432,4 +443,83 @@ math {
432
443
  .justify {
433
444
  display: block;
434
445
  text-align: justify;
446
+ }
447
+
448
+ /* Admonitions */
449
+ .admonition {
450
+ padding: 0.5rem 1rem;
451
+ margin-bottom: 1rem;
452
+ border-left: 0.25rem solid;
453
+ border-radius: 0.3rem;
454
+ }
455
+
456
+ .admonition header {
457
+ display: flex;
458
+ align-items: center;
459
+ gap: 0.25rem;
460
+ margin-bottom: 0.25rem;
461
+ font-weight: bold;
462
+ }
463
+
464
+ .admonition strong {
465
+ text-transform: capitalize;
466
+ }
467
+
468
+ .admonition em:not(:empty)::before {
469
+ content: ": ";
470
+ font-weight: bold;
471
+ }
472
+
473
+ .admonition em {
474
+ font-weight: 600;
475
+ font-style: normal;
476
+ }
477
+
478
+ .admonition p {
479
+ margin: 0;
480
+ }
481
+
482
+ .admonition.note {
483
+ border-left-color: var(--color-ad-note);
484
+ background-color: oklch(from var(--color-ad-note) 0.97 0.015 h);
485
+ }
486
+
487
+ .admonition.note strong {
488
+ color: var(--color-ad-note);
489
+ }
490
+
491
+ .admonition.tip {
492
+ border-left-color: var(--color-ad-tip);
493
+ background-color: oklch(from var(--color-ad-tip) 0.97 0.015 h);
494
+ }
495
+
496
+ .admonition.tip strong {
497
+ color: var(--color-ad-tip);
498
+ }
499
+
500
+ .admonition.important {
501
+ border-left-color: var(--color-ad-important);
502
+ background-color: oklch(from var(--color-ad-important) 0.97 0.015 h);
503
+ }
504
+
505
+ .admonition.important strong {
506
+ color: var(--color-ad-important);
507
+ }
508
+
509
+ .admonition.warning {
510
+ border-left-color: var(--color-ad-warning);
511
+ background-color: oklch(from var(--color-ad-warning) 0.97 0.015 h);
512
+ }
513
+
514
+ .admonition.warning strong {
515
+ color: var(--color-ad-warning);
516
+ }
517
+
518
+ .admonition.caution {
519
+ border-left-color: var(--color-ad-caution);
520
+ background-color: oklch(from var(--color-ad-caution) 0.97 0.015 h);
521
+ }
522
+
523
+ .admonition.caution strong {
524
+ color: var(--color-ad-caution);
435
525
  }
@@ -0,0 +1,71 @@
1
+ """
2
+ Autoinstall chromium headless browser for playwright.
3
+ """
4
+
5
+ import os
6
+
7
+ from install_playwright import install
8
+ from playwright.sync_api import sync_playwright
9
+
10
+ from .constants import GREEN, RED
11
+ from .utils import color
12
+
13
+
14
+ def ensure_chromium(loud=True):
15
+ """
16
+ Ensures the chromium playwright browser is installed.
17
+ If not, tries to install it.
18
+ """
19
+ with sync_playwright() as playwright:
20
+ if is_browser_installed(playwright.chromium):
21
+ return
22
+
23
+ if loud:
24
+ print(
25
+ "The Playwright Chromium browser was not found."
26
+ " Attempting to install it, please wait..."
27
+ )
28
+
29
+ try:
30
+ result = install([playwright.chromium])
31
+ if not result:
32
+ result = install([playwright.chromium], with_deps=True)
33
+
34
+ if loud:
35
+ if result:
36
+ print(
37
+ color(
38
+ GREEN,
39
+ "The Playwright Chromium browser was successfully installed.\n",
40
+ )
41
+ )
42
+ else:
43
+ print(
44
+ color(
45
+ RED,
46
+ "ERROR: The Playwright Chromium browser could not be automatically installed."
47
+ "\nPlease manually run 'playwright install' and try again.\n",
48
+ )
49
+ )
50
+
51
+ return result
52
+
53
+ except Exception as exc:
54
+ print(
55
+ color(
56
+ RED,
57
+ f"ERROR: There was an exception while trying to install the Playwright Chromium browser:\n{exc}",
58
+ )
59
+ )
60
+ return False
61
+
62
+
63
+ def is_browser_installed(browser):
64
+ """
65
+ Checks if a specific browser is installed by verifying its executable path.
66
+ Browser type can be 'chromium', 'firefox', or 'webkit'.
67
+ """
68
+ try:
69
+ return os.path.exists(browser.executable_path)
70
+ except Exception:
71
+ return False
@@ -22,6 +22,7 @@ MARKDOWN_EXTENSIONS = {
22
22
  "latex": True,
23
23
  "mermaid": None,
24
24
  "strike": None,
25
+ "admonitions": None,
25
26
  }
26
27
 
27
28
  BROWSER_ARGS = [
@@ -12,13 +12,19 @@ from pathlib import Path
12
12
  import markdown2
13
13
  from playwright.sync_api import sync_playwright
14
14
 
15
- from .constants import MARKDOWN_EXTENSIONS, BROWSER_ARGS, CSP_TEMPLATE, PDF_PARAMS
15
+ from .autoinstall import ensure_chromium
16
+ from .constants import (
17
+ BROWSER_ARGS,
18
+ CSP_TEMPLATE,
19
+ MARKDOWN_EXTENSIONS,
20
+ PDF_PARAMS,
21
+ )
16
22
  from .resources import get_code_css_path, get_css_path, get_output_path
17
23
  from .transform import (
18
- create_sections,
19
- render_mermaid_diagrams,
20
24
  create_html_document,
25
+ create_sections,
21
26
  render_extra_features,
27
+ render_mermaid_diagrams,
22
28
  )
23
29
  from .utils import drop_duplicates
24
30
 
@@ -50,6 +56,7 @@ def _generate_pdf_with_playwright(
50
56
  csp = CSP_TEMPLATE.format(nonce=nonce)
51
57
  full_html = create_html_document(html_content, css_content, csp)
52
58
 
59
+ ensure_chromium()
53
60
  with sync_playwright() as playwright:
54
61
  browser = playwright.chromium.launch(headless=True, args=BROWSER_ARGS)
55
62
  context = browser.new_context(
@@ -0,0 +1,100 @@
1
+ """
2
+ Extras are defined as helper functions called by
3
+ render_extra_features from transform.py
4
+ """
5
+
6
+
7
+ def create_checkbox(soup, match):
8
+ """
9
+ Render a tag for a checkbox.
10
+
11
+ Args:
12
+ soup: HTML beautifulsoup
13
+ match: Element identified as a checkbox
14
+ Returns:
15
+ tag: Beautifulsoup tag representing the checkbox
16
+ """
17
+ tag = soup.new_tag("input", type="checkbox")
18
+ if "[x]" in match.group("checkbox"):
19
+ tag["checked"] = ""
20
+ return tag
21
+
22
+
23
+ def create_highlight(soup, match):
24
+ """
25
+ Render a tag for a highlight.
26
+
27
+ Args:
28
+ soup: HTML beautifulsoup
29
+ match: Element identified as a highlight
30
+ Returns:
31
+ tag: Beautifulsoup tag representing the highlight
32
+ """
33
+ tag = soup.new_tag("span", attrs={"class": "highlight"})
34
+ tag.string = match.group("hl_content")
35
+ return tag
36
+
37
+
38
+ def create_custom_span(soup, match):
39
+ """
40
+ Render a tag for a custom span.
41
+
42
+ Args:
43
+ soup: HTML beautifulsoup
44
+ match: Element identified as a custom span
45
+ Returns:
46
+ tag: Beautifulsoup tag representing the custom span
47
+ """
48
+ tag = soup.new_tag("span", attrs={"class": match.group("cls")})
49
+ tag.string = match.group("sp_content")
50
+ return tag
51
+
52
+
53
+ def create_toc(soup, match):
54
+ """
55
+ Render a tag for a table of contents
56
+
57
+ Args:
58
+ soup: HTML beautifulsoup
59
+ match: Element identified as a table of contents
60
+ Returns:
61
+ tag: Beautifulsoup tag representing the table of contents
62
+ """
63
+ max_level = match.group("depth")
64
+ max_level = 3 if max_level is None else int(max_level)
65
+
66
+ headers = [
67
+ header
68
+ for header in soup.find_all([f"h{index}" for index in range(1, max_level + 1)])
69
+ if header.get("id")
70
+ ]
71
+ if not headers:
72
+ return ""
73
+
74
+ tag = soup.new_tag("ul", attrs={"class": "toc"})
75
+ active_list = {0: tag}
76
+ last_list_element = {}
77
+
78
+ for header in headers:
79
+ level = int(header.name[1])
80
+
81
+ if level not in active_list:
82
+ parent_lvl = max(key for key in active_list if key < level)
83
+ if last_list_element.get(parent_lvl):
84
+ sub_list = soup.new_tag("ul")
85
+ last_list_element[parent_lvl].append(sub_list)
86
+ active_list[level] = sub_list
87
+ else:
88
+ active_list[level] = active_list[parent_lvl]
89
+
90
+ active_list = {key: value for key, value in active_list.items() if key <= level}
91
+
92
+ list_item = soup.new_tag("li")
93
+ link = soup.new_tag("a", href=f"#{header['id']}")
94
+ link.string = header.get_text(strip=True)
95
+ list_item.append(link)
96
+
97
+ active_list[level].append(list_item)
98
+ last_list_element[level] = list_item
99
+
100
+ return tag
@@ -13,7 +13,7 @@ except ImportError:
13
13
  # Fallback for older Python versions
14
14
  from importlib_resources import files
15
15
 
16
- from .constants import BLUE, CYAN, GREEN, YELLOW, OPTIONS, OPTIONS_MODES
16
+ from .constants import BLUE, CYAN, GREEN, OPTIONS, OPTIONS_MODES, YELLOW
17
17
  from .utils import color
18
18
 
19
19
 
@@ -6,6 +6,10 @@ import re
6
6
 
7
7
  from bs4 import BeautifulSoup
8
8
 
9
+ from .constants import YELLOW
10
+ from .extras import create_checkbox, create_custom_span, create_highlight, create_toc
11
+ from .utils import color
12
+
9
13
 
10
14
  def create_html_document(html_content, css_content, csp):
11
15
  """
@@ -34,8 +38,8 @@ def create_html_document(html_content, css_content, csp):
34
38
 
35
39
  def create_sections(html_string):
36
40
  """
37
- Wraps each h2 or h3 and its following content in a <section> tag.
38
- The section ends when the next h2 or h3 is encountered, or the parent ends.
41
+ Wraps each h2 and its following content in a <section> tag.
42
+ The section ends when the next h2 is encountered, or the parent ends.
39
43
 
40
44
  Args:
41
45
  html_string (str): The input HTML string.
@@ -44,14 +48,12 @@ def create_sections(html_string):
44
48
  """
45
49
  soup = BeautifulSoup(html_string, "html.parser")
46
50
 
47
- for header in soup.find_all(["h2", "h3"]):
51
+ for header in soup.find_all("h2"):
48
52
  new_section = soup.new_tag("section")
49
53
  header.insert_before(new_section)
50
54
 
51
55
  current = header
52
- while current is not None and (
53
- current == header or current.name not in ["h2", "h3"]
54
- ):
56
+ while current is not None and (current == header or current.name != "h2"):
55
57
  next_sibling = current.next_sibling
56
58
  new_section.append(current)
57
59
  current = next_sibling
@@ -97,32 +99,18 @@ def render_extra_features(html):
97
99
  str: HTML content with extra features rendered.
98
100
  """
99
101
 
100
- def _create_checkbox(soup, match):
101
- tag = soup.new_tag("input", type="checkbox")
102
- if "[x]" in match.group("checkbox"):
103
- tag["checked"] = ""
104
- return tag
105
-
106
- def _create_highlight(soup, match):
107
- tag = soup.new_tag("span", attrs={"class": "highlight"})
108
- tag.string = match.group("hl_content")
109
- return tag
110
-
111
- def _create_custom_span(soup, match):
112
- tag = soup.new_tag("span", attrs={"class": match.group("cls")})
113
- tag.string = match.group("sp_content")
114
- return tag
115
-
116
102
  handlers = {
117
- "checkbox": _create_checkbox,
118
- "highlight": _create_highlight,
119
- "span": _create_custom_span,
103
+ "checkbox": create_checkbox,
104
+ "highlight": create_highlight,
105
+ "span": create_custom_span,
106
+ "toc": create_toc,
120
107
  }
121
108
 
122
109
  master_pattern = re.compile(
123
110
  r"(?P<checkbox>\[\s\]|\[x\])|"
124
111
  r"(?P<highlight>==(?P<hl_content>.*?)==)|"
125
- r"(?P<span>(?P<cls>[a-zA-Z0-9_-]+)\{\{\s*(?P<sp_content>.*?)\s*\}\})"
112
+ r"(?P<span>(?P<cls>[a-zA-Z0-9_-]+)\{\{\s*(?P<sp_content>.*?)\s*\}\})|"
113
+ r"(?P<toc>\[TOC(?:\s+depth=(?P<depth>\d+))?\])"
126
114
  )
127
115
 
128
116
  ignored_tags = {"code", "pre", "script", "style"}
@@ -156,7 +144,12 @@ def render_extra_features(html):
156
144
  tag = handler(soup, match)
157
145
  new_nodes.append(tag)
158
146
  except Exception as exc:
159
- print(f"Warning: Handler for '{kind}' failed with exception: {exc}")
147
+ print(
148
+ color(
149
+ YELLOW,
150
+ f"WARNING: Handler for '{kind}' failed with exception: {exc}",
151
+ )
152
+ )
160
153
  new_nodes.append(match.group(0))
161
154
 
162
155
  last_end = end
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: markdown_convert
3
- Version: 1.2.49
3
+ Version: 1.2.51
4
4
  Summary: Convert Markdown files to PDF from your command line.
5
5
  Project-URL: homepage, https://github.com/Julynx/markdown_convert
6
6
  Author-email: Julio Cabria <juliocabria@tutanota.com>
@@ -12,6 +12,7 @@ Classifier: Programming Language :: Python :: 3
12
12
  Requires-Python: <3.15,>=3.11
13
13
  Requires-Dist: argsdict==1.0.0
14
14
  Requires-Dist: beautifulsoup4>=4.14.3
15
+ Requires-Dist: install-playwright>=1.0.0
15
16
  Requires-Dist: latex2mathml>=3.78.1
16
17
  Requires-Dist: markdown2<3,>=2.4.13
17
18
  Requires-Dist: playwright>=1.57.0
@@ -0,0 +1,18 @@
1
+ markdown_convert/__init__.py,sha256=0hLMtJnCIuApqopx5P4tiDSw850AmnuVcohmAbPVEZ4,303
2
+ markdown_convert/__main__.py,sha256=AocRo1iF1El_-Uo0owJ-QLbJUF0rum5R_AlNrTTTSOQ,2780
3
+ markdown_convert/code.css,sha256=Wt4FqFqJcpT-jwY3GN-o4ZRCCXU8DQj-9lqKdGiuoyw,4935
4
+ markdown_convert/default.css,sha256=XmIR6Kx4evwmLTZr9QZc3XhDj4jxjmGkwaeftfvHNmU,8149
5
+ markdown_convert/modules/__init__.py,sha256=PFPgiQhMXgyfjD8BkfLC_X8AR1jz-dCxfif2qmNofJs,65
6
+ markdown_convert/modules/autoinstall.py,sha256=Tnrde6MIcO11PWT7GZwhs_QTVRy6CSpUB_gIi9G5ve8,2063
7
+ markdown_convert/modules/constants.py,sha256=FA8DrQa9nzTUIJFXwXrK-AuOc5_ToGSFaD4sJqsnAjU,1305
8
+ markdown_convert/modules/convert.py,sha256=1AjQfnOXJoxKyfqr4misDuTvE4YXnwaoWw668FUHiEQ,8972
9
+ markdown_convert/modules/extras.py,sha256=GwNx6nseztHOWExcYmovxomdvOs078dMeknQTwzTCJo,2730
10
+ markdown_convert/modules/resources.py,sha256=eskLLbrkLJWs-vqtCLq4qV2Hjy6XeGFCUdT0VN2b_tA,2488
11
+ markdown_convert/modules/transform.py,sha256=e4QllWx5BYKEQqIzOkYigtxcSAWqSUHsoKkvqzYzEpY,4567
12
+ markdown_convert/modules/utils.py,sha256=NX0WegM8e8MPKNNmweTujAWO8ZghdB8LSGDx20K2E44,655
13
+ markdown_convert/modules/validate.py,sha256=XV_k7cHeifEKDaltF26tCmabs2-Me5msP3enI_eVwfA,1517
14
+ markdown_convert-1.2.51.dist-info/METADATA,sha256=sejp1Y3EzxGpfMqDxIJsieDaxkO9_o0gFffs7ULpwKw,4118
15
+ markdown_convert-1.2.51.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
16
+ markdown_convert-1.2.51.dist-info/entry_points.txt,sha256=RCmzC7C0sX-SpzIP2Cr34rhg3lMd7BRx-exaZPfK8bU,68
17
+ markdown_convert-1.2.51.dist-info/licenses/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
18
+ markdown_convert-1.2.51.dist-info/RECORD,,
@@ -1,16 +0,0 @@
1
- markdown_convert/__init__.py,sha256=ysW3pXsDGGK4PzZHcIBTpfVW58IkDUwHffDkf_GM6UU,303
2
- markdown_convert/__main__.py,sha256=w6sHfJcJQpMOERPqiNoKgoRM38YUwK22eGBmVkpZj1g,2807
3
- markdown_convert/code.css,sha256=Wt4FqFqJcpT-jwY3GN-o4ZRCCXU8DQj-9lqKdGiuoyw,4935
4
- markdown_convert/default.css,sha256=ZL2f-SQIM2BG1sGD51wRqTN06K2rnIYyELXdJQq1xRs,6355
5
- markdown_convert/modules/__init__.py,sha256=PFPgiQhMXgyfjD8BkfLC_X8AR1jz-dCxfif2qmNofJs,65
6
- markdown_convert/modules/constants.py,sha256=eUeIFRxZerP3E0Rgp_Nsl0Q38IBELwca5UO3fbUcxRA,1280
7
- markdown_convert/modules/convert.py,sha256=4UEZhx_v4lqcgTYeCiQ3V4Bkxz4siHCKQIHGvHICHuI,8888
8
- markdown_convert/modules/resources.py,sha256=tnW8JmCrJNBRbzOcaOVG6GX5jPC8Kzj3dA7gX0B935A,2488
9
- markdown_convert/modules/transform.py,sha256=_Pz-t_7g_n_IwtHtc386WDugkw6CfDpdgw_aFgiSM4Q,4796
10
- markdown_convert/modules/utils.py,sha256=NX0WegM8e8MPKNNmweTujAWO8ZghdB8LSGDx20K2E44,655
11
- markdown_convert/modules/validate.py,sha256=XV_k7cHeifEKDaltF26tCmabs2-Me5msP3enI_eVwfA,1517
12
- markdown_convert-1.2.49.dist-info/METADATA,sha256=PkXwu7z2NarPFUwveuXstMJ-I2jQfdF8WzMZ9LwfZfA,4077
13
- markdown_convert-1.2.49.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
14
- markdown_convert-1.2.49.dist-info/entry_points.txt,sha256=RCmzC7C0sX-SpzIP2Cr34rhg3lMd7BRx-exaZPfK8bU,68
15
- markdown_convert-1.2.49.dist-info/licenses/LICENSE,sha256=gXf5dRMhNSbfLPYYTY_5hsZ1r7UU1OaKQEAQUhuIBkM,18092
16
- markdown_convert-1.2.49.dist-info/RECORD,,