torque 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/bin/torque +28 -10
- data/lib/torque.rb +79 -32
- data/lib/torque/iteration.rb +48 -0
- data/lib/torque/pivotal.rb +44 -8
- data/lib/torque/pivotal_html_parser.rb +60 -10
- data/lib/torque/record_pathname_settings.rb +20 -10
- data/lib/torque/settings.rb +19 -2
- data/lib/torque/story.rb +9 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1081effd8b2bc6581372cd2a3bd38540d1b38411
|
4
|
+
data.tar.gz: bab3ee280120bb1e291e7c608aa57034b885e760
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ac460137f176e2720c94544e3d74d930282901e7708ba6ea3e7d7c5d218ee23c14cd2769391a3fb89bd4537ace39f2b0bd5471a26bd006d47595d3cbfcd5ae8
|
7
|
+
data.tar.gz: 561657b3d705c34150fffcf6fe2b09d712e753df97ce7ed1ff07052a75f45d55dc1b7547a90230a853131057952d1aa87eeb0b7e07973d059a5d8801c05ea649
|
data/VERSION
CHANGED
data/bin/torque
CHANGED
@@ -20,9 +20,12 @@ options = {}
|
|
20
20
|
|
21
21
|
option_parser = OptionParser.new do |opts|
|
22
22
|
|
23
|
-
help_message = "
|
24
|
-
|
25
|
-
|
23
|
+
help_message = "The main executable for the Torque gem. Generates release notes automatically for your Pivotal " \
|
24
|
+
"Tracker project.\n"
|
25
|
+
help_message += "\n"
|
26
|
+
help_message += "This script must be run in a directory that has been configured for Torque by running 'torque " \
|
27
|
+
"config' in that directory (see 'torque config --help' for more). You can use the --root option to make Torque " \
|
28
|
+
"run from a directory different from your working directory."
|
26
29
|
|
27
30
|
opts.banner = "\nUsage:"
|
28
31
|
opts.banner += "\n torque [options]"
|
@@ -52,6 +55,11 @@ option_parser = OptionParser.new do |opts|
|
|
52
55
|
options[:help] = arg
|
53
56
|
end
|
54
57
|
|
58
|
+
opts.on("-i", "--iterations NUMBER", "Compiles notes for NUMBER of previous iterations") do
|
59
|
+
|arg|
|
60
|
+
options[:iterations] = arg
|
61
|
+
end
|
62
|
+
|
55
63
|
opts.on("--label LABELS", "Filters stories by a (comma-separated) list of labels") do
|
56
64
|
|arg|
|
57
65
|
options[:label] = arg
|
@@ -118,6 +126,16 @@ begin
|
|
118
126
|
puts "Unknown arguments: #{ARGV.join(", ")}"
|
119
127
|
puts option_parser.help
|
120
128
|
exit
|
129
|
+
|
130
|
+
elsif (options.has_key?(:accept_to) || options.has_key?(:accept_from)) && options.has_key?(:iterations)
|
131
|
+
err_str = "Conflicting options: "
|
132
|
+
err_str += "-t/--to, " if options.has_key? :accept_to
|
133
|
+
err_str += "-f/--from, " if options.has_key? :accept_from
|
134
|
+
err_str += "-i/iterations. "
|
135
|
+
err_str += "Cannot use a custom date range with 'iterations' option"
|
136
|
+
puts err_str
|
137
|
+
# puts option_parser.help
|
138
|
+
exit 1
|
121
139
|
end
|
122
140
|
|
123
141
|
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
|
@@ -131,7 +149,7 @@ end
|
|
131
149
|
if !Torque::Pivotal.connection?
|
132
150
|
puts "ABORTING"
|
133
151
|
puts "Cannot connect to www.pivotaltracker.com. A connection is required to use Torque"
|
134
|
-
exit
|
152
|
+
exit 2
|
135
153
|
end
|
136
154
|
|
137
155
|
# Runs Torque, handling errors
|
@@ -142,28 +160,28 @@ rescue Torque::MissingTokenError, Torque::InvalidTokenError => e
|
|
142
160
|
puts "ABORTING"
|
143
161
|
puts e.message
|
144
162
|
puts "Run 'torque config --token' to set your Pivotal Tracker API token"
|
145
|
-
(e.is_a? Torque::MissingTokenError) ? (exit
|
163
|
+
(e.is_a? Torque::MissingTokenError) ? (exit 3) : (exit 4)
|
146
164
|
rescue Torque::MissingProjectError, Torque::InvalidProjectError => e
|
147
165
|
puts "ABORTING"
|
148
166
|
puts e.message
|
149
167
|
puts "Run 'torque project' to set your Pivotal Tracker project"
|
150
|
-
(e.is_a? Torque::MissingProjectError) ? (exit
|
168
|
+
(e.is_a? Torque::MissingProjectError) ? (exit 5) : (exit 6)
|
151
169
|
rescue Torque::PivotalAPIError => e
|
152
170
|
puts "ABORTING"
|
153
171
|
puts e.message
|
154
|
-
exit
|
172
|
+
exit 7
|
155
173
|
rescue Torque::MissingTorqueInfoFileError => e
|
156
174
|
puts "ABORTING"
|
157
175
|
puts e.message
|
158
176
|
puts "Run 'torque config' in this directory, or change your working directory"
|
159
|
-
exit
|
177
|
+
exit 8
|
160
178
|
rescue Torque::MissingOutputDirectoryError => e
|
161
179
|
puts "ABORTING"
|
162
180
|
puts e.message
|
163
181
|
puts "Run 'torque config -o [directory]' to create/change your output directory"
|
164
|
-
exit
|
182
|
+
exit 9
|
165
183
|
rescue ArgumentError => e
|
166
184
|
puts "ABORTING"
|
167
185
|
puts e.message
|
168
|
-
exit
|
186
|
+
exit 10
|
169
187
|
end
|
data/lib/torque.rb
CHANGED
@@ -29,22 +29,20 @@ class Torque
|
|
29
29
|
##
|
30
30
|
# @param project_html An html string containing all the project's stories
|
31
31
|
# @param project_name The name of the current project
|
32
|
+
# @param iterations True if the stories are organized into iterations, else false
|
32
33
|
#
|
33
34
|
# Returns a string comprising the release notes document
|
34
|
-
def generate_notes(project_html, project_name)
|
35
|
+
def generate_notes(project_html, project_name, iterations=false)
|
35
36
|
|
36
37
|
notes_string = ""
|
37
38
|
|
38
|
-
# Parses stories from html, filters them by date and returns them in an array
|
39
|
-
stories = PivotalHTMLParser.new.process_project_date_filter(project_html, @settings.accept_from, @settings.accept_to)
|
40
|
-
|
41
39
|
# Adds the header
|
42
40
|
notes_string += Date.today.strftime(@date_format)+"\n"
|
43
41
|
notes_string += "Release Notes\n"
|
44
42
|
notes_string += "Project #{@settings.project} - '#{project_name}'\n"
|
45
43
|
|
46
|
-
if @settings.filters_on
|
47
|
-
notes_string += "Filter
|
44
|
+
if @settings.filters_on && !@settings.iterations
|
45
|
+
notes_string += "Filter '#{@settings.filter_string}'\n"
|
48
46
|
end
|
49
47
|
|
50
48
|
if @settings.custom_date_range
|
@@ -53,33 +51,44 @@ class Torque
|
|
53
51
|
notes_string += "("+@settings.accept_from.strftime(@date_format)+" - "+@settings.accept_to.strftime(@date_format)+")\n"
|
54
52
|
end
|
55
53
|
|
56
|
-
notes_string += "\nThese notes were generated with Torque, a Pivotal Tracker command line utility"
|
57
54
|
notes_string += "\n"
|
55
|
+
notes_string += "These notes were generated with Torque, a Pivotal Tracker command line utility\n"
|
58
56
|
notes_string += "\n"
|
59
57
|
|
60
|
-
|
61
|
-
|
62
|
-
stories.length.times do |i|
|
63
|
-
story = stories[i]
|
58
|
+
if iterations
|
59
|
+
# Adds each iteration
|
64
60
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
61
|
+
iteration_list = PivotalHTMLParser.new.process_project_iterations(project_html)
|
62
|
+
|
63
|
+
iteration_list = Iteration.sort_list(iteration_list)
|
64
|
+
iteration_list.each do |iteration|
|
65
|
+
notes_string += "-- Iteration #{iteration.number} --\n"
|
66
|
+
notes_string += "\n"
|
69
67
|
|
70
|
-
|
68
|
+
print_if_verbose "[Torque]"
|
69
|
+
print_if_verbose "[Torque] -- Iteration #{iteration.number} --"
|
71
70
|
|
72
|
-
|
73
|
-
|
74
|
-
|
71
|
+
iteration.sort_stories
|
72
|
+
iteration.stories.each do |story|
|
73
|
+
notes_string += generate_story_string(story)
|
74
|
+
print_if_verbose "[Torque] (#{story.date_accepted.strftime(@date_format)}) #{story.name}"
|
75
|
+
end
|
75
76
|
end
|
77
|
+
|
78
|
+
else
|
79
|
+
# Adds each story
|
76
80
|
|
77
|
-
|
81
|
+
stories = PivotalHTMLParser.new.process_project_date_filter(project_html, @settings.accept_from, @settings.accept_to)
|
78
82
|
|
79
|
-
|
83
|
+
stories = Story.sort_list(stories)
|
84
|
+
stories.each do |story|
|
85
|
+
notes_string += generate_story_string(story)
|
86
|
+
print_if_verbose "[Torque] (#{story.date_accepted.strftime(@date_format)}) #{story.name}"
|
87
|
+
end
|
80
88
|
|
81
|
-
|
82
|
-
|
89
|
+
print_if_verbose "[Torque]"
|
90
|
+
print_if_verbose "[Torque] Added #{stories.length} stories"
|
91
|
+
end
|
83
92
|
|
84
93
|
notes_string
|
85
94
|
end
|
@@ -144,17 +153,34 @@ class Torque
|
|
144
153
|
|
145
154
|
print_if_not_silent "Filter: '#{@settings.filter_string}'" if @settings.filters_on
|
146
155
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
156
|
+
if @settings.iterations
|
157
|
+
print_if_not_silent "Generating release notes: Last #{@settings.iterations} iterations..."
|
158
|
+
|
159
|
+
else
|
160
|
+
print_if_not_silent "Generating release notes: " \
|
161
|
+
+ (@settings.custom_date_range ? "Custom " : "") \
|
162
|
+
+ @settings.accept_from.strftime(@date_format) + " - " \
|
163
|
+
+ @settings.accept_to.strftime(@date_format) \
|
164
|
+
+ "..."
|
165
|
+
end
|
152
166
|
|
153
167
|
pivotal = Pivotal.new(@settings.token)
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
168
|
+
|
169
|
+
project_html = ""
|
170
|
+
|
171
|
+
if @settings.iterations
|
172
|
+
puts "WARNING: Filters are currently not supported when running with iterations" if @settings.filters_on
|
173
|
+
project_html = pivotal.get_project_iterations(@settings.project, @settings.iterations)
|
174
|
+
else
|
175
|
+
if @settings.filters_on
|
176
|
+
project_html = pivotal.get_project_stories(@settings.project, @settings.filter_string)
|
177
|
+
else
|
178
|
+
project_html = pivotal.get_project_stories(@settings.project)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
iterations_on = (!@settings.iterations.nil?)
|
183
|
+
notes_string = generate_notes(project_html, project_name, iterations_on)
|
158
184
|
|
159
185
|
# Writes the notes to file
|
160
186
|
|
@@ -188,6 +214,27 @@ class Torque
|
|
188
214
|
|
189
215
|
private
|
190
216
|
|
217
|
+
# story: A Story object
|
218
|
+
#
|
219
|
+
# Generates a string of release notes for the story
|
220
|
+
# If verbose, prints a statement saying the story was added to stdout
|
221
|
+
def generate_story_string(story)
|
222
|
+
notes_string = ""
|
223
|
+
|
224
|
+
notes_string += "#{story.story_id}\n"
|
225
|
+
notes_string += "#{story.name}\n"
|
226
|
+
notes_string += "Accepted on "+story.date_accepted.strftime(@date_format)+"\n"
|
227
|
+
notes_string += "https://www.pivotaltracker.com/story/show/#{story.story_id}\n"
|
228
|
+
|
229
|
+
descArray = story.description.split("\n")
|
230
|
+
descArray.length.times do |i|
|
231
|
+
notes_string += "\t"+descArray[i]+"\n"
|
232
|
+
end
|
233
|
+
|
234
|
+
notes_string += "\n"
|
235
|
+
notes_string
|
236
|
+
end
|
237
|
+
|
191
238
|
# Prints a message if silent is not on
|
192
239
|
def print_if_not_silent(msg)
|
193
240
|
puts msg unless @settings.silent
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class Torque
|
2
|
+
|
3
|
+
##
|
4
|
+
# Stores the data for one finished iteration of a PT project
|
5
|
+
class Iteration
|
6
|
+
|
7
|
+
##
|
8
|
+
# The iteration's number
|
9
|
+
attr_reader :number
|
10
|
+
|
11
|
+
##
|
12
|
+
# The iteration's list of stories
|
13
|
+
attr_reader :stories
|
14
|
+
|
15
|
+
##
|
16
|
+
# @param number The iteration's number
|
17
|
+
# @param stories A list of stories in the iteration
|
18
|
+
def initialize(number, stories=[])
|
19
|
+
|
20
|
+
@number = number
|
21
|
+
@stories = stories
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# @param story A story
|
26
|
+
#
|
27
|
+
# Adds story to the list of stories
|
28
|
+
def add_story(story)
|
29
|
+
@stories << story
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Sorts the list of stories from most to least recent
|
34
|
+
def sort_stories
|
35
|
+
@stories = Story.sort_list(stories)
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# @param iterations A list of iterations
|
40
|
+
#
|
41
|
+
# Returns a list of iterations sorted from most to least recent
|
42
|
+
def self.sort_list(iterations)
|
43
|
+
sorted = iterations.sort {|i1, i2| Integer(i2.number) - Integer(i1.number) }
|
44
|
+
sorted
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
data/lib/torque/pivotal.rb
CHANGED
@@ -34,6 +34,35 @@ class Torque
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
##
|
38
|
+
# Sends a request through the Pivotal Tracker API
|
39
|
+
#
|
40
|
+
# Returns a string of html data from Pivotal Tracker with data on each of a user's projects
|
41
|
+
def get_project_data
|
42
|
+
|
43
|
+
host="pivotaltracker.com"
|
44
|
+
port=80
|
45
|
+
url="http://www.pivotaltracker.com/services/v3/projects"
|
46
|
+
headers={'X-TrackerToken'=>@token}
|
47
|
+
|
48
|
+
url = URI.escape(url)
|
49
|
+
|
50
|
+
response=Net::HTTP.new(host, port).get(url, headers)
|
51
|
+
|
52
|
+
# Handles network errors
|
53
|
+
if response.code == "401"
|
54
|
+
raise InvalidTokenError.new "The Pivotal Tracker API token supplied is not valid"
|
55
|
+
|
56
|
+
elsif response.code != "200"
|
57
|
+
raise PivotalAPIError.new(
|
58
|
+
"The Pivotal Tracker API responded with an unexpected error code: #{response.code}. Check your " \
|
59
|
+
"API token, and/or internet connection"
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
response.body
|
64
|
+
end
|
65
|
+
|
37
66
|
##
|
38
67
|
# @param project The ID of the Pivotal Tracker project from which to get data
|
39
68
|
# @param filter_string A string filter for the stories
|
@@ -73,14 +102,18 @@ class Torque
|
|
73
102
|
end
|
74
103
|
|
75
104
|
##
|
76
|
-
#
|
105
|
+
# @param project The ID of the Pivotal Tracker project from which to get data
|
106
|
+
# @param number The number of project iterations to fetch
|
77
107
|
#
|
78
|
-
#
|
79
|
-
|
108
|
+
# Sends a request throgh the Pivotal Tracker API
|
109
|
+
#
|
110
|
+
# Returns a string of html data from Pivotal Tracker with data on previous finished project iterations
|
111
|
+
def get_project_iterations(project, number=1)
|
80
112
|
|
113
|
+
# Polls story data from pivotal tracker
|
81
114
|
host="pivotaltracker.com"
|
82
115
|
port=80
|
83
|
-
url="http://www.pivotaltracker.com/services/v3/projects"
|
116
|
+
url="http://www.pivotaltracker.com/services/v3/projects/#{project}/iterations/done?offset=-#{number}"
|
84
117
|
headers={'X-TrackerToken'=>@token}
|
85
118
|
|
86
119
|
url = URI.escape(url)
|
@@ -89,18 +122,21 @@ class Torque
|
|
89
122
|
|
90
123
|
# Handles network errors
|
91
124
|
if response.code == "401"
|
92
|
-
raise InvalidTokenError.new "The Pivotal Tracker API token supplied is not valid"
|
125
|
+
raise InvalidTokenError.new "The Pivotal Tracker API token supplied is not valid for project #{project}"
|
93
126
|
|
94
|
-
elsif response.code
|
127
|
+
elsif response.code == "500"
|
128
|
+
raise InvalidProjectError.new "The Pivotal Tracker project ID supplied, #{project}, is invalid"
|
129
|
+
|
130
|
+
elsif !(["2","3"].member? response.code[0])
|
95
131
|
raise PivotalAPIError.new(
|
96
132
|
"The Pivotal Tracker API responded with an unexpected error code: #{response.code}. Check your " \
|
97
|
-
|
133
|
+
+ "project ID, API token, and/or internet connection"
|
98
134
|
)
|
135
|
+
|
99
136
|
end
|
100
137
|
|
101
138
|
response.body
|
102
139
|
end
|
103
140
|
|
104
|
-
|
105
141
|
end
|
106
142
|
end
|
@@ -3,8 +3,10 @@
|
|
3
3
|
|
4
4
|
require 'date'
|
5
5
|
require 'nokogiri'
|
6
|
+
require_relative 'iteration'
|
6
7
|
require_relative 'story'
|
7
8
|
|
9
|
+
|
8
10
|
class Torque
|
9
11
|
class PivotalHTMLParser
|
10
12
|
|
@@ -29,29 +31,41 @@ class Torque
|
|
29
31
|
|
30
32
|
end
|
31
33
|
|
34
|
+
##
|
32
35
|
# @param project_html_string An html string containing the story data for a Pivotal Tracker project
|
33
36
|
#
|
34
37
|
# Returns a list of all Story objects parsed from project_html_string (least recent to most recent date accepted)
|
35
38
|
def process_project(project_html_string)
|
36
39
|
|
40
|
+
project_element = Nokogiri::HTML(project_html_string)
|
41
|
+
process_project_element(project_element)
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
##
|
46
|
+
# @param project_html_string An html string containing iteration data for a Pivotal Tracker project
|
47
|
+
#
|
48
|
+
# Returns a list of Iteration objects parsed from project_html_string (least recent to most recent iteration)
|
49
|
+
def process_project_iterations(project_html_string)
|
50
|
+
|
37
51
|
project_html = Nokogiri::HTML(project_html_string)
|
38
|
-
|
39
|
-
|
52
|
+
iteration_html_array = project_html.search('iteration')
|
53
|
+
iteration_list = []
|
40
54
|
|
41
|
-
|
42
|
-
|
|
43
|
-
|
55
|
+
iteration_html_array.each do
|
56
|
+
|iteration_element|
|
57
|
+
iteration_list << process_iteration_element(iteration_element)
|
44
58
|
end
|
45
59
|
|
46
|
-
|
60
|
+
iteration_list
|
47
61
|
end
|
48
62
|
|
49
|
-
|
50
63
|
private
|
51
|
-
|
64
|
+
|
65
|
+
# story_element: A Nokogiri::XML::Element representing a story (root element - "story")
|
52
66
|
#
|
53
|
-
# Returns a Story object based off the information in the
|
54
|
-
def
|
67
|
+
# Returns a Story object based off the information in the element
|
68
|
+
def process_story_element(story_element)
|
55
69
|
|
56
70
|
story_hash = {}
|
57
71
|
|
@@ -63,5 +77,41 @@ class Torque
|
|
63
77
|
Story.create(story_hash)
|
64
78
|
end
|
65
79
|
|
80
|
+
# project_element: A Nokogiri::XML::Element representing a project (root element - "stories")
|
81
|
+
#
|
82
|
+
# Returns a list of all Story objects parsed from project_element (least recent to most recent date accepted)
|
83
|
+
def process_project_element(project_element)
|
84
|
+
|
85
|
+
story_html_array = project_element.search('story')
|
86
|
+
story_list = []
|
87
|
+
|
88
|
+
story_html_array.each do
|
89
|
+
|story_element|
|
90
|
+
story_list << process_story_element(story_element)
|
91
|
+
end
|
92
|
+
|
93
|
+
story_list
|
94
|
+
end
|
95
|
+
|
96
|
+
# iteraton_element: A Nokogiri::XML::Element representing an iteration (root element - "iteration")
|
97
|
+
#
|
98
|
+
# Returns an Interation object based off the object in the element
|
99
|
+
def process_iteration_element(iteration_element)
|
100
|
+
|
101
|
+
iter_number = nil
|
102
|
+
stories = []
|
103
|
+
|
104
|
+
iteration_element.children.each do
|
105
|
+
|child|
|
106
|
+
if child.name == "id"
|
107
|
+
iter_number = child.children[0].text
|
108
|
+
elsif child.name == "stories"
|
109
|
+
stories = process_project_element(child)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
Iteration.new(iter_number, stories)
|
114
|
+
end
|
115
|
+
|
66
116
|
end
|
67
117
|
end
|
@@ -9,12 +9,13 @@ class Torque
|
|
9
9
|
##
|
10
10
|
# @param output_dir The path to the release notes output directory
|
11
11
|
# @param project The project id
|
12
|
-
# @param custom True if this is a run with custom settings, else false
|
12
|
+
# @param custom True if this is a run with custom date settings, else false
|
13
13
|
# @param fs An instance of the FileSystem class
|
14
|
-
def initialize(output_dir, project, custom, fs)
|
14
|
+
def initialize(output_dir, project, custom, iterations, fs)
|
15
15
|
@output_dir = output_dir
|
16
16
|
@project = project
|
17
17
|
@custom = custom
|
18
|
+
@iterations = iterations
|
18
19
|
@fs = fs
|
19
20
|
end
|
20
21
|
|
@@ -32,18 +33,13 @@ class Torque
|
|
32
33
|
def generate_record_path
|
33
34
|
if @custom
|
34
35
|
path_for_custom_date_range
|
36
|
+
elsif @iterations
|
37
|
+
path_for_iterations
|
35
38
|
else
|
36
|
-
|
39
|
+
path_for_default
|
37
40
|
end
|
38
41
|
end
|
39
42
|
|
40
|
-
# The path to the record of the release notes file if default dates were used
|
41
|
-
def path_for_default_date_range
|
42
|
-
title = "#{@project}-#{current_date_string}"
|
43
|
-
@path="#{@output_dir}/previous/#{title}.txt"
|
44
|
-
@path
|
45
|
-
end
|
46
|
-
|
47
43
|
# The path to the record of the release notes file if a custom date range was used
|
48
44
|
def path_for_custom_date_range
|
49
45
|
title = "#{@project}-#{current_date_string}"
|
@@ -71,6 +67,20 @@ class Torque
|
|
71
67
|
@path
|
72
68
|
end
|
73
69
|
|
70
|
+
def path_for_iterations
|
71
|
+
title = "#{@project}-#{current_date_string}"
|
72
|
+
title += "-iter-#{@iterations}"
|
73
|
+
@path = "#{@output_dir}/previous/#{title}.txt"
|
74
|
+
@path
|
75
|
+
end
|
76
|
+
|
77
|
+
# The path to the record of the release notes file if default dates were used
|
78
|
+
def path_for_default
|
79
|
+
title = "#{@project}-#{current_date_string}"
|
80
|
+
@path = "#{@output_dir}/previous/#{title}.txt"
|
81
|
+
@path
|
82
|
+
end
|
83
|
+
|
74
84
|
# A string representing the current date: YYYY-MM-DD
|
75
85
|
def current_date_string
|
76
86
|
Date.today.strftime("%Y-%m-%d")
|
data/lib/torque/settings.rb
CHANGED
@@ -61,6 +61,10 @@ class Torque
|
|
61
61
|
# True if filters are being used for the stories, else false
|
62
62
|
attr_reader :filters_on
|
63
63
|
|
64
|
+
##
|
65
|
+
# The number of iterations of the project to generate notes for, or nil if not using iterations
|
66
|
+
attr_reader :iterations
|
67
|
+
|
64
68
|
##
|
65
69
|
# The path to the .last-run file in the records directory
|
66
70
|
attr_reader :last_run_path
|
@@ -111,7 +115,7 @@ class Torque
|
|
111
115
|
@root_dir = @options[:root_dir] || "."
|
112
116
|
@root_dir = File.expand_path(@root_dir)
|
113
117
|
|
114
|
-
# Handles
|
118
|
+
# Handles basic options
|
115
119
|
|
116
120
|
@email = @options[:email] || false
|
117
121
|
@silent = @options[:silent] || false
|
@@ -188,6 +192,19 @@ class Torque
|
|
188
192
|
@accept_from, @accept_to = date_settings.get_dates
|
189
193
|
@custom_date_range = date_settings.custom_date_range?
|
190
194
|
|
195
|
+
# Determines the number of iterations to generate for, throwing an error if it is invalid
|
196
|
+
|
197
|
+
@iterations = @options[:iterations]
|
198
|
+
|
199
|
+
if @iterations
|
200
|
+
begin
|
201
|
+
@iterations = Integer(@iterations)
|
202
|
+
raise ArgumentError.new if @iterations <= 0
|
203
|
+
rescue ArgumentError
|
204
|
+
raise ArgumentError.new "Invalid number of iterations: #{@iterations}"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
191
208
|
# Sets the path to the main "release-notes.txt" file
|
192
209
|
|
193
210
|
@current_notes_path = "#{output_dir}/release-notes.txt"
|
@@ -198,7 +215,7 @@ class Torque
|
|
198
215
|
|
199
216
|
# Determines the path name to use for the record of the output file
|
200
217
|
|
201
|
-
record_pathname_settings = RecordPathnameSettings.new(@output_dir, @project, @custom, @fs)
|
218
|
+
record_pathname_settings = RecordPathnameSettings.new(@output_dir, @project, @custom, @iterations, @fs)
|
202
219
|
@record_path = record_pathname_settings.get_path
|
203
220
|
|
204
221
|
end
|
data/lib/torque/story.rb
CHANGED
@@ -63,6 +63,8 @@ class Torque
|
|
63
63
|
end
|
64
64
|
private :handle_nil
|
65
65
|
|
66
|
+
# TODO Add an 'owner' field
|
67
|
+
|
66
68
|
##
|
67
69
|
# Parses the story's fields from its html hash
|
68
70
|
def parse
|
@@ -100,6 +102,13 @@ class Torque
|
|
100
102
|
self
|
101
103
|
end
|
102
104
|
|
105
|
+
##
|
106
|
+
# Sorts a list of stories so that the most recent come first
|
107
|
+
def self.sort_list(stories)
|
108
|
+
sorted = stories.sort { |s1, s2| -(s1.date_accepted <=> s2.date_accepted) }
|
109
|
+
sorted
|
110
|
+
end
|
111
|
+
|
103
112
|
def to_s
|
104
113
|
return "Story("+@name+", "+"#{@story_id}"+")"
|
105
114
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: torque
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nico Adams, Scrimmage
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-08-
|
11
|
+
date: 2013-08-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: highline
|
@@ -103,6 +103,7 @@ files:
|
|
103
103
|
- lib/torque/error/missing_torque_info_file_error.rb
|
104
104
|
- lib/torque/error/pivotal_api_error.rb
|
105
105
|
- lib/torque/file_system.rb
|
106
|
+
- lib/torque/iteration.rb
|
106
107
|
- lib/torque/mailer.rb
|
107
108
|
- lib/torque/pivotal.rb
|
108
109
|
- lib/torque/pivotal_html_parser.rb
|