tpope-pickler 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +4 -2
- data/lib/pickler/feature.rb +38 -2
- data/lib/pickler/runner.rb +54 -0
- data/lib/pickler/tracker/iteration.rb +30 -0
- data/lib/pickler/tracker/note.rb +23 -0
- data/lib/pickler/tracker/story.rb +16 -4
- data/lib/pickler/tracker.rb +18 -3
- data/lib/pickler.rb +12 -83
- data/pickler.gemspec +5 -2
- metadata +4 -1
data/README.rdoc
CHANGED
@@ -35,9 +35,11 @@ List all stories matching the given query.
|
|
35
35
|
|
36
36
|
Show details for the story referenced by the id.
|
37
37
|
|
38
|
-
pickler start <id>
|
38
|
+
pickler start <id> [basename]
|
39
39
|
|
40
|
-
Pull a given feature and change its state to started.
|
40
|
+
Pull a given feature and change its state to started. If basename is given
|
41
|
+
and no local file exists, features/basename.feature will be created in lieu
|
42
|
+
of features/id.feature.
|
41
43
|
|
42
44
|
pickler finish <id>
|
43
45
|
|
data/lib/pickler/feature.rb
CHANGED
@@ -9,6 +9,10 @@ class Pickler
|
|
9
9
|
when nil, /^\s+$/
|
10
10
|
raise Error, "No feature given"
|
11
11
|
|
12
|
+
when Pickler::Tracker::Story
|
13
|
+
@story = identifier
|
14
|
+
@id = @story.id
|
15
|
+
|
12
16
|
when /^#{URL_REGEX}$/, /^(\d+)$/
|
13
17
|
@id = $1.to_i
|
14
18
|
|
@@ -22,11 +26,11 @@ class Pickler
|
|
22
26
|
@filename = path
|
23
27
|
end
|
24
28
|
|
25
|
-
end or raise Error, "Unrecogizable feature #{
|
29
|
+
end or raise Error, "Unrecogizable feature #{identifier}"
|
26
30
|
end
|
27
31
|
|
28
32
|
def local_body
|
29
|
-
File.read(
|
33
|
+
File.read(filename) if filename
|
30
34
|
end
|
31
35
|
|
32
36
|
def filename
|
@@ -42,6 +46,19 @@ class Pickler
|
|
42
46
|
local_body || story.to_s
|
43
47
|
end
|
44
48
|
|
49
|
+
def pull(default = nil)
|
50
|
+
body = "# http://www.pivotaltracker.com/story/show/#{id}\n" <<
|
51
|
+
normalize_feature(story.to_s)
|
52
|
+
filename = filename() || pickler.features_path("#{default||id}.feature")
|
53
|
+
File.open(filename,'w') {|f| f.puts body}
|
54
|
+
@filename = filename
|
55
|
+
end
|
56
|
+
|
57
|
+
def start(default = nil)
|
58
|
+
story.transition!("started") if story.startable?
|
59
|
+
pull(default)
|
60
|
+
end
|
61
|
+
|
45
62
|
def push
|
46
63
|
return if story.to_s == local_body.to_s
|
47
64
|
story.to_s = local_body
|
@@ -67,5 +84,24 @@ class Pickler
|
|
67
84
|
@story ||= @pickler.project.story(id) if id
|
68
85
|
end
|
69
86
|
|
87
|
+
protected
|
88
|
+
|
89
|
+
def normalize_feature(body)
|
90
|
+
return body unless ast = pickler.parser.parse(body)
|
91
|
+
feature = ast.compile
|
92
|
+
new = ''
|
93
|
+
(feature.header.chomp << "\n").each_line do |l|
|
94
|
+
new << ' ' unless new.empty?
|
95
|
+
new << l.strip << "\n"
|
96
|
+
end
|
97
|
+
feature.scenarios.each do |scenario|
|
98
|
+
new << "\n Scenario: #{scenario.name}\n"
|
99
|
+
scenario.steps.each do |step|
|
100
|
+
new << " #{step.keyword} #{step.name}\n"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
new
|
104
|
+
end
|
105
|
+
|
70
106
|
end
|
71
107
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class Pickler
|
2
|
+
class Runner
|
3
|
+
|
4
|
+
attr_reader :pickler, :argv
|
5
|
+
|
6
|
+
def initialize(argv)
|
7
|
+
@argv = argv
|
8
|
+
@pickler = Pickler.new(Dir.getwd)
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
case first = argv.shift
|
13
|
+
when 'show', /^\d+$/
|
14
|
+
story = pickler.project.story(first == 'show' ? argv.shift : first)
|
15
|
+
puts story
|
16
|
+
when 'search'
|
17
|
+
stories = pickler.project.stories(*argv).group_by {|s| s.current_state}
|
18
|
+
first = true
|
19
|
+
states = Pickler::Tracker::Story::STATES
|
20
|
+
states -= %w(unstarted accepted) if argv.empty?
|
21
|
+
states.each do |state|
|
22
|
+
next unless stories[state]
|
23
|
+
puts unless first
|
24
|
+
first = false
|
25
|
+
puts state.upcase
|
26
|
+
puts '-' * state.length
|
27
|
+
stories[state].each do |story|
|
28
|
+
puts "[#{story.id}] #{story.story_type.capitalize}: #{story.name}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
when 'push'
|
32
|
+
pickler.push(*argv)
|
33
|
+
when 'pull'
|
34
|
+
pickler.pull(*argv)
|
35
|
+
when 'start'
|
36
|
+
pickler.start(argv.first,argv[1])
|
37
|
+
when 'finish'
|
38
|
+
pickler.finish(argv.first)
|
39
|
+
when 'help', '--help', '-h', '', nil
|
40
|
+
puts 'pickler commands: [show|start|finish] <id>, search <query>, push, pull'
|
41
|
+
else
|
42
|
+
$stderr.puts "pickler: unknown command #{first}"
|
43
|
+
exit 1
|
44
|
+
end
|
45
|
+
rescue Pickler::Error
|
46
|
+
$stderr.puts "#$!"
|
47
|
+
exit 1
|
48
|
+
rescue Interrupt
|
49
|
+
$stderr.puts "Interrupted!"
|
50
|
+
exit 130
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class Pickler
|
2
|
+
class Tracker
|
3
|
+
class Iteration < Abstract
|
4
|
+
attr_reader :project
|
5
|
+
date_reader :start, :finish
|
6
|
+
|
7
|
+
def initialize(project, attributes = {})
|
8
|
+
@project = project
|
9
|
+
super(attributes)
|
10
|
+
end
|
11
|
+
|
12
|
+
def number
|
13
|
+
@attributes['number'].to_i
|
14
|
+
end
|
15
|
+
alias to_i number
|
16
|
+
|
17
|
+
def range
|
18
|
+
start...finish
|
19
|
+
end
|
20
|
+
|
21
|
+
def succ
|
22
|
+
self.class.new(project, 'number' => number.succ.to_s, 'start' => @attributes['finish'], 'finish' => (finish + (finish - start)).strftime("%b %d, %Y"))
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
"#<#{self.class.inspect}:#{number.inspect} (#{range.inspect})>"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Pickler
|
2
|
+
class Tracker
|
3
|
+
class Note < Abstract
|
4
|
+
attr_reader :story
|
5
|
+
reader :text, :author
|
6
|
+
date_reader :date
|
7
|
+
|
8
|
+
def initialize(story, attributes = {})
|
9
|
+
@story = story
|
10
|
+
super(attributes)
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_xml
|
14
|
+
@attributes.to_xml(:root => 'note')
|
15
|
+
end
|
16
|
+
|
17
|
+
def inspect
|
18
|
+
"#<#{self.class.inspect}:#{id.inspect}, story_id: #{story.id.inspect}, date: #{date.inspect}, author: #{author.inspect}, text: #{text.inspect}>"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -3,14 +3,16 @@ class Pickler
|
|
3
3
|
class Story < Abstract
|
4
4
|
|
5
5
|
TYPES = %w(bug feature chore release)
|
6
|
-
STATES = %w(unstarted started finished delivered rejected accepted)
|
6
|
+
STATES = %w(unscheduled unstarted started finished delivered rejected accepted)
|
7
7
|
|
8
|
-
attr_reader :project
|
9
|
-
reader :
|
8
|
+
attr_reader :project, :iteration
|
9
|
+
reader :url, :labels
|
10
|
+
date_reader :created_at, :accepted_at, :deadline
|
10
11
|
accessor :current_state, :name, :description, :estimate, :owned_by, :requested_by, :story_type
|
11
12
|
|
12
13
|
def initialize(project, attributes = {})
|
13
14
|
@project = project
|
15
|
+
@iteration = Iteration.new(project, attributes["iteration"]) if attributes["iteration"]
|
14
16
|
super(attributes)
|
15
17
|
end
|
16
18
|
|
@@ -30,6 +32,10 @@ class Pickler
|
|
30
32
|
%w(finished delivered accepted).include?(current_state)
|
31
33
|
end
|
32
34
|
|
35
|
+
def startable?
|
36
|
+
%w(unscheduled unstarted rejected).include?(current_state)
|
37
|
+
end
|
38
|
+
|
33
39
|
def tracker
|
34
40
|
project.tracker
|
35
41
|
end
|
@@ -69,7 +75,13 @@ class Pickler
|
|
69
75
|
end
|
70
76
|
|
71
77
|
def notes
|
72
|
-
[@attributes["notes"]].flatten.compact
|
78
|
+
[@attributes["notes"]].flatten.compact.map {|n| Note.new(self,n)}
|
79
|
+
end
|
80
|
+
|
81
|
+
def comment!(body)
|
82
|
+
raise ArgumentError if body.strip.empty? || body.size > 5000
|
83
|
+
response = tracker.request_xml(:post, "#{resource_url}/notes",{:text => body}.to_xml(:root => 'note'))
|
84
|
+
Note.new(self, response["note"])
|
73
85
|
end
|
74
86
|
|
75
87
|
def to_xml
|
data/lib/pickler/tracker.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
1
3
|
class Pickler
|
2
4
|
class Tracker
|
3
5
|
|
@@ -18,9 +20,9 @@ class Pickler
|
|
18
20
|
require 'net/http'
|
19
21
|
Net::HTTP.start(ADDRESS) do |http|
|
20
22
|
headers = {
|
21
|
-
"
|
22
|
-
"Accept"
|
23
|
-
"Content-type"
|
23
|
+
"X-TrackerToken" => @token,
|
24
|
+
"Accept" => "application/xml",
|
25
|
+
"Content-type" => "application/xml"
|
24
26
|
}
|
25
27
|
klass = Net::HTTP.const_get(method.to_s.capitalize)
|
26
28
|
http.request(klass.new("#{BASE_PATH}#{path}", headers), *args)
|
@@ -61,6 +63,12 @@ class Pickler
|
|
61
63
|
end
|
62
64
|
end
|
63
65
|
|
66
|
+
def self.date_reader(*methods)
|
67
|
+
methods.each do |method|
|
68
|
+
define_method(method) { value = @attributes[method.to_s] and Date.parse(value) }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
64
72
|
def self.accessor(*methods)
|
65
73
|
reader(*methods)
|
66
74
|
methods.each do |method|
|
@@ -68,6 +76,11 @@ class Pickler
|
|
68
76
|
end
|
69
77
|
end
|
70
78
|
reader :id
|
79
|
+
|
80
|
+
def to_xml(options = nil)
|
81
|
+
@attributes.to_xml({:root => self.class.name.split('::').last.downcase}.merge(options||{}))
|
82
|
+
end
|
83
|
+
|
71
84
|
end
|
72
85
|
|
73
86
|
end
|
@@ -75,3 +88,5 @@ end
|
|
75
88
|
|
76
89
|
require 'pickler/tracker/project'
|
77
90
|
require 'pickler/tracker/story'
|
91
|
+
require 'pickler/tracker/iteration'
|
92
|
+
require 'pickler/tracker/note'
|
data/lib/pickler.rb
CHANGED
@@ -14,47 +14,8 @@ class Pickler
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def self.run(argv)
|
17
|
-
pickler
|
18
|
-
|
19
|
-
case first = argv.shift
|
20
|
-
when 'show', /^\d+$/
|
21
|
-
story = pickler.project.story(first == 'show' ? argv.shift : first)
|
22
|
-
puts story
|
23
|
-
when 'search'
|
24
|
-
stories = pickler.project.stories(*argv).group_by {|s| s.current_state}
|
25
|
-
first = true
|
26
|
-
states = Tracker::Story::STATES
|
27
|
-
states -= %w(unstarted accepted) if argv.empty?
|
28
|
-
states.each do |state|
|
29
|
-
next unless stories[state]
|
30
|
-
puts unless first
|
31
|
-
first = false
|
32
|
-
puts state.upcase
|
33
|
-
puts '-' * state.length
|
34
|
-
stories[state].each do |story|
|
35
|
-
puts "[#{story.id}] #{story.story_type.capitalize}: #{story.name}"
|
36
|
-
end
|
37
|
-
end
|
38
|
-
when 'push'
|
39
|
-
pickler.push(*argv)
|
40
|
-
when 'pull'
|
41
|
-
pickler.pull(*argv)
|
42
|
-
when 'start'
|
43
|
-
pickler.start(argv.first)
|
44
|
-
when 'finish'
|
45
|
-
pickler.finish(argv.first)
|
46
|
-
when 'help', '--help', '-h', '', nil
|
47
|
-
puts 'pickler commands: [show|start|finish] <id>, search <query>, push, pull'
|
48
|
-
else
|
49
|
-
$stderr.puts "pickler: unknown command #{first}"
|
50
|
-
exit 1
|
51
|
-
end
|
52
|
-
rescue Pickler::Error
|
53
|
-
$stderr.puts "#$!"
|
54
|
-
exit 1
|
55
|
-
rescue Interrupt
|
56
|
-
$stderr.puts "Interrupted!"
|
57
|
-
exit 130
|
17
|
+
require 'pickler/runner'
|
18
|
+
Runner.new(argv).run
|
58
19
|
end
|
59
20
|
|
60
21
|
attr_reader :directory
|
@@ -111,12 +72,6 @@ class Pickler
|
|
111
72
|
Cucumber.language['scenario']
|
112
73
|
end
|
113
74
|
|
114
|
-
def stories
|
115
|
-
project.stories(scenario_word, :includedone => true).select do |s|
|
116
|
-
s.to_s =~ /^\s*#{Regexp.escape(scenario_word)}:/ && parser.parse(s.to_s)
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
75
|
def features(*args)
|
121
76
|
if args.any?
|
122
77
|
args.map {|a| feature(a)}
|
@@ -134,29 +89,20 @@ class Pickler
|
|
134
89
|
end
|
135
90
|
|
136
91
|
def pull(*args)
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
normalize_feature(remote.to_s)
|
143
|
-
if local = l.detect {|f| f.id == remote.id}
|
144
|
-
filename = local.filename
|
145
|
-
else
|
146
|
-
next if remote.current_state == 'unstarted'
|
147
|
-
filename = features_path("#{remote.id}.feature")
|
92
|
+
if args.empty?
|
93
|
+
args = project.stories(scenario_word, :includedone => true).reject do |s|
|
94
|
+
s.current_state == 'unstarted'
|
95
|
+
end.select do |s|
|
96
|
+
s.to_s =~ /^\s*#{Regexp.escape(scenario_word)}:/ && parser.parse(s.to_s)
|
148
97
|
end
|
149
|
-
File.open(filename,'w') {|f| f.puts body}
|
150
98
|
end
|
151
|
-
nil
|
152
|
-
end
|
153
|
-
|
154
|
-
def start(*args)
|
155
99
|
args.each do |arg|
|
156
|
-
|
157
|
-
story.transition!("started") if %w(unstarted rejected).include?(story.current_state)
|
100
|
+
feature(arg).pull
|
158
101
|
end
|
159
|
-
|
102
|
+
end
|
103
|
+
|
104
|
+
def start(arg, default = nil)
|
105
|
+
feature(arg).start(default)
|
160
106
|
end
|
161
107
|
|
162
108
|
def push(*args)
|
@@ -173,23 +119,6 @@ class Pickler
|
|
173
119
|
|
174
120
|
protected
|
175
121
|
|
176
|
-
def normalize_feature(body)
|
177
|
-
return body unless ast = parser.parse(body)
|
178
|
-
feature = ast.compile
|
179
|
-
new = ''
|
180
|
-
(feature.header.chomp << "\n").each_line do |l|
|
181
|
-
new << ' ' unless new.empty?
|
182
|
-
new << l.strip << "\n"
|
183
|
-
end
|
184
|
-
feature.scenarios.each do |scenario|
|
185
|
-
new << "\n Scenario: #{scenario.name}\n"
|
186
|
-
scenario.steps.each do |step|
|
187
|
-
new << " #{step.keyword} #{step.name}\n"
|
188
|
-
end
|
189
|
-
end
|
190
|
-
new
|
191
|
-
end
|
192
|
-
|
193
122
|
end
|
194
123
|
|
195
124
|
require 'pickler/feature'
|
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.3"
|
4
4
|
|
5
5
|
s.summary = "PIvotal traCKer Liaison to cucumbER"
|
6
6
|
s.description = "Synchronize between Cucumber and Pivotal Tracker"
|
@@ -16,9 +16,12 @@ Gem::Specification.new do |s|
|
|
16
16
|
"bin/pickler",
|
17
17
|
"lib/pickler.rb",
|
18
18
|
"lib/pickler/feature.rb",
|
19
|
+
"lib/pickler/runner.rb",
|
19
20
|
"lib/pickler/tracker.rb",
|
20
21
|
"lib/pickler/tracker/project.rb",
|
21
|
-
"lib/pickler/tracker/story.rb"
|
22
|
+
"lib/pickler/tracker/story.rb",
|
23
|
+
"lib/pickler/tracker/iteration.rb",
|
24
|
+
"lib/pickler/tracker/note.rb"
|
22
25
|
]
|
23
26
|
s.add_dependency("activesupport", [">= 2.0.0"])
|
24
27
|
s.add_dependency("cucumber", [">= 0.1.9"])
|
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.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tim Pope
|
@@ -45,9 +45,12 @@ files:
|
|
45
45
|
- bin/pickler
|
46
46
|
- lib/pickler.rb
|
47
47
|
- lib/pickler/feature.rb
|
48
|
+
- lib/pickler/runner.rb
|
48
49
|
- lib/pickler/tracker.rb
|
49
50
|
- lib/pickler/tracker/project.rb
|
50
51
|
- lib/pickler/tracker/story.rb
|
52
|
+
- lib/pickler/tracker/iteration.rb
|
53
|
+
- lib/pickler/tracker/note.rb
|
51
54
|
has_rdoc: false
|
52
55
|
homepage: http://github.com/tpope/pickler
|
53
56
|
post_install_message:
|