#!/usr/bin/env python3 """ get_si_constant.py Fetches SI constants from https://si-digital-framework.org/constants Saves as constants.ttl and outputs as JSON. Usage: python get_si_constant.py # All constants python get_si_constant.py planck # Constants matching "planck" python get_si_constant.py --help # Show help Part of the ΞSUS project: https://esus.name Repository: https://git.esus.name/esus/spin_paper/ License: CC BY-SA 4.0 """ import os import sys import json import requests import argparse from typing import Dict, Any, Optional def fetch_constants() -> str: """Fetch constants from SI digital framework if not already cached""" ttl_file = "constants.ttl" if not os.path.exists(ttl_file): print("Fetching constants from https://si-digital-framework.org/constants...", file=sys.stderr) try: # Set proper headers as seen in the request headers = { 'User-Agent': 'ΞSUS-project/1.0 (https://esus.name)', 'Accept': 'text/turtle, application/rdf+xml, */*' } response = requests.get( "https://si-digital-framework.org/constants", headers=headers, timeout=30 ) response.raise_for_status() with open(ttl_file, 'w', encoding='utf-8') as f: f.write(response.text) print(f"Saved {len(response.text)} bytes to {ttl_file}", file=sys.stderr) except requests.RequestException as e: print(f"Error fetching constants: {e}", file=sys.stderr) sys.exit(1) else: print(f"Using cached {ttl_file}", file=sys.stderr) return ttl_file def parse_ttl_to_json(ttl_file: str, filter_name: Optional[str] = None) -> Dict[str, Any]: """Parse TTL file and convert to JSON format""" try: # Try to import rdflib, fallback to basic parsing if not available try: from rdflib import Graph return parse_with_rdflib(ttl_file, filter_name) except ImportError: print("rdflib not available, using basic TTL parsing", file=sys.stderr) return parse_ttl_basic(ttl_file, filter_name) except Exception as e: print(f"Error parsing TTL file: {e}", file=sys.stderr) sys.exit(1) def parse_with_rdflib(ttl_file: str, filter_name: Optional[str] = None) -> Dict[str, Any]: """Parse TTL using rdflib library""" from rdflib import Graph g = Graph() g.parse(ttl_file, format="turtle") constants = {} # Group triples by subject subjects = set(g.subjects()) for subject in subjects: # Extract identifier from URI subj_str = str(subject) if '/' in subj_str: const_id = subj_str.split('/')[-1] elif '#' in subj_str: const_id = subj_str.split('#')[-1] else: const_id = subj_str # Skip if it's just a namespace or empty if not const_id or const_id in ['', 'http', 'https']: continue const_data = {'uri': subj_str} # Get all properties for this subject for predicate, obj in g.predicate_objects(subject): pred_str = str(predicate) obj_str = str(obj) # Extract property name if '/' in pred_str: prop_name = pred_str.split('/')[-1] elif '#' in pred_str: prop_name = pred_str.split('#')[-1] else: prop_name = pred_str const_data[prop_name] = obj_str constants[const_id] = const_data return filter_constants(constants, filter_name) def parse_ttl_basic(ttl_file: str, filter_name: Optional[str] = None) -> Dict[str, Any]: """Basic TTL parsing without rdflib (fallback)""" constants = {} with open(ttl_file, 'r', encoding='utf-8') as f: content = f.read() # Very basic TTL parsing - this is a simplified approach lines = content.split('\n') current_subject = None for line in lines: line = line.strip() if not line or line.startswith('#') or line.startswith('@'): continue # Look for subject lines (simplified) if line.startswith('<') and '>' in line: parts = line.split('>') if len(parts) > 0: uri = parts[0][1:] # Remove < at start if '/' in uri: const_id = uri.split('/')[-1] elif '#' in uri: const_id = uri.split('#')[-1] else: const_id = uri if const_id and const_id not in constants: constants[const_id] = {'uri': uri, 'raw_line': line} current_subject = const_id # Store additional properties (very basic) elif current_subject and ':' in line: constants[current_subject]['raw_data'] = constants[current_subject].get('raw_data', []) constants[current_subject]['raw_data'].append(line) return filter_constants(constants, filter_name) def filter_constants(constants: Dict[str, Any], filter_name: Optional[str] = None) -> Dict[str, Any]: """Filter constants by name if specified""" if not filter_name: return constants filter_lower = filter_name.lower() filtered_constants = {} for const_id, const_data in constants.items(): match_found = False # Check constant ID if filter_lower in const_id.lower(): match_found = True else: # Check all property values for prop_value in const_data.values(): if isinstance(prop_value, str) and filter_lower in prop_value.lower(): match_found = True break elif isinstance(prop_value, list): for item in prop_value: if isinstance(item, str) and filter_lower in item.lower(): match_found = True break if match_found: filtered_constants[const_id] = const_data return filtered_constants def print_installation_help(): """Print help for installing dependencies""" print(""" To get better TTL parsing, install rdflib: pip install rdflib Or using conda: conda install rdflib For the basic version, no additional dependencies are needed. """, file=sys.stderr) def main(): parser = argparse.ArgumentParser( description="Fetch SI constants from digital framework and output as JSON", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python get_si_constant.py # All constants python get_si_constant.py planck # Constants matching "planck" python get_si_constant.py electron # Constants matching "electron" python get_si_constant.py hbar # Constants matching "hbar" Part of the ΞSUS project: https://esus.name Repository: https://git.esus.name/esus/spin_paper/ License: CC BY-SA 4.0 """ ) parser.add_argument( 'filter_name', nargs='?', help='Filter constants by name (case-insensitive substring match)' ) parser.add_argument( '--indent', type=int, default=2, help='JSON indentation (default: 2)' ) parser.add_argument( '--install-help', action='store_true', help='Show installation help for dependencies' ) parser.add_argument( '--raw', action='store_true', help='Output raw TTL content instead of parsed JSON' ) args = parser.parse_args() if args.install_help: print_installation_help() return # Fetch constants ttl_file = fetch_constants() if args.raw: # Just output the raw TTL content with open(ttl_file, 'r', encoding='utf-8') as f: print(f.read()) return # Parse and convert to JSON constants = parse_ttl_to_json(ttl_file, args.filter_name) # Output JSON print(json.dumps(constants, indent=args.indent, ensure_ascii=False)) # Print summary to stderr if args.filter_name: print(f"Found {len(constants)} constants matching '{args.filter_name}'", file=sys.stderr) else: print(f"Total constants: {len(constants)}", file=sys.stderr) if __name__ == "__main__": main()