当前位置: 首页 > news >正文

Mkdocs 阅读时间统计插件

预览效果:https://wcowin.work/mkdocs-reading-time/

1. 基础配置

步骤1

创建reading_time.py

import re
import threading
import time
from functools import lru_cache
from collections import OrderedDict
import hashlib# 预编译正则表达式(性能优化版本)
EXCLUDE_PATTERNS = [re.compile(r'^index\.md$'),re.compile(r'^trip/index\.md$'),re.compile(r'^relax/index\.md$'),re.compile(r'^blog/indexblog\.md$'),re.compile(r'^blog/posts\.md$'),re.compile(r'^develop/index\.md$'),re.compile(r'waline\.md$'),re.compile(r'link\.md$'),re.compile(r'404\.md$'),
]# 高度优化的正则表达式(一次性编译)
CHINESE_CHARS_PATTERN = re.compile(r'[\u4e00-\u9fff\u3400-\u4dbf]')
CODE_BLOCK_PATTERN = re.compile(r'```.*?```', re.DOTALL)
INLINE_CODE_PATTERN = re.compile(r'`[^`]+`')
YAML_FRONT_PATTERN = re.compile(r'^---.*?---\s*', re.DOTALL)
HTML_TAG_PATTERN = re.compile(r'<[^>]+>')
IMAGE_PATTERN = re.compile(r'!\[.*?\]\([^)]+\)')
LINK_PATTERN = re.compile(r'\[([^\]]+)\]\([^)]+\)')# 预定义排除类型
EXCLUDE_TYPES = frozenset({'landing', 'special', 'widget'})# 扩展非编程行内代码词汇(更全面的过滤)
NON_CODE_WORDS = frozenset({'markdown', 'target', 'blank', 'lg', 'middle', 'small', 'large','left', 'right', 'center', 'top', 'bottom', 'primary', 'secondary','success', 'warning', 'danger', 'info', 'light', 'dark', 'grid','cards', 'octicons', 'bookmark', 'div', 'class', 'img', 'src','alt', 'width', 'height', 'style', 'id', 'data', 'href', 'title'
})# 支持的编程和标记语言(扩展版本)
PROGRAMMING_LANGUAGES = frozenset({# 编程语言'python', 'py', 'javascript', 'js', 'typescript', 'ts', 'java', 'cpp', 'c', 'go', 'rust', 'php', 'ruby', 'swift', 'kotlin', 'csharp', 'cs',# 脚本语言'bash', 'sh', 'powershell', 'ps1', 'zsh', 'fish', 'bat', 'cmd',# 标记和配置语言'html', 'css', 'scss', 'sass', 'less', 'yaml', 'yml', 'json', 'xml','toml', 'ini', 'conf', 'dockerfile', 'makefile',# 数据库和查询'sql', 'mysql', 'postgresql', 'sqlite', 'mongodb',# 其他'r', 'matlab', 'scala', 'perl', 'lua', 'dart', 'tex', 'latex',# 数据格式'csv', 'properties',# 无标识符(空字符串也算作有效语言)''
})@lru_cache(maxsize=256)
def clean_markdown_content_for_chinese(content_hash, markdown):"""清理Markdown内容,只保留中文文本用于统计(添加缓存)"""content = markdown# 使用预编译的正则表达式content = YAML_FRONT_PATTERN.sub('', content)content = HTML_TAG_PATTERN.sub('', content)content = IMAGE_PATTERN.sub('', content)content = LINK_PATTERN.sub(r'\1', content)content = CODE_BLOCK_PATTERN.sub('', content)content = INLINE_CODE_PATTERN.sub('', content)return contentdef count_code_lines(markdown):"""统计代码行数(修复版本 - 正确处理所有代码行)"""code_blocks = CODE_BLOCK_PATTERN.findall(markdown)total_code_lines = 0for i, block in enumerate(code_blocks):# 提取语言标识lang_match = re.match(r'^```(\w*)', block)language = lang_match.group(1).lower() if lang_match else ''# 移除开头的语言标识和结尾的```code_content = re.sub(r'^```\w*\n?', '', block)code_content = re.sub(r'\n?```$', '', code_content)# 过滤空代码块if not code_content.strip():continue# 计算有效行数(包含所有非空行,包括注释行)lines = [line for line in code_content.split('\n') if line.strip()]line_count = len(lines)# 如果有明确的编程语言标识,直接统计if language and language in PROGRAMMING_LANGUAGES:total_code_lines += line_countcontinue# 增强的检测策略 - 更宽松的判断is_code = False# 1. 命令行检测command_indicators = ['sudo ', 'npm ', 'pip ', 'git ', 'cd ', 'ls ', 'mkdir ', 'rm ', 'cp ', 'mv ','chmod ', 'chown ', 'grep ', 'find ', 'ps ', 'kill ', 'top ', 'cat ', 'echo ','wget ', 'curl ', 'tar ', 'zip ', 'unzip ', 'ssh ', 'scp ', 'rsync ','xattr ', 'codesign ', 'xcode-select ', 'spctl ', 'launchctl ','brew ', 'defaults ', 'ditto ', 'hdiutil ', 'diskutil ','dir ', 'copy ', 'xcopy ', 'del ', 'rd ', 'md ', 'type ', 'attrib ','$ ', '# ', '% ', '> ', 'C:\\>', 'PS>','--', '-r', '-d', '-f', '-v', '-h', '--help', '--version','--force', '--deep', '--sign', '--master-disable','/Applications/', '/usr/', '/etc/', '/var/', '/home/', '~/','C:\\', 'D:\\', '.app', '.exe', '.pkg', '.dmg', '.zip', '.tar','#!/',]if any(indicator in code_content for indicator in command_indicators):is_code = True# 2. 编程语法检测(增强版)if not is_code:programming_indicators = [# Python语法特征'def ', 'class ', 'import ', 'from ', 'return ', 'yield ', 'lambda ','with ', 'as ', 'try:', 'except:', 'finally:', 'elif ', 'if __name__','print(', '.append(', '.extend(', '.remove(', '.sort(', '.reverse(','range(', 'len(', 'str(', 'int(', 'float(', 'list(', 'dict(',# JavaScript/TypeScript语法'function', 'var ', 'let ', 'const ', 'async ', 'await ', '=>','console.log', 'document.', 'window.', 'require(',# 通用编程语法'public ', 'private ', 'protected ', 'static ', 'void ', 'int ','string ', 'boolean ', 'float ', 'double ', 'char ',# 操作符和结构'==', '!=', '<=', '>=', '&&', '||', '++', '--', '+=', '-=', '**',# 特殊结构'while ', 'for ', 'if ', 'else:', 'switch ', 'case ',# HTML/XML语法'<!DOCTYPE', '<html', '<head', '<body', '<div', '<span', '<p>','<style', '<script', '<link', '<meta', '<title', '<img',# CSS语法'display:', 'color:', 'background:', 'margin:', 'padding:','font-size:', 'width:', 'height:', 'position:', 'border:',# YAML语法'name:', 'version:', 'theme:', 'title:', 'description:',# JSON语法'{"', '"}', '":', '",', '[{', '}]', 'null', 'true', 'false',# 配置文件语法'[', ']', '//', '/*', '*/', '<!--', '-->',# SQL语法'SELECT ', 'FROM ', 'WHERE ', 'INSERT ', 'UPDATE ', 'DELETE ','CREATE ', 'ALTER ', 'DROP ', 'INDEX ', 'TABLE ',# 数学公式和LaTeX'\\', '$', '$$', '\\begin', '\\end', '\\frac', '\\sum',]if any(indicator in code_content for indicator in programming_indicators):is_code = True# 3. 结构化检测if not is_code:# 缩进结构检测if len(lines) > 1 and any(line.startswith('  ') or line.startswith('\t') for line in lines):is_code = True# HTML标签结构elif '<' in code_content and '>' in code_content:is_code = True# 包含特殊字符组合elif any(char in code_content for char in ['{', '}', '(', ')', '[', ']']) and ('=' in code_content or ':' in code_content):is_code = True# 4. 模式匹配检测(宽松策略)if not is_code and len(lines) >= 1:special_patterns = [r'\w+\(\)', r'\w+\[\]', r'\w+\{\}', r'\w+=\w+', r'\w+:\w+',r'<\w+>', r'\$\w+', r'#\w+', r'@\w+', r'\w+\.\w+\(\)',r'\d+\.\d+\.\d+', r'http[s]?://', r'ftp://', r'localhost',r'def\s+\w+', r'class\s+\w+', r'import\s+\w+', r'from\s+\w+',r'if\s+\w+', r'while\s+\w+', r'for\s+\w+', r'return\s+\w*',r'\w+\s*=\s*\w+', r'\w+\.\w+', r'#.*输出', r'#.*结果']if any(re.search(pattern, code_content) for pattern in special_patterns):is_code = True# 如果判断为代码,则统计行数if is_code:total_code_lines += line_countreturn total_code_linesdef calculate_reading_stats(markdown):"""计算中文字符数和代码行数"""# 生成内容哈希用于缓存content_hash = hash(markdown)# 使用缓存的清理函数clean_content = clean_markdown_content_for_chinese(content_hash, markdown)chinese_chars = len(CHINESE_CHARS_PATTERN.findall(clean_content))# 统计代码行数code_lines = count_code_lines(markdown)# 计算阅读时间(中文:400字/分钟)reading_time = max(1, round(chinese_chars / 400))return reading_time, chinese_chars, code_linesdef on_page_markdown(markdown, **kwargs):page = kwargs['page']# 快速排除检查if page.meta.get('hide_reading_time', False):return markdown# 保持原有的EXCLUDE_PATTERNS循环检查方式src_path = page.file.src_pathfor pattern in EXCLUDE_PATTERNS:if pattern.match(src_path):return markdown# 优化类型检查page_type = page.meta.get('type', '')if page_type in EXCLUDE_TYPES:return markdown# 快速预检查if len(markdown) < 300:return markdown# 计算统计信息reading_time, chinese_chars, code_lines = calculate_reading_stats(markdown)# 过滤太短的内容if chinese_chars < 50:return markdown# 生成阅读信息if code_lines > 0:reading_info = f"""!!! tip "📖 阅读信息":material-clock-time-two-outline:阅读时间:**{reading_time}** 分钟 | :material-circle-edit-outline:中文字符:**{chinese_chars}** | :fontawesome-solid-code:有效代码行数:**{code_lines}**"""else:reading_info = f"""!!! tip "📖 阅读信息":material-clock-time-two-outline:阅读时间:**{reading_time}** 分钟 | :material-circle-edit-outline:中文字符:**{chinese_chars}**"""return reading_info + markdown

步骤2

把reading_time.py放到docs/overrides/hooks目录下,然后在mkdocs.yml中添加:

# 在 mkdocs.yml 中添加
hooks:- docs/overrides/hooks/reading_time.py    # 阅读时间统计

步骤3

配置MkDocs主题以及覆写路径custom_dir

# 在 mkdocs.yml 中添加
theme:name: materialcustom_dir: docs/overrides  # 必需配置!!!features:- content.code.copy- content.code.select

到这里检查下目录树状图:

$ tree -a
文件名
├── .github
│   ├── .DS_Store
│   └── workflows
│       └── ci.yml
├── docs
│   └── index.md
|   └── overrides
│       └── hooks
│           └── reading_time.py
│           └── ...
└── mkdocs.yml

步骤4

mkdocs serve  # 本地预览

2. 效果截图展示

image
在这里插入图片描述

3.高级配置

3.1 排除特定页面

如果有一些页面不想统计阅读时间,可以在页面的元数据中添加 hide_reading_time: true。例如:

---
title: 不统计阅读时间的页面
hide_reading_time: true
---

或者直接在reading_time.py中添加:

# 你想排除的页面路径
EXCLUDE_PATTERNS = [re.compile(r'^index\.md$'),re.compile(r'^trip/index\.md$'),re.compile(r'^relax/index\.md$'),re.compile(r'^blog/indexblog\.md$'),re.compile(r'^blog/posts\.md$'),re.compile(r'^develop/index\.md$'),re.compile(r'waline\.md$'),re.compile(r'link\.md$'),re.compile(r'404\.md$'),
]

3.2 自定义统计信息

如果需要自定义统计信息的格式,可以修改reading_time.py中的calculate_reading_stats函数。例如:

def calculate_reading_stats(markdown):# 计算统计信息reading_time, chinese_chars, code_lines = calculate_reading_stats(markdown) # 自定义统计信息格式if code_lines > 0:reading_info = f"""!!! info "📖 阅读信息"阅读时间:**{reading_time}** 分钟 | 中文字符:**{chinese_chars}** | 有效代码行数:**{code_lines}**"""else:reading_info = f"""!!! info "📖 阅读信息"阅读时间:**{reading_time}** 分钟 | 中文字符:**{chinese_chars}**"""return reading_info + markdown

相关文章:

  • 华为云Flexus+DeepSeek征文 | 基于华为云ModelArts Studio搭建PandaWiki知识库问答系统
  • 极客时间《后端存储实战课》阅读笔记
  • linux 阻塞和非阻塞
  • 【一天一个知识点】RAG 是“问答脑”,智能体是“有行动力的大脑”
  • XP POWER EJ ET EY FJ FR 系列软件和驱动程序和手侧
  • 『uniapp』onThemeChange监听主题样式,动态主题不正确生效,样式被覆盖的坑
  • 如何提高电脑打字速度?
  • PHP Swoft2 框架精华系列:Controller 控制器组件解析,用法详解
  • leetcode 1432. 改变一个整数能得到的最大差值 中等
  • PCB设计教程【大师篇】stm32开发板PCB布线(电源部分)
  • 基于C_PSO与BP神经网络回归模型的特征选择方法研究(Python实现)
  • Nginx超快速入门
  • Vite:下一代前端构建工具的革命性突破
  • 对于数据库触发器自动执行的理解
  • 使用VSCode开发MCU,FreeRTOS进Hard_Fault调试
  • idea2024使用卡顿
  • golang-linux环境配置
  • LeetCode 1385.两个数组间的距离值
  • 基于深度强化学习的智能机器人路径规划系统:技术与实践
  • 车载软件架构 --- 汽车中央控制单元HPC软件架构方案实例
  • 学校网站建设新闻/宁波seo外包推广
  • 武汉公司注册网站/知乎seo排名帝搜软件
  • 优惠券怎做网站/ebay欧洲站网址
  • 模板做图 网站有哪些内容/百度搜索引擎的网址是多少
  • wordpress网页走丢/英文seo外链发布工具
  • 网站制作上海/百度推广助手客户端