mailmap.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. # mailmap.py -- Mailmap reader
  2. # Copyright (C) 2018 Jelmer Vernooij <jelmer@jelmer.uk>
  3. #
  4. # Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
  5. # General Public License as public by the Free Software Foundation; version 2.0
  6. # or (at your option) any later version. You can redistribute it and/or
  7. # modify it under the terms of either of these two licenses.
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. #
  15. # You should have received a copy of the licenses; if not, see
  16. # <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
  17. # and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
  18. # License, Version 2.0.
  19. #
  20. """Mailmap file reader."""
  21. from typing import Dict, Optional, Tuple
  22. def parse_identity(text):
  23. # TODO(jelmer): Integrate this with dulwich.fastexport.split_email and
  24. # dulwich.repo.check_user_identity
  25. (name, email) = text.rsplit(b"<", 1)
  26. name = name.strip()
  27. email = email.rstrip(b">").strip()
  28. if not name:
  29. name = None
  30. if not email:
  31. email = None
  32. return (name, email)
  33. def read_mailmap(f):
  34. """Read a mailmap.
  35. Args:
  36. f: File-like object to read from
  37. Returns: Iterator over
  38. ((canonical_name, canonical_email), (from_name, from_email)) tuples
  39. """
  40. for line in f:
  41. # Remove comments
  42. line = line.split(b"#")[0]
  43. line = line.strip()
  44. if not line:
  45. continue
  46. (canonical_identity, from_identity) = line.split(b">", 1)
  47. canonical_identity += b">"
  48. if from_identity.strip():
  49. parsed_from_identity = parse_identity(from_identity)
  50. else:
  51. parsed_from_identity = None
  52. parsed_canonical_identity = parse_identity(canonical_identity)
  53. yield parsed_canonical_identity, parsed_from_identity
  54. class Mailmap:
  55. """Class for accessing a mailmap file."""
  56. def __init__(self, map=None) -> None:
  57. self._table: Dict[Tuple[Optional[str], str], Tuple[str, str]] = {}
  58. if map:
  59. for canonical_identity, from_identity in map:
  60. self.add_entry(canonical_identity, from_identity)
  61. def add_entry(self, canonical_identity, from_identity=None):
  62. """Add an entry to the mail mail.
  63. Any of the fields can be None, but at least one of them needs to be
  64. set.
  65. Args:
  66. canonical_identity: The canonical identity (tuple)
  67. from_identity: The from identity (tuple)
  68. """
  69. if from_identity is None:
  70. from_name, from_email = None, None
  71. else:
  72. (from_name, from_email) = from_identity
  73. (canonical_name, canonical_email) = canonical_identity
  74. if from_name is None and from_email is None:
  75. self._table[canonical_name, None] = canonical_identity
  76. self._table[None, canonical_email] = canonical_identity
  77. else:
  78. self._table[from_name, from_email] = canonical_identity
  79. def lookup(self, identity):
  80. """Lookup an identity in this mailmail."""
  81. if not isinstance(identity, tuple):
  82. was_tuple = False
  83. identity = parse_identity(identity)
  84. else:
  85. was_tuple = True
  86. for query in [identity, (None, identity[1]), (identity[0], None)]:
  87. canonical_identity = self._table.get(query)
  88. if canonical_identity is not None:
  89. identity = (
  90. canonical_identity[0] or identity[0],
  91. canonical_identity[1] or identity[1],
  92. )
  93. break
  94. if was_tuple:
  95. return identity
  96. else:
  97. return identity[0] + b" <" + identity[1] + b">"
  98. @classmethod
  99. def from_path(cls, path):
  100. with open(path, "rb") as f:
  101. return cls(read_mailmap(f))