ethspecify 0.2.4__tar.gz → 0.2.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ethspecify
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: A utility for processing Ethereum specification tags.
5
5
  Home-page: https://github.com/jtraglia/ethspecify
6
6
  Author: Justin Traglia
@@ -89,30 +89,36 @@ def check(args):
89
89
  total_source_files = {"valid": 0, "total": 0}
90
90
 
91
91
  for section_name, section_results in results.items():
92
- # Determine the type prefix from section name
93
- if "Config Variables" in section_name:
94
- type_prefix = "config_var"
95
- elif "Preset Variables" in section_name:
96
- type_prefix = "preset_var"
97
- elif "Ssz Objects" in section_name:
98
- type_prefix = "ssz_object"
99
- elif "Dataclasses" in section_name:
100
- type_prefix = "dataclass"
101
- else:
102
- type_prefix = section_name.lower().replace(" ", "_")
103
-
104
92
  # Collect source file errors
105
93
  source = section_results['source_files']
106
94
  total_source_files["valid"] += source["valid"]
107
95
  total_source_files["total"] += source["total"]
108
96
  all_errors.extend(source["errors"])
109
97
 
110
- # Collect missing items with type prefix
98
+ # Collect missing items
111
99
  coverage = section_results['coverage']
112
100
  total_coverage["found"] += coverage["found"]
113
101
  total_coverage["expected"] += coverage["expected"]
114
- for missing in coverage['missing']:
115
- all_missing.append(f"MISSING: {type_prefix}.{missing}")
102
+
103
+ # For Project Coverage, items already have the proper prefix
104
+ if section_name == "Project Coverage":
105
+ for missing in coverage['missing']:
106
+ all_missing.append(f"MISSING: {missing}")
107
+ else:
108
+ # Determine the type prefix from section name for YAML-based checks
109
+ if "Config Variables" in section_name:
110
+ type_prefix = "config_var"
111
+ elif "Preset Variables" in section_name:
112
+ type_prefix = "preset_var"
113
+ elif "Ssz Objects" in section_name:
114
+ type_prefix = "ssz_object"
115
+ elif "Dataclasses" in section_name:
116
+ type_prefix = "dataclass"
117
+ else:
118
+ type_prefix = section_name.lower().replace(" ", "_")
119
+
120
+ for missing in coverage['missing']:
121
+ all_missing.append(f"MISSING: {type_prefix}.{missing}")
116
122
 
117
123
  # Display only errors and missing items
118
124
  for error in all_errors:
@@ -822,7 +822,197 @@ def extract_spec_tags_from_yaml(yaml_file, tag_type=None):
822
822
  return tag_types_found, pairs
823
823
 
824
824
 
825
- def check_coverage(yaml_file, tag_type, exceptions, preset="mainnet"):
825
+ def generate_specrefs_from_files(files_with_spec_tags, project_dir):
826
+ """
827
+ Generate specrefs data from files containing spec tags.
828
+ Returns a dict with spec tag info and their source locations.
829
+ """
830
+ specrefs = {}
831
+
832
+ for file_path in files_with_spec_tags:
833
+ try:
834
+ with open(file_path, 'r', encoding='utf-8') as f:
835
+ content = f.read()
836
+
837
+ # Find all spec tags in the file
838
+ spec_tag_pattern = r'<spec\s+([^>]+?)(?:\s*/>|>)'
839
+ matches = re.finditer(spec_tag_pattern, content)
840
+
841
+ for match in matches:
842
+ tag_attrs_str = match.group(1)
843
+ attrs = extract_attributes(f"<spec {tag_attrs_str}>")
844
+
845
+ # Determine the spec type and name
846
+ spec_type = None
847
+ spec_name = None
848
+ fork = attrs.get('fork', None)
849
+
850
+ # Check each possible spec attribute
851
+ for attr_name in ['fn', 'function', 'constant_var', 'config_var',
852
+ 'preset_var', 'ssz_object', 'dataclass', 'custom_type']:
853
+ if attr_name in attrs:
854
+ spec_type = attr_name
855
+ spec_name = attrs[attr_name]
856
+ break
857
+
858
+ if spec_type and spec_name:
859
+ # Create a unique key for this spec reference
860
+ key = f"{spec_type}.{spec_name}"
861
+ if fork:
862
+ key += f"#{fork}"
863
+
864
+ if key not in specrefs:
865
+ specrefs[key] = {
866
+ 'name': spec_name,
867
+ 'type': spec_type,
868
+ 'fork': fork,
869
+ 'sources': []
870
+ }
871
+
872
+ # Add this source location
873
+ rel_path = os.path.relpath(file_path, project_dir)
874
+
875
+ # Get line number of the match
876
+ lines_before = content[:match.start()].count('\n')
877
+ line_num = lines_before + 1
878
+
879
+ specrefs[key]['sources'].append({
880
+ 'file': rel_path,
881
+ 'line': line_num
882
+ })
883
+
884
+ except (IOError, UnicodeDecodeError):
885
+ continue
886
+
887
+ return specrefs
888
+
889
+
890
+ def process_generated_specrefs(specrefs, exceptions, version):
891
+ """
892
+ Process the generated specrefs and check coverage.
893
+ Returns (success, results)
894
+ """
895
+ results = {}
896
+ overall_success = True
897
+
898
+ # Group specrefs by type for coverage checking
899
+ specrefs_by_type = {}
900
+ for _, data in specrefs.items():
901
+ spec_type = data['type']
902
+ if spec_type not in specrefs_by_type:
903
+ specrefs_by_type[spec_type] = []
904
+ specrefs_by_type[spec_type].append(data)
905
+
906
+ # Map spec types to history keys
907
+ type_to_history_key = {
908
+ 'fn': 'functions',
909
+ 'function': 'functions',
910
+ 'constant_var': 'constant_vars',
911
+ 'config_var': 'config_vars',
912
+ 'preset_var': 'preset_vars',
913
+ 'ssz_object': 'ssz_objects',
914
+ 'dataclass': 'dataclasses',
915
+ 'custom_type': 'custom_types'
916
+ }
917
+
918
+ # Map to exception keys
919
+ type_to_exception_key = {
920
+ 'fn': 'functions',
921
+ 'function': 'functions',
922
+ 'constant_var': 'constants',
923
+ 'config_var': 'configs',
924
+ 'preset_var': 'presets',
925
+ 'ssz_object': 'ssz_objects',
926
+ 'dataclass': 'dataclasses',
927
+ 'custom_type': 'custom_types'
928
+ }
929
+
930
+ # Get spec history for coverage checking
931
+ history = get_spec_item_history("mainnet", version)
932
+
933
+ # Check coverage for each type
934
+ total_found = 0
935
+ total_expected = 0
936
+ all_missing = []
937
+
938
+ for spec_type, items in specrefs_by_type.items():
939
+ history_key = type_to_history_key.get(spec_type, spec_type)
940
+ exception_key = type_to_exception_key.get(spec_type, spec_type)
941
+
942
+ # Get exceptions for this type - handle both singular and plural keys
943
+ type_exceptions = []
944
+ if exception_key in exceptions:
945
+ type_exceptions = exceptions[exception_key]
946
+ # Also check plural forms
947
+ elif exception_key + 's' in exceptions:
948
+ type_exceptions = exceptions[exception_key + 's']
949
+ # Check if singular form exists when we have plural
950
+ elif exception_key.endswith('s') and exception_key[:-1] in exceptions:
951
+ type_exceptions = exceptions[exception_key[:-1]]
952
+
953
+ # Build set of what we found
954
+ found_items = set()
955
+ for item in items:
956
+ if item['fork']:
957
+ found_items.add(f"{item['name']}#{item['fork']}")
958
+ else:
959
+ # If no fork specified, we need to check all forks
960
+ if history_key in history and item['name'] in history[history_key]:
961
+ for fork in history[history_key][item['name']]:
962
+ found_items.add(f"{item['name']}#{fork}")
963
+
964
+ # Check what's expected
965
+ if history_key in history:
966
+ for item_name, forks in history[history_key].items():
967
+ for fork in forks:
968
+ expected_key = f"{item_name}#{fork}"
969
+ total_expected += 1
970
+
971
+ # Check if excepted
972
+ if is_excepted(item_name, fork, type_exceptions):
973
+ total_found += 1
974
+ continue
975
+
976
+ if expected_key in found_items:
977
+ total_found += 1
978
+ else:
979
+ # Use the proper type prefix for the missing item
980
+ type_prefix_map = {
981
+ 'functions': 'functions',
982
+ 'constant_vars': 'constants',
983
+ 'config_vars': 'configs',
984
+ 'preset_vars': 'presets',
985
+ 'ssz_objects': 'ssz_objects',
986
+ 'dataclasses': 'dataclasses',
987
+ 'custom_types': 'custom_types'
988
+ }
989
+ prefix = type_prefix_map.get(history_key, history_key)
990
+ all_missing.append(f"{prefix}.{expected_key}")
991
+
992
+ # Count total spec references found
993
+ total_refs = len(specrefs)
994
+
995
+ # Store results
996
+ results['Project Coverage'] = {
997
+ 'source_files': {
998
+ 'valid': total_refs,
999
+ 'total': total_refs,
1000
+ 'errors': []
1001
+ },
1002
+ 'coverage': {
1003
+ 'found': total_found,
1004
+ 'expected': total_expected,
1005
+ 'missing': all_missing
1006
+ }
1007
+ }
1008
+
1009
+ if all_missing:
1010
+ overall_success = False
1011
+
1012
+ return overall_success, results
1013
+
1014
+
1015
+ def check_coverage(yaml_file, tag_type, exceptions, preset="mainnet", version="nightly"):
826
1016
  """
827
1017
  Check that all spec items from ethspecify have corresponding tags in the YAML file.
828
1018
  Returns (found_count, total_count, missing_items)
@@ -839,7 +1029,7 @@ def check_coverage(yaml_file, tag_type, exceptions, preset="mainnet"):
839
1029
  }
840
1030
 
841
1031
  # Get expected items from ethspecify
842
- history = get_spec_item_history(preset)
1032
+ history = get_spec_item_history(preset, version)
843
1033
  expected_pairs = set()
844
1034
 
845
1035
  history_key = history_key_map.get(tag_type, tag_type)
@@ -876,6 +1066,9 @@ def run_checks(project_dir, config):
876
1066
  results = {}
877
1067
  overall_success = True
878
1068
 
1069
+ # Get version from config
1070
+ version = config.get('version', 'nightly')
1071
+
879
1072
  # Get specrefs config
880
1073
  specrefs_config = config.get('specrefs', {})
881
1074
 
@@ -887,12 +1080,48 @@ def run_checks(project_dir, config):
887
1080
  else:
888
1081
  # New format: specrefs: { files: [...], exceptions: {...} }
889
1082
  specrefs_files = specrefs_config.get('files', [])
890
- exceptions = specrefs_config.get('exceptions', {})
891
1083
 
1084
+ # Support exceptions in either specrefs section or root, but not both
1085
+ specrefs_exceptions = specrefs_config.get('exceptions', {})
1086
+ root_exceptions = config.get('exceptions', {})
1087
+
1088
+ if specrefs_exceptions and root_exceptions:
1089
+ print("Warning: Exceptions found in both root and specrefs sections. Using specrefs exceptions.")
1090
+ exceptions = specrefs_exceptions
1091
+ elif specrefs_exceptions:
1092
+ exceptions = specrefs_exceptions
1093
+ else:
1094
+ exceptions = root_exceptions
1095
+
1096
+ # If no files specified, search the whole project for spec tags
892
1097
  if not specrefs_files:
893
- print("Error: No specrefs files specified in .ethspecify.yml")
894
- print("Please add a 'specrefs:' section with 'files:' listing the files to check")
895
- return False, {}
1098
+ print("No specific files configured, searching entire project for spec tags...")
1099
+
1100
+ # Determine search root - configurable in specrefs section
1101
+ if 'search_root' in specrefs_config:
1102
+ # Use configured search_root (relative to project_dir)
1103
+ search_root_rel = specrefs_config['search_root']
1104
+ search_root = os.path.join(project_dir, search_root_rel) if not os.path.isabs(search_root_rel) else search_root_rel
1105
+ search_root = os.path.abspath(search_root)
1106
+ else:
1107
+ # Default behavior: if we're in a specrefs directory, search in the parent directory
1108
+ search_root = os.path.dirname(project_dir) if os.path.basename(project_dir) == 'specrefs' else project_dir
1109
+
1110
+ print(f"Searching for spec tags in: {search_root}")
1111
+
1112
+ # Use grep to find all files containing spec tags
1113
+ files_with_spec_tags = grep(search_root, r'<spec\b[^>]*>', [])
1114
+
1115
+ if not files_with_spec_tags:
1116
+ print(f"No files with spec tags found in the project")
1117
+ return True, {}
1118
+
1119
+ # Generate in-memory specrefs data from the found spec tags
1120
+ all_specrefs = generate_specrefs_from_files(files_with_spec_tags, search_root)
1121
+
1122
+ # Process the generated specrefs
1123
+ return process_generated_specrefs(all_specrefs, exceptions, version)
1124
+
896
1125
 
897
1126
  # Map tag types to exception keys (support both singular and plural)
898
1127
  exception_key_map = {
@@ -925,7 +1154,16 @@ def run_checks(project_dir, config):
925
1154
  # Process each tag type found in the file
926
1155
  if not tag_types_found:
927
1156
  # No spec tags found, still check source files
928
- valid_count, total_count, source_errors = check_source_files(yaml_path, os.path.dirname(project_dir), [])
1157
+ # Determine source root - use search_root if configured, otherwise use default behavior
1158
+ if 'search_root' in specrefs_config:
1159
+ search_root_rel = specrefs_config['search_root']
1160
+ source_root = os.path.join(project_dir, search_root_rel) if not os.path.isabs(search_root_rel) else search_root_rel
1161
+ source_root = os.path.abspath(source_root)
1162
+ else:
1163
+ # Default behavior: parent directory
1164
+ source_root = os.path.dirname(project_dir)
1165
+
1166
+ valid_count, total_count, source_errors = check_source_files(yaml_path, source_root, [])
929
1167
 
930
1168
  # Store results using filename as section name
931
1169
  section_name = filename.replace('.yml', '').replace('-', ' ').title()
@@ -963,7 +1201,7 @@ def run_checks(project_dir, config):
963
1201
  break
964
1202
 
965
1203
  # Check coverage for this specific tag type
966
- found_count, expected_count, missing_items = check_coverage(yaml_path, tag_type, section_exceptions, preset)
1204
+ found_count, expected_count, missing_items = check_coverage(yaml_path, tag_type, section_exceptions, preset, version)
967
1205
  total_found += found_count
968
1206
  total_expected += expected_count
969
1207
  all_missing_items.extend(missing_items)
@@ -977,7 +1215,16 @@ def run_checks(project_dir, config):
977
1215
  if key in exceptions:
978
1216
  all_exceptions.extend(exceptions[key])
979
1217
 
980
- valid_count, total_count, source_errors = check_source_files(yaml_path, os.path.dirname(project_dir), all_exceptions)
1218
+ # Determine source root - use search_root if configured, otherwise use default behavior
1219
+ if 'search_root' in specrefs_config:
1220
+ search_root_rel = specrefs_config['search_root']
1221
+ source_root = os.path.join(project_dir, search_root_rel) if not os.path.isabs(search_root_rel) else search_root_rel
1222
+ source_root = os.path.abspath(source_root)
1223
+ else:
1224
+ # Default behavior: parent directory
1225
+ source_root = os.path.dirname(project_dir)
1226
+
1227
+ valid_count, total_count, source_errors = check_source_files(yaml_path, source_root, all_exceptions)
981
1228
 
982
1229
  # Store results using filename as section name
983
1230
  section_name = filename.replace('.yml', '').replace('-', ' ').title()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ethspecify
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: A utility for processing Ethereum specification tags.
5
5
  Home-page: https://github.com/jtraglia/ethspecify
6
6
  Author: Justin Traglia
@@ -8,7 +8,7 @@ long_description = (this_directory / "README.md").read_text(encoding="utf-8")
8
8
 
9
9
  setup(
10
10
  name="ethspecify",
11
- version="0.2.4",
11
+ version="0.2.6",
12
12
  description="A utility for processing Ethereum specification tags.",
13
13
  long_description=long_description,
14
14
  long_description_content_type="text/markdown",
File without changes
File without changes
File without changes