amanuensis 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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
+