mkdocs-decision-records 1.0.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Timo Reymann
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,100 @@
1
+ Metadata-Version: 2.1
2
+ Name: mkdocs-decision-records
3
+ Version: 1.0.0
4
+ Summary: Manage decision records with mkdocs in a customizable and minimal fashion.
5
+ Author: Timo Reymann
6
+ Author-email: mail@timo-reymann.de
7
+ Requires-Python: >=3.12,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Requires-Dist: mkdocs (>=1.6.1,<2.0.0)
12
+ Requires-Dist: mkdocs-material (>=9.5.48,<10.0.0)
13
+ Description-Content-Type: text/markdown
14
+
15
+ mkdocs-decision-records
16
+ ==
17
+
18
+ <p align="center">
19
+ <img width="600" src="https://raw.githubusercontent.com/timo-reymann/mkdocs-decision-records/main/.github/images/demo.png">
20
+ <br />
21
+ Manage decision records with mkdocs in a customizable and minimal fashion.
22
+ </p>
23
+
24
+
25
+
26
+ ## Features
27
+
28
+ - Customizable status colors and lifecycle
29
+ - Enforces information to be present for ADRs
30
+ - Allows description being kept as markdown
31
+
32
+ ## Installation
33
+
34
+ 1. Install `mkdocs-decision-records` from the PyPi registry using your favorite package manager
35
+ 2. Configure your `mkdocs.yml`
36
+ ```yaml
37
+ plugins:
38
+ - decision-records:
39
+ # Folder where your decision records are located, defaults to adr
40
+ decisions_folder: adr
41
+ # Optional prefix to prepend to ticket numbers
42
+ ticket_url_prefix: https://ticket.example.com/
43
+ # Configure amount of required deciders
44
+ required_deciders_count: 1
45
+ # Configure available stages and the badge colors
46
+ lifecycle_stages:
47
+ {status}: {color}
48
+ ```
49
+ 3. Create your ADRs ensuring to add the frontmatter meta data:
50
+ ```markdown
51
+ ---
52
+ id: 000
53
+ status: proposed | rejected | accepted | deprecated | … | superseded by
54
+ date: YYYY-MM-DD
55
+ deciders:
56
+ - decider 1
57
+ - decider 2
58
+ # Optional ticket
59
+ ticket: FOO-1
60
+ ---
61
+ ```
62
+
63
+ ## Motivation
64
+
65
+ I love ADRs and documenting decisions in general. This plugin makes it a bit easier, enforcing basic meta information
66
+ while keeping the format open enough so you can do your thing.
67
+
68
+ ## Contributing
69
+
70
+ I love your input! I want to make contributing to this project as easy and transparent as possible, whether it's:
71
+
72
+ - Reporting a bug
73
+ - Discussing the current state of the configuration
74
+ - Submitting a fix
75
+ - Proposing new features
76
+ - Becoming a maintainer
77
+
78
+ To get started please read the [Contribution Guidelines](./CONTRIBUTING.md).
79
+
80
+ ## Development
81
+
82
+ ### Requirements
83
+
84
+ - Python 3.12+
85
+ - Poetry
86
+
87
+ ### Build
88
+
89
+ ````sh
90
+ poetry install
91
+ ````
92
+
93
+ ### Alternatives
94
+
95
+ - [mkdocs-material](https://pypi.org/project/mkdocs-material-adr/)
96
+ - Needs to use the theme
97
+ - ADR graph
98
+ - [mkdocs-macros-adr-summary](https://github.com/febus982/mkdocs-macros-adr-summary)
99
+ - works entirely with macros
100
+ - no metadata table at the top
@@ -0,0 +1,86 @@
1
+ mkdocs-decision-records
2
+ ==
3
+
4
+ <p align="center">
5
+ <img width="600" src="https://raw.githubusercontent.com/timo-reymann/mkdocs-decision-records/main/.github/images/demo.png">
6
+ <br />
7
+ Manage decision records with mkdocs in a customizable and minimal fashion.
8
+ </p>
9
+
10
+
11
+
12
+ ## Features
13
+
14
+ - Customizable status colors and lifecycle
15
+ - Enforces information to be present for ADRs
16
+ - Allows description being kept as markdown
17
+
18
+ ## Installation
19
+
20
+ 1. Install `mkdocs-decision-records` from the PyPi registry using your favorite package manager
21
+ 2. Configure your `mkdocs.yml`
22
+ ```yaml
23
+ plugins:
24
+ - decision-records:
25
+ # Folder where your decision records are located, defaults to adr
26
+ decisions_folder: adr
27
+ # Optional prefix to prepend to ticket numbers
28
+ ticket_url_prefix: https://ticket.example.com/
29
+ # Configure amount of required deciders
30
+ required_deciders_count: 1
31
+ # Configure available stages and the badge colors
32
+ lifecycle_stages:
33
+ {status}: {color}
34
+ ```
35
+ 3. Create your ADRs ensuring to add the frontmatter meta data:
36
+ ```markdown
37
+ ---
38
+ id: 000
39
+ status: proposed | rejected | accepted | deprecated | … | superseded by
40
+ date: YYYY-MM-DD
41
+ deciders:
42
+ - decider 1
43
+ - decider 2
44
+ # Optional ticket
45
+ ticket: FOO-1
46
+ ---
47
+ ```
48
+
49
+ ## Motivation
50
+
51
+ I love ADRs and documenting decisions in general. This plugin makes it a bit easier, enforcing basic meta information
52
+ while keeping the format open enough so you can do your thing.
53
+
54
+ ## Contributing
55
+
56
+ I love your input! I want to make contributing to this project as easy and transparent as possible, whether it's:
57
+
58
+ - Reporting a bug
59
+ - Discussing the current state of the configuration
60
+ - Submitting a fix
61
+ - Proposing new features
62
+ - Becoming a maintainer
63
+
64
+ To get started please read the [Contribution Guidelines](./CONTRIBUTING.md).
65
+
66
+ ## Development
67
+
68
+ ### Requirements
69
+
70
+ - Python 3.12+
71
+ - Poetry
72
+
73
+ ### Build
74
+
75
+ ````sh
76
+ poetry install
77
+ ````
78
+
79
+ ### Alternatives
80
+
81
+ - [mkdocs-material](https://pypi.org/project/mkdocs-material-adr/)
82
+ - Needs to use the theme
83
+ - ADR graph
84
+ - [mkdocs-macros-adr-summary](https://github.com/febus982/mkdocs-macros-adr-summary)
85
+ - works entirely with macros
86
+ - no metadata table at the top
@@ -0,0 +1,15 @@
1
+ from collections.abc import Generator
2
+
3
+
4
+ def _meta_table(items: list[tuple[str, str]]) -> Generator[str, None, None]:
5
+ yield "<table>"
6
+ for (h, v) in items:
7
+ yield f"<tr><td><strong>{h}</strong></td><td>{v}</td></tr>"
8
+ yield "</table>"
9
+
10
+
11
+ def _list(items: list[str]) -> Generator[str]:
12
+ yield "<ul>"
13
+ for item in items:
14
+ yield f"<li>{item}</li>"
15
+ yield "</ul>"
@@ -0,0 +1,127 @@
1
+ import re
2
+
3
+ from mkdocs.config import config_options
4
+ from mkdocs.config.defaults import MkDocsConfig
5
+ from mkdocs.exceptions import PluginError
6
+ from mkdocs.plugins import BasePlugin
7
+ from mkdocs.structure.files import Files
8
+ from mkdocs.structure.pages import Page
9
+
10
+ from mkdocs_decision_records._markdown_utils import _list, _meta_table
11
+
12
+ CONFIG_DECISIONS_FOLDER_KEY = "decisions_folder"
13
+ CONFIG_TICKET_URL_PREFIX = "ticket_url_prefix"
14
+
15
+ CONFIG_LIFECYCLE_COLORS_KEY = "lifecycle_stages"
16
+ CONFIG_DECISIONS_FOLDER_DEFAULT = "adr"
17
+
18
+ CONFIG_REQUIRED_DECIDERS_COUNT_KEY = "required_deciders_count"
19
+ CONFIG_REQUIRED_DECIDERS_COUNT_DEFAULT = 1
20
+
21
+ DR_TITLE_WITH_NUM = re.compile(r'\d{3,}.*')
22
+
23
+ DEFAULT_LIFECYCLE_COLORS = {
24
+ "accepted": "#28a745",
25
+ "proposed": "gray",
26
+ "rejected": "#dc3545",
27
+ "deprecated": "#6c757d",
28
+ "superseded": "#17a2b8",
29
+ }
30
+
31
+
32
+ class InvalidMetaDataError(PluginError):
33
+ def __init__(self, page: Page, field: str, message: str):
34
+ self.field = field
35
+ self.raw_message = message
36
+ self.message = f"Invalid metadata for field '{field}' in {page.file.src_path}: {message}"
37
+
38
+
39
+ def _require_meta(page: Page, field: str) -> any:
40
+ val = page.meta.get(field, None)
41
+ if val is None:
42
+ raise InvalidMetaDataError(page, field, "Required, but not set.")
43
+ return val
44
+
45
+
46
+ class DecisionRecordsPlugin(BasePlugin):
47
+ config_scheme = (
48
+ (CONFIG_DECISIONS_FOLDER_KEY, config_options.Type(str, default=CONFIG_DECISIONS_FOLDER_DEFAULT)),
49
+ (CONFIG_TICKET_URL_PREFIX, config_options.Type(str, default=None)),
50
+ (CONFIG_LIFECYCLE_COLORS_KEY, config_options.Type(dict, default=DEFAULT_LIFECYCLE_COLORS)),
51
+ (CONFIG_REQUIRED_DECIDERS_COUNT_KEY, config_options.Type(int, default=CONFIG_REQUIRED_DECIDERS_COUNT_DEFAULT))
52
+ )
53
+
54
+ def on_page_markdown(self, markdown: str, page: Page, config: MkDocsConfig, files: Files):
55
+ if not page.file.src_path.startswith(self.config.get(CONFIG_DECISIONS_FOLDER_KEY)):
56
+ return markdown
57
+
58
+ title = page.meta.get("title", None) or page.title
59
+ dr_id = _require_meta(page, "id")
60
+
61
+ if dr_id == 0:
62
+ page.title = "000 - Template"
63
+ return markdown
64
+
65
+ meta = [
66
+ ("Status", self._create_status_badge(page)),
67
+ ("Date", _require_meta(page, "date")),
68
+ ]
69
+
70
+ deciders = page.meta.get("deciders", [])
71
+ if len(deciders) < self.required_deciders_count:
72
+ raise InvalidMetaDataError(page, "deciders",
73
+ f"At least {self.required_deciders_count} deciders are required for a decision")
74
+ elif len(deciders) > 0:
75
+ meta.append((
76
+ "Deciders" if len(deciders) > 1 else "Decider",
77
+ "\n".join(_list(deciders)) if len(deciders) > 1 else deciders[0],
78
+ ))
79
+
80
+ ticket = page.meta.get("ticket", None)
81
+ if ticket is not None:
82
+ if self.config.get(CONFIG_TICKET_URL_PREFIX) is not None:
83
+ ticket_text = f"<a href='{self.config.get(CONFIG_TICKET_URL_PREFIX)}/{ticket}'>{ticket.upper()}</a>"
84
+ else:
85
+ ticket_text = ticket.upper()
86
+
87
+ meta.append((
88
+ "Ticket",
89
+ ticket_text,
90
+ ))
91
+
92
+ meta_info = "\n".join(_meta_table(meta))
93
+
94
+ header = title if DR_TITLE_WITH_NUM.match(title) else f"{dr_id:03d} - {title}"
95
+ page.title = header
96
+
97
+ return (
98
+ f"{header}\n"
99
+ f"===\n"
100
+ f"{meta_info}\n"
101
+ f"{markdown}"
102
+ )
103
+
104
+ @property
105
+ def lifecycles(self) -> dict[str, str]:
106
+ configured_lifecycle_colors = self.config.get(CONFIG_LIFECYCLE_COLORS_KEY, None)
107
+ return {
108
+ **DEFAULT_LIFECYCLE_COLORS,
109
+ **configured_lifecycle_colors,
110
+ }
111
+
112
+ @property
113
+ def required_deciders_count(self):
114
+ return self.config.get(CONFIG_REQUIRED_DECIDERS_COUNT_KEY, CONFIG_REQUIRED_DECIDERS_COUNT_DEFAULT)
115
+
116
+ def _create_status_badge(self, page):
117
+ status = _require_meta(page, "status")
118
+
119
+ status_color = self.lifecycles.get(status, None)
120
+ if status_color is None:
121
+ raise InvalidMetaDataError(page, "status", f"Invalid status {status}")
122
+
123
+ return (
124
+ f"<span style='color: white;background:{status_color};padding:.4em;border-radius:8px;font-size:100%;'>"
125
+ f"{status}"
126
+ f"</span>"
127
+ )
@@ -0,0 +1,19 @@
1
+ [tool.poetry]
2
+ name = "mkdocs-decision-records"
3
+ version = "1.0.0"
4
+ description = "Manage decision records with mkdocs in a customizable and minimal fashion."
5
+ authors = ["Timo Reymann <mail@timo-reymann.de>"]
6
+ readme = "README.md"
7
+ include = ["mkdocs_decision_records"]
8
+
9
+ [tool.poetry.dependencies]
10
+ python = "^3.12"
11
+ mkdocs = "^1.6.1"
12
+ mkdocs-material = "^9.5.48"
13
+
14
+ [build-system]
15
+ requires = ["poetry-core"]
16
+ build-backend = "poetry.core.masonry.api"
17
+
18
+ [tool.poetry.plugins."mkdocs.plugins"]
19
+ decision-records = "mkdocs_decision_records.plugin:DecisionRecordsPlugin"