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