torque 0.0.1
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 +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: []
|