cp8_cli 4.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +19 -0
- data/Rakefile +11 -0
- data/cp8_cli.gemspec +42 -0
- data/exe/cp8 +34 -0
- data/exe/git-cleanup +7 -0
- data/exe/git-finish +19 -0
- data/exe/git-open +7 -0
- data/exe/git-start +7 -0
- data/lib/cp8_cli.rb +9 -0
- data/lib/cp8_cli/branch.rb +61 -0
- data/lib/cp8_cli/branch_name.rb +30 -0
- data/lib/cp8_cli/cleanup.rb +36 -0
- data/lib/cp8_cli/command.rb +61 -0
- data/lib/cp8_cli/config_store.rb +32 -0
- data/lib/cp8_cli/current_user.rb +29 -0
- data/lib/cp8_cli/github/base.rb +13 -0
- data/lib/cp8_cli/github/issue.rb +92 -0
- data/lib/cp8_cli/github/parsed_short_link.rb +25 -0
- data/lib/cp8_cli/github/parsed_url.rb +25 -0
- data/lib/cp8_cli/global_config.rb +51 -0
- data/lib/cp8_cli/local_config.rb +29 -0
- data/lib/cp8_cli/main.rb +74 -0
- data/lib/cp8_cli/pull_request.rb +63 -0
- data/lib/cp8_cli/repo.rb +21 -0
- data/lib/cp8_cli/story_query.rb +23 -0
- data/lib/cp8_cli/table.rb +38 -0
- data/lib/cp8_cli/table/row.rb +45 -0
- data/lib/cp8_cli/trello/base.rb +36 -0
- data/lib/cp8_cli/trello/board.rb +13 -0
- data/lib/cp8_cli/trello/card.rb +71 -0
- data/lib/cp8_cli/trello/error.rb +5 -0
- data/lib/cp8_cli/trello/error_handler.rb +15 -0
- data/lib/cp8_cli/trello/json_parser.rb +13 -0
- data/lib/cp8_cli/trello/label.rb +15 -0
- data/lib/cp8_cli/trello/list.rb +32 -0
- data/lib/cp8_cli/trello/member.rb +11 -0
- data/lib/cp8_cli/version.rb +27 -0
- metadata +357 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
module Cp8Cli
|
2
|
+
class ConfigStore
|
3
|
+
def initialize(path)
|
4
|
+
@path = path
|
5
|
+
end
|
6
|
+
|
7
|
+
def [](key)
|
8
|
+
data[key]
|
9
|
+
end
|
10
|
+
|
11
|
+
def save(key, value)
|
12
|
+
data[key] = value
|
13
|
+
File.new(path, "w") unless File.exists?(path)
|
14
|
+
File.open(path, "w") { |f| f.write(data.to_yaml) }
|
15
|
+
value
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :path
|
21
|
+
|
22
|
+
def data
|
23
|
+
@_data ||= load_data
|
24
|
+
end
|
25
|
+
|
26
|
+
def load_data
|
27
|
+
YAML.load File.read(path)
|
28
|
+
rescue
|
29
|
+
{}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Cp8Cli
|
2
|
+
class CurrentUser
|
3
|
+
def initials
|
4
|
+
git_user_name.parameterize(separator: " ").split.map(&:first).join
|
5
|
+
end
|
6
|
+
|
7
|
+
def trello_id
|
8
|
+
trello_user.id
|
9
|
+
end
|
10
|
+
|
11
|
+
def github_login
|
12
|
+
github_user.login
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def git_user_name
|
18
|
+
@_git_user_name ||= Command.read("git config user.name")
|
19
|
+
end
|
20
|
+
|
21
|
+
def github_user
|
22
|
+
@_github_user ||= Github::Base.client.user
|
23
|
+
end
|
24
|
+
|
25
|
+
def trello_user
|
26
|
+
@_trello_user ||= Trello::Member.current
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require "cp8_cli/github/base"
|
2
|
+
require "cp8_cli/github/parsed_url"
|
3
|
+
require "cp8_cli/github/parsed_short_link"
|
4
|
+
|
5
|
+
module Cp8Cli
|
6
|
+
module Github
|
7
|
+
class Issue < Base
|
8
|
+
def initialize(number:, repo:, attributes:)
|
9
|
+
@number = number
|
10
|
+
@repo = repo
|
11
|
+
@attributes = attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.fields
|
15
|
+
[:title]
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.find_by_url(url)
|
19
|
+
url = ParsedUrl.new(url)
|
20
|
+
issue = client.issue(url.repo, url.number)
|
21
|
+
new number: url.number, repo: url.repo, attributes: issue
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.find_by_short_link(short_link)
|
25
|
+
short_link = ParsedShortLink.new(short_link)
|
26
|
+
issue = client.issue(short_link.repo, short_link.number)
|
27
|
+
new number: short_link.number, repo: short_link.repo, attributes: issue
|
28
|
+
end
|
29
|
+
|
30
|
+
def title
|
31
|
+
attributes[:title]
|
32
|
+
end
|
33
|
+
|
34
|
+
def pr_title
|
35
|
+
title
|
36
|
+
end
|
37
|
+
|
38
|
+
def url
|
39
|
+
attributes[:html_url]
|
40
|
+
end
|
41
|
+
|
42
|
+
def summary
|
43
|
+
"Closes #{short_link}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def start
|
47
|
+
# noop for now
|
48
|
+
end
|
49
|
+
|
50
|
+
def finish
|
51
|
+
# noop for now
|
52
|
+
end
|
53
|
+
|
54
|
+
def accept
|
55
|
+
# noop for now
|
56
|
+
end
|
57
|
+
|
58
|
+
def assign(user)
|
59
|
+
# add_assignes not released as gem yet https://github.com/octokit/octokit.rb/pull/894
|
60
|
+
client.post "#{Octokit::Repository.path repo}/issues/#{number}/assignees", assignees: [user.github_login]
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_label(label)
|
64
|
+
self.class.request(:post, "cards/#{id}/idLabels", value: label.id)
|
65
|
+
end
|
66
|
+
|
67
|
+
def attach(url:)
|
68
|
+
self.class.request(:post, "cards/#{id}/attachments", url: url)
|
69
|
+
end
|
70
|
+
|
71
|
+
def short_link
|
72
|
+
"#{repo}##{number}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def short_url
|
76
|
+
attributes[:shortUrl]
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
attr_reader :number, :repo, :attributes
|
82
|
+
|
83
|
+
def move_to(list)
|
84
|
+
self.class.with("cards/:id/idList").where(id: id, value: list.id).put
|
85
|
+
end
|
86
|
+
|
87
|
+
def member_ids
|
88
|
+
attributes["idMembers"] || []
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Cp8Cli
|
2
|
+
module Github
|
3
|
+
class ParsedShortLink
|
4
|
+
def initialize(short_link)
|
5
|
+
@short_link = short_link
|
6
|
+
end
|
7
|
+
|
8
|
+
def number
|
9
|
+
parts.last
|
10
|
+
end
|
11
|
+
|
12
|
+
def repo
|
13
|
+
parts.first
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_accessor :short_link
|
19
|
+
|
20
|
+
def parts
|
21
|
+
short_link.split("#")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Cp8Cli
|
2
|
+
module Github
|
3
|
+
class ParsedUrl
|
4
|
+
def initialize(url)
|
5
|
+
@url = url
|
6
|
+
end
|
7
|
+
|
8
|
+
def number
|
9
|
+
parts.last
|
10
|
+
end
|
11
|
+
|
12
|
+
def repo
|
13
|
+
parts[3, 2].join("/")
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_accessor :url
|
19
|
+
|
20
|
+
def parts
|
21
|
+
url.split("/")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "cp8_cli/config_store"
|
2
|
+
require "cp8_cli/trello/base"
|
3
|
+
|
4
|
+
module Cp8Cli
|
5
|
+
class GlobalConfig
|
6
|
+
PATH = ENV["HOME"] + "/.trello_flow"
|
7
|
+
|
8
|
+
def initialize(store = nil)
|
9
|
+
@store = store || ConfigStore.new(PATH)
|
10
|
+
end
|
11
|
+
|
12
|
+
def trello_key
|
13
|
+
@_trello_key ||= store[:key] || configure_trello_key
|
14
|
+
end
|
15
|
+
|
16
|
+
def trello_token
|
17
|
+
@_trello_token ||= store[:token] || configure_trello_token
|
18
|
+
end
|
19
|
+
|
20
|
+
def github_token
|
21
|
+
@_github_token ||= store[:github_token] || env_github_token || configure_github_token
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :store
|
27
|
+
|
28
|
+
def configure_trello_key
|
29
|
+
Command.ask "Press enter to setup Trello for this project (will open public key url)"
|
30
|
+
Command.open_url "https://trello.com/app-key"
|
31
|
+
store.save :key, Command.ask("Input Developer API key")
|
32
|
+
end
|
33
|
+
|
34
|
+
def configure_trello_token
|
35
|
+
Command.open_url trello_authorize_url
|
36
|
+
store.save :token, Command.ask("Input member token")
|
37
|
+
end
|
38
|
+
|
39
|
+
def env_github_token
|
40
|
+
ENV["OCTOKIT_ACCESS_TOKEN"]
|
41
|
+
end
|
42
|
+
|
43
|
+
def configure_github_token
|
44
|
+
store.save :github_token, Command.ask("Input GitHub token")
|
45
|
+
end
|
46
|
+
|
47
|
+
def trello_authorize_url
|
48
|
+
"https://trello.com/1/authorize?key=#{trello_key}&name=Trello-Flow&scope=read,write,account&expiration=never&response_type=token"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Cp8Cli
|
2
|
+
class LocalConfig
|
3
|
+
PATH = ".trello_flow"
|
4
|
+
|
5
|
+
def initialize(store = nil)
|
6
|
+
@store = store || ConfigStore.new(PATH)
|
7
|
+
end
|
8
|
+
|
9
|
+
def board
|
10
|
+
@_board ||= Trello::Board.find(board_id)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_reader :store
|
16
|
+
|
17
|
+
def board_id
|
18
|
+
@_board_id ||= store[:board_id] || configure_board_id
|
19
|
+
end
|
20
|
+
|
21
|
+
def configure_board_id
|
22
|
+
store.save :board_id, Table.pick(trello_user.boards.active).id
|
23
|
+
end
|
24
|
+
|
25
|
+
def trello_user
|
26
|
+
Trello::Member.current
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/cp8_cli/main.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require "cp8_cli/version"
|
2
|
+
require "cp8_cli/local_config"
|
3
|
+
require "cp8_cli/global_config"
|
4
|
+
require "cp8_cli/github/issue"
|
5
|
+
require "cp8_cli/current_user"
|
6
|
+
|
7
|
+
module Cp8Cli
|
8
|
+
class Main
|
9
|
+
def initialize(global_config = GlobalConfig.new, local_config = LocalConfig.new)
|
10
|
+
Trello::Base.configure(key: global_config.trello_key, token: global_config.trello_token)
|
11
|
+
Github::Base.configure(token: global_config.github_token)
|
12
|
+
@local_config = local_config
|
13
|
+
end
|
14
|
+
|
15
|
+
def start(name)
|
16
|
+
Command.error "Your `cp8_cli` version is out of date. Please run `gem update cp8_cli`." unless Version.latest?
|
17
|
+
story = create_or_pick_story(name)
|
18
|
+
story.assign(current_user)
|
19
|
+
story.start
|
20
|
+
Branch.from_story(user: current_user, story: story).checkout
|
21
|
+
rescue Trello::Error => error
|
22
|
+
Command.error(error.message)
|
23
|
+
end
|
24
|
+
|
25
|
+
def open
|
26
|
+
Branch.current.open_story_in_browser
|
27
|
+
end
|
28
|
+
|
29
|
+
def finish(options = {})
|
30
|
+
branch = Branch.current
|
31
|
+
branch.push
|
32
|
+
branch.open_pull_request(options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def cleanup
|
36
|
+
Cleanup.new(Branch.current.target).run
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
attr_reader :local_config
|
42
|
+
|
43
|
+
def board
|
44
|
+
@_board ||= local_config.board
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_or_pick_story(name)
|
48
|
+
if name.to_s.start_with?("https://github.com")
|
49
|
+
Github::Issue.find_by_url(name)
|
50
|
+
elsif name.to_s.start_with?("http")
|
51
|
+
Trello::Card.find_by_url(name)
|
52
|
+
elsif name.present?
|
53
|
+
create_new_card(name)
|
54
|
+
else
|
55
|
+
pick_existing_card
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_new_card(name)
|
60
|
+
label = Table.pick board.labels, caption: "Add label:"
|
61
|
+
card = board.lists.backlog.cards.create name: name
|
62
|
+
card.add_label(label) if label
|
63
|
+
card
|
64
|
+
end
|
65
|
+
|
66
|
+
def pick_existing_card
|
67
|
+
Table.pick board.lists.backlog.cards
|
68
|
+
end
|
69
|
+
|
70
|
+
def current_user
|
71
|
+
@_current_user ||= CurrentUser.new
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require "cp8_cli/repo"
|
2
|
+
|
3
|
+
module Cp8Cli
|
4
|
+
class PullRequest
|
5
|
+
def initialize(from:, target:, story: nil, **options)
|
6
|
+
@story = story
|
7
|
+
@from = from
|
8
|
+
@target = target
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def open
|
13
|
+
Command.open_url url
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :story, :from, :target, :options
|
19
|
+
|
20
|
+
def url
|
21
|
+
repo.url + "/compare/#{target}...#{escape from}?expand=1&title=#{escape title_with_prefixes}&body=#{escape body}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def title
|
25
|
+
return unless story
|
26
|
+
story.pr_title
|
27
|
+
end
|
28
|
+
|
29
|
+
def body
|
30
|
+
return unless story
|
31
|
+
body = story.summary
|
32
|
+
body << release_note unless release_branch?
|
33
|
+
body
|
34
|
+
end
|
35
|
+
|
36
|
+
def release_note
|
37
|
+
"\n\n_Release note: #{story.title}_"
|
38
|
+
end
|
39
|
+
|
40
|
+
def prefixes
|
41
|
+
prefixes = []
|
42
|
+
prefixes << "[WIP]" if options[:wip]
|
43
|
+
prefixes << "[#{target.titleize}]" if release_branch?
|
44
|
+
prefixes.join(" ")
|
45
|
+
end
|
46
|
+
|
47
|
+
def release_branch?
|
48
|
+
target != "master"
|
49
|
+
end
|
50
|
+
|
51
|
+
def title_with_prefixes
|
52
|
+
"#{prefixes} #{title}".strip
|
53
|
+
end
|
54
|
+
|
55
|
+
def escape(text)
|
56
|
+
URI.escape text.to_s.strip
|
57
|
+
end
|
58
|
+
|
59
|
+
def repo
|
60
|
+
@_repo ||= Repo.new
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/cp8_cli/repo.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Cp8Cli
|
2
|
+
class Repo
|
3
|
+
def user
|
4
|
+
path.split('/').first
|
5
|
+
end
|
6
|
+
|
7
|
+
def name
|
8
|
+
path.split('/').last
|
9
|
+
end
|
10
|
+
|
11
|
+
def url
|
12
|
+
"https://github.com/#{user}/#{name}"
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def path
|
18
|
+
@_path ||= Command.read("git config --get remote.origin.url").match(/github.com[:\/](\S+\/\S+)\.git/)[1]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|