markdown-to-confluence 0.2.1__tar.gz → 0.2.2__tar.gz

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.
Files changed (30) hide show
  1. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/PKG-INFO +25 -7
  2. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/README.md +24 -6
  3. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/markdown_to_confluence.egg-info/PKG-INFO +25 -7
  4. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/markdown_to_confluence.egg-info/SOURCES.txt +3 -0
  5. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/md2conf/__init__.py +1 -1
  6. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/md2conf/application.py +9 -6
  7. markdown_to_confluence-0.2.2/md2conf/matcher.py +83 -0
  8. markdown_to_confluence-0.2.2/md2conf/mermaid.py +76 -0
  9. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/md2conf/processor.py +8 -5
  10. markdown_to_confluence-0.2.2/md2conf/puppeteer-config.json +8 -0
  11. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/setup.cfg +1 -0
  12. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/tests/test_conversion.py +6 -1
  13. markdown_to_confluence-0.2.2/tests/test_matcher.py +43 -0
  14. markdown_to_confluence-0.2.1/md2conf/mermaid.py +0 -54
  15. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/LICENSE +0 -0
  16. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/markdown_to_confluence.egg-info/dependency_links.txt +0 -0
  17. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/markdown_to_confluence.egg-info/entry_points.txt +0 -0
  18. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/markdown_to_confluence.egg-info/requires.txt +0 -0
  19. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/markdown_to_confluence.egg-info/top_level.txt +0 -0
  20. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/markdown_to_confluence.egg-info/zip-safe +0 -0
  21. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/md2conf/__main__.py +0 -0
  22. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/md2conf/api.py +0 -0
  23. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/md2conf/converter.py +0 -0
  24. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/md2conf/entities.dtd +0 -0
  25. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/md2conf/properties.py +0 -0
  26. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/md2conf/py.typed +0 -0
  27. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/pyproject.toml +0 -0
  28. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/setup.py +0 -0
  29. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/tests/test_mermaid.py +0 -0
  30. {markdown_to_confluence-0.2.1 → markdown_to_confluence-0.2.2}/tests/test_processor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: markdown-to-confluence
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Publish Markdown files to Confluence wiki
5
5
  Home-page: https://github.com/hunyadi/md2conf
6
6
  Author: Levente Hunyadi
@@ -144,6 +144,12 @@ Provide generated-by prompt text in the Markdown file with a tag:
144
144
 
145
145
  Alternatively, use the `--generated-by GENERATED_BY` option. The tag takes precedence.
146
146
 
147
+ ### Ignoring files
148
+
149
+ Skip files in a directory with rules defined in `.mdignore`. Each rule should occupy a single line. Rules follow the syntax of [fnmatch](https://docs.python.org/3/library/fnmatch.html#fnmatch.fnmatch). Specifically, `?` matches any single character, and `*` matches zero or more characters. For example, use `up-*.md` to exclude Markdown files that start with `up-`. Lines that start with `#` are treated as comments.
150
+
151
+ Files that don't have the extension `*.md` are skipped automatically. Hidden directories (whose name starts with `.`) are not recursed into.
152
+
147
153
  ### Running the tool
148
154
 
149
155
  You execute the command-line tool `md2conf` to synchronize the Markdown file with Confluence:
@@ -195,18 +201,30 @@ options:
195
201
  --webui-links Enable Confluence Web UI links.
196
202
  ```
197
203
 
198
- ### Using the docker container
204
+ ### Using the Docker container
205
+
206
+ You can run the Docker container via `docker run` or via `Dockerfile`. Either can accept the environment variables or arguments similar to the Python options. The final argument `./` corresponds to `mdpath` in the command-line utility.
207
+
208
+ With `docker run`, you can pass Confluence domain, user, API and space key directly to `docker run`:
199
209
 
200
- You can run the docker container via `docker run` or via `Dockerfile`. Either can accept the environment variables or arguments similar to the Python options. The final argument `./` corresponds to `mdpath` in the command-line utility.
210
+ ```sh
211
+ docker run --rm --name md2conf -v $(pwd):/data leventehunyadi/md2conf -d instructure.atlassian.net -u levente.hunyadi@instructure.com -a 0123456789abcdef -s DAP ./
212
+ ```
213
+
214
+ Alternatively, you can use a separate file `.env` to pass these parameters as environment variables:
201
215
 
202
216
  ```sh
203
- docker run --rm --name md2conf hunyadi/md2conf -d instructure.atlassian.net -u levente.hunyadi@instructure.com -a 0123456789abcdef -s DAP ./
217
+ docker run --rm --env-file .env --name md2conf -v $(pwd):/data leventehunyadi/md2conf ./
204
218
  ```
205
219
 
206
- Note that the entry point for the docker container's base image is `ENTRYPOINT ["python3", "-m", "md2conf"]`.
220
+ In each case, `-v $(pwd):/data` maps the current directory to Docker container's `WORKDIR` such *md2conf* can scan files and directories in the local file system.
221
+
222
+ Note that the entry point for the Docker container's base image is `ENTRYPOINT ["python3", "-m", "md2conf"]`.
223
+
224
+ With the `Dockerfile` approach, you can extend the base image:
207
225
 
208
226
  ```Dockerfile
209
- FROM hunyadi/md2conf:latest
227
+ FROM leventehunyadi/md2conf:latest
210
228
 
211
229
  ENV CONFLUENCE_DOMAIN='instructure.atlassian.net'
212
230
  ENV CONFLUENCE_PATH='/wiki/'
@@ -220,7 +238,7 @@ CMD ["./"]
220
238
  Alternatively,
221
239
 
222
240
  ```Dockerfile
223
- FROM hunyadi/md2conf:latest
241
+ FROM leventehunyadi/md2conf:latest
224
242
 
225
243
  CMD ["-d", "instructure.atlassian.net", "-u", "levente.hunyadi@instructure.com", "-a", "0123456789abcdef", "-s", "DAP", "./"]
226
244
  ```
@@ -113,6 +113,12 @@ Provide generated-by prompt text in the Markdown file with a tag:
113
113
 
114
114
  Alternatively, use the `--generated-by GENERATED_BY` option. The tag takes precedence.
115
115
 
116
+ ### Ignoring files
117
+
118
+ Skip files in a directory with rules defined in `.mdignore`. Each rule should occupy a single line. Rules follow the syntax of [fnmatch](https://docs.python.org/3/library/fnmatch.html#fnmatch.fnmatch). Specifically, `?` matches any single character, and `*` matches zero or more characters. For example, use `up-*.md` to exclude Markdown files that start with `up-`. Lines that start with `#` are treated as comments.
119
+
120
+ Files that don't have the extension `*.md` are skipped automatically. Hidden directories (whose name starts with `.`) are not recursed into.
121
+
116
122
  ### Running the tool
117
123
 
118
124
  You execute the command-line tool `md2conf` to synchronize the Markdown file with Confluence:
@@ -164,18 +170,30 @@ options:
164
170
  --webui-links Enable Confluence Web UI links.
165
171
  ```
166
172
 
167
- ### Using the docker container
173
+ ### Using the Docker container
174
+
175
+ You can run the Docker container via `docker run` or via `Dockerfile`. Either can accept the environment variables or arguments similar to the Python options. The final argument `./` corresponds to `mdpath` in the command-line utility.
176
+
177
+ With `docker run`, you can pass Confluence domain, user, API and space key directly to `docker run`:
168
178
 
169
- You can run the docker container via `docker run` or via `Dockerfile`. Either can accept the environment variables or arguments similar to the Python options. The final argument `./` corresponds to `mdpath` in the command-line utility.
179
+ ```sh
180
+ docker run --rm --name md2conf -v $(pwd):/data leventehunyadi/md2conf -d instructure.atlassian.net -u levente.hunyadi@instructure.com -a 0123456789abcdef -s DAP ./
181
+ ```
182
+
183
+ Alternatively, you can use a separate file `.env` to pass these parameters as environment variables:
170
184
 
171
185
  ```sh
172
- docker run --rm --name md2conf hunyadi/md2conf -d instructure.atlassian.net -u levente.hunyadi@instructure.com -a 0123456789abcdef -s DAP ./
186
+ docker run --rm --env-file .env --name md2conf -v $(pwd):/data leventehunyadi/md2conf ./
173
187
  ```
174
188
 
175
- Note that the entry point for the docker container's base image is `ENTRYPOINT ["python3", "-m", "md2conf"]`.
189
+ In each case, `-v $(pwd):/data` maps the current directory to Docker container's `WORKDIR` such *md2conf* can scan files and directories in the local file system.
190
+
191
+ Note that the entry point for the Docker container's base image is `ENTRYPOINT ["python3", "-m", "md2conf"]`.
192
+
193
+ With the `Dockerfile` approach, you can extend the base image:
176
194
 
177
195
  ```Dockerfile
178
- FROM hunyadi/md2conf:latest
196
+ FROM leventehunyadi/md2conf:latest
179
197
 
180
198
  ENV CONFLUENCE_DOMAIN='instructure.atlassian.net'
181
199
  ENV CONFLUENCE_PATH='/wiki/'
@@ -189,7 +207,7 @@ CMD ["./"]
189
207
  Alternatively,
190
208
 
191
209
  ```Dockerfile
192
- FROM hunyadi/md2conf:latest
210
+ FROM leventehunyadi/md2conf:latest
193
211
 
194
212
  CMD ["-d", "instructure.atlassian.net", "-u", "levente.hunyadi@instructure.com", "-a", "0123456789abcdef", "-s", "DAP", "./"]
195
213
  ```
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: markdown-to-confluence
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Publish Markdown files to Confluence wiki
5
5
  Home-page: https://github.com/hunyadi/md2conf
6
6
  Author: Levente Hunyadi
@@ -144,6 +144,12 @@ Provide generated-by prompt text in the Markdown file with a tag:
144
144
 
145
145
  Alternatively, use the `--generated-by GENERATED_BY` option. The tag takes precedence.
146
146
 
147
+ ### Ignoring files
148
+
149
+ Skip files in a directory with rules defined in `.mdignore`. Each rule should occupy a single line. Rules follow the syntax of [fnmatch](https://docs.python.org/3/library/fnmatch.html#fnmatch.fnmatch). Specifically, `?` matches any single character, and `*` matches zero or more characters. For example, use `up-*.md` to exclude Markdown files that start with `up-`. Lines that start with `#` are treated as comments.
150
+
151
+ Files that don't have the extension `*.md` are skipped automatically. Hidden directories (whose name starts with `.`) are not recursed into.
152
+
147
153
  ### Running the tool
148
154
 
149
155
  You execute the command-line tool `md2conf` to synchronize the Markdown file with Confluence:
@@ -195,18 +201,30 @@ options:
195
201
  --webui-links Enable Confluence Web UI links.
196
202
  ```
197
203
 
198
- ### Using the docker container
204
+ ### Using the Docker container
205
+
206
+ You can run the Docker container via `docker run` or via `Dockerfile`. Either can accept the environment variables or arguments similar to the Python options. The final argument `./` corresponds to `mdpath` in the command-line utility.
207
+
208
+ With `docker run`, you can pass Confluence domain, user, API and space key directly to `docker run`:
199
209
 
200
- You can run the docker container via `docker run` or via `Dockerfile`. Either can accept the environment variables or arguments similar to the Python options. The final argument `./` corresponds to `mdpath` in the command-line utility.
210
+ ```sh
211
+ docker run --rm --name md2conf -v $(pwd):/data leventehunyadi/md2conf -d instructure.atlassian.net -u levente.hunyadi@instructure.com -a 0123456789abcdef -s DAP ./
212
+ ```
213
+
214
+ Alternatively, you can use a separate file `.env` to pass these parameters as environment variables:
201
215
 
202
216
  ```sh
203
- docker run --rm --name md2conf hunyadi/md2conf -d instructure.atlassian.net -u levente.hunyadi@instructure.com -a 0123456789abcdef -s DAP ./
217
+ docker run --rm --env-file .env --name md2conf -v $(pwd):/data leventehunyadi/md2conf ./
204
218
  ```
205
219
 
206
- Note that the entry point for the docker container's base image is `ENTRYPOINT ["python3", "-m", "md2conf"]`.
220
+ In each case, `-v $(pwd):/data` maps the current directory to Docker container's `WORKDIR` such *md2conf* can scan files and directories in the local file system.
221
+
222
+ Note that the entry point for the Docker container's base image is `ENTRYPOINT ["python3", "-m", "md2conf"]`.
223
+
224
+ With the `Dockerfile` approach, you can extend the base image:
207
225
 
208
226
  ```Dockerfile
209
- FROM hunyadi/md2conf:latest
227
+ FROM leventehunyadi/md2conf:latest
210
228
 
211
229
  ENV CONFLUENCE_DOMAIN='instructure.atlassian.net'
212
230
  ENV CONFLUENCE_PATH='/wiki/'
@@ -220,7 +238,7 @@ CMD ["./"]
220
238
  Alternatively,
221
239
 
222
240
  ```Dockerfile
223
- FROM hunyadi/md2conf:latest
241
+ FROM leventehunyadi/md2conf:latest
224
242
 
225
243
  CMD ["-d", "instructure.atlassian.net", "-u", "levente.hunyadi@instructure.com", "-a", "0123456789abcdef", "-s", "DAP", "./"]
226
244
  ```
@@ -16,10 +16,13 @@ md2conf/api.py
16
16
  md2conf/application.py
17
17
  md2conf/converter.py
18
18
  md2conf/entities.dtd
19
+ md2conf/matcher.py
19
20
  md2conf/mermaid.py
20
21
  md2conf/processor.py
21
22
  md2conf/properties.py
23
+ md2conf/puppeteer-config.json
22
24
  md2conf/py.typed
23
25
  tests/test_conversion.py
26
+ tests/test_matcher.py
24
27
  tests/test_mermaid.py
25
28
  tests/test_processor.py
@@ -5,7 +5,7 @@ Parses Markdown files, converts Markdown content into the Confluence Storage For
5
5
  Confluence API endpoints to upload images and content.
6
6
  """
7
7
 
8
- __version__ = "0.2.1"
8
+ __version__ = "0.2.2"
9
9
  __author__ = "Levente Hunyadi"
10
10
  __copyright__ = "Copyright 2022-2024, Levente Hunyadi"
11
11
  __license__ = "MIT"
@@ -13,6 +13,7 @@ from .converter import (
13
13
  extract_qualified_id,
14
14
  read_qualified_id,
15
15
  )
16
+ from .matcher import Matcher, MatcherOptions
16
17
 
17
18
  LOGGER = logging.getLogger(__name__)
18
19
 
@@ -89,16 +90,18 @@ class Application:
89
90
 
90
91
  LOGGER.info(f"Indexing directory: {local_dir}")
91
92
 
93
+ matcher = Matcher(MatcherOptions(source=".mdignore", extension="md"), local_dir)
94
+
92
95
  files: List[Path] = []
93
96
  directories: List[Path] = []
94
97
  for entry in os.scandir(local_dir):
98
+ if matcher.is_excluded(entry.name):
99
+ continue
100
+
95
101
  if entry.is_file():
96
- if entry.name.endswith(".md"):
97
- # skip non-markdown files
98
- files.append((Path(local_dir) / entry.name).absolute())
102
+ files.append((Path(local_dir) / entry.name).absolute())
99
103
  elif entry.is_dir():
100
- if not entry.name.startswith("."):
101
- directories.append((Path(local_dir) / entry.name).absolute())
104
+ directories.append((Path(local_dir) / entry.name).absolute())
102
105
 
103
106
  # make page act as parent node in Confluence
104
107
  parent_id: Optional[ConfluenceQualifiedID] = None
@@ -141,7 +144,7 @@ class Application:
141
144
  else:
142
145
  if parent_id is None:
143
146
  raise ValueError(
144
- "expected: Confluence page ID to act as parent for Markdown files with no linked Confluence page"
147
+ f"expected: parent page ID for Markdown file with no linked Confluence page: {absolute_path}"
145
148
  )
146
149
 
147
150
  confluence_page = self._create_page(
@@ -0,0 +1,83 @@
1
+ import os.path
2
+ from dataclasses import dataclass
3
+ from fnmatch import fnmatch
4
+ from pathlib import Path
5
+ from typing import Iterable, List, Optional
6
+
7
+
8
+ @dataclass
9
+ class MatcherOptions:
10
+ """
11
+ Options for checking against a list of exclude/include patterns.
12
+
13
+ :param source: File name to read exclusion rules from.
14
+ :param extension: Extension to narrow down search to.
15
+ """
16
+
17
+ source: str
18
+ extension: Optional[str] = None
19
+
20
+ def __post_init__(self) -> None:
21
+ if self.extension is not None and not self.extension.startswith("."):
22
+ self.extension = f".{self.extension}"
23
+
24
+
25
+ class Matcher:
26
+ "Compares file and directory names against a list of exclude/include patterns."
27
+
28
+ options: MatcherOptions
29
+ rules: List[str]
30
+
31
+ def __init__(self, options: MatcherOptions, directory: Path) -> None:
32
+ self.options = options
33
+ if os.path.exists(directory / options.source):
34
+ with open(directory / options.source, "r") as f:
35
+ rules = f.read().splitlines()
36
+ self.rules = [rule for rule in rules if rule and not rule.startswith("#")]
37
+ else:
38
+ self.rules = []
39
+
40
+ def extension_matches(self, name: str) -> bool:
41
+ "True if the file name has the expected extension."
42
+
43
+ return self.options.extension is None or name.endswith(self.options.extension)
44
+
45
+ def is_excluded(self, name: str) -> bool:
46
+ "True if the file or directory name matches any of the exclusion patterns."
47
+
48
+ if name.startswith("."):
49
+ return True
50
+
51
+ if not self.extension_matches(name):
52
+ return True
53
+
54
+ for rule in self.rules:
55
+ if fnmatch(name, rule):
56
+ return True
57
+ else:
58
+ return False
59
+
60
+ def is_included(self, name: str) -> bool:
61
+ "True if the file or directory name matches none of the exclusion patterns."
62
+
63
+ return not self.is_excluded(name)
64
+
65
+ def filter(self, items: Iterable[str]) -> List[str]:
66
+ """
67
+ Returns only those elements from the input that don't match any of the exclusion rules.
68
+
69
+ :param items: A list of names to filter.
70
+ :returns: A filtered list of names that didn't match any of the exclusion rules.
71
+ """
72
+
73
+ return [item for item in items if self.is_included(item)]
74
+
75
+ def scandir(self, path: Path) -> List[str]:
76
+ """
77
+ Returns only those entries in a directory whose name doesn't match any of the exclusion rules.
78
+
79
+ :param path: Directory to scan.
80
+ :returns: A filtered list of entries whose name didn't match any of the exclusion rules.
81
+ """
82
+
83
+ return self.filter(entry.name for entry in os.scandir(path))
@@ -0,0 +1,76 @@
1
+ import logging
2
+ import os
3
+ import os.path
4
+ import shutil
5
+ import subprocess
6
+ from typing import Literal
7
+
8
+ LOGGER = logging.getLogger(__name__)
9
+
10
+
11
+ def is_docker() -> bool:
12
+ "True if the application is running in a Docker container."
13
+
14
+ return (
15
+ os.environ.get("CHROME_BIN") == "/usr/bin/chromium-browser"
16
+ and os.environ.get("PUPPETEER_SKIP_DOWNLOAD") == "true"
17
+ )
18
+
19
+
20
+ def get_mmdc() -> str:
21
+ "Path to the Mermaid diagram converter."
22
+
23
+ if is_docker():
24
+ return "/home/md2conf/node_modules/.bin/mmdc"
25
+ elif os.name == "nt":
26
+ return "mmdc.cmd"
27
+ else:
28
+ return "mmdc"
29
+
30
+
31
+ def has_mmdc() -> bool:
32
+ "True if Mermaid diagram converter is available on the OS."
33
+
34
+ executable = get_mmdc()
35
+ return shutil.which(executable) is not None
36
+
37
+
38
+ def render(source: str, output_format: Literal["png", "svg"] = "png") -> bytes:
39
+ "Generates a PNG or SVG image from a Mermaid diagram source."
40
+
41
+ filename = f"tmp_mermaid.{output_format}"
42
+
43
+ cmd = [
44
+ get_mmdc(),
45
+ "--input",
46
+ "-",
47
+ "--output",
48
+ filename,
49
+ "--outputFormat",
50
+ output_format,
51
+ ]
52
+ if is_docker():
53
+ cmd.extend(
54
+ ["-p", os.path.join(os.path.dirname(__file__), "puppeteer-config.json")]
55
+ )
56
+ LOGGER.debug(f"Executing: {' '.join(cmd)}")
57
+ try:
58
+ proc = subprocess.Popen(
59
+ cmd,
60
+ stdout=subprocess.PIPE,
61
+ stdin=subprocess.PIPE,
62
+ stderr=subprocess.PIPE,
63
+ text=False,
64
+ )
65
+ stdout, stderr = proc.communicate(input=source.encode("utf-8"))
66
+ if proc.returncode:
67
+ raise RuntimeError(
68
+ f"failed to convert Mermaid diagram; exit code: {proc.returncode}, "
69
+ f"output:\n{stdout.decode('utf-8')}\n{stderr.decode('utf-8')}"
70
+ )
71
+ with open(filename, "rb") as image:
72
+ return image.read()
73
+
74
+ finally:
75
+ if os.path.exists(filename):
76
+ os.remove(filename)
@@ -11,6 +11,7 @@ from .converter import (
11
11
  ConfluenceQualifiedID,
12
12
  extract_qualified_id,
13
13
  )
14
+ from .matcher import Matcher, MatcherOptions
14
15
  from .properties import ConfluenceProperties
15
16
 
16
17
  LOGGER = logging.getLogger(__name__)
@@ -69,16 +70,18 @@ class Processor:
69
70
 
70
71
  LOGGER.info(f"Indexing directory: {local_dir}")
71
72
 
73
+ matcher = Matcher(MatcherOptions(source=".mdignore", extension="md"), local_dir)
74
+
72
75
  files: List[Path] = []
73
76
  directories: List[Path] = []
74
77
  for entry in os.scandir(local_dir):
78
+ if matcher.is_excluded(entry.name):
79
+ continue
80
+
75
81
  if entry.is_file():
76
- if entry.name.endswith(".md"):
77
- # skip non-markdown files
78
- files.append((Path(local_dir) / entry.name).absolute())
82
+ files.append((Path(local_dir) / entry.name).absolute())
79
83
  elif entry.is_dir():
80
- if not entry.name.startswith("."):
81
- directories.append((Path(local_dir) / entry.name).absolute())
84
+ directories.append((Path(local_dir) / entry.name).absolute())
82
85
 
83
86
  for doc in files:
84
87
  metadata = self._get_page(doc)
@@ -0,0 +1,8 @@
1
+ {
2
+ "executablePath": "/usr/bin/chromium-browser",
3
+ "args": [
4
+ "--no-sandbox",
5
+ "--disable-gpu",
6
+ "--disable-setuid-sandbox"
7
+ ]
8
+ }
@@ -43,6 +43,7 @@ exclude =
43
43
  [options.package_data]
44
44
  md2conf =
45
45
  entities.dtd
46
+ puppeteer-config.json
46
47
  py.typed
47
48
 
48
49
  [options.entry_points]
@@ -12,6 +12,7 @@ from md2conf.converter import (
12
12
  elements_from_string,
13
13
  elements_to_string,
14
14
  )
15
+ from md2conf.matcher import Matcher, MatcherOptions
15
16
  from md2conf.mermaid import has_mmdc
16
17
 
17
18
  logging.basicConfig(
@@ -50,8 +51,12 @@ class TestConversion(unittest.TestCase):
50
51
  shutil.rmtree(self.out_dir)
51
52
 
52
53
  def test_markdown(self) -> None:
54
+ matcher = Matcher(
55
+ MatcherOptions(source=".mdignore", extension="md"), self.source_dir
56
+ )
57
+
53
58
  for entry in os.scandir(self.source_dir):
54
- if not entry.name.endswith(".md"):
59
+ if matcher.is_excluded(entry.name):
55
60
  continue
56
61
 
57
62
  name, _ = os.path.splitext(entry.name)
@@ -0,0 +1,43 @@
1
+ import logging
2
+ import os
3
+ import os.path
4
+ import unittest
5
+ from pathlib import Path
6
+
7
+ from md2conf.matcher import Matcher, MatcherOptions
8
+
9
+ logging.basicConfig(
10
+ level=logging.INFO,
11
+ format="%(asctime)s - %(levelname)s - %(funcName)s [%(lineno)d] - %(message)s",
12
+ )
13
+
14
+
15
+ class TestMatcher(unittest.TestCase):
16
+ def test_extension(self) -> None:
17
+ directory = Path(os.path.dirname(__file__))
18
+ expected = [
19
+ entry.name for entry in os.scandir(directory) if entry.name.endswith(".py")
20
+ ]
21
+
22
+ options = MatcherOptions(".mdignore", ".py")
23
+ matcher = Matcher(options, directory)
24
+ actual = matcher.scandir(directory)
25
+
26
+ self.assertCountEqual(expected, actual)
27
+
28
+ def test_rules(self) -> None:
29
+ directory = Path(os.path.dirname(__file__)) / "source"
30
+ expected = [
31
+ entry.name for entry in os.scandir(directory) if entry.name.endswith(".md")
32
+ ]
33
+ expected.remove("ignore.md")
34
+
35
+ options = MatcherOptions(".mdignore", ".md")
36
+ matcher = Matcher(options, directory)
37
+ actual = matcher.scandir(directory)
38
+
39
+ self.assertCountEqual(expected, actual)
40
+
41
+
42
+ if __name__ == "__main__":
43
+ unittest.main()
@@ -1,54 +0,0 @@
1
- import os
2
- import os.path
3
- import shutil
4
- import subprocess
5
- from typing import Literal
6
-
7
-
8
- def has_mmdc() -> bool:
9
- "True if Mermaid diagram converter is available on the OS."
10
-
11
- if os.name == "nt":
12
- executable = "mmdc.cmd"
13
- else:
14
- executable = "mmdc"
15
- return shutil.which(executable) is not None
16
-
17
-
18
- def render(source: str, output_format: Literal["png", "svg"] = "png") -> bytes:
19
- "Generates a PNG or SVG image from a Mermaid diagram source."
20
-
21
- filename = f"tmp_mermaid.{output_format}"
22
-
23
- if os.name == "nt":
24
- executable = "mmdc.cmd"
25
- else:
26
- executable = "mmdc"
27
- try:
28
- cmd = [
29
- executable,
30
- "--input",
31
- "-",
32
- "--output",
33
- filename,
34
- "--outputFormat",
35
- output_format,
36
- ]
37
- proc = subprocess.Popen(
38
- cmd,
39
- stdout=subprocess.PIPE,
40
- stdin=subprocess.PIPE,
41
- stderr=subprocess.PIPE,
42
- text=False,
43
- )
44
- proc.communicate(input=source.encode("utf-8"))
45
- if proc.returncode:
46
- raise RuntimeError(
47
- f"failed to convert Mermaid diagram; exit code: {proc.returncode}"
48
- )
49
- with open(filename, "rb") as image:
50
- return image.read()
51
-
52
- finally:
53
- if os.path.exists(filename):
54
- os.remove(filename)