7
7
import traceback
8
8
import json
9
9
from ruamel import yaml
10
- from pathlib import PosixPath
10
+ from pathlib import Path , PosixPath
11
11
from urllib .request import urlretrieve
12
12
from urllib .error import HTTPError , URLError
13
13
from distutils .dir_util import remove_tree
16
16
from pkg_resources import resource_filename
17
17
from subprocess import run , PIPE
18
18
19
- from foliant .preprocessors .base import BasePreprocessor
19
+ from foliant .preprocessors .utils .preprocessor_ext import (BasePreprocessorExt ,
20
+ allow_fail )
21
+ from foliant .preprocessors .utils .combined_options import (Options ,
22
+ CombinedOptions ,
23
+ validate_exists ,
24
+ validate_in ,
25
+ rel_path_convertor )
20
26
from foliant .utils import output
21
27
22
28
23
- class Preprocessor (BasePreprocessor ):
29
+ class Preprocessor (BasePreprocessorExt ):
24
30
tags = ('swaggerdoc' ,)
25
31
26
32
defaults = {
27
- 'json_url' : '' ,
33
+ 'json_url' : [], # deprecated
34
+ 'spec_url' : [],
28
35
'additional_json_path' : '' ,
29
- 'json_path' : '' ,
36
+ 'json_path' : '' , # deprecated
37
+ 'spec_path' : '' ,
30
38
'mode' : 'widdershins' ,
31
39
'template' : 'swagger.j2'
32
40
}
@@ -52,79 +60,75 @@ def __init__(self, *args, **kwargs):
52
60
os .makedirs (self ._swagger_tmp )
53
61
54
62
self ._counter = 0
63
+ self .options = Options (self .options ,
64
+ validators = {'json_path' : validate_exists ,
65
+ 'spec_path' : validate_exists })
55
66
56
67
def _gather_specs (self ,
57
68
urls : list ,
58
- path_ : PosixPath ) -> PosixPath :
69
+ path_ : PosixPath or None ) -> PosixPath :
59
70
"""
60
71
Download first swagger spec from the url list; copy it into the
61
72
temp dir and return path to it. If all urls fail — check path_ and
62
73
return it.
63
74
64
75
Return None if everything fails
65
76
"""
66
-
77
+ self . logger . debug ( f'Gathering specs. Got list of urls: { urls } , path: { path_ } ' )
67
78
if urls :
68
79
for url in urls :
69
80
try :
70
81
filename = self ._swagger_tmp / f'swagger_spec'
71
82
urlretrieve (url , filename )
83
+ self .logger .debug (f'Using spec from { url } ({ filename } )' )
72
84
return filename
73
- except (HTTPError , URLError ):
74
- err = traceback .format_exc ()
75
- self .logger .debug (f'Cannot retrieve swagger spec file from url { url } .\n { err } ' )
76
- print (f'\n Cannot retrieve swagger spec file from url { url } . Skipping.' )
85
+ except (HTTPError , URLError ) as e :
86
+ self ._warning (f'\n Cannot retrieve swagger spec file from url { url } . Skipping.' ,
87
+ error = e )
77
88
78
89
if path_ :
79
90
dest = self ._swagger_tmp / f'swagger_spec'
80
91
if not path_ .exists ():
81
- self .logger .debug (f'{ path_ } not found' )
82
- print (f"\n Can't find file { path_ } . Skipping." )
92
+ self ._warning (f"Can't find file { path_ } . Skipping." )
83
93
else : # file exists
84
94
copyfile (str (path_ ), str (dest ))
85
95
return dest
86
96
87
97
def _process_jinja (self ,
88
98
spec : PosixPath ,
89
- tag_options : dict ) -> str :
99
+ options : CombinedOptions ) -> str :
90
100
"""Process swagger.json with jinja and return the resulting string"""
101
+ self .logger .debug ('Using jinja mode' )
91
102
data = yaml .safe_load (open (spec , encoding = "utf8" ))
92
- additional = tag_options .get ('additional_json_path' ) or \
93
- self .options ['additional_json_path' ]
103
+ additional = options .get ('additional_json_path' )
94
104
if additional :
95
- if type (additional ) is str :
96
- additional = self .project_path / additional
97
105
if not additional .exists ():
98
- print (f'Additional swagger spec file { additional } is missing. Skipping' )
106
+ self . _warning (f'Additional swagger spec file { additional } is missing. Skipping' )
99
107
else :
100
108
add = yaml .safe_load (open (additional , encoding = "utf8" ))
101
109
data = {** add , ** data }
102
110
103
- template = tag_options .get ('template' , self .options ['template' ])
104
- if type (template ) is str :
105
- template = self .project_path / template
106
- if template == self .project_path / self .defaults ['template' ] and \
107
- not template .exists ():
111
+ if options .is_default ('template' ) and not Path (options ['template' ]).exists ():
108
112
copyfile (resource_filename (__name__ , 'template/' +
109
- self .defaults ['template' ]), template )
110
- return self ._to_md (data , template )
113
+ self .defaults ['template' ]), options [ ' template' ] )
114
+ return self ._to_md (data , options [ ' template' ] )
111
115
112
116
def _process_widdershins (self ,
113
117
spec : PosixPath ,
114
- tag_options : dict ) -> str :
118
+ options : CombinedOptions ) -> str :
115
119
"""
116
120
Process swagger.json with widdershins and return the resulting string
117
121
"""
118
122
119
- environment = tag_options . get ( 'environment' ) or \
120
- self . options .get ('environment' )
123
+ self . logger . debug ( 'Using widdershins mode' )
124
+ environment = options .get ('environment' )
121
125
if environment :
122
- if type (environment ) is str :
126
+ if isinstance (environment , str ) or isinstance ( environment , PosixPath ) :
123
127
env_str = f'--environment { environment } '
124
128
else : # inline config in foliant.yaml
125
- env_yaml = str (self ._swagger_tmp / 'emv .yaml' )
129
+ env_yaml = str (self ._swagger_tmp / 'env .yaml' )
126
130
with open (env_yaml , 'w' ) as f :
127
- f . write ( yaml .dump (environment ) )
131
+ yaml .dump (environment , f )
128
132
env_str = f'--environment { env_yaml } '
129
133
else : # not environment
130
134
env_str = ''
@@ -147,71 +151,51 @@ def _process_widdershins(self,
147
151
self .logger .info (f'Build log saved at { log_path } ' )
148
152
if result .stderr :
149
153
error_fragment = '\n ' .join (result .stderr .decode ().split ("\n " )[:3 ])
150
- self .logger .warning ('Widdershins builder returned error or warning:\n '
151
- f'{ error_fragment } \n ...\n '
152
- f'Full build log at { log_path .absolute ()} ' )
153
- output ('Widdershins builder returned error or warning:\n '
154
- f'{ error_fragment } \n ...\n '
155
- f'Full build log at { log_path .absolute ()} ' , self .quiet )
156
- return open (out_str ).read ()
154
+ self ._warning ('Widdershins builder returned error or warning:\n '
155
+ f'{ error_fragment } \n ...\n '
156
+ f'Full build log at { log_path .absolute ()} ' )
157
+ with open (out_str ) as f :
158
+ return f .read ()
157
159
158
160
def _to_md (self ,
159
161
data : dict ,
160
162
template_path : PosixPath or str ) -> str :
161
163
"""generate markdown string from 'data' dict using jinja 'template'"""
162
164
163
165
try :
164
- o = open (str (template_path ), 'r' )
165
166
template = self ._env .get_template (str (template_path ))
166
167
result = template .render (swagger_data = data , dumps = json .dumps )
167
168
except Exception as e :
168
- info = traceback .format_exc ()
169
- print (f'\n Failed to render doc template { template_path } :' , info )
170
- self .logger .debug (f'Failed to render doc template:\n \n { info } ' )
169
+ self ._warning (f'\n Failed to render doc template { template_path } ' ,
170
+ error = e )
171
171
return ''
172
172
return result
173
173
174
- def process_swaggerdoc_blocks (self , content : str ) -> str :
175
- def _sub (block : str ) -> str :
176
- if block .group ('options' ):
177
- tag_options = self .get_options (block .group ('options' ))
178
- else :
179
- tag_options = {}
180
-
181
- spec_url = tag_options .get ('json_url' ) or self .options ['json_url' ]
182
- if spec_url and type (spec_url ) is str :
183
- spec_url = [spec_url ]
184
- spec_path = tag_options .get ('json_path' ) or self .options ['json_path' ]
185
- if spec_path and type (spec_path ) is str :
186
- spec_path = self .project_path / spec_path
187
-
188
- if not (spec_path or spec_url ):
189
- print ('\n Error: No swagger spec file specified!' )
190
- return ''
191
-
192
- mode = tag_options .get ('mode' ) or self .options ['mode' ]
193
- if mode not in self ._modes :
194
- print (f'\n Error: Unrecognised mode { mode } .'
195
- f' Should be one of { self ._modes } ' )
196
- return ''
197
-
198
- spec = self ._gather_specs (spec_url , spec_path )
199
- if not spec :
200
- raise RuntimeError ("No valid swagger spec file specified" )
201
-
202
- return self ._modes [mode ](spec , tag_options )
203
- return self .pattern .sub (_sub , content )
174
+ @allow_fail ()
175
+ def process_swaggerdoc_blocks (self , block ) -> str :
176
+ tag_options = Options (self .get_options (block .group ('options' )),
177
+ convertors = {'json_path' : rel_path_convertor (self .current_filepath .parent ),
178
+ 'spec_path' : rel_path_convertor (self .current_filepath .parent ),
179
+ 'additional_json_path' : rel_path_convertor (self .current_filepath .parent )})
180
+ options = CombinedOptions (options = {'config' : self .options ,
181
+ 'tag' : tag_options },
182
+ priority = 'tag' ,
183
+ required = [('json_url' ,),
184
+ ('json_path' ,),
185
+ ('spec_url' ,),
186
+ ('spec_path' ,)],
187
+ validators = {'mode' : validate_in (self ._modes )})
188
+ self .logger .debug (f'Processing swaggerdoc tag in { self .current_filepath } ' )
189
+ spec_url = options ['spec_url' ] or options ['json_url' ]
190
+ if spec_url and isinstance (spec_url , str ):
191
+ spec_url = [spec_url ]
192
+ spec_path = options ['spec_path' ] or options ['json_path' ]
193
+ spec = self ._gather_specs (spec_url , spec_path )
194
+ if not spec :
195
+ raise RuntimeError ("No valid swagger spec file specified" )
196
+
197
+ return self ._modes [options ['mode' ]](spec , options )
204
198
205
199
def apply (self ):
206
- self .logger .info ('Applying preprocessor' )
207
-
208
- for markdown_file_path in self .working_dir .rglob ('*.md' ):
209
- self .logger .debug (f'Processing Markdown file: { markdown_file_path } ' )
210
-
211
- with open (markdown_file_path , encoding = 'utf8' ) as markdown_file :
212
- content = markdown_file .read ()
213
-
214
- with open (markdown_file_path , 'w' , encoding = 'utf8' ) as markdown_file :
215
- markdown_file .write (self .process_swaggerdoc_blocks (content ))
216
-
200
+ self ._process_tags_for_all_files (func = self .process_swaggerdoc_blocks )
217
201
self .logger .info ('Preprocessor applied' )
0 commit comments