ethspecify 0.1.4__py3-none-any.whl → 0.2.1__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 +183 -12
- ethspecify/core.py +33 -18
- {ethspecify-0.1.4.dist-info → ethspecify-0.2.1.dist-info}/METADATA +17 -2
- ethspecify-0.2.1.dist-info/RECORD +9 -0
- {ethspecify-0.1.4.dist-info → ethspecify-0.2.1.dist-info}/WHEEL +1 -1
- ethspecify-0.1.4.dist-info/RECORD +0 -9
- {ethspecify-0.1.4.dist-info → ethspecify-0.2.1.dist-info}/entry_points.txt +0 -0
- {ethspecify-0.1.4.dist-info → ethspecify-0.2.1.dist-info/licenses}/LICENSE +0 -0
- {ethspecify-0.1.4.dist-info → ethspecify-0.2.1.dist-info}/top_level.txt +0 -0
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
@@ -83,23 +83,23 @@ def diff(a_name, a_content, b_name, b_content):
|
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
@functools.lru_cache()
|
|
86
|
-
def get_links():
|
|
87
|
-
url = f"https://raw.githubusercontent.com/jtraglia/ethspecify/main/links.json"
|
|
86
|
+
def get_links(version="nightly"):
|
|
87
|
+
url = f"https://raw.githubusercontent.com/jtraglia/ethspecify/main/pyspec/{version}/links.json"
|
|
88
88
|
response = requests.get(url)
|
|
89
89
|
response.raise_for_status()
|
|
90
90
|
return response.json()
|
|
91
91
|
|
|
92
92
|
|
|
93
93
|
@functools.lru_cache()
|
|
94
|
-
def get_pyspec():
|
|
95
|
-
url = f"https://raw.githubusercontent.com/jtraglia/ethspecify/main/pyspec.json"
|
|
94
|
+
def get_pyspec(version="nightly"):
|
|
95
|
+
url = f"https://raw.githubusercontent.com/jtraglia/ethspecify/main/pyspec/{version}/pyspec.json"
|
|
96
96
|
response = requests.get(url)
|
|
97
97
|
response.raise_for_status()
|
|
98
98
|
return response.json()
|
|
99
99
|
|
|
100
100
|
|
|
101
|
-
def get_previous_forks(fork):
|
|
102
|
-
pyspec = get_pyspec()
|
|
101
|
+
def get_previous_forks(fork, version="nightly"):
|
|
102
|
+
pyspec = get_pyspec(version)
|
|
103
103
|
config_vars = pyspec["mainnet"][fork]["config_vars"]
|
|
104
104
|
previous_forks = ["phase0"]
|
|
105
105
|
for key in config_vars.keys():
|
|
@@ -111,8 +111,8 @@ def get_previous_forks(fork):
|
|
|
111
111
|
return list(reversed(previous_forks))
|
|
112
112
|
|
|
113
113
|
|
|
114
|
-
def get_spec(attributes, preset, fork):
|
|
115
|
-
pyspec = get_pyspec()
|
|
114
|
+
def get_spec(attributes, preset, fork, version="nightly"):
|
|
115
|
+
pyspec = get_pyspec(version)
|
|
116
116
|
spec = None
|
|
117
117
|
if "function" in attributes or "fn" in attributes:
|
|
118
118
|
if "function" in attributes and "fn" in attributes:
|
|
@@ -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(version="nightly"):
|
|
201
|
+
"""A helper function to get the latest non-eip fork."""
|
|
202
|
+
pyspec = get_pyspec(version)
|
|
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:
|
|
@@ -204,36 +214,41 @@ def parse_common_attributes(attributes):
|
|
|
204
214
|
except KeyError:
|
|
205
215
|
preset = "mainnet"
|
|
206
216
|
|
|
217
|
+
try:
|
|
218
|
+
version = attributes["version"]
|
|
219
|
+
except KeyError:
|
|
220
|
+
version = "nightly"
|
|
221
|
+
|
|
207
222
|
try:
|
|
208
223
|
fork = attributes["fork"]
|
|
209
224
|
except KeyError:
|
|
210
|
-
|
|
225
|
+
fork = get_latest_fork(version)
|
|
211
226
|
|
|
212
227
|
try:
|
|
213
228
|
style = attributes["style"]
|
|
214
229
|
except KeyError:
|
|
215
230
|
style = "hash"
|
|
216
231
|
|
|
217
|
-
return preset, fork, style
|
|
232
|
+
return preset, fork, style, version
|
|
218
233
|
|
|
219
234
|
def get_spec_item(attributes):
|
|
220
|
-
preset, fork, style = parse_common_attributes(attributes)
|
|
221
|
-
spec = get_spec(attributes, preset, fork)
|
|
235
|
+
preset, fork, style, version = parse_common_attributes(attributes)
|
|
236
|
+
spec = get_spec(attributes, preset, fork, version)
|
|
222
237
|
|
|
223
238
|
if style == "full" or style == "hash":
|
|
224
239
|
return spec
|
|
225
240
|
elif style == "diff":
|
|
226
|
-
previous_forks = get_previous_forks(fork)
|
|
241
|
+
previous_forks = get_previous_forks(fork, version)
|
|
227
242
|
|
|
228
243
|
previous_fork = None
|
|
229
244
|
previous_spec = None
|
|
230
245
|
for i, _ in enumerate(previous_forks):
|
|
231
246
|
previous_fork = previous_forks[i]
|
|
232
|
-
previous_spec = get_spec(attributes, preset, previous_fork)
|
|
247
|
+
previous_spec = get_spec(attributes, preset, previous_fork, version)
|
|
233
248
|
if previous_spec != "phase0":
|
|
234
249
|
try:
|
|
235
250
|
previous_previous_fork = previous_forks[i+1]
|
|
236
|
-
previous_previous_spec = get_spec(attributes, preset, previous_previous_fork)
|
|
251
|
+
previous_previous_spec = get_spec(attributes, preset, previous_previous_fork, version)
|
|
237
252
|
if previous_previous_spec == previous_spec:
|
|
238
253
|
continue
|
|
239
254
|
except KeyError:
|
|
@@ -253,7 +268,7 @@ def get_spec_item(attributes):
|
|
|
253
268
|
function_name = attributes["function"]
|
|
254
269
|
else:
|
|
255
270
|
function_name = attributes["fn"]
|
|
256
|
-
for key, value in get_links().items():
|
|
271
|
+
for key, value in get_links(version).items():
|
|
257
272
|
if fork in key and key.endswith(function_name):
|
|
258
273
|
return value
|
|
259
274
|
return "Could not find link"
|
|
@@ -312,8 +327,8 @@ def replace_spec_tags(file_path):
|
|
|
312
327
|
|
|
313
328
|
attributes = extract_attributes(original_tag_text)
|
|
314
329
|
print(f"spec tag: {attributes}")
|
|
315
|
-
preset, fork, style = parse_common_attributes(attributes)
|
|
316
|
-
spec = get_spec(attributes, preset, fork)
|
|
330
|
+
preset, fork, style, version = parse_common_attributes(attributes)
|
|
331
|
+
spec = get_spec(attributes, preset, fork, version)
|
|
317
332
|
hash_value = hashlib.sha256(spec.encode('utf-8')).hexdigest()[:8]
|
|
318
333
|
|
|
319
334
|
if style == "hash":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: ethspecify
|
|
3
|
-
Version: 0.1
|
|
3
|
+
Version: 0.2.1
|
|
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
|
|
@@ -65,6 +66,20 @@ ethspecify
|
|
|
65
66
|
|
|
66
67
|
## Specification Options
|
|
67
68
|
|
|
69
|
+
### Version
|
|
70
|
+
|
|
71
|
+
This attribute specifies which version of the consensus specifications to use. Default is `nightly`.
|
|
72
|
+
|
|
73
|
+
- `nightly` (default) - Uses the latest nightly build from the master branch
|
|
74
|
+
- `v1.6.0-alpha.2`, `v1.6.0-alpha.3`, etc. - Uses a specific tagged release version
|
|
75
|
+
|
|
76
|
+
Example:
|
|
77
|
+
```
|
|
78
|
+
/*
|
|
79
|
+
* <spec fn="apply_deposit" fork="electra" version="v1.6.0-alpha.3" />
|
|
80
|
+
*/
|
|
81
|
+
```
|
|
82
|
+
|
|
68
83
|
### Fork
|
|
69
84
|
|
|
70
85
|
This attribute can be any of the [executable
|
|
@@ -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=Nu5-onBdtmibdO9FkwZL1gGWjRAvZkTPeWZ0H9v2NoI,13292
|
|
4
|
+
ethspecify-0.2.1.dist-info/licenses/LICENSE,sha256=Awxsr73mm9YMBVhBYnzeI7bNdRd-bH6RDtO5ItG0DaM,1071
|
|
5
|
+
ethspecify-0.2.1.dist-info/METADATA,sha256=qYoT3-9O7b2SbY7mDJrDJo7T--VpjkIJTPrD2Ob03rc,9185
|
|
6
|
+
ethspecify-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
7
|
+
ethspecify-0.2.1.dist-info/entry_points.txt,sha256=09viGkCg9J3h0c9BFRN-BKaJUEaIc4JyULNgBP5EL_g,51
|
|
8
|
+
ethspecify-0.2.1.dist-info/top_level.txt,sha256=0klaMvlVyOkXW09fwZTijJpdybITEp2c9zQKV5v30VM,11
|
|
9
|
+
ethspecify-0.2.1.dist-info/RECORD,,
|
|
@@ -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=dJnhQpAhNpp_cY_fMj8k_cthXOVW_nagTl2oBZsIDc8,12654
|
|
4
|
-
ethspecify-0.1.4.dist-info/LICENSE,sha256=Awxsr73mm9YMBVhBYnzeI7bNdRd-bH6RDtO5ItG0DaM,1071
|
|
5
|
-
ethspecify-0.1.4.dist-info/METADATA,sha256=bALmnZ4tsQQ4ftX5JlwP06-i-O2jLcXowalbdEHa-2w,8790
|
|
6
|
-
ethspecify-0.1.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
7
|
-
ethspecify-0.1.4.dist-info/entry_points.txt,sha256=09viGkCg9J3h0c9BFRN-BKaJUEaIc4JyULNgBP5EL_g,51
|
|
8
|
-
ethspecify-0.1.4.dist-info/top_level.txt,sha256=0klaMvlVyOkXW09fwZTijJpdybITEp2c9zQKV5v30VM,11
|
|
9
|
-
ethspecify-0.1.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|