Files
sincos.cc-website/blog/build.py
T

262 lines
8.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
博客构建脚本:blog/posts/*.md -> website/static/blog/(列表 + 文章页)+ website/static/home.html(首页)
front matter 格式:
---
title: 文章标题
date: 2026-08-13
tags: 工业软件, ResLib
---
"""
import re
import sys
from datetime import datetime
from pathlib import Path
import markdown
BASE = Path(__file__).resolve().parent
POSTS_DIR = BASE / "posts"
OUT_BLOG = BASE.parent / "website" / "static" / "blog"
OUT_HOME = BASE.parent / "website" / "static" / "home.html"
OUT_ABOUT = BASE.parent / "website" / "static" / "about.html"
SITE_NAME = "Easy Data Collector"
SEO_NAME = "江北的渔夫的空间"
NAV = [
("/", "博客"),
("/reslib/", "ResLib"),
("/reslib/apply", "申请 License"),
("/reslib/status", "查询 / 下载"),
("/reslib/package", "资源包获取"),
("/neural/", "Neural Research"),
("http://124.220.63.9:25000", "Research Digest"),
]
def page_header(active: str, title: str = "") -> str:
seo_title = f"{title} - {SEO_NAME}" if title else SEO_NAME
links = "\n".join(
f' <a href="{href}"{" class=active" if href == active else ""}>{label}</a>'
for href, label in NAV
)
return f"""<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{seo_title}</title>
<meta name="description" content="{SEO_NAME} —— 工业软件、标准件库与数字化实践分享">
<meta name="keywords" content="{SEO_NAME}, Easy Data Collector, ResLib, 资源中心管理器, Creo, 标准零部件库, 数字孪生">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="icon" href="/static/img/logo.svg" type="image/svg+xml">
</head>
<body>
<header class="site-header">
<div class="container header-inner">
<a class="brand" href="/">
<img src="/static/img/logo.svg" alt="{SITE_NAME}">
<span class="brand-name">{SITE_NAME}</span>
</a>
<nav class="main-nav" id="mainNav">
{links}
</nav>
<button class="nav-toggle" id="navToggle" aria-label="菜单">☰</button>
</div>
</header>
<main>"""
def page_footer() -> str:
return f"""</main>
<footer class="site-footer">
<div class="container footer-inner">
<span>Copyright &copy; 2026 {SITE_NAME}. All rights reserved.</span>
<span><a href="/links.html">内部资源</a> · <a href="/admin">管理页面</a> · <a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">苏ICP备2026056863号</a></span>
</div>
</footer>
<script src="/static/js/nav.js"></script>
</body>
</html>"""
def parse_post(path: Path) -> dict:
text = path.read_text(encoding="utf-8")
m = re.match(r"^---\s*\n(.*?)\n---\s*\n?(.*)$", text, re.S)
front, body = ("", text)
if m:
front, body = m.group(1), m.group(2)
meta = {}
for line in front.splitlines():
if ":" in line:
k, _, v = line.partition(":")
meta[k.strip()] = v.strip()
html = markdown.markdown(
body,
extensions=["extra", "toc", "sane_lists", "attr_list", "admonition"],
)
return {
"slug": path.stem,
"title": meta.get("title", path.stem),
"date": meta.get("date", ""),
"tags": [t.strip() for t in meta.get("tags", "").split(",") if t.strip()],
"html": html,
}
def summary(html: str, length: int = 120) -> str:
text = re.sub(r"<[^>]+>", "", html)
text = re.sub(r"\s+", " ", text).strip()
if len(text) > length:
text = text[:length] + "…"
return text
def fmt_date(s: str) -> str:
try:
return datetime.strptime(s, "%Y-%m-%d").strftime("%Y年%m月%d日")
except ValueError:
return s
def build():
posts = [parse_post(p) for p in sorted(POSTS_DIR.glob("*.md"), reverse=True)]
posts.sort(key=lambda p: p["date"], reverse=True)
OUT_BLOG.mkdir(parents=True, exist_ok=True)
# ---------- 博客列表页 ----------
cards = []
for p in posts:
tags = "".join(f'<span class="tag">{t}</span>' for t in p["tags"])
cards.append(f"""
<a class="post-card" href="/blog/{p['slug']}/">
<div class="post-meta"><span class="post-date">{fmt_date(p['date'])}</span>{tags}</div>
<h3 class="post-title">{p['title']}</h3>
<p class="post-excerpt">{summary(p['html'])}</p>
</a>""")
if not cards:
cards.append('<p class="section-desc">暂无文章,敬请期待。</p>')
blog_list = f"""{page_header("/blog/")}
<section class="section">
<div class="container">
<h2 class="section-title">博客</h2>
<p class="section-desc">工业软件、标准件库与数字化实践记录。</p>
<div class="post-list">
{''.join(cards)}
</div>
</div>
</section>
{page_footer()}"""
(OUT_BLOG / "index.html").write_text(blog_list, encoding="utf-8")
# ---------- 文章详情页 ----------
for p in posts:
tags = "".join(f'<span class="tag">{t}</span>' for t in p["tags"])
html = f"""{page_header(p['slug'], p['title'])}
<section class="section">
<div class="container narrow">
<nav class="breadcrumb"><a href="/">博客</a> / <a href="/blog/">文章列表</a></nav>
<h2 class="section-title">{p['title']}</h2>
<div class="post-meta"><span class="post-date">{fmt_date(p['date'])}</span>{tags}</div>
<article class="article">
{p['html']}
</article>
</div>
</section>
{page_footer()}"""
(OUT_BLOG / p["slug"]).mkdir(parents=True, exist_ok=True)
(OUT_BLOG / p["slug"] / "index.html").write_text(html, encoding="utf-8")
# ---------- 首页(博客首页) ----------
recent = "".join(f"""
<a class="post-card" href="/blog/{p['slug']}/">
<div class="post-meta"><span class="post-date">{fmt_date(p['date'])}</span></div>
<h3 class="post-title">{p['title']}</h3>
<p class="post-excerpt">{summary(p['html'], 90)}</p>
</a>""" for p in posts[:3])
if not recent:
recent = '<p class="section-desc">暂无文章,敬请期待。</p>'
home = f"""{page_header("/")}
<section class="hero">
<div class="hero-euler" aria-hidden="true">e<sup>iπ</sup> + 1 = 0</div>
<div class="container">
<p class="hero-eyebrow">INDUSTRIAL SOFTWARE INTEGRATION · STANDARDIZED MANAGEMENT · SKU REDUCTION · NEURAL NETWORK PRACTICE</p>
<h1>数字化和人工智能</h1>
<p class="hero-sub">工业软件集成 · 标准化管理 · SKU 降低 · 神经网络实践等 —— 基于 LLM 的流程工业技术研究和探索</p>
<div class="hero-actions">
<a href="/blog/" class="btn btn-light">全部文章</a>
<a href="/reslib/" class="btn btn-outline-light">ResLib 产品</a>
</div>
</div>
</section>
<section class="section">
<div class="container">
<h2 class="section-title">平台</h2>
<div class="grid">
<a class="card card-link" href="/reslib/">
<div class="icon">▤</div><h3>ResLib</h3><p>资源中心管理器 —— Creo 7.0+ 企业级标准零部件管理库,含 LIC 授权体系与在线申请。</p>
</a>
<a class="card card-link" href="/neural/">
<div class="icon">◈</div><h3>Neural Research</h3><p>NeuralMix AI —— 工业混合机智能终点判定引擎,1D-CNN 深度学习评估混合均匀度。</p>
</a>
</div>
</div>
</section>
<section class="section section-alt">
<div class="container">
<h2 class="section-title">最新文章</h2>
<div class="post-list">
{recent}
</div>
<div style="text-align:center; margin-top: 30px;">
<a href="/blog/" class="btn btn-primary">查看全部文章</a>
</div>
</div>
</section>
{page_footer()}"""
OUT_HOME.write_text(home, encoding="utf-8")
# ---------- 关于页 ----------
about = f"""{page_header("/about/")}
<section class="section">
<div class="container narrow">
<h2 class="section-title">关于</h2>
<div class="article">
<h3>SinCos</h3>
<p>工业软件方向开发者。</p>
<h3>正在做</h3>
<ul>
<li><b>ResLib</b>:资源中心管理器 —— Creo 7.0+ 标准零部件管理库(Pro/TOOLKIT DLL 插件),支持 License 在线申请与审批</li>
<li><b>Engineer Craft</b>:工程实践与工艺设计平台</li>
<li><b>Digital Twin</b>:数字孪生平台(three.js + Python + MQTT/EMQX)</li>
<li><b>Neural Research</b>:神经网络与算法研究</li>
<li><b>Gitea Project</b>:代码托管与版本管理</li>
</ul>
<h3>联系方式</h3>
<ul>
<li>博客:<a href="/">sincos.cc</a></li>
<li>License 申请:<a href="/reslib/apply">/reslib/apply</a></li>
</ul>
</div>
</div>
</section>
{page_footer()}"""
OUT_ABOUT.write_text(about, encoding="utf-8")
print(f"构建完成:{len(posts)} 篇文章 -> {OUT_BLOG} / home.html / about.html")
if __name__ == "__main__":
build()