trellohub 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|