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 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