trellohub 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/.travis.yml +14 -0
- data/Gemfile +9 -0
- data/README.md +33 -7
- data/bin/trellohub +7 -0
- data/lib/trellohub.rb +10 -3
- data/lib/trellohub/card.rb +8 -79
- data/lib/trellohub/configurable.rb +75 -36
- data/lib/trellohub/core_ext/array.rb +10 -0
- data/lib/trellohub/core_ext/hash.rb +9 -1
- data/lib/trellohub/core_ext/string.rb +22 -0
- data/lib/trellohub/form.rb +142 -0
- data/lib/trellohub/form/card.rb +163 -0
- data/lib/trellohub/form/issue.rb +191 -0
- data/lib/trellohub/list.rb +18 -2
- data/lib/trellohub/member.rb +14 -2
- data/lib/trellohub/mocking.rb +87 -0
- data/lib/trellohub/repository.rb +47 -0
- data/lib/trellohub/synchronal.rb +22 -41
- data/lib/trellohub/version.rb +1 -1
- data/spec/helper.rb +59 -0
- data/spec/trellohub_spec.rb +51 -0
- metadata +20 -7
- data/lib/trellohub/cards.rb +0 -58
- data/lib/trellohub/lists.rb +0 -17
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'trellohub/form/card'
|
2
|
+
require 'trellohub/form/issue'
|
3
|
+
|
4
|
+
module Trellohub
|
5
|
+
class Form
|
6
|
+
include Form::Card
|
7
|
+
include Form::Issue
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def common_attributes
|
11
|
+
%i(
|
12
|
+
key
|
13
|
+
state
|
14
|
+
imported_from
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def origin_attributes
|
19
|
+
%i(
|
20
|
+
origin_issue
|
21
|
+
origin_card
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def array_ext
|
26
|
+
<<-METHODS
|
27
|
+
def find_by_key(key)
|
28
|
+
self.find { |form| form.key == key }
|
29
|
+
end
|
30
|
+
METHODS
|
31
|
+
end
|
32
|
+
|
33
|
+
def with_issues
|
34
|
+
@issues ||= self.with_issues!
|
35
|
+
end
|
36
|
+
|
37
|
+
def with_issues!
|
38
|
+
array = Trellohub.repositories.each.with_object([]) do |repo, forms|
|
39
|
+
forms.concat with_issues_on(repo)
|
40
|
+
end
|
41
|
+
array.instance_eval array_ext
|
42
|
+
array
|
43
|
+
end
|
44
|
+
|
45
|
+
def with_issues_on(repo)
|
46
|
+
repo.issues.each.with_object([]) do |issue, forms|
|
47
|
+
form = Trellohub::Form.new
|
48
|
+
form.import_issue repo.full_name, issue
|
49
|
+
forms << form
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def with_cards
|
54
|
+
@cards ||= self.with_cards!
|
55
|
+
end
|
56
|
+
|
57
|
+
def with_cards!
|
58
|
+
array = Trellohub::Card.all.
|
59
|
+
each.with_object([]) do |card, forms|
|
60
|
+
form = Trellohub::Form.new
|
61
|
+
form.import_card card
|
62
|
+
forms << form
|
63
|
+
end
|
64
|
+
array.instance_eval array_ext
|
65
|
+
array
|
66
|
+
end
|
67
|
+
|
68
|
+
def compare(base, target)
|
69
|
+
return unless base.updated_at < target.updated_at
|
70
|
+
type = target.imported_from
|
71
|
+
|
72
|
+
printings = [[
|
73
|
+
"#{type} attribute",
|
74
|
+
"#{'base'.green} (#{base.imported_from}: #{base.own_key}, #{base.updated_at})",
|
75
|
+
"#{'comparison'.yellow} (#{target.imported_from}: #{target.own_key}, #{target.updated_at})"
|
76
|
+
]] if Trellohub.debug
|
77
|
+
|
78
|
+
diff = target.send(:"to_valid_#{type}").each.with_object({}) do |(key, value), hash|
|
79
|
+
base_value = base.send(:"to_valid_#{type}")[key]
|
80
|
+
hash[key] = value unless value == base_value
|
81
|
+
|
82
|
+
printings << [
|
83
|
+
key,
|
84
|
+
base_value.to_s.color(value == base_value ? :green : :red),
|
85
|
+
value.to_s.yellow
|
86
|
+
] if Trellohub.debug
|
87
|
+
end
|
88
|
+
|
89
|
+
if Trellohub.debug && !diff.empty?
|
90
|
+
max = printings.max_lengths
|
91
|
+
printings.each.with_index(1) do |line, index|
|
92
|
+
puts '[DIFF: Update the base]' if index == 1
|
93
|
+
puts 3.times.map.with_index { |i| ''.ljust(max[i], '-') }.join(' | ') if index == 2
|
94
|
+
puts line.map.with_index { |word, i| "#{word}".ljust(max[i]) }.join(' | ')
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
diff unless diff.empty?
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
attr_accessor(*self.common_attributes + self.origin_attributes)
|
103
|
+
|
104
|
+
def created_at
|
105
|
+
@created_at ||= send(:"#{@imported_from}_#{__method__}")
|
106
|
+
end
|
107
|
+
|
108
|
+
def updated_at
|
109
|
+
return @issue_updated_at if @imported_from == :issue
|
110
|
+
@updated_at ||= card_updated_at || card_created_at
|
111
|
+
end
|
112
|
+
|
113
|
+
def closed_at
|
114
|
+
return @issue_closed_at if @imported_from == :issue
|
115
|
+
@closed_at ||= card_closed_at || card_created_at
|
116
|
+
end
|
117
|
+
|
118
|
+
def open?
|
119
|
+
@state == 'open'
|
120
|
+
end
|
121
|
+
|
122
|
+
def closed?
|
123
|
+
@state == 'closed'
|
124
|
+
end
|
125
|
+
|
126
|
+
def own_key
|
127
|
+
if @imported_from == :issue
|
128
|
+
@key
|
129
|
+
else
|
130
|
+
@card_shortLink
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def to_hash
|
135
|
+
Hash[instance_variables.map { |variable|
|
136
|
+
next if variable.is_a?(Sawyer::Resource)
|
137
|
+
variable_no_at = variable.to_s.gsub('@', '')
|
138
|
+
[variable_no_at.to_sym, instance_variable_get(:"#{variable}")]
|
139
|
+
}.compact]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module Trellohub
|
2
|
+
class Form
|
3
|
+
module Card
|
4
|
+
class << self
|
5
|
+
def valid_attributes
|
6
|
+
%i(
|
7
|
+
closed
|
8
|
+
desc
|
9
|
+
idBoard
|
10
|
+
idList
|
11
|
+
name
|
12
|
+
idMembers
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def accessible_attributes
|
17
|
+
self.prefix(self.valid_attributes + %i(list_name))
|
18
|
+
end
|
19
|
+
|
20
|
+
def readable_attributes
|
21
|
+
self.prefix %i(labels)
|
22
|
+
end
|
23
|
+
|
24
|
+
def prefix(array)
|
25
|
+
array.map { |key| :"card_#{key}" }
|
26
|
+
end
|
27
|
+
|
28
|
+
def included(base)
|
29
|
+
base.class_eval do
|
30
|
+
attr_accessor(*Trellohub::Form::Card.accessible_attributes)
|
31
|
+
attr_reader(*Trellohub::Form::Card.readable_attributes)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def import_card(card)
|
37
|
+
@origin_card = card.dup
|
38
|
+
@imported_from = :card
|
39
|
+
|
40
|
+
card.attrs.keys.each do |key|
|
41
|
+
next if key == :badges
|
42
|
+
instance_variable_set(:"@card_#{key}", card.send(key))
|
43
|
+
end
|
44
|
+
|
45
|
+
build_card_attributes_by_card
|
46
|
+
build_issue_attributes_by_card
|
47
|
+
end
|
48
|
+
|
49
|
+
# e.g.
|
50
|
+
# => #<MatchData
|
51
|
+
# "synced_issue: https://github.com/linyows-Z_2/trellohub-foo_aaa-123/issues/127"
|
52
|
+
# 1:"linyows-Z_2/trellohub-foo_aaa-123"
|
53
|
+
# 2:"127">
|
54
|
+
def key_matcher
|
55
|
+
/synced_issue:\shttps?:\/\/.*?\/([\w\-\/]+)\/(?:issues|pulls)\/(\d+)/
|
56
|
+
end
|
57
|
+
|
58
|
+
def card_name_prefix_matcher
|
59
|
+
/^[\w\-]+#\d+\s/
|
60
|
+
end
|
61
|
+
|
62
|
+
def build_card_attributes_by_card
|
63
|
+
list = Trellohub::List.find_by(id: @origin_card.idList)
|
64
|
+
@card_list_name = list.name if list
|
65
|
+
end
|
66
|
+
|
67
|
+
def build_issue_attributes_by_card
|
68
|
+
@issue_title = @origin_card.name.gsub(card_name_prefix_matcher, '')
|
69
|
+
@issue_state = @state = @origin_card.closed ? 'closed' : 'open'
|
70
|
+
|
71
|
+
if @origin_card.desc =~ key_matcher
|
72
|
+
@issue_repository = $1
|
73
|
+
@issue_number = $2
|
74
|
+
@key = "#{$1}##{$2}"
|
75
|
+
|
76
|
+
repo = Trellohub.repository_by(full_name: @issue_repository)
|
77
|
+
@issue_milestone = repo.milestone.title if repo.milestone?
|
78
|
+
end
|
79
|
+
|
80
|
+
unless @origin_card.idMembers.empty?
|
81
|
+
member = Trellohub::Member.find_by(id: @origin_card.idMembers.first)
|
82
|
+
@issue_assignee = member.username if member
|
83
|
+
end
|
84
|
+
|
85
|
+
if @card_list_name
|
86
|
+
label = Trellohub.list_by(name: @card_list_name).issue_label
|
87
|
+
@issue_labels = label ? [label] : []
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
%i(create update delete).each do |cud|
|
92
|
+
class_eval <<-METHODS, __FILE__, __LINE__ + 1
|
93
|
+
def card_#{cud}d_at
|
94
|
+
return if @card_id.nil?
|
95
|
+
card_#{cud}_action.date if card_#{cud}_action
|
96
|
+
end
|
97
|
+
|
98
|
+
def card_#{cud}_user
|
99
|
+
return if @card_id.nil?
|
100
|
+
if card_#{cud}_action && card_#{cud}_action.memberCreator
|
101
|
+
card_#{cud}_action.memberCreator.username
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def card_#{cud}_action
|
106
|
+
return if @card_id.nil?
|
107
|
+
@card_#{cud}_action ||= Trell.card_actions(@card_id, filter: '#{cud}Card').
|
108
|
+
sort_by(&:date).last
|
109
|
+
rescue Trell::NotFound
|
110
|
+
end
|
111
|
+
METHODS
|
112
|
+
end
|
113
|
+
|
114
|
+
def card_id
|
115
|
+
if @card_id.nil? && @imported_from == :issue
|
116
|
+
form = Trellohub::Form.with_cards.find_by_key(@key)
|
117
|
+
@card_id = form.card_id if form
|
118
|
+
end
|
119
|
+
|
120
|
+
@card_id
|
121
|
+
end
|
122
|
+
|
123
|
+
def card_update?
|
124
|
+
!card_id.nil?
|
125
|
+
end
|
126
|
+
|
127
|
+
def create_card
|
128
|
+
Trell.create_card(to_valid_card)
|
129
|
+
end
|
130
|
+
|
131
|
+
def update_card
|
132
|
+
Trell.update_card(@card_id, to_valid_card)
|
133
|
+
end
|
134
|
+
|
135
|
+
def close_card
|
136
|
+
Trell.update_card(@card_id, to_valid_card.merge(closed: true))
|
137
|
+
end
|
138
|
+
|
139
|
+
def save_as_card
|
140
|
+
case
|
141
|
+
when card_update? && open? then update_card
|
142
|
+
when card_update? && closed? then close_card
|
143
|
+
when open? then create_card
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def to_valid_card
|
148
|
+
Hash[Trellohub::Form::Card.valid_attributes.map { |key|
|
149
|
+
[key, instance_variable_get(:"@card_#{key}")]
|
150
|
+
}]
|
151
|
+
end
|
152
|
+
|
153
|
+
def to_card
|
154
|
+
Hash[Trellohub::Form::Card.accessible_attributes.map { |key|
|
155
|
+
[
|
156
|
+
key.to_s.gsub('card_', '').to_sym,
|
157
|
+
instance_variable_get(:"@#{key}")
|
158
|
+
]
|
159
|
+
}]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
module Trellohub
|
2
|
+
class Form
|
3
|
+
module Issue
|
4
|
+
class << self
|
5
|
+
def valid_attributes
|
6
|
+
%i(
|
7
|
+
title
|
8
|
+
labels
|
9
|
+
state
|
10
|
+
assignee
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def accessible_attributes
|
15
|
+
self.prefix self.valid_attributes
|
16
|
+
end
|
17
|
+
|
18
|
+
def readable_attributes
|
19
|
+
self.prefix %i(
|
20
|
+
number
|
21
|
+
created_at
|
22
|
+
updated_at
|
23
|
+
closed_at
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def prefix(array)
|
28
|
+
array.map { |key| :"issue_#{key}" }
|
29
|
+
end
|
30
|
+
|
31
|
+
def included(base)
|
32
|
+
base.class_eval do
|
33
|
+
attr_accessor(*Trellohub::Form::Issue.accessible_attributes)
|
34
|
+
attr_reader(*Trellohub::Form::Issue.readable_attributes)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def import_issue(repository, issue)
|
40
|
+
@origin_issue = issue.dup
|
41
|
+
@issue_repository = repository
|
42
|
+
@key = "#{repository}##{@origin_issue.number}"
|
43
|
+
@state = @origin_issue.state
|
44
|
+
@imported_from = :issue
|
45
|
+
|
46
|
+
build_card_attributes_by_issue
|
47
|
+
build_issue_attributes_by_issue
|
48
|
+
end
|
49
|
+
|
50
|
+
def build_card_attributes_by_issue
|
51
|
+
@card_idBoard = Trellohub::Board.id
|
52
|
+
@card_name = "#{issue_repo_name}##{@origin_issue.number} #{@origin_issue.title}"
|
53
|
+
@card_desc = "synced_issue: #{Octokit.web_endpoint}#{@issue_repository}/issues/#{@origin_issue.number}"
|
54
|
+
@card_closed = @origin_issue.state == 'closed'
|
55
|
+
assign_card_members_by_issue
|
56
|
+
assign_card_list_by_issue
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_issue_attributes_by_issue
|
60
|
+
@origin_issue.attrs.keys.each do |key|
|
61
|
+
next if key == :pull_request
|
62
|
+
|
63
|
+
value = case key
|
64
|
+
when :user
|
65
|
+
@origin_issue.user.login
|
66
|
+
when :assignee
|
67
|
+
@origin_issue.assignee ? @origin_issue.assignee.login : nil
|
68
|
+
when :labels
|
69
|
+
@origin_issue.labels.empty? ? [] : @origin_issue.labels.map(&:name)
|
70
|
+
else
|
71
|
+
@origin_issue.send(key)
|
72
|
+
end
|
73
|
+
|
74
|
+
instance_variable_set(:"@issue_#{key}", value)
|
75
|
+
end
|
76
|
+
|
77
|
+
@issue_id = @origin_issue.id
|
78
|
+
@issue_number = @origin_issue.number
|
79
|
+
|
80
|
+
if @origin_issue.milestone
|
81
|
+
@issue_milestone_title = @origin_issue.milestone.title
|
82
|
+
@issue_milestone = @origin_issue.milestone.number
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def issue_repository_name
|
87
|
+
@issue_repository.split('/').last
|
88
|
+
end
|
89
|
+
alias_method :issue_repo_name, :issue_repository_name
|
90
|
+
|
91
|
+
def assign_card_members_by_issue
|
92
|
+
@card_idMembers = []
|
93
|
+
return unless @origin_issue.assignee
|
94
|
+
|
95
|
+
member = Trellohub::Member.find_by(username: @origin_issue.assignee.login)
|
96
|
+
unless member.nil?
|
97
|
+
@card_idMembers << member.id
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def assign_card_list_by_issue
|
102
|
+
labels = @origin_issue.labels.map(&:name).uniq
|
103
|
+
list = Trellohub.list_by(labels: labels)
|
104
|
+
return unless list
|
105
|
+
@card_idList = list.id
|
106
|
+
@card_list_name = list.name
|
107
|
+
end
|
108
|
+
|
109
|
+
def issue_update?
|
110
|
+
!@key.nil?
|
111
|
+
end
|
112
|
+
|
113
|
+
def issue_body
|
114
|
+
if @issue_body.nil? && @imported_from == :card
|
115
|
+
form = Trellohub::Form.with_issues.find_by_key(@key)
|
116
|
+
@issue_body = form.issue_body if form
|
117
|
+
end
|
118
|
+
|
119
|
+
@issue_body
|
120
|
+
end
|
121
|
+
|
122
|
+
def create_issue
|
123
|
+
return if @issue_repository.nil? || @issue_title.nil?
|
124
|
+
|
125
|
+
Octokit.create_issue(
|
126
|
+
@issue_repository,
|
127
|
+
@issue_title,
|
128
|
+
nil,
|
129
|
+
to_valid_issue
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
133
|
+
def update_issue
|
134
|
+
return if @issue_repository.nil? || @issue_number.nil? || @issue_title.nil?
|
135
|
+
|
136
|
+
Octokit.update_issue(
|
137
|
+
@issue_repository,
|
138
|
+
@issue_number,
|
139
|
+
@issue_title,
|
140
|
+
issue_body,
|
141
|
+
to_valid_issue
|
142
|
+
)
|
143
|
+
end
|
144
|
+
|
145
|
+
def close_issue
|
146
|
+
return if @issue_repository.nil? || @issue_number.nil?
|
147
|
+
|
148
|
+
Octokit.close_issue(
|
149
|
+
@issue_repository,
|
150
|
+
@issue_number,
|
151
|
+
to_valid_issue
|
152
|
+
)
|
153
|
+
end
|
154
|
+
|
155
|
+
def save_as_issue
|
156
|
+
case
|
157
|
+
when issue_update? && open? then update_issue
|
158
|
+
when issue_update? && closed? then close_issue
|
159
|
+
when open? then create_issue
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def to_valid_issue
|
164
|
+
Hash[Trellohub::Form::Issue.valid_attributes.map { |key|
|
165
|
+
value = instance_variable_get(:"@issue_#{key}")
|
166
|
+
|
167
|
+
case
|
168
|
+
when @imported_from == :issue && !value.empty?
|
169
|
+
valid_label = value.find { |v| Trellohub.issue_labels.include?(v) }
|
170
|
+
value = []
|
171
|
+
value << valid_label if valid_label
|
172
|
+
when @imported_from == :card
|
173
|
+
form = Trellohub::Form.with_issues.find_by_key(@key)
|
174
|
+
value = form.issue_labels if form
|
175
|
+
end if key == :labels
|
176
|
+
|
177
|
+
[key, value]
|
178
|
+
}]
|
179
|
+
end
|
180
|
+
|
181
|
+
def to_issue
|
182
|
+
Hash[Trellohub::Form::Issue.accessible_attributes.map { |key|
|
183
|
+
[
|
184
|
+
key.to_s.gsub('issue_', '').to_sym,
|
185
|
+
instance_variable_get(:"@#{key}")
|
186
|
+
]
|
187
|
+
}]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|