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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1c7055b042760f90fba0fe791442b4ba857bec52
4
- data.tar.gz: 2e386cca466678b01c5f3e8dc7b45606285c672b
3
+ metadata.gz: 1081effd8b2bc6581372cd2a3bd38540d1b38411
4
+ data.tar.gz: bab3ee280120bb1e291e7c608aa57034b885e760
5
5
  SHA512:
6
- metadata.gz: eace72e27669cb57a907a3155a522d2159dcf12e592c4aa062ce89a4d053f3d7727c5483a322e020794d4235992584422044513c4eec863c0f0b6dac5e982443
7
- data.tar.gz: a3c738c870d46ffbc8d84fb1fb7f09ffdcdc566de1ce9ac01ffa807fff82d49fa294be4d11918ff70c766274e3dbb21327dc2511e84bef330362df7b485196e8
6
+ metadata.gz: 3ac460137f176e2720c94544e3d74d930282901e7708ba6ea3e7d7c5d218ee23c14cd2769391a3fb89bd4537ace39f2b0bd5471a26bd006d47595d3cbfcd5ae8
7
+ data.tar.gz: 561657b3d705c34150fffcf6fe2b09d712e753df97ce7ed1ff07052a75f45d55dc1b7547a90230a853131057952d1aa87eeb0b7e07973d059a5d8801c05ea649
data/VERSION CHANGED
@@ -1,3 +1,3 @@
1
1
  major:0
2
- minor:1
2
+ minor:2
3
3
  patch:0
data/bin/torque CHANGED
@@ -20,9 +20,12 @@ options = {}
20
20
 
21
21
  option_parser = OptionParser.new do |opts|
22
22
 
23
- help_message = "This script will fail unless it is run in a directory that was configured for Torque by running " \
24
- "'torque config' (see 'torque config --help' for more). To change the directory torque runs in, change your " \
25
- "current working directory, or use the --root option"
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 1
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 2) : (exit 3)
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 4) : (exit 5)
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 6
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 7
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 8
182
+ exit 9
165
183
  rescue ArgumentError => e
166
184
  puts "ABORTING"
167
185
  puts e.message
168
- exit 9
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 - '#{@settings.filter_string}'\n"
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
- # Adds each story
61
- stories.sort! { |s1, s2| -(s1.date_accepted <=> s2.date_accepted) }
62
- stories.length.times do |i|
63
- story = stories[i]
58
+ if iterations
59
+ # Adds each iteration
64
60
 
65
- notes_string += "#{story.story_id}\n"
66
- notes_string += "#{story.name}\n"
67
- notes_string += "Accepted on "+story.date_accepted.strftime(@date_format)+"\n"
68
- notes_string += "https://www.pivotaltracker.com/story/show/#{story.story_id}\n"
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
- print_if_verbose "[Torque] (#{story.date_accepted.strftime(@date_format)}) #{story.name}"
68
+ print_if_verbose "[Torque]"
69
+ print_if_verbose "[Torque] -- Iteration #{iteration.number} --"
71
70
 
72
- descArray = story.description.split("\n")
73
- descArray.length.times do |i|
74
- notes_string += "\t"+descArray[i]+"\n"
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
- notes_string += "\n"
81
+ stories = PivotalHTMLParser.new.process_project_date_filter(project_html, @settings.accept_from, @settings.accept_to)
78
82
 
79
- end
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
- print_if_verbose "[Torque]"
82
- print_if_verbose "[Torque] Added #{stories.length} stories"
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
- print_if_not_silent "Generating release notes: " \
148
- + (@settings.custom_date_range ? "Custom " : "") \
149
- + @settings.accept_from.strftime(@date_format) + " - " \
150
- + @settings.accept_to.strftime(@date_format) \
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
- @settings.filters_on \
155
- ? project_html = pivotal.get_project_stories(@settings.project, @settings.filter_string) \
156
- : project_html = pivotal.get_project_stories(@settings.project)
157
- notes_string = generate_notes(project_html, project_name)
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
@@ -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
- # Sends a request through the Pivotal Tracker API
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
- # Returns a string of html data from Pivotal Tracker with data on each of a user's projects
79
- def get_project_data
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 != "200"
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
- "API token, and/or internet connection"
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
- story_html_array = project_html.search('story')
39
- story_list = []
52
+ iteration_html_array = project_html.search('iteration')
53
+ iteration_list = []
40
54
 
41
- story_html_array.each do
42
- |story_element|
43
- story_list << process_story(story_element)
55
+ iteration_html_array.each do
56
+ |iteration_element|
57
+ iteration_list << process_iteration_element(iteration_element)
44
58
  end
45
59
 
46
- story_list
60
+ iteration_list
47
61
  end
48
62
 
49
-
50
63
  private
51
- # story: A Nokogiri::XML::Element
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 story
54
- def process_story(story_element)
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
- path_for_default_date_range
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")
@@ -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 boolean options
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.1.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-05 00:00:00.000000000 Z
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