bb-flow 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +15 -0
- data/.rubocop_todo.yml +22 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +98 -0
- data/README.md +19 -0
- data/Rakefile +1 -0
- data/bb-flow.gemspec +31 -0
- data/bin/bb-flow +65 -0
- data/bin/git-pr +5 -0
- data/lib/bb-flow.rb +1 -0
- data/lib/bb_flow/github.rb +91 -0
- data/lib/bb_flow/misc.rb +44 -0
- data/lib/bb_flow/persistent.rb +78 -0
- data/lib/bb_flow/printer.rb +74 -0
- data/lib/bb_flow/pull_requester.rb +131 -0
- data/lib/bb_flow/trello.rb +72 -0
- data/lib/bb_flow/version.rb +17 -0
- data/lib/bb_flow.rb +57 -0
- metadata +205 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4aa0d7d44a2b105073dccd71ae17aa7276447fef
|
4
|
+
data.tar.gz: 68f380edd8dd3a5dbd7da6666f5e172048bc4f0f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d0cccdd262c828455900d03245a5693037aef291d5e74567fd7651fead5ce62f00ab56b2a845c9c0924d941be25ea3ea0ff616a9068cfeca94630021a499ca70
|
7
|
+
data.tar.gz: 880d912266652986830fe373f44150289f23902649bf93dd4ffd4c4627f4f378b81f774263e76f3b12c2e48446743df1d5213b4241930adfdaee5121a96fa0a9
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.gem
|
data/.rubocop.yml
ADDED
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2015-10-08 19:43:18 +0300 using RuboCop version 0.34.2.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 2
|
10
|
+
# Cop supports --auto-correct.
|
11
|
+
# Configuration parameters: AlignWith, SupportedStyles, AutoCorrect.
|
12
|
+
Lint/EndAlignment:
|
13
|
+
Enabled: false
|
14
|
+
|
15
|
+
# Offense count: 4
|
16
|
+
Metrics/AbcSize:
|
17
|
+
Max: 35
|
18
|
+
|
19
|
+
# Offense count: 4
|
20
|
+
# Configuration parameters: CountComments.
|
21
|
+
Metrics/MethodLength:
|
22
|
+
Max: 25
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
bb-flow (0.1.0)
|
5
|
+
ansi-select (~> 0.2.5)
|
6
|
+
colorize (~> 0.7.7)
|
7
|
+
git (~> 1.2.9.1)
|
8
|
+
memoist (~> 0.12.0)
|
9
|
+
octokit (~> 4.1.1)
|
10
|
+
ruby-trello (~> 1.3.0)
|
11
|
+
|
12
|
+
GEM
|
13
|
+
remote: https://rubygems.org/
|
14
|
+
specs:
|
15
|
+
activemodel (4.2.4)
|
16
|
+
activesupport (= 4.2.4)
|
17
|
+
builder (~> 3.1)
|
18
|
+
activesupport (4.2.4)
|
19
|
+
i18n (~> 0.7)
|
20
|
+
json (~> 1.7, >= 1.7.7)
|
21
|
+
minitest (~> 5.1)
|
22
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
23
|
+
tzinfo (~> 1.1)
|
24
|
+
addressable (2.3.8)
|
25
|
+
ansi-select (0.2.5)
|
26
|
+
ast (2.1.0)
|
27
|
+
astrolabe (1.3.1)
|
28
|
+
parser (~> 2.2)
|
29
|
+
builder (3.2.2)
|
30
|
+
coderay (1.1.0)
|
31
|
+
colorize (0.7.7)
|
32
|
+
domain_name (0.5.25)
|
33
|
+
unf (>= 0.0.5, < 1.0.0)
|
34
|
+
faraday (0.9.2)
|
35
|
+
multipart-post (>= 1.2, < 3)
|
36
|
+
git (1.2.9.1)
|
37
|
+
http-cookie (1.0.2)
|
38
|
+
domain_name (~> 0.5)
|
39
|
+
i18n (0.7.0)
|
40
|
+
json (1.8.3)
|
41
|
+
memoist (0.12.0)
|
42
|
+
method_source (0.8.2)
|
43
|
+
mime-types (2.6.2)
|
44
|
+
minitest (5.8.1)
|
45
|
+
multipart-post (2.0.0)
|
46
|
+
netrc (0.10.3)
|
47
|
+
oauth (0.4.7)
|
48
|
+
octokit (4.1.1)
|
49
|
+
sawyer (~> 0.6.0, >= 0.5.3)
|
50
|
+
parser (2.2.3.0)
|
51
|
+
ast (>= 1.1, < 3.0)
|
52
|
+
powerpack (0.1.1)
|
53
|
+
pry (0.10.2)
|
54
|
+
coderay (~> 1.1.0)
|
55
|
+
method_source (~> 0.8.1)
|
56
|
+
slop (~> 3.4)
|
57
|
+
rainbow (2.0.0)
|
58
|
+
rake (10.4.2)
|
59
|
+
rest-client (1.8.0)
|
60
|
+
http-cookie (>= 1.0.2, < 2.0)
|
61
|
+
mime-types (>= 1.16, < 3.0)
|
62
|
+
netrc (~> 0.7)
|
63
|
+
rubocop (0.34.2)
|
64
|
+
astrolabe (~> 1.3)
|
65
|
+
parser (>= 2.2.2.5, < 3.0)
|
66
|
+
powerpack (~> 0.1)
|
67
|
+
rainbow (>= 1.99.1, < 3.0)
|
68
|
+
ruby-progressbar (~> 1.4)
|
69
|
+
ruby-progressbar (1.7.5)
|
70
|
+
ruby-trello (1.3.0)
|
71
|
+
activemodel (>= 3.2.0)
|
72
|
+
addressable (~> 2.3)
|
73
|
+
json
|
74
|
+
oauth (~> 0.4.5)
|
75
|
+
rest-client (~> 1.8.0)
|
76
|
+
sawyer (0.6.0)
|
77
|
+
addressable (~> 2.3.5)
|
78
|
+
faraday (~> 0.8, < 0.10)
|
79
|
+
slop (3.6.0)
|
80
|
+
thread_safe (0.3.5)
|
81
|
+
tzinfo (1.2.2)
|
82
|
+
thread_safe (~> 0.1)
|
83
|
+
unf (0.1.4)
|
84
|
+
unf_ext
|
85
|
+
unf_ext (0.0.7.1)
|
86
|
+
|
87
|
+
PLATFORMS
|
88
|
+
ruby
|
89
|
+
|
90
|
+
DEPENDENCIES
|
91
|
+
bb-flow!
|
92
|
+
bundler (~> 1.10.6)
|
93
|
+
pry (~> 0.10.2)
|
94
|
+
rake (~> 10.4.2)
|
95
|
+
rubocop (~> 0.34.2)
|
96
|
+
|
97
|
+
BUNDLED WITH
|
98
|
+
1.10.6
|
data/README.md
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
A command line utility, which was born to simplify routines required by the BrightBites workflow.
|
2
|
+
|
3
|
+
## Installation
|
4
|
+
|
5
|
+
|
6
|
+
git clone git@github.com:brightbytes/bb-flow.git
|
7
|
+
cd bb-flow
|
8
|
+
# At this point choose the correct ruby version and gemset to install to, if you use RVM.
|
9
|
+
rake install
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Currently we support two commands:
|
14
|
+
|
15
|
+
bb-flow commit -m 'Commit message' # Create a commit.
|
16
|
+
git pr # Create a pull request.
|
17
|
+
|
18
|
+
## Roadmap
|
19
|
+
[Roadmap google doc](https://docs.google.com/document/d/1voXHU5H-M3YEwKKp1bGeYxGALNc6GB91fJ7eBfOMZ4k/edit)
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bb-flow.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'bb_flow/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'bb-flow'
|
8
|
+
spec.version = BBFlow::VERSION
|
9
|
+
spec.authors = ['gregolsen']
|
10
|
+
spec.email = ['mihailov.in@gmail.com']
|
11
|
+
spec.summary = 'Simplifies typical BrightBytes work flows'
|
12
|
+
spec.homepage = ''
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_dependency 'ansi-select', '~> 0.2.5'
|
21
|
+
spec.add_dependency 'git', '~> 1.2.9.1'
|
22
|
+
spec.add_dependency 'memoist', '~> 0.12.0'
|
23
|
+
spec.add_dependency 'ruby-trello', '~> 1.3.0'
|
24
|
+
spec.add_dependency 'colorize', '~> 0.7.7'
|
25
|
+
spec.add_dependency 'octokit', '~> 4.1.1'
|
26
|
+
|
27
|
+
spec.add_development_dependency 'bundler', '~> 1.10.6'
|
28
|
+
spec.add_development_dependency 'pry', '~> 0.10.2'
|
29
|
+
spec.add_development_dependency 'rake', '~> 10.4.2'
|
30
|
+
spec.add_development_dependency 'rubocop', '~> 0.34.2'
|
31
|
+
end
|
data/bin/bb-flow
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/bb-flow'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
subtext = <<HELP
|
7
|
+
Commands are:
|
8
|
+
commit : Commit changes and add comment to Trello cards
|
9
|
+
pr : Create a pull request
|
10
|
+
|
11
|
+
See 'bb-flow COMMAND --help' for more information on a specific command.
|
12
|
+
HELP
|
13
|
+
|
14
|
+
options = {}
|
15
|
+
|
16
|
+
global = OptionParser.new do |opts|
|
17
|
+
opts.banner = 'Usage: bb-flow [options] [command [options]]'
|
18
|
+
opts.separator ''
|
19
|
+
opts.separator subtext
|
20
|
+
opts.on_tail('-h', '--help', 'Show this message') do
|
21
|
+
puts opts
|
22
|
+
exit
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
subcommands = {
|
27
|
+
commit: OptionParser.new do |opts|
|
28
|
+
opts.banner = 'Usage: commit [options]'
|
29
|
+
opts.on('-m', '--message MESSAGE', 'Commit message') do |m|
|
30
|
+
options[:message] = m
|
31
|
+
end
|
32
|
+
end,
|
33
|
+
pr: OptionParser.new do |opts|
|
34
|
+
opts.banner = 'Usage: pr'
|
35
|
+
end
|
36
|
+
}
|
37
|
+
|
38
|
+
begin
|
39
|
+
global.order!
|
40
|
+
options[:command] = command = (ARGV.shift || '').to_sym
|
41
|
+
mandatory = {
|
42
|
+
commit: [],
|
43
|
+
pr: []
|
44
|
+
}
|
45
|
+
if !command || !mandatory[command]
|
46
|
+
puts global
|
47
|
+
exit
|
48
|
+
end
|
49
|
+
subcommands[command].order! if subcommands[command]
|
50
|
+
missing = mandatory[command].select { |param| options[param].nil? }
|
51
|
+
unless missing.empty?
|
52
|
+
puts "Missing options: #{missing.join(', ')}"
|
53
|
+
puts subcommands[command]
|
54
|
+
exit
|
55
|
+
end
|
56
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => exception
|
57
|
+
puts exception.message
|
58
|
+
exit(1)
|
59
|
+
end
|
60
|
+
|
61
|
+
if Gem::Platform.local.os == 'darwin' && !File.exist?(BBFlow::Persistent::Store.location(:global))
|
62
|
+
Thread.new { `say "Hi. It seems to be your first time running bb-flow. I hope you will like it. By the way, in this session I'll ask you more questions that usual: don't be scared, next time it will be a lot simpler."` }
|
63
|
+
end
|
64
|
+
|
65
|
+
BBFlow::Application.run(options)
|
data/bin/git-pr
ADDED
data/lib/bb-flow.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative './bb_flow'
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'octokit'
|
2
|
+
|
3
|
+
module BBFlow
|
4
|
+
class Github
|
5
|
+
class << self
|
6
|
+
extend BBFlow::Persistent
|
7
|
+
|
8
|
+
# @return [String]
|
9
|
+
def http_url
|
10
|
+
"https://github.com/#{repository_name}"
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [String]
|
14
|
+
persistent local def repository_url
|
15
|
+
remote.url
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [Sawyer::Resource]
|
19
|
+
def find_pull_request
|
20
|
+
octokit.pull_requests(repository_name).detect { |pr| pr.head.ref == branch_name }
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param [String] title
|
24
|
+
# @param [String] body
|
25
|
+
#
|
26
|
+
# @return [Sawyer::Resource] the newly created pull request.
|
27
|
+
def create_pull_request(title, body)
|
28
|
+
puts 'Creating a pull request...'
|
29
|
+
octokit.create_pull_request(repository_name, 'master', branch_name, title, body)
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Array<Array<String>>] Array<[login, name]>
|
33
|
+
persistent local def collaborators
|
34
|
+
octokit.collaborators(repository_name).map(&:login).sort.map(&octokit.method(:user)).map { |user| [user.login, user.name] }
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [Boolean]
|
38
|
+
def pushed?
|
39
|
+
branch_name
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [String]
|
43
|
+
def remote_name
|
44
|
+
remote.name
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# @return [Octokit::Client]
|
50
|
+
def octokit
|
51
|
+
@octokit ||= Octokit::Client.new(access_token: token)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [String]
|
55
|
+
def repository_name
|
56
|
+
# Match both git@github.com:shockone/bb-flow-test.git and https://github.com/shockone/bb-flow-test
|
57
|
+
repository_url[%r{github\.com[/:]([\w-]+/[\w-]+)(\.git|$)}, 1]
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [Git::Remote]
|
61
|
+
def remote
|
62
|
+
remotes = Misc.git.remotes.select { |remote| remote.url.include?('github') }
|
63
|
+
|
64
|
+
case remotes.size
|
65
|
+
when 0
|
66
|
+
Printer.panic("You don't have any remotes set up.")
|
67
|
+
when 1
|
68
|
+
remotes.first
|
69
|
+
else
|
70
|
+
Printer.select_item(
|
71
|
+
remotes,
|
72
|
+
'Which remote would you like to push to?',
|
73
|
+
formatter: ->(remote) { [remote.name, remote.url] }
|
74
|
+
)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [String]
|
79
|
+
def branch_name
|
80
|
+
rev = Misc.execute('git rev-parse --abbrev-ref --symbolic-full-name @{u}').strip
|
81
|
+
%r{(.*?)/(.*)}.match(rev)[2] unless rev.empty?
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [String]
|
85
|
+
persistent global def token
|
86
|
+
url = 'https://github.com/settings/tokens/new'
|
87
|
+
Printer.ask("Paste a Github token. You can generate it on #{url} (leave default scopes): ")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/bb_flow/misc.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'git'
|
2
|
+
|
3
|
+
module BBFlow
|
4
|
+
class Misc
|
5
|
+
class << self
|
6
|
+
extend Memoist
|
7
|
+
|
8
|
+
# @param [Array<Object>] args
|
9
|
+
#
|
10
|
+
# @return [String]
|
11
|
+
def execute(*args)
|
12
|
+
Open3.popen3(*args) { |_i, o| return o.read }
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param [String] text
|
16
|
+
#
|
17
|
+
# @return [String]
|
18
|
+
def edit(text = '')
|
19
|
+
file = Tempfile.new(%w(bb-flow .md))
|
20
|
+
file.write(text)
|
21
|
+
file.flush
|
22
|
+
|
23
|
+
system(*[*Misc.editor, file.path])
|
24
|
+
|
25
|
+
File.read(file.path)
|
26
|
+
ensure
|
27
|
+
file.close
|
28
|
+
file.unlink
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Git::Base]
|
32
|
+
memoize def git
|
33
|
+
Git.open(Dir.pwd)
|
34
|
+
rescue ArgumentError
|
35
|
+
Printer.panic("This directory (#{__dir__}) is not a git repository.")
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Array<String>] editor with arguments.
|
39
|
+
memoize def editor
|
40
|
+
[`git config --global core.editor`.chomp.split(/\s+/), ['vi']].reject(&:empty?).first
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'yaml/store'
|
2
|
+
|
3
|
+
module BBFlow
|
4
|
+
# A bit of meta programming that allows to replace
|
5
|
+
#
|
6
|
+
# def trello_key
|
7
|
+
# @trello_key ||= Config.global.transaction do |store|
|
8
|
+
# store['trello'] ||= {}
|
9
|
+
# store['trello']['key'] ||= Printer.ask('Go to https://trello.com/app-key and paste Key: ')
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# with
|
14
|
+
#
|
15
|
+
# class Trello
|
16
|
+
# extend Persistent
|
17
|
+
#
|
18
|
+
# persistent global def key
|
19
|
+
# Printer.ask('Go to https://trello.com/app-key and paste Key: ')
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# The method with be memoized both within and across executions.
|
24
|
+
module Persistent
|
25
|
+
# @param [Array<Config, Symbol>] args
|
26
|
+
def persistent(args)
|
27
|
+
store, method_name = args
|
28
|
+
|
29
|
+
module_with_persistent_method = Module.new do
|
30
|
+
define_method(method_name) do
|
31
|
+
prefix = name.gsub(/^.*::/, '').downcase
|
32
|
+
instance_variable_name = "@#{method_name}_memo"
|
33
|
+
|
34
|
+
accessor = proc do |hash|
|
35
|
+
hash[prefix] ||= {}
|
36
|
+
hash[prefix][method_name.to_s] ||= super()
|
37
|
+
end
|
38
|
+
|
39
|
+
instance_variable_get(instance_variable_name) ||
|
40
|
+
instance_variable_set(instance_variable_name, (store.instance_variable_get('@lock').locked? ? accessor.call(store) : store.transaction(&accessor)))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
prepend module_with_persistent_method
|
45
|
+
end
|
46
|
+
|
47
|
+
# @!method global(method_name)
|
48
|
+
# @!method local(method_name)
|
49
|
+
# @param [Symbol] method_name
|
50
|
+
# @note intended to be used with #persistent
|
51
|
+
#
|
52
|
+
# @return [Array<Config, Symbol>]
|
53
|
+
%i(global local).each do |store_helper|
|
54
|
+
define_method(store_helper) do |method_name|
|
55
|
+
[Store.get(store_helper), method_name]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Store
|
60
|
+
FILE_NAME = '.bb_flow'
|
61
|
+
DIRS = { global: ENV['HOME'], local: Dir.pwd }
|
62
|
+
|
63
|
+
# @param [Symbol] name
|
64
|
+
#
|
65
|
+
# @return [YAML::Store]
|
66
|
+
def self.get(name)
|
67
|
+
@memo ||= {}
|
68
|
+
@memo[name] ||= YAML::Store.new(location(name))
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.location(storage_type)
|
72
|
+
fail "Unknown store: #{name}" unless DIRS.keys.include?(storage_type)
|
73
|
+
|
74
|
+
File.join(DIRS[storage_type], FILE_NAME)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module BBFlow
|
2
|
+
class Printer
|
3
|
+
class << self
|
4
|
+
# @param [String] question
|
5
|
+
#
|
6
|
+
# @return [String] answer
|
7
|
+
def ask(question)
|
8
|
+
puts
|
9
|
+
note(question)
|
10
|
+
print('> ')
|
11
|
+
gets.strip
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [String] message
|
15
|
+
#
|
16
|
+
# @return [void]
|
17
|
+
def panic(message)
|
18
|
+
error(message)
|
19
|
+
abort
|
20
|
+
end
|
21
|
+
|
22
|
+
# @param [String] message
|
23
|
+
#
|
24
|
+
# @return [void]
|
25
|
+
def error(message)
|
26
|
+
puts("[ERROR] #{message}".colorize(:red))
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param [String] message
|
30
|
+
#
|
31
|
+
# @return [void]
|
32
|
+
def success(message)
|
33
|
+
puts(message.colorize(:green))
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param [String] message
|
37
|
+
#
|
38
|
+
# @return [void]
|
39
|
+
def note(message)
|
40
|
+
puts(message.colorize(:yellow))
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param [String] message
|
44
|
+
#
|
45
|
+
# @return [void]
|
46
|
+
def auxiliary(message)
|
47
|
+
puts(message.colorize(:light_black))
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param [Array<#name>] items
|
51
|
+
# @param [String] title
|
52
|
+
#
|
53
|
+
# @return [#name]
|
54
|
+
def select_item(items, title, formatter: :name.to_proc)
|
55
|
+
puts
|
56
|
+
note(title)
|
57
|
+
auxiliary('Move: arrows or j/k Finish: return')
|
58
|
+
Ansi::Selector.select(items, formatter: formatter)
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param [Array<#name>] items
|
62
|
+
# @param [String] title
|
63
|
+
#
|
64
|
+
# @return [Array<#name>]
|
65
|
+
def select_items(items, title, formatter: :name.to_proc, preselected: [])
|
66
|
+
puts
|
67
|
+
note(title)
|
68
|
+
# TODO: Remove after implementing it in ansi-select.
|
69
|
+
auxiliary('Move: arrows or j/k Toggle: space Finish: return')
|
70
|
+
Ansi::Selector.multi_select(items, formatter: formatter, preselected: preselected)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module BBFlow
|
4
|
+
class PullRequester
|
5
|
+
class << self
|
6
|
+
extend Memoist
|
7
|
+
|
8
|
+
AD = "\n\n<sub>This pull request has been created using [bb-flow](https://github.com/brightbytes/bb-flow).</sub>"
|
9
|
+
|
10
|
+
# @return [void]
|
11
|
+
def create!
|
12
|
+
ensure_pushed!
|
13
|
+
|
14
|
+
existing_pull_request = Github.find_pull_request
|
15
|
+
|
16
|
+
if existing_pull_request
|
17
|
+
Printer.success("A pull request for this branch already exists.\n#{existing_pull_request.html_url}")
|
18
|
+
else
|
19
|
+
response = Github.create_pull_request(title, Misc.edit(body_template) + AD)
|
20
|
+
|
21
|
+
unless cards.empty?
|
22
|
+
add_link_to_cards(response.html_url)
|
23
|
+
move_cards_to_done_list
|
24
|
+
end
|
25
|
+
|
26
|
+
Printer.success('The pull request has been created, sir.')
|
27
|
+
puts response.html_url
|
28
|
+
end
|
29
|
+
rescue Octokit::Error => exception
|
30
|
+
Printer.error(exception.message)
|
31
|
+
end
|
32
|
+
|
33
|
+
def ensure_pushed!
|
34
|
+
unless Github.pushed?
|
35
|
+
yes = 'Yes, please'
|
36
|
+
|
37
|
+
case Printer.select_item(
|
38
|
+
[yes, 'No, thank you'],
|
39
|
+
"The branch isn't pushed yet. Would you like me to push it for you?",
|
40
|
+
formatter: :to_s.to_proc
|
41
|
+
)
|
42
|
+
when yes
|
43
|
+
Misc.execute("git push --set-upstream #{Github.remote_name} #{Misc.git.current_branch}")
|
44
|
+
else
|
45
|
+
puts 'Exiting...'
|
46
|
+
exit
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [void]
|
52
|
+
def move_cards_to_done_list
|
53
|
+
puts "Moving cards to the „#{Trello.done_list.name}“ list..."
|
54
|
+
cards.each { |card| card.move_to_list(Trello.done_list) }
|
55
|
+
end
|
56
|
+
|
57
|
+
# @param [String] url
|
58
|
+
def add_link_to_cards(url)
|
59
|
+
puts 'Adding the pull request link to the trello cards...'
|
60
|
+
|
61
|
+
cards.each do |card|
|
62
|
+
next if card.desc.include?(url)
|
63
|
+
|
64
|
+
card.desc = "[Pull Request](#{url})\n\n#{card.desc}"
|
65
|
+
card.save
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# @return [String]
|
72
|
+
def title
|
73
|
+
if cards.size == 1
|
74
|
+
cards.first.name
|
75
|
+
else
|
76
|
+
Printer.ask('Title:')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# @return [String]
|
81
|
+
def body_template
|
82
|
+
reviewers_md = reviewers.map { |login, _name| "- [ ] @#{login}" }
|
83
|
+
card_links = cards.map { |card| " [#{card.name}](#{card.url})" }
|
84
|
+
|
85
|
+
body_parts = []
|
86
|
+
body_parts << reviewers_md.join("\n") unless reviewers_md.empty?
|
87
|
+
|
88
|
+
unless card_links.empty?
|
89
|
+
body_parts << '###### Trello'
|
90
|
+
body_parts << card_links.join("\n")
|
91
|
+
end
|
92
|
+
|
93
|
+
body_parts << '###### Changes'
|
94
|
+
body_parts << commits.map(&:message).map(&:lines).map(&:first).map { |message| " * #{message}" }.join
|
95
|
+
|
96
|
+
body_parts.join("\n\n")
|
97
|
+
end
|
98
|
+
|
99
|
+
# @return [Array<String>] [login, name]
|
100
|
+
def reviewers
|
101
|
+
previous_reviewers = Persistent::Store.get(:local).transaction(true) { |store| store['previous_reviewers'] } || []
|
102
|
+
|
103
|
+
reviewers = Printer.select_items(
|
104
|
+
Github.collaborators,
|
105
|
+
'Who will be reviewing the pull request?',
|
106
|
+
formatter: proc { |login, name| [login, name] },
|
107
|
+
preselected: Github.collaborators.map { |user| previous_reviewers.include?(user) }
|
108
|
+
)
|
109
|
+
|
110
|
+
Persistent::Store.get(:local).transaction { |store| store['previous_reviewers'] = reviewers }
|
111
|
+
end
|
112
|
+
|
113
|
+
# @return [Array<Trello::Card>]
|
114
|
+
memoize def cards
|
115
|
+
return [] if Trello.cards.blank?
|
116
|
+
|
117
|
+
Printer.select_items(
|
118
|
+
Trello.cards,
|
119
|
+
'Which cards does it implement?',
|
120
|
+
formatter: ->(card) { [card.name, card.short_url] },
|
121
|
+
preselected: Trello.cards.map { |card| commits.any? { |commit| commit.message.include?(card.short_url) } }
|
122
|
+
)
|
123
|
+
end
|
124
|
+
|
125
|
+
# @return [Array<Git::Object::Commit>]
|
126
|
+
memoize def commits
|
127
|
+
Misc.git.log.between('master', 'HEAD').to_a
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module BBFlow
|
2
|
+
class Trello
|
3
|
+
class << self
|
4
|
+
extend BBFlow::Persistent
|
5
|
+
extend Memoist
|
6
|
+
|
7
|
+
# @return [Array<Trello::Card>]
|
8
|
+
memoize def cards
|
9
|
+
puts 'Fetching Trello cards...'
|
10
|
+
lists.flat_map { |list| list.cards.target }.select { |card| card.member_ids.include?(member.id) }
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Trello::List]
|
14
|
+
memoize def done_list
|
15
|
+
trello::List.find(done_list_id)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
# @return [Trello::Board]
|
21
|
+
memoize def board
|
22
|
+
trello::Board.find(board_id)
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Array<Trello::List>]
|
26
|
+
memoize def lists
|
27
|
+
list_ids.map { |list| trello::List.find(list) }
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Trello::Member]
|
31
|
+
memoize def member
|
32
|
+
trello::Member.find('me')
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Class]
|
36
|
+
memoize def trello
|
37
|
+
::Trello.configure do |config|
|
38
|
+
config.developer_public_key = key
|
39
|
+
config.member_token = member_token
|
40
|
+
end
|
41
|
+
|
42
|
+
::Trello
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [String]
|
46
|
+
persistent global def key
|
47
|
+
Printer.ask('Go to https://trello.com/app-key and paste Key: ')
|
48
|
+
end
|
49
|
+
|
50
|
+
# @return [String]
|
51
|
+
persistent global def member_token
|
52
|
+
authorization_url = "http://trello.com/1/authorize?key=#{key}&response_type=token&expiration=never&scope=read,write"
|
53
|
+
Printer.ask("Go to #{authorization_url}, click 'Allow', and paste Token")
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [String]
|
57
|
+
persistent local def board_id
|
58
|
+
Printer.select_item(trello::Board.all, 'Which Trello board do you work with?', formatter: ->(board) { [board.name, board.url] }).id
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [Array<String>]
|
62
|
+
persistent local def list_ids
|
63
|
+
Printer.select_items(board.lists, 'Which lists contain not finished - „in progress“ - cards?').map(&:id)
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [String]
|
67
|
+
persistent local def done_list_id
|
68
|
+
Printer.select_item(board.lists, 'Where should I move finished cards?').id
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module BBFlow
|
2
|
+
# Current major release.
|
3
|
+
# @return [String]
|
4
|
+
MAJOR = 0
|
5
|
+
|
6
|
+
# Current minor release.
|
7
|
+
# @return [String]
|
8
|
+
MINOR = 1
|
9
|
+
|
10
|
+
# Current patch level.
|
11
|
+
# @return [String]
|
12
|
+
PATCH = 0
|
13
|
+
|
14
|
+
# Full release version.
|
15
|
+
# @return [String]
|
16
|
+
VERSION = [MAJOR, MINOR, PATCH].join('.').freeze
|
17
|
+
end
|
data/lib/bb_flow.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
# Standard
|
4
|
+
require 'open3'
|
5
|
+
|
6
|
+
# Installed
|
7
|
+
require 'ansi/selector'
|
8
|
+
require 'colorize'
|
9
|
+
require 'memoist'
|
10
|
+
require 'readline'
|
11
|
+
require 'trello'
|
12
|
+
require 'yaml'
|
13
|
+
|
14
|
+
# Internal
|
15
|
+
require_relative 'bb_flow/persistent'
|
16
|
+
require_relative 'bb_flow/version'
|
17
|
+
require_relative 'bb_flow/printer'
|
18
|
+
require_relative 'bb_flow/misc'
|
19
|
+
require_relative 'bb_flow/pull_requester'
|
20
|
+
require_relative 'bb_flow/trello'
|
21
|
+
require_relative 'bb_flow/github'
|
22
|
+
|
23
|
+
module BBFlow
|
24
|
+
class Application
|
25
|
+
# @param [Hash] options
|
26
|
+
#
|
27
|
+
# @return [void]
|
28
|
+
def self.run(options)
|
29
|
+
case options[:command]
|
30
|
+
when :commit
|
31
|
+
return puts Misc.execute('git commit') if Misc.execute('git diff-index HEAD --name-only --cached').empty?
|
32
|
+
|
33
|
+
message = options[:message] || Misc.edit
|
34
|
+
cards = Trello.cards
|
35
|
+
cards = Printer.select_items(cards, 'Which cards does this commit address?') unless cards.blank?
|
36
|
+
card_lines = cards.map do |card|
|
37
|
+
"Trello Card: #{card.name}, #{card.short_url}"
|
38
|
+
end.join("\n")
|
39
|
+
# TODO: re-write with plumbing commands
|
40
|
+
# e.g. git write-tree | xargs git commit-tree -m'message'
|
41
|
+
Misc.execute("git commit -m \"#{message}\n\n#{card_lines}\"")
|
42
|
+
commit_sha = Misc.execute('git show-ref --head -s /HEAD/')
|
43
|
+
# TODO: convert to http url.
|
44
|
+
commit_url = "#{Github.http_url}/commit/#{commit_sha}"
|
45
|
+
|
46
|
+
cards.each do |card|
|
47
|
+
card.add_comment("Commit: #{options[:message]}\n#{commit_url}")
|
48
|
+
Printer.success("Added a comment to the „#{card.name}“ card (#{card.url}).")
|
49
|
+
end
|
50
|
+
when :pr
|
51
|
+
PullRequester.create!
|
52
|
+
else
|
53
|
+
Printer.panic("Incorrect parameter: #{options[:command]}")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
metadata
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bb-flow
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- gregolsen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ansi-select
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.2.5
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.2.5
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: git
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.2.9.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.2.9.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: memoist
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.12.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.12.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: ruby-trello
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.3.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.3.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: colorize
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.7.7
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.7.7
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: octokit
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 4.1.1
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 4.1.1
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: bundler
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.10.6
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.10.6
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: pry
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.10.2
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.10.2
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rake
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 10.4.2
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 10.4.2
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rubocop
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 0.34.2
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 0.34.2
|
153
|
+
description:
|
154
|
+
email:
|
155
|
+
- mihailov.in@gmail.com
|
156
|
+
executables:
|
157
|
+
- bb-flow
|
158
|
+
- git-pr
|
159
|
+
extensions: []
|
160
|
+
extra_rdoc_files: []
|
161
|
+
files:
|
162
|
+
- ".gitignore"
|
163
|
+
- ".rubocop.yml"
|
164
|
+
- ".rubocop_todo.yml"
|
165
|
+
- Gemfile
|
166
|
+
- Gemfile.lock
|
167
|
+
- README.md
|
168
|
+
- Rakefile
|
169
|
+
- bb-flow.gemspec
|
170
|
+
- bin/bb-flow
|
171
|
+
- bin/git-pr
|
172
|
+
- lib/bb-flow.rb
|
173
|
+
- lib/bb_flow.rb
|
174
|
+
- lib/bb_flow/github.rb
|
175
|
+
- lib/bb_flow/misc.rb
|
176
|
+
- lib/bb_flow/persistent.rb
|
177
|
+
- lib/bb_flow/printer.rb
|
178
|
+
- lib/bb_flow/pull_requester.rb
|
179
|
+
- lib/bb_flow/trello.rb
|
180
|
+
- lib/bb_flow/version.rb
|
181
|
+
homepage: ''
|
182
|
+
licenses:
|
183
|
+
- MIT
|
184
|
+
metadata: {}
|
185
|
+
post_install_message:
|
186
|
+
rdoc_options: []
|
187
|
+
require_paths:
|
188
|
+
- lib
|
189
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
190
|
+
requirements:
|
191
|
+
- - ">="
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
version: '0'
|
194
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
195
|
+
requirements:
|
196
|
+
- - ">="
|
197
|
+
- !ruby/object:Gem::Version
|
198
|
+
version: '0'
|
199
|
+
requirements: []
|
200
|
+
rubyforge_project:
|
201
|
+
rubygems_version: 2.4.8
|
202
|
+
signing_key:
|
203
|
+
specification_version: 4
|
204
|
+
summary: Simplifies typical BrightBytes work flows
|
205
|
+
test_files: []
|