mirror of
https://github.com/CyC2018/Interview-Notebook.git
synced 2026-10-29 23:23:43 +00:00
Add standalone site builder using repository images (#1473)
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
/.source-git/
|
||||
/upstream/
|
||||
/dist/
|
||||
/.cache/
|
||||
/.snapshot-*/
|
||||
/.site-build-*/
|
||||
/.update-check-*/
|
||||
/source-version.json
|
||||
/*-report.json
|
||||
/*-validation.json
|
||||
/validation.json
|
||||
/*.log
|
||||
/*.jpg
|
||||
/*.png
|
||||
__pycache__/
|
||||
!/requirements.txt
|
||||
@@ -0,0 +1,44 @@
|
||||
# CS-Notes 静态网站构建
|
||||
|
||||
只读获取 GitHub 最新 master 的文章和图片,生成可独立部署的静态目录。文章和原图仍由仓库统一维护,构建不下载另一套图床备份,也不向 GitHub 写入数据。
|
||||
|
||||
## 生成与预览
|
||||
|
||||
需要 Python 3.12+。在仓库根目录运行:
|
||||
|
||||
```bash
|
||||
python3 -m pip install -r website/requirements.txt
|
||||
python3 website/refresh.py
|
||||
python3 website/builder/serve.py
|
||||
```
|
||||
|
||||
打开 http://127.0.0.1:8769/。预览服务只监听本机,只提供 website/dist 中的生成文件。
|
||||
|
||||
refresh.py 使用独立浅层 Git 缓存读取最新 master,并生成源码快照。缓存的推送地址已停用。临时目录内完成生成和校验后才替换 website/dist;缺图或站内链接校验失败时保留上一版产物。
|
||||
|
||||
源码缓存、upstream、dist、图片优化缓存、版本记录和验证报告均已忽略,不加入提交。
|
||||
|
||||
## 图片来源与发布
|
||||
|
||||
旧 COS 图片 URL 如果能在当前源码中唯一匹配原文件名,构建时就改用对应仓库文件;同名不唯一或没有匹配时保留原外链。当前内容的 246 个旧图床配图均能匹配,不复制另一套图片到源码中。
|
||||
|
||||
生成时只收集页面实际引用的文件。无损 WebP 经逐像素校验,只有能节省至少 10% 才使用;原图仍保留在生成目录,仓库原文件不改动。搜索索引在构建时生成,访客首次搜索只加载一个索引文件。
|
||||
|
||||
发布时应把整个 website/dist(或内容位于 ZIP 根目录的压缩包)上传到静态托管,网页和图片一起上传。访客从网站域名读取图片,无需另建 COS/TOS 图床或替换一套对象存储链接。
|
||||
|
||||
原文的头像、徽章、许可证图标和 GitHub 附件等 18 个外链继续沿用,依赖原服务可用性。构建生成的是静态文件;目前不包含每日定时配置、腾讯云上传代码或部署凭据。
|
||||
|
||||
## 验证
|
||||
|
||||
Node.js 22+ 用于搜索与更新回归检查。生成后,在仓库根目录运行:
|
||||
|
||||
```bash
|
||||
python3 website/builder/check.py
|
||||
python3 website/builder/test_site_assets.py
|
||||
node website/builder/test_search.cjs
|
||||
python3 website/builder/verify_updates.py
|
||||
```
|
||||
|
||||
verify_updates.py 在临时副本中修改文章、替换配图、新增文章与图片,检查搜索索引更新,并模拟缺图、链接校验失败。测试结束自动清理副本,原始快照不改动。
|
||||
|
||||
页面模板保留手机分类导航、折叠目录、搜索和旧文章入口兼容;固定版本的浏览器依赖及许可证保存在 templates/vendor 中。
|
||||
@@ -0,0 +1,228 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the standalone CS-Notes website from the published Markdown source."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import html
|
||||
import json
|
||||
import posixpath
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
import urllib.parse
|
||||
from pathlib import Path
|
||||
|
||||
from site_assets import copy_referenced_assets, optimize_images, write_manifest, write_search_index
|
||||
from check import check
|
||||
|
||||
BASE = Path(__file__).resolve().parents[1]
|
||||
ROOT = BASE / 'upstream'
|
||||
SITE = BASE / 'templates'
|
||||
DIST = BASE / 'dist'
|
||||
CACHE = BASE / '.cache/site-assets'
|
||||
VERSION_FILE = BASE / 'source-version.json'
|
||||
SOURCE_COMMIT = json.loads(VERSION_FILE.read_text())['commit'] if VERSION_FILE.is_file() else 'unknown'
|
||||
IMAGE_BY_NAME = {}
|
||||
SOURCE_IMAGE_HOST = 'cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com'
|
||||
IMAGE = re.compile(r'(<img\b[^>]*?\bsrc=["\'])([^"\']+)(["\'])', re.I)
|
||||
MD_IMAGE = re.compile(r'(!\[[^\]]*\]\()((?:[^()\s]|\([^()\n]*\))*)(\))')
|
||||
MD_LINK = re.compile(r'(?<!!)\[([^\]]+)\]\(([^\s)]+)\)')
|
||||
HTML_LINK = re.compile(r'(<a\b[^>]*?\bhref=["\'])([^"\']+)(["\'])', re.I)
|
||||
|
||||
|
||||
def sources():
|
||||
return [ROOT / 'README.md', ROOT / 'CONTRIBUTING.md',
|
||||
*sorted((ROOT / 'notes').glob('*.md')),
|
||||
*sorted((ROOT / 'assets').glob('*.md'))]
|
||||
|
||||
|
||||
def image_urls():
|
||||
found = set()
|
||||
for path in sources():
|
||||
text = re.sub(r'```[^\n]*\n[\s\S]*?```|<!--[\s\S]*?-->', '', path.read_text())
|
||||
for pattern in (IMAGE, MD_IMAGE):
|
||||
for match in pattern.finditer(text):
|
||||
url = html.unescape(match[2].strip())
|
||||
if url.startswith(('https://', 'http://')):
|
||||
found.add(url)
|
||||
return sorted(found)
|
||||
|
||||
|
||||
def repository_images():
|
||||
result = {}
|
||||
for directory in ('notes/pics', 'assets', 'docs/_media'):
|
||||
for path in (ROOT / directory).rglob('*'):
|
||||
if path.is_file():
|
||||
result.setdefault(path.name, []).append(path)
|
||||
return result
|
||||
|
||||
|
||||
def local_target(url, source, html_link=False):
|
||||
url = html.unescape(url)
|
||||
parsed = urllib.parse.urlsplit(url)
|
||||
path = urllib.parse.unquote(parsed.path)
|
||||
if parsed.netloc.lower() in ('github.com', 'www.github.com'):
|
||||
prefix = '/CyC2018/CS-Notes/blob/master/'
|
||||
if path.startswith(prefix):
|
||||
path = path[len(prefix):]
|
||||
else:
|
||||
return url
|
||||
elif parsed.hostname == SOURCE_IMAGE_HOST:
|
||||
candidates = IMAGE_BY_NAME.get(Path(path).name, [])
|
||||
if len(candidates) != 1:
|
||||
return url
|
||||
path = candidates[0].relative_to(ROOT).as_posix()
|
||||
elif parsed.netloc:
|
||||
return url
|
||||
elif not path or url.startswith(('#', 'mailto:', 'data:', '//')):
|
||||
return url
|
||||
elif not path.startswith('/'):
|
||||
path = posixpath.normpath(str(source.relative_to(ROOT).parent / path))
|
||||
path = path.lstrip('/')
|
||||
if not (ROOT / path).resolve().is_relative_to(ROOT.resolve()) or not (ROOT / path).is_file():
|
||||
return url
|
||||
if path.endswith('.md'):
|
||||
if not html_link:
|
||||
result = '/' + urllib.parse.quote(path, safe='/')
|
||||
if parsed.fragment:
|
||||
result += '#' + urllib.parse.quote(urllib.parse.unquote(parsed.fragment), safe='')
|
||||
return result
|
||||
if path == 'README.md':
|
||||
route = '/'
|
||||
else:
|
||||
route = '/' + urllib.parse.quote(path[:-3], safe='/')
|
||||
if parsed.fragment:
|
||||
route += '?id=' + urllib.parse.quote(urllib.parse.unquote(parsed.fragment), safe='')
|
||||
return '/#' + route
|
||||
return '/' + urllib.parse.quote(path, safe='/')
|
||||
|
||||
|
||||
def render_markdown(path):
|
||||
content = path.read_text()
|
||||
def image(match):
|
||||
url = html.unescape(match[2].strip())
|
||||
parsed = urllib.parse.urlsplit(url)
|
||||
if parsed.path and not parsed.scheme and not parsed.netloc:
|
||||
candidate = (ROOT / urllib.parse.unquote(parsed.path).lstrip('/')) if parsed.path.startswith('/') else (path.parent / urllib.parse.unquote(parsed.path))
|
||||
if not candidate.resolve().is_relative_to(ROOT.resolve()) or not candidate.is_file():
|
||||
raise ValueError('Missing source image: ' + parsed.path)
|
||||
url = local_target(url, path)
|
||||
return match[1] + url + match[3]
|
||||
def convert(part):
|
||||
part = IMAGE.sub(image, part)
|
||||
part = MD_IMAGE.sub(image, part)
|
||||
part = MD_LINK.sub(lambda m: f'[{m[1]}]({local_target(m[2], path)})', part)
|
||||
return HTML_LINK.sub(lambda m: m[1] + local_target(m[2], path, html_link=True) + m[3], part)
|
||||
parts = re.split(r'(```[^\n]*\n[\s\S]*?```|<!--[\s\S]*?-->)', content)
|
||||
return ''.join(part if i % 2 else convert(part) for i, part in enumerate(parts))
|
||||
|
||||
|
||||
def build(optimize=True):
|
||||
global IMAGE_BY_NAME
|
||||
IMAGE_BY_NAME = repository_images()
|
||||
external = image_urls()
|
||||
replacements = {url: local_target(url, ROOT / 'README.md') for url in external}
|
||||
reused = {url: path for url, path in replacements.items() if path.startswith('/')}
|
||||
unchanged_external = [url for url, path in replacements.items() if not path.startswith('/')]
|
||||
with tempfile.TemporaryDirectory(prefix='.site-build-', dir=DIST.parent) as temporary:
|
||||
dist = Path(temporary) / 'dist'
|
||||
dist.mkdir()
|
||||
shutil.copytree(SITE / 'vendor', dist / 'vendor')
|
||||
for name in ('index.html', 'site.css', 'site.js', 'search.js'):
|
||||
shutil.copyfile(SITE / name, dist / name)
|
||||
for path in sources():
|
||||
target = dist / path.relative_to(ROOT)
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_text(render_markdown(path))
|
||||
homepage = (dist / 'README.md').read_text()
|
||||
emojis = {'pencil2': '✏️', 'computer': '💻', 'cloud': '☁️', 'floppy_disk': '💾',
|
||||
'coffee': '☕', 'bulb': '💡', 'art': '🎨', 'wrench': '🔧',
|
||||
'watermelon': '🍉', 'memo': '📝'}
|
||||
for name, symbol in emojis.items():
|
||||
homepage = re.sub(r'^(## ):' + name + r':\s*', r'\1', homepage, flags=re.M)
|
||||
homepage = homepage.replace('#' + name + '-', '#')
|
||||
homepage = homepage.replace(':' + name + ':', symbol)
|
||||
topics = [('算法', '✏️'), ('操作系统', '💻'), ('网络', '☁️'), ('面向对象', '🎨'),
|
||||
('数据库', '💾'), ('Java', '☕'), ('系统设计', '💡'), ('工具', '🔧'),
|
||||
('编码实践', '🍉'), ('后记', '📝')]
|
||||
cards = '<nav class="topic-grid" aria-label="笔记分类">' + ''.join(
|
||||
f'<a href="/#/?id={urllib.parse.quote(label.lower())}"><span aria-hidden="true">{symbol}</span>{label}</a>'
|
||||
for label, symbol in topics) + '</nav>'
|
||||
homepage = re.sub(r'^\| 算法[^\n]*\n\|[^\n]*\n\|[^\n]*', cards, homepage, count=1, flags=re.M)
|
||||
homepage = homepage.replace('width="200px"', 'class="site-logo" width="200px"', 1)
|
||||
(dist / 'README.md').write_text('# CS-Notes\n\n' + homepage)
|
||||
note_paths = ['/' + str(p.relative_to(ROOT).with_suffix('')) for p in sorted((ROOT / 'notes').glob('*.md'))]
|
||||
sidebar = ['- [首页](/)', '- [面试高频问题](/notes/面试高频问题)', '']
|
||||
for line in homepage.splitlines():
|
||||
if line.startswith('## '):
|
||||
label = re.sub(r':[a-z0-9_]+:', '', line[3:]).strip()
|
||||
if label == '后记':
|
||||
break
|
||||
sidebar.extend(['', '- **' + label + '**'])
|
||||
elif line.startswith('- ['):
|
||||
sidebar.append(' ' + line)
|
||||
(dist / '_sidebar.md').write_text('\n'.join(sidebar) + '\n')
|
||||
(dist / '_404.md').write_text('# 页面暂未找到\n\n请从目录或搜索查找笔记。\n\n[返回首页](/)\n')
|
||||
(dist / '.nojekyll').touch()
|
||||
shutil.copyfile(ROOT / 'docs/_media/LogoMakr_1J56bI.png', dist / 'favicon.png')
|
||||
copy_referenced_assets(dist, ROOT)
|
||||
image_report = optimize_images(dist, CACHE) if optimize else {'converted': 0}
|
||||
index_name, search_report = write_search_index(dist, sorted((dist / 'notes').glob('*.md')))
|
||||
legacy = json.loads((SITE / 'legacy-routes.json').read_text())
|
||||
(dist / 'search-paths.js').write_text(
|
||||
'window.CS_NOTES_PATHS = ' + json.dumps(note_paths, ensure_ascii=False) + ';\n'
|
||||
'window.CS_NOTES_LEGACY_ROUTES = ' + json.dumps(legacy, ensure_ascii=False, separators=(',', ':')) + ';\n'
|
||||
'window.CS_NOTES_SEARCH_INDEX = "/' + index_name + '";\n')
|
||||
generator = hashlib.sha256()
|
||||
for resource in sorted([*Path(__file__).parent.glob('*.py'), *SITE.rglob('*')]):
|
||||
if resource.is_file():
|
||||
generator.update(resource.relative_to(BASE).as_posix().encode())
|
||||
generator.update(resource.read_bytes())
|
||||
build_info = {'sourceCommit': SOURCE_COMMIT, 'generatorHash': generator.hexdigest(), 'notes': len(note_paths),
|
||||
'searchIndex': index_name, 'legacyRoutes': len(legacy)}
|
||||
(dist / 'build-info.json').write_text(json.dumps(build_info, indent=2) + '\n')
|
||||
entry = (dist / 'index.html').read_text()
|
||||
for name in ('site.css', 'site.js', 'search.js', 'search-paths.js'):
|
||||
digest = hashlib.sha256((dist / name).read_bytes()).hexdigest()[:12]
|
||||
entry = entry.replace('/' + name + '"', '/' + name + '?v=' + digest + '"')
|
||||
(dist / 'index.html').write_text(entry)
|
||||
write_manifest(dist, build_info)
|
||||
files = [p for p in dist.rglob('*') if p.is_file()]
|
||||
report = {'notes': len(note_paths), 'files': len(files), 'bytes': sum(p.stat().st_size for p in files),
|
||||
'oversized': [str(p.relative_to(dist)) for p in files if p.stat().st_size > 25*1024*1024],
|
||||
'repositoryImagesUsedForOldUrls': len(reused), 'externalImages': len(unchanged_external), 'search': search_report, 'images': image_report, 'legacyRoutes': len(legacy)}
|
||||
if report['oversized'] or len(files) > 20000:
|
||||
raise ValueError('Static hosting quota exceeded')
|
||||
validation = check(ROOT, dist)
|
||||
if validation['errors']:
|
||||
raise ValueError('Static site validation failed: ' + '; '.join(validation['errors']))
|
||||
previous = Path(temporary) / 'previous'
|
||||
if DIST.exists():
|
||||
DIST.rename(previous)
|
||||
try:
|
||||
dist.rename(DIST)
|
||||
except BaseException:
|
||||
if previous.exists():
|
||||
previous.rename(DIST)
|
||||
raise
|
||||
report.update({'sourceCommit': SOURCE_COMMIT, 'imageReplacements': reused, 'externalImageUrls': unchanged_external})
|
||||
DIST.with_name(DIST.name + '-report.json').write_text(json.dumps(report, ensure_ascii=False, indent=2) + '\n')
|
||||
print(json.dumps({k: v for k, v in report.items() if k not in ('imageReplacements', 'externalImageUrls')}, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='仅在本机从最新上游源码生成静态站,不下载图片、不发布。')
|
||||
parser.add_argument('--source', type=Path, default=ROOT)
|
||||
parser.add_argument('--output', type=Path, default=DIST)
|
||||
parser.add_argument('--source-commit', default=SOURCE_COMMIT)
|
||||
parser.add_argument('--no-optimize-images', action='store_true')
|
||||
args = parser.parse_args()
|
||||
ROOT, DIST, SOURCE_COMMIT = args.source.resolve(), args.output.resolve(), args.source_commit
|
||||
if not ROOT.is_dir():
|
||||
parser.error("源码目录不存在,请先运行 website/refresh.py。")
|
||||
if ROOT == DIST or ROOT.is_relative_to(DIST) or DIST.is_relative_to(ROOT):
|
||||
parser.error('源码和输出目录必须相互独立。')
|
||||
if not DIST.is_relative_to(BASE):
|
||||
parser.error('本地验证输出必须保存在当前验证目录中。')
|
||||
build(optimize=not args.no_optimize_images)
|
||||
@@ -0,0 +1,80 @@
|
||||
"""Check only local build output and its provenance in the GitHub snapshot."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from urllib.parse import unquote, urlsplit
|
||||
|
||||
BASE = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def check(source, output):
|
||||
manifest = json.loads((output / 'asset-manifest.json').read_text())
|
||||
info = json.loads((output / 'build-info.json').read_text())
|
||||
files = {p.relative_to(output).as_posix(): p for p in output.rglob('*') if p.is_file()}
|
||||
errors = []
|
||||
expected = {item['path']: item for item in manifest['files']}
|
||||
if set(files) != set(expected) | {'asset-manifest.json'}:
|
||||
errors.append('发布文件与清单不一致')
|
||||
for name, record in expected.items():
|
||||
path = output / name
|
||||
if not path.is_file() or path.stat().st_size != record['bytes'] or hashlib.sha256(path.read_bytes()).hexdigest() != record['sha256']:
|
||||
errors.append('文件清单校验失败:' + name)
|
||||
references = 0
|
||||
for document in output.rglob('*.md'):
|
||||
content = re.sub(r'```[^\n]*\n[\s\S]*?```|<!--[\s\S]*?-->', '', document.read_text())
|
||||
for match in re.finditer(r'''\]\((/[^\s)]+)\)|(?:src|href)=["'](/[^"']+)''', content):
|
||||
url = match[1] or match[2]
|
||||
if url.startswith(('/#', '//')):
|
||||
continue
|
||||
name = unquote(urlsplit(url).path).lstrip('/')
|
||||
if not name:
|
||||
continue
|
||||
references += 1
|
||||
if name not in files and name + '.md' not in files:
|
||||
errors.append('缺失引用:' + name)
|
||||
for name in re.findall(r'(?:src|href)="(/[^"#]+)"', (output / 'index.html').read_text()):
|
||||
if name.split('?')[0].lstrip('/') not in files:
|
||||
errors.append('缺失页面资源:' + name)
|
||||
forbidden = [name for name in files if set(Path(name).parts) & {
|
||||
'.git', '.cache', 'node_modules', 'builder', 'templates', 'upstream', 'remote',
|
||||
'tools', 'backups', '__pycache__', 'CLAUDE.md', 'AGENTS.md', '.env',
|
||||
}]
|
||||
if forbidden:
|
||||
errors.append('混入维护文件:' + ', '.join(forbidden))
|
||||
images = 0
|
||||
for name, path in files.items():
|
||||
if name.startswith(('notes/pics/', 'assets/', 'docs/_media/')) and path.suffix.lower() in {'.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico'}:
|
||||
images += 1
|
||||
original = source / name
|
||||
if not original.is_file() or original.read_bytes() != path.read_bytes():
|
||||
errors.append('图片与当前源码不一致:' + name)
|
||||
index = json.loads((output / info['searchIndex']).read_text())
|
||||
note_names = {p.stem for p in (source / 'notes').glob('*.md')}
|
||||
if {Path(doc['path']).name for doc in index['documents']} != note_names:
|
||||
errors.append('搜索索引未覆盖当前全部文章')
|
||||
legacy = json.loads((BASE / 'templates/legacy-routes.json').read_text())
|
||||
for target in legacy.values():
|
||||
if not (output / (target.lstrip('/') + '.md')).is_file():
|
||||
errors.append('旧入口目标缺失:' + target)
|
||||
variants = json.loads((output / 'image-variants.json').read_text()) if (output / 'image-variants.json').exists() else {}
|
||||
for name, variant in variants.items():
|
||||
for path, key in [(output / name.lstrip('/'), 'sourceSha256'), (output / variant['path'].lstrip('/'), 'sha256')]:
|
||||
if not path.is_file() or hashlib.sha256(path.read_bytes()).hexdigest() != variant[key]:
|
||||
errors.append('图片优化清单不一致:' + name)
|
||||
report = {'sourceCommit': info['sourceCommit'], 'notes': len(note_names),
|
||||
'files': len(files), 'localReferences': references, 'repositoryImages': images,
|
||||
'imageVariants': len(variants), 'legacyRoutes': len(legacy), 'errors': errors}
|
||||
return report
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--source', type=Path, default=BASE / 'upstream')
|
||||
parser.add_argument('--output', type=Path, default=BASE / 'dist')
|
||||
args = parser.parse_args()
|
||||
result = check(args.source.resolve(), args.output.resolve())
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
(BASE / 'validation.json').write_text(json.dumps(result, ensure_ascii=False, indent=2) + '\n')
|
||||
raise SystemExit(bool(result['errors']))
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Serve only generated files on the loopback interface."""
|
||||
import argparse
|
||||
import functools
|
||||
import json
|
||||
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
||||
from pathlib import Path
|
||||
from urllib.parse import unquote, urlsplit
|
||||
|
||||
BASE = Path(__file__).resolve().parents[1]
|
||||
LEGACY = json.loads((BASE / 'templates/legacy-routes.json').read_text())
|
||||
|
||||
|
||||
class Handler(SimpleHTTPRequestHandler):
|
||||
def send_head(self):
|
||||
if unquote(urlsplit(self.path).path) in LEGACY and not Path(self.translate_path(self.path)).exists():
|
||||
self.path = '/index.html'
|
||||
return super().send_head()
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--port', type=int, default=8769)
|
||||
args = parser.parse_args()
|
||||
server = ThreadingHTTPServer(('127.0.0.1', args.port), functools.partial(Handler, directory=str(BASE / 'dist')))
|
||||
print(f'本地预览:http://127.0.0.1:{args.port}/', flush=True)
|
||||
try:
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
server.server_close()
|
||||
@@ -0,0 +1,174 @@
|
||||
"""Deterministic search, legacy routes and lossless delivery assets for the site."""
|
||||
from __future__ import annotations
|
||||
|
||||
import concurrent.futures
|
||||
import hashlib
|
||||
import gzip
|
||||
import html
|
||||
import io
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
import urllib.parse
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
IMAGE_SUFFIXES = {'.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico', '.avif'}
|
||||
PUNCTUATION = re.compile(r'''[\u2000-\u206f\u2e00-\u2e7f\\'!"#$%&()*+,./:;<=>?@\[\]^`{|}~]''')
|
||||
|
||||
|
||||
def plain_text(text):
|
||||
text = re.sub(r'<!--[\s\S]*?-->', '', text)
|
||||
text = re.sub(r'<(?:script|style)\b[^>]*>[\s\S]*?</(?:script|style)>', '', text, flags=re.I)
|
||||
text = re.sub(r'<[^>]+>', ' ', text)
|
||||
text = re.sub(r'!\[[^\]]*\]\([^\n]*?\)', ' ', text)
|
||||
text = re.sub(r'\[([^\]]+)\]\([^\n]*?\)', r'\1', text)
|
||||
text = re.sub(r'^\s*```[^\n]*$', '', text, flags=re.M)
|
||||
text = text.replace('`', '').replace('**', '').replace('__', '')
|
||||
return re.sub(r'\s+', ' ', html.unescape(text)).strip()
|
||||
|
||||
|
||||
def heading_slug(title, counts):
|
||||
# Match the pinned Docsify 4.13.1 slugger, including numeric and duplicate headings.
|
||||
title = title.replace('{docsify-ignore}', '').replace('{docsify-ignore-all}', '')
|
||||
slug = PUNCTUATION.sub('', re.sub(r'<[^>]+>', '', title.strip()).lower())
|
||||
slug = re.sub(r'-+', '-', re.sub(r'\s', '-', slug))
|
||||
slug = re.sub(r'^(\d)', r'_\1', slug)
|
||||
count = counts[slug]
|
||||
counts[slug] += 1
|
||||
return slug + (f'-{count}' if count else '')
|
||||
|
||||
|
||||
def search_document(path, content):
|
||||
counts = Counter()
|
||||
sections = []
|
||||
current = {'title': path.stem, 'id': '', 'text': ''}
|
||||
lines = []
|
||||
fence = None
|
||||
|
||||
def finish():
|
||||
current['text'] = plain_text('\n'.join(lines))
|
||||
if current['text'] or current['title']:
|
||||
sections.append(dict(current))
|
||||
lines.clear()
|
||||
|
||||
for line in re.sub(r'<!--[\s\S]*?-->', '', content).splitlines():
|
||||
marker = re.match(r'^\s*(`{3,}|~{3,})', line)
|
||||
if marker:
|
||||
if fence is None:
|
||||
fence = marker[1][0]
|
||||
elif marker[1][0] == fence:
|
||||
fence = None
|
||||
continue
|
||||
heading = re.match(r'^(#{1,6})\s+(.+?)(?:\s+#+)?\s*$', line) if fence is None else None
|
||||
if heading:
|
||||
finish()
|
||||
raw_title = heading[2]
|
||||
current = {'title': plain_text(raw_title), 'id': heading_slug(raw_title, counts), 'text': ''}
|
||||
elif not re.match(r'^\s*[-*+]\s+\[[^]]+\]\(#[^)]*\)\s*$', line):
|
||||
lines.append(line)
|
||||
finish()
|
||||
return {'path': '/notes/' + path.stem, 'title': path.stem, 'sections': sections}
|
||||
|
||||
|
||||
def write_search_index(dist, paths):
|
||||
documents = [search_document(path, path.read_text()) for path in paths]
|
||||
data = json.dumps({'version': 1, 'documents': documents}, ensure_ascii=False, separators=(',', ':')).encode()
|
||||
name = 'search-index-' + hashlib.sha256(data).hexdigest()[:16] + '.json'
|
||||
(dist / name).write_bytes(data)
|
||||
return name, {'documents': len(documents), 'sections': sum(len(d['sections']) for d in documents),
|
||||
'bytes': len(data), 'gzipBytes': len(gzip.compress(data, mtime=0))}
|
||||
|
||||
|
||||
def referenced_paths(content):
|
||||
content = re.sub(r'```[^\n]*\n[\s\S]*?```|<!--[\s\S]*?-->', '', content)
|
||||
for match in re.finditer(r'''\]\((/[^\s)]+)\)|(?:src|href)=["'](/[^"']+)''', content):
|
||||
url = match[1] or match[2]
|
||||
if url.startswith(('/#', '//')):
|
||||
continue
|
||||
path = urllib.parse.unquote(urllib.parse.urlsplit(url).path).lstrip('/')
|
||||
if path and '..' not in Path(path).parts:
|
||||
yield path
|
||||
|
||||
|
||||
def copy_referenced_assets(dist, root):
|
||||
references = set()
|
||||
for doc in dist.rglob('*.md'):
|
||||
references.update(referenced_paths(doc.read_text()))
|
||||
for relative in sorted(references):
|
||||
destination = dist / relative
|
||||
if destination.is_file() or Path(str(destination) + '.md').is_file() or destination.suffix == '.md':
|
||||
continue
|
||||
source = root / relative
|
||||
if not source.resolve().is_relative_to(root.resolve()) or not source.is_file():
|
||||
raise ValueError(f'Missing published resource: {relative}')
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copyfile(source, destination)
|
||||
|
||||
|
||||
def optimize_images(dist, cache):
|
||||
"""Add pixel-identical WebP alternatives; retain each referenced original URL."""
|
||||
from PIL import Image
|
||||
cache.mkdir(parents=True, exist_ok=True)
|
||||
candidates = [p for p in dist.rglob('*') if p.suffix.lower() in {'.png', '.jpg', '.jpeg'} and p.stat().st_size >= 4096]
|
||||
|
||||
def convert(path):
|
||||
original = path.read_bytes()
|
||||
digest = hashlib.sha256(original).hexdigest()
|
||||
cached = cache / (digest + '.webp')
|
||||
with Image.open(io.BytesIO(original)) as image:
|
||||
if getattr(image, 'is_animated', False):
|
||||
return None
|
||||
rgba = image.convert('RGBA')
|
||||
if cached.exists():
|
||||
data = cached.read_bytes()
|
||||
else:
|
||||
stream = io.BytesIO()
|
||||
rgba.save(stream, format='WEBP', lossless=True, exact=True, method=4)
|
||||
data = stream.getvalue()
|
||||
if len(data) >= len(original) * .9:
|
||||
return None
|
||||
with Image.open(io.BytesIO(data)) as decoded:
|
||||
if decoded.convert('RGBA').tobytes() != rgba.tobytes():
|
||||
raise ValueError(f'Lossless image verification failed: {path}')
|
||||
if not cached.exists():
|
||||
with tempfile.NamedTemporaryFile(dir=cache, delete=False) as temporary:
|
||||
temporary.write(data)
|
||||
temporary_path = Path(temporary.name)
|
||||
temporary_path.replace(cached)
|
||||
relative = 'optimized/' + digest[:24] + '.webp'
|
||||
target = dist / relative
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
target.write_bytes(data)
|
||||
return '/' + path.relative_to(dist).as_posix(), {
|
||||
'path': '/' + relative, 'originalBytes': len(original), 'deliveryBytes': len(data),
|
||||
'width': rgba.width, 'height': rgba.height, 'sourceSha256': digest,
|
||||
'sha256': hashlib.sha256(data).hexdigest(), 'pixelIdentical': True,
|
||||
}
|
||||
|
||||
mapping = {}
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as pool:
|
||||
for result in pool.map(convert, candidates):
|
||||
if result:
|
||||
mapping[result[0]] = result[1]
|
||||
image_pattern = re.compile(r'''(<img\b[^>]*?\bsrc=["'])([^"']+)(["'])|(!\[[^\]]*\]\()([^\s)]+)(\))''', re.I)
|
||||
for path in dist.rglob('*.md'):
|
||||
def replace(match):
|
||||
before, url, after = (match[1], match[2], match[3]) if match[1] else (match[4], match[5], match[6])
|
||||
return before + mapping.get(url, {}).get('path', url) + after
|
||||
parts = re.split(r'(```[^\n]*\n[\s\S]*?```|<!--[\s\S]*?-->)', path.read_text())
|
||||
path.write_text(''.join(part if i % 2 else image_pattern.sub(replace, part) for i, part in enumerate(parts)))
|
||||
(dist / 'image-variants.json').write_text(json.dumps(mapping, ensure_ascii=False, indent=2) + '\n')
|
||||
return {'converted': len(mapping), 'originalBytes': sum(v['originalBytes'] for v in mapping.values()),
|
||||
'deliveryBytes': sum(v['deliveryBytes'] for v in mapping.values()), 'pixelIdentical': True}
|
||||
|
||||
|
||||
def write_manifest(dist, build_info):
|
||||
records = []
|
||||
for path in sorted(p for p in dist.rglob('*') if p.is_file() and p.name != 'asset-manifest.json'):
|
||||
data = path.read_bytes()
|
||||
records.append({'path': path.relative_to(dist).as_posix(), 'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()})
|
||||
manifest = {'version': 1, **build_info, 'files': records}
|
||||
(dist / 'asset-manifest.json').write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + '\n')
|
||||
return records
|
||||
@@ -0,0 +1,15 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { search } = require('../templates/search.js');
|
||||
const dist = process.argv[2] || path.join(__dirname, '../dist');
|
||||
const info = JSON.parse(fs.readFileSync(path.join(dist, 'build-info.json')));
|
||||
const index = JSON.parse(fs.readFileSync(path.join(dist, info.searchIndex)));
|
||||
assert(search(index, '拥塞控制').some(r => r.path.includes('计算机网络 - 传输层') && r.id === 'tcp-拥塞控制'));
|
||||
assert(search(index, '检索增强生成').some(r => r.path === '/notes/大模型应用' && r.id === '三、检索增强生成'));
|
||||
assert(search(index, 'Token 分词').some(r => r.path === '/notes/大模型应用' && r.id === 'token-与分词'));
|
||||
assert.equal(search(index, '').length, 0);
|
||||
if (process.argv.includes('--updated')) {
|
||||
assert(search(index, '本地同步验证示例词').some(r => r.id === '本地验证更新'));
|
||||
}
|
||||
console.log(`搜索与锚点验证通过:${index.documents.length} 篇文章。`);
|
||||
@@ -0,0 +1,31 @@
|
||||
"""Regression checks for generated search anchors and asset collection."""
|
||||
import sys
|
||||
import unittest
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
from site_assets import heading_slug, referenced_paths, search_document
|
||||
|
||||
|
||||
class SearchIndexTests(unittest.TestCase):
|
||||
def test_docsify_numeric_duplicate_and_chinese_punctuation(self):
|
||||
counts = Counter()
|
||||
self.assertEqual(heading_slug('1. 慢开始与拥塞避免', counts), '_1-慢开始与拥塞避免')
|
||||
self.assertEqual(heading_slug('三、检索增强生成', counts), '三、检索增强生成')
|
||||
self.assertEqual(heading_slug('TCP 拥塞控制', counts), 'tcp-拥塞控制')
|
||||
self.assertEqual(heading_slug('TCP 拥塞控制', counts), 'tcp-拥塞控制-1')
|
||||
|
||||
def test_headings_in_code_do_not_change_real_anchors(self):
|
||||
doc = search_document(Path('示例.md'), '# 示例\n\n```python\n# TCP 拥塞控制\n```\n\n## TCP 拥塞控制\n慢开始\n')
|
||||
sections = [s for s in doc['sections'] if s['id'] == 'tcp-拥塞控制']
|
||||
self.assertEqual(len(sections), 1)
|
||||
self.assertEqual(sections[0]['text'], '慢开始')
|
||||
|
||||
def test_comments_and_example_asset_urls_are_not_published(self):
|
||||
content = '<!-- <img src="/secret.png"> -->\n```html\n<img src="/example.png">\n```\n<img src="/notes/pics/actual.png">\n[正文](/notes/示例.md)\n'
|
||||
self.assertEqual(set(referenced_paths(content)), {'notes/pics/actual.png', 'notes/示例.md'})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,84 @@
|
||||
"""Exercise repeat builds, article/image updates, and failed-build preservation locally."""
|
||||
import hashlib
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from urllib.parse import unquote
|
||||
|
||||
from check import check
|
||||
|
||||
BASE = Path(__file__).resolve().parents[1]
|
||||
SOURCE = BASE / 'upstream'
|
||||
OUTPUT = BASE / 'dist'
|
||||
|
||||
|
||||
def digest(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def build(source, output, *extra):
|
||||
return subprocess.run([sys.executable, str(BASE / 'builder/build.py'),
|
||||
'--source', str(source), '--output', str(output), *extra], capture_output=True, text=True)
|
||||
|
||||
|
||||
first_manifest = digest(OUTPUT / 'asset-manifest.json')
|
||||
result = build(SOURCE, OUTPUT)
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert digest(OUTPUT / 'asset-manifest.json') == first_manifest, '相同输入的两次构建不一致'
|
||||
|
||||
report = json.loads((BASE / 'dist-report.json').read_text())
|
||||
url, image = next((url, path) for url, path in report['imageReplacements'].items()
|
||||
if path.startswith('/notes/pics/') and path.endswith('.png'))
|
||||
image_name = unquote(image).lstrip('/')
|
||||
note = next(path for path in (SOURCE / 'notes').glob('*.md') if url in path.read_text())
|
||||
note_name = note.relative_to(SOURCE)
|
||||
original_note = digest(note)
|
||||
original_image = digest(SOURCE / image_name)
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix='.update-check-', dir=BASE) as temp:
|
||||
root = Path(temp)
|
||||
source, output = root / 'source', root / 'dist'
|
||||
shutil.copytree(SOURCE, source)
|
||||
with (source / note_name).open('a') as handle:
|
||||
handle.write('\n\n## 本地验证更新\n\n本地同步验证示例词:这是未上传的更新测试。\n')
|
||||
replacement = next(path for path in (SOURCE / 'notes/pics').glob('*.png') if digest(path) != original_image)
|
||||
shutil.copyfile(replacement, source / image_name)
|
||||
shutil.copyfile(replacement, source / 'notes/pics/local-validation-new.png')
|
||||
(source / 'notes/本地新增验证.md').write_text('# 本地新增验证\n\n仅在测试副本中生成。\n\n\n')
|
||||
result = build(source, output, '--no-optimize-images', '--source-commit', 'local-update-check')
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert '本地同步验证示例词' in (output / note_name).read_text()
|
||||
assert digest(output / image_name) == digest(replacement)
|
||||
assert digest(output / image_name) != original_image
|
||||
assert (output / 'notes/本地新增验证.md').is_file()
|
||||
assert (output / 'notes/pics/local-validation-new.png').is_file()
|
||||
validation = check(source, output)
|
||||
assert not validation['errors'], validation['errors']
|
||||
subprocess.run(['node', str(BASE / 'builder/test_search.cjs'), str(output), '--updated'], check=True)
|
||||
|
||||
successful_manifest = digest(output / 'asset-manifest.json')
|
||||
with (source / note_name).open('a') as handle:
|
||||
handle.write('\n\n\n')
|
||||
failed = build(source, output, '--no-optimize-images', '--source-commit', 'local-failure-check')
|
||||
assert failed.returncode != 0, '缺少必须图片时构建应失败'
|
||||
assert digest(output / 'asset-manifest.json') == successful_manifest, '失败的构建覆盖了上次产物'
|
||||
|
||||
text = (source / note_name).read_text()
|
||||
(source / note_name).write_text(text.replace('',
|
||||
'[故障模拟](/notes/local-validation-missing.md)'))
|
||||
failed = build(source, output, '--no-optimize-images', '--source-commit', 'local-link-failure-check')
|
||||
assert failed.returncode != 0, '站内链接校验失败时构建应失败'
|
||||
assert digest(output / 'asset-manifest.json') == successful_manifest, '校验失败覆盖了上次产物'
|
||||
|
||||
assert digest(note) == original_note and digest(SOURCE / image_name) == original_image
|
||||
result = {'sameInputRebuildIdentical': True, 'articleUpdatePickedUp': True,
|
||||
'repositoryImageUpdatePickedUp': True, 'newArticleAndImageIncluded': True,
|
||||
'updatedSearchIndexPassed': True, 'failedBuildPreservesPreviousOutput': True,
|
||||
'failedValidationPreservesPreviousOutput': True,
|
||||
'upstreamSnapshotUnchanged': True, 'testFixtureRemoved': True,
|
||||
'sourceCommit': report['sourceCommit'], 'manifestSha256': first_manifest}
|
||||
(BASE / 'update-validation.json').write_text(json.dumps(result, ensure_ascii=False, indent=2) + '\n')
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
@@ -0,0 +1,52 @@
|
||||
"""Read the latest public master and build locally; no write operation to GitHub or cloud."""
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
BASE = Path(__file__).resolve().parent
|
||||
CACHE = BASE / '.source-git'
|
||||
REPOSITORY = 'https://github.com/CyC2018/CS-Notes.git'
|
||||
|
||||
|
||||
def git(*args):
|
||||
return subprocess.check_output(['git', '--git-dir=' + str(CACHE), *args], text=True).strip()
|
||||
|
||||
|
||||
if not CACHE.exists():
|
||||
subprocess.run(['git', 'init', '--bare', str(CACHE)], check=True, stdout=subprocess.DEVNULL)
|
||||
git('remote', 'add', 'origin', REPOSITORY)
|
||||
git('config', 'credential.helper', '')
|
||||
git('remote', 'set-url', '--push', 'origin', 'disabled://local-validation-read-only')
|
||||
subprocess.run(['git', '--git-dir=' + str(CACHE), 'fetch', '--depth=1', 'origin',
|
||||
'+refs/heads/master:refs/heads/master'], check=True)
|
||||
commit = git('rev-parse', 'refs/heads/master')
|
||||
with tempfile.TemporaryDirectory(prefix='.snapshot-', dir=BASE) as temp:
|
||||
snapshot = Path(temp) / 'source'
|
||||
snapshot.mkdir()
|
||||
process = subprocess.Popen(['git', '--git-dir=' + str(CACHE), 'archive', commit], stdout=subprocess.PIPE)
|
||||
try:
|
||||
with tarfile.open(fileobj=process.stdout, mode='r|') as archive:
|
||||
archive.extractall(snapshot, filter='data')
|
||||
finally:
|
||||
process.stdout.close()
|
||||
if process.wait():
|
||||
raise RuntimeError('无法生成源码快照')
|
||||
previous = Path(temp) / 'previous'
|
||||
destination = BASE / 'upstream'
|
||||
if destination.exists():
|
||||
destination.rename(previous)
|
||||
try:
|
||||
snapshot.rename(destination)
|
||||
except BaseException:
|
||||
if previous.exists():
|
||||
previous.rename(destination)
|
||||
raise
|
||||
(BASE / 'source-version.json').write_text(json.dumps({
|
||||
'repository': REPOSITORY, 'ref': 'master', 'commit': commit,
|
||||
}, indent=2) + '\n')
|
||||
print('已读取 GitHub 最新主分支:' + commit, flush=True)
|
||||
subprocess.run([sys.executable, str(BASE / 'builder/build.py')], check=True)
|
||||
subprocess.run([sys.executable, str(BASE / 'builder/check.py')], check=True)
|
||||
@@ -0,0 +1 @@
|
||||
Pillow==11.3.0
|
||||
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="CS-Notes:技术面试必备的计算机基础知识,涵盖算法、操作系统、计算机网络、数据库、Java 与系统设计。">
|
||||
<meta name="theme-color" content="#42b983">
|
||||
<title>CS-Notes · CyC2018</title>
|
||||
<link rel="icon" href="/favicon.png">
|
||||
<link rel="stylesheet" href="/vendor/vue.css">
|
||||
<link rel="stylesheet" href="/site.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">正在载入 CS-Notes…</div>
|
||||
<noscript>请启用 JavaScript 阅读网站,或访问 <a href="https://github.com/CyC2018/CS-Notes">GitHub 仓库</a>。</noscript>
|
||||
<script src="/search-paths.js"></script>
|
||||
<script src="/site.js"></script>
|
||||
<script src="/search.js"></script>
|
||||
<script src="/vendor/docsify.min.js"></script>
|
||||
<script src="/vendor/docsify-copy-code.min.js"></script>
|
||||
<script src="/vendor/zoom-image.min.js"></script>
|
||||
<script src="/vendor/prism-bash.min.js"></script>
|
||||
<script src="/vendor/prism-c.min.js"></script>
|
||||
<script src="/vendor/prism-java.min.js"></script>
|
||||
<script src="/vendor/prism-sql.min.js"></script>
|
||||
<script src="/vendor/prism-python.min.js"></script>
|
||||
<script src="/vendor/prism-json.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,506 @@
|
||||
{
|
||||
"/Java/Java IO": "/notes/Java IO",
|
||||
"/Java/Java IO.html": "/notes/Java IO",
|
||||
"/Java/Java IO.md": "/notes/Java IO",
|
||||
"/Java/Java 基础": "/notes/Java 基础",
|
||||
"/Java/Java 基础.html": "/notes/Java 基础",
|
||||
"/Java/Java 基础.md": "/notes/Java 基础",
|
||||
"/Java/Java 容器": "/notes/Java 容器",
|
||||
"/Java/Java 容器.html": "/notes/Java 容器",
|
||||
"/Java/Java 容器.md": "/notes/Java 容器",
|
||||
"/Java/Java 并发": "/notes/Java 并发",
|
||||
"/Java/Java 并发.html": "/notes/Java 并发",
|
||||
"/Java/Java 并发.md": "/notes/Java 并发",
|
||||
"/Java/Java 虚拟机": "/notes/Java 虚拟机",
|
||||
"/Java/Java 虚拟机.html": "/notes/Java 虚拟机",
|
||||
"/Java/Java 虚拟机.md": "/notes/Java 虚拟机",
|
||||
"/其它/系统设计/分布式": "/notes/分布式",
|
||||
"/其它/系统设计/分布式.html": "/notes/分布式",
|
||||
"/其它/系统设计/分布式.md": "/notes/分布式",
|
||||
"/其它/系统设计/攻击技术": "/notes/攻击技术",
|
||||
"/其它/系统设计/攻击技术.html": "/notes/攻击技术",
|
||||
"/其它/系统设计/攻击技术.md": "/notes/攻击技术",
|
||||
"/其它/系统设计/消息队列": "/notes/消息队列",
|
||||
"/其它/系统设计/消息队列.html": "/notes/消息队列",
|
||||
"/其它/系统设计/消息队列.md": "/notes/消息队列",
|
||||
"/其它/系统设计/系统设计基础": "/notes/系统设计基础",
|
||||
"/其它/系统设计/系统设计基础.html": "/notes/系统设计基础",
|
||||
"/其它/系统设计/系统设计基础.md": "/notes/系统设计基础",
|
||||
"/其它/系统设计/缓存": "/notes/缓存",
|
||||
"/其它/系统设计/缓存.html": "/notes/缓存",
|
||||
"/其它/系统设计/缓存.md": "/notes/缓存",
|
||||
"/其它/系统设计/集群": "/notes/集群",
|
||||
"/其它/系统设计/集群.html": "/notes/集群",
|
||||
"/其它/系统设计/集群.md": "/notes/集群",
|
||||
"/其它/编码实践/Docker": "/notes/Docker",
|
||||
"/其它/编码实践/Docker.html": "/notes/Docker",
|
||||
"/其它/编码实践/Docker.md": "/notes/Docker",
|
||||
"/其它/编码实践/Git": "/notes/Git",
|
||||
"/其它/编码实践/Git.html": "/notes/Git",
|
||||
"/其它/编码实践/Git.md": "/notes/Git",
|
||||
"/其它/编码实践/代码可读性": "/notes/代码可读性",
|
||||
"/其它/编码实践/代码可读性.html": "/notes/代码可读性",
|
||||
"/其它/编码实践/代码可读性.md": "/notes/代码可读性",
|
||||
"/其它/编码实践/代码风格规范": "/notes/代码风格规范",
|
||||
"/其它/编码实践/代码风格规范.html": "/notes/代码风格规范",
|
||||
"/其它/编码实践/代码风格规范.md": "/notes/代码风格规范",
|
||||
"/其它/编码实践/构建工具": "/notes/构建工具",
|
||||
"/其它/编码实践/构建工具.html": "/notes/构建工具",
|
||||
"/其它/编码实践/构建工具.md": "/notes/构建工具",
|
||||
"/其它/编码实践/正则表达式": "/notes/正则表达式",
|
||||
"/其它/编码实践/正则表达式.html": "/notes/正则表达式",
|
||||
"/其它/编码实践/正则表达式.md": "/notes/正则表达式",
|
||||
"/其它/编码实践/面向对象思想": "/notes/面向对象思想",
|
||||
"/其它/编码实践/面向对象思想.html": "/notes/面向对象思想",
|
||||
"/其它/编码实践/面向对象思想.md": "/notes/面向对象思想",
|
||||
"/其它/设计模式/设计模式 - 单例": "/notes/设计模式 - 单例",
|
||||
"/其它/设计模式/设计模式 - 单例.html": "/notes/设计模式 - 单例",
|
||||
"/其它/设计模式/设计模式 - 单例.md": "/notes/设计模式 - 单例",
|
||||
"/其它/设计模式/设计模式 - 中介者": "/notes/设计模式 - 中介者",
|
||||
"/其它/设计模式/设计模式 - 中介者.html": "/notes/设计模式 - 中介者",
|
||||
"/其它/设计模式/设计模式 - 中介者.md": "/notes/设计模式 - 中介者",
|
||||
"/其它/设计模式/设计模式 - 享元": "/notes/设计模式 - 享元",
|
||||
"/其它/设计模式/设计模式 - 享元.html": "/notes/设计模式 - 享元",
|
||||
"/其它/设计模式/设计模式 - 享元.md": "/notes/设计模式 - 享元",
|
||||
"/其它/设计模式/设计模式 - 代理": "/notes/设计模式 - 代理",
|
||||
"/其它/设计模式/设计模式 - 代理.html": "/notes/设计模式 - 代理",
|
||||
"/其它/设计模式/设计模式 - 代理.md": "/notes/设计模式 - 代理",
|
||||
"/其它/设计模式/设计模式 - 原型模式": "/notes/设计模式 - 原型模式",
|
||||
"/其它/设计模式/设计模式 - 原型模式.html": "/notes/设计模式 - 原型模式",
|
||||
"/其它/设计模式/设计模式 - 原型模式.md": "/notes/设计模式 - 原型模式",
|
||||
"/其它/设计模式/设计模式 - 命令": "/notes/设计模式 - 命令",
|
||||
"/其它/设计模式/设计模式 - 命令.html": "/notes/设计模式 - 命令",
|
||||
"/其它/设计模式/设计模式 - 命令.md": "/notes/设计模式 - 命令",
|
||||
"/其它/设计模式/设计模式 - 备忘录": "/notes/设计模式 - 备忘录",
|
||||
"/其它/设计模式/设计模式 - 备忘录.html": "/notes/设计模式 - 备忘录",
|
||||
"/其它/设计模式/设计模式 - 备忘录.md": "/notes/设计模式 - 备忘录",
|
||||
"/其它/设计模式/设计模式 - 外观": "/notes/设计模式 - 外观",
|
||||
"/其它/设计模式/设计模式 - 外观.html": "/notes/设计模式 - 外观",
|
||||
"/其它/设计模式/设计模式 - 外观.md": "/notes/设计模式 - 外观",
|
||||
"/其它/设计模式/设计模式 - 工厂方法": "/notes/设计模式 - 工厂方法",
|
||||
"/其它/设计模式/设计模式 - 工厂方法.html": "/notes/设计模式 - 工厂方法",
|
||||
"/其它/设计模式/设计模式 - 工厂方法.md": "/notes/设计模式 - 工厂方法",
|
||||
"/其它/设计模式/设计模式 - 抽象工厂": "/notes/设计模式 - 抽象工厂",
|
||||
"/其它/设计模式/设计模式 - 抽象工厂.html": "/notes/设计模式 - 抽象工厂",
|
||||
"/其它/设计模式/设计模式 - 抽象工厂.md": "/notes/设计模式 - 抽象工厂",
|
||||
"/其它/设计模式/设计模式 - 桥接": "/notes/设计模式 - 桥接",
|
||||
"/其它/设计模式/设计模式 - 桥接.html": "/notes/设计模式 - 桥接",
|
||||
"/其它/设计模式/设计模式 - 桥接.md": "/notes/设计模式 - 桥接",
|
||||
"/其它/设计模式/设计模式 - 模板方法": "/notes/设计模式 - 模板方法",
|
||||
"/其它/设计模式/设计模式 - 模板方法.html": "/notes/设计模式 - 模板方法",
|
||||
"/其它/设计模式/设计模式 - 模板方法.md": "/notes/设计模式 - 模板方法",
|
||||
"/其它/设计模式/设计模式 - 状态": "/notes/设计模式 - 状态",
|
||||
"/其它/设计模式/设计模式 - 状态.html": "/notes/设计模式 - 状态",
|
||||
"/其它/设计模式/设计模式 - 状态.md": "/notes/设计模式 - 状态",
|
||||
"/其它/设计模式/设计模式 - 生成器": "/notes/设计模式 - 生成器",
|
||||
"/其它/设计模式/设计模式 - 生成器.html": "/notes/设计模式 - 生成器",
|
||||
"/其它/设计模式/设计模式 - 生成器.md": "/notes/设计模式 - 生成器",
|
||||
"/其它/设计模式/设计模式 - 目录": "/notes/设计模式 - 目录",
|
||||
"/其它/设计模式/设计模式 - 目录.html": "/notes/设计模式 - 目录",
|
||||
"/其它/设计模式/设计模式 - 目录.md": "/notes/设计模式 - 目录",
|
||||
"/其它/设计模式/设计模式 - 空对象": "/notes/设计模式 - 空对象",
|
||||
"/其它/设计模式/设计模式 - 空对象.html": "/notes/设计模式 - 空对象",
|
||||
"/其它/设计模式/设计模式 - 空对象.md": "/notes/设计模式 - 空对象",
|
||||
"/其它/设计模式/设计模式 - 策略": "/notes/设计模式 - 策略",
|
||||
"/其它/设计模式/设计模式 - 策略.html": "/notes/设计模式 - 策略",
|
||||
"/其它/设计模式/设计模式 - 策略.md": "/notes/设计模式 - 策略",
|
||||
"/其它/设计模式/设计模式 - 简单工厂": "/notes/设计模式 - 简单工厂",
|
||||
"/其它/设计模式/设计模式 - 简单工厂.html": "/notes/设计模式 - 简单工厂",
|
||||
"/其它/设计模式/设计模式 - 简单工厂.md": "/notes/设计模式 - 简单工厂",
|
||||
"/其它/设计模式/设计模式 - 组合": "/notes/设计模式 - 组合",
|
||||
"/其它/设计模式/设计模式 - 组合.html": "/notes/设计模式 - 组合",
|
||||
"/其它/设计模式/设计模式 - 组合.md": "/notes/设计模式 - 组合",
|
||||
"/其它/设计模式/设计模式 - 装饰": "/notes/设计模式 - 装饰",
|
||||
"/其它/设计模式/设计模式 - 装饰.html": "/notes/设计模式 - 装饰",
|
||||
"/其它/设计模式/设计模式 - 装饰.md": "/notes/设计模式 - 装饰",
|
||||
"/其它/设计模式/设计模式 - 观察者": "/notes/设计模式 - 观察者",
|
||||
"/其它/设计模式/设计模式 - 观察者.html": "/notes/设计模式 - 观察者",
|
||||
"/其它/设计模式/设计模式 - 观察者.md": "/notes/设计模式 - 观察者",
|
||||
"/其它/设计模式/设计模式 - 解释器": "/notes/设计模式 - 解释器",
|
||||
"/其它/设计模式/设计模式 - 解释器.html": "/notes/设计模式 - 解释器",
|
||||
"/其它/设计模式/设计模式 - 解释器.md": "/notes/设计模式 - 解释器",
|
||||
"/其它/设计模式/设计模式 - 访问者": "/notes/设计模式 - 访问者",
|
||||
"/其它/设计模式/设计模式 - 访问者.html": "/notes/设计模式 - 访问者",
|
||||
"/其它/设计模式/设计模式 - 访问者.md": "/notes/设计模式 - 访问者",
|
||||
"/其它/设计模式/设计模式 - 责任链": "/notes/设计模式 - 责任链",
|
||||
"/其它/设计模式/设计模式 - 责任链.html": "/notes/设计模式 - 责任链",
|
||||
"/其它/设计模式/设计模式 - 责任链.md": "/notes/设计模式 - 责任链",
|
||||
"/其它/设计模式/设计模式 - 迭代器": "/notes/设计模式 - 迭代器",
|
||||
"/其它/设计模式/设计模式 - 迭代器.html": "/notes/设计模式 - 迭代器",
|
||||
"/其它/设计模式/设计模式 - 迭代器.md": "/notes/设计模式 - 迭代器",
|
||||
"/其它/设计模式/设计模式 - 适配器": "/notes/设计模式 - 适配器",
|
||||
"/其它/设计模式/设计模式 - 适配器.html": "/notes/设计模式 - 适配器",
|
||||
"/其它/设计模式/设计模式 - 适配器.md": "/notes/设计模式 - 适配器",
|
||||
"/数据库/MySQL": "/notes/MySQL",
|
||||
"/数据库/MySQL.html": "/notes/MySQL",
|
||||
"/数据库/MySQL.md": "/notes/MySQL",
|
||||
"/数据库/Redis": "/notes/Redis",
|
||||
"/数据库/Redis.html": "/notes/Redis",
|
||||
"/数据库/Redis.md": "/notes/Redis",
|
||||
"/数据库/SQL 练习": "/notes/SQL 练习",
|
||||
"/数据库/SQL 练习.html": "/notes/SQL 练习",
|
||||
"/数据库/SQL 练习.md": "/notes/SQL 练习",
|
||||
"/数据库/SQL 语法": "/notes/SQL 语法",
|
||||
"/数据库/SQL 语法.html": "/notes/SQL 语法",
|
||||
"/数据库/SQL 语法.md": "/notes/SQL 语法",
|
||||
"/数据库/数据库系统原理": "/notes/数据库系统原理",
|
||||
"/数据库/数据库系统原理.html": "/notes/数据库系统原理",
|
||||
"/数据库/数据库系统原理.md": "/notes/数据库系统原理",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 二分查找": "/notes/Leetcode 题解 - 二分查找",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 二分查找.html": "/notes/Leetcode 题解 - 二分查找",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 二分查找.md": "/notes/Leetcode 题解 - 二分查找",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 位运算": "/notes/Leetcode 题解 - 位运算",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 位运算.html": "/notes/Leetcode 题解 - 位运算",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 位运算.md": "/notes/Leetcode 题解 - 位运算",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 分治": "/notes/Leetcode 题解 - 分治",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 分治.html": "/notes/Leetcode 题解 - 分治",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 分治.md": "/notes/Leetcode 题解 - 分治",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 动态规划": "/notes/Leetcode 题解 - 动态规划",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 动态规划.html": "/notes/Leetcode 题解 - 动态规划",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 动态规划.md": "/notes/Leetcode 题解 - 动态规划",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 双指针": "/notes/Leetcode 题解 - 双指针",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 双指针.html": "/notes/Leetcode 题解 - 双指针",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 双指针.md": "/notes/Leetcode 题解 - 双指针",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 哈希表": "/notes/Leetcode 题解 - 哈希表",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 哈希表.html": "/notes/Leetcode 题解 - 哈希表",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 哈希表.md": "/notes/Leetcode 题解 - 哈希表",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 图": "/notes/Leetcode 题解 - 图",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 图.html": "/notes/Leetcode 题解 - 图",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 图.md": "/notes/Leetcode 题解 - 图",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 字符串": "/notes/Leetcode 题解 - 字符串",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 字符串.html": "/notes/Leetcode 题解 - 字符串",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 字符串.md": "/notes/Leetcode 题解 - 字符串",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 排序": "/notes/Leetcode 题解 - 排序",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 排序.html": "/notes/Leetcode 题解 - 排序",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 排序.md": "/notes/Leetcode 题解 - 排序",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 搜索": "/notes/Leetcode 题解 - 搜索",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 搜索.html": "/notes/Leetcode 题解 - 搜索",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 搜索.md": "/notes/Leetcode 题解 - 搜索",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 数学": "/notes/Leetcode 题解 - 数学",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 数学.html": "/notes/Leetcode 题解 - 数学",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 数学.md": "/notes/Leetcode 题解 - 数学",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 数组与矩阵": "/notes/Leetcode 题解 - 数组与矩阵",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 数组与矩阵.html": "/notes/Leetcode 题解 - 数组与矩阵",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 数组与矩阵.md": "/notes/Leetcode 题解 - 数组与矩阵",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 栈和队列": "/notes/Leetcode 题解 - 栈和队列",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 栈和队列.html": "/notes/Leetcode 题解 - 栈和队列",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 栈和队列.md": "/notes/Leetcode 题解 - 栈和队列",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 树": "/notes/Leetcode 题解 - 树",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 树.html": "/notes/Leetcode 题解 - 树",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 树.md": "/notes/Leetcode 题解 - 树",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 目录": "/notes/Leetcode 题解 - 目录",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 目录.html": "/notes/Leetcode 题解 - 目录",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 目录.md": "/notes/Leetcode 题解 - 目录",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 贪心思想": "/notes/Leetcode 题解 - 贪心思想",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 贪心思想.html": "/notes/Leetcode 题解 - 贪心思想",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 贪心思想.md": "/notes/Leetcode 题解 - 贪心思想",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 链表": "/notes/Leetcode 题解 - 链表",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 链表.html": "/notes/Leetcode 题解 - 链表",
|
||||
"/算法/Leetcode 题解/Leetcode 题解 - 链表.md": "/notes/Leetcode 题解 - 链表",
|
||||
"/算法/剑指 Offer 题解/10.1 斐波那契数列": "/notes/10.1 斐波那契数列",
|
||||
"/算法/剑指 Offer 题解/10.1 斐波那契数列.html": "/notes/10.1 斐波那契数列",
|
||||
"/算法/剑指 Offer 题解/10.1 斐波那契数列.md": "/notes/10.1 斐波那契数列",
|
||||
"/算法/剑指 Offer 题解/10.2 矩形覆盖": "/notes/10.2 矩形覆盖",
|
||||
"/算法/剑指 Offer 题解/10.2 矩形覆盖.html": "/notes/10.2 矩形覆盖",
|
||||
"/算法/剑指 Offer 题解/10.2 矩形覆盖.md": "/notes/10.2 矩形覆盖",
|
||||
"/算法/剑指 Offer 题解/10.3 跳台阶": "/notes/10.3 跳台阶",
|
||||
"/算法/剑指 Offer 题解/10.3 跳台阶.html": "/notes/10.3 跳台阶",
|
||||
"/算法/剑指 Offer 题解/10.3 跳台阶.md": "/notes/10.3 跳台阶",
|
||||
"/算法/剑指 Offer 题解/10.4 变态跳台阶": "/notes/10.4 变态跳台阶",
|
||||
"/算法/剑指 Offer 题解/10.4 变态跳台阶.html": "/notes/10.4 变态跳台阶",
|
||||
"/算法/剑指 Offer 题解/10.4 变态跳台阶.md": "/notes/10.4 变态跳台阶",
|
||||
"/算法/剑指 Offer 题解/11. 旋转数组的最小数字": "/notes/11. 旋转数组的最小数字",
|
||||
"/算法/剑指 Offer 题解/11. 旋转数组的最小数字.html": "/notes/11. 旋转数组的最小数字",
|
||||
"/算法/剑指 Offer 题解/11. 旋转数组的最小数字.md": "/notes/11. 旋转数组的最小数字",
|
||||
"/算法/剑指 Offer 题解/12. 矩阵中的路径": "/notes/12. 矩阵中的路径",
|
||||
"/算法/剑指 Offer 题解/12. 矩阵中的路径.html": "/notes/12. 矩阵中的路径",
|
||||
"/算法/剑指 Offer 题解/12. 矩阵中的路径.md": "/notes/12. 矩阵中的路径",
|
||||
"/算法/剑指 Offer 题解/13. 机器人的运动范围": "/notes/13. 机器人的运动范围",
|
||||
"/算法/剑指 Offer 题解/13. 机器人的运动范围.html": "/notes/13. 机器人的运动范围",
|
||||
"/算法/剑指 Offer 题解/13. 机器人的运动范围.md": "/notes/13. 机器人的运动范围",
|
||||
"/算法/剑指 Offer 题解/14. 剪绳子": "/notes/14. 剪绳子",
|
||||
"/算法/剑指 Offer 题解/14. 剪绳子.html": "/notes/14. 剪绳子",
|
||||
"/算法/剑指 Offer 题解/14. 剪绳子.md": "/notes/14. 剪绳子",
|
||||
"/算法/剑指 Offer 题解/15. 二进制中 1 的个数": "/notes/15. 二进制中 1 的个数",
|
||||
"/算法/剑指 Offer 题解/15. 二进制中 1 的个数.html": "/notes/15. 二进制中 1 的个数",
|
||||
"/算法/剑指 Offer 题解/15. 二进制中 1 的个数.md": "/notes/15. 二进制中 1 的个数",
|
||||
"/算法/剑指 Offer 题解/16. 数值的整数次方": "/notes/16. 数值的整数次方",
|
||||
"/算法/剑指 Offer 题解/16. 数值的整数次方.html": "/notes/16. 数值的整数次方",
|
||||
"/算法/剑指 Offer 题解/16. 数值的整数次方.md": "/notes/16. 数值的整数次方",
|
||||
"/算法/剑指 Offer 题解/17. 打印从 1 到最大的 n 位数": "/notes/17. 打印从 1 到最大的 n 位数",
|
||||
"/算法/剑指 Offer 题解/17. 打印从 1 到最大的 n 位数.html": "/notes/17. 打印从 1 到最大的 n 位数",
|
||||
"/算法/剑指 Offer 题解/17. 打印从 1 到最大的 n 位数.md": "/notes/17. 打印从 1 到最大的 n 位数",
|
||||
"/算法/剑指 Offer 题解/18.1 在 O(1) 时间内删除链表节点": "/notes/18.1 在 O(1) 时间内删除链表节点",
|
||||
"/算法/剑指 Offer 题解/18.1 在 O(1) 时间内删除链表节点.html": "/notes/18.1 在 O(1) 时间内删除链表节点",
|
||||
"/算法/剑指 Offer 题解/18.1 在 O(1) 时间内删除链表节点.md": "/notes/18.1 在 O(1) 时间内删除链表节点",
|
||||
"/算法/剑指 Offer 题解/18.2 删除链表中重复的结点": "/notes/18.2 删除链表中重复的结点",
|
||||
"/算法/剑指 Offer 题解/18.2 删除链表中重复的结点.html": "/notes/18.2 删除链表中重复的结点",
|
||||
"/算法/剑指 Offer 题解/18.2 删除链表中重复的结点.md": "/notes/18.2 删除链表中重复的结点",
|
||||
"/算法/剑指 Offer 题解/19. 正则表达式匹配": "/notes/19. 正则表达式匹配",
|
||||
"/算法/剑指 Offer 题解/19. 正则表达式匹配.html": "/notes/19. 正则表达式匹配",
|
||||
"/算法/剑指 Offer 题解/19. 正则表达式匹配.md": "/notes/19. 正则表达式匹配",
|
||||
"/算法/剑指 Offer 题解/20. 表示数值的字符串": "/notes/20. 表示数值的字符串",
|
||||
"/算法/剑指 Offer 题解/20. 表示数值的字符串.html": "/notes/20. 表示数值的字符串",
|
||||
"/算法/剑指 Offer 题解/20. 表示数值的字符串.md": "/notes/20. 表示数值的字符串",
|
||||
"/算法/剑指 Offer 题解/21. 调整数组顺序使奇数位于偶数前面": "/notes/21. 调整数组顺序使奇数位于偶数前面",
|
||||
"/算法/剑指 Offer 题解/21. 调整数组顺序使奇数位于偶数前面.html": "/notes/21. 调整数组顺序使奇数位于偶数前面",
|
||||
"/算法/剑指 Offer 题解/21. 调整数组顺序使奇数位于偶数前面.md": "/notes/21. 调整数组顺序使奇数位于偶数前面",
|
||||
"/算法/剑指 Offer 题解/22. 链表中倒数第 K 个结点": "/notes/22. 链表中倒数第 K 个结点",
|
||||
"/算法/剑指 Offer 题解/22. 链表中倒数第 K 个结点.html": "/notes/22. 链表中倒数第 K 个结点",
|
||||
"/算法/剑指 Offer 题解/22. 链表中倒数第 K 个结点.md": "/notes/22. 链表中倒数第 K 个结点",
|
||||
"/算法/剑指 Offer 题解/23. 链表中环的入口结点": "/notes/23. 链表中环的入口结点",
|
||||
"/算法/剑指 Offer 题解/23. 链表中环的入口结点.html": "/notes/23. 链表中环的入口结点",
|
||||
"/算法/剑指 Offer 题解/23. 链表中环的入口结点.md": "/notes/23. 链表中环的入口结点",
|
||||
"/算法/剑指 Offer 题解/24. 反转链表": "/notes/24. 反转链表",
|
||||
"/算法/剑指 Offer 题解/24. 反转链表.html": "/notes/24. 反转链表",
|
||||
"/算法/剑指 Offer 题解/24. 反转链表.md": "/notes/24. 反转链表",
|
||||
"/算法/剑指 Offer 题解/25. 合并两个排序的链表": "/notes/25. 合并两个排序的链表",
|
||||
"/算法/剑指 Offer 题解/25. 合并两个排序的链表.html": "/notes/25. 合并两个排序的链表",
|
||||
"/算法/剑指 Offer 题解/25. 合并两个排序的链表.md": "/notes/25. 合并两个排序的链表",
|
||||
"/算法/剑指 Offer 题解/26. 树的子结构": "/notes/26. 树的子结构",
|
||||
"/算法/剑指 Offer 题解/26. 树的子结构.html": "/notes/26. 树的子结构",
|
||||
"/算法/剑指 Offer 题解/26. 树的子结构.md": "/notes/26. 树的子结构",
|
||||
"/算法/剑指 Offer 题解/27. 二叉树的镜像": "/notes/27. 二叉树的镜像",
|
||||
"/算法/剑指 Offer 题解/27. 二叉树的镜像.html": "/notes/27. 二叉树的镜像",
|
||||
"/算法/剑指 Offer 题解/27. 二叉树的镜像.md": "/notes/27. 二叉树的镜像",
|
||||
"/算法/剑指 Offer 题解/28. 对称的二叉树": "/notes/28. 对称的二叉树",
|
||||
"/算法/剑指 Offer 题解/28. 对称的二叉树.html": "/notes/28. 对称的二叉树",
|
||||
"/算法/剑指 Offer 题解/28. 对称的二叉树.md": "/notes/28. 对称的二叉树",
|
||||
"/算法/剑指 Offer 题解/29. 顺时针打印矩阵": "/notes/29. 顺时针打印矩阵",
|
||||
"/算法/剑指 Offer 题解/29. 顺时针打印矩阵.html": "/notes/29. 顺时针打印矩阵",
|
||||
"/算法/剑指 Offer 题解/29. 顺时针打印矩阵.md": "/notes/29. 顺时针打印矩阵",
|
||||
"/算法/剑指 Offer 题解/3. 数组中重复的数字": "/notes/3. 数组中重复的数字",
|
||||
"/算法/剑指 Offer 题解/3. 数组中重复的数字.html": "/notes/3. 数组中重复的数字",
|
||||
"/算法/剑指 Offer 题解/3. 数组中重复的数字.md": "/notes/3. 数组中重复的数字",
|
||||
"/算法/剑指 Offer 题解/30. 包含 min 函数的栈": "/notes/30. 包含 min 函数的栈",
|
||||
"/算法/剑指 Offer 题解/30. 包含 min 函数的栈.html": "/notes/30. 包含 min 函数的栈",
|
||||
"/算法/剑指 Offer 题解/30. 包含 min 函数的栈.md": "/notes/30. 包含 min 函数的栈",
|
||||
"/算法/剑指 Offer 题解/31. 栈的压入、弹出序列": "/notes/31. 栈的压入、弹出序列",
|
||||
"/算法/剑指 Offer 题解/31. 栈的压入、弹出序列.html": "/notes/31. 栈的压入、弹出序列",
|
||||
"/算法/剑指 Offer 题解/31. 栈的压入、弹出序列.md": "/notes/31. 栈的压入、弹出序列",
|
||||
"/算法/剑指 Offer 题解/32.1 从上往下打印二叉树": "/notes/32.1 从上往下打印二叉树",
|
||||
"/算法/剑指 Offer 题解/32.1 从上往下打印二叉树.html": "/notes/32.1 从上往下打印二叉树",
|
||||
"/算法/剑指 Offer 题解/32.1 从上往下打印二叉树.md": "/notes/32.1 从上往下打印二叉树",
|
||||
"/算法/剑指 Offer 题解/32.2 把二叉树打印成多行": "/notes/32.2 把二叉树打印成多行",
|
||||
"/算法/剑指 Offer 题解/32.2 把二叉树打印成多行.html": "/notes/32.2 把二叉树打印成多行",
|
||||
"/算法/剑指 Offer 题解/32.2 把二叉树打印成多行.md": "/notes/32.2 把二叉树打印成多行",
|
||||
"/算法/剑指 Offer 题解/32.3 按之字形顺序打印二叉树": "/notes/32.3 按之字形顺序打印二叉树",
|
||||
"/算法/剑指 Offer 题解/32.3 按之字形顺序打印二叉树.html": "/notes/32.3 按之字形顺序打印二叉树",
|
||||
"/算法/剑指 Offer 题解/32.3 按之字形顺序打印二叉树.md": "/notes/32.3 按之字形顺序打印二叉树",
|
||||
"/算法/剑指 Offer 题解/33. 二叉搜索树的后序遍历序列": "/notes/33. 二叉搜索树的后序遍历序列",
|
||||
"/算法/剑指 Offer 题解/33. 二叉搜索树的后序遍历序列.html": "/notes/33. 二叉搜索树的后序遍历序列",
|
||||
"/算法/剑指 Offer 题解/33. 二叉搜索树的后序遍历序列.md": "/notes/33. 二叉搜索树的后序遍历序列",
|
||||
"/算法/剑指 Offer 题解/34. 二叉树中和为某一值的路径": "/notes/34. 二叉树中和为某一值的路径",
|
||||
"/算法/剑指 Offer 题解/34. 二叉树中和为某一值的路径.html": "/notes/34. 二叉树中和为某一值的路径",
|
||||
"/算法/剑指 Offer 题解/34. 二叉树中和为某一值的路径.md": "/notes/34. 二叉树中和为某一值的路径",
|
||||
"/算法/剑指 Offer 题解/35. 复杂链表的复制": "/notes/35. 复杂链表的复制",
|
||||
"/算法/剑指 Offer 题解/35. 复杂链表的复制.html": "/notes/35. 复杂链表的复制",
|
||||
"/算法/剑指 Offer 题解/35. 复杂链表的复制.md": "/notes/35. 复杂链表的复制",
|
||||
"/算法/剑指 Offer 题解/36. 二叉搜索树与双向链表": "/notes/36. 二叉搜索树与双向链表",
|
||||
"/算法/剑指 Offer 题解/36. 二叉搜索树与双向链表.html": "/notes/36. 二叉搜索树与双向链表",
|
||||
"/算法/剑指 Offer 题解/36. 二叉搜索树与双向链表.md": "/notes/36. 二叉搜索树与双向链表",
|
||||
"/算法/剑指 Offer 题解/37. 序列化二叉树": "/notes/37. 序列化二叉树",
|
||||
"/算法/剑指 Offer 题解/37. 序列化二叉树.html": "/notes/37. 序列化二叉树",
|
||||
"/算法/剑指 Offer 题解/37. 序列化二叉树.md": "/notes/37. 序列化二叉树",
|
||||
"/算法/剑指 Offer 题解/38. 字符串的排列": "/notes/38. 字符串的排列",
|
||||
"/算法/剑指 Offer 题解/38. 字符串的排列.html": "/notes/38. 字符串的排列",
|
||||
"/算法/剑指 Offer 题解/38. 字符串的排列.md": "/notes/38. 字符串的排列",
|
||||
"/算法/剑指 Offer 题解/39. 数组中出现次数超过一半的数字": "/notes/39. 数组中出现次数超过一半的数字",
|
||||
"/算法/剑指 Offer 题解/39. 数组中出现次数超过一半的数字.html": "/notes/39. 数组中出现次数超过一半的数字",
|
||||
"/算法/剑指 Offer 题解/39. 数组中出现次数超过一半的数字.md": "/notes/39. 数组中出现次数超过一半的数字",
|
||||
"/算法/剑指 Offer 题解/4. 二维数组中的查找": "/notes/4. 二维数组中的查找",
|
||||
"/算法/剑指 Offer 题解/4. 二维数组中的查找.html": "/notes/4. 二维数组中的查找",
|
||||
"/算法/剑指 Offer 题解/4. 二维数组中的查找.md": "/notes/4. 二维数组中的查找",
|
||||
"/算法/剑指 Offer 题解/40. 最小的 K 个数": "/notes/40. 最小的 K 个数",
|
||||
"/算法/剑指 Offer 题解/40. 最小的 K 个数.html": "/notes/40. 最小的 K 个数",
|
||||
"/算法/剑指 Offer 题解/40. 最小的 K 个数.md": "/notes/40. 最小的 K 个数",
|
||||
"/算法/剑指 Offer 题解/41.1 数据流中的中位数": "/notes/41.1 数据流中的中位数",
|
||||
"/算法/剑指 Offer 题解/41.1 数据流中的中位数.html": "/notes/41.1 数据流中的中位数",
|
||||
"/算法/剑指 Offer 题解/41.1 数据流中的中位数.md": "/notes/41.1 数据流中的中位数",
|
||||
"/算法/剑指 Offer 题解/41.2 字符流中第一个不重复的字符": "/notes/41.2 字符流中第一个不重复的字符",
|
||||
"/算法/剑指 Offer 题解/41.2 字符流中第一个不重复的字符.html": "/notes/41.2 字符流中第一个不重复的字符",
|
||||
"/算法/剑指 Offer 题解/41.2 字符流中第一个不重复的字符.md": "/notes/41.2 字符流中第一个不重复的字符",
|
||||
"/算法/剑指 Offer 题解/42. 连续子数组的最大和": "/notes/42. 连续子数组的最大和",
|
||||
"/算法/剑指 Offer 题解/42. 连续子数组的最大和.html": "/notes/42. 连续子数组的最大和",
|
||||
"/算法/剑指 Offer 题解/42. 连续子数组的最大和.md": "/notes/42. 连续子数组的最大和",
|
||||
"/算法/剑指 Offer 题解/43. 从 1 到 n 整数中 1 出现的次数": "/notes/43. 从 1 到 n 整数中 1 出现的次数",
|
||||
"/算法/剑指 Offer 题解/43. 从 1 到 n 整数中 1 出现的次数.html": "/notes/43. 从 1 到 n 整数中 1 出现的次数",
|
||||
"/算法/剑指 Offer 题解/43. 从 1 到 n 整数中 1 出现的次数.md": "/notes/43. 从 1 到 n 整数中 1 出现的次数",
|
||||
"/算法/剑指 Offer 题解/44. 数字序列中的某一位数字": "/notes/44. 数字序列中的某一位数字",
|
||||
"/算法/剑指 Offer 题解/44. 数字序列中的某一位数字.html": "/notes/44. 数字序列中的某一位数字",
|
||||
"/算法/剑指 Offer 题解/44. 数字序列中的某一位数字.md": "/notes/44. 数字序列中的某一位数字",
|
||||
"/算法/剑指 Offer 题解/45. 把数组排成最小的数": "/notes/45. 把数组排成最小的数",
|
||||
"/算法/剑指 Offer 题解/45. 把数组排成最小的数.html": "/notes/45. 把数组排成最小的数",
|
||||
"/算法/剑指 Offer 题解/45. 把数组排成最小的数.md": "/notes/45. 把数组排成最小的数",
|
||||
"/算法/剑指 Offer 题解/46. 把数字翻译成字符串": "/notes/46. 把数字翻译成字符串",
|
||||
"/算法/剑指 Offer 题解/46. 把数字翻译成字符串.html": "/notes/46. 把数字翻译成字符串",
|
||||
"/算法/剑指 Offer 题解/46. 把数字翻译成字符串.md": "/notes/46. 把数字翻译成字符串",
|
||||
"/算法/剑指 Offer 题解/47. 礼物的最大价值": "/notes/47. 礼物的最大价值",
|
||||
"/算法/剑指 Offer 题解/47. 礼物的最大价值.html": "/notes/47. 礼物的最大价值",
|
||||
"/算法/剑指 Offer 题解/47. 礼物的最大价值.md": "/notes/47. 礼物的最大价值",
|
||||
"/算法/剑指 Offer 题解/48. 最长不含重复字符的子字符串": "/notes/48. 最长不含重复字符的子字符串",
|
||||
"/算法/剑指 Offer 题解/48. 最长不含重复字符的子字符串.html": "/notes/48. 最长不含重复字符的子字符串",
|
||||
"/算法/剑指 Offer 题解/48. 最长不含重复字符的子字符串.md": "/notes/48. 最长不含重复字符的子字符串",
|
||||
"/算法/剑指 Offer 题解/49. 丑数": "/notes/49. 丑数",
|
||||
"/算法/剑指 Offer 题解/49. 丑数.html": "/notes/49. 丑数",
|
||||
"/算法/剑指 Offer 题解/49. 丑数.md": "/notes/49. 丑数",
|
||||
"/算法/剑指 Offer 题解/5. 替换空格": "/notes/5. 替换空格",
|
||||
"/算法/剑指 Offer 题解/5. 替换空格.html": "/notes/5. 替换空格",
|
||||
"/算法/剑指 Offer 题解/5. 替换空格.md": "/notes/5. 替换空格",
|
||||
"/算法/剑指 Offer 题解/50. 第一个只出现一次的字符位置": "/notes/50. 第一个只出现一次的字符位置",
|
||||
"/算法/剑指 Offer 题解/50. 第一个只出现一次的字符位置.html": "/notes/50. 第一个只出现一次的字符位置",
|
||||
"/算法/剑指 Offer 题解/50. 第一个只出现一次的字符位置.md": "/notes/50. 第一个只出现一次的字符位置",
|
||||
"/算法/剑指 Offer 题解/51. 数组中的逆序对": "/notes/51. 数组中的逆序对",
|
||||
"/算法/剑指 Offer 题解/51. 数组中的逆序对.html": "/notes/51. 数组中的逆序对",
|
||||
"/算法/剑指 Offer 题解/51. 数组中的逆序对.md": "/notes/51. 数组中的逆序对",
|
||||
"/算法/剑指 Offer 题解/52. 两个链表的第一个公共结点": "/notes/52. 两个链表的第一个公共结点",
|
||||
"/算法/剑指 Offer 题解/52. 两个链表的第一个公共结点.html": "/notes/52. 两个链表的第一个公共结点",
|
||||
"/算法/剑指 Offer 题解/52. 两个链表的第一个公共结点.md": "/notes/52. 两个链表的第一个公共结点",
|
||||
"/算法/剑指 Offer 题解/53. 数字在排序数组中出现的次数": "/notes/53. 数字在排序数组中出现的次数",
|
||||
"/算法/剑指 Offer 题解/53. 数字在排序数组中出现的次数.html": "/notes/53. 数字在排序数组中出现的次数",
|
||||
"/算法/剑指 Offer 题解/53. 数字在排序数组中出现的次数.md": "/notes/53. 数字在排序数组中出现的次数",
|
||||
"/算法/剑指 Offer 题解/54. 二叉查找树的第 K 个结点": "/notes/54. 二叉查找树的第 K 个结点",
|
||||
"/算法/剑指 Offer 题解/54. 二叉查找树的第 K 个结点.html": "/notes/54. 二叉查找树的第 K 个结点",
|
||||
"/算法/剑指 Offer 题解/54. 二叉查找树的第 K 个结点.md": "/notes/54. 二叉查找树的第 K 个结点",
|
||||
"/算法/剑指 Offer 题解/55.1 二叉树的深度": "/notes/55.1 二叉树的深度",
|
||||
"/算法/剑指 Offer 题解/55.1 二叉树的深度.html": "/notes/55.1 二叉树的深度",
|
||||
"/算法/剑指 Offer 题解/55.1 二叉树的深度.md": "/notes/55.1 二叉树的深度",
|
||||
"/算法/剑指 Offer 题解/55.2 平衡二叉树": "/notes/55.2 平衡二叉树",
|
||||
"/算法/剑指 Offer 题解/55.2 平衡二叉树.html": "/notes/55.2 平衡二叉树",
|
||||
"/算法/剑指 Offer 题解/55.2 平衡二叉树.md": "/notes/55.2 平衡二叉树",
|
||||
"/算法/剑指 Offer 题解/56. 数组中只出现一次的数字": "/notes/56. 数组中只出现一次的数字",
|
||||
"/算法/剑指 Offer 题解/56. 数组中只出现一次的数字.html": "/notes/56. 数组中只出现一次的数字",
|
||||
"/算法/剑指 Offer 题解/56. 数组中只出现一次的数字.md": "/notes/56. 数组中只出现一次的数字",
|
||||
"/算法/剑指 Offer 题解/57.1 和为 S 的两个数字": "/notes/57.1 和为 S 的两个数字",
|
||||
"/算法/剑指 Offer 题解/57.1 和为 S 的两个数字.html": "/notes/57.1 和为 S 的两个数字",
|
||||
"/算法/剑指 Offer 题解/57.1 和为 S 的两个数字.md": "/notes/57.1 和为 S 的两个数字",
|
||||
"/算法/剑指 Offer 题解/57.2 和为 S 的连续正数序列": "/notes/57.2 和为 S 的连续正数序列",
|
||||
"/算法/剑指 Offer 题解/57.2 和为 S 的连续正数序列.html": "/notes/57.2 和为 S 的连续正数序列",
|
||||
"/算法/剑指 Offer 题解/57.2 和为 S 的连续正数序列.md": "/notes/57.2 和为 S 的连续正数序列",
|
||||
"/算法/剑指 Offer 题解/58.1 翻转单词顺序列": "/notes/58.1 翻转单词顺序列",
|
||||
"/算法/剑指 Offer 题解/58.1 翻转单词顺序列.html": "/notes/58.1 翻转单词顺序列",
|
||||
"/算法/剑指 Offer 题解/58.1 翻转单词顺序列.md": "/notes/58.1 翻转单词顺序列",
|
||||
"/算法/剑指 Offer 题解/58.2 左旋转字符串": "/notes/58.2 左旋转字符串",
|
||||
"/算法/剑指 Offer 题解/58.2 左旋转字符串.html": "/notes/58.2 左旋转字符串",
|
||||
"/算法/剑指 Offer 题解/58.2 左旋转字符串.md": "/notes/58.2 左旋转字符串",
|
||||
"/算法/剑指 Offer 题解/59. 滑动窗口的最大值": "/notes/59. 滑动窗口的最大值",
|
||||
"/算法/剑指 Offer 题解/59. 滑动窗口的最大值.html": "/notes/59. 滑动窗口的最大值",
|
||||
"/算法/剑指 Offer 题解/59. 滑动窗口的最大值.md": "/notes/59. 滑动窗口的最大值",
|
||||
"/算法/剑指 Offer 题解/6. 从尾到头打印链表": "/notes/6. 从尾到头打印链表",
|
||||
"/算法/剑指 Offer 题解/6. 从尾到头打印链表.html": "/notes/6. 从尾到头打印链表",
|
||||
"/算法/剑指 Offer 题解/6. 从尾到头打印链表.md": "/notes/6. 从尾到头打印链表",
|
||||
"/算法/剑指 Offer 题解/60. n 个骰子的点数": "/notes/60. n 个骰子的点数",
|
||||
"/算法/剑指 Offer 题解/60. n 个骰子的点数.html": "/notes/60. n 个骰子的点数",
|
||||
"/算法/剑指 Offer 题解/60. n 个骰子的点数.md": "/notes/60. n 个骰子的点数",
|
||||
"/算法/剑指 Offer 题解/61. 扑克牌顺子": "/notes/61. 扑克牌顺子",
|
||||
"/算法/剑指 Offer 题解/61. 扑克牌顺子.html": "/notes/61. 扑克牌顺子",
|
||||
"/算法/剑指 Offer 题解/61. 扑克牌顺子.md": "/notes/61. 扑克牌顺子",
|
||||
"/算法/剑指 Offer 题解/62. 圆圈中最后剩下的数": "/notes/62. 圆圈中最后剩下的数",
|
||||
"/算法/剑指 Offer 题解/62. 圆圈中最后剩下的数.html": "/notes/62. 圆圈中最后剩下的数",
|
||||
"/算法/剑指 Offer 题解/62. 圆圈中最后剩下的数.md": "/notes/62. 圆圈中最后剩下的数",
|
||||
"/算法/剑指 Offer 题解/63. 股票的最大利润": "/notes/63. 股票的最大利润",
|
||||
"/算法/剑指 Offer 题解/63. 股票的最大利润.html": "/notes/63. 股票的最大利润",
|
||||
"/算法/剑指 Offer 题解/63. 股票的最大利润.md": "/notes/63. 股票的最大利润",
|
||||
"/算法/剑指 Offer 题解/64. 求 1+2+3+...+n": "/notes/64. 求 1+2+3+...+n",
|
||||
"/算法/剑指 Offer 题解/64. 求 1+2+3+...+n.html": "/notes/64. 求 1+2+3+...+n",
|
||||
"/算法/剑指 Offer 题解/64. 求 1+2+3+...+n.md": "/notes/64. 求 1+2+3+...+n",
|
||||
"/算法/剑指 Offer 题解/65. 不用加减乘除做加法": "/notes/65. 不用加减乘除做加法",
|
||||
"/算法/剑指 Offer 题解/65. 不用加减乘除做加法.html": "/notes/65. 不用加减乘除做加法",
|
||||
"/算法/剑指 Offer 题解/65. 不用加减乘除做加法.md": "/notes/65. 不用加减乘除做加法",
|
||||
"/算法/剑指 Offer 题解/66. 构建乘积数组": "/notes/66. 构建乘积数组",
|
||||
"/算法/剑指 Offer 题解/66. 构建乘积数组.html": "/notes/66. 构建乘积数组",
|
||||
"/算法/剑指 Offer 题解/66. 构建乘积数组.md": "/notes/66. 构建乘积数组",
|
||||
"/算法/剑指 Offer 题解/67. 把字符串转换成整数": "/notes/67. 把字符串转换成整数",
|
||||
"/算法/剑指 Offer 题解/67. 把字符串转换成整数.html": "/notes/67. 把字符串转换成整数",
|
||||
"/算法/剑指 Offer 题解/67. 把字符串转换成整数.md": "/notes/67. 把字符串转换成整数",
|
||||
"/算法/剑指 Offer 题解/68. 树中两个节点的最低公共祖先": "/notes/68. 树中两个节点的最低公共祖先",
|
||||
"/算法/剑指 Offer 题解/68. 树中两个节点的最低公共祖先.html": "/notes/68. 树中两个节点的最低公共祖先",
|
||||
"/算法/剑指 Offer 题解/68. 树中两个节点的最低公共祖先.md": "/notes/68. 树中两个节点的最低公共祖先",
|
||||
"/算法/剑指 Offer 题解/7. 重建二叉树": "/notes/7. 重建二叉树",
|
||||
"/算法/剑指 Offer 题解/7. 重建二叉树.html": "/notes/7. 重建二叉树",
|
||||
"/算法/剑指 Offer 题解/7. 重建二叉树.md": "/notes/7. 重建二叉树",
|
||||
"/算法/剑指 Offer 题解/8. 二叉树的下一个结点": "/notes/8. 二叉树的下一个结点",
|
||||
"/算法/剑指 Offer 题解/8. 二叉树的下一个结点.html": "/notes/8. 二叉树的下一个结点",
|
||||
"/算法/剑指 Offer 题解/8. 二叉树的下一个结点.md": "/notes/8. 二叉树的下一个结点",
|
||||
"/算法/剑指 Offer 题解/9. 用两个栈实现队列": "/notes/9. 用两个栈实现队列",
|
||||
"/算法/剑指 Offer 题解/9. 用两个栈实现队列.html": "/notes/9. 用两个栈实现队列",
|
||||
"/算法/剑指 Offer 题解/9. 用两个栈实现队列.md": "/notes/9. 用两个栈实现队列",
|
||||
"/算法/剑指 Offer 题解/剑指 Offer 题解 - 目录": "/notes/剑指 Offer 题解 - 目录",
|
||||
"/算法/剑指 Offer 题解/剑指 Offer 题解 - 目录.html": "/notes/剑指 Offer 题解 - 目录",
|
||||
"/算法/剑指 Offer 题解/剑指 Offer 题解 - 目录.md": "/notes/剑指 Offer 题解 - 目录",
|
||||
"/算法/基础/算法 - 其它": "/notes/算法 - 其它",
|
||||
"/算法/基础/算法 - 其它.html": "/notes/算法 - 其它",
|
||||
"/算法/基础/算法 - 其它.md": "/notes/算法 - 其它",
|
||||
"/算法/基础/算法 - 并查集": "/notes/算法 - 并查集",
|
||||
"/算法/基础/算法 - 并查集.html": "/notes/算法 - 并查集",
|
||||
"/算法/基础/算法 - 并查集.md": "/notes/算法 - 并查集",
|
||||
"/算法/基础/算法 - 排序": "/notes/算法 - 排序",
|
||||
"/算法/基础/算法 - 排序.html": "/notes/算法 - 排序",
|
||||
"/算法/基础/算法 - 排序.md": "/notes/算法 - 排序",
|
||||
"/算法/基础/算法 - 栈和队列": "/notes/算法 - 栈和队列",
|
||||
"/算法/基础/算法 - 栈和队列.html": "/notes/算法 - 栈和队列",
|
||||
"/算法/基础/算法 - 栈和队列.md": "/notes/算法 - 栈和队列",
|
||||
"/算法/基础/算法 - 目录": "/notes/算法 - 目录",
|
||||
"/算法/基础/算法 - 目录.html": "/notes/算法 - 目录",
|
||||
"/算法/基础/算法 - 目录.md": "/notes/算法 - 目录",
|
||||
"/算法/基础/算法 - 符号表": "/notes/算法 - 符号表",
|
||||
"/算法/基础/算法 - 符号表.html": "/notes/算法 - 符号表",
|
||||
"/算法/基础/算法 - 符号表.md": "/notes/算法 - 符号表",
|
||||
"/算法/基础/算法 - 算法分析": "/notes/算法 - 算法分析",
|
||||
"/算法/基础/算法 - 算法分析.html": "/notes/算法 - 算法分析",
|
||||
"/算法/基础/算法 - 算法分析.md": "/notes/算法 - 算法分析",
|
||||
"/算法/基础/算法": "/notes/算法",
|
||||
"/算法/基础/算法.html": "/notes/算法",
|
||||
"/算法/基础/算法.md": "/notes/算法",
|
||||
"/计算机基础/HTTP/HTTP": "/notes/HTTP",
|
||||
"/计算机基础/HTTP/HTTP.html": "/notes/HTTP",
|
||||
"/计算机基础/HTTP/HTTP.md": "/notes/HTTP",
|
||||
"/计算机基础/Linux/Linux": "/notes/Linux",
|
||||
"/计算机基础/Linux/Linux.html": "/notes/Linux",
|
||||
"/计算机基础/Linux/Linux.md": "/notes/Linux",
|
||||
"/计算机基础/Socket/Socket": "/notes/Socket",
|
||||
"/计算机基础/Socket/Socket.html": "/notes/Socket",
|
||||
"/计算机基础/Socket/Socket.md": "/notes/Socket",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 内存管理": "/notes/计算机操作系统 - 内存管理",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 内存管理.html": "/notes/计算机操作系统 - 内存管理",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 内存管理.md": "/notes/计算机操作系统 - 内存管理",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 概述": "/notes/计算机操作系统 - 概述",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 概述.html": "/notes/计算机操作系统 - 概述",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 概述.md": "/notes/计算机操作系统 - 概述",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 死锁": "/notes/计算机操作系统 - 死锁",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 死锁.html": "/notes/计算机操作系统 - 死锁",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 死锁.md": "/notes/计算机操作系统 - 死锁",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 目录": "/notes/计算机操作系统 - 目录",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 目录.html": "/notes/计算机操作系统 - 目录",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 目录.md": "/notes/计算机操作系统 - 目录",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 设备管理": "/notes/计算机操作系统 - 设备管理",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 设备管理.html": "/notes/计算机操作系统 - 设备管理",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 设备管理.md": "/notes/计算机操作系统 - 设备管理",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 进程管理": "/notes/计算机操作系统 - 进程管理",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 进程管理.html": "/notes/计算机操作系统 - 进程管理",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 进程管理.md": "/notes/计算机操作系统 - 进程管理",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 链接": "/notes/计算机操作系统 - 链接",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 链接.html": "/notes/计算机操作系统 - 链接",
|
||||
"/计算机基础/操作系统基础/计算机操作系统 - 链接.md": "/notes/计算机操作系统 - 链接",
|
||||
"/计算机基础/网络基础/计算机网络 - 传输层": "/notes/计算机网络 - 传输层",
|
||||
"/计算机基础/网络基础/计算机网络 - 传输层.html": "/notes/计算机网络 - 传输层",
|
||||
"/计算机基础/网络基础/计算机网络 - 传输层.md": "/notes/计算机网络 - 传输层",
|
||||
"/计算机基础/网络基础/计算机网络 - 应用层": "/notes/计算机网络 - 应用层",
|
||||
"/计算机基础/网络基础/计算机网络 - 应用层.html": "/notes/计算机网络 - 应用层",
|
||||
"/计算机基础/网络基础/计算机网络 - 应用层.md": "/notes/计算机网络 - 应用层",
|
||||
"/计算机基础/网络基础/计算机网络 - 概述": "/notes/计算机网络 - 概述",
|
||||
"/计算机基础/网络基础/计算机网络 - 概述.html": "/notes/计算机网络 - 概述",
|
||||
"/计算机基础/网络基础/计算机网络 - 概述.md": "/notes/计算机网络 - 概述",
|
||||
"/计算机基础/网络基础/计算机网络 - 物理层": "/notes/计算机网络 - 物理层",
|
||||
"/计算机基础/网络基础/计算机网络 - 物理层.html": "/notes/计算机网络 - 物理层",
|
||||
"/计算机基础/网络基础/计算机网络 - 物理层.md": "/notes/计算机网络 - 物理层",
|
||||
"/计算机基础/网络基础/计算机网络 - 目录": "/notes/计算机网络 - 目录",
|
||||
"/计算机基础/网络基础/计算机网络 - 目录.html": "/notes/计算机网络 - 目录",
|
||||
"/计算机基础/网络基础/计算机网络 - 目录.md": "/notes/计算机网络 - 目录",
|
||||
"/计算机基础/网络基础/计算机网络 - 网络层": "/notes/计算机网络 - 网络层",
|
||||
"/计算机基础/网络基础/计算机网络 - 网络层.html": "/notes/计算机网络 - 网络层",
|
||||
"/计算机基础/网络基础/计算机网络 - 网络层.md": "/notes/计算机网络 - 网络层",
|
||||
"/计算机基础/网络基础/计算机网络 - 链路层": "/notes/计算机网络 - 链路层",
|
||||
"/计算机基础/网络基础/计算机网络 - 链路层.html": "/notes/计算机网络 - 链路层",
|
||||
"/计算机基础/网络基础/计算机网络 - 链路层.md": "/notes/计算机网络 - 链路层"
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function normalize(value) {
|
||||
return String(value).normalize('NFKC').toLowerCase().replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function search(index, query, limit) {
|
||||
var terms = normalize(query).slice(0, 120).split(' ').filter(Boolean);
|
||||
if (!terms.length) return [];
|
||||
var results = [];
|
||||
index.documents.forEach(function (doc) {
|
||||
var page = normalize(doc.title);
|
||||
doc.sections.forEach(function (section) {
|
||||
var heading = normalize(section.title);
|
||||
var body = normalize(section.text);
|
||||
var text = heading + ' ' + page + ' ' + body;
|
||||
if (!terms.every(function (term) { return text.includes(term); })) return;
|
||||
var score = terms.reduce(function (sum, term) {
|
||||
return sum + (heading.includes(term) ? 40 : 0) + (page.includes(term) ? 15 : 0) +
|
||||
(body.includes(term) ? Math.min(body.split(term).length - 1, 6) : 0);
|
||||
}, 0);
|
||||
if (heading === normalize(query)) score += 100;
|
||||
results.push({ page: doc.title, path: doc.path, title: section.title || doc.title,
|
||||
id: section.id, text: section.text, score: score });
|
||||
});
|
||||
});
|
||||
return results.sort(function (a, b) { return b.score - a.score; }).slice(0, limit || 20);
|
||||
}
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = { search: search, normalize: normalize };
|
||||
return;
|
||||
}
|
||||
|
||||
var indexPromise;
|
||||
function loadIndex() {
|
||||
if (!indexPromise) {
|
||||
indexPromise = fetch(window.CS_NOTES_SEARCH_INDEX, { credentials: 'omit' }).then(function (response) {
|
||||
if (!response.ok) throw new Error('Search index unavailable');
|
||||
return response.json();
|
||||
}).then(function (index) {
|
||||
if (index.version !== 1 || !Array.isArray(index.documents)) throw new Error('Invalid search index');
|
||||
return index;
|
||||
}).catch(function (error) { indexPromise = null; throw error; });
|
||||
}
|
||||
return indexPromise;
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value).replace(/[&<>"']/g, function (character) {
|
||||
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[character];
|
||||
});
|
||||
}
|
||||
|
||||
function snippet(text, query) {
|
||||
var first = normalize(query).split(' ')[0];
|
||||
var position = normalize(text).indexOf(first);
|
||||
var start = Math.max(0, position - 45);
|
||||
return (start ? '…' : '') + text.slice(start, start + 170) + (text.length > start + 170 ? '…' : '');
|
||||
}
|
||||
|
||||
function mountSearch() {
|
||||
var sidebar = document.querySelector('.sidebar');
|
||||
if (!sidebar || sidebar.querySelector('.site-search')) return;
|
||||
var wrapper = document.createElement('div');
|
||||
wrapper.className = 'search site-search';
|
||||
wrapper.innerHTML = '<input type="search" aria-label="搜索全部笔记" aria-controls="site-search-results" placeholder="搜索全部笔记" autocomplete="off">' +
|
||||
'<p class="search-status" role="status" aria-live="polite"></p>' +
|
||||
'<div id="site-search-results" class="results-panel" hidden></div>';
|
||||
sidebar.insertBefore(wrapper, sidebar.firstChild);
|
||||
var input = wrapper.querySelector('input');
|
||||
var status = wrapper.querySelector('.search-status');
|
||||
var panel = wrapper.querySelector('.results-panel');
|
||||
var timer;
|
||||
var sequence = 0;
|
||||
|
||||
function update() {
|
||||
var query = input.value.trim();
|
||||
var request = ++sequence;
|
||||
panel.hidden = !query;
|
||||
panel.innerHTML = '';
|
||||
if (!query) { status.textContent = ''; return; }
|
||||
status.textContent = '正在搜索…';
|
||||
loadIndex().then(function (index) {
|
||||
if (request !== sequence) return;
|
||||
var results = search(index, query);
|
||||
status.textContent = results.length ? '找到 ' + results.length + ' 个相关章节' : '没有找到相关内容';
|
||||
panel.innerHTML = results.map(function (result) {
|
||||
var url = '/#' + result.path.split('/').map(encodeURIComponent).join('/') +
|
||||
(result.id ? '?id=' + encodeURIComponent(result.id) : '');
|
||||
return '<a class="matching-post" href="' + escapeHtml(url) + '"><strong>' + escapeHtml(result.title) +
|
||||
'</strong><small>' + escapeHtml(result.page) + '</small><p>' + escapeHtml(snippet(result.text, query)) + '</p></a>';
|
||||
}).join('');
|
||||
}).catch(function () {
|
||||
if (request === sequence) status.textContent = '搜索暂时不可用,请稍后重试。';
|
||||
});
|
||||
}
|
||||
input.addEventListener('focus', function () { loadIndex().catch(function () {}); });
|
||||
input.addEventListener('input', function () { clearTimeout(timer); timer = setTimeout(update, 140); });
|
||||
input.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Escape') { input.value = ''; update(); }
|
||||
var first = panel.querySelector('a');
|
||||
if (first && event.key === 'ArrowDown') { event.preventDefault(); first.focus(); }
|
||||
if (first && event.key === 'Enter') { event.preventDefault(); first.click(); }
|
||||
});
|
||||
panel.addEventListener('click', function (event) {
|
||||
if (event.target.closest('a') && window.matchMedia('(max-width: 768px)').matches) {
|
||||
document.body.classList.remove('close');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
window.$docsify.plugins.push(function (hook) {
|
||||
hook.mounted(mountSearch);
|
||||
hook.doneEach(mountSearch);
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,54 @@
|
||||
:root { --theme-color: #369b72; }
|
||||
body, .sidebar, .sidebar-toggle { background: #f3f2ee; }
|
||||
body { color: #34495e; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif; }
|
||||
.app-name { font-weight: 650 !important; letter-spacing: .02em; }
|
||||
.sidebar { padding-top: 24px; border-right: 1px solid #e2e3dc; }
|
||||
.sidebar .search { border-bottom: 1px solid #e2e3dc; margin-bottom: 20px; }
|
||||
.search input { background: #fff !important; border-radius: 6px; }
|
||||
.sidebar-nav > ul { padding-left: 18px; }
|
||||
.sidebar ul li { margin: 7px 0; }
|
||||
.sidebar ul li strong { font-size: 13px; letter-spacing: .08em; color: #718078; }
|
||||
.sidebar ul li a { font-size: 14px; }
|
||||
.markdown-section { max-width: 960px; padding: 36px 48px 100px; }
|
||||
.markdown-section h1 { margin: 1rem 0 2rem; font-size: 2rem; }
|
||||
.markdown-section h2 { margin: 2.8rem 0 1.2rem; padding-bottom: .45rem; border-bottom: 1px solid #dfe3da; }
|
||||
.markdown-section h3 { margin-top: 2rem; }
|
||||
.markdown-section p, .markdown-section ul, .markdown-section ol { line-height: 1.95; }
|
||||
.markdown-section img { max-width: 100%; height: auto; border-radius: 4px; }
|
||||
.markdown-section pre { background: #fafbf8; border-radius: 6px; border: 1px solid #e2e6dc; }
|
||||
.markdown-section pre > code { font-family: "SFMono-Regular", Consolas, monospace; line-height: 1.65; }
|
||||
.markdown-section table { display: block; overflow: auto; }
|
||||
.markdown-section tr:nth-child(2n) { background: #f8f9f5; }
|
||||
.markdown-section td, .markdown-section th { border-color: #dfe3da; }
|
||||
.docsify-copy-code-button { font-family: inherit !important; }
|
||||
.search .matching-post { border-color: #dde3d8; }
|
||||
.site-footer { border-top: 1px solid #dfe3da; margin-top: 64px; padding-top: 20px; color: #7c877d; font-size: 12px; text-align: center; }
|
||||
.site-footer a { color: inherit; font-weight: 400; }
|
||||
.topic-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 10px; margin: 18px 0; }
|
||||
.topic-grid a { display: flex; align-items: center; justify-content: center; gap: 7px; padding: 13px 8px; border: 1px solid #dfe3da; border-radius: 7px; background: #fafbf8; text-decoration: none; font-size: 14px; white-space: nowrap; }
|
||||
.topic-grid a:hover, .topic-grid a:focus-visible { background: #e8f1e9; border-color: #369b72; }
|
||||
.site-search { padding: 8px 15px; }
|
||||
.site-search input { box-sizing: border-box; width: 100%; min-height: 42px; padding: 10px; border: 1px solid #dfe3da; color: #34495e; font-size: 15px; }
|
||||
.site-search input:focus { outline: 2px solid #369b72; outline-offset: 1px; }
|
||||
.search-status { color: #718078; font-size: 12px; margin: 7px 0; }
|
||||
.search-status:empty { display: none; }
|
||||
.site-search .matching-post { display: block; padding: 12px 0; border-bottom: 1px solid #dde3d8; text-decoration: none; }
|
||||
.matching-post strong, .matching-post small { display: block; }
|
||||
.matching-post strong { color: #369b72; font-size: 14px; }
|
||||
.matching-post small { color: #718078; margin: 5px 0; font-size: 12px; }
|
||||
.matching-post p { color: #34495e; margin: 4px 0; font-size: 13px; line-height: 1.7; overflow-wrap: anywhere; }
|
||||
.article-toc { border: 1px solid #dfe3da; border-radius: 7px; padding: 12px 18px; margin: 20px 0 28px; background: #fafbf8; }
|
||||
.article-toc summary { color: #369b72; font-weight: 600; cursor: pointer; min-height: 24px; }
|
||||
.article-toc > ul { margin-bottom: 0; }
|
||||
@media (min-width: 769px) { .content { padding-top: 12px; } }
|
||||
@media (max-width: 768px) {
|
||||
.markdown-section { padding: 24px 22px 80px; }
|
||||
.markdown-section h1 { font-size: 1.65rem; }
|
||||
.markdown-section pre { padding: 14px; }
|
||||
.topic-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
|
||||
.topic-grid a { min-height: 44px; padding: 8px; }
|
||||
.site-logo { width: 110px; }
|
||||
.sidebar-toggle { width: 48px; height: 48px; padding: 13px; background: rgba(243, 242, 238, .95); }
|
||||
.sidebar ul li a { min-height: 28px; line-height: 2; }
|
||||
.article-toc summary { min-height: 32px; line-height: 2; }
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
(function () {
|
||||
function decode(value) {
|
||||
try { return decodeURIComponent(value); } catch (error) { return value; }
|
||||
}
|
||||
function encodedRoute(path) { return path.split('/').map(encodeURIComponent).join('/'); }
|
||||
function preserveLegacyLinks() {
|
||||
var hash = location.hash;
|
||||
var legacy = (window.CS_NOTES_LEGACY_ROUTES || {})[decode(location.pathname)];
|
||||
if (legacy) {
|
||||
var heading = hash && !hash.startsWith('#/') ? '?id=' + encodeURIComponent(decode(hash.slice(1))) : '';
|
||||
location.replace('/#' + encodedRoute(legacy) + heading);
|
||||
return;
|
||||
}
|
||||
var route = decode(hash.split('?')[0].replace(/^#\//, '').replace(/\.md$/, ''));
|
||||
if (route && !route.includes('/') && window.CS_NOTES_PATHS.includes('/notes/' + route)) {
|
||||
location.replace('#/notes/' + encodeURIComponent(route) + (hash.includes('?') ? '?' + hash.split('?').slice(1).join('?') : ''));
|
||||
}
|
||||
}
|
||||
preserveLegacyLinks();
|
||||
window.addEventListener('hashchange', preserveLegacyLinks);
|
||||
window.$docsify = {
|
||||
name: 'CS-Notes',
|
||||
repo: 'https://github.com/CyC2018/CS-Notes',
|
||||
homepage: 'README.md',
|
||||
loadSidebar: true,
|
||||
alias: { '/.*/_sidebar.md': '/_sidebar.md' },
|
||||
subMaxLevel: 0,
|
||||
auto2top: true,
|
||||
notFoundPage: true,
|
||||
relativePath: false,
|
||||
copyCode: { buttonText: '复制', errorText: '复制失败', successText: '已复制' },
|
||||
plugins: [function (hook, vm) {
|
||||
hook.afterEach(function (content) {
|
||||
content = content.replace(/<img\b[^>]*>/gi, function (tag) {
|
||||
if (!/\bloading\s*=/i.test(tag)) tag = tag.replace(/<img\b/i, '<img loading="lazy"');
|
||||
if (!/\bdecoding\s*=/i.test(tag)) tag = tag.replace(/<img\b/i, '<img decoding="async"');
|
||||
return tag;
|
||||
});
|
||||
var document = new DOMParser().parseFromString(content, 'text/html');
|
||||
var heading = document.querySelector('h1');
|
||||
var list = heading && heading.nextElementSibling;
|
||||
if (list && list.tagName === 'UL' && list.querySelectorAll('a').length > 5 &&
|
||||
Array.from(list.querySelectorAll('a')).every(function (link) { return link.getAttribute('href').includes('?id='); })) {
|
||||
var details = document.createElement('details');
|
||||
details.className = 'article-toc';
|
||||
details.innerHTML = '<summary>本文目录</summary>';
|
||||
list.replaceWith(details);
|
||||
details.appendChild(list);
|
||||
}
|
||||
return document.body.innerHTML + '<footer class="site-footer"><a href="https://github.com/CyC2018/CS-Notes" target="_blank" rel="noopener">CyC2018 · CS-Notes</a><span> · </span><a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener">粤ICP备19071404号-1</a></footer>';
|
||||
});
|
||||
hook.doneEach(function () {
|
||||
var heading = document.querySelector('.markdown-section h1, .markdown-section h2');
|
||||
document.title = (vm.route.path === '/' || !heading ? 'CS-Notes' : heading.textContent.trim() + ' · CS-Notes') + ' · CyC2018';
|
||||
document.querySelectorAll('.markdown-section img').forEach(function (img) {
|
||||
img.loading = 'lazy';
|
||||
img.decoding = 'async';
|
||||
});
|
||||
document.querySelectorAll('.article-toc').forEach(function (toc) {
|
||||
toc.open = window.matchMedia('(min-width: 769px)').matches;
|
||||
});
|
||||
});
|
||||
}]
|
||||
};
|
||||
})();
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 - present Docsify Contributors (https://github.com/docsifyjs/docsify/graphs/contributors)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 JP Erasmus
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* docsify-copy-code
|
||||
* v3.0.2
|
||||
* https://github.com/jperasmus/docsify-copy-code
|
||||
* (c) 2017-2025 JP Erasmus <jperasmus11@gmail.com>
|
||||
* MIT license
|
||||
*/
|
||||
!function(){"use strict";function e(o){return e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},e(o)}!function(e,o){void 0===o&&(o={});var t=o.insertAt;if(e&&"undefined"!=typeof document){var n=document.head||document.getElementsByTagName("head")[0],c=document.createElement("style");c.type="text/css","top"===t&&n.firstChild?n.insertBefore(c,n.firstChild):n.appendChild(c),c.styleSheet?c.styleSheet.cssText=e:c.appendChild(document.createTextNode(e))}}(".docsify-copy-code-button,.docsify-copy-code-button>span{cursor:pointer;transition:all .25s ease}.docsify-copy-code-button{background:grey;background:var(--theme-color,grey);border:0;border-radius:0;color:#fff;font-size:1em;opacity:0;outline:0;overflow:visible;padding:.65em .8em;position:absolute;right:0;top:0;z-index:1}.docsify-copy-code-button>span{background:inherit;border-radius:3px;pointer-events:none}.docsify-copy-code-button>.error,.docsify-copy-code-button>.success{font-size:.825em;opacity:0;padding:.5em .65em;position:absolute;right:0;top:50%;transform:translateY(-50%);z-index:-100}.docsify-copy-code-button.error>.error,.docsify-copy-code-button.success>.success{opacity:1;right:100%;transform:translate(-25%,-50%)}.docsify-copy-code-button:focus,pre:hover .docsify-copy-code-button{opacity:1}.docsify-copy-code-button>[aria-live]{height:1px;left:-10000px;overflow:hidden;position:absolute;top:auto;width:1px}"),document.querySelector('link[href*="docsify-copy-code"]')&&console.warn("[Deprecation] Link to external docsify-copy-code stylesheet is no longer necessary."),window.DocsifyCopyCodePlugin={init:function(){return function(e,o){e.ready((function(){console.warn("[Deprecation] Manually initializing docsify-copy-code using window.DocsifyCopyCodePlugin.init() is no longer necessary.")}))}}},window.$docsify=window.$docsify||{},window.$docsify.plugins=[function(o,t){var n={buttonText:"Copy to clipboard",errorText:"Error",successText:"Copied"};o.doneEach((function(){var o=Array.from(document.querySelectorAll("pre[data-lang]"));t.config.copyCode&&Object.keys(n).forEach((function(o){var c=t.config.copyCode[o];"string"==typeof c?n[o]=c:"object"===e(c)&&Object.keys(c).some((function(e){var t=location.href.indexOf(e)>-1;return n[o]=t?c[e]:n[o],t}))}));var c=['<button class="docsify-copy-code-button">','<span class="label">'.concat(n.buttonText,"</span>"),'<span class="error" aria-hidden="hidden">'.concat(n.errorText,"</span>"),'<span class="success" aria-hidden="hidden">'.concat(n.successText,"</span>"),'<span aria-live="polite"></span>',"</button>"].join("");o.forEach((function(e){e.insertAdjacentHTML("beforeend",c)}))})),o.mounted((function(){var e=document.querySelector(".content");e&&e.addEventListener("click",(function(e){if(e.target.classList.contains("docsify-copy-code-button")){var o="BUTTON"===e.target.tagName?e.target:e.target.parentNode,t=document.createRange(),c=o.parentNode.querySelector("code"),i=o.querySelector("[aria-live]"),r=window.getSelection();t.selectNode(c),r&&(r.removeAllRanges(),r.addRange(t));try{document.execCommand("copy")&&(o.classList.add("success"),i.innerText=n.successText,setTimeout((function(){o.classList.remove("success"),i.innerText=""}),1e3))}catch(e){console.error("docsify-copy-code: ".concat(e)),o.classList.add("error"),i.innerText=n.errorText,setTimeout((function(){o.classList.remove("error"),i.innerText=""}),1e3)}setTimeout((function(){e.target.blur()}),1500),(r=window.getSelection())&&("function"==typeof r.removeRange?r.removeRange(t):"function"==typeof r.removeAllRanges&&r.removeAllRanges())}}))}))}].concat(window.$docsify.plugins||[])}();
|
||||
//# sourceMappingURL=docsify-copy-code.min.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+17
@@ -0,0 +1,17 @@
|
||||
[
|
||||
{
|
||||
"package": "docsify",
|
||||
"version": "4.13.1",
|
||||
"source": "https://registry.npmjs.org/docsify/-/docsify-4.13.1.tgz"
|
||||
},
|
||||
{
|
||||
"package": "prismjs",
|
||||
"version": "1.30.0",
|
||||
"source": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz"
|
||||
},
|
||||
{
|
||||
"package": "docsify-copy-code",
|
||||
"version": "3.0.2",
|
||||
"source": "https://registry.npmjs.org/docsify-copy-code/-/docsify-copy-code-3.0.2.tgz"
|
||||
}
|
||||
]
|
||||
+1
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
Prism.languages.c=Prism.languages.extend("clike",{comment:{pattern:/\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},"class-name":{pattern:/(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,lookbehind:!0},keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/,function:/\b[a-z_]\w*(?=\s*\()/i,number:/(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),Prism.languages.insertBefore("c","string",{char:{pattern:/'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,greedy:!0}}),Prism.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},Prism.languages.c.string],char:Prism.languages.c.char,comment:Prism.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:Prism.languages.c}}}}),Prism.languages.insertBefore("c","function",{constant:/\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/}),delete Prism.languages.c.boolean;
|
||||
+1
@@ -0,0 +1 @@
|
||||
!function(e){var n=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record(?!\s*[(){}[\]<>=%~.:,;?+\-*/&|^])|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,t="(?:[a-z]\\w*\\s*\\.\\s*)*(?:[A-Z]\\w*\\s*\\.\\s*)*",s={pattern:RegExp("(^|[^\\w.])"+t+"[A-Z](?:[\\d_A-Z]*[a-z]\\w*)?\\b"),lookbehind:!0,inside:{namespace:{pattern:/^[a-z]\w*(?:\s*\.\s*[a-z]\w*)*(?:\s*\.)?/,inside:{punctuation:/\./}},punctuation:/\./}};e.languages.java=e.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"/,lookbehind:!0,greedy:!0},"class-name":[s,{pattern:RegExp("(^|[^\\w.])"+t+"[A-Z]\\w*(?=\\s+\\w+\\s*[;,=()]|\\s*(?:\\[[\\s,]*\\]\\s*)?::\\s*new\\b)"),lookbehind:!0,inside:s.inside},{pattern:RegExp("(\\b(?:class|enum|extends|implements|instanceof|interface|new|record|throws)\\s+)"+t+"[A-Z]\\w*\\b"),lookbehind:!0,inside:s.inside}],keyword:n,function:[e.languages.clike.function,{pattern:/(::\s*)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x(?:\.[\da-f_p+-]+|[\da-f_]+(?:\.[\da-f_p+-]+)?)\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0},constant:/\b[A-Z][A-Z_\d]+\b/}),e.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"},char:{pattern:/'(?:\\.|[^'\\\r\n]){1,6}'/,greedy:!0}}),e.languages.insertBefore("java","class-name",{annotation:{pattern:/(^|[^.])@\w+(?:\s*\.\s*\w+)*/,lookbehind:!0,alias:"punctuation"},generics:{pattern:/<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&))*>)*>)*>)*>/,inside:{"class-name":s,keyword:n,punctuation:/[<>(),.:]/,operator:/[?&|]/}},import:[{pattern:RegExp("(\\bimport\\s+)"+t+"(?:[A-Z]\\w*|\\*)(?=\\s*;)"),lookbehind:!0,inside:{namespace:s.inside.namespace,punctuation:/\./,operator:/\*/,"class-name":/\w+/}},{pattern:RegExp("(\\bimport\\s+static\\s+)"+t+"(?:\\w+|\\*)(?=\\s*;)"),lookbehind:!0,alias:"static",inside:{namespace:s.inside.namespace,static:/\b\w+$/,punctuation:/\./,operator:/\*/,"class-name":/\w+/}}],namespace:{pattern:RegExp("(\\b(?:exports|import(?:\\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\\s+)(?!<keyword>)[a-z]\\w*(?:\\.[a-z]\\w*)*\\.?".replace(/<keyword>/g,(function(){return n.source}))),lookbehind:!0,inside:{punctuation:/\./}}})}(Prism);
|
||||
+1
@@ -0,0 +1 @@
|
||||
Prism.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},Prism.languages.webmanifest=Prism.languages.json;
|
||||
@@ -0,0 +1 @@
|
||||
Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern://,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python;
|
||||
+1
@@ -0,0 +1 @@
|
||||
Prism.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},variable:[{pattern:/@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,greedy:!0},/@[\w.$]+/],string:{pattern:/(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\]|\2\2)*\2/,greedy:!0,lookbehind:!0},identifier:{pattern:/(^|[^@\\])`(?:\\[\s\S]|[^`\\]|``)*`/,greedy:!0,lookbehind:!0,inside:{punctuation:/^`|`$/}},function:/\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:COL|_INSERT)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURN(?:ING|S)?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,boolean:/\b(?:FALSE|NULL|TRUE)\b/i,number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?|\B\.\d+\b/i,operator:/[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/};
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT LICENSE
|
||||
|
||||
Copyright (c) 2012 Lea Verou
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user