torque 0.1.0 → 0.2.0
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.
- 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
|