Skip to content

Commit 91a0a64

Browse files
committed
feat(cut_header): --skips and --ignores can be GitHub gist URLs
1 parent 8bb927d commit 91a0a64

1 file changed

Lines changed: 57 additions & 14 deletions

File tree

cut_header.py

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
import argparse
44
import csv
55
import curses
6+
import io
67
import logging
78
import platform
9+
import re
810
import subprocess
911
import sys
12+
import urllib.request
1013
from typing import List, Optional, Set, Tuple
1114

1215
import networkx as nx
@@ -531,25 +534,53 @@ def calculate_floors(
531534
}
532535

533536

537+
def is_gist_url(path: str) -> bool:
538+
"""Check if a path is a GitHub gist URL."""
539+
return bool(re.match(r"https?://gist\.github(?:usercontent)?\.com/", path))
540+
541+
542+
def gist_to_raw_url(url: str) -> str:
543+
"""Convert a GitHub gist URL to its raw content URL."""
544+
# Already a raw gist URL
545+
if "gist.githubusercontent.com" in url:
546+
return url
547+
# Convert https://gist.github.com/{user}/{id} or .../raw etc.
548+
match = re.match(r"https?://gist\.github\.com/([^/]+)/([a-f0-9]+)(?:/raw)?/?$", url)
549+
if match:
550+
return f"https://gist.githubusercontent.com/{match.group(1)}/{match.group(2)}/raw"
551+
return url
552+
553+
534554
def load_edges_from_file(filepath: str) -> Set[Tuple[str, str]]:
535555
edges: Set[Tuple[str, str]] = set()
536556

537-
with open(filepath, "r", newline="") as f:
538-
edges.update(tuple(row) for row in csv.reader(f) if row and row[0].strip() and not row[0].startswith("#"))
557+
if is_gist_url(filepath):
558+
raw_url = gist_to_raw_url(filepath)
559+
response = urllib.request.urlopen(raw_url)
560+
content = response.read().decode("utf-8")
561+
reader = csv.reader(io.StringIO(content))
562+
edges.update(tuple(row) for row in reader if row and row[0].strip() and not row[0].startswith("#"))
563+
else:
564+
with open(filepath, "r", newline="") as f:
565+
edges.update(tuple(row) for row in csv.reader(f) if row and row[0].strip() and not row[0].startswith("#"))
539566

540567
return edges
541568

542569

543570
def run_interactive(
544571
include_analysis,
545572
target: str,
546-
ignores_file: str,
547-
skips_file: str,
573+
ignores_files: List[str],
574+
skips_files: List[str],
548575
top_n: int,
549576
sort_by: str,
550577
):
551578
"""Run the interactive curses-based TUI for cut_header."""
552579

580+
# The first file in each list is the one that gets written to
581+
ignores_output_file = ignores_files[0]
582+
skips_output_file = skips_files[0]
583+
553584
def prepend_to_file(filepath: str, includer: str, included: str):
554585
"""Prepend a CSV row to the given file."""
555586
new_line = f"{includer},{included}\n"
@@ -747,8 +778,8 @@ def addstr_line(y, x, includer_file, included_file, prevalence, dominated_edges,
747778
action_row += 1
748779

749780
options = [
750-
("i", "Ignore", f"prepend to {ignores_file}"),
751-
("s", "Remove (skip)", f"prepend to {skips_file}"),
781+
("i", "Ignore", f"prepend to {ignores_output_file}"),
782+
("s", "Remove (skip)", f"prepend to {skips_output_file}"),
752783
]
753784
for oi, (key, label, desc) in enumerate(options):
754785
prefix = "> " if action_selected == oi else " "
@@ -792,8 +823,12 @@ def curses_main(stdscr):
792823
stdscr.addstr(0, 0, "Computing... please wait", curses.A_BOLD)
793824
stdscr.refresh()
794825

795-
ignores = load_edges_from_file(ignores_file)
796-
skips = load_edges_from_file(skips_file)
826+
ignores: Set[Tuple[str, str]] = set()
827+
for f in ignores_files:
828+
ignores.update(load_edges_from_file(f))
829+
skips: Set[Tuple[str, str]] = set()
830+
for f in skips_files:
831+
skips.update(load_edges_from_file(f))
797832
data = compute_data(include_analysis, target, ignores, skips, top_n, sort_by)
798833
stdscr.redrawwin()
799834
stdscr.refresh()
@@ -819,22 +854,22 @@ def curses_main(stdscr):
819854
elif key in (curses.KEY_ENTER, 10, 13):
820855
includer, included = selectable_lines[selected_idx]
821856
if action_selected == 0: # Ignore
822-
prepend_to_file(ignores_file, includer, included)
857+
prepend_to_file(ignores_output_file, includer, included)
823858
acted_on[(includer, included)] = "ignored"
824859
else: # Remove (skip)
825-
prepend_to_file(skips_file, includer, included)
860+
prepend_to_file(skips_output_file, includer, included)
826861
acted_on[(includer, included)] = "skipped"
827862
action_mode = False
828863
action_taken = True
829864
elif key == ord("i"):
830865
includer, included = selectable_lines[selected_idx]
831-
prepend_to_file(ignores_file, includer, included)
866+
prepend_to_file(ignores_output_file, includer, included)
832867
acted_on[(includer, included)] = "ignored"
833868
action_mode = False
834869
action_taken = True
835870
elif key == ord("s"):
836871
includer, included = selectable_lines[selected_idx]
837-
prepend_to_file(skips_file, includer, included)
872+
prepend_to_file(skips_output_file, includer, included)
838873
acted_on[(includer, included)] = "skipped"
839874
action_mode = False
840875
action_taken = True
@@ -940,12 +975,20 @@ def main():
940975
print("error: interactive mode requires at least one --skips file")
941976
return 1
942977

978+
if is_gist_url(args.ignores[0]):
979+
print("error: the first --ignores in interactive mode must be a local file, not a gist URL")
980+
return 1
981+
982+
if is_gist_url(args.skips[0]):
983+
print("error: the first --skips in interactive mode must be a local file, not a gist URL")
984+
return 1
985+
943986
# NOTE: the first --ignores and --skips files are the ones updated
944987
run_interactive(
945988
include_analysis=include_analysis,
946989
target=args.target,
947-
ignores_file=args.ignores[0],
948-
skips_file=args.skips[0],
990+
ignores_files=args.ignores,
991+
skips_files=args.skips,
949992
top_n=args.top,
950993
sort_by=args.sort_by,
951994
)

0 commit comments

Comments
 (0)