+ def _parse_milestone(self, identifier: str, value: Any) -> Milestone:
+ if not isinstance(value, dict):
+ raise ConfigParseError(
+ f"milestones entry for {identifier!r} must be a table")
+ canonical_bug_id = None
+ for k, v in value.items():
+ assert isinstance(k, str)
+ if k == "canonical_bug_id":
+ if not isinstance(v, int):
+ raise ConfigParseError(
+ f"`canonical_bug_id` field in milestones entry for "
+ f"{identifier!r} must be an integer")
+ canonical_bug_id = v
+ else:
+ raise ConfigParseError(f"unknown field in milestones entry "
+ f"for {identifier!r}: `{k}`")
+ if canonical_bug_id is None:
+ raise ConfigParseError(f"`canonical_bug_id` field is missing in "
+ f"milestones entry for {identifier!r}")
+ return Milestone(config=self, identifier=identifier,
+ canonical_bug_id=canonical_bug_id)
+
+ def _parse_milestones(self, milestones: Any):
+ if not isinstance(milestones, dict):
+ raise ConfigParseError("`milestones` field must be a table")
+ for identifier, value in milestones.items():
+ assert isinstance(identifier, str)
+ self.milestones[identifier] = \
+ self._parse_milestone(identifier, value)
+
+ # self.canonical_bug_ids checks for bug id clashes and raises
+ # ConfigParseError if any are detected, so the following line
+ # is needed:
+ self.canonical_bug_ids
+