torque 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE +18 -0
- data/README.md +116 -0
- data/VERSION +3 -0
- data/bin/config +257 -0
- data/bin/email +172 -0
- data/bin/project +136 -0
- data/bin/torque +150 -0
- data/lib/torque.rb +190 -0
- data/lib/torque/date_settings.rb +166 -0
- data/lib/torque/error/invalid_project_error.rb +10 -0
- data/lib/torque/error/invalid_token_error.rb +10 -0
- data/lib/torque/error/missing_output_directory_error.rb +7 -0
- data/lib/torque/error/missing_project_error.rb +8 -0
- data/lib/torque/error/missing_token_error.rb +8 -0
- data/lib/torque/error/missing_torque_info_file_error.rb +8 -0
- data/lib/torque/error/pivotal_api_error.rb +8 -0
- data/lib/torque/file_system.rb +70 -0
- data/lib/torque/mailer.rb +62 -0
- data/lib/torque/pivotal.rb +98 -0
- data/lib/torque/pivotal_html_parser.rb +67 -0
- data/lib/torque/project/project.rb +25 -0
- data/lib/torque/project/project_manager.rb +140 -0
- data/lib/torque/record_pathname_settings.rb +80 -0
- data/lib/torque/settings.rb +164 -0
- data/lib/torque/story.rb +108 -0
- data/lib/torque/torque_info_parser.rb +230 -0
- data/lib/torque/version.rb +24 -0
- metadata +145 -0
data/lib/torque/story.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
class Torque
|
2
|
+
|
3
|
+
##
|
4
|
+
# Stores the data to represent a Pivotal Tracker story
|
5
|
+
class Story
|
6
|
+
|
7
|
+
##
|
8
|
+
# The current state of the story (finished, accepted, etc)
|
9
|
+
attr_reader :current_state
|
10
|
+
|
11
|
+
##
|
12
|
+
# The date that the story was accepted, or nil if it has not been accepted yet
|
13
|
+
attr_reader :date_accepted
|
14
|
+
|
15
|
+
##
|
16
|
+
# The story description
|
17
|
+
attr_reader :description
|
18
|
+
|
19
|
+
##
|
20
|
+
# The estimate for the story
|
21
|
+
attr_reader :estimate
|
22
|
+
|
23
|
+
##
|
24
|
+
# The labels for the story
|
25
|
+
attr_reader :labels
|
26
|
+
|
27
|
+
##
|
28
|
+
# The name of the story
|
29
|
+
attr_reader :name
|
30
|
+
|
31
|
+
##
|
32
|
+
# The ID of the story's project
|
33
|
+
attr_reader :project_id
|
34
|
+
|
35
|
+
##
|
36
|
+
# The story's ID
|
37
|
+
attr_reader :story_id
|
38
|
+
|
39
|
+
##
|
40
|
+
# The story's type (feature, chore, etc)
|
41
|
+
attr_reader :story_type
|
42
|
+
|
43
|
+
##
|
44
|
+
# @param html_hash A hash (of html elements keyed by their tags) generated from Pivotal Tracker story html
|
45
|
+
#
|
46
|
+
# Returns a new story whose fields were parsed from the html_hash provided
|
47
|
+
def self.create(html_hash)
|
48
|
+
Story.new(html_hash).parse
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# html_hash:: A hash (of html elements keyed by their tags) generated from Pivotal Tracker story html
|
53
|
+
#
|
54
|
+
# Creates a new story without parsing its fields
|
55
|
+
def initialize(html_hash)
|
56
|
+
@html_hash = html_hash
|
57
|
+
end
|
58
|
+
|
59
|
+
# If the second input is an empty array, returns the first input
|
60
|
+
# Else returns the index 0 of the second
|
61
|
+
def handle_nil(default, value)
|
62
|
+
(value.nil? ? default : value)
|
63
|
+
end
|
64
|
+
private :handle_nil
|
65
|
+
|
66
|
+
##
|
67
|
+
# Parses the story's fields from its html hash
|
68
|
+
def parse
|
69
|
+
html_hash = @html_hash
|
70
|
+
|
71
|
+
# Default values
|
72
|
+
@current_state = ""
|
73
|
+
@date_accepted = nil # Will be a date
|
74
|
+
@description = ""
|
75
|
+
@estimate = -1
|
76
|
+
@labels = "" # Will be an array
|
77
|
+
@name = ""
|
78
|
+
@project_id = -1
|
79
|
+
@story_id = -1
|
80
|
+
@story_type = ""
|
81
|
+
|
82
|
+
@current_state = handle_nil(@current_state, html_hash["current_state"])
|
83
|
+
@description = handle_nil(@description, html_hash["description"])
|
84
|
+
@name = handle_nil(@name, html_hash["name"])
|
85
|
+
@story_type = handle_nil(@story_type, html_hash["story_type"])
|
86
|
+
|
87
|
+
@estimate = Integer(handle_nil(@estimate, html_hash["estimate"]))
|
88
|
+
@project_id = Integer(handle_nil(@project_id, html_hash["project_id"]))
|
89
|
+
@story_id = Integer(handle_nil(@story_id, html_hash["id"]))
|
90
|
+
|
91
|
+
@labels = handle_nil(@labels, html_hash["labels"])
|
92
|
+
@labels = @labels.split(",")
|
93
|
+
|
94
|
+
date_acceptedString = html_hash["accepted_at"]
|
95
|
+
if(date_acceptedString != nil && date_acceptedString != "")
|
96
|
+
@date_accepted = Date.strptime(date_acceptedString, "%Y/%m/%d")
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns the generated story
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
def to_s()
|
104
|
+
return "Story("+@name+", "+"#{@story_id}"+")"
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
require_relative "file_system"
|
2
|
+
require_relative "error/missing_torque_info_file_error"
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
class Torque
|
6
|
+
|
7
|
+
##
|
8
|
+
# Parses the raw contents of the torque info file
|
9
|
+
# Stores each of the fields in .torqueinfo.yaml as publicly accessible fields
|
10
|
+
class TorqueInfoParser
|
11
|
+
|
12
|
+
##
|
13
|
+
# The email_address field (String)
|
14
|
+
attr_reader :email_address
|
15
|
+
|
16
|
+
##
|
17
|
+
# The email_password field (String)
|
18
|
+
attr_reader :email_password
|
19
|
+
|
20
|
+
##
|
21
|
+
# The email_to field (String or Array)
|
22
|
+
attr_reader :email_to
|
23
|
+
|
24
|
+
##
|
25
|
+
# The project field (Fixnum)
|
26
|
+
attr_reader :project
|
27
|
+
|
28
|
+
##
|
29
|
+
# The output_dir field (String)
|
30
|
+
attr_reader :output_dir
|
31
|
+
|
32
|
+
##
|
33
|
+
# The token field (String)
|
34
|
+
attr_reader :token
|
35
|
+
|
36
|
+
##
|
37
|
+
# @param file_path The path to the .torqueinfo.yaml file
|
38
|
+
# @param file_system An instance of the FileSystem class
|
39
|
+
#
|
40
|
+
# Creates a new parser. Does not parse the file
|
41
|
+
def initialize(file_path = "./.torqueinfo.yaml", file_system = FileSystem.new)
|
42
|
+
|
43
|
+
@file_path = file_path
|
44
|
+
@fs = file_system
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Parses the file, storing the results as public instnance fields
|
49
|
+
# Stores each field with no corresponding value in the file as 'nil'
|
50
|
+
# Returns self
|
51
|
+
def parse
|
52
|
+
|
53
|
+
if !@fs.path_exist?(@file_path)
|
54
|
+
directory = File.expand_path(File.dirname(@file_path))
|
55
|
+
raise MissingTorqueInfoFileError.new "'#{directory}' is not configured for Torque"
|
56
|
+
end
|
57
|
+
|
58
|
+
torque_info_hash = {}
|
59
|
+
begin
|
60
|
+
torque_info_hash = YAML::load(@fs.file_read(@file_path)) || {}
|
61
|
+
rescue Psych::SyntaxError
|
62
|
+
# If cannot parse, ignore contents
|
63
|
+
end
|
64
|
+
|
65
|
+
@email_address = torque_info_hash["email_address"]
|
66
|
+
@email_password = torque_info_hash["email_password"]
|
67
|
+
@email_to = torque_info_hash["email_to"]
|
68
|
+
@project = torque_info_hash["project"]
|
69
|
+
@output_dir = torque_info_hash["output_dir"]
|
70
|
+
@token = torque_info_hash["token"]
|
71
|
+
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Sets the specified field to the specified value in the file on disk
|
77
|
+
def set(field, value)
|
78
|
+
file_string = @fs.file_read(@file_path)
|
79
|
+
new_file_string = set_string(field, value, file_string)
|
80
|
+
@fs.file_write(@file_path, new_file_string)
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Adds a list of values to a sequence belonging to field on disk
|
85
|
+
def add(field, values)
|
86
|
+
file_string = @fs.file_read(@file_path)
|
87
|
+
new_file_string = add_string(field, values, file_string)
|
88
|
+
@fs.file_write(@file_path, new_file_string)
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# Adds a list of values to a sequence belonging to field, eliminating duplicates, on disk
|
93
|
+
# Returns a list of all values that were added (were not duplicates)
|
94
|
+
def add_no_duplicates(field, values)
|
95
|
+
file_string = @fs.file_read(@file_path)
|
96
|
+
values_copy = values.clone
|
97
|
+
|
98
|
+
new_file_string = add_no_duplicates_string(field, values_copy, file_string)
|
99
|
+
@fs.file_write(@file_path, new_file_string)
|
100
|
+
values_copy
|
101
|
+
end
|
102
|
+
|
103
|
+
##
|
104
|
+
# Removes a field to a sequence of values on disk
|
105
|
+
# Returns a list of the values that were removed (could be found)
|
106
|
+
def rm(field, values)
|
107
|
+
file_string = @fs.file_read(@file_path)
|
108
|
+
values_copy = values.clone
|
109
|
+
|
110
|
+
new_file_string = rm_string(field, values_copy, file_string)
|
111
|
+
@fs.file_write(@file_path, new_file_string)
|
112
|
+
values_copy
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
# Util method. Changes the first array to the second array in place
|
119
|
+
def set_array_in_place(array1, array2)
|
120
|
+
array1.delete_if {true}
|
121
|
+
array1.concat(array2)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Util method. Converts a yaml field to an applicable array
|
125
|
+
def to_array(value)
|
126
|
+
if value.is_a? Array; value
|
127
|
+
elsif value.is_a? String; [value]
|
128
|
+
elsif value.is_a? NilClass; []
|
129
|
+
else; []
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Alters the string contents of a file, setting the specified field to the specified value
|
134
|
+
def set_string(field, value, file_string)
|
135
|
+
|
136
|
+
file_yaml = YAML.load(file_string) || {}
|
137
|
+
file_yaml[field] = value
|
138
|
+
file_yaml.to_yaml
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
# Alters the string contents of a file, adding all elements of 'values' to field 'field'
|
143
|
+
# Returns the new string contents
|
144
|
+
def add_string(field, values, file_string)
|
145
|
+
|
146
|
+
return file_string if values.empty?
|
147
|
+
|
148
|
+
file_yaml = YAML.load(file_string) || {}
|
149
|
+
file_yaml[field] = to_array(file_yaml[field])
|
150
|
+
file_yaml[field].concat(values)
|
151
|
+
file_yaml.to_yaml
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
# Alters the string contents of a file, adding all elements of 'values' to field 'field' ignoring duplicates
|
156
|
+
# At finish, 'values' contains all values that were added (were not duplicates)
|
157
|
+
# Returns the new string contents
|
158
|
+
def add_no_duplicates_string(field, values, file_string)
|
159
|
+
|
160
|
+
return file_string if values.empty?
|
161
|
+
|
162
|
+
file_yaml = YAML.load(file_string) || {}
|
163
|
+
file_yaml[field] = to_array(file_yaml[field])
|
164
|
+
file_yaml[field].delete(nil)
|
165
|
+
|
166
|
+
values.uniq!
|
167
|
+
|
168
|
+
index = 0
|
169
|
+
while index < values.length
|
170
|
+
value = values[index]
|
171
|
+
|
172
|
+
if file_yaml[field].member? value
|
173
|
+
values.delete_at(index)
|
174
|
+
index-=1
|
175
|
+
else
|
176
|
+
file_yaml[field] << value
|
177
|
+
end
|
178
|
+
|
179
|
+
index+=1
|
180
|
+
end
|
181
|
+
|
182
|
+
file_yaml.to_yaml
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
# Alters the string contents of a file so that value is added to a sequence belonging to field
|
187
|
+
# At finish, 'values' contains all values that were removed (could be found)
|
188
|
+
# Returns the new string contents
|
189
|
+
def rm_string(field, values, file_string)
|
190
|
+
|
191
|
+
return file_string if values.empty?
|
192
|
+
|
193
|
+
file_yaml = YAML.load(file_string) || {}
|
194
|
+
|
195
|
+
# The field does not exist
|
196
|
+
if file_yaml[field].is_a? NilClass
|
197
|
+
set_array_in_place(values, [])
|
198
|
+
return file_string
|
199
|
+
|
200
|
+
# The field is a single value
|
201
|
+
elsif file_yaml[field].is_a? String
|
202
|
+
if values.member? file_yaml[field]
|
203
|
+
set_array_in_place(values, [ file_yaml[field] ])
|
204
|
+
file_yaml.delete(field)
|
205
|
+
else
|
206
|
+
set_array_in_place(values, [])
|
207
|
+
end
|
208
|
+
|
209
|
+
# The field is a sequence
|
210
|
+
else
|
211
|
+
index = 0;
|
212
|
+
while index < values.length
|
213
|
+
value = values[index]
|
214
|
+
if file_yaml[field].member? value
|
215
|
+
file_yaml[field].delete(value)
|
216
|
+
else
|
217
|
+
values.delete_at(index)
|
218
|
+
index -= 1
|
219
|
+
end
|
220
|
+
index += 1
|
221
|
+
end
|
222
|
+
|
223
|
+
file_yaml.delete(field) if file_yaml[field].empty?
|
224
|
+
end
|
225
|
+
|
226
|
+
file_yaml.to_yaml
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Torque
|
2
|
+
class Version
|
3
|
+
|
4
|
+
version = {}
|
5
|
+
File.read(File.join(File.dirname(__FILE__), '../', '../', 'VERSION')).each_line do |line|
|
6
|
+
type, value = line.chomp.split(":")
|
7
|
+
next if type =~ /^\s+$/ || value =~ /^\s+$/
|
8
|
+
version[type] = value
|
9
|
+
end
|
10
|
+
|
11
|
+
MAJOR = version['major']
|
12
|
+
MINOR = version['minor']
|
13
|
+
PATCH = version['patch']
|
14
|
+
|
15
|
+
STRING = [MAJOR, MINOR, PATCH].compact.join('.')
|
16
|
+
|
17
|
+
##
|
18
|
+
# The string representing the current version of Torque (eg "1.9.2")
|
19
|
+
def self.string
|
20
|
+
STRING
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: torque
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nico Adams, Scrimmage
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-08-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: highline
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.6.19
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.6.19
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mail
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.5.4
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.5.4
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: nokogiri
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.6.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.6.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: mocha
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Compiles Pivotal Tracker stories into a local document to help generate
|
84
|
+
release notes
|
85
|
+
email:
|
86
|
+
- nico@wescrimmage.com
|
87
|
+
executables:
|
88
|
+
- torque
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files:
|
91
|
+
- README.md
|
92
|
+
files:
|
93
|
+
- bin/config
|
94
|
+
- bin/email
|
95
|
+
- bin/project
|
96
|
+
- bin/torque
|
97
|
+
- lib/torque/date_settings.rb
|
98
|
+
- lib/torque/error/invalid_project_error.rb
|
99
|
+
- lib/torque/error/invalid_token_error.rb
|
100
|
+
- lib/torque/error/missing_output_directory_error.rb
|
101
|
+
- lib/torque/error/missing_project_error.rb
|
102
|
+
- lib/torque/error/missing_token_error.rb
|
103
|
+
- lib/torque/error/missing_torque_info_file_error.rb
|
104
|
+
- lib/torque/error/pivotal_api_error.rb
|
105
|
+
- lib/torque/file_system.rb
|
106
|
+
- lib/torque/mailer.rb
|
107
|
+
- lib/torque/pivotal.rb
|
108
|
+
- lib/torque/pivotal_html_parser.rb
|
109
|
+
- lib/torque/project/project.rb
|
110
|
+
- lib/torque/project/project_manager.rb
|
111
|
+
- lib/torque/record_pathname_settings.rb
|
112
|
+
- lib/torque/settings.rb
|
113
|
+
- lib/torque/story.rb
|
114
|
+
- lib/torque/torque_info_parser.rb
|
115
|
+
- lib/torque/version.rb
|
116
|
+
- lib/torque.rb
|
117
|
+
- Gemfile
|
118
|
+
- LICENSE
|
119
|
+
- README.md
|
120
|
+
- VERSION
|
121
|
+
homepage: https://github.com/Scrimmage/torque
|
122
|
+
licenses:
|
123
|
+
- MIT
|
124
|
+
metadata: {}
|
125
|
+
post_install_message:
|
126
|
+
rdoc_options: []
|
127
|
+
require_paths:
|
128
|
+
- lib
|
129
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
130
|
+
requirements:
|
131
|
+
- - '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
requirements: []
|
140
|
+
rubyforge_project:
|
141
|
+
rubygems_version: 2.0.3
|
142
|
+
signing_key:
|
143
|
+
specification_version: 4
|
144
|
+
summary: Generates release notes for a project from Pivotal Tracker stories
|
145
|
+
test_files: []
|