ethspecify 0.1.3__py3-none-any.whl → 0.2.0__py3-none-any.whl

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.

Potentially problematic release.


This version of ethspecify might be problematic. Click here for more details.

ethspecify/cli.py CHANGED
@@ -1,34 +1,205 @@
1
1
  import argparse
2
+ import json
2
3
  import os
4
+ import sys
5
+
6
+ from .core import grep, replace_spec_tags, get_pyspec, get_latest_fork
7
+
8
+
9
+ def process(args):
10
+ """Process all spec tags."""
11
+ project_dir = os.path.abspath(os.path.expanduser(args.path))
12
+ if not os.path.isdir(project_dir):
13
+ print(f"Error: The directory {repr(project_dir)} does not exist.")
14
+ return 1
15
+
16
+ for f in grep(project_dir, r"<spec\b.*?>", args.exclude):
17
+ print(f"Processing file: {f}")
18
+ replace_spec_tags(f)
19
+
20
+ return 0
21
+
22
+
23
+ def list_tags(args):
24
+ """List all available tags for a specific fork and preset."""
25
+ # Get the specification data
26
+ pyspec = get_pyspec()
27
+ fork = args.fork
28
+ preset = args.preset
29
+
30
+ # Validate that the fork exists
31
+ if fork not in pyspec[preset]:
32
+ print(f"Error: Fork '{fork}' not found in {preset} preset")
33
+ available_forks = list(pyspec[preset].keys())
34
+ print(f"Available forks: {', '.join(available_forks)}")
35
+ return 1
36
+
37
+ # Format output based on requested format
38
+ if args.format == "json":
39
+ result = {
40
+ "fork": fork,
41
+ "preset": preset,
42
+ "tags": {
43
+ "functions": list(pyspec[preset][fork]['functions'].keys()),
44
+ "constant_vars": list(pyspec[preset][fork]['constant_vars'].keys()),
45
+ "custom_types": list(pyspec[preset][fork]['custom_types'].keys()),
46
+ "ssz_objects": list(pyspec[preset][fork]['ssz_objects'].keys()),
47
+ "dataclasses": list(pyspec[preset][fork]['dataclasses'].keys()),
48
+ "preset_vars": list(pyspec[preset][fork]['preset_vars'].keys()),
49
+ "config_vars": list(pyspec[preset][fork]['config_vars'].keys()),
50
+ }
51
+ }
52
+ print(json.dumps(result, indent=2))
53
+ else:
54
+ # Plain text output
55
+ print(f"Available tags for {fork} fork ({preset} preset):")
56
+ maybe_fork = f' fork="{fork}"' if fork != get_latest_fork() else ""
57
+
58
+ print("\nFunctions:")
59
+ for fn_name in sorted(pyspec[preset][fork]['functions'].keys()):
60
+ if args.search is None or args.search.lower() in fn_name.lower():
61
+ print(f" <spec fn=\"{fn_name}\"{maybe_fork} />")
62
+
63
+ print("\nConstants:")
64
+ for const_name in sorted(pyspec[preset][fork]['constant_vars'].keys()):
65
+ if args.search is None or args.search.lower() in const_name.lower():
66
+ print(f" <spec constant_var=\"{const_name}\"{maybe_fork} />")
67
+
68
+ print("\nCustom Types:")
69
+ for type_name in sorted(pyspec[preset][fork]['custom_types'].keys()):
70
+ if args.search is None or args.search.lower() in type_name.lower():
71
+ print(f" <spec custom_type=\"{type_name}\"{maybe_fork} />")
72
+
73
+ print("\nSSZ Objects:")
74
+ for obj_name in sorted(pyspec[preset][fork]['ssz_objects'].keys()):
75
+ if args.search is None or args.search.lower() in obj_name.lower():
76
+ print(f" <spec ssz_object=\"{obj_name}\"{maybe_fork} />")
77
+
78
+ print("\nDataclasses:")
79
+ for class_name in sorted(pyspec[preset][fork]['dataclasses'].keys()):
80
+ if args.search is None or args.search.lower() in class_name.lower():
81
+ print(f" <spec dataclass=\"{class_name}\"{maybe_fork} />")
82
+
83
+ print("\nPreset Variables:")
84
+ for var_name in sorted(pyspec[preset][fork]['preset_vars'].keys()):
85
+ if args.search is None or args.search.lower() in var_name.lower():
86
+ print(f" <spec preset_var=\"{var_name}\"{maybe_fork} />")
87
+
88
+ print("\nConfig Variables:")
89
+ for var_name in sorted(pyspec[preset][fork]['config_vars'].keys()):
90
+ if args.search is None or args.search.lower() in var_name.lower():
91
+ print(f" <spec config_var=\"{var_name}\"{maybe_fork} />")
92
+
93
+ return 0
94
+
95
+
96
+ def list_forks(args):
97
+ """List all available forks."""
98
+ pyspec = get_pyspec()
99
+ preset = args.preset
100
+
101
+ if preset not in pyspec:
102
+ print(f"Error: Preset '{preset}' not found.")
103
+ print(f"Available presets: {', '.join(pyspec.keys())}")
104
+ return 1
105
+
106
+ forks = sorted(
107
+ pyspec[preset].keys(),
108
+ # Put phase0 at the top & EIP feature forks at the bottom
109
+ key=lambda x: (x != "phase0", x.startswith("eip"), x)
110
+ )
111
+
112
+ if args.format == "json":
113
+ result = {
114
+ "preset": preset,
115
+ "forks": forks
116
+ }
117
+ print(json.dumps(result, indent=2))
118
+ else:
119
+ print(f"Available forks for {preset} preset:")
120
+ for fork in forks:
121
+ print(f" {fork}")
122
+
123
+ return 0
3
124
 
4
- from .core import grep, replace_spec_tags
5
125
 
6
126
  def main():
7
127
  parser = argparse.ArgumentParser(
8
128
  description="Process files containing <spec> tags."
9
129
  )
10
- parser.add_argument(
130
+
131
+ # Create subparsers for different commands
132
+ subparsers = parser.add_subparsers(dest="command", help="Command to execute")
133
+
134
+ # Parser for 'process' command
135
+ process_parser = subparsers.add_parser("process", help="Process spec tags in files")
136
+ process_parser.set_defaults(func=process)
137
+ process_parser.add_argument(
11
138
  "--path",
12
139
  type=str,
13
140
  help="Directory to search for files containing <spec> tags",
14
141
  default=".",
15
142
  )
16
- parser.add_argument(
143
+ process_parser.add_argument(
17
144
  "--exclude",
18
145
  action="append",
19
146
  help="Exclude paths matching this regex",
20
147
  default=[],
21
148
  )
22
- args = parser.parse_args()
23
149
 
24
- project_dir = os.path.abspath(os.path.expanduser(args.path))
25
- if not os.path.isdir(project_dir):
26
- print(f"Error: The directory '{project_dir}' does not exist.")
27
- exit(1)
150
+ # Parser for 'list-tags' command
151
+ list_tags_parser = subparsers.add_parser("list-tags", help="List available specification tags")
152
+ list_tags_parser.set_defaults(func=list_tags)
153
+ list_tags_parser.add_argument(
154
+ "--fork",
155
+ type=str,
156
+ help="Fork to list tags for",
157
+ default=get_latest_fork(),
158
+ )
159
+ list_tags_parser.add_argument(
160
+ "--preset",
161
+ type=str,
162
+ help="Preset to use (mainnet or minimal)",
163
+ default="mainnet",
164
+ )
165
+ list_tags_parser.add_argument(
166
+ "--format",
167
+ type=str,
168
+ choices=["text", "json"],
169
+ default="text",
170
+ help="Output format (text or json)",
171
+ )
172
+ list_tags_parser.add_argument(
173
+ "--search",
174
+ type=str,
175
+ help="Filter tags by search term",
176
+ default=None,
177
+ )
178
+
179
+ # Parser for 'list-forks' command
180
+ list_forks_parser = subparsers.add_parser("list-forks", help="List available forks")
181
+ list_forks_parser.set_defaults(func=list_forks)
182
+ list_forks_parser.add_argument(
183
+ "--preset",
184
+ type=str,
185
+ help="Preset to use (mainnet or minimal)",
186
+ default="mainnet",
187
+ )
188
+ list_forks_parser.add_argument(
189
+ "--format",
190
+ type=str,
191
+ choices=["text", "json"],
192
+ default="text",
193
+ help="Output format (text or json)",
194
+ )
195
+
196
+ # Default to 'process' if no args are provided
197
+ if len(sys.argv) == 1:
198
+ sys.argv.insert(1, "process")
199
+
200
+ args = parser.parse_args()
201
+ exit(args.func(args))
28
202
 
29
- for f in grep(project_dir, r"<spec\b.*?>", args.exclude):
30
- print(f"Processing file: {f}")
31
- replace_spec_tags(f)
32
203
 
33
204
  if __name__ == "__main__":
34
- main()
205
+ main()
ethspecify/core.py CHANGED
@@ -197,6 +197,16 @@ def get_spec(attributes, preset, fork):
197
197
  raise Exception("invalid spec tag")
198
198
  return spec
199
199
 
200
+ def get_latest_fork():
201
+ """A helper function to get the latest non-eip fork."""
202
+ pyspec = get_pyspec()
203
+ forks = sorted(
204
+ pyspec["mainnet"].keys(),
205
+ key=lambda x: (x != "phase0", x.startswith("eip"), x)
206
+ )
207
+ for fork in reversed(forks):
208
+ if not fork.startswith("eip"):
209
+ return fork
200
210
 
201
211
  def parse_common_attributes(attributes):
202
212
  try:
@@ -207,7 +217,7 @@ def parse_common_attributes(attributes):
207
217
  try:
208
218
  fork = attributes["fork"]
209
219
  except KeyError:
210
- raise Exception(f"Missing fork attribute")
220
+ fork = get_latest_fork()
211
221
 
212
222
  try:
213
223
  style = attributes["style"]
@@ -287,12 +297,28 @@ def replace_spec_tags(file_path):
287
297
  new_opening += f' hash="{hash_value}">'
288
298
  return new_opening
289
299
 
300
+ def rebuild_self_closing_tag(attributes, hash_value):
301
+ # Build a self-closing tag from attributes, forcing a single space before the slash.
302
+ new_tag = "<spec"
303
+ for key, val in attributes.items():
304
+ if key != "hash":
305
+ new_tag += f' {key}="{val}"'
306
+ new_tag += f' hash="{hash_value}" />'
307
+ return new_tag
308
+
290
309
  def replacer(match):
291
310
  # Always use the tag text from whichever group matched:
292
311
  if match.group("self") is not None:
293
312
  original_tag_text = match.group("self")
294
313
  else:
295
314
  original_tag_text = match.group("long")
315
+ # Determine the original opening tag (ignore inner content)
316
+ if match.group("self") is not None:
317
+ original_tag_text = match.group("self")
318
+ else:
319
+ long_tag_text = match.group("long")
320
+ opening_tag_match = re.search(r'<spec\b[^>]*>', long_tag_text)
321
+ original_tag_text = opening_tag_match.group(0) if opening_tag_match else long_tag_text
296
322
 
297
323
  attributes = extract_attributes(original_tag_text)
298
324
  print(f"spec tag: {attributes}")
@@ -301,18 +327,11 @@ def replace_spec_tags(file_path):
301
327
  hash_value = hashlib.sha256(spec.encode('utf-8')).hexdigest()[:8]
302
328
 
303
329
  if style == "hash":
304
- # For hash style, output a self-closing tag.
305
- if 'hash="' in original_tag_text:
306
- updated_tag = re.sub(
307
- r'(hash=")[^"]*(")',
308
- lambda m: f'{m.group(1)}{hash_value}{m.group(2)}',
309
- original_tag_text
310
- )
311
- else:
312
- updated_tag = re.sub(r'\s*/>$', f' hash="{hash_value}" />', original_tag_text)
330
+ # Rebuild a fresh self-closing tag.
331
+ updated_tag = rebuild_self_closing_tag(attributes, hash_value)
313
332
  return updated_tag
314
333
  else:
315
- # For full/diff styles, always rebuild as a long (paired) tag.
334
+ # For full/diff styles, rebuild as a long (paired) tag.
316
335
  new_opening = rebuild_opening_tag(attributes, hash_value)
317
336
  spec_content = get_spec_item(attributes)
318
337
  prefix = content[:match.start()].splitlines()[-1]
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: ethspecify
3
- Version: 0.1.3
3
+ Version: 0.2.0
4
4
  Summary: A utility for processing Ethereum specification tags.
5
5
  Home-page: https://github.com/jtraglia/ethspecify
6
6
  Author: Justin Traglia
@@ -18,6 +18,7 @@ Dynamic: classifier
18
18
  Dynamic: description
19
19
  Dynamic: description-content-type
20
20
  Dynamic: home-page
21
+ Dynamic: license-file
21
22
  Dynamic: requires-dist
22
23
  Dynamic: requires-python
23
24
  Dynamic: summary
@@ -0,0 +1,9 @@
1
+ ethspecify/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ ethspecify/cli.py,sha256=73NnY6-xvFcxMnpeZ1LqvU02EiNiPx5jOor1KPERenk,6903
3
+ ethspecify/core.py,sha256=tkkuBXXJtjWZOSvrpohaFa89VsiocgdAm9QrB55dtGE,12963
4
+ ethspecify-0.2.0.dist-info/licenses/LICENSE,sha256=Awxsr73mm9YMBVhBYnzeI7bNdRd-bH6RDtO5ItG0DaM,1071
5
+ ethspecify-0.2.0.dist-info/METADATA,sha256=wE94OOQKuoZL8hGliq93NX7LQRLsU5eGbtD380J1kzc,8812
6
+ ethspecify-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ ethspecify-0.2.0.dist-info/entry_points.txt,sha256=09viGkCg9J3h0c9BFRN-BKaJUEaIc4JyULNgBP5EL_g,51
8
+ ethspecify-0.2.0.dist-info/top_level.txt,sha256=0klaMvlVyOkXW09fwZTijJpdybITEp2c9zQKV5v30VM,11
9
+ ethspecify-0.2.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,9 +0,0 @@
1
- ethspecify/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- ethspecify/cli.py,sha256=TTKmSGUkuRdYlYS6t-cpVXpdpSKsYCWvmRDy8DcC10E,882
3
- ethspecify/core.py,sha256=pUybFZnO8nDDQ-7tQjxbUjQHFTV4PEGFWJTyR3gy0hM,12194
4
- ethspecify-0.1.3.dist-info/LICENSE,sha256=Awxsr73mm9YMBVhBYnzeI7bNdRd-bH6RDtO5ItG0DaM,1071
5
- ethspecify-0.1.3.dist-info/METADATA,sha256=JI4igGfL54P1EEmtIG-54A1SV5mrhxIbCCOs2WufJmQ,8790
6
- ethspecify-0.1.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
7
- ethspecify-0.1.3.dist-info/entry_points.txt,sha256=09viGkCg9J3h0c9BFRN-BKaJUEaIc4JyULNgBP5EL_g,51
8
- ethspecify-0.1.3.dist-info/top_level.txt,sha256=0klaMvlVyOkXW09fwZTijJpdybITEp2c9zQKV5v30VM,11
9
- ethspecify-0.1.3.dist-info/RECORD,,