# This file is part of the pycalver project # https://github.com/mbarkhau/pycalver # # Copyright (c) 2018-2020 Manuel Barkhau (mbarkhau@gmail.com) - MIT License # SPDX-License-Identifier: MIT """Parse PyCalVer strings from files.""" import typing as typ from .patterns import Pattern LineNo = int Start = int End = int class LineSpan(typ.NamedTuple): lineno: LineNo start : Start end : End LineSpans = typ.List[LineSpan] def _has_overlap(needle: LineSpan, haystack: LineSpans) -> bool: for span in haystack: # assume needle is in the center has_overlap = ( span.lineno == needle.lineno # needle starts before (or at) span end and needle.start <= span.end # needle ends after (or at) span start and needle.end >= span.start ) if has_overlap: return True return False class PatternMatch(typ.NamedTuple): """Container to mark a version string in a file.""" lineno : LineNo # zero based line : str pattern: Pattern span : typ.Tuple[Start, End] match : str PatternMatches = typ.Iterable[PatternMatch] def _iter_for_pattern(lines: typ.List[str], pattern: Pattern) -> PatternMatches: for lineno, line in enumerate(lines): match = pattern.regexp.search(line) if match: yield PatternMatch(lineno, line, pattern, match.span(), match.group(0)) def iter_matches(lines: typ.List[str], patterns: typ.List[Pattern]) -> PatternMatches: """Iterate over all matches of any pattern on any line. >>> from . import v1patterns >>> lines = ["__version__ = 'v201712.0002-alpha'"] >>> version_pattern = "{pycalver}" >>> raw_patterns = ["{pycalver}", "{pep440_pycalver}"] >>> patterns = [v1patterns.compile_pattern(version_pattern, p) for p in raw_patterns] >>> matches = list(iter_matches(lines, patterns)) >>> assert matches[0] == PatternMatch( ... lineno = 0, ... line = "__version__ = 'v201712.0002-alpha'", ... pattern= v1patterns.compile_pattern(version_pattern), ... span = (15, 33), ... match = "v201712.0002-alpha", ... ) """ matched_spans: LineSpans = [] for pattern in patterns: for match in _iter_for_pattern(lines, pattern): needle_span = LineSpan(match.lineno, *match.span) if not _has_overlap(needle_span, matched_spans): yield match matched_spans.append(needle_span)