From 24c4ea8b96ed01e0bdd18f62e065ae7d6edcfe0b Mon Sep 17 00:00:00 2001 From: Greg Wilson Date: Fri, 30 Aug 2024 11:14:42 -0400 Subject: [PATCH] feat: add site statistics version: 0.2.5 --- README.md | 1 + mccole/clui.py | 6 ++++++ mccole/lint.py | 13 +------------ mccole/stats.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ mccole/util.py | 12 ++++++++++++ pyproject.toml | 3 ++- 6 files changed, 70 insertions(+), 13 deletions(-) create mode 100644 mccole/stats.py diff --git a/README.md b/README.md index 6ae6713..04a011a 100644 --- a/README.md +++ b/README.md @@ -14,3 +14,4 @@ After installation, the following commands will be available: - `mccole lint`: internal project check - `mccole render`: Markdown-to-HTML translator +- `mccole stats`: site statistics diff --git a/mccole/clui.py b/mccole/clui.py index a3ea70c..7608beb 100644 --- a/mccole/clui.py +++ b/mccole/clui.py @@ -8,6 +8,7 @@ from .lint import lint, parse_args as lint_parser from .render import render, parse_args as render_parser +from .stats import stats, parse_args as stats_parser INSTALL_FILES = ( @@ -23,10 +24,13 @@ def main(): '''Main driver.''' parser = argparse.ArgumentParser() parser.add_argument('--version', action='store_true', help='show version') + subparsers = parser.add_subparsers(dest='cmd') install_parser(subparsers.add_parser('install', help='install files')) lint_parser(subparsers.add_parser('lint', help='check site')) render_parser(subparsers.add_parser('render', help='build site')) + stats_parser(subparsers.add_parser('stats', help='show stats')) + opt = parser.parse_args() if opt.version: print(importlib.metadata.version('mccole')) @@ -36,6 +40,8 @@ def main(): lint(opt) elif opt.cmd == 'render': render(opt) + elif opt.cmd == 'stats': + stats(opt) else: print(f'unknown command {opt.cmd}', file=sys.stderr) sys.exit(1) diff --git a/mccole/lint.py b/mccole/lint.py index 0c515d6..f8758d5 100644 --- a/mccole/lint.py +++ b/mccole/lint.py @@ -6,15 +6,13 @@ import re import tomli -from .util import SUFFIXES, SUFFIXES_SRC, find_files +from .util import MD_LINK_DEF, SUFFIXES, SUFFIXES_SRC, find_files, find_key_defs BIB_REF = re.compile(r"\[.+?\]\(b:(.+?)\)", re.MULTILINE) GLOSS_REF = re.compile(r"\[.+?\]\(g:(.+?)\)", re.MULTILINE) -KEY_DEF = re.compile(r'^.+?\s*$', re.MULTILINE) MD_CODEBLOCK_FILE = re.compile(r"^```\s*\{\s*\.(.+?)\s+\#(.+?)\s*\}\s*$(.+?)^```\s*$", re.DOTALL + re.MULTILINE) MD_FILE_LINK = re.compile(r"\[(.+?)\]\((.+?)\)", re.MULTILINE) -MD_LINK_DEF = re.compile(r"^\[(.+?)\]:\s+(.+?)\s*$", re.MULTILINE) MD_LINK_REF = re.compile(r"\[(.+?)\]\[(.+?)\]", re.MULTILINE) DEFAULT_CONFIG = { @@ -83,15 +81,6 @@ def check_references(files, term, regexp, available): return ok -def find_key_defs(files, term): - """Find key definitions in definition list file.""" - candidates = [k for k in files if term in str(k).lower()] - if len(candidates) != 1: - return None - file_key = candidates[0] - return set(KEY_DEF.findall(files[file_key])) - - def is_missing(actual, available): """Is a file missing?""" return (not actual.exists()) or ( diff --git a/mccole/stats.py b/mccole/stats.py new file mode 100644 index 0000000..d119a43 --- /dev/null +++ b/mccole/stats.py @@ -0,0 +1,48 @@ +"""Report site stats.""" + +import argparse +from prettytable import PrettyTable + +from .util import MD_LINK_DEF, SUFFIXES, SUFFIXES_SRC, find_files, find_key_defs + + +TABLE_FMT = { + "stat": "l", + "value": "r", +} + + +def stats(opt): + """Main driver.""" + table = PrettyTable(field_names=TABLE_FMT.keys()) + for k, v in TABLE_FMT.items(): + table.align[k] = v + + files = find_files(opt, set(["bin", opt.out])) + table.add_row(("bibliography entries", len(find_key_defs(files, "bibliography")))) + table.add_row(("glossary entries", len(find_key_defs(files, "glossary")))) + table.add_row(("link definitions", len(find_markdown_link_defs(files)))) + + print(table) + + +def find_markdown_link_defs(files): + """Collect Markdown link key definnitions.""" + found = set() + for filepath, content in files.items(): + if filepath.suffix == ".md": + for link in MD_LINK_DEF.finditer(content): + found.add(link[0]) + return found + + +def parse_args(parser): + """Parse command-line arguments.""" + parser.add_argument("--out", type=str, default="docs", help="output directory") + parser.add_argument("--root", type=str, default=".", help="root directory") + return parser + + +if __name__ == "__main__": + opt = parse_args(argparse.ArgumentParser()).parse_args() + stats(opt) diff --git a/mccole/util.py b/mccole/util.py index bdf4bbc..bfcce8b 100644 --- a/mccole/util.py +++ b/mccole/util.py @@ -1,8 +1,11 @@ """Utilities.""" from pathlib import Path +import re +KEY_DEF = re.compile(r'^.+?\s*$', re.MULTILINE) +MD_LINK_DEF = re.compile(r"^\[(.+?)\]:\s+(.+?)\s*$", re.MULTILINE) SUFFIXES_BIN = {".ico", ".jpg", ".png"} SUFFIXES_SRC = {".css", ".html", ".js", ".md", ".py", ".sh", ".txt"} SUFFIXES_TXT = SUFFIXES_SRC | {".csv", ".json", ".svg"} @@ -18,6 +21,15 @@ def find_files(opt, root_skips=[]): } +def find_key_defs(files, term): + """Find key definitions in definition list file.""" + candidates = [k for k in files if term in str(k).lower()] + if len(candidates) != 1: + return None + file_key = candidates[0] + return set(KEY_DEF.findall(files[file_key])) + + def find_symlinks(opt, root_skips=[]): """Collect all interesting files.""" return [ diff --git a/pyproject.toml b/pyproject.toml index 88741c9..48e1849 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ mypkg = ["*.css", "*.js", "*.html"] [project] name = "mccole" -version = "0.2.4" +version = "0.2.5" authors = [ {name = "Greg Wilson", email = "gvwilson@third-bit.com"}, ] @@ -31,6 +31,7 @@ dependencies = [ "html5validator>=0.4.2", "jinja2>=3.1.4", "markdown>=3.6", + "prettytable>=3.11", "ruff>=0.6.0", "tomli>=2.0.1", ]