feat: auto-open generated bestpractices.dev URL in browser when run interactively

This commit is contained in:
Devid HW
2026-08-26 03:26:38 +07:00
parent c65c4b5d1c
commit 65b0346628
+41 -3
View File
@@ -8,15 +8,21 @@ Usage:
(default: passing) (default: passing)
--exclude: comma-separated criterion names to SKIP from the pre-filled URL --exclude: comma-separated criterion names to SKIP from the pre-filled URL
(they stay at their current Unmet/?/N/A in the form). (they stay at their current Unmet/?/N/A in the form).
--na: comma-separated criterion names to mark as N/A in the pre-filled URL --na: comma-separated criterion names to mark as N/A in the pre-filled URL
(emits <name>_status=N/A with the justification from the JSON). (emits <name>_status=N/A with the justification from the JSON).
--no-open: never open the generated URL in a browser (useful for CI/piping).
--open: force-open the URL even when stdout is not a TTY (e.g. when piped).
When run interactively (stdout is a TTY) the script auto-opens the generated
edit URL in the default browser. Piping to a file or another command prints the
URL only (no browser spawn). Use --no-open to suppress auto-open, --open to
force it regardless of TTY.
The script fetches the upstream criteria.yml to learn which criteria belong to The script fetches the upstream criteria.yml to learn which criteria belong to
which tier/section, then restricts BOTH the printed incomplete list AND the which tier/section, then restricts BOTH the printed incomplete list AND the
generated edit-URL parameters to only that section's criteria. generated edit-URL parameters to only that section's criteria.
""" """
import sys, os, json, urllib.request, urllib.parse, argparse import sys, os, json, subprocess, urllib.request, urllib.parse, argparse
import ssl import ssl
try: try:
@@ -64,6 +70,27 @@ def fetch(url):
return r.read().decode() return r.read().decode()
def open_url(url):
"""Open `url` in the default browser for the current platform.
Fails silently with a clear fallback message if the opener is unavailable
or errors — never raises, so the script always exits cleanly.
"""
if sys.platform == "darwin":
cmd = ["open", url]
elif sys.platform.startswith("win"):
# Windows: `start` needs a dummy title before the URL argument.
cmd = ["cmd", "/c", "start", "", url]
else:
cmd = ["xdg-open", url]
try:
subprocess.run(cmd, check=True)
except Exception as e:
print(f"\nCould not auto-open the browser ({e}).\n"
f"Open this URL manually:\n{url}", file=sys.stderr)
def _collect_names(node, out): def _collect_names(node, out):
"""Recursively walk the criteria.yml nested structure. """Recursively walk the criteria.yml nested structure.
@@ -127,6 +154,11 @@ def main():
parser.add_argument("--na", default="", parser.add_argument("--na", default="",
help="Comma-separated criterion names to mark as N/A " help="Comma-separated criterion names to mark as N/A "
"with the justification from cii_justifications.json.") "with the justification from cii_justifications.json.")
parser.add_argument("--no-open", action="store_true",
help="Never open the generated URL in a browser "
"(useful for CI/piping).")
parser.add_argument("--open", action="store_true",
help="Force-open the URL even when stdout is not a TTY.")
args = parser.parse_args() args = parser.parse_args()
project_id = args.project_id project_id = args.project_id
@@ -215,6 +247,12 @@ def main():
"justifications from cii_justifications.json.") "justifications from cii_justifications.json.")
print("Review EVERY field before Save; only mark Met what you genuinely satisfy.") print("Review EVERY field before Save; only mark Met what you genuinely satisfy.")
# Auto-open the URL in a browser. Default: only when run interactively
# (stdout is a TTY) and --no-open was not given. --open forces it even when
# piped; --no-open always disables it.
if args.open or (sys.stdout.isatty() and not args.no_open):
open_url(edit_url)
if __name__ == "__main__": if __name__ == "__main__":
main() main()