torque 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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