#!/usr/bin/python3 """sscDD.py -- Python port of sscDD.php v1.1.2 (CFBaum). Reads the BOC bocode RDF files (x and y) from RePEc and prints one line per Stata package: " ", where is the Revision-Date if it is later than the Creation-Date, otherwise the Creation-Date. Writes the listing to standard output; run from cron and redirect it to the static file that Stata reads. Exits with status 1 (and writes nothing to standard output) if either source file cannot be fetched. Notes on fidelity to the PHP original: * Lines are handled as raw bytes and split on "\n" only, as PHP file() does, so non-UTF-8 characters and stray "\r" pass through unchanged. * The regexes have the same semantics as the PHP preg_match patterns ("$" also matches before a final newline; "." does not match "\n"). * The PHP fmt() function was dead code (its results were never used), so it is not ported. * The date comparison mirrors PHP 8's "$revd > $cred": numeric when both are numeric strings, otherwise a plain string comparison (so an empty Revision-Date never wins). cfb 20261025: removed bocodey """ import io import sys import re import urllib.request SOURCES = ( "http://RePEc.org/boc/bocode/bocodex.rdf", ) TIMEOUT = 30 # seconds per fetch TITLE_RE = re.compile(rb"^Title: (.+): Stata (.+)$") CREATED_RE = re.compile(rb"^Creation-Date: (.+)$") REVISED_RE = re.compile(rb"^Revision-Date: (.+)$") HANDLE_RE = re.compile(rb"^Handle: (.+)bocode:S(.+)\n$") CHOP = b" \t\n\r\0\x0b" # characters stripped by PHP chop()/rtrim() def is_later(a, b): """Return PHP 8's result for `a > b` on two date strings.""" if a.isdigit() and b.isdigit(): return int(a) > int(b) return a > b def fetch_lines(): """Fetch every source file and return their lines, in order.""" lines = [] for url in SOURCES: with urllib.request.urlopen(url, timeout=TIMEOUT) as resp: lines.extend(io.BytesIO(resp.read()).readlines()) return lines def build_listing(lines): """Return the listing (bytes) for the given RDF lines.""" out = [] name = b"" cred = b"" revd = b"" for line in lines: m = TITLE_RE.match(line) if m: name = m.group(1) m = CREATED_RE.match(line) if m: cred = m.group(1).rstrip(CHOP) m = REVISED_RE.match(line) if m: revd = m.group(1).rstrip(CHOP) if HANDLE_RE.match(line): date = revd if is_later(revd, cred) else cred out.append(b" " + name + b" " + date + b"\n") cred = b"" revd = b"" return b"".join(out) def main(): try: body = build_listing(fetch_lines()) except Exception as exc: # network or HTTP error reading RePEc sys.stderr.write("sscDD.py: could not read source data: %s\n" % exc) sys.exit(1) # nonzero, so "&& mv" in cron keeps the old file out = sys.stdout.buffer out.write(body) out.flush() if __name__ == "__main__": main()