📂 solution-1.0

← 返回上级
#!/usr/bin/env python3
"""代码浏览器 — 只读,为 /root/solution-1.0/ 提供 GitHub 风格的文件浏览"""
import http.server
import os
import urllib.parse
import json

ROOT = '/root/solution-1.0'
PORT = 8082

# 文件类型图标
ICON_MAP = {
    '.py': '🐍', '.js': '🟨', '.html': '🌐', '.css': '🎨',
    '.md': '📝', '.json': '📋', '.yaml': '⚙️', '.yml': '⚙️',
    '.sh': '💻', '.conf': '⚙️', '.txt': '📄', '.env': '🔒',
    '.gitignore': '🚫', '.example': '📋',
}

def get_icon(name):
    _, ext = os.path.splitext(name)
    return ICON_MAP.get(ext, '📄')

class CodeBrowserHandler(http.server.SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=ROOT, **kwargs)

    def log_message(self, format, *args):
        pass  # 安静日志

    TEXT_EXTS = {'.py','.js','.html','.css','.md','.json','.yaml','.yml',
                 '.sh','.conf','.txt','.env','.example','.gitignore','.toml'}

    LANG_MAP = {'.py':'python','.js':'javascript','.html':'html','.css':'css',
                '.md':'markdown','.json':'json','.yaml':'yaml','.yml':'yaml',
                '.sh':'bash','.conf':'nginx','.toml':'toml'}

    def send_head(self):
        path = self.translate_path(self.path)
        if os.path.isdir(path):
            return self.list_directory_html(path)
        # 代码文件 → 生成语法高亮 HTML(在发 headers 之前完成)
        if os.path.isfile(path):
            _, ext = os.path.splitext(path)
            if ext in self.TEXT_EXTS or not ext:
                html = self._render_source_page(path, ext)
                if html:
                    encoded = html.encode('utf-8')
                    self.send_response(200)
                    self.send_header('Content-Type', 'text/html; charset=utf-8')
                    self.send_header('Content-Length', str(len(encoded)))
                    self.send_header('Cache-Control', 'no-cache')
                    self.end_headers()
                    return self._buffer(encoded)
        return super().send_head()

    def _render_source_page(self, path, ext):
        """生成语法高亮 HTML 源码页"""
        try:
            content = open(path, 'rb').read()
            text = content.decode('utf-8')
        except:
            return None
        lang = self.LANG_MAP.get(ext, 'plaintext')
        rel = os.path.relpath(path, ROOT)
        breadcrumb = self._breadcrumb(os.path.dirname(rel))
        escaped = text.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;')
        return f'''<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="utf-8">
<title>{os.path.basename(path)} - solution-1.0</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script>hljs.highlightAll();</script>
<style>
* {{ margin:0; padding:0; box-sizing:border-box; }}
body {{ background:#0d1117; color:#c9d1d9; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; }}
.header {{ background:#161b22; border-bottom:1px solid #30363d; padding:16px 24px; }}
.header h1 a {{ color:#58a6ff; text-decoration:none; font-size:16px; }}
.breadcrumb {{ padding:8px 24px; font-size:14px; color:#58a6ff; }}
.breadcrumb a {{ color:#58a6ff; text-decoration:none; }}
.breadcrumb a:hover {{ text-decoration:underline; }}
.breadcrumb .sep {{ color:#484f58; padding:0 4px; }}
.code-view pre {{ padding:16px 24px; font-size:13px; line-height:1.5; tab-size:4; margin:0; overflow-x:auto; }}
.code-view pre code {{ background:transparent !important; padding:0 !important; }}
.back {{ display:block; padding:8px 24px; color:#58a6ff; font-size:14px; text-decoration:none; }}
.back:hover {{ text-decoration:underline; }}
</style>
</head>
<body>
<div class="header"><h1><a href="/">📂 solution-1.0</a></h1></div>
<div class="breadcrumb">{breadcrumb}</div>
<a class="back" href="{os.path.dirname(self.path)}">← 返回上级</a>
<div class="code-view"><pre><code class="language-{lang}">{escaped}</code></pre></div>
</body>
</html>'''

    def list_directory_html(self, path):
        """生成 GitHub 风格的文件列表页面"""
        entries = []
        try:
            for name in sorted(os.listdir(path)):
                if name.startswith('.'):
                    continue
                full = os.path.join(path, name)
                is_dir = os.path.isdir(full)
                size = os.path.getsize(full) if not is_dir else ''
                icon = get_icon(name) if not is_dir else '📁'
                entries.append((name, is_dir, size, icon))
        except OSError:
            pass

        rel = os.path.relpath(path, ROOT)
        breadcrumb = self._breadcrumb(rel)

        html = f'''<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{'solution-1.0/' + rel if rel != '.' else 'solution-1.0/'}</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<style>
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
        background: #0d1117; color: #c9d1d9; }}
.header {{ background: #161b22; border-bottom: 1px solid #30363d; padding: 16px 24px; }}
.header h1 {{ font-size: 16px; font-weight: 600; }}
.header h1 a {{ color: #58a6ff; text-decoration: none; }}
.header h1 a:hover {{ text-decoration: underline; }}
.header .sub {{ color: #8b949e; font-size: 12px; margin-top: 4px; }}
.breadcrumb {{ padding: 8px 24px; background: #0d1117; border-bottom: 1px solid #21262d;
               font-size: 14px; color: #58a6ff; }}
.breadcrumb a {{ color: #58a6ff; text-decoration: none; }}
.breadcrumb a:hover {{ text-decoration: underline; }}
.breadcrumb .sep {{ color: #484f58; padding: 0 4px; }}
.file-list {{ margin: 0 24px; }}
.file-row {{ display: flex; align-items: center; padding: 8px 0; border-bottom: 1px solid #21262d;
              font-size: 14px; cursor: pointer; }}
.file-row:hover {{ background: #161b22; }}
.file-row .icon {{ width: 24px; text-align: center; font-size: 16px; }}
.file-row .name {{ flex: 1; margin-left: 8px; }}
.file-row .name a {{ color: #c9d1d9; text-decoration: none; }}
.file-row:hover .name a {{ color: #58a6ff; }}
.file-row .size {{ color: #8b949e; font-size: 12px; width: 80px; text-align: right; }}
.code-view {{ padding: 0; max-width: 100%; overflow-x: auto; }}
.code-view pre {{ padding: 16px 24px; font-size: 13px; line-height: 1.5; tab-size: 4; }}
.code-view pre code {{ background: transparent; padding: 0; }}
.back-link {{ display: inline-block; margin: 16px 24px; color: #58a6ff; text-decoration: none; font-size: 14px; }}
.back-link:hover {{ text-decoration: underline; }}
</style>
</head>
<body>
<div class="header">
  <h1><a href="/">📂 solution-1.0</a></h1>
  <div class="sub">Hermes Agent Solution · 只读代码浏览器</div>
</div>
<div class="breadcrumb">{breadcrumb}</div>
<div class="file-list">
'''
        # 父目录
        if rel != '.':
            parent = os.path.dirname(rel)
            if parent == '.':
                parent = ''
            html += f'''<div class="file-row">
  <div class="icon">📂</div>
  <div class="name"><a href="/{parent}">..</a></div>
  <div class="size"></div>
</div>'''

        for name, is_dir, size, icon in entries:
            href = urllib.parse.quote(name)
            if is_dir:
                href += '/'
            size_str = self._format_size(size) if size != '' else ''
            html += f'''<div class="file-row">
  <div class="icon">{icon}</div>
  <div class="name"><a href="{href}">{name}</a></div>
  <div class="size">{size_str}</div>
</div>'''

        html += '''</div>
</body>
</html>'''
        encoded = html.encode('utf-8')
        self.send_response(200)
        self.send_header('Content-Type', 'text/html; charset=utf-8')
        self.send_header('Content-Length', str(len(encoded)))
        self.send_header('Cache-Control', 'no-cache')
        self.end_headers()
        return self._buffer(html.encode('utf-8'))

    def _buffer(self, data):
        import io
        buf = io.BytesIO(data)
        buf.seek(0)
        return buf

    def _breadcrumb(self, rel):
        parts = rel.split(os.sep) if rel != '.' else []
        html = '<a href="/">solution-1.0</a>'
        path = ''
        for p in parts:
            path = os.path.join(path, p)
            html += f'<span class="sep">/</span><a href="/{path}">{p}</a>'
        return html

    def _format_size(self, size):
        for unit in ['B', 'KB', 'MB', 'GB']:
            if size < 1024:
                return f'{size:.1f} {unit}' if unit != 'B' else f'{size} B'
            size /= 1024
        return f'{size:.1f} TB'

    def copyfile(self, source, outputfile):
        """委托给父类(所有逻辑已移到 send_head)"""
        return super().copyfile(source, outputfile)

if __name__ == '__main__':
    server = http.server.HTTPServer(('127.0.0.1', PORT), CodeBrowserHandler)
    print(f'📂 Code Browser: http://127.0.0.1:{PORT}/')
    print(f'📁 Serving: {ROOT}')
    server.serve_forever()