lib/hoe/halostatue.rb



# frozen_string_literal: true

require "shellwords"
require_relative "halostatue/version"

Hoe.plugin :gemspec2
Hoe.plugin :markdown
Hoe.plugin :rubygems

# This module is a Hoe plugin. You can set its options in your Rakefile Hoe spec, like
# this:
#
#    Hoe.plugin :git
#
#    Hoe.spec "myproj" do
#      self.checklist = nil if ENV["rubygems_release_gem"] == "true"
#      self.git_release_tag_prefix = "REL_"
#      self.git_remotes << "myremote"
#    end
#
# === Tasks
#
# git:changelog:: Print the current changelog.
# git:manifest::  Update the manifest with Git's file list.
# git:tag::       Create and push a tag.
#
# === Options
#
# - +checklist+: An array of reminder questions that should be asked before a release, in
#   the form, "Did you... [question]?" You can see the defaults by running <tt>rake
#   checklist</tt>. If the checklist is +nil+ or empty, the checklist will not shown
#   during release. This is originally from hoe-doofus and called +doofus_checklist+.
#
# - +git_release_tag_prefix+: What do you want at the front of your release tags? The
#   default is <tt>"v"</tt>.
#
# - +git_remotes+: Which remotes do you want to push tags, etc. to? The default is
#   <tt>%w[origin]</tt>
#
# - +git_tag_enabled+: Whether a git tag should be created on release. The default is
#   +true+.
module Hoe::Halostatue
  # Indicates that this release is being run as part of a trusted release workflow.
  # [default: +false+]
  attr_accessor :trusted_release

  # An array of reminder questions that should be asked before a release, in the form,
  # "Did you... [question]?" You can see the defaults by running <tt>rake checklist</tt>.
  #
  # If the checklist is +nil+ or empty, the checklist will not shown during release.
  attr_accessor :checklist

  # What do you want at the front of your release tags?
  # [default: <tt>"v"</tt>]
  attr_accessor :git_release_tag_prefix

  # Which remotes do you want to push tags, etc. to?
  # [default: <tt>%w[origin]</tt>]
  attr_accessor :git_remotes

  # Should git tags be created on release? [default: +true+]
  attr_accessor :git_tag_enabled

  def initialize_halostatue # :nodoc:
    Hoe::URLS_TO_META_MAP.update({
      "bugs" => "bug_tracker_uri",
      "changelog" => "changelog_uri",
      "changes" => "changelog_uri",
      "clog" => "changelog_uri",
      "code" => "source_code_uri",
      "doco" => "documentation_uri",
      "docs" => "documentation_uri",
      "documentation" => "documentation_uri",
      "history" => "changelog_uri",
      "home" => "homepage_uri",
      "issues" => "bug_tracker_uri",
      "mail" => "mailing_list_uri",
      "tickets" => "bug_tracker_uri",
      "wiki" => "wiki_uri"
    })
    Hoe.prepend Hoe::Halostatue::ParseUrls

    self.checklist = [
      "bump the version",
      "check everything in",
      "review the manifest",
      "update the README and RDocs",
      "update the changelog",
      "regenerate the gemspec"
    ]

    self.git_release_tag_prefix = "v"
    self.git_remotes = %w[origin]
    self.git_tag_enabled = true
    self.trusted_release = false
  end

  LINKS = /\[(?<name>.+?)\](?:\(.+?\)|\[.+?\])/

  def define_halostatue_tasks # :nodoc:
    desc "Show a reminder for steps I frequently forget"
    task :checklist do
      if checklist.nil? || checklist.empty?
        puts "Checklist is empty."
      else
        puts "\n### HEY! Did you...\n\n"

        checklist.each do |question|
          question = question[0..0].upcase + question[1..]
          question = "#{question}?" unless question.end_with?("?")
          puts "  * #{question}"
        end

        puts
      end
    end

    task :release_sanity do
      unless checklist.nil? || checklist.empty? || trusted_release
        Rake::Task[:checklist].invoke
        puts "Hit return if you're sure, Ctrl-C if you forgot something."
        $stdin.gets
      end
    end

    task :spec_clean_markdown_links do
      spec.description = spec.description.gsub(LINKS, '\k<name>').gsub(/\r?\n/, " ")
      spec.summary = spec.summary.gsub(LINKS, '\k<name>').gsub(/\r?\n/, " ")
    end

    task "#{spec.name}.gemspec" => :spec_clean_markdown_links

    if trusted_release
      task :trusted_release do
        vm = %r{^(?<version>\d+(?:\.\d+)+)(?:\.(?<pre>[a-z]\w+(?:\.\d+)+))?}
          .match(spec.version.to_s)

        ENV["VERSION"] = vm[:version]
        ENV["PRERELEASE"] = vm[:pre]
      end

      task release_sanity: :trusted_release
    end

    return unless __run_git("rev-parse", "--is-inside-work-tree") == "true"

    desc "Update the manifest with Git's file list. Use Hoe's excludes."
    task "git:manifest" do
      with_config do |config, _|
        files = __run_git("ls-files").split($/)
        files.reject! { |f| f =~ config["exclude"] }

        File.open "Manifest.txt", "w" do |f|
          f.puts files.sort.join("\n")
        end
      end
    end

    desc "Create and push a TAG (default #{git_release_tag_prefix}#{version})."
    task "git:tag" do
      if git_tag_enabled
        tag = ENV["TAG"]
        ver = ENV["VERSION"] || version
        pre = ENV["PRERELEASE"] || ENV["PRE"]
        ver += ".#{pre}" if pre
        tag ||= "#{git_release_tag_prefix}#{ver}"

        git_tag_and_push tag
      end
    end

    task :release_sanity do
      unless __run_git("status", "--porcelain").empty?
        abort "Won't release: Dirty index or untracked files present!"
      end
    end

    task release_to: "git:tag"
  end

  def __git(command, *params)
    "git #{command.shellescape} #{params.compact.shelljoin}"
  end

  def __run_git(command, *params)
    `#{__git(command, *params)}`.strip.chomp
  end

  def git_svn?
    File.exist?(File.join(__run_git("rev-parse", "--show-toplevel"), ".git/svn"))
  end

  def git_tag_and_push tag
    msg = "Tagging #{tag}."

    if git_svn?
      sh __git("svn", "tag", tag, "-m", msg)
    else
      flags =
        if __run_git("config", "--get", "user.signingkey").empty?
          nil
        else
          "-s"
        end

      sh __git("tag", flags, "-f", tag, "-m", msg)
      git_remotes.each { |remote| sh __git("push", "-f", remote, "tag", tag) }
    end
  end

  # This replaces Hoe#parse_urls with something that works better for Markdown.
  module ParseUrls # :nodoc:
    def parse_urls text
      keys = Hoe::URLS_TO_META_MAP.keys.join("|")
      pattern = %r{^[-+*]\s+(#{keys})\s+::\s+[<]?(\w+://[^>\s]+)[>]?}m

      text.scan(pattern).to_h
    end
  end
end