tpope-pickler 0.0.7 → 0.0.8
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/lib/pickler.rb +7 -4
- data/lib/pickler/feature.rb +23 -7
- data/lib/pickler/runner.rb +19 -4
- data/lib/pickler/tracker.rb +17 -14
- data/lib/pickler/tracker/iteration.rb +9 -2
- data/lib/pickler/tracker/note.rb +5 -1
- data/lib/pickler/tracker/project.rb +1 -1
- data/lib/pickler/tracker/story.rb +33 -6
- data/pickler.gemspec +2 -2
- metadata +5 -3
data/lib/pickler.rb
CHANGED
@@ -84,9 +84,8 @@ class Pickler
|
|
84
84
|
|
85
85
|
def parser
|
86
86
|
require 'cucumber'
|
87
|
-
require "cucumber/treetop_parser/feature_#@lang"
|
88
87
|
Cucumber.load_language(@lang)
|
89
|
-
@parser ||= Cucumber::
|
88
|
+
@parser ||= Cucumber::Parser::FeatureParser.new
|
90
89
|
end
|
91
90
|
|
92
91
|
def project_id
|
@@ -108,11 +107,15 @@ class Pickler
|
|
108
107
|
|
109
108
|
def scenario_word
|
110
109
|
parser
|
111
|
-
Cucumber.
|
110
|
+
Cucumber.keyword_hash['scenario']
|
111
|
+
end
|
112
|
+
|
113
|
+
def format
|
114
|
+
(config['format'] || :comment).to_sym
|
112
115
|
end
|
113
116
|
|
114
117
|
def local_features
|
115
|
-
Dir[features_path('**','*.feature')].map {|f|feature(f)}.select {|f|f.
|
118
|
+
Dir[features_path('**','*.feature')].map {|f|feature(f)}.select {|f|f.pushable?}
|
116
119
|
end
|
117
120
|
|
118
121
|
def scenario_features
|
data/lib/pickler/feature.rb
CHANGED
@@ -39,20 +39,20 @@ class Pickler
|
|
39
39
|
def filename
|
40
40
|
unless defined?(@filename)
|
41
41
|
@filename = Dir[pickler.features_path("**","*.feature")].detect do |f|
|
42
|
-
File.read(f)[
|
42
|
+
File.read(f)[/(?:#\s*|@[[:punct:]]?)#{URL_REGEX}/,1].to_i == @id
|
43
43
|
end
|
44
44
|
end
|
45
45
|
@filename
|
46
46
|
end
|
47
47
|
|
48
48
|
def to_s
|
49
|
-
local_body || story.to_s
|
49
|
+
local_body || story.to_s(pickler.format)
|
50
50
|
end
|
51
51
|
|
52
52
|
def pull(default = nil)
|
53
53
|
filename = filename() || pickler.features_path("#{default||id}.feature")
|
54
54
|
story = story() # force the read into local_body before File.open below blows it away
|
55
|
-
File.open(filename,'w') {|f| f.puts story}
|
55
|
+
File.open(filename,'w') {|f| f.puts story.to_s(pickler.format)}
|
56
56
|
@filename = filename
|
57
57
|
end
|
58
58
|
|
@@ -63,10 +63,26 @@ class Pickler
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
+
def pushable?
|
67
|
+
id || local_body =~ %r{\A(?:#\s*|@[[:punct:]]?(?:http://www\.pivotaltracker\.com/story/new)?[[:punct:]]?(?:\s+@\S+)*\s*)\n[[:upper:]][[:lower:]]+:} ? true : false
|
68
|
+
end
|
69
|
+
|
66
70
|
def push
|
67
|
-
|
68
|
-
story.to_s
|
69
|
-
story
|
71
|
+
body = local_body
|
72
|
+
return if story.to_s(pickler.format) == body.to_s
|
73
|
+
if story
|
74
|
+
story.to_s = body
|
75
|
+
story.save!
|
76
|
+
else
|
77
|
+
unless pushable?
|
78
|
+
raise Error, "To create a new story, make the first line an empty comment"
|
79
|
+
end
|
80
|
+
story = pickler.new_story
|
81
|
+
story.to_s = body
|
82
|
+
@story = story.save!
|
83
|
+
body.sub!(/\A(?:#.*\n)?/,"# #{story.url}\n")
|
84
|
+
File.open(filename,'w') {|f| f.write body}
|
85
|
+
end
|
70
86
|
end
|
71
87
|
|
72
88
|
def finish
|
@@ -81,7 +97,7 @@ class Pickler
|
|
81
97
|
|
82
98
|
def id
|
83
99
|
unless defined?(@id)
|
84
|
-
@id = if id = local_body.to_s[
|
100
|
+
@id = if id = local_body.to_s[/(?:#\s*|@[[:punct:]]?)#{URL_REGEX}/,1]
|
85
101
|
id.to_i
|
86
102
|
end
|
87
103
|
end
|
data/lib/pickler/runner.rb
CHANGED
@@ -201,14 +201,28 @@ class Pickler
|
|
201
201
|
banner_arguments "<story>"
|
202
202
|
summary "Show details for a story"
|
203
203
|
|
204
|
+
on "--full", "default format" do
|
205
|
+
@format = :full
|
206
|
+
end
|
207
|
+
|
208
|
+
on "--raw", "same as the .feature" do
|
209
|
+
@format = :raw
|
210
|
+
end
|
211
|
+
|
204
212
|
process do |*args|
|
205
213
|
case args.size
|
206
214
|
when 0
|
207
215
|
puts "#{pickler.project_id} #{pickler.project.name}"
|
208
216
|
when 1
|
209
|
-
|
210
|
-
|
211
|
-
|
217
|
+
feature = pickler.feature(args.first)
|
218
|
+
story = feature.story
|
219
|
+
case @format
|
220
|
+
when :raw
|
221
|
+
puts feature.story.to_s(pickler.format) if feature.story
|
222
|
+
else
|
223
|
+
paginated_output do
|
224
|
+
puts_full feature.story
|
225
|
+
end
|
212
226
|
end
|
213
227
|
else
|
214
228
|
too_many
|
@@ -284,7 +298,8 @@ class Pickler
|
|
284
298
|
summary "Upload stories"
|
285
299
|
description <<-EOF
|
286
300
|
Upload the given story or all features with a tracker url in a comment on the
|
287
|
-
first line.
|
301
|
+
first line. Features with a blank comment in the first line will created as
|
302
|
+
new stories.
|
288
303
|
EOF
|
289
304
|
|
290
305
|
process do |*args|
|
data/lib/pickler/tracker.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'date'
|
2
|
+
require 'cgi'
|
2
3
|
|
3
4
|
class Pickler
|
4
5
|
class Tracker
|
5
6
|
|
6
7
|
ADDRESS = 'www.pivotaltracker.com'
|
7
|
-
BASE_PATH = '/services/
|
8
|
+
BASE_PATH = '/services/v2'
|
8
9
|
SEARCH_KEYS = %w(label type state requester owner mywork id includedone)
|
9
10
|
|
10
11
|
class Error < Pickler::Error; end
|
@@ -50,23 +51,19 @@ class Pickler
|
|
50
51
|
def request_xml(method, path, *args)
|
51
52
|
response = request(method,path,*args)
|
52
53
|
raise response.inspect if response["Content-type"].split(/; */).first != "application/xml"
|
53
|
-
Hash.from_xml(response.body)
|
54
|
+
hash = Hash.from_xml(response.body)
|
55
|
+
if hash["message"] && (response.code.to_i >= 400 || hash["success"] == "false")
|
56
|
+
raise Error, hash["message"], caller
|
57
|
+
end
|
58
|
+
hash
|
54
59
|
end
|
55
60
|
|
56
61
|
def get_xml(path)
|
57
|
-
|
58
|
-
unless response["success"] == "true"
|
59
|
-
if response["message"]
|
60
|
-
raise Error, response["message"], caller
|
61
|
-
else
|
62
|
-
raise "#{path}: #{response.inspect}"
|
63
|
-
end
|
64
|
-
end
|
65
|
-
response
|
62
|
+
request_xml(:get, path)
|
66
63
|
end
|
67
64
|
|
68
65
|
def project(id)
|
69
|
-
Project.new(self,get_xml("/projects/#{id}")["project"]
|
66
|
+
Project.new(self,get_xml("/projects/#{id}")["project"])
|
70
67
|
end
|
71
68
|
|
72
69
|
class Abstract
|
@@ -86,7 +83,10 @@ class Pickler
|
|
86
83
|
|
87
84
|
def self.date_reader(*methods)
|
88
85
|
methods.each do |method|
|
89
|
-
define_method(method)
|
86
|
+
define_method(method) do
|
87
|
+
value = @attributes[method.to_s]
|
88
|
+
value.kind_of?(String) ? Date.parse(value) : value
|
89
|
+
end
|
90
90
|
end
|
91
91
|
end
|
92
92
|
|
@@ -96,7 +96,10 @@ class Pickler
|
|
96
96
|
define_method("#{method}=") { |v| @attributes[method.to_s] = v }
|
97
97
|
end
|
98
98
|
end
|
99
|
-
|
99
|
+
|
100
|
+
def id
|
101
|
+
id = @attributes['id'] and Integer(id)
|
102
|
+
end
|
100
103
|
|
101
104
|
def to_xml(options = nil)
|
102
105
|
@attributes.to_xml({:dasherize => false, :root => self.class.name.split('::').last.downcase}.merge(options||{}))
|
@@ -2,13 +2,20 @@ class Pickler
|
|
2
2
|
class Tracker
|
3
3
|
class Iteration < Abstract
|
4
4
|
attr_reader :project
|
5
|
-
date_reader :start, :finish
|
6
5
|
|
7
6
|
def initialize(project, attributes = {})
|
8
7
|
@project = project
|
9
8
|
super(attributes)
|
10
9
|
end
|
11
10
|
|
11
|
+
def start
|
12
|
+
Date.parse(@attributes['start'].to_s)
|
13
|
+
end
|
14
|
+
|
15
|
+
def finish
|
16
|
+
Date.parse(@attributes['finish'].to_s)
|
17
|
+
end
|
18
|
+
|
12
19
|
def number
|
13
20
|
@attributes['number'].to_i
|
14
21
|
end
|
@@ -23,7 +30,7 @@ class Pickler
|
|
23
30
|
end
|
24
31
|
|
25
32
|
def succ
|
26
|
-
self.class.new(project, 'number' => number.succ.to_s, 'start' => @attributes['finish'], 'finish' => (finish + (finish - start))
|
33
|
+
self.class.new(project, 'number' => number.succ.to_s, 'start' => @attributes['finish'], 'finish' => (finish + (finish - start)))
|
27
34
|
end
|
28
35
|
|
29
36
|
def inspect
|
data/lib/pickler/tracker/note.rb
CHANGED
@@ -3,7 +3,11 @@ class Pickler
|
|
3
3
|
class Note < Abstract
|
4
4
|
attr_reader :story
|
5
5
|
reader :text, :author
|
6
|
-
date_reader :
|
6
|
+
date_reader :noted_at
|
7
|
+
|
8
|
+
def date
|
9
|
+
noted_at && Date.new(noted_at.year, noted_at.mon, noted_at.day)
|
10
|
+
end
|
7
11
|
|
8
12
|
def initialize(story, attributes = {})
|
9
13
|
@story = story
|
@@ -20,7 +20,7 @@ class Pickler
|
|
20
20
|
path = "/projects/#{id}/stories"
|
21
21
|
path << "?filter=#{CGI.escape(filter)}" if filter
|
22
22
|
response = tracker.get_xml(path)
|
23
|
-
[response["stories"]
|
23
|
+
[response["stories"]].flatten.compact.map {|s| Story.new(self,s)}
|
24
24
|
end
|
25
25
|
|
26
26
|
def new_story(attributes = {}, &block)
|
@@ -5,7 +5,7 @@ class Pickler
|
|
5
5
|
TYPES = %w(bug feature chore release)
|
6
6
|
STATES = %w(unscheduled unstarted started finished delivered rejected accepted)
|
7
7
|
|
8
|
-
attr_reader :project, :
|
8
|
+
attr_reader :project, :labels
|
9
9
|
reader :url
|
10
10
|
date_reader :created_at, :accepted_at, :deadline
|
11
11
|
accessor :current_state, :name, :description, :owned_by, :requested_by, :story_type
|
@@ -17,6 +17,13 @@ class Pickler
|
|
17
17
|
@labels = normalize_labels(@attributes["labels"])
|
18
18
|
end
|
19
19
|
|
20
|
+
def iteration
|
21
|
+
unless current_state == 'unscheduled' || defined?(@iteration)
|
22
|
+
@iteration = project.stories(:id => id, :includedone => true).first.iteration
|
23
|
+
end
|
24
|
+
@iteration
|
25
|
+
end
|
26
|
+
|
20
27
|
def labels=(value)
|
21
28
|
@labels = normalize_labels(value)
|
22
29
|
end
|
@@ -72,16 +79,28 @@ class Pickler
|
|
72
79
|
project.tracker
|
73
80
|
end
|
74
81
|
|
75
|
-
def to_s
|
76
|
-
to_s = "#
|
82
|
+
def to_s(format = :comment)
|
83
|
+
to_s = "#{header(format)}\n#{story_type.capitalize}: #{name}\n"
|
77
84
|
description_lines.each do |line|
|
78
85
|
to_s << " #{line}".rstrip << "\n"
|
79
86
|
end
|
80
87
|
to_s
|
81
88
|
end
|
82
89
|
|
90
|
+
def header(format = :comment)
|
91
|
+
case format
|
92
|
+
when :tag
|
93
|
+
"@#{url}#{labels.map {|l| " @#{l.tr('_,',' _')}"}.join}"
|
94
|
+
else
|
95
|
+
"# #{url}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
83
99
|
def to_s=(body)
|
84
|
-
body
|
100
|
+
if body =~ /\A@https?\b\S*(\s+@\S+)*\s*$/
|
101
|
+
self.labels = body[/\A@.*/].split(/\s+/)[1..-1].map {|l| l[1..-1].tr(' _','_,')}
|
102
|
+
end
|
103
|
+
body = body.sub(/\A(?:[@#].*\n)+/,'')
|
85
104
|
if body =~ /\A(\w+): (.*)/
|
86
105
|
self.story_type = $1.downcase
|
87
106
|
self.name = $2
|
@@ -136,7 +155,7 @@ class Pickler
|
|
136
155
|
def destroy
|
137
156
|
if id
|
138
157
|
response = tracker.request_xml(:delete, "/projects/#{project.id}/stories/#{id}", "")
|
139
|
-
raise Error, response["message"], caller if response["
|
158
|
+
raise Error, response["message"], caller if response["message"]
|
140
159
|
@attributes["id"] = nil
|
141
160
|
self
|
142
161
|
end
|
@@ -148,7 +167,7 @@ class Pickler
|
|
148
167
|
|
149
168
|
def save
|
150
169
|
response = tracker.request_xml(id ? :put : :post, resource_url, to_xml(false))
|
151
|
-
if response["
|
170
|
+
if response["story"]
|
152
171
|
initialize(project, response["story"])
|
153
172
|
true
|
154
173
|
else
|
@@ -156,6 +175,14 @@ class Pickler
|
|
156
175
|
end
|
157
176
|
end
|
158
177
|
|
178
|
+
def save!
|
179
|
+
errors = save
|
180
|
+
if errors != true
|
181
|
+
raise Pickler::Tracker::Error, Array(error).join("\n"), caller
|
182
|
+
end
|
183
|
+
self
|
184
|
+
end
|
185
|
+
|
159
186
|
private
|
160
187
|
def normalize_labels(value)
|
161
188
|
Array(value).join(", ").strip.split(/\s*,\s*/)
|
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.8"
|
4
4
|
|
5
5
|
s.summary = "PIvotal traCKer Liaison to cucumbER"
|
6
6
|
s.description = "Synchronize between Cucumber and Pivotal Tracker"
|
@@ -24,5 +24,5 @@ Gem::Specification.new do |s|
|
|
24
24
|
"lib/pickler/tracker/note.rb"
|
25
25
|
]
|
26
26
|
s.add_dependency("activesupport", [">= 2.0.0"])
|
27
|
-
s.add_dependency("cucumber", [">= 0.
|
27
|
+
s.add_dependency("cucumber", [">= 0.2.0"])
|
28
28
|
end
|
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.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tim Pope
|
@@ -9,11 +9,12 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-03-21 00:00:00 -07:00
|
13
13
|
default_executable: pickler
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
17
|
+
type: :runtime
|
17
18
|
version_requirement:
|
18
19
|
version_requirements: !ruby/object:Gem::Requirement
|
19
20
|
requirements:
|
@@ -23,12 +24,13 @@ dependencies:
|
|
23
24
|
version:
|
24
25
|
- !ruby/object:Gem::Dependency
|
25
26
|
name: cucumber
|
27
|
+
type: :runtime
|
26
28
|
version_requirement:
|
27
29
|
version_requirements: !ruby/object:Gem::Requirement
|
28
30
|
requirements:
|
29
31
|
- - ">="
|
30
32
|
- !ruby/object:Gem::Version
|
31
|
-
version: 0.
|
33
|
+
version: 0.2.0
|
32
34
|
version:
|
33
35
|
description: Synchronize between Cucumber and Pivotal Tracker
|
34
36
|
email: ruby@tpope.info
|