diff --git a/chaotic/chaotic/front/parser.py b/chaotic/chaotic/front/parser.py index ef83d0f40c4e..c330edc55dcc 100644 --- a/chaotic/chaotic/front/parser.py +++ b/chaotic/chaotic/front/parser.py @@ -2,6 +2,7 @@ import contextlib import dataclasses import os +import re from typing import Dict from typing import Generator from typing import List @@ -13,6 +14,27 @@ from chaotic.front import types +@dataclasses.dataclass(init=False) +class NameMapItem: + pattern: re.Pattern + dest: str # string for str.format() + + def __init__(self, data: str): + groups = data.split('=') + if len(groups) != 2: + raise Exception(f'-n arg must contain "=" ({data})') + + pattern, dest = groups + self.pattern = re.compile(pattern) + self.dest = dest + + def match(self, data: str) -> Optional[str]: + match = self.pattern.fullmatch(data) # pylint: disable=no-member + if match: + return self.dest.format(*match.groups()) + return None + + @dataclasses.dataclass(frozen=True) class ParserConfig: erase_prefix: str @@ -33,7 +55,9 @@ class ParserError(error.BaseError): class SchemaParser: def __init__( - self, *, config: ParserConfig, full_filepath: str, full_vfilepath: str, + self, *, config: ParserConfig, + full_filepath: str, full_vfilepath: str, + plain_object_path_map: List[NameMapItem] = None, ) -> None: self._config = config # Full filepath on real filesystem @@ -41,6 +65,7 @@ def __init__( # Full filepath on virtual filesystem, used by $ref self.full_vfilepath: str = full_vfilepath self._state = ParserState(infile_path='', schemas=dict()) + self._plain_object_path_map = plain_object_path_map def parse_schema(self, infile_path: str, input__: dict) -> None: self._state.infile_path = '' @@ -312,7 +337,20 @@ def _make_abs_ref(self, ref: str) -> str: return self.full_vfilepath + ref else: my_ref = '/'.join(self.full_vfilepath.split('/')[:-1]) - file, infile = ref.split('#') + if '#' in ref: + file, infile = ref.split('#') + else: + file = ref + if file.startswith('./'): + file = file[2:] + + for item in self._plain_object_path_map: + infile = item.match(ref) + if infile is not None: + infile = infile.rstrip('/') + break + if infile is None: + self._raise(f'Invalid ref without in-file path') out_file = os.path.join(my_ref, file) # print(f'ref: {out_file} # {infile}') return out_file + '#' + infile diff --git a/chaotic/chaotic/main.py b/chaotic/chaotic/main.py index 6a4d747aaa6a..64363fc7a358 100644 --- a/chaotic/chaotic/main.py +++ b/chaotic/chaotic/main.py @@ -1,14 +1,11 @@ import argparse -import dataclasses import os import pathlib -import re import sys from typing import Any from typing import Callable from typing import Dict from typing import List -from typing import Optional import yaml @@ -17,27 +14,7 @@ from chaotic.front import parser as front_parser from chaotic.front import ref_resolver from chaotic.front import types - - -@dataclasses.dataclass(init=False) -class NameMapItem: - pattern: re.Pattern - dest: str # string for str.format() - - def __init__(self, data: str): - groups = data.split('=') - if len(groups) != 2: - raise Exception(f'-n arg must contain "=" ({data})') - - pattern, dest = groups - self.pattern = re.compile(pattern) - self.dest = dest - - def match(self, data: str) -> Optional[str]: - match = self.pattern.fullmatch(data) # pylint: disable=no-member - if match: - return self.dest.format(*match.groups()) - return None +from chaotic.front.parser import NameMapItem def parse_args() -> argparse.Namespace: @@ -60,6 +37,13 @@ def parse_args() -> argparse.Namespace: help='full filepath to virtual filepath mapping', ) + parser.add_argument( + '--plain-object-path-map', + type=NameMapItem, + action='append', + help='plain object filepath to in-file path', + ) + parser.add_argument( '-u', '--userver', @@ -174,9 +158,17 @@ def traverse_dfs(path: str, data: Any): def extract_schemas_to_scan( inp: dict, name_map: List[NameMapItem], + fname: str, plain_object_path_map: List[NameMapItem], ) -> Dict[str, Any]: schemas = [] + if plain_object_path_map is not None and ('type' in inp) and inp['type'] == 'object': + for item in plain_object_path_map: + path = item.match(fname) + if path is not None: + schemas.append((path, inp)) + return dict(schemas) + gen = traverse_dfs('/', inp) ok_ = None while True: @@ -201,6 +193,7 @@ def read_schemas( name_map, file_map, dependencies: List[types.ResolvedSchemas] = [], + plain_object_path_map: List[NameMapItem] = None, ) -> types.ResolvedSchemas: config = front_parser.ParserConfig(erase_prefix=erase_path_prefix) rr = ref_resolver.RefResolver() @@ -210,11 +203,12 @@ def read_schemas( with open(fname) as ifile: data = yaml.load(ifile, Loader=yaml.CLoader) - scan_objects = extract_schemas_to_scan(data, name_map) + scan_objects = extract_schemas_to_scan(data, name_map, fname, plain_object_path_map) vfilepath = vfilepath_from_filepath(fname, file_map) parser = front_parser.SchemaParser( config=config, full_filepath=fname, full_vfilepath=vfilepath, + plain_object_path_map=plain_object_path_map, ) for path, obj in rr.sort_json_types( scan_objects, erase_path_prefix, @@ -246,7 +240,11 @@ def main() -> None: args = parse_args() schemas = read_schemas( - args.erase_path_prefix, args.file, args.name_map, args.file_map, + erase_path_prefix=args.erase_path_prefix, + filepaths=args.file, + name_map=args.name_map, + file_map=args.file_map, + plain_object_path_map=args.plain_object_path_map, ) cpp_name_func = generate_cpp_name_func( args.name_map, args.erase_path_prefix,