docs: def build_changelog(docs_path, package_name="yacms"):
      """
      Converts Mercurial commits into a changelog in RST format.
      """
  
      project_path = os.path.join(docs_path, "..")
      version_file = os.path.join(package_name, "__init__.py")
      version_var = "__version__"
      changelog_filename = "CHANGELOG"
      changelog_file = os.path.join(project_path, changelog_filename)
      versions = OrderedDict()
      repo = None
      ignore = ("AUTHORS", "formatting", "typo", "pep8", "pep 8",
                "whitespace", "README", "trans", "print debug",
                "debugging", "tabs", "style", "sites", "ignore",
                "tweak", "cleanup", "minor", "for changeset",
                ".com``", "oops", "syntax")
      hotfixes = {
          "40cbc47b8d8a": "1.0.9",
          "a25749986abc": "1.0.10",
      }
  
      # Load the repo.
      try:
          from mercurial import ui, hg, error
          from mercurial.commands import tag
      except ImportError:
          pass
      else:
          try:
              ui = ui.ui()
              repo = hg.repository(ui, project_path)
          except error.RepoError:
              return
      if repo is None:
          return
  
      # Go through each changeset and assign it to the versions dict.
      changesets = [repo.changectx(changeset) for changeset in repo.changelog]
      for cs in sorted(changesets, reverse=True, key=_changeset_date):
          # Check if the file with the version number is in this changeset
          # and if it is, pull it out and assign it as a variable.
          files = cs.files()
          new_version = False
          # Commit message cleanup hacks.
          description = cs.description().decode("utf-8")
          description = description.rstrip(".").replace("\n", ". ")
          while "  " in description:
              description = description.replace("  ", " ")
          description = description.replace(". . ", ". ").replace("...", ",")
          while ".." in description:
              description = description.replace("..", ".")
          description = description.replace(":.", ":").replace("n'. t", "n't")
          words = description.split()
          # Format var names in commit.
          for i, word in enumerate(words):
              if (set("._") & set(word[:-1]) and set(letters) & set(word) and
                      "`" not in word and not word[0].isdigit()):
                  last = ""
                  if word[-1] in ",.":
                      last, word = word[-1], word[:-1]
                  words[i] = "``%s``%s" % (word, last)
          description = " ".join(words)
          if version_file in files:
              for line in cs[version_file].data().split("\n"):
                  if line.startswith(version_var):
                      exec(line)
                      if locals()[version_var] == "0.1.0":
                          locals()[version_var] = "1.0.0"
                          break
                      versions[locals()[version_var]] = {
                          "changes": [],
                          "date": _changeset_date(cs).strftime("%b %d, %Y")
                      }
                      new_version = len(files) == 1
  
          # Tag new versions.
          hotfix = hotfixes.get(cs.hex()[:12])
          if hotfix or new_version:
              if hotfix:
                  version_tag = hotfix
              else:
                  try:
                      version_tag = locals()[version_var]
                  except KeyError:
                      version_tag = None
              if version_tag and version_tag not in cs.tags():
                  try:
                      tag(ui, repo, version_tag, rev=cs.hex())
                      print("Tagging version %s" % version_tag)
                  except:
                      pass
  
          # Ignore changesets that are merges, bumped the version, closed
          # a branch, regenerated the changelog itself, contain an ignore
          # word, or contain too few words to be meaningful.
          merge = len(cs.parents()) > 1
          branch_closed = len(files) == 0
          changelog_update = changelog_filename in files
          ignored = [w for w in ignore if w.lower() in description.lower()]
          too_few_words = len(description.split()) <= 3
          if (merge or new_version or branch_closed or changelog_update or
                  ignored or too_few_words):
              continue
          # Ensure we have a current version and if so, add this changeset's
          # description to it.
          version = None
          try:
              version = locals()[version_var]
          except KeyError:
              if not hotfix:
                  continue
          user = cs.user().decode("utf-8").split("<")[0].strip()
          entry = "%s - %s" % (description, user)
          if hotfix or entry not in versions[version]["changes"]:
              if hotfix:
                  versions[hotfix] = {
                      "changes": [entry],
                      "date": _changeset_date(cs).strftime("%b %d, %Y"),
                  }
              else:
                  versions[version]["changes"].insert(0, entry)
  
      # Write out the changelog.
      with open(changelog_file, "w") as f:
          for version, version_info in versions.items():
              header = "Version %s (%s)" % (version, version_info["date"])
              f.write("%s\n" % header)
              f.write("%s\n" % ("-" * len(header)))
              f.write("\n")
              if version_info["changes"]:
                  for change in version_info["changes"]:
                      f.write("  * %s\n" % change)
              else:
                  f.write("  * No changes listed.\n")
              f.write("\n")