viewmd 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.
viewmd-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 German Greiner
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,3 @@
1
+ include README.md
2
+ include LICENSE
3
+ include requirements.txt
viewmd-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.4
2
+ Name: viewmd
3
+ Version: 0.1.0
4
+ Summary: Simple HTTP server for viewing Markdown files in your browser
5
+ Author: German Greiner
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/driangle/viewmd
8
+ Keywords: markdown,viewer,http-server,cli
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.7
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Topic :: Utilities
20
+ Requires-Python: >=3.7
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: markdown>=3.0
24
+ Dynamic: license-file
25
+
26
+ # viewmd
27
+
28
+ A simple HTTP server for viewing Markdown files in your browser.
29
+
30
+ ## Installation
31
+
32
+ ### Option 1: Using pipx (Recommended)
33
+
34
+ [pipx](https://pypa.github.io/pipx/) installs CLI tools in isolated environments, preventing dependency conflicts:
35
+
36
+ ```bash
37
+ # Install pipx if you don't have it
38
+ python3 -m pip install --user pipx
39
+ python3 -m pipx ensurepath
40
+
41
+ # Install viewmd
42
+ pipx install .
43
+ ```
44
+
45
+ ### Option 2: Using pip
46
+
47
+ Install globally with pip:
48
+
49
+ ```bash
50
+ pip install .
51
+ ```
52
+
53
+ Or install in development mode (changes to source code take effect immediately):
54
+
55
+ ```bash
56
+ pip install -e .
57
+ ```
58
+
59
+ ### Option 3: Direct symlink (Manual)
60
+
61
+ ```bash
62
+ chmod +x viewmd.py
63
+ sudo ln -s "$(pwd)/viewmd.py" /usr/local/bin/viewmd
64
+ ```
65
+
66
+ ## Quick Start
67
+
68
+ ```bash
69
+ # Run from any directory
70
+ viewmd
71
+
72
+ # Open http://localhost:8000
73
+ ```
74
+
75
+ ## What It Does
76
+
77
+ - **Markdown files** (`.md`) - Rendered as HTML with nice styling
78
+ - **Text files** (`.py`, `.json`, `.gitignore`, etc.) - Displayed in browser
79
+ - **Directories** - Shows file listing, auto-displays `README.md`
80
+ - **Other files** - Served normally (images, PDFs, etc.)
81
+
82
+ ## Usage
83
+
84
+ ```bash
85
+ viewmd # Starts on port 8000
86
+ viewmd 3000 # Custom port
87
+ ```
88
+
89
+ ## Uninstallation
90
+
91
+ ```bash
92
+ # If installed with pipx
93
+ pipx uninstall viewmd
94
+
95
+ # If installed with pip
96
+ pip uninstall viewmd
97
+ ```
98
+
99
+ ## Publishing to PyPI (Optional)
100
+
101
+ To make viewmd installable via `pip install viewmd` for everyone:
102
+
103
+ ```bash
104
+ # Install build tools
105
+ pip install build twine
106
+
107
+ # Build the package
108
+ python -m build
109
+
110
+ # Upload to PyPI (requires account at pypi.org)
111
+ python -m twine upload dist/*
112
+ ```
113
+
114
+ That's it. Simple.
viewmd-0.1.0/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # viewmd
2
+
3
+ A simple HTTP server for viewing Markdown files in your browser.
4
+
5
+ ## Installation
6
+
7
+ ### Option 1: Using pipx (Recommended)
8
+
9
+ [pipx](https://pypa.github.io/pipx/) installs CLI tools in isolated environments, preventing dependency conflicts:
10
+
11
+ ```bash
12
+ # Install pipx if you don't have it
13
+ python3 -m pip install --user pipx
14
+ python3 -m pipx ensurepath
15
+
16
+ # Install viewmd
17
+ pipx install .
18
+ ```
19
+
20
+ ### Option 2: Using pip
21
+
22
+ Install globally with pip:
23
+
24
+ ```bash
25
+ pip install .
26
+ ```
27
+
28
+ Or install in development mode (changes to source code take effect immediately):
29
+
30
+ ```bash
31
+ pip install -e .
32
+ ```
33
+
34
+ ### Option 3: Direct symlink (Manual)
35
+
36
+ ```bash
37
+ chmod +x viewmd.py
38
+ sudo ln -s "$(pwd)/viewmd.py" /usr/local/bin/viewmd
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ```bash
44
+ # Run from any directory
45
+ viewmd
46
+
47
+ # Open http://localhost:8000
48
+ ```
49
+
50
+ ## What It Does
51
+
52
+ - **Markdown files** (`.md`) - Rendered as HTML with nice styling
53
+ - **Text files** (`.py`, `.json`, `.gitignore`, etc.) - Displayed in browser
54
+ - **Directories** - Shows file listing, auto-displays `README.md`
55
+ - **Other files** - Served normally (images, PDFs, etc.)
56
+
57
+ ## Usage
58
+
59
+ ```bash
60
+ viewmd # Starts on port 8000
61
+ viewmd 3000 # Custom port
62
+ ```
63
+
64
+ ## Uninstallation
65
+
66
+ ```bash
67
+ # If installed with pipx
68
+ pipx uninstall viewmd
69
+
70
+ # If installed with pip
71
+ pip uninstall viewmd
72
+ ```
73
+
74
+ ## Publishing to PyPI (Optional)
75
+
76
+ To make viewmd installable via `pip install viewmd` for everyone:
77
+
78
+ ```bash
79
+ # Install build tools
80
+ pip install build twine
81
+
82
+ # Build the package
83
+ python -m build
84
+
85
+ # Upload to PyPI (requires account at pypi.org)
86
+ python -m twine upload dist/*
87
+ ```
88
+
89
+ That's it. Simple.
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=45", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "viewmd"
7
+ version = "0.1.0"
8
+ description = "Simple HTTP server for viewing Markdown files in your browser"
9
+ readme = "README.md"
10
+ requires-python = ">=3.7"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "German Greiner"}
14
+ ]
15
+ keywords = ["markdown", "viewer", "http-server", "cli"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Environment :: Console",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.7",
23
+ "Programming Language :: Python :: 3.8",
24
+ "Programming Language :: Python :: 3.9",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Topic :: Utilities",
28
+ ]
29
+ dependencies = [
30
+ "markdown>=3.0",
31
+ ]
32
+
33
+ [project.scripts]
34
+ viewmd = "viewmd:main"
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/driangle/viewmd"
@@ -0,0 +1 @@
1
+ markdown>=3.0
viewmd-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,114 @@
1
+ Metadata-Version: 2.4
2
+ Name: viewmd
3
+ Version: 0.1.0
4
+ Summary: Simple HTTP server for viewing Markdown files in your browser
5
+ Author: German Greiner
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/driangle/viewmd
8
+ Keywords: markdown,viewer,http-server,cli
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.7
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Topic :: Utilities
20
+ Requires-Python: >=3.7
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: markdown>=3.0
24
+ Dynamic: license-file
25
+
26
+ # viewmd
27
+
28
+ A simple HTTP server for viewing Markdown files in your browser.
29
+
30
+ ## Installation
31
+
32
+ ### Option 1: Using pipx (Recommended)
33
+
34
+ [pipx](https://pypa.github.io/pipx/) installs CLI tools in isolated environments, preventing dependency conflicts:
35
+
36
+ ```bash
37
+ # Install pipx if you don't have it
38
+ python3 -m pip install --user pipx
39
+ python3 -m pipx ensurepath
40
+
41
+ # Install viewmd
42
+ pipx install .
43
+ ```
44
+
45
+ ### Option 2: Using pip
46
+
47
+ Install globally with pip:
48
+
49
+ ```bash
50
+ pip install .
51
+ ```
52
+
53
+ Or install in development mode (changes to source code take effect immediately):
54
+
55
+ ```bash
56
+ pip install -e .
57
+ ```
58
+
59
+ ### Option 3: Direct symlink (Manual)
60
+
61
+ ```bash
62
+ chmod +x viewmd.py
63
+ sudo ln -s "$(pwd)/viewmd.py" /usr/local/bin/viewmd
64
+ ```
65
+
66
+ ## Quick Start
67
+
68
+ ```bash
69
+ # Run from any directory
70
+ viewmd
71
+
72
+ # Open http://localhost:8000
73
+ ```
74
+
75
+ ## What It Does
76
+
77
+ - **Markdown files** (`.md`) - Rendered as HTML with nice styling
78
+ - **Text files** (`.py`, `.json`, `.gitignore`, etc.) - Displayed in browser
79
+ - **Directories** - Shows file listing, auto-displays `README.md`
80
+ - **Other files** - Served normally (images, PDFs, etc.)
81
+
82
+ ## Usage
83
+
84
+ ```bash
85
+ viewmd # Starts on port 8000
86
+ viewmd 3000 # Custom port
87
+ ```
88
+
89
+ ## Uninstallation
90
+
91
+ ```bash
92
+ # If installed with pipx
93
+ pipx uninstall viewmd
94
+
95
+ # If installed with pip
96
+ pip uninstall viewmd
97
+ ```
98
+
99
+ ## Publishing to PyPI (Optional)
100
+
101
+ To make viewmd installable via `pip install viewmd` for everyone:
102
+
103
+ ```bash
104
+ # Install build tools
105
+ pip install build twine
106
+
107
+ # Build the package
108
+ python -m build
109
+
110
+ # Upload to PyPI (requires account at pypi.org)
111
+ python -m twine upload dist/*
112
+ ```
113
+
114
+ That's it. Simple.
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ requirements.txt
6
+ viewmd.py
7
+ viewmd.egg-info/PKG-INFO
8
+ viewmd.egg-info/SOURCES.txt
9
+ viewmd.egg-info/dependency_links.txt
10
+ viewmd.egg-info/entry_points.txt
11
+ viewmd.egg-info/requires.txt
12
+ viewmd.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ viewmd = viewmd:main
@@ -0,0 +1 @@
1
+ markdown>=3.0
@@ -0,0 +1 @@
1
+ viewmd
viewmd-0.1.0/viewmd.py ADDED
@@ -0,0 +1,361 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ viewmd - Simple HTTP server for viewing Markdown files in your browser.
4
+ Usage: viewmd [port]
5
+ """
6
+ import os
7
+ import sys
8
+ import html
9
+ from http.server import HTTPServer, SimpleHTTPRequestHandler
10
+ from pathlib import Path
11
+ import urllib.parse
12
+ from datetime import datetime
13
+
14
+ VERSION = "0.1.0"
15
+
16
+ try:
17
+ import markdown
18
+ except ImportError:
19
+ print("Error: 'markdown' package not found.")
20
+ print("Install it with: pip3 install markdown")
21
+ sys.exit(1)
22
+
23
+
24
+ class MarkdownHandler(SimpleHTTPRequestHandler):
25
+ # Text file extensions to display in browser
26
+ TEXT_EXTENSIONS = {
27
+ '.txt', '.log', '.json', '.xml', '.yaml', '.yml', '.toml', '.ini', '.cfg', '.conf',
28
+ '.sh', '.bash', '.zsh', '.fish', '.py', '.js', '.ts', '.jsx', '.tsx', '.java', '.c',
29
+ '.cpp', '.h', '.hpp', '.cs', '.go', '.rs', '.rb', '.php', '.swift', '.kt', '.sql',
30
+ '.html', '.css', '.scss', '.sass', '.less', '.vue', '.svelte', '.r', '.m', '.scala',
31
+ '.pl', '.lua', '.vim', '.el', '.clj', '.ex', '.exs', '.dockerfile', '.env', '.gitignore',
32
+ '.gitattributes', '.editorconfig', '.eslintrc', '.prettierrc', '.babelrc'
33
+ }
34
+
35
+ # Files without extensions that are typically text
36
+ TEXT_FILENAMES = {
37
+ 'makefile', 'dockerfile', 'gemfile', 'rakefile', 'procfile', 'jenkinsfile',
38
+ 'license', 'readme', 'changelog', 'authors', 'contributors', 'codeowners'
39
+ }
40
+
41
+ def do_GET(self):
42
+ # Parse the URL and remove query parameters
43
+ parsed_path = urllib.parse.urlparse(self.path)
44
+ path = parsed_path.path.lstrip('/')
45
+
46
+ # Decode URL encoding
47
+ path = urllib.parse.unquote(path)
48
+
49
+ print(f"[{datetime.now().strftime('%H:%M:%S')}] Request: {path or '/'}")
50
+
51
+ # Handle root directory
52
+ if not path:
53
+ print(f" → Serving directory listing")
54
+ self.serve_directory_listing()
55
+ return
56
+
57
+ # Get the file path
58
+ file_path = Path(path)
59
+
60
+ # Check if it's a directory
61
+ if file_path.is_dir():
62
+ # Check for README.md in the directory
63
+ readme_path = file_path / "README.md"
64
+ if readme_path.is_file():
65
+ print(f" → Rendering markdown: {readme_path}")
66
+ self.serve_markdown(readme_path)
67
+ else:
68
+ print(f" → Serving directory listing")
69
+ self.serve_directory_listing()
70
+ return
71
+
72
+ # Check if file exists
73
+ if not file_path.is_file():
74
+ print(f" → File not found, using default handler")
75
+ super().do_GET()
76
+ return
77
+
78
+ # Check if it's a markdown file
79
+ if file_path.suffix.lower() in ['.md', '.markdown']:
80
+ print(f" → Rendering markdown: {file_path}")
81
+ self.serve_markdown(file_path)
82
+ # Check if it's a text file
83
+ elif self.is_text_file(file_path):
84
+ print(f" → Displaying text file: {file_path}")
85
+ self.serve_text_file(file_path)
86
+ else:
87
+ # Serve other files normally (images, PDFs, etc.)
88
+ print(f" → Serving binary file: {file_path}")
89
+ super().do_GET()
90
+
91
+ def is_text_file(self, file_path):
92
+ """Check if a file should be displayed as text."""
93
+ filename = file_path.name
94
+
95
+ # Check extension
96
+ if file_path.suffix.lower() in self.TEXT_EXTENSIONS:
97
+ print(f" → Detected as text (extension: {file_path.suffix})")
98
+ return True
99
+
100
+ # Check filename (for files without extensions)
101
+ if filename.lower() in self.TEXT_FILENAMES:
102
+ print(f" → Detected as text (known filename: {filename})")
103
+ return True
104
+
105
+ # Check if filename starts with a dot (hidden config files like .gitignore, .env, etc.)
106
+ # In pathlib, .gitignore has suffix='.gitignore' and stem='', so we check if name starts with '.'
107
+ if filename.startswith('.'):
108
+ print(f" → Detected as text (dotfile: {filename})")
109
+ return True
110
+
111
+ print(f" → Not detected as text file")
112
+ return False
113
+
114
+ def serve_markdown(self, file_path):
115
+ try:
116
+ with open(file_path, 'r', encoding='utf-8') as f:
117
+ content = f.read()
118
+
119
+ # Convert markdown to HTML
120
+ md = markdown.Markdown(extensions=['fenced_code', 'tables', 'nl2br'])
121
+ html_content = md.convert(content)
122
+
123
+ # Calculate the base URL for relative links
124
+ # This ensures links in markdown are relative to the file's directory
125
+ base_path = file_path.parent if file_path.parent != Path('.') else Path('/')
126
+ base_url = f"/{base_path}/" if str(base_path) != '.' else "/"
127
+
128
+ # Wrap in a nice HTML template
129
+ html = f"""<!DOCTYPE html>
130
+ <html>
131
+ <head>
132
+ <meta charset="utf-8">
133
+ <meta name="viewport" content="width=device-width, initial-scale=1">
134
+ <base href="{base_url}">
135
+ <title>{file_path.name}</title>
136
+ <style>
137
+ body {{
138
+ max-width: 800px;
139
+ margin: 40px auto;
140
+ padding: 0 20px;
141
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
142
+ line-height: 1.6;
143
+ color: #333;
144
+ }}
145
+ pre {{
146
+ background: #f4f4f4;
147
+ border: 1px solid #ddd;
148
+ border-radius: 4px;
149
+ padding: 12px;
150
+ overflow-x: auto;
151
+ }}
152
+ code {{
153
+ background: #f4f4f4;
154
+ padding: 2px 6px;
155
+ border-radius: 3px;
156
+ font-family: 'Courier New', monospace;
157
+ }}
158
+ pre code {{
159
+ background: none;
160
+ padding: 0;
161
+ }}
162
+ table {{
163
+ border-collapse: collapse;
164
+ width: 100%;
165
+ margin: 20px 0;
166
+ }}
167
+ th, td {{
168
+ border: 1px solid #ddd;
169
+ padding: 8px 12px;
170
+ text-align: left;
171
+ }}
172
+ th {{
173
+ background: #f4f4f4;
174
+ }}
175
+ a {{
176
+ color: #0066cc;
177
+ text-decoration: none;
178
+ }}
179
+ a:hover {{
180
+ text-decoration: underline;
181
+ }}
182
+ blockquote {{
183
+ border-left: 4px solid #ddd;
184
+ margin: 0;
185
+ padding-left: 20px;
186
+ color: #666;
187
+ }}
188
+ img {{
189
+ max-width: 100%;
190
+ height: auto;
191
+ }}
192
+ </style>
193
+ </head>
194
+ <body>
195
+ {html_content}
196
+ </body>
197
+ </html>"""
198
+
199
+ # Send response
200
+ self.send_response(200)
201
+ self.send_header('Content-type', 'text/html; charset=utf-8')
202
+ self.end_headers()
203
+ self.wfile.write(html.encode('utf-8'))
204
+
205
+ except Exception as e:
206
+ self.send_error(500, f"Error rendering markdown: {str(e)}")
207
+
208
+ def serve_text_file(self, file_path):
209
+ """Serve a text file with HTML formatting."""
210
+ try:
211
+ with open(file_path, 'r', encoding='utf-8') as f:
212
+ content = f.read()
213
+
214
+ # Escape HTML characters
215
+ escaped_content = html.escape(content)
216
+
217
+ # Wrap in HTML template
218
+ html_output = f"""<!DOCTYPE html>
219
+ <html>
220
+ <head>
221
+ <meta charset="utf-8">
222
+ <meta name="viewport" content="width=device-width, initial-scale=1">
223
+ <title>{file_path.name}</title>
224
+ <style>
225
+ body {{
226
+ max-width: 1000px;
227
+ margin: 20px auto;
228
+ padding: 0 20px;
229
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
230
+ background: #f6f8fa;
231
+ }}
232
+ .header {{
233
+ background: white;
234
+ border: 1px solid #d0d7de;
235
+ border-radius: 6px 6px 0 0;
236
+ padding: 12px 16px;
237
+ font-weight: 600;
238
+ border-bottom: 1px solid #d0d7de;
239
+ }}
240
+ .content {{
241
+ background: white;
242
+ border: 1px solid #d0d7de;
243
+ border-top: none;
244
+ border-radius: 0 0 6px 6px;
245
+ padding: 16px;
246
+ overflow-x: auto;
247
+ }}
248
+ pre {{
249
+ margin: 0;
250
+ font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Mono', 'Courier New', monospace;
251
+ font-size: 12px;
252
+ line-height: 1.5;
253
+ white-space: pre;
254
+ word-wrap: normal;
255
+ }}
256
+ </style>
257
+ </head>
258
+ <body>
259
+ <div class="header">{file_path.name}</div>
260
+ <div class="content">
261
+ <pre>{escaped_content}</pre>
262
+ </div>
263
+ </body>
264
+ </html>"""
265
+
266
+ # Send response
267
+ self.send_response(200)
268
+ self.send_header('Content-type', 'text/html; charset=utf-8')
269
+ self.end_headers()
270
+ self.wfile.write(html_output.encode('utf-8'))
271
+
272
+ except UnicodeDecodeError:
273
+ # If it's not valid UTF-8, serve it normally (might be binary)
274
+ super().do_GET()
275
+ except Exception as e:
276
+ self.send_error(500, f"Error displaying file: {str(e)}")
277
+
278
+ def serve_directory_listing(self):
279
+ try:
280
+ parsed_path = urllib.parse.urlparse(self.path)
281
+ path = parsed_path.path.lstrip('/') or '.'
282
+ path = urllib.parse.unquote(path)
283
+
284
+ dir_path = Path(path)
285
+ if not dir_path.is_dir():
286
+ self.send_error(404, "Directory not found")
287
+ return
288
+
289
+ items = sorted(dir_path.iterdir(), key=lambda x: (not x.is_dir(), x.name.lower()))
290
+
291
+ html = """<!DOCTYPE html>
292
+ <html>
293
+ <head>
294
+ <meta charset="utf-8">
295
+ <meta name="viewport" content="width=device-width, initial-scale=1">
296
+ <title>Directory listing</title>
297
+ <style>
298
+ body {
299
+ max-width: 800px;
300
+ margin: 40px auto;
301
+ padding: 0 20px;
302
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
303
+ }
304
+ ul { list-style: none; padding: 0; }
305
+ li { margin: 8px 0; }
306
+ a { text-decoration: none; color: #0066cc; }
307
+ a:hover { text-decoration: underline; }
308
+ .dir::before { content: '📁 '; }
309
+ .file::before { content: '📄 '; }
310
+ </style>
311
+ </head>
312
+ <body>
313
+ """
314
+ html += f"<h1>Directory: /{path}</h1>\n<ul>\n"
315
+
316
+ if path != '.':
317
+ parent = dir_path.parent
318
+ html += f'<li><a href="/{parent}" class="dir">..</a></li>\n'
319
+
320
+ for item in items:
321
+ rel_path = item.relative_to('.')
322
+ if item.is_dir():
323
+ html += f'<li><a href="/{rel_path}" class="dir">{item.name}/</a></li>\n'
324
+ else:
325
+ html += f'<li><a href="/{rel_path}" class="file">{item.name}</a></li>\n'
326
+
327
+ html += "</ul>\n</body>\n</html>"
328
+
329
+ self.send_response(200)
330
+ self.send_header('Content-type', 'text/html; charset=utf-8')
331
+ self.end_headers()
332
+ self.wfile.write(html.encode('utf-8'))
333
+
334
+ except Exception as e:
335
+ self.send_error(500, f"Error listing directory: {str(e)}")
336
+
337
+
338
+ def main():
339
+ port = int(sys.argv[1]) if len(sys.argv) > 1 else 8000
340
+
341
+ server = HTTPServer(('', port), MarkdownHandler)
342
+ print(f"=" * 60)
343
+ print(f"Markdown Server v{VERSION}")
344
+ print(f"=" * 60)
345
+ print(f"Server: http://localhost:{port}")
346
+ print(f"Features:")
347
+ print(f" - Markdown rendering (.md, .markdown)")
348
+ print(f" - Text file viewer (.py, .js, .gitignore, etc.)")
349
+ print(f" - Directory browsing")
350
+ print(f"=" * 60)
351
+ print("Press Ctrl+C to stop\n")
352
+
353
+ try:
354
+ server.serve_forever()
355
+ except KeyboardInterrupt:
356
+ print("\nShutting down...")
357
+ server.shutdown()
358
+
359
+
360
+ if __name__ == '__main__':
361
+ main()