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