|
| 1 | + |
| 2 | +import argparse |
| 3 | +import json |
| 4 | +import re |
| 5 | + |
| 6 | +from typing import List, Optional |
| 7 | + |
| 8 | +class _Tag(object): |
| 9 | + |
| 10 | + def __init__( |
| 11 | + self, |
| 12 | + raw_tag: str, |
| 13 | + is_integration: bool = False, |
| 14 | + is_release: bool = False, |
| 15 | + is_hotfix: bool = False, |
| 16 | + is_prerelease: bool = False, |
| 17 | + is_build: bool = False, |
| 18 | + is_invalid: bool = False, |
| 19 | + is_regression: bool = False, |
| 20 | + is_robot: bool = False, |
| 21 | + repository_shorthand: Optional[str] = None, |
| 22 | + run_type: Optional[str] = None, |
| 23 | + run_id: Optional[str] = None, |
| 24 | + ): |
| 25 | + self._raw_tag = raw_tag |
| 26 | + self._is_integration = is_integration |
| 27 | + self._is_release = is_release |
| 28 | + self._is_hotfix = is_hotfix |
| 29 | + self._is_prerelease = is_prerelease |
| 30 | + self._is_build = is_build |
| 31 | + self._is_invalid = is_invalid |
| 32 | + self._is_regression = is_regression |
| 33 | + self._is_robot = is_robot |
| 34 | + self._repository_shorthand = repository_shorthand if repository_shorthand else '' |
| 35 | + self._run_type = run_type if run_type else '' |
| 36 | + self._run_id = run_id if run_id else '' |
| 37 | + |
| 38 | + |
| 39 | + def is_robot(self) -> bool: |
| 40 | + return self._is_robot |
| 41 | + |
| 42 | + def is_regression(self) -> bool: |
| 43 | + return self._is_regression |
| 44 | + |
| 45 | + def get_run_id(self) -> str: |
| 46 | + return self._run_id |
| 47 | + |
| 48 | + def get_run_type(self) -> str: |
| 49 | + return self._run_type |
| 50 | + |
| 51 | + def get_repository_shorthand(self) -> str: |
| 52 | + return self._repository_shorthand |
| 53 | + |
| 54 | + def _to_dict(self) -> dict: |
| 55 | + return { |
| 56 | + 'raw_tag': self._raw_tag, |
| 57 | + 'is_integration': self._is_integration, |
| 58 | + 'is_release': self._is_release, |
| 59 | + 'is_hotfix': self._is_hotfix, |
| 60 | + 'is_prerelease': self._is_prerelease, |
| 61 | + 'is_build': self._is_build, |
| 62 | + 'is_invalid': self._is_invalid, |
| 63 | + 'is_regression': self._is_regression, |
| 64 | + 'is_robot': self._is_robot, |
| 65 | + 'repository_shorthand': self.get_repository_shorthand(), |
| 66 | + 'run_type': self._run_type, |
| 67 | + 'run_id': self._run_id, |
| 68 | + } |
| 69 | + |
| 70 | + def json(self) -> str: |
| 71 | + return json.dumps(self._to_dict()) |
| 72 | + |
| 73 | + |
| 74 | +class _TagParser(object): |
| 75 | + |
| 76 | + _BUILD_ONLY_TAG_PATTERN = re.compile(r'^build-(?P<descriptor>[^-]*)-(?P<seq>\d+).*$') |
| 77 | + _BUILD_RELEASE_TAG_PATTERN = re.compile(r'^build-release-(?P<descriptor>[^-]*)-(?P<seq>\d+).*$') |
| 78 | + _SCENARIO_ONLY_TAG_PATTERN = re.compile(r'^scenario-(?P<run_id>[^-]*)-(?P<run_type>[^-]*)-(?P<repository_shorthand>[^-]*)$') |
| 79 | + _ROBOT_TAG_PATTERN = re.compile(r'^robot-(?P<run_id>[^-]*)-(?P<run_type>[^-]*)-(?P<repository_shorthand>[^-]*)-(?P<seq>\d+)$') |
| 80 | + _ROBOT_ONLY_TAG_PATTERN = re.compile(r'^robot-(?P<descriptor>[^-]*)-(?P<seq>\d+).*$') |
| 81 | + _REGRESSION_ONLY_TAG_PATTERN = re.compile(r'^regression-(?P<descriptor>[^-]*)-(?P<seq>\d+).*$') |
| 82 | + |
| 83 | + def __init__( |
| 84 | + self, |
| 85 | + raw_tag: str, |
| 86 | + permitted_types: List[str] |
| 87 | + ): |
| 88 | + self._raw_tag = raw_tag |
| 89 | + self._permitted_types = permitted_types |
| 90 | + |
| 91 | + def _parse_build_tag(self) -> _Tag: |
| 92 | + match = self._BUILD_ONLY_TAG_PATTERN.match(self._raw_tag) |
| 93 | + if match: |
| 94 | + return _Tag(raw_tag=self._raw_tag, is_build=True) |
| 95 | + match = self._BUILD_RELEASE_TAG_PATTERN.match(self._raw_tag) |
| 96 | + if match: |
| 97 | + return _Tag(raw_tag=self._raw_tag, is_build=True) |
| 98 | + return None |
| 99 | + |
| 100 | + def _parse_scenario_tag(self) -> _Tag: |
| 101 | + match = self._SCENARIO_ONLY_TAG_PATTERN.match(self._raw_tag) |
| 102 | + run_id = match.group('run_id') |
| 103 | + run_type = match.group('run_type') |
| 104 | + repository_shorthand = match.group('repository_shorthand') |
| 105 | + if run_id and run_type: |
| 106 | + return _Tag(raw_tag=self._raw_tag, is_integration=True, run_id=run_id, run_type=run_type, repository_shorthand=repository_shorthand) |
| 107 | + return None |
| 108 | + |
| 109 | + def _parse_robot_tag(self) -> _Tag: |
| 110 | + match = self._ROBOT_TAG_PATTERN.match(self._raw_tag) |
| 111 | + if match: |
| 112 | + return _Tag(raw_tag=self._raw_tag, is_robot=True) |
| 113 | + match = self._ROBOT_ONLY_TAG_PATTERN.match(self._raw_tag) |
| 114 | + if match: |
| 115 | + return _Tag(raw_tag=self._raw_tag, is_robot=True) |
| 116 | + return None |
| 117 | + |
| 118 | + def _parse_regression_tag(self) -> _Tag: |
| 119 | + match = self._REGRESSION_ONLY_TAG_PATTERN.match(self._raw_tag) |
| 120 | + if match: |
| 121 | + return _Tag(raw_tag=self._raw_tag, is_regression=True) |
| 122 | + return None |
| 123 | + |
| 124 | + def parse(self) -> _Tag: |
| 125 | + if not self._permitted_types: |
| 126 | + raise ValueError('No permitted types specified') |
| 127 | + for t in self._permitted_types: |
| 128 | + if t == 'build': |
| 129 | + tag = self._parse_build_tag() |
| 130 | + if tag: |
| 131 | + return tag |
| 132 | + elif t == 'scenario': |
| 133 | + tag = self._parse_scenario_tag() |
| 134 | + if tag: |
| 135 | + return tag |
| 136 | + elif t == 'robot': |
| 137 | + tag = self._parse_robot_tag() |
| 138 | + if tag: |
| 139 | + return tag |
| 140 | + elif t == 'regression': |
| 141 | + tag = self._parse_regression_tag() |
| 142 | + if tag: |
| 143 | + return tag |
| 144 | + raise ValueError(f'Raw tag: "{self._raw_tag}" is incompatible with permitted types: {" ".join(self._permitted_types)}') |
| 145 | + |
| 146 | +def _parse_args() -> argparse.Namespace: |
| 147 | + """ |
| 148 | + Parse the arguments. |
| 149 | + """ |
| 150 | + parser = argparse.ArgumentParser(description='Handle and interpret git tags.') |
| 151 | + parser.add_argument('tag', help='The tag to parse', type=str) |
| 152 | + parser.add_argument('--parse-registry-tag', help='Opt-in to parse a registry tag', action=argparse.BooleanOptionalAction) |
| 153 | + parser.add_argument('--parse-scenario-tag', help='Opt-in to parse a scenario tag', action=argparse.BooleanOptionalAction) |
| 154 | + return parser.parse_args() |
| 155 | + |
| 156 | +def main(): |
| 157 | + args = _parse_args() |
| 158 | + if args.parse_registry_tag: |
| 159 | + tag_parser = _TagParser(args.tag, ['robot', 'regression']) |
| 160 | + tag = tag_parser.parse() |
| 161 | + if tag.is_robot(): |
| 162 | + print(tag.json()) |
| 163 | + return |
| 164 | + elif tag.is_regression(): |
| 165 | + print(tag.json()) |
| 166 | + return |
| 167 | + if args.parse_scenario_tag: |
| 168 | + tag_parser = _TagParser(args.tag, ['scenario']) |
| 169 | + tag = tag_parser.parse() |
| 170 | + if tag.get_run_id() and tag.get_run_type(): |
| 171 | + print(tag.json()) |
| 172 | + return |
| 173 | + else: |
| 174 | + raise ValueError(f'Invalid scenario tag inferred: {tag.json()}') |
| 175 | + raise ValueError('No action specified') |
| 176 | + |
| 177 | +if __name__ == '__main__': |
| 178 | + main() |
| 179 | + |
0 commit comments