ethspecify 0.2.5__tar.gz → 0.2.7__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.5
3
+ Version: 0.2.7
4
4
  Summary: A utility for processing Ethereum specification tags.
5
5
  Home-page: https://github.com/jtraglia/ethspecify
6
6
  Author: Justin Traglia
@@ -26,13 +26,14 @@ def process(args):
26
26
  def list_tags(args):
27
27
  """List all available tags with their fork history."""
28
28
  preset = getattr(args, 'preset', 'mainnet')
29
- return _list_tags_with_history(args, preset)
29
+ version = getattr(args, 'version', 'nightly')
30
+ return _list_tags_with_history(args, preset, version)
30
31
 
31
32
 
32
- def _list_tags_with_history(args, preset):
33
+ def _list_tags_with_history(args, preset, version):
33
34
  """List all tags with their fork history."""
34
35
  try:
35
- history = get_spec_item_history(preset)
36
+ history = get_spec_item_history(preset, version)
36
37
  except ValueError as e:
37
38
  print(f"Error: {e}")
38
39
  return 1
@@ -89,30 +90,36 @@ def check(args):
89
90
  total_source_files = {"valid": 0, "total": 0}
90
91
 
91
92
  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
93
  # Collect source file errors
105
94
  source = section_results['source_files']
106
95
  total_source_files["valid"] += source["valid"]
107
96
  total_source_files["total"] += source["total"]
108
97
  all_errors.extend(source["errors"])
109
98
 
110
- # Collect missing items with type prefix
99
+ # Collect missing items
111
100
  coverage = section_results['coverage']
112
101
  total_coverage["found"] += coverage["found"]
113
102
  total_coverage["expected"] += coverage["expected"]
114
- for missing in coverage['missing']:
115
- all_missing.append(f"MISSING: {type_prefix}.{missing}")
103
+
104
+ # For Project Coverage, items already have the proper prefix
105
+ if section_name == "Project Coverage":
106
+ for missing in coverage['missing']:
107
+ all_missing.append(f"MISSING: {missing}")
108
+ else:
109
+ # Determine the type prefix from section name for YAML-based checks
110
+ if "Config Variables" in section_name:
111
+ type_prefix = "config_var"
112
+ elif "Preset Variables" in section_name:
113
+ type_prefix = "preset_var"
114
+ elif "Ssz Objects" in section_name:
115
+ type_prefix = "ssz_object"
116
+ elif "Dataclasses" in section_name:
117
+ type_prefix = "dataclass"
118
+ else:
119
+ type_prefix = section_name.lower().replace(" ", "_")
120
+
121
+ for missing in coverage['missing']:
122
+ all_missing.append(f"MISSING: {type_prefix}.{missing}")
116
123
 
117
124
  # Display only errors and missing items
118
125
  for error in all_errors:
@@ -199,6 +206,12 @@ def main():
199
206
  help="Filter tags by search term",
200
207
  default=None,
201
208
  )
209
+ list_tags_parser.add_argument(
210
+ "--version",
211
+ type=str,
212
+ help="Specification version to use (default: nightly)",
213
+ default="nightly",
214
+ )
202
215
 
203
216
  # Parser for 'check' command
204
217
  check_parser = subparsers.add_parser("check", help="Check spec reference coverage and validity")
@@ -822,6 +822,196 @@ def extract_spec_tags_from_yaml(yaml_file, tag_type=None):
822
822
  return tag_types_found, pairs
823
823
 
824
824
 
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
+
825
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.
@@ -890,12 +1080,48 @@ def run_checks(project_dir, config):
890
1080
  else:
891
1081
  # New format: specrefs: { files: [...], exceptions: {...} }
892
1082
  specrefs_files = specrefs_config.get('files', [])
893
- exceptions = specrefs_config.get('exceptions', {})
894
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
895
1097
  if not specrefs_files:
896
- print("Error: No specrefs files specified in .ethspecify.yml")
897
- print("Please add a 'specrefs:' section with 'files:' listing the files to check")
898
- 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
+
899
1125
 
900
1126
  # Map tag types to exception keys (support both singular and plural)
901
1127
  exception_key_map = {
@@ -928,7 +1154,16 @@ def run_checks(project_dir, config):
928
1154
  # Process each tag type found in the file
929
1155
  if not tag_types_found:
930
1156
  # No spec tags found, still check source files
931
- 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, [])
932
1167
 
933
1168
  # Store results using filename as section name
934
1169
  section_name = filename.replace('.yml', '').replace('-', ' ').title()
@@ -980,7 +1215,16 @@ def run_checks(project_dir, config):
980
1215
  if key in exceptions:
981
1216
  all_exceptions.extend(exceptions[key])
982
1217
 
983
- 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)
984
1228
 
985
1229
  # Store results using filename as section name
986
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.5
3
+ Version: 0.2.7
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.5",
11
+ version="0.2.7",
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