mdok 0.1.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.
mdok-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anton
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.
mdok-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,97 @@
1
+ Metadata-Version: 2.4
2
+ Name: mdok
3
+ Version: 0.1.0
4
+ Summary: A document-grade terminal markdown reader with responsive centering and clean tables
5
+ Author: Anton
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/anton/mdok
8
+ Project-URL: Repository, https://github.com/anton/mdok
9
+ Project-URL: Issues, https://github.com/anton/mdok/issues
10
+ Keywords: markdown,terminal,cli,reader,rich,viewer,booktabs
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
24
+ Classifier: Topic :: Utilities
25
+ Requires-Python: >=3.9
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: rich>=13.0.0
29
+ Requires-Dist: markdown-it-py>=3.0.0
30
+ Requires-Dist: pygments>=2.15.0
31
+ Provides-Extra: dev
32
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # mdok
36
+
37
+ A small CLI tool for reading Markdown files in the terminal.
38
+
39
+ Most terminal renderers either stretch text across the entire display width or force it into a rigid, narrow column on the left edge. Tables often fuse rows together, eat technical tags like `<Type>`, or ignore `<br>` line breaks.
40
+
41
+ `mdok` formats Markdown like a clean document:
42
+
43
+ - **Proportional width & centering**: Centers text on wide displays with gentle margins and scales comfortably (~75% of terminal width up to ~120 columns).
44
+ - **Readable tables**: LaTeX *booktabs* style—horizontal rules, no heavy ASCII borders, soft cell wrapping, alignment support, and row dividers so multiline rows don't run together.
45
+ - **Tag & line break handling**: Preserves technical tags (like `<Tag>` or `<Property>`) and respects `<br>` breaks inside cells.
46
+ - **Muted colors**: Inline code uses a warm amber (`#e0af68`) on a subtle dark badge instead of bright yellow. Code blocks default to the Nord syntax theme.
47
+ - **Automatic pager**: Pipes into `less` (with colors preserved) when the document is longer than your terminal screen.
48
+ - **GitHub-style callouts**: Renders `> [!NOTE]`, `> [!TIP]`, `> [!WARNING]`, etc.
49
+
50
+ ---
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ pip install mdok
56
+ ```
57
+
58
+ Or from source:
59
+
60
+ ```bash
61
+ git clone https://github.com/anton/mdok.git
62
+ cd mdok
63
+ pip install .
64
+ ```
65
+
66
+ For development:
67
+
68
+ ```bash
69
+ pip install -e .
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Usage
75
+
76
+ Read a file:
77
+
78
+ ```bash
79
+ mdok README.md
80
+ ```
81
+
82
+ Pipe from standard input:
83
+
84
+ ```bash
85
+ cat doc.md | mdok
86
+ ```
87
+
88
+ ### Options
89
+
90
+ ```bash
91
+ mdok doc.md -w 100 # Explicit document width in columns
92
+ mdok doc.md -m 4 # Explicit left margin
93
+ mdok doc.md --no-center # Align to left edge
94
+ mdok doc.md -b rounded # Table style (booktabs, rounded, simple, minimal, none)
95
+ mdok doc.md --code-theme gruvbox-dark # Syntax highlighting theme
96
+ mdok doc.md --no-pager # Print directly without pager
97
+ ```
mdok-0.1.0/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # mdok
2
+
3
+ A small CLI tool for reading Markdown files in the terminal.
4
+
5
+ Most terminal renderers either stretch text across the entire display width or force it into a rigid, narrow column on the left edge. Tables often fuse rows together, eat technical tags like `<Type>`, or ignore `<br>` line breaks.
6
+
7
+ `mdok` formats Markdown like a clean document:
8
+
9
+ - **Proportional width & centering**: Centers text on wide displays with gentle margins and scales comfortably (~75% of terminal width up to ~120 columns).
10
+ - **Readable tables**: LaTeX *booktabs* style—horizontal rules, no heavy ASCII borders, soft cell wrapping, alignment support, and row dividers so multiline rows don't run together.
11
+ - **Tag & line break handling**: Preserves technical tags (like `<Tag>` or `<Property>`) and respects `<br>` breaks inside cells.
12
+ - **Muted colors**: Inline code uses a warm amber (`#e0af68`) on a subtle dark badge instead of bright yellow. Code blocks default to the Nord syntax theme.
13
+ - **Automatic pager**: Pipes into `less` (with colors preserved) when the document is longer than your terminal screen.
14
+ - **GitHub-style callouts**: Renders `> [!NOTE]`, `> [!TIP]`, `> [!WARNING]`, etc.
15
+
16
+ ---
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pip install mdok
22
+ ```
23
+
24
+ Or from source:
25
+
26
+ ```bash
27
+ git clone https://github.com/anton/mdok.git
28
+ cd mdok
29
+ pip install .
30
+ ```
31
+
32
+ For development:
33
+
34
+ ```bash
35
+ pip install -e .
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Usage
41
+
42
+ Read a file:
43
+
44
+ ```bash
45
+ mdok README.md
46
+ ```
47
+
48
+ Pipe from standard input:
49
+
50
+ ```bash
51
+ cat doc.md | mdok
52
+ ```
53
+
54
+ ### Options
55
+
56
+ ```bash
57
+ mdok doc.md -w 100 # Explicit document width in columns
58
+ mdok doc.md -m 4 # Explicit left margin
59
+ mdok doc.md --no-center # Align to left edge
60
+ mdok doc.md -b rounded # Table style (booktabs, rounded, simple, minimal, none)
61
+ mdok doc.md --code-theme gruvbox-dark # Syntax highlighting theme
62
+ mdok doc.md --no-pager # Print directly without pager
63
+ ```
@@ -0,0 +1,61 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "mdok"
7
+ version = "0.1.0"
8
+ description = "A document-grade terminal markdown reader with responsive centering and clean tables"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "Anton" }
14
+ ]
15
+ keywords = [
16
+ "markdown",
17
+ "terminal",
18
+ "cli",
19
+ "reader",
20
+ "rich",
21
+ "viewer",
22
+ "booktabs",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 4 - Beta",
26
+ "Environment :: Console",
27
+ "Intended Audience :: Developers",
28
+ "Intended Audience :: End Users/Desktop",
29
+ "Operating System :: OS Independent",
30
+ "Programming Language :: Python :: 3",
31
+ "Programming Language :: Python :: 3.9",
32
+ "Programming Language :: Python :: 3.10",
33
+ "Programming Language :: Python :: 3.11",
34
+ "Programming Language :: Python :: 3.12",
35
+ "Programming Language :: Python :: 3.13",
36
+ "Programming Language :: Python :: 3.14",
37
+ "Topic :: Text Processing :: Markup :: Markdown",
38
+ "Topic :: Utilities",
39
+ ]
40
+ dependencies = [
41
+ "rich>=13.0.0",
42
+ "markdown-it-py>=3.0.0",
43
+ "pygments>=2.15.0",
44
+ ]
45
+
46
+ [project.optional-dependencies]
47
+ dev = [
48
+ "pytest>=7.0.0",
49
+ ]
50
+
51
+ [project.scripts]
52
+ mdok = "mdok.cli:main"
53
+ mda = "mdok.cli:main"
54
+
55
+ [project.urls]
56
+ Homepage = "https://github.com/anton/mdok"
57
+ Repository = "https://github.com/anton/mdok"
58
+ Issues = "https://github.com/anton/mdok/issues"
59
+
60
+ [tool.setuptools.packages.find]
61
+ where = ["src"]
mdok-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ """mdok - Document-grade Markdown Reader for CLI."""
2
+
3
+ __version__ = "0.1.0"
4
+
@@ -0,0 +1,4 @@
1
+ from mdok.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,102 @@
1
+ import shutil
2
+ from dataclasses import dataclass
3
+ from typing import Optional, Tuple
4
+ from rich.console import Console, ConsoleOptions, RenderResult, RenderableType
5
+ from rich.segment import Segment
6
+ from rich.measure import Measurement
7
+
8
+
9
+ @dataclass
10
+ class CanvasConfig:
11
+ """Configuration for document canvas layout."""
12
+ max_width: Optional[int] = None
13
+ min_width: int = 70
14
+ cap_width: int = 124
15
+ ratio: float = 0.74
16
+ margin: Optional[int] = None
17
+ center: bool = True
18
+
19
+
20
+ def calculate_dimensions(
21
+ terminal_width: int,
22
+ config: Optional[CanvasConfig] = None,
23
+ ) -> Tuple[int, int]:
24
+ """
25
+ Calculate (doc_width, left_margin) based on terminal width and config.
26
+
27
+ Rules:
28
+ - Narrow screens (<= 82): maximize width with minimal 2-char gutter.
29
+ - Medium screens (83-105): generous 80-85% width.
30
+ - Wide screens (> 105): proportional document sheet (~74% width) up to cap_width (default 124),
31
+ avoiding both full-screen blowout and artificial narrow text walls.
32
+ """
33
+ cfg = config or CanvasConfig()
34
+
35
+ if cfg.max_width is not None:
36
+ doc_w = min(cfg.max_width, terminal_width)
37
+ else:
38
+ if terminal_width <= 82:
39
+ doc_w = max(30, terminal_width - 4)
40
+ elif terminal_width <= 105:
41
+ doc_w = min(terminal_width - 6, max(76, int(terminal_width * 0.84)))
42
+ else:
43
+ proportional = int(terminal_width * cfg.ratio)
44
+ doc_w = min(cfg.cap_width, max(88, proportional))
45
+ doc_w = min(doc_w, terminal_width - 12)
46
+
47
+ doc_w = max(20, min(doc_w, terminal_width))
48
+
49
+ if cfg.margin is not None:
50
+ left_margin = max(0, min(cfg.margin, terminal_width - doc_w))
51
+ elif cfg.center:
52
+ left_margin = max(0, (terminal_width - doc_w) // 2)
53
+ else:
54
+ left_margin = 0
55
+
56
+ return doc_w, left_margin
57
+
58
+
59
+ class DocumentCanvas:
60
+ """
61
+ Renders a renderable constrained to `doc_width` and indented by `left_margin`.
62
+ This provides a centered 'sheet of paper' reading experience.
63
+ """
64
+
65
+ def __init__(
66
+ self,
67
+ renderable: RenderableType,
68
+ doc_width: int,
69
+ left_margin: int,
70
+ ) -> None:
71
+ self.renderable = renderable
72
+ self.doc_width = doc_width
73
+ self.left_margin = left_margin
74
+
75
+ def __rich_console__(
76
+ self, console: Console, options: ConsoleOptions
77
+ ) -> RenderResult:
78
+ if getattr(self.renderable, "has_canvas_layout", False):
79
+ # Renderable manages its own margins and widths (including table breakout)
80
+ yield from console.render(self.renderable, options)
81
+ return
82
+
83
+ # Constrain the inner document to doc_width
84
+ doc_options = options.update_width(self.doc_width)
85
+ lines = console.render_lines(self.renderable, doc_options, pad=False)
86
+
87
+ margin_prefix = " " * self.left_margin if self.left_margin > 0 else ""
88
+ margin_segment = Segment(margin_prefix) if margin_prefix else None
89
+
90
+ for line in lines:
91
+ if margin_segment and line:
92
+ yield margin_segment
93
+ for segment in line:
94
+ yield segment
95
+ yield Segment.line()
96
+ elif margin_segment and not line:
97
+ # Blank line
98
+ yield Segment.line()
99
+ else:
100
+ for segment in line:
101
+ yield segment
102
+ yield Segment.line()
@@ -0,0 +1,169 @@
1
+ import argparse
2
+ import sys
3
+ import os
4
+ import shutil
5
+ from typing import Optional, List
6
+ from rich.console import Console
7
+ from rich.theme import Theme
8
+
9
+ from mdok import __version__
10
+ from mdok.theme import DEFAULT_THEME, BOX_STYLES
11
+ from mdok.canvas import CanvasConfig, calculate_dimensions, DocumentCanvas
12
+ from mdok.renderer import DocMarkdown
13
+
14
+
15
+ def parse_args(args: Optional[List[str]] = None) -> argparse.Namespace:
16
+ parser = argparse.ArgumentParser(
17
+ prog="mdok",
18
+ description="Document-grade Markdown Reader for the terminal.",
19
+ )
20
+ parser.add_argument(
21
+ "file",
22
+ nargs="?",
23
+ default="-",
24
+ help="Markdown file to read, or '-' for standard input (default: '-')",
25
+ )
26
+ parser.add_argument(
27
+ "-w",
28
+ "--width",
29
+ type=int,
30
+ default=None,
31
+ help="Max document reading width (default: auto ~88 chars on wide displays)",
32
+ )
33
+ parser.add_argument(
34
+ "-m",
35
+ "--margin",
36
+ type=int,
37
+ default=None,
38
+ help="Explicit left margin in columns (default: auto-centered)",
39
+ )
40
+ parser.add_argument(
41
+ "--no-center",
42
+ action="store_true",
43
+ help="Disable centering and align to left edge",
44
+ )
45
+ parser.add_argument(
46
+ "-b",
47
+ "--box",
48
+ choices=list(BOX_STYLES.keys()),
49
+ default="booktabs",
50
+ help="Table box style: booktabs (default), minimal, rounded, simple, simple_head, none",
51
+ )
52
+ parser.add_argument(
53
+ "--no-lines",
54
+ action="store_true",
55
+ help="Hide horizontal row divider lines in tables",
56
+ )
57
+ parser.add_argument(
58
+ "--code-theme",
59
+ default="nord",
60
+ help="Pygments code syntax highlighting theme (default: nord)",
61
+ )
62
+ parser.add_argument(
63
+ "-p",
64
+ "--pager",
65
+ action="store_true",
66
+ default=None,
67
+ help="Force output through pager (less -RFX)",
68
+ )
69
+ parser.add_argument(
70
+ "--no-pager",
71
+ action="store_true",
72
+ help="Disable pager and print directly to terminal",
73
+ )
74
+ parser.add_argument(
75
+ "-v",
76
+ "--version",
77
+ action="version",
78
+ version=f"%(prog)s {__version__}",
79
+ )
80
+ return parser.parse_args(args)
81
+
82
+
83
+ def read_content(file_arg: str) -> str:
84
+ if file_arg == "-":
85
+ if sys.stdin.isatty():
86
+ # Nothing piped and stdin is a tty
87
+ return ""
88
+ return sys.stdin.read()
89
+
90
+ path = os.path.expanduser(file_arg)
91
+ if not os.path.exists(path):
92
+ sys.stderr.write(f"Error: file not found: {file_arg}\n")
93
+ sys.exit(1)
94
+
95
+ with open(path, "r", encoding="utf-8", errors="replace") as f:
96
+ return f.read()
97
+
98
+
99
+ def main(argv: Optional[List[str]] = None) -> int:
100
+ args = parse_args(argv)
101
+
102
+ content = read_content(args.file)
103
+ if not content:
104
+ if args.file == "-":
105
+ sys.stderr.write("mdok: no input provided via stdin or file. Use 'mdok --help' for usage.\n")
106
+ return 1
107
+ return 0
108
+
109
+ # Determine terminal dimensions
110
+ is_tty = sys.stdout.isatty()
111
+ term_width, term_height = shutil.get_terminal_size(fallback=(100, 24))
112
+
113
+ # Canvas layout calculations
114
+ config = CanvasConfig(
115
+ max_width=args.width,
116
+ margin=args.margin,
117
+ center=not args.no_center if is_tty else False,
118
+ )
119
+ doc_width, left_margin = calculate_dimensions(term_width, config)
120
+
121
+ # If piped to another process (not a tty) and user didn't specify width/margin,
122
+ # don't pad with left margin
123
+ if not is_tty and args.margin is None and not args.no_center:
124
+ left_margin = 0
125
+
126
+ # Build document
127
+ markdown_doc = DocMarkdown(
128
+ content,
129
+ code_theme=args.code_theme,
130
+ box_style=args.box,
131
+ show_lines=not args.no_lines,
132
+ doc_width=doc_width,
133
+ left_margin=left_margin,
134
+ term_width=term_width,
135
+ )
136
+
137
+ canvas = DocumentCanvas(
138
+ renderable=markdown_doc,
139
+ doc_width=doc_width,
140
+ left_margin=left_margin,
141
+ )
142
+
143
+ console = Console(theme=DEFAULT_THEME, width=term_width)
144
+
145
+ # Determine whether to page
146
+ use_pager = False
147
+ if is_tty:
148
+ if args.no_pager:
149
+ use_pager = False
150
+ elif args.pager:
151
+ use_pager = True
152
+ else:
153
+ # Auto-pager heuristic: estimate rendered line count
154
+ # Measure rendered lines
155
+ line_count = sum(1 for _ in console.render_lines(canvas, console.options))
156
+ if line_count > term_height - 2:
157
+ use_pager = True
158
+
159
+ if use_pager:
160
+ with console.pager(styles=True):
161
+ console.print(canvas)
162
+ else:
163
+ console.print(canvas)
164
+
165
+ return 0
166
+
167
+
168
+ if __name__ == "__main__":
169
+ sys.exit(main())