mwseaql 0.3.0

MediaWiki table definitions for sea-ql
Documentation
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2022 Kunal Mehta <legoktm@debian.org>

import json
import requests
import subprocess
from copy import deepcopy
from pathlib import Path


TOOLFORGE_USERINDEX = [
    "archive",
    "filearchive",
    "logging",
    "oldimage",
    "recentchanges",
    "revision",
]

TOOLFORGE_ACTOR = [
    "user",
    "archive",
    "ipblocks",
    "image",
    "oldimage",
    "filearchive",
    "recentchanges",
    "logging",
    "revision"
]


def snake_to_camel(phrase: str) -> str:
    """snake_case to CamelCase"""
    sp = phrase.split('_')
    return ''.join(word.title() for word in sp)


def strip_prefix(items: list) -> dict:
    """Strip a common prefix from a list of items"""
    data = {}
    for item in items:
        data[item] = item
    # Unmodified copy of the "last" round
    last = deepcopy(data)
    while True:
        # First segment of the first thing
        first = list(data.values())[0].split('_', 1)[0] + '_'
        print(first)
        for key in data:
            print(key)
            print(data[key])
            if data[key].startswith(first):
                # It also starts with it, shift over by the segment
                data[key] = data[key][len(first):]
            else:
                return last
        # Finished a round
        last = deepcopy(data)


def build_rust(table: dict, toolforge=False) -> str:
    if toolforge:
        rust = '#[cfg(feature = "toolforge")]\n'
        rust += '#[cfg_attr(docs, doc(cfg(feature = "toolforge")))]\n'
    else:
        rust = ""
    name = table["name"]
    rust += f"/// `{name}` table\n"
    rust += "///\n"
    if toolforge:
        rust += "/// This is an optimized view for Toolforge users.\n"
    else:
        rust += f"/// Documentation may be available on [mediawiki.org](https://www.mediawiki.org/wiki/Manual:{name}_table).\n"
    rust += "#[non_exhaustive]\n"
    rust += "#[derive(Copy, Clone, IdenStatic)]\n"
    rust += f'#[iden="{name}"]\n'
    camel_name = snake_to_camel(name)
    print(f"Generating {camel_name} for {name}")
    rust += f"pub enum {camel_name} {{\nTable,\n"
    columns = [item["name"] for item in table["columns"]]
    stripped = strip_prefix(columns)
    for column, name in sorted(stripped.items()):
        camel_name = snake_to_camel(name)
        rust += f"/// `{column}` column\n"
        rust += f'#[iden="{column}"]\n'
        rust += f"{camel_name},\n"
    rust += "}"
    rust += "\n\n"
    return rust


def make(repo: str, path: str, filename: str):
    req = requests.get(f"https://github.com/wikimedia/{repo.replace('/', '-')}/raw/master/{path}")
    req.raise_for_status()
    tables = req.json()
    rust = "// This file is autogenerated.\n"
    rust += f"//! Schema definitions for the `{repo}` repository\n"
    rust += "use sea_query::IdenStatic;\n\n"
    for table in sorted(tables, key=lambda x: x["name"]):
        rust += build_rust(table)
        if table["name"] in TOOLFORGE_USERINDEX:
            table["name"] = f"{table['name']}_userindex"
            rust += build_rust(table, toolforge=True)
        if table["name"] == "actor":
            for variant in TOOLFORGE_ACTOR:
                table["name"] = f"actor_{variant}"
                rust += build_rust(table, toolforge=True)

    Path(f"src/{filename}.rs").write_text(rust)
    subprocess.check_call(["cargo", "fmt"])
    print("Done!")


def main():
    make('mediawiki/core', 'maintenance/tables.json', 'core')
    make('mediawiki/extensions/Linter', 'sql/tables.json', 'linter')
    make('mediawiki/extensions/PageAssessments', 'db/tables.json', 'page_assessments')
    make('mediawiki/extensions/ProofreadPage', 'sql/tables.json', 'proofread_page')


if __name__ == "__main__":
    main()