10
10
import re
11
11
import os
12
12
import hashlib
13
- from os .path import join , isfile , split
13
+ from os .path import join , isfile , split , basename
14
+ from argparse import ArgumentParser
14
15
15
16
def extract_test_cases (path ):
16
17
with open (path , encoding = "utf8" , errors = 'ignore' , mode = 'r' , newline = '' ) as file :
@@ -35,54 +36,50 @@ def extract_test_cases(path):
35
36
36
37
return tests
37
38
38
- # Contract sources are indented by 4 spaces.
39
- # Look for `pragma solidity`, `contract`, `library` or `interface`
40
- # and abort a line not indented properly .
39
+ # Extract code examples based on a start marker
40
+ # up until we reach EOF or a line that is not empty and doesn't start with 4
41
+ # spaces .
41
42
def extract_docs_cases (path ):
43
+ beginMarkers = ['.. code-block:: solidity' , '::' ]
44
+ immediatelyAfterMarker = False
42
45
insideBlock = False
43
- insideBlockParameters = False
44
- pastBlockParameters = False
45
- extractedLines = []
46
46
tests = []
47
47
48
48
# Collect all snippets of indented blocks
49
-
50
49
with open (path , mode = 'r' , errors = 'ignore' , encoding = 'utf8' , newline = '' ) as f :
51
50
lines = f .read ().splitlines ()
52
- for l in lines :
53
- if l != '' :
54
- if not insideBlock and l .startswith (' ' ):
55
- # start new test
56
- extractedLines += ['' ]
57
- insideBlockParameters = False
58
- pastBlockParameters = False
59
- insideBlock = l .startswith (' ' )
51
+
52
+ for line in lines :
60
53
if insideBlock :
61
- if not pastBlockParameters :
62
- # NOTE: For simplicity this allows blank lines between block parameters even
63
- # though Sphinx does not. This does not matter since the first non-empty line in
64
- # a Solidity file cannot start with a colon anyway.
65
- if not l .strip ().startswith (':' ) and (l != '' or not insideBlockParameters ):
66
- insideBlockParameters = False
67
- pastBlockParameters = True
68
- else :
69
- insideBlockParameters = True
70
-
71
- if not insideBlockParameters :
72
- extractedLines [- 1 ] += l + '\n '
54
+ if immediatelyAfterMarker :
55
+ # Skip Sphinx instructions and empty lines between them
56
+ if line == '' or line .lstrip ().startswith (":" ):
57
+ continue
58
+
59
+ if line == '' or line .startswith (" " ):
60
+ tests [- 1 ] += line + "\n "
61
+ immediatelyAfterMarker = False
62
+ else :
63
+ insideBlock = False
64
+ elif any (map (line .lower ().startswith , beginMarkers )):
65
+ insideBlock = True
66
+ immediatelyAfterMarker = True
67
+ tests += ['' ]
73
68
74
69
codeStart = "(// SPDX-License-Identifier:|pragma solidity|contract.*{|library.*{|interface.*{)"
75
70
76
- # Filter all tests that do not contain Solidity or are indented incorrectly.
77
- for lines in extractedLines :
78
- if re .search (r'^\s{0,3}' + codeStart , lines , re .MULTILINE ):
71
+ for test in tests :
72
+ if re .search (r'^\s{0,3}' + codeStart , test , re .MULTILINE ):
79
73
print ("Indentation error in " + path + ":" )
80
- print (lines )
74
+ print (test )
81
75
exit (1 )
82
- if re .search (r'^\s{4}' + codeStart , lines , re .MULTILINE ):
83
- tests .append (lines )
84
76
85
- return tests
77
+ # Filter out tests that are not supposed to be compilable.
78
+ return [
79
+ test .lstrip ("\n " )
80
+ for test in tests
81
+ if re .search (r'^\s{4}' + codeStart , test , re .MULTILINE ) is not None
82
+ ]
86
83
87
84
def write_cases (f , tests ):
88
85
cleaned_filename = f .replace ("." ,"_" ).replace ("-" ,"_" ).replace (" " ,"_" ).lower ()
@@ -94,39 +91,38 @@ def write_cases(f, tests):
94
91
with open (sol_filename , mode = 'w' , encoding = 'utf8' , newline = '' ) as fi :
95
92
fi .write (remainder )
96
93
97
- def extract_and_write (f , path ):
98
- if docs :
94
+ def extract_and_write (path ):
95
+ if path . lower (). endswith ( '.rst' ) :
99
96
cases = extract_docs_cases (path )
97
+ elif path .endswith ('.sol' ):
98
+ with open (path , mode = 'r' , encoding = 'utf8' , newline = '' ) as f :
99
+ cases = [f .read ()]
100
100
else :
101
- if f .endswith ('.sol' ):
102
- with open (path , mode = 'r' , encoding = 'utf8' , newline = '' ) as _f :
103
- cases = [_f .read ()]
104
- else :
105
- cases = extract_test_cases (path )
106
- write_cases (f , cases )
101
+ cases = extract_test_cases (path )
102
+
103
+ write_cases (basename (path ), cases )
107
104
108
105
if __name__ == '__main__' :
109
- if len (sys .argv ) == 1 :
110
- print ("Usage: " + sys .argv [0 ] + " path-to-file-or-folder-to-extract-code-from [docs]" )
111
- exit (1 )
106
+ script_description = (
107
+ "Reads Solidity, C++ or RST source files and extracts compilable solidity and yul code blocks from them. "
108
+ "Can be used to generate test cases to validade code examples. "
109
+ )
112
110
113
- path = sys . argv [ 1 ]
114
- docs = False
115
- if len ( sys . argv ) > 2 and sys . argv [ 2 ] == 'docs' :
116
- docs = True
111
+ parser = ArgumentParser ( description = script_description )
112
+ parser . add_argument ( dest = 'path' , help = 'Path to file or directory to look for code in.' )
113
+ options = parser . parse_args ()
114
+ path = options . path
117
115
118
116
if isfile (path ):
119
- _ , tail = split (path )
120
- extract_and_write (tail , path )
117
+ extract_and_write (path )
121
118
else :
122
119
for root , subdirs , files in os .walk (path ):
123
120
if '_build' in subdirs :
124
121
subdirs .remove ('_build' )
125
122
if 'compilationTests' in subdirs :
126
123
subdirs .remove ('compilationTests' )
127
124
for f in files :
128
- _ , tail = split (f )
129
- if tail == "invalid_utf8_sequence.sol" :
125
+ if basename (f ) == "invalid_utf8_sequence.sol" :
130
126
continue # ignore the test with broken utf-8 encoding
131
127
path = join (root , f )
132
- extract_and_write (f , path )
128
+ extract_and_write (path )
0 commit comments