Skip to content

Commit 19d2646

Browse files
author
minuhin
committed
rewrite with utils, add spec_path, spec_url, path parameters in tags are relative to cur file
1 parent 3b7c05e commit 19d2646

File tree

3 files changed

+78
-86
lines changed

3 files changed

+78
-86
lines changed

changelog.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# 1.2.0
2+
3+
- Add `spec_path` and `spec_url` parameters.
4+
- All path tag parameters are now loaded relative to current file.
5+
- Better logging and error reporting
6+
17
# 1.1.3
28

39
- Fix issues with json and yaml. All spec files are now loaded with yaml loader.

foliant/preprocessors/swaggerdoc/swaggerdoc.py

+68-84
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import traceback
88
import json
99
from ruamel import yaml
10-
from pathlib import PosixPath
10+
from pathlib import Path, PosixPath
1111
from urllib.request import urlretrieve
1212
from urllib.error import HTTPError, URLError
1313
from distutils.dir_util import remove_tree
@@ -16,17 +16,25 @@
1616
from pkg_resources import resource_filename
1717
from subprocess import run, PIPE
1818

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)
2026
from foliant.utils import output
2127

2228

23-
class Preprocessor(BasePreprocessor):
29+
class Preprocessor(BasePreprocessorExt):
2430
tags = ('swaggerdoc',)
2531

2632
defaults = {
27-
'json_url': '',
33+
'json_url': [], # deprecated
34+
'spec_url': [],
2835
'additional_json_path': '',
29-
'json_path': '',
36+
'json_path': '', # deprecated
37+
'spec_path': '',
3038
'mode': 'widdershins',
3139
'template': 'swagger.j2'
3240
}
@@ -52,79 +60,75 @@ def __init__(self, *args, **kwargs):
5260
os.makedirs(self._swagger_tmp)
5361

5462
self._counter = 0
63+
self.options = Options(self.options,
64+
validators={'json_path': validate_exists,
65+
'spec_path': validate_exists})
5566

5667
def _gather_specs(self,
5768
urls: list,
58-
path_: PosixPath) -> PosixPath:
69+
path_: PosixPath or None) -> PosixPath:
5970
"""
6071
Download first swagger spec from the url list; copy it into the
6172
temp dir and return path to it. If all urls fail — check path_ and
6273
return it.
6374
6475
Return None if everything fails
6576
"""
66-
77+
self.logger.debug(f'Gathering specs. Got list of urls: {urls}, path: {path_}')
6778
if urls:
6879
for url in urls:
6980
try:
7081
filename = self._swagger_tmp / f'swagger_spec'
7182
urlretrieve(url, filename)
83+
self.logger.debug(f'Using spec from {url} ({filename})')
7284
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'\nCannot retrieve swagger spec file from url {url}. Skipping.')
85+
except (HTTPError, URLError) as e:
86+
self._warning(f'\nCannot retrieve swagger spec file from url {url}. Skipping.',
87+
error=e)
7788

7889
if path_:
7990
dest = self._swagger_tmp / f'swagger_spec'
8091
if not path_.exists():
81-
self.logger.debug(f'{path_} not found')
82-
print(f"\nCan't find file {path_}. Skipping.")
92+
self._warning(f"Can't find file {path_}. Skipping.")
8393
else: # file exists
8494
copyfile(str(path_), str(dest))
8595
return dest
8696

8797
def _process_jinja(self,
8898
spec: PosixPath,
89-
tag_options: dict) -> str:
99+
options: CombinedOptions) -> str:
90100
"""Process swagger.json with jinja and return the resulting string"""
101+
self.logger.debug('Using jinja mode')
91102
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')
94104
if additional:
95-
if type(additional) is str:
96-
additional = self.project_path / additional
97105
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')
99107
else:
100108
add = yaml.safe_load(open(additional, encoding="utf8"))
101109
data = {**add, **data}
102110

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():
108112
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'])
111115

112116
def _process_widdershins(self,
113117
spec: PosixPath,
114-
tag_options: dict) -> str:
118+
options: CombinedOptions) -> str:
115119
"""
116120
Process swagger.json with widdershins and return the resulting string
117121
"""
118122

119-
environment = tag_options.get('environment') or \
120-
self.options.get('environment')
123+
self.logger.debug('Using widdershins mode')
124+
environment = options.get('environment')
121125
if environment:
122-
if type(environment) is str:
126+
if isinstance(environment, str) or isinstance(environment, PosixPath):
123127
env_str = f'--environment {environment}'
124128
else: # inline config in foliant.yaml
125-
env_yaml = str(self._swagger_tmp / 'emv.yaml')
129+
env_yaml = str(self._swagger_tmp / 'env.yaml')
126130
with open(env_yaml, 'w') as f:
127-
f.write(yaml.dump(environment))
131+
yaml.dump(environment, f)
128132
env_str = f'--environment {env_yaml}'
129133
else: # not environment
130134
env_str = ''
@@ -147,71 +151,51 @@ def _process_widdershins(self,
147151
self.logger.info(f'Build log saved at {log_path}')
148152
if result.stderr:
149153
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()
157159

158160
def _to_md(self,
159161
data: dict,
160162
template_path: PosixPath or str) -> str:
161163
"""generate markdown string from 'data' dict using jinja 'template'"""
162164

163165
try:
164-
o = open(str(template_path), 'r')
165166
template = self._env.get_template(str(template_path))
166167
result = template.render(swagger_data=data, dumps=json.dumps)
167168
except Exception as e:
168-
info = traceback.format_exc()
169-
print(f'\nFailed to render doc template {template_path}:', info)
170-
self.logger.debug(f'Failed to render doc template:\n\n{info}')
169+
self._warning(f'\nFailed to render doc template {template_path}',
170+
error=e)
171171
return ''
172172
return result
173173

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('\nError: 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'\nError: 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)
204198

205199
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)
217201
self.logger.info('Preprocessor applied')

setup.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
description=SHORT_DESCRIPTION,
1717
long_description=LONG_DESCRIPTION,
1818
long_description_content_type='text/markdown',
19-
version='1.1.3',
19+
version='1.2.0',
2020
author='Daniil Minukhin',
2121
author_email='[email protected]',
2222
packages=['foliant.preprocessors.swaggerdoc'],
@@ -26,7 +26,9 @@
2626
install_requires=[
2727
'foliant>=1.0.5',
2828
'jinja2',
29-
'ruamel.yaml'
29+
'ruamel.yaml',
30+
'foliantcontrib.utils.combined_options>=1.0.7',
31+
'foliantcontrib.utils.preprocessor_ext',
3032
],
3133
classifiers=[
3234
"Development Status :: 5 - Production/Stable",

0 commit comments

Comments
 (0)