tpope-pickler 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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::TreetopParser::FeatureParser.new
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.language['scenario']
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.id}
118
+ Dir[features_path('**','*.feature')].map {|f|feature(f)}.select {|f|f.pushable?}
116
119
  end
117
120
 
118
121
  def scenario_features
@@ -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)[/#\s*#{URL_REGEX}/,1].to_i == @id
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
- return if story.to_s == local_body.to_s
68
- story.to_s = local_body
69
- story.save
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[/#\s*#{URL_REGEX}/,1]
100
+ @id = if id = local_body.to_s[/(?:#\s*|@[[:punct:]]?)#{URL_REGEX}/,1]
85
101
  id.to_i
86
102
  end
87
103
  end
@@ -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
- story = pickler.story(args.first)
210
- paginated_output do
211
- puts_full story
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|
@@ -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/v1'
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)["response"]
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
- response = request_xml(:get, path)
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"].merge("id" => id.to_i))
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) { value = @attributes[method.to_s] and Date.parse(value) }
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
- reader :id
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)).strftime("%b %d, %Y"))
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
@@ -3,7 +3,11 @@ class Pickler
3
3
  class Note < Abstract
4
4
  attr_reader :story
5
5
  reader :text, :author
6
- date_reader :date
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"]["story"]].flatten.compact.map {|s| Story.new(self,s)}
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, :iteration, :labels
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 = "# #{url}\n#{story_type.capitalize}: #{name}\n"
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 = body.sub(/\A# .*\n/,'')
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["success"] != "true"
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["success"] == "true"
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.7"
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.1.9"])
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.7
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-02-05 00:00:00 -08:00
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.1.9
33
+ version: 0.2.0
32
34
  version:
33
35
  description: Synchronize between Cucumber and Pivotal Tracker
34
36
  email: ruby@tpope.info