mdx-viewer 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.
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.4
2
+ Name: mdx-viewer
3
+ Version: 0.1.0
4
+ Summary: A beautiful Markdown viewer for terminal with code execution support
5
+ Author-email: Your Name <you@example.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/mmx8lb/mdx
8
+ Project-URL: Repository, https://github.com/mmx8lb/mdx
9
+ Keywords: markdown,terminal,cli,viewer
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Requires-Python: >=3.8
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: click>=8.0.0
21
+
22
+ # MDX - Markdown Viewer for Terminal
23
+
24
+ <p align="center">
25
+ <img src="https://img.shields.io/pypi/v/mdx" alt="PyPI">
26
+ <img src="https://img.shields.io/pypi/l/mdx" alt="License">
27
+ <img src="https://img.shields.io/pypi/pyversions/mdx" alt="Python">
28
+ </p>
29
+
30
+ A beautiful Markdown viewer for terminal with code execution support.
31
+
32
+ ## Features
33
+
34
+ - 🎨 Beautiful syntax highlighting with multiple themes
35
+ - 📑 Table of contents navigation
36
+ - ⚡ Execute code blocks directly (bash, python)
37
+ - 🔍 Search within documents
38
+ - 📖 Multiple rendering modes
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ pip install mdx
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ ```bash
49
+ # View a markdown file
50
+ mdx README.md
51
+
52
+ # Show table of contents
53
+ mdx README.md --toc
54
+
55
+ # Execute code blocks
56
+ mdx README.md --execute
57
+
58
+ # Jump to specific line
59
+ mdx README.md --line 100
60
+
61
+ # Use different theme
62
+ mdx README.md --theme dracula
63
+ ```
64
+
65
+ ## Options
66
+
67
+ | Option | Description |
68
+ |--------|-------------|
69
+ | `-e, --execute` | Execute code blocks |
70
+ | `-t, --toc` | Show table of contents |
71
+ | `-l, --line` | Jump to specific line |
72
+ | `-m, --theme` | Syntax highlighting theme |
73
+
74
+ ## Themes
75
+
76
+ Available syntax highlighting themes:
77
+ - monokai (default)
78
+ - dracula
79
+ - github-dark
80
+ - nord
81
+ - solarized-dark
82
+
83
+ ## Example
84
+
85
+ ```bash
86
+ # Read documentation
87
+ mdx docs/linux-0.11-filesystem-analysis.md --toc
88
+
89
+ # Execute tutorial code blocks
90
+ mdx docs/tutorial.md --execute
91
+ ```
92
+
93
+ ## License
94
+
95
+ MIT License
@@ -0,0 +1,74 @@
1
+ # MDX - Markdown Viewer for Terminal
2
+
3
+ <p align="center">
4
+ <img src="https://img.shields.io/pypi/v/mdx" alt="PyPI">
5
+ <img src="https://img.shields.io/pypi/l/mdx" alt="License">
6
+ <img src="https://img.shields.io/pypi/pyversions/mdx" alt="Python">
7
+ </p>
8
+
9
+ A beautiful Markdown viewer for terminal with code execution support.
10
+
11
+ ## Features
12
+
13
+ - 🎨 Beautiful syntax highlighting with multiple themes
14
+ - 📑 Table of contents navigation
15
+ - ⚡ Execute code blocks directly (bash, python)
16
+ - 🔍 Search within documents
17
+ - 📖 Multiple rendering modes
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pip install mdx
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ```bash
28
+ # View a markdown file
29
+ mdx README.md
30
+
31
+ # Show table of contents
32
+ mdx README.md --toc
33
+
34
+ # Execute code blocks
35
+ mdx README.md --execute
36
+
37
+ # Jump to specific line
38
+ mdx README.md --line 100
39
+
40
+ # Use different theme
41
+ mdx README.md --theme dracula
42
+ ```
43
+
44
+ ## Options
45
+
46
+ | Option | Description |
47
+ |--------|-------------|
48
+ | `-e, --execute` | Execute code blocks |
49
+ | `-t, --toc` | Show table of contents |
50
+ | `-l, --line` | Jump to specific line |
51
+ | `-m, --theme` | Syntax highlighting theme |
52
+
53
+ ## Themes
54
+
55
+ Available syntax highlighting themes:
56
+ - monokai (default)
57
+ - dracula
58
+ - github-dark
59
+ - nord
60
+ - solarized-dark
61
+
62
+ ## Example
63
+
64
+ ```bash
65
+ # Read documentation
66
+ mdx docs/linux-0.11-filesystem-analysis.md --toc
67
+
68
+ # Execute tutorial code blocks
69
+ mdx docs/tutorial.md --execute
70
+ ```
71
+
72
+ ## License
73
+
74
+ MIT License
@@ -0,0 +1,9 @@
1
+ """MDX - A beautiful Markdown viewer for terminal"""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ from .cli import main
6
+ from .parser import MarkdownParser
7
+ from .renderer import TerminalRenderer
8
+
9
+ __all__ = ["main", "MarkdownParser", "TerminalRenderer"]
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ MDX - A beautiful Markdown viewer for terminal
4
+ """
5
+
6
+ import click
7
+ import curses
8
+ from pathlib import Path
9
+ from .parser import MarkdownParser
10
+ from .renderer import TerminalRenderer
11
+
12
+
13
+ class MDXViewer:
14
+ """Main MDX viewer class"""
15
+
16
+ def __init__(self, filepath: str):
17
+ self.filepath = Path(filepath)
18
+ self.content = self.filepath.read_text()
19
+ self.parser = MarkdownParser(self.content)
20
+ self.parsed_content = self.parser.parse()
21
+ self.renderer = TerminalRenderer()
22
+ self.scroll = 0
23
+
24
+ def run(self):
25
+ """Run the viewer"""
26
+ def run_curses(stdscr):
27
+ self._run_interactive(stdscr)
28
+
29
+ curses.wrapper(run_curses)
30
+
31
+ def _run_interactive(self, stdscr):
32
+ """Run interactive mode with curses"""
33
+ import time
34
+
35
+ try:
36
+ stdscr.nodelay(True)
37
+ last_scroll = -1
38
+ last_screen_size = (0, 0)
39
+
40
+ # Initial render
41
+ self.renderer.render(stdscr, self.parsed_content, self.scroll)
42
+ last_scroll = self.scroll
43
+
44
+ while True:
45
+ try:
46
+ # Get key press
47
+ key = stdscr.getch()
48
+
49
+ # Handle keys (less-style)
50
+ if key == ord('q'):
51
+ break
52
+ elif key in [ord('j'), ord('J'), curses.KEY_DOWN]:
53
+ self.scroll_down()
54
+ # Force render on key press
55
+ self.renderer.render(stdscr, self.parsed_content, self.scroll)
56
+ last_scroll = self.scroll
57
+ elif key in [ord('k'), ord('K'), curses.KEY_UP]:
58
+ self.scroll_up()
59
+ # Force render on key press
60
+ self.renderer.render(stdscr, self.parsed_content, self.scroll)
61
+ last_scroll = self.scroll
62
+ elif key in [ord('g')]:
63
+ self.scroll_to_top()
64
+ # Force render on key press
65
+ self.renderer.render(stdscr, self.parsed_content, self.scroll)
66
+ last_scroll = self.scroll
67
+ elif key in [ord('G')]:
68
+ self.scroll_to_bottom()
69
+ # Force render on key press
70
+ self.renderer.render(stdscr, self.parsed_content, self.scroll)
71
+ last_scroll = self.scroll
72
+ elif key in [ord(' '), ord('f')]:
73
+ self.scroll_down(10)
74
+ # Force render on key press
75
+ self.renderer.render(stdscr, self.parsed_content, self.scroll)
76
+ last_scroll = self.scroll
77
+ elif key in [ord('b')]:
78
+ self.scroll_up(10)
79
+ # Force render on key press
80
+ self.renderer.render(stdscr, self.parsed_content, self.scroll)
81
+ last_scroll = self.scroll
82
+ elif key in [ord('/')]:
83
+ # Simple search (to be implemented)
84
+ pass
85
+ elif key in [ord('n')]:
86
+ # Next search result (to be implemented)
87
+ pass
88
+ elif key in [ord('N')]:
89
+ # Previous search result (to be implemented)
90
+ pass
91
+
92
+ # Small delay to reduce CPU usage but maintain responsiveness
93
+ time.sleep(0.005)
94
+
95
+ except KeyboardInterrupt:
96
+ # Handle Ctrl+C gracefully
97
+ break
98
+ except Exception as e:
99
+ # Display error
100
+ try:
101
+ stdscr.clear()
102
+ stdscr.addstr(0, 0, f"Error: {str(e)}", curses.A_REVERSE | curses.A_BOLD)
103
+ stdscr.addstr(2, 0, "Press q to exit", curses.A_DIM)
104
+ stdscr.refresh()
105
+ time.sleep(2)
106
+ except:
107
+ pass
108
+ break
109
+ except KeyboardInterrupt:
110
+ # Handle Ctrl+C at the outer level
111
+ pass
112
+ except Exception as e:
113
+ # Display overall error
114
+ try:
115
+ stdscr.clear()
116
+ stdscr.addstr(0, 0, f"Fatal error: {str(e)}", curses.A_REVERSE | curses.A_BOLD)
117
+ stdscr.addstr(2, 0, "Press any key to exit", curses.A_DIM)
118
+ stdscr.refresh()
119
+ stdscr.getch() # Wait for user input
120
+ except:
121
+ pass
122
+
123
+ def scroll_down(self, n: int = 1):
124
+ """Scroll down by n lines"""
125
+ # Get actual terminal height for dynamic calculation
126
+ import shutil
127
+ try:
128
+ height = shutil.get_terminal_size().lines
129
+ content_height = max(10, height - 5) # Reserve space for header and status
130
+ except:
131
+ content_height = 20 # Fallback to reasonable default
132
+ max_scroll = max(0, len(self.parsed_content) - content_height)
133
+ self.scroll = min(max_scroll, self.scroll + n)
134
+
135
+ def scroll_up(self, n: int = 1):
136
+ """Scroll up by n lines"""
137
+ self.scroll = max(0, self.scroll - n)
138
+
139
+ def scroll_to_top(self):
140
+ """Scroll to top"""
141
+ self.scroll = 0
142
+
143
+ def scroll_to_bottom(self):
144
+ """Scroll to bottom"""
145
+ # Get actual terminal height for dynamic calculation
146
+ import shutil
147
+ try:
148
+ height = shutil.get_terminal_size().lines
149
+ content_height = max(10, height - 5) # Reserve space for header and status
150
+ except:
151
+ content_height = 20 # Fallback to reasonable default
152
+ self.scroll = max(0, len(self.parsed_content) - content_height)
153
+
154
+
155
+ def main():
156
+ """Main entry point"""
157
+ @click.command()
158
+ @click.argument('file', type=click.Path(exists=True))
159
+ @click.version_option(version='1.0.0', prog_name='mdx')
160
+ def cli(file):
161
+ """MDX - Markdown Viewer for Terminal
162
+
163
+ View Markdown files in the terminal with beautiful formatting.
164
+ """
165
+ viewer = MDXViewer(file)
166
+ viewer.run()
167
+
168
+ cli()
169
+
170
+
171
+ if __name__ == '__main__':
172
+ main()
@@ -0,0 +1,158 @@
1
+ """Markdown parser module"""
2
+
3
+ import re
4
+ from typing import List, Tuple, Dict, Any
5
+
6
+
7
+ class MarkdownParser:
8
+ """Markdown parser class"""
9
+
10
+ def __init__(self, content: str):
11
+ self.content = content
12
+ self.lines = content.split('\n')
13
+
14
+ def parse(self) -> List[Tuple[str, Any]]:
15
+ """Parse markdown content into structured data"""
16
+ result = []
17
+ in_code = False
18
+ code_lang = ""
19
+ code_lines = []
20
+ in_table = False
21
+ table_lines = []
22
+
23
+ try:
24
+ for line in self.lines:
25
+ try:
26
+ stripped = line.strip()
27
+
28
+ # Code block handling
29
+ if stripped.startswith('```'):
30
+ if not in_code:
31
+ in_code = True
32
+ code_lang = stripped[3:].strip() or "text"
33
+ code_lines = []
34
+ else:
35
+ in_code = False
36
+ result.append(('code_block', (code_lang, code_lines)))
37
+ continue
38
+
39
+ if in_code:
40
+ code_lines.append(line)
41
+ continue
42
+
43
+ # Table handling
44
+ if stripped.startswith('|') and '|' in stripped:
45
+ if not in_table:
46
+ in_table = True
47
+ table_lines = []
48
+ table_lines.append(line)
49
+ continue
50
+ elif in_table:
51
+ in_table = False
52
+ result.append(('table', table_lines))
53
+ table_lines = []
54
+
55
+ # Header
56
+ if stripped.startswith('#'):
57
+ level = len(stripped) - len(stripped.lstrip('#'))
58
+ if level <= 6:
59
+ title = stripped.lstrip('#').strip()
60
+ result.append(('header', (level, title)))
61
+ continue
62
+
63
+ # Horizontal rule
64
+ if stripped in ['---', '***', '___']:
65
+ result.append(('hr', None))
66
+ continue
67
+
68
+ # List item
69
+ if stripped.startswith(('- ', '* ', '+ ')):
70
+ content = stripped[2:]
71
+ result.append(('list_item', ('unordered', content)))
72
+ continue
73
+
74
+ # Numbered list
75
+ m = re.match(r'^\d+\.\s+(.*)', stripped)
76
+ if m:
77
+ result.append(('list_item', ('ordered', m.group(1))))
78
+ continue
79
+
80
+ # Blockquote
81
+ if stripped.startswith('>'):
82
+ content = stripped[1:].strip()
83
+ result.append(('blockquote', content))
84
+ continue
85
+
86
+ # Bold and italic
87
+ if '**' in line or '*' in line:
88
+ result.append(('text', line))
89
+ continue
90
+
91
+ # Regular text
92
+ result.append(('text', line))
93
+ except Exception as e:
94
+ # Handle line-level parsing errors
95
+ result.append(('text', line)) # Fallback to plain text
96
+ except Exception as e:
97
+ # Handle major parsing errors
98
+ result = [('text', f"Error parsing markdown: {str(e)}")]
99
+
100
+ # Handle any remaining table
101
+ if in_table and table_lines:
102
+ try:
103
+ result.append(('table', table_lines))
104
+ except:
105
+ # Fallback if table parsing fails
106
+ for line in table_lines:
107
+ result.append(('text', line))
108
+
109
+ return result
110
+
111
+ def parse_table(self, table_lines: List[str]) -> Dict[str, Any]:
112
+ """Parse table lines into structured data"""
113
+ if not table_lines:
114
+ return {}
115
+
116
+ # Parse header
117
+ header = table_lines[0].strip().split('|')
118
+ header = [h.strip() for h in header if h.strip()]
119
+ num_columns = len(header)
120
+
121
+ # Parse alignment
122
+ alignment = []
123
+ if len(table_lines) > 1:
124
+ align_line = table_lines[1].strip().split('|')
125
+ for i, cell in enumerate(align_line):
126
+ if i == 0 or i == len(align_line) - 1:
127
+ continue
128
+ cell = cell.strip()
129
+ if cell.startswith(':') and cell.endswith(':'):
130
+ alignment.append('center')
131
+ elif cell.startswith(':'):
132
+ alignment.append('left')
133
+ elif cell.endswith(':'):
134
+ alignment.append('right')
135
+ else:
136
+ alignment.append('left')
137
+
138
+ # Parse rows
139
+ rows = []
140
+ # Start from index 2 if there's an alignment line and data rows
141
+ # Skip processing if there are no data rows
142
+ if len(table_lines) > 2:
143
+ for line in table_lines[2:]:
144
+ cells = line.strip().split('|')
145
+ cells = [c.strip() for c in cells if c.strip()]
146
+ # Ensure each row has the same number of columns as header
147
+ while len(cells) < num_columns:
148
+ cells.append('')
149
+ if len(cells) > num_columns:
150
+ cells = cells[:num_columns]
151
+ if cells:
152
+ rows.append(cells)
153
+
154
+ return {
155
+ 'header': header,
156
+ 'alignment': alignment,
157
+ 'rows': rows
158
+ }