← Share New
Permanent link

untitled.md

Shared 10/1/2026

Raw

Check a device and software source log before sharing it

Use this Python checker to find missing fields and repeated device/software pairs in a CSV source log. Save the complete source below as check_records.py. It uses Python's standard library.

Prepare the CSV

Include these column names: device, software, source_url, and read_date. Write dates as YYYY-MM-DD. Put the public source URL beside the device and software names. The checker accepts HTTP and HTTPS URLs with a hostname and rejects URLs containing login details or whitespace.

Save the file as records.csv. Run python3 check_records.py records.csv from the folder containing both files. You can pass - instead of a filename to read CSV from standard input.

Read the result

The checker prints a JSON report with the number of records and an issues list. Each issue includes a record number and the fields to review. Record numbering starts at two, after the header.

The checker flags missing values, uneven column counts and invalid date or URL forms. The checker also flags repeated device/software pairs, ignoring letter case. Check the named records against your source notes before changing them.

An exit status of zero means the checker found no row issues. One means the report contains issues. Two means the checker could not read the file or accept its header. A header-only CSV reports zero records, so check the record count before treating a file as complete.

Keep the source check separate

This checker does not open URLs, read source pages or test device compatibility. A clean report describes the CSV fields. Read the underlying documents before drawing a compatibility conclusion.

GolfCompat publishes this source-log checker. The complete original source and its MIT license follow. Retain the license when copying the software.

Source: Source-record CSV checker; publisher: GolfCompat; date: 2026 copyright year.

Complete Python source

#!/usr/bin/env python3
"""Check source-record CSV completeness without fetching URLs."""
import argparse
import csv
import datetime
import json
import sys
from urllib.parse import urlsplit

FIELDS = ("device", "software", "source_url", "read_date")


def check(stream):
    reader = csv.DictReader(stream)
    if not reader.fieldnames or any(f not in reader.fieldnames for f in FIELDS):
        raise ValueError("Required columns: " + ", ".join(FIELDS))
    if len(reader.fieldnames) != len(set(reader.fieldnames)):
        raise ValueError("Duplicate column names")
    issues = []
    seen = {}
    count = 0
    for number, row in enumerate(reader, 2):
        count += 1
        errors = []
        if None in row or any(row.get(f) is None for f in FIELDS):
            errors.append("column-count")
        values = {f: (row.get(f) or "").strip() for f in FIELDS}
        for field in FIELDS:
            if not values[field]:
                errors.append("missing:" + field)
        if values["source_url"]:
            try:
                url = urlsplit(values["source_url"])
                valid = (url.scheme in ("http", "https") and url.hostname
                         and not url.username and not url.password
                         and not any(c.isspace() for c in values["source_url"]))
                _ = url.port
            except ValueError:
                valid = False
            if not valid:
                errors.append("invalid:source_url")
        if values["read_date"]:
            date = values["read_date"]
            try:
                valid_date = (len(date) == 10 and datetime.date.fromisoformat(date).isoformat() == date)
            except ValueError:
                valid_date = False
            if not valid_date:
                errors.append("invalid:read_date")
        pair = (values["device"].casefold(), values["software"].casefold())
        if all(pair):
            if pair in seen:
                errors.append("duplicate-pair:record-" + str(seen[pair]))
            else:
                seen[pair] = number
        if errors:
            issues.append({"record": number, "issues": errors})
    return {"records": count, "issues": issues}


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("csv_file", help="CSV path, or - for standard input")
    args = parser.parse_args()
    try:
        if args.csv_file == "-":
            report = check(sys.stdin)
        else:
            with open(args.csv_file, encoding="utf-8-sig", newline="") as stream:
                report = check(stream)
        print(json.dumps(report, ensure_ascii=False, indent=2))
        return 1 if report["issues"] else 0
    except (OSError, UnicodeError, ValueError, csv.Error) as error:
        print(str(error), file=sys.stderr)
        return 2


if __name__ == "__main__":
    sys.exit(main())

License

MIT License

Copyright (c) 2026 GolfCompat

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.

This link does not expire.