amanuensis 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.env.sample +10 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +248 -0
- data/Rakefile +2 -0
- data/amanuensis.gemspec +36 -0
- data/bin/amanuensis +6 -0
- data/lib/amanuensis.rb +41 -0
- data/lib/amanuensis/builder.rb +52 -0
- data/lib/amanuensis/cli.rb +54 -0
- data/lib/amanuensis/code_manager.rb +6 -0
- data/lib/amanuensis/fake.rb +15 -0
- data/lib/amanuensis/fake/code_manager.rb +18 -0
- data/lib/amanuensis/fake/push.rb +10 -0
- data/lib/amanuensis/fake/tracker.rb +11 -0
- data/lib/amanuensis/file.rb +14 -0
- data/lib/amanuensis/file/push.rb +37 -0
- data/lib/amanuensis/generator.rb +87 -0
- data/lib/amanuensis/github.rb +24 -0
- data/lib/amanuensis/github/code_manager.rb +42 -0
- data/lib/amanuensis/github/push.rb +27 -0
- data/lib/amanuensis/github/tracker.rb +33 -0
- data/lib/amanuensis/issue.rb +4 -0
- data/lib/amanuensis/logger.rb +26 -0
- data/lib/amanuensis/mail.rb +18 -0
- data/lib/amanuensis/mail/push.rb +12 -0
- data/lib/amanuensis/pivotal.rb +17 -0
- data/lib/amanuensis/pivotal/tracker.rb +18 -0
- data/lib/amanuensis/pull.rb +4 -0
- data/lib/amanuensis/push.rb +5 -0
- data/lib/amanuensis/release.rb +4 -0
- data/lib/amanuensis/tracker.rb +7 -0
- data/lib/amanuensis/trello.rb +19 -0
- data/lib/amanuensis/trello/tracker.rb +31 -0
- data/lib/amanuensis/validatable.rb +34 -0
- data/lib/amanuensis/version.rb +26 -0
- data/spec/fixtures/vcr_cassettes/github/amanuensis_generates_a_github_changelog_and_release.yml +550 -0
- data/spec/fixtures/vcr_cassettes/pivotal/amanuensis_generates_a_changelog_from_pivotal_tracker.yml +120 -0
- data/spec/fixtures/vcr_cassettes/trello/amanuensis_generates_a_changelog_from_trello_tracker.yml +359 -0
- data/spec/integrations/file_spec.rb +19 -0
- data/spec/integrations/github_spec.rb +16 -0
- data/spec/integrations/mail_spec.rb +16 -0
- data/spec/integrations/pivotal_spec.rb +16 -0
- data/spec/integrations/trello_spec.rb +18 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/unit/fake_spec.rb +13 -0
- metadata +300 -0
| @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            module Amanuensis
         | 
| 2 | 
            +
              class CLI < Thor
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                desc "generate", "Generate a changelog"
         | 
| 5 | 
            +
                option :push,         type: :array,   aliases: :p, default: [:file]
         | 
| 6 | 
            +
                option :tracker,      type: :string,  aliases: :t, default: :github
         | 
| 7 | 
            +
                option :code_manager, type: :string,  aliases: :c, default: :github
         | 
| 8 | 
            +
                option :version,      type: :string,  aliases: :u, default: :patch
         | 
| 9 | 
            +
                option :verbose,      type: :boolean, aliases: :v, default: false
         | 
| 10 | 
            +
                option :github,       type: :hash,    aliases: :g
         | 
| 11 | 
            +
                option :trello,       type: :hash,    aliases: :c
         | 
| 12 | 
            +
                option :pivotal,      type: :hash,    aliases: :i
         | 
| 13 | 
            +
                option :mail,         type: :hash,    aliases: :m
         | 
| 14 | 
            +
                option :file,         type: :hash,    aliases: :f
         | 
| 15 | 
            +
                def generate
         | 
| 16 | 
            +
                  Amanuensis.push         = options.push
         | 
| 17 | 
            +
                  Amanuensis.tracker      = options.tracker
         | 
| 18 | 
            +
                  Amanuensis.code_manager = options.code_manager
         | 
| 19 | 
            +
                  Amanuensis.version      = options.version
         | 
| 20 | 
            +
                  Amanuensis.verbose      = options.verbose
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  if options.github.present?
         | 
| 23 | 
            +
                    options.github.each do |key, value|
         | 
| 24 | 
            +
                      Amanuensis::Github.send "#{key}=", value
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  if options.file.present?
         | 
| 29 | 
            +
                    options.file.each do |key, value|
         | 
| 30 | 
            +
                      Amanuensis::File.send "#{key}=", value
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  if options.trello.present?
         | 
| 35 | 
            +
                    options.trello.each do |key, value|
         | 
| 36 | 
            +
                      Amanuensis::Trello.send "#{key}=", value
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  if options.pivotal.present?
         | 
| 41 | 
            +
                    options.pivotal.each do |key, value|
         | 
| 42 | 
            +
                      Amanuensis::Pivotal.send "#{key}=", value
         | 
| 43 | 
            +
                    end
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  Amanuensis::Mail.pony  = options.mail
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  Amanuensis.generate
         | 
| 49 | 
            +
                rescue ValidationError => e
         | 
| 50 | 
            +
                  puts e.message
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
            end
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            require_relative 'fake/code_manager'
         | 
| 2 | 
            +
            require_relative 'fake/tracker'
         | 
| 3 | 
            +
            require_relative 'fake/push'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Amanuensis
         | 
| 6 | 
            +
              module Fake
         | 
| 7 | 
            +
                include Validatable
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              Push.register        :fake, Fake::Push.new
         | 
| 11 | 
            +
              CodeManager.register :fake, Fake::CodeManager.new
         | 
| 12 | 
            +
              Tracker.register     :fake, Fake::Tracker.new
         | 
| 13 | 
            +
            end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
             | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            module Amanuensis
         | 
| 2 | 
            +
              module File
         | 
| 3 | 
            +
                class Push
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  def run(changelog)
         | 
| 6 | 
            +
                    if ::File.exists?(file_name)
         | 
| 7 | 
            +
                      prepend(changelog)
         | 
| 8 | 
            +
                    else
         | 
| 9 | 
            +
                      create(changelog)
         | 
| 10 | 
            +
                    end
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  private
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def file_name
         | 
| 16 | 
            +
                    @file_name ||= File.file_name
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  def create(changelog)
         | 
| 20 | 
            +
                    f = ::File.new(file_name, 'w')
         | 
| 21 | 
            +
                    f.write changelog
         | 
| 22 | 
            +
                    f.close
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def prepend(changelog)
         | 
| 26 | 
            +
                    ::File.open(file_name, 'r') do |orig|
         | 
| 27 | 
            +
                      ::File.unlink(file_name)
         | 
| 28 | 
            +
                      ::File.open(file_name, 'w') do |new|
         | 
| 29 | 
            +
                        new.write changelog
         | 
| 30 | 
            +
                        new.write orig.read
         | 
| 31 | 
            +
                      end
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
            end
         | 
| @@ -0,0 +1,87 @@ | |
| 1 | 
            +
            module Amanuensis
         | 
| 2 | 
            +
              class Generator
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                def run!
         | 
| 5 | 
            +
                  valid_configurations!
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  CodeManager.use Amanuensis.code_manager.to_sym
         | 
| 8 | 
            +
                  Tracker.use     Amanuensis.tracker.to_sym
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  changelog = build_changelog
         | 
| 11 | 
            +
                  result    = push_changelog(changelog)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  create_release if result.all?
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                private
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def verbose(message, &block)
         | 
| 19 | 
            +
                  if Amanuensis.verbose
         | 
| 20 | 
            +
                    logger.call message, block
         | 
| 21 | 
            +
                  else
         | 
| 22 | 
            +
                    block.call
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def logger
         | 
| 27 | 
            +
                  @logger ||= Logger.new
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def build_changelog
         | 
| 31 | 
            +
                  verbose "Build changelog for #{version}" do
         | 
| 32 | 
            +
                    Builder.new(version, latest_release.created_at).build
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def push_changelog(changelog)
         | 
| 37 | 
            +
                  Amanuensis.push.map do |type|
         | 
| 38 | 
            +
                    Push.use type.to_sym
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    verbose "Push on #{type}" do
         | 
| 41 | 
            +
                      Push.run changelog
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                def latest_release
         | 
| 47 | 
            +
                  @latest_release ||= CodeManager.latest_release
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def create_release
         | 
| 51 | 
            +
                  verbose 'Create release' do
         | 
| 52 | 
            +
                    CodeManager.create_release version
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def valid_configurations!
         | 
| 57 | 
            +
                  verbose 'Valid configuration' do
         | 
| 58 | 
            +
                    Amanuensis.valid!
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    push.map(&:valid!)
         | 
| 61 | 
            +
                    tracker.valid!
         | 
| 62 | 
            +
                    code_manager.valid!
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                def version
         | 
| 67 | 
            +
                  @version = Version.new(latest_release.tag).get
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                def push
         | 
| 71 | 
            +
                  @push ||= Amanuensis.push.map(&method(:classify))
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                def tracker
         | 
| 75 | 
            +
                  @tracker ||= classify Amanuensis.tracker
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                def code_manager
         | 
| 79 | 
            +
                  @code_manager ||= classify Amanuensis.code_manager
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                def classify(integration)
         | 
| 83 | 
            +
                  "Amanuensis::#{integration.to_s.classify}".constantize
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
              end
         | 
| 87 | 
            +
            end
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            require 'octokit'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'github/code_manager'
         | 
| 4 | 
            +
            require_relative 'github/tracker'
         | 
| 5 | 
            +
            require_relative 'github/push'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module Amanuensis
         | 
| 8 | 
            +
              module Github
         | 
| 9 | 
            +
                include ActiveSupport::Configurable
         | 
| 10 | 
            +
                include Validatable
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                config_accessor(:oauth_token)
         | 
| 13 | 
            +
                config_accessor(:repo)
         | 
| 14 | 
            +
                config_accessor(:file_name)   { 'Changelog.md' }
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                validate_presence_of :oauth_token, :repo
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              Push.register        :github, Github::Push.new
         | 
| 20 | 
            +
              CodeManager.register :github, Github::CodeManager.new
         | 
| 21 | 
            +
              Tracker.register     :github, Github::Tracker.new
         | 
| 22 | 
            +
            end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
             | 
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            module Amanuensis
         | 
| 2 | 
            +
              module Github
         | 
| 3 | 
            +
                class CodeManager
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  def latest_release
         | 
| 6 | 
            +
                    hash = client.latest_release(Github.repo)
         | 
| 7 | 
            +
                    Release.new hash.created_at, hash.tag_name
         | 
| 8 | 
            +
                  rescue
         | 
| 9 | 
            +
                    Release.new Date.new(1900), '0.0.0'
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def create_release(version)
         | 
| 13 | 
            +
                    client.create_release(Github.repo, version, {
         | 
| 14 | 
            +
                      body:       "Release generated by amanuensis.",
         | 
| 15 | 
            +
                      draft:      false,
         | 
| 16 | 
            +
                      prerelease: false
         | 
| 17 | 
            +
                    })
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def pulls(from)
         | 
| 21 | 
            +
                    filter(closed_pulls, from).map do |pull|
         | 
| 22 | 
            +
                      Pull.new pull['number'], pull['html_url'], pull['title']
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  private
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def filter(list, from)
         | 
| 29 | 
            +
                    list.select { |object| object.closed_at > from.to_time }
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def closed_pulls
         | 
| 33 | 
            +
                    client.pull_requests(Github.repo, state: 'closed')
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  def client
         | 
| 37 | 
            +
                    @client ||= Octokit::Client.new(access_token: Github.oauth_token, auto_paginate: true)
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
            end
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            module Amanuensis
         | 
| 2 | 
            +
              module Github
         | 
| 3 | 
            +
                class Push
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  def run(changelog)
         | 
| 6 | 
            +
                    client = Octokit::Client.new(access_token: Github.oauth_token, auto_paginate: true)
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                    repo_name      = Github.repo
         | 
| 9 | 
            +
                    default_branch = client.repo(repo_name)[:default_branch]
         | 
| 10 | 
            +
                    ref            = "heads/#{default_branch}"
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                    content = (client.contents repo_name, path: Github.file_name, ref: ref, accept: 'application/vnd.github.V3.raw' rescue '')
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    if content.empty?
         | 
| 15 | 
            +
                      client.create_content(repo_name, Github.file_name, 'Creating changelog', changelog, branch: default_branch)
         | 
| 16 | 
            +
                    else
         | 
| 17 | 
            +
                      content.prepend(changelog)
         | 
| 18 | 
            +
                      content_object = client.contents repo_name, path: Github.file_name, ref: ref
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                      client.update_content(repo_name, Github.file_name, 'Updating changelog', content_object.sha, content, branch: default_branch)
         | 
| 21 | 
            +
                    end
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
| @@ -0,0 +1,33 @@ | |
| 1 | 
            +
            module Amanuensis
         | 
| 2 | 
            +
              module Github
         | 
| 3 | 
            +
                class Tracker
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  def issues(from)
         | 
| 6 | 
            +
                    filter(closed_issues, from).map do |issue|
         | 
| 7 | 
            +
                      Issue.new issue['number'], issue['html_url'], issue['title']
         | 
| 8 | 
            +
                    end
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  private
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  def filter(list, from)
         | 
| 14 | 
            +
                    list.select { |object| object.closed_at > from.to_time }
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def closed_issues
         | 
| 18 | 
            +
                    client.list_issues(Github.repo, state: 'closed').select do |issue|
         | 
| 19 | 
            +
                      !issue['html_url'].include?('pull')
         | 
| 20 | 
            +
                    end
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def closed_pulls
         | 
| 24 | 
            +
                    client.pull_requests(Github.repo, state: 'closed')
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  def client
         | 
| 28 | 
            +
                    Octokit::Client.new(access_token: Github.oauth_token, auto_paginate: true)
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
            end
         | 
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            require 'benchmark'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Amanuensis
         | 
| 4 | 
            +
              class Logger
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                def call(message, block)
         | 
| 7 | 
            +
                  formatter.info "#{message}"
         | 
| 8 | 
            +
                  result = nil
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  duration = Benchmark.realtime do
         | 
| 11 | 
            +
                    result = block.call
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  formatter.info "#{message} ends after #{duration}s"
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  result
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                private
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def formatter
         | 
| 22 | 
            +
                  @formatter ||= ::Logger.new(STDOUT)
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            require 'pony'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative 'mail/push'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Amanuensis
         | 
| 6 | 
            +
              module Mail
         | 
| 7 | 
            +
                include ActiveSupport::Configurable
         | 
| 8 | 
            +
                include Validatable
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                config_accessor(:pony) {{}}
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                validate_presence_of :pony
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              Push.register :mail, Mail::Push.new
         | 
| 16 | 
            +
            end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
             |