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
@@ -0,0 +1,67 @@
|
|
1
|
+
# Processes a project by matching commits in its git commit history to pivotal id's
|
2
|
+
# Should have already run the getProject script, generating "commitHistory.txt" and "project_html.txt"
|
3
|
+
|
4
|
+
require 'date'
|
5
|
+
require 'nokogiri'
|
6
|
+
require_relative 'story'
|
7
|
+
|
8
|
+
class Torque
|
9
|
+
class PivotalHTMLParser
|
10
|
+
|
11
|
+
##
|
12
|
+
# @param project_html_string An html string containing the story data for a Pivotal Tracker project
|
13
|
+
# @param accept_from A Date marking the lower bound for the date_accepted field of a story
|
14
|
+
# @param accept_to A Date marking the upper bound for the date_accepted field of a story
|
15
|
+
#
|
16
|
+
# Returns a list of Story objects parsed from project_html_string whose date_accepted fields are within acceptable
|
17
|
+
# bounds (least recent to most recent date accepted)
|
18
|
+
def process_project_date_filter(project_html_string, accept_from, accept_to)
|
19
|
+
|
20
|
+
story_list = process_project(project_html_string)
|
21
|
+
story_list.select! {
|
22
|
+
|story|
|
23
|
+
(!story.date_accepted.nil? \
|
24
|
+
&& story.date_accepted >= accept_from \
|
25
|
+
&& story.date_accepted <= accept_to)
|
26
|
+
}
|
27
|
+
|
28
|
+
story_list
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param project_html_string An html string containing the story data for a Pivotal Tracker project
|
33
|
+
#
|
34
|
+
# Returns a list of all Story objects parsed from project_html_string (least recent to most recent date accepted)
|
35
|
+
def process_project(project_html_string)
|
36
|
+
|
37
|
+
project_html = Nokogiri::HTML(project_html_string)
|
38
|
+
story_html_array = project_html.search('story')
|
39
|
+
story_list = []
|
40
|
+
|
41
|
+
story_html_array.each do
|
42
|
+
|story_element|
|
43
|
+
story_list << process_story(story_element)
|
44
|
+
end
|
45
|
+
|
46
|
+
story_list
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
private
|
51
|
+
# story: A Nokogiri::XML::Element
|
52
|
+
#
|
53
|
+
# Returns a Story object based off the information in the story
|
54
|
+
def process_story(story_element)
|
55
|
+
|
56
|
+
story_hash = {}
|
57
|
+
|
58
|
+
story_element.children.each do
|
59
|
+
|child|
|
60
|
+
story_hash[child.name] = child.text
|
61
|
+
end
|
62
|
+
|
63
|
+
Story.create(story_hash)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Torque
|
2
|
+
|
3
|
+
##
|
4
|
+
# Stores the name and id of a single Pivotal project
|
5
|
+
class Project
|
6
|
+
|
7
|
+
##
|
8
|
+
# True if this is the current project, else false
|
9
|
+
attr_accessor :current
|
10
|
+
|
11
|
+
##
|
12
|
+
# The project ID
|
13
|
+
attr_reader :id
|
14
|
+
|
15
|
+
##
|
16
|
+
# The project name
|
17
|
+
attr_reader :name
|
18
|
+
|
19
|
+
def initialize(id, name, current=false)
|
20
|
+
@current = current
|
21
|
+
@id = id
|
22
|
+
@name = name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
lib_dir = File.expand_path(File.dirname(__FILE__)) + "/../.."
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'nokogiri'
|
5
|
+
require 'pathname'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
require_relative "#{lib_dir}/torque/file_system"
|
9
|
+
require_relative "#{lib_dir}/torque/pivotal"
|
10
|
+
require_relative "#{lib_dir}/torque/torque_info_parser"
|
11
|
+
require_relative "#{lib_dir}/torque/error/invalid_token_error"
|
12
|
+
require_relative "#{lib_dir}/torque/error/missing_token_error"
|
13
|
+
require_relative "#{lib_dir}/torque/error/pivotal_api_error"
|
14
|
+
require_relative "project"
|
15
|
+
|
16
|
+
class Torque
|
17
|
+
|
18
|
+
##
|
19
|
+
# Retrieves the list of Pivotal Tracker projects from the Pivotal API, automates the switching of projects
|
20
|
+
class ProjectManager
|
21
|
+
|
22
|
+
##
|
23
|
+
# @param torque_info_parser An instance of the TorqueInfoParser class
|
24
|
+
def initialize(torque_info_parser=TorqueInfoParser.new)
|
25
|
+
@torque_info_parser = torque_info_parser
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# @param token A Pivotal Tracker API token (default: Loads token from .torqueinfo.yaml)
|
30
|
+
#
|
31
|
+
# Requests and processes the project list from the Pivotal Tracker API
|
32
|
+
#
|
33
|
+
# Returns the project list
|
34
|
+
def load_project_list(token=nil)
|
35
|
+
get_project_list(token)
|
36
|
+
@project_list
|
37
|
+
end
|
38
|
+
|
39
|
+
##
|
40
|
+
# Returns the project list if 'load_project_list' has been called, else returns nil
|
41
|
+
#
|
42
|
+
# Does not do any processing
|
43
|
+
def project_list
|
44
|
+
@project_list
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Returns the current project if 'load_project_list' has been called and one exists, else returns nil
|
49
|
+
def current_project
|
50
|
+
@project_list.nil? \
|
51
|
+
? nil
|
52
|
+
: @project_list.select{|project| project.current}[0]
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Formats the project list as a printable string
|
57
|
+
#
|
58
|
+
# Prepends an asterisk to the current project
|
59
|
+
def format_project_list
|
60
|
+
|
61
|
+
list_str = "PROJECTS\n"
|
62
|
+
@project_list.each do |project|
|
63
|
+
|
64
|
+
project.current \
|
65
|
+
? list_str += "* " \
|
66
|
+
: list_str += " "
|
67
|
+
list_str += "#{project.id} #{project.name}"
|
68
|
+
list_str += "\n"
|
69
|
+
end
|
70
|
+
|
71
|
+
list_str
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
# token: The API token to use for the request
|
78
|
+
#
|
79
|
+
# Retrieves the project list from Pivotal Tracker
|
80
|
+
# Stores the project list in @project_list
|
81
|
+
# Returns the project list
|
82
|
+
def get_project_list(token=nil)
|
83
|
+
|
84
|
+
# Parses data from the torque info file
|
85
|
+
torque_info_token, project_id = parse_torque_info
|
86
|
+
token = torque_info_token unless token
|
87
|
+
|
88
|
+
# If the API token doesn't exist, prompt for it
|
89
|
+
raise MissingTokenError.new "API token for Pivotal Tracker has not been set" \
|
90
|
+
unless token
|
91
|
+
|
92
|
+
# Parses the project list from the Pivotal Tracker API
|
93
|
+
html = Pivotal.new(token).get_project_data
|
94
|
+
@project_list = parse_project_html(html)
|
95
|
+
|
96
|
+
identify_current_project(project_id)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Identifies the current project and marks it as such
|
100
|
+
def identify_current_project(project_id)
|
101
|
+
@project_list.each do
|
102
|
+
|project|
|
103
|
+
if String(project_id) == project.id
|
104
|
+
project.current = true
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Parses the API token and current project from .torqueinfo.yaml
|
110
|
+
# Returns [token, project]
|
111
|
+
def parse_torque_info
|
112
|
+
|
113
|
+
torque_info = @torque_info_parser.parse
|
114
|
+
|
115
|
+
return torque_info.token, torque_info.project
|
116
|
+
end
|
117
|
+
|
118
|
+
# Parses the html returned from the Pivotal API
|
119
|
+
def parse_project_html(html)
|
120
|
+
project_html = Nokogiri::HTML(html)
|
121
|
+
project_html_array = project_html.search('project')
|
122
|
+
project_list = []
|
123
|
+
|
124
|
+
project_html_array.each do
|
125
|
+
|project_element|
|
126
|
+
|
127
|
+
project_hash = {}
|
128
|
+
project_element.children.each do
|
129
|
+
|child|
|
130
|
+
project_hash[child.name] = child.text
|
131
|
+
end
|
132
|
+
|
133
|
+
project_list << Project.new(project_hash["id"], project_hash["name"])
|
134
|
+
end
|
135
|
+
|
136
|
+
project_list
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require_relative 'file_system'
|
2
|
+
|
3
|
+
class Torque
|
4
|
+
|
5
|
+
##
|
6
|
+
# Generates the pathname to use for the record file of the release notes
|
7
|
+
class RecordPathnameSettings
|
8
|
+
|
9
|
+
##
|
10
|
+
# @param output_dir The path to the release notes output directory
|
11
|
+
# @param project The project id
|
12
|
+
# @param custom_date_range True if a custom date range is being used, else false
|
13
|
+
# @param fs An instance of the FileSystem class
|
14
|
+
def initialize(output_dir, project, custom_date_range, fs)
|
15
|
+
@output_dir = output_dir
|
16
|
+
@project = project
|
17
|
+
@custom = custom_date_range
|
18
|
+
@fs = fs
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Returns the path to the record file, generating one if it does not exist
|
23
|
+
def get_path
|
24
|
+
generate_record_path if !@path
|
25
|
+
@path
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# Generates a value for @path
|
32
|
+
def generate_record_path
|
33
|
+
if @custom
|
34
|
+
path_for_custom_date_range
|
35
|
+
else
|
36
|
+
path_for_default_date_range
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
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
|
+
# The path to the record of the release notes file if a custom date range was used
|
48
|
+
def path_for_custom_date_range
|
49
|
+
title = "#{@project}-#{current_date_string}"
|
50
|
+
title += "-custom"
|
51
|
+
path_base = "#{@output_dir}/previous/#{title}"
|
52
|
+
path_to_test = "#{path_base}.txt"
|
53
|
+
|
54
|
+
# If the first pathname tried is not in use, use it
|
55
|
+
if !(@fs.path_exist? path_to_test)
|
56
|
+
@path=path_to_test
|
57
|
+
|
58
|
+
# Else, will append "1", "2", "3"... to the end of the pathname, returning the first name that is not in use
|
59
|
+
else
|
60
|
+
i=1
|
61
|
+
while true
|
62
|
+
path_to_test = "#{path_base}#{i}.txt"
|
63
|
+
if !(@fs.path_exist? path_to_test)
|
64
|
+
@path=path_to_test
|
65
|
+
break
|
66
|
+
end
|
67
|
+
i+=1
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
@path
|
72
|
+
end
|
73
|
+
|
74
|
+
# A string representing the current date: YYYY-MM-DD
|
75
|
+
def current_date_string
|
76
|
+
Date.today.strftime("%Y-%m-%d")
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'date'
|
2
|
+
require_relative 'date_settings'
|
3
|
+
require_relative 'file_system'
|
4
|
+
require_relative 'record_pathname_settings'
|
5
|
+
require_relative 'torque_info_parser'
|
6
|
+
require_relative 'error/missing_project_error'
|
7
|
+
require_relative 'error/missing_token_error'
|
8
|
+
require_relative 'error/missing_output_directory_error'
|
9
|
+
|
10
|
+
class Torque
|
11
|
+
|
12
|
+
##
|
13
|
+
# Stores all of the settings for a release notes generation
|
14
|
+
class Settings
|
15
|
+
|
16
|
+
##
|
17
|
+
# The accept-from date. All stories accepted on Pivotal Tracker before this date will be ignored
|
18
|
+
attr_reader :accept_from
|
19
|
+
|
20
|
+
##
|
21
|
+
# The accept-to date. All stories accepted on Pivotal Tracker after this date will be ignored
|
22
|
+
attr_reader :accept_to
|
23
|
+
|
24
|
+
##
|
25
|
+
# The path to the most recently generated notes
|
26
|
+
attr_reader :current_notes_path
|
27
|
+
|
28
|
+
##
|
29
|
+
# True if a custom date range is being used (set manually through the command line), false if using the default one
|
30
|
+
attr_reader :custom_date_range
|
31
|
+
|
32
|
+
##
|
33
|
+
# True if should send the notes to the email list after notes are generated, else false
|
34
|
+
attr_reader :email
|
35
|
+
|
36
|
+
##
|
37
|
+
# The email address from which to send notes
|
38
|
+
attr_reader :email_address
|
39
|
+
|
40
|
+
##
|
41
|
+
# The password to the email address from which to send notes
|
42
|
+
attr_reader :email_password
|
43
|
+
|
44
|
+
##
|
45
|
+
# A list of email addresses to send the notes to
|
46
|
+
attr_reader :email_to
|
47
|
+
|
48
|
+
##
|
49
|
+
# The path to the .last-run file in the records directory
|
50
|
+
attr_reader :last_run_path
|
51
|
+
|
52
|
+
##
|
53
|
+
# The Pivotal Tracker project ID to use
|
54
|
+
attr_reader :project
|
55
|
+
|
56
|
+
##
|
57
|
+
# The output directory to use
|
58
|
+
attr_reader :output_dir
|
59
|
+
|
60
|
+
##
|
61
|
+
# The path to the record file of the notes
|
62
|
+
attr_reader :record_path
|
63
|
+
|
64
|
+
##
|
65
|
+
# The path to the root directory (the directory which contains a .torqueinfo.yaml file)
|
66
|
+
attr_reader :root_dir
|
67
|
+
|
68
|
+
##
|
69
|
+
# True if should silence all output, else false
|
70
|
+
attr_reader :silent
|
71
|
+
|
72
|
+
##
|
73
|
+
# The Pivotal Tracker api token to use to access the Pivotal project
|
74
|
+
attr_reader :token
|
75
|
+
|
76
|
+
##
|
77
|
+
# The path to the .torqueinfo.yaml file
|
78
|
+
attr_reader :torque_info_path
|
79
|
+
|
80
|
+
##
|
81
|
+
# True if should be verbose, else false
|
82
|
+
attr_reader :verbose
|
83
|
+
|
84
|
+
# @param options A hash of the options used to run the program
|
85
|
+
# @param fs An instance of the FileSystem class
|
86
|
+
#
|
87
|
+
# Determines the project settings from the environment
|
88
|
+
def initialize(options={}, fs=FileSystem.new)
|
89
|
+
|
90
|
+
@options = options
|
91
|
+
@fs = fs
|
92
|
+
|
93
|
+
# Handles basic "true/false" options
|
94
|
+
|
95
|
+
@email = @options[:email] || false
|
96
|
+
@silent = @options[:silent] || false
|
97
|
+
@verbose = @options[:verbose] || false
|
98
|
+
|
99
|
+
# Initializes the root directory for Torque
|
100
|
+
|
101
|
+
@root_dir = @options[:root_dir] || "."
|
102
|
+
@root_dir = File.expand_path(@root_dir)
|
103
|
+
|
104
|
+
# Parses and processes data from .torqueinfo.yaml
|
105
|
+
|
106
|
+
@torque_info_path = "#{@root_dir}/.torqueinfo.yaml"
|
107
|
+
torque_info = TorqueInfoParser.new(torque_info_path).parse
|
108
|
+
|
109
|
+
@email_address = torque_info.email_address
|
110
|
+
@email_password = torque_info.email_password
|
111
|
+
@email_to = torque_info.email_to
|
112
|
+
@output_dir = torque_info.output_dir
|
113
|
+
@project = torque_info.project
|
114
|
+
@token = torque_info.token
|
115
|
+
|
116
|
+
raise MissingTokenError.new(
|
117
|
+
"API token for Pivotal Tracker has not been set"
|
118
|
+
) if @token.blank?
|
119
|
+
raise MissingProjectError.new(
|
120
|
+
"Project ID for Pivotal Tracker has not been set"
|
121
|
+
) if @project.blank?
|
122
|
+
|
123
|
+
@output_dir = "release_notes" if @output_dir.blank?
|
124
|
+
@output_dir = "#{@root_dir}/#{@output_dir}"
|
125
|
+
|
126
|
+
if @email_to.class == NilClass; @email_to = []
|
127
|
+
elsif @email_to.class == String; @email_to = [@email_to]
|
128
|
+
elsif @email_to.class == Array; @email_to = @email_to
|
129
|
+
else; raise "Unknown parsing error on .torqueinfo.yaml's 'email_to' field: #{@email_to}"
|
130
|
+
end
|
131
|
+
|
132
|
+
# Sets up the output directory, throwing an error if it cannot be found
|
133
|
+
|
134
|
+
if !@fs.path_exist? @output_dir
|
135
|
+
raise MissingOutputDirectoryError.new(
|
136
|
+
"Could not find the output directory: #{@output_dir}"
|
137
|
+
)
|
138
|
+
elsif !@fs.path_exist? "#{@output_dir}/previous"
|
139
|
+
@fs.mkdir_p("#{@output_dir}/previous")
|
140
|
+
end
|
141
|
+
|
142
|
+
# The path to the last-run file for this project
|
143
|
+
|
144
|
+
@last_run_path = "#{output_dir}/previous/.last-run-#{project}"
|
145
|
+
|
146
|
+
# Determines the date range within which to accept stories
|
147
|
+
|
148
|
+
date_settings = DateSettings.new(@options, @last_run_path, @fs)
|
149
|
+
@accept_from, @accept_to = date_settings.get_dates
|
150
|
+
@custom_date_range = date_settings.custom_date_range?
|
151
|
+
|
152
|
+
# Sets the path to the main "release-notes.txt" file
|
153
|
+
|
154
|
+
@current_notes_path = "#{output_dir}/release-notes.txt"
|
155
|
+
|
156
|
+
# Determines the path name to use for the record of the output file
|
157
|
+
|
158
|
+
record_pathname_settings = RecordPathnameSettings.new(@output_dir, @project, @custom_date_range, @fs)
|
159
|
+
@record_path = record_pathname_settings.get_path
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
end
|