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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.env.sample +10 -0
  3. data/.gitignore +15 -0
  4. data/.rspec +2 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +248 -0
  8. data/Rakefile +2 -0
  9. data/amanuensis.gemspec +36 -0
  10. data/bin/amanuensis +6 -0
  11. data/lib/amanuensis.rb +41 -0
  12. data/lib/amanuensis/builder.rb +52 -0
  13. data/lib/amanuensis/cli.rb +54 -0
  14. data/lib/amanuensis/code_manager.rb +6 -0
  15. data/lib/amanuensis/fake.rb +15 -0
  16. data/lib/amanuensis/fake/code_manager.rb +18 -0
  17. data/lib/amanuensis/fake/push.rb +10 -0
  18. data/lib/amanuensis/fake/tracker.rb +11 -0
  19. data/lib/amanuensis/file.rb +14 -0
  20. data/lib/amanuensis/file/push.rb +37 -0
  21. data/lib/amanuensis/generator.rb +87 -0
  22. data/lib/amanuensis/github.rb +24 -0
  23. data/lib/amanuensis/github/code_manager.rb +42 -0
  24. data/lib/amanuensis/github/push.rb +27 -0
  25. data/lib/amanuensis/github/tracker.rb +33 -0
  26. data/lib/amanuensis/issue.rb +4 -0
  27. data/lib/amanuensis/logger.rb +26 -0
  28. data/lib/amanuensis/mail.rb +18 -0
  29. data/lib/amanuensis/mail/push.rb +12 -0
  30. data/lib/amanuensis/pivotal.rb +17 -0
  31. data/lib/amanuensis/pivotal/tracker.rb +18 -0
  32. data/lib/amanuensis/pull.rb +4 -0
  33. data/lib/amanuensis/push.rb +5 -0
  34. data/lib/amanuensis/release.rb +4 -0
  35. data/lib/amanuensis/tracker.rb +7 -0
  36. data/lib/amanuensis/trello.rb +19 -0
  37. data/lib/amanuensis/trello/tracker.rb +31 -0
  38. data/lib/amanuensis/validatable.rb +34 -0
  39. data/lib/amanuensis/version.rb +26 -0
  40. data/spec/fixtures/vcr_cassettes/github/amanuensis_generates_a_github_changelog_and_release.yml +550 -0
  41. data/spec/fixtures/vcr_cassettes/pivotal/amanuensis_generates_a_changelog_from_pivotal_tracker.yml +120 -0
  42. data/spec/fixtures/vcr_cassettes/trello/amanuensis_generates_a_changelog_from_trello_tracker.yml +359 -0
  43. data/spec/integrations/file_spec.rb +19 -0
  44. data/spec/integrations/github_spec.rb +16 -0
  45. data/spec/integrations/mail_spec.rb +16 -0
  46. data/spec/integrations/pivotal_spec.rb +16 -0
  47. data/spec/integrations/trello_spec.rb +18 -0
  48. data/spec/spec_helper.rb +37 -0
  49. data/spec/unit/fake_spec.rb +13 -0
  50. 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,6 @@
1
+ module Amanuensis
2
+ class CodeManager
3
+ extend Interchange.new(:latest_release, :create_release, :pulls)
4
+
5
+ end
6
+ 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,18 @@
1
+ module Amanuensis
2
+ module Fake
3
+ class CodeManager
4
+
5
+ def latest_release
6
+ Release.new(Date.new(2015), '1.0.0')
7
+ end
8
+
9
+ def create_release(_)
10
+ end
11
+
12
+ def pulls(_)
13
+ []
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ module Amanuensis
2
+ module Fake
3
+ class Push
4
+
5
+ def run(_)
6
+ end
7
+
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ module Amanuensis
2
+ module Fake
3
+ class Tracker
4
+
5
+ def issues(_)
6
+ [Issue.new(1, 'https://www.google.com', 'Issue')]
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ require_relative 'file/push'
2
+
3
+ module Amanuensis
4
+ module File
5
+ include ActiveSupport::Configurable
6
+ include Validatable
7
+
8
+ config_accessor(:file_name) { 'Changelog.md' }
9
+ end
10
+
11
+ Push.register :file, File::Push.new
12
+ end
13
+
14
+
@@ -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,4 @@
1
+ module Amanuensis
2
+ class Issue < Struct.new(:number, :html_url, :title)
3
+ end
4
+ 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
+