tpope-pickler 0.0.1 → 0.0.2
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.
- data/README.rdoc +9 -0
- data/lib/pickler/feature.rb +71 -0
- data/lib/pickler/tracker/story.rb +26 -10
- data/lib/pickler.rb +43 -41
- data/pickler.gemspec +2 -1
- metadata +2 -1
data/README.rdoc
CHANGED
@@ -4,6 +4,7 @@ Synchronize user stories in Pivotal Tracker with Cucumber features.
|
|
4
4
|
|
5
5
|
== Getting started
|
6
6
|
|
7
|
+
gem install tpope-pickler --source=http://gems.github.com
|
7
8
|
echo "api_token: ..." > ~/.tracker.yml
|
8
9
|
echo "project_id: ..." > ~/my/app/features/tracker.yml
|
9
10
|
|
@@ -34,6 +35,14 @@ List all stories matching the given query.
|
|
34
35
|
|
35
36
|
Show details for the story referenced by the id.
|
36
37
|
|
38
|
+
pickler start <id>
|
39
|
+
|
40
|
+
Pull a given feature and change its state to started.
|
41
|
+
|
42
|
+
pickler finish <id>
|
43
|
+
|
44
|
+
Push a given feature and change its state to finished.
|
45
|
+
|
37
46
|
== Disclaimer
|
38
47
|
|
39
48
|
No warranties, expressed or implied.
|
@@ -0,0 +1,71 @@
|
|
1
|
+
class Pickler
|
2
|
+
class Feature
|
3
|
+
URL_REGEX = %r{\bhttp://www\.pivotaltracker\.com/\S*/(\d+)\b}
|
4
|
+
attr_reader :pickler
|
5
|
+
|
6
|
+
def initialize(pickler, identifier)
|
7
|
+
@pickler = pickler
|
8
|
+
case identifier
|
9
|
+
when nil, /^\s+$/
|
10
|
+
raise Error, "No feature given"
|
11
|
+
|
12
|
+
when /^#{URL_REGEX}$/, /^(\d+)$/
|
13
|
+
@id = $1.to_i
|
14
|
+
|
15
|
+
when /\.feature$/
|
16
|
+
if File.exist?(identifier)
|
17
|
+
@filename = identifier
|
18
|
+
end
|
19
|
+
|
20
|
+
else
|
21
|
+
if File.exist?(path = pickler.features_path("#{identifier}.feature"))
|
22
|
+
@filename = path
|
23
|
+
end
|
24
|
+
|
25
|
+
end or raise Error, "Unrecogizable feature #{string}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def local_body
|
29
|
+
File.read(@filename) if @filename
|
30
|
+
end
|
31
|
+
|
32
|
+
def filename
|
33
|
+
unless defined?(@filename)
|
34
|
+
@filename = Dir[pickler.features_path("**","*.feature")].detect do |f|
|
35
|
+
File.read(f)[/#\s*#{URL_REGEX}/,1].to_i == @id
|
36
|
+
end
|
37
|
+
end
|
38
|
+
@filename
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
local_body || story.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
def push
|
46
|
+
return if story.to_s == local_body.to_s
|
47
|
+
story.to_s = local_body
|
48
|
+
story.save
|
49
|
+
end
|
50
|
+
|
51
|
+
def finish
|
52
|
+
story.current_state = "finished" unless story.complete?
|
53
|
+
story.to_s = local_body
|
54
|
+
story.save
|
55
|
+
end
|
56
|
+
|
57
|
+
def id
|
58
|
+
unless defined?(@id)
|
59
|
+
@id = if id = local_body.to_s[/#\s*#{URL_REGEX}/,1]
|
60
|
+
id.to_i
|
61
|
+
end
|
62
|
+
end
|
63
|
+
@id
|
64
|
+
end
|
65
|
+
|
66
|
+
def story
|
67
|
+
@story ||= @pickler.project.story(id) if id
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
@@ -14,6 +14,22 @@ class Pickler
|
|
14
14
|
super(attributes)
|
15
15
|
end
|
16
16
|
|
17
|
+
def transition!(state)
|
18
|
+
raise Pickler::Tracker::Error, "Invalid state #{state}", caller unless STATES.include?(state)
|
19
|
+
self.current_state = state
|
20
|
+
if id
|
21
|
+
xml = "<story><current-state>#{state}</current-state></story>"
|
22
|
+
error = tracker.request_xml(:put, resource_url, xml).fetch("errors",{})["error"] || true
|
23
|
+
else
|
24
|
+
error = save
|
25
|
+
end
|
26
|
+
raise Pickler::Tracker::Error, Array(error).join("\n"), caller unless error == true
|
27
|
+
end
|
28
|
+
|
29
|
+
def complete?
|
30
|
+
%w(finished delivered accepted).include?(current_state)
|
31
|
+
end
|
32
|
+
|
17
33
|
def tracker
|
18
34
|
project.tracker
|
19
35
|
end
|
@@ -64,24 +80,24 @@ class Pickler
|
|
64
80
|
|
65
81
|
def destroy
|
66
82
|
if id
|
67
|
-
|
68
|
-
raise Error,
|
83
|
+
response = tracker.request_xml(:delete, "/projects/#{project.id}/stories/#{id}", to_xml)
|
84
|
+
raise Error, response["message"], caller if response["success"] != "true"
|
69
85
|
@attributes["id"] = nil
|
70
86
|
self
|
71
87
|
end
|
72
88
|
end
|
73
89
|
|
90
|
+
def resource_url
|
91
|
+
["/projects/#{project.id}/stories",id].compact.join("/")
|
92
|
+
end
|
93
|
+
|
74
94
|
def save
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
request = tracker.request_xml(:post, "/projects/#{project.id}/stories", to_xml)
|
79
|
-
end
|
80
|
-
if request["success"] == "true"
|
81
|
-
initialize(project, request["story"])
|
95
|
+
response = tracker.request_xml(id ? :put : :post, resource_url, to_xml)
|
96
|
+
if response["success"] == "true"
|
97
|
+
initialize(project, response["story"])
|
82
98
|
true
|
83
99
|
else
|
84
|
-
Array(
|
100
|
+
Array(response["errors"]["error"])
|
85
101
|
end
|
86
102
|
end
|
87
103
|
|
data/lib/pickler.rb
CHANGED
@@ -39,8 +39,12 @@ class Pickler
|
|
39
39
|
pickler.push(*argv)
|
40
40
|
when 'pull'
|
41
41
|
pickler.pull(*argv)
|
42
|
+
when 'start'
|
43
|
+
pickler.start(argv.first)
|
44
|
+
when 'finish'
|
45
|
+
pickler.finish(argv.first)
|
42
46
|
when 'help', '--help', '-h', '', nil
|
43
|
-
puts 'pickler commands: show <id>, search <query>, push, pull'
|
47
|
+
puts 'pickler commands: [show|start|finish] <id>, search <query>, push, pull'
|
44
48
|
else
|
45
49
|
$stderr.puts "pickler: unknown command #{first}"
|
46
50
|
exit 1
|
@@ -107,19 +111,33 @@ class Pickler
|
|
107
111
|
Cucumber.language['scenario']
|
108
112
|
end
|
109
113
|
|
110
|
-
def
|
114
|
+
def stories
|
111
115
|
project.stories(scenario_word, :includedone => true).select do |s|
|
112
116
|
s.to_s =~ /^\s*#{Regexp.escape(scenario_word)}:/ && parser.parse(s.to_s)
|
113
117
|
end
|
114
118
|
end
|
115
119
|
|
116
|
-
def
|
117
|
-
|
120
|
+
def features(*args)
|
121
|
+
if args.any?
|
122
|
+
args.map {|a| feature(a)}
|
123
|
+
else
|
124
|
+
Dir[features_path('**','*.feature')].map {|f|feature(f)}.select {|f|f.id}
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def feature(string)
|
129
|
+
Feature.new(self,string)
|
130
|
+
end
|
131
|
+
|
132
|
+
def story(string)
|
133
|
+
feature(string).story
|
118
134
|
end
|
119
135
|
|
120
|
-
def pull
|
121
|
-
l =
|
122
|
-
|
136
|
+
def pull(*args)
|
137
|
+
l = features
|
138
|
+
args.map! {|arg| story(arg)}
|
139
|
+
args.replace(stories) if args.empty?
|
140
|
+
args.each do |remote|
|
123
141
|
body = "# http://www.pivotaltracker.com/story/show/#{remote.id}\n" <<
|
124
142
|
normalize_feature(remote.to_s)
|
125
143
|
if local = l.detect {|f| f.id == remote.id}
|
@@ -133,13 +151,23 @@ class Pickler
|
|
133
151
|
nil
|
134
152
|
end
|
135
153
|
|
136
|
-
def
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
154
|
+
def start(*args)
|
155
|
+
args.each do |arg|
|
156
|
+
story = story(arg)
|
157
|
+
story.transition!("started") if %w(unstarted rejected).include?(story.current_state)
|
158
|
+
end
|
159
|
+
pull(*args)
|
160
|
+
end
|
161
|
+
|
162
|
+
def push(*args)
|
163
|
+
features(*args).each do |feature|
|
164
|
+
feature.push
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def finish(*args)
|
169
|
+
features(*args).each do |feature|
|
170
|
+
feature.finish
|
143
171
|
end
|
144
172
|
end
|
145
173
|
|
@@ -162,33 +190,7 @@ class Pickler
|
|
162
190
|
new
|
163
191
|
end
|
164
192
|
|
165
|
-
class LocalFeature
|
166
|
-
attr_reader :pickler, :filename
|
167
|
-
|
168
|
-
def initialize(pickler, filename)
|
169
|
-
@pickler = pickler
|
170
|
-
@filename = filename
|
171
|
-
end
|
172
|
-
|
173
|
-
def to_s
|
174
|
-
File.read(@filename)
|
175
|
-
end
|
176
|
-
|
177
|
-
def id
|
178
|
-
unless defined?(@id)
|
179
|
-
@id = if id = to_s[%r{#\s*http://www\.pivotaltracker\.com/\S*?/(\d+)},1]
|
180
|
-
id.to_i
|
181
|
-
end
|
182
|
-
end
|
183
|
-
@id
|
184
|
-
end
|
185
|
-
|
186
|
-
def story
|
187
|
-
@pickler.project.story(id) if id
|
188
|
-
end
|
189
|
-
|
190
|
-
end
|
191
|
-
|
192
193
|
end
|
193
194
|
|
195
|
+
require 'pickler/feature'
|
194
196
|
require 'pickler/tracker'
|
data/pickler.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "pickler"
|
3
|
-
s.version = "0.0.
|
3
|
+
s.version = "0.0.2"
|
4
4
|
|
5
5
|
s.summary = "PIvotal traCKer Liaison to cucumbER"
|
6
6
|
s.description = "Synchronize between Cucumber and Pivotal Tracker"
|
@@ -15,6 +15,7 @@ Gem::Specification.new do |s|
|
|
15
15
|
"pickler.gemspec",
|
16
16
|
"bin/pickler",
|
17
17
|
"lib/pickler.rb",
|
18
|
+
"lib/pickler/feature.rb",
|
18
19
|
"lib/pickler/tracker.rb",
|
19
20
|
"lib/pickler/tracker/project.rb",
|
20
21
|
"lib/pickler/tracker/story.rb"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tpope-pickler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tim Pope
|
@@ -44,6 +44,7 @@ files:
|
|
44
44
|
- pickler.gemspec
|
45
45
|
- bin/pickler
|
46
46
|
- lib/pickler.rb
|
47
|
+
- lib/pickler/feature.rb
|
47
48
|
- lib/pickler/tracker.rb
|
48
49
|
- lib/pickler/tracker/project.rb
|
49
50
|
- lib/pickler/tracker/story.rb
|