Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| scripts/normalize_sources.py | |
| Normalize or remove local filesystem paths in the cleaned dataset artifacts. | |
| - Reads files from a `final_cleaned_data` directory (or other path you pass). | |
| - Produces normalized outputs with source paths replaced by a compact identifier. | |
| - Writes a `source_map.json` mapping original -> normalized for traceability. | |
| Usage examples (from project root): | |
| # Dry-run: show 10 mappings | |
| python scripts/normalize_sources.py --input-dir final_cleaned_data --dry-run --show 10 | |
| # Apply changes and write normalized files | |
| python scripts/normalize_sources.py --input-dir final_cleaned_data --apply | |
| # Use a custom output dir | |
| python scripts/normalize_sources.py --input-dir final_cleaned_data --apply --out-dir final_cleaned_data/normalized | |
| Behavior / heuristics | |
| - If a source contains an .onion hostname, normalized id will be the onion hostname (e.g. 222222222hsoeiok.onion). | |
| - Else, we use the basename of `source_path` (filename). | |
| - For concatenated records with `::original_filename`, the part after `::` is used when present. | |
| - The script preserves the original files by writing new outputs (normalized_*). | |
| """ | |
| from pathlib import Path | |
| import argparse | |
| import json | |
| import csv | |
| import re | |
| from urllib.parse import urlparse | |
| ONION_RE = re.compile(r"([a-z2-7]{16,}\.onion)", flags=re.I) | |
| def extract_identifier(src: str) -> str: | |
| if not src: | |
| return "unknown" | |
| # handle concatenated style: path::inner | |
| if "::" in src: | |
| src = src.split("::", 1)[1] | |
| # try to find onion host | |
| m = ONION_RE.search(src) | |
| if m: | |
| return m.group(1).lower() | |
| # try to parse as URL | |
| if src.startswith("http://") or src.startswith("https://"): | |
| try: | |
| p = urlparse(src) | |
| host = p.netloc | |
| if host: | |
| return host | |
| except Exception: | |
| pass | |
| # fallback: use basename | |
| try: | |
| return Path(src).name | |
| except Exception: | |
| return src.replace('\\', '/').split('/')[-1] | |
| def normalize_jsonl(in_path: Path, out_path: Path, source_map: dict): | |
| out_path.parent.mkdir(parents=True, exist_ok=True) | |
| written = 0 | |
| with in_path.open('r', encoding='utf-8') as inf, out_path.open('w', encoding='utf-8') as outf: | |
| for line in inf: | |
| line = line.strip() | |
| if not line: | |
| continue | |
| try: | |
| obj = json.loads(line) | |
| except Exception: | |
| continue | |
| orig = obj.get('source_path') or obj.get('source') or '' | |
| nid = extract_identifier(orig) | |
| source_map.setdefault(orig, nid) | |
| # update fields | |
| if 'source_path' in obj: | |
| obj['source_path'] = nid | |
| if 'source' in obj: | |
| obj['source'] = nid | |
| # also update site_folder if it looks like a path | |
| if 'site_folder' in obj: | |
| sf = obj.get('site_folder') | |
| if sf and ('\\' in sf or '/' in sf or ':' in sf): | |
| obj['site_folder'] = nid | |
| outf.write(json.dumps(obj, ensure_ascii=False) + '\n') | |
| written += 1 | |
| return written | |
| def normalize_csv(in_path: Path, out_path: Path, source_map: dict): | |
| out_path.parent.mkdir(parents=True, exist_ok=True) | |
| written = 0 | |
| with in_path.open('r', encoding='utf-8', newline='') as inf: | |
| reader = csv.DictReader(inf) | |
| fieldnames = reader.fieldnames | |
| rows = list(reader) | |
| # update rows | |
| for r in rows: | |
| orig = r.get('source_path') or '' | |
| nid = extract_identifier(orig) | |
| source_map.setdefault(orig, nid) | |
| r['source_path'] = nid | |
| # if there is site_folder field, normalize it too | |
| if 'site_folder' in r and (not r['site_folder'] or ('\\' in r['site_folder'] or '/' in r['site_folder'] or ':' in r['site_folder'])): | |
| r['site_folder'] = nid | |
| # write out | |
| with out_path.open('w', encoding='utf-8', newline='') as outf: | |
| writer = csv.DictWriter(outf, fieldnames=fieldnames) | |
| writer.writeheader() | |
| for r in rows: | |
| writer.writerow(r) | |
| written += 1 | |
| return written | |
| def main(): | |
| p = argparse.ArgumentParser() | |
| p.add_argument('--input-dir', type=Path, default=Path('final_cleaned_data')) | |
| p.add_argument('--out-dir', type=Path, default=None) | |
| p.add_argument('--apply', action='store_true') | |
| p.add_argument('--show', type=int, default=0, help='Number of mappings to print in dry-run') | |
| p.add_argument('--dry-run', action='store_true') | |
| args = p.parse_args() | |
| inp = args.input_dir | |
| if not inp.exists(): | |
| print('Input dir not found:', inp) | |
| return | |
| out_root = Path(args.out_dir) if args.out_dir else inp / 'normalized' | |
| out_root.mkdir(parents=True, exist_ok=True) | |
| source_map = {} | |
| # files to normalize (if present) | |
| jsonl_in = inp / 'cleaned_extracted_text.jsonl' | |
| csv_in = inp / 'cleaned_labeled_dataset.csv' | |
| instr_in = inp / 'cleaned_instruction_tuning.jsonl' | |
| if jsonl_in.exists(): | |
| written = normalize_jsonl(jsonl_in, out_root / 'normalized_extracted_text.jsonl', source_map) | |
| print('Processed JSONL:', written) | |
| else: | |
| print('No cleaned_extracted_text.jsonl at', jsonl_in) | |
| if csv_in.exists(): | |
| written = normalize_csv(csv_in, out_root / 'normalized_labeled_dataset.csv', source_map) | |
| print('Processed CSV:', written) | |
| else: | |
| print('No cleaned_labeled_dataset.csv at', csv_in) | |
| if instr_in.exists(): | |
| written = normalize_jsonl(instr_in, out_root / 'normalized_instruction_tuning.jsonl', source_map) | |
| print('Processed instruction JSONL:', written) | |
| else: | |
| print('No cleaned_instruction_tuning.jsonl at', instr_in) | |
| # write mapping | |
| map_path = out_root / 'source_map.json' | |
| with map_path.open('w', encoding='utf-8') as f: | |
| json.dump(source_map, f, indent=2, ensure_ascii=False) | |
| if args.dry_run or not args.apply: | |
| print('\nDRY RUN - no files overwritten in originals. To write outputs add --apply') | |
| # print sample mappings | |
| cnt = 0 | |
| for orig, nid in list(source_map.items())[:args.show or 20]: | |
| print(f'"{orig}" -> "{nid}"') | |
| cnt += 1 | |
| print(f'Printed {cnt} mappings. Mapping written to', map_path) | |
| return | |
| # If apply, we've already written normalized files to out_root | |
| print('Wrote normalized outputs to', out_root) | |
| print('Source map written to', map_path) | |
| if __name__ == '__main__': | |
| main() | |