torque 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/bin/project ADDED
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Manages user projects for Torque. Displays the list of Pivotal Tracker projects that can be accessed by a given
4
+ # token, or automatically changes the project when given a project name or ID
5
+
6
+ lib_dir = File.expand_path("../lib", File.dirname(__FILE__))
7
+ working_dir = '.'
8
+
9
+ require 'optparse'
10
+ require 'pathname'
11
+ require_relative "#{lib_dir}/torque/pivotal"
12
+ require_relative "#{lib_dir}/torque/torque_info_parser"
13
+ require_relative "#{lib_dir}/torque/error/missing_torque_info_file_error"
14
+ require_relative "#{lib_dir}/torque/project/project"
15
+ require_relative "#{lib_dir}/torque/project/project_manager"
16
+
17
+
18
+ # Handles arguments
19
+
20
+ options = {}
21
+ option_parser = OptionParser.new do |opts|
22
+
23
+ help_message = "Changes the current Pivotal Tracker project to the one specified, or lists the projects available " \
24
+ "with the current API token when run with no arguments"
25
+
26
+ opts.banner = "\nUsage:"
27
+ opts.banner += "\n torque project [ID]"
28
+ opts.banner += "\n torque project [-n/--name NAME]"
29
+ opts.banner += "\n"
30
+ opts.banner += "\n#{help_message}"
31
+ opts.banner += "\n"
32
+ opts.banner += "\n"
33
+
34
+ opts.on("-h", "--help", "Displays this help screen") do
35
+ |arg|
36
+ options[:help] = arg
37
+ end
38
+
39
+ opts.on("-n", "--name NAME", "Switches the Torque project to the project named NAME") do
40
+ |arg|
41
+ options[:name] = arg
42
+ end
43
+
44
+ end
45
+
46
+ # Handles invalid parsing errors
47
+
48
+ begin
49
+ option_parser.parse!
50
+
51
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument
52
+ puts $!.to_s
53
+ puts option_parser
54
+ exit
55
+ end
56
+
57
+ # Handles special cases and exceptions
58
+
59
+ if (ARGV.member? "help") || options[:help]
60
+ puts "(Ran with 'help' option; other options ignored)"
61
+ puts option_parser
62
+ exit
63
+ end
64
+
65
+ # Checks for a connection to Pivotal Tracker
66
+
67
+ if !Torque::Pivotal.connection?
68
+ puts "ABORTING"
69
+ puts "Cannot connect to www.pivotaltracker.com. A connection is required to use Torque"
70
+ exit 1
71
+ end
72
+
73
+ # Locates project identifiers
74
+
75
+ project_identifiers = ARGV
76
+
77
+ is_name = false
78
+
79
+ if options[:name]
80
+ is_name = true
81
+ project_identifiers << options[:name]
82
+ end
83
+
84
+ if project_identifiers.size == 0
85
+
86
+ project_manager = Torque::ProjectManager.new
87
+ begin
88
+ project_manager.load_project_list
89
+ rescue Torque::MissingTorqueInfoFileError => e
90
+ puts "ABORTING"
91
+ puts e.message
92
+ puts "Run 'torque config' in this directory, or change your working directory"
93
+ exit 2
94
+ end
95
+
96
+ puts project_manager.format_project_list
97
+
98
+ elsif project_identifiers.size == 1
99
+
100
+ # Throw error if cannot find a matching project
101
+ if !Pathname.new("./.torqueinfo.yaml").exist?
102
+ puts "Directory is not configured for torque (.torqueinfo.yaml file is missing). Run 'torque config' in this " \
103
+ + "directory, or change your working directory"
104
+ exit
105
+ end
106
+
107
+ # Switch to a new project
108
+ identifier = project_identifiers[0]
109
+
110
+ project_manager = Torque::ProjectManager.new
111
+ project_manager.load_project_list
112
+
113
+ if is_name
114
+ project = project_manager.project_list.select { |project| project.name == identifier }[0]
115
+ else
116
+ project = project_manager.project_list.select { |project| project.id == identifier }[0]
117
+ end
118
+
119
+ if project.nil?
120
+ if is_name
121
+ puts "No project named #{identifier}"
122
+ else
123
+ puts "No project with id #{identifier}"
124
+ end
125
+ puts
126
+ puts project_manager.format_project_list
127
+ exit
128
+ end
129
+
130
+ Torque::TorqueInfoParser.new.set("project", project.id)
131
+ puts "Switched to project #{project.id} - '#{project.name}'"
132
+
133
+ else
134
+ # Too many project identifiers. Throw error
135
+ puts "Only 1 project allowed, found #{project_identifiers.size}: #{project_identifiers.to_s}"
136
+ end
data/bin/torque ADDED
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib_dir = File.expand_path("../lib", File.dirname(__FILE__))
4
+ exec_dir = File.expand_path(File.dirname(__FILE__))
5
+
6
+ require 'net/http'
7
+ require 'optparse'
8
+ require "#{lib_dir}/torque"
9
+ require "#{lib_dir}/torque/pivotal"
10
+ require "#{lib_dir}/torque/error/invalid_project_error"
11
+ require "#{lib_dir}/torque/error/invalid_token_error"
12
+ require "#{lib_dir}/torque/error/missing_project_error"
13
+ require "#{lib_dir}/torque/error/missing_token_error"
14
+ require "#{lib_dir}/torque/error/pivotal_api_error"
15
+
16
+
17
+ # Parses the options
18
+
19
+ options = {}
20
+
21
+ option_parser = OptionParser.new do |opts|
22
+
23
+ help_message = "This script will fail unless it is run in a directory that was configured for Torque by running " \
24
+ "'torque config' (see 'torque config --help' for more). To change the directory torque runs in, change your " \
25
+ "current working directory, or use the --root option"
26
+
27
+ opts.banner = "\nUsage:"
28
+ opts.banner += "\n torque [options]"
29
+ opts.banner += "\n"
30
+ opts.banner += "\n#{help_message}"
31
+ opts.banner += "\n"
32
+ opts.banner += "\nSpecial commands:"
33
+ opts.banner += "\n config Configures a directory for use with Torque"
34
+ opts.banner += "\n email Sets up user email, adds to/removes from Torque's mailing list"
35
+ opts.banner += "\n project Displays & switches between available Pivotal Tracker projects"
36
+ opts.banner += "\n"
37
+ opts.banner += "\nOptions:"
38
+ opts.banner += "\n"
39
+
40
+ opts.on("--email", "Email the compiled notes to the email list in .torqueinfo") do
41
+ |arg|
42
+ options[:email] = arg
43
+ end
44
+
45
+ opts.on("-f,", "--from FROM_DATE", "Set the date (inclusive) from which to accept Pivotal stories") do
46
+ |arg|
47
+ options[:accept_from] = arg
48
+ end
49
+
50
+ opts.on("-h", "--help", "Displays this help screen") do
51
+ |arg|
52
+ options[:help] = arg
53
+ end
54
+
55
+ opts.on("--root ROOT_DIR", "Set the root directory for the script (should contain a .torqueinfo.yaml file)") do
56
+ |arg|
57
+ options[:root_dir] = arg
58
+ end
59
+
60
+ opts.on("-s", "--silent", "Run silently (no output) (overrides -v)") do
61
+ |arg|
62
+ options[:silent] = arg
63
+ end
64
+
65
+ opts.on("-t,", "--to TO_DATE", "Set the date (inclusive) until which to accept Pivotal stories") do
66
+ |arg|
67
+ options[:accept_to] = arg
68
+ end
69
+
70
+ opts.on("-v", "--verbose", "Run verbosely") do
71
+ |arg|
72
+ options[:verbose] = arg
73
+ end
74
+ end
75
+
76
+
77
+ # Handles special commands
78
+
79
+ arg_string = (ARGV.length <= 1 ? "" : ARGV[1..ARGV.length].join(" "))
80
+
81
+ if(ARGV[0]=="config")
82
+ exec("#{exec_dir}/config #{arg_string}")
83
+
84
+ elsif(ARGV[0]=="email")
85
+ exec("#{exec_dir}/email #{arg_string}")
86
+
87
+ elsif(ARGV[0]=="project")
88
+ exec("#{exec_dir}/project #{arg_string}")
89
+
90
+ end
91
+
92
+ # Handles special cases and exceptions
93
+
94
+ begin
95
+ option_parser.parse!
96
+
97
+ if (ARGV.member? "help") || options[:help]
98
+ puts "(Ran with 'help' option; other options ignored)"
99
+ puts option_parser.help
100
+ exit
101
+
102
+ elsif !ARGV.empty?
103
+ puts "Unknown arguments: #{ARGV.join(", ")}"
104
+ puts option_parser.help
105
+ exit
106
+ end
107
+
108
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument
109
+ puts $!.to_s
110
+ puts option_parser.help
111
+ exit
112
+ end
113
+
114
+ # Checks for a connection to Pivotal Tracker
115
+
116
+ if !Torque::Pivotal.connection?
117
+ puts "ABORTING"
118
+ puts "Cannot connect to www.pivotaltracker.com. A connection is required to use Torque"
119
+ exit 1
120
+ end
121
+
122
+ # Runs Torque, handling errors
123
+
124
+ begin
125
+ Torque.new(options).execute
126
+ rescue Torque::MissingTokenError, Torque::InvalidTokenError => e
127
+ puts "ABORTING"
128
+ puts e.message
129
+ puts "Run 'torque config --token' to set your Pivotal Tracker API token"
130
+ (e.is_a? Torque::MissingTokenError) ? (exit 2) : (exit 3)
131
+ rescue Torque::MissingProjectError, Torque::InvalidProjectError => e
132
+ puts "ABORTING"
133
+ puts e.message
134
+ puts "Run 'torque project' to set your Pivotal Tracker project"
135
+ (e.is_a? Torque::MissingProjectError) ? (exit 4) : (exit 5)
136
+ rescue Torque::PivotalAPIError => e
137
+ puts "ABORTING"
138
+ puts e.message
139
+ exit 6
140
+ rescue Torque::MissingTorqueInfoFileError => e
141
+ puts "ABORTING"
142
+ puts e.message
143
+ puts "Run 'torque config' in this directory, or change your working directory"
144
+ exit 7
145
+ rescue Torque::MissingOutputDirectoryError => e
146
+ puts "ABORTING"
147
+ puts e.message
148
+ puts "Run 'torque config -o [directory]' to create/change your output directory"
149
+ exit 8
150
+ end
data/lib/torque.rb ADDED
@@ -0,0 +1,190 @@
1
+ require 'date'
2
+ require 'pathname'
3
+ require_relative 'torque/pivotal'
4
+ require_relative 'torque/pivotal_html_parser'
5
+ require_relative 'torque/mailer'
6
+ require_relative 'torque/settings'
7
+ require_relative 'torque/torque_info_parser'
8
+ require_relative 'torque/project/project'
9
+ require_relative 'torque/project/project_manager'
10
+
11
+ ##
12
+ # The central class for Torque. Takes in a hash of options, generates release notes from online data, writes them to
13
+ # file and emails them
14
+ class Torque
15
+
16
+ ##
17
+ # @param options A hash of the settings to use for
18
+ # @param settings An instance of Torque::Settings (default: Torque::Settings.new)
19
+ # @param settings An instance of Torque::FileSystem (default: Torque::FileSystem.new)
20
+ #
21
+ # Creates a new instance of Torque
22
+ def initialize(options, settings=nil, fs=nil)
23
+ @fs = fs || FileSystem.new
24
+ @settings = settings || Settings.new(options, @fs)
25
+
26
+ @date_format = "%Y/%m/%d"
27
+ end
28
+
29
+ ##
30
+ # @param project_html An html string containing all the project's stories
31
+ # @param project_name The name of the current project
32
+ #
33
+ # Returns a string comprising the release notes document
34
+ def generate_notes(project_html, project_name)
35
+
36
+ notes_string = ""
37
+
38
+ # Parses stories from html, filters them by date and returns them in an array
39
+ stories = PivotalHTMLParser.new.process_project_date_filter(project_html, @settings.accept_from, @settings.accept_to)
40
+
41
+ # Adds the header
42
+ notes_string += Date.today.strftime(@date_format)+"\n"
43
+ notes_string += "Release Notes\n"
44
+ notes_string += "Project #{@settings.project} - '#{project_name}'\n"
45
+ if @settings.custom_date_range
46
+ notes_string += "(custom "+@settings.accept_from.strftime(@date_format)+" - "+@settings.accept_to.strftime(@date_format)+")\n"
47
+ else
48
+ notes_string += "("+@settings.accept_from.strftime(@date_format)+" - "+@settings.accept_to.strftime(@date_format)+")\n"
49
+ end
50
+ notes_string += "\nThese notes were generated with Torque, a Pivotal Tracker command line utility"
51
+ notes_string += "\n"
52
+ notes_string += "\n"
53
+
54
+ # Adds each story
55
+ stories.sort! { |s1, s2| -(s1.date_accepted <=> s2.date_accepted) }
56
+ stories.length.times do |i|
57
+ story = stories[i]
58
+
59
+ notes_string += "#{story.story_id}\n"
60
+ notes_string += "#{story.name}\n"
61
+ notes_string += "Accepted on "+story.date_accepted.strftime(@date_format)+"\n"
62
+ notes_string += "https://www.pivotaltracker.com/story/show/#{story.story_id}\n"
63
+
64
+ print_if_verbose "[Torque] (#{story.date_accepted.strftime(@date_format)}) #{story.name}"
65
+
66
+ descArray = story.description.split("\n")
67
+ descArray.length.times do |i|
68
+ notes_string += "\t"+descArray[i]+"\n"
69
+ end
70
+
71
+ notes_string += "\n"
72
+
73
+ end
74
+
75
+ print_if_verbose "[Torque]"
76
+ print_if_verbose "[Torque] Added #{stories.length} stories"
77
+
78
+ notes_string
79
+ end
80
+
81
+ ##
82
+ # @param notes_string A string representation of the generated release notes
83
+ # @param project_name The name of the current project
84
+ # @param mailer An instance of Torque::Mailer (default: Torque::Mailer.new(@settings.email_address, @settings.email_password) )
85
+ #
86
+ # Emails notes_string to the email list specified in the torque info file
87
+ def email_notes(notes_string, project_name, mailer=nil)
88
+ print_if_not_silent "Emailing notes..."
89
+
90
+ mailer ||= Mailer.new(@settings.email_address, @settings.email_password)
91
+
92
+ if @settings.email_address.blank?
93
+ print_if_not_silent "Emailing notes failed: No sender email address found. (Set this with 'torque email')"
94
+ elsif @settings.email_password.blank?
95
+ print_if_not_silent "Emailing notes failed: No sender email password found. (Set this with 'torque email')"
96
+ elsif(@settings.email_to.length == 0)
97
+ print_if_not_silent "No email addresses found. (Add them with 'torque email')"
98
+ else
99
+
100
+ address_list = ""
101
+ @settings.email_to.each_with_index {
102
+ |address, i|
103
+
104
+ if i!=0
105
+ address_list << ", "
106
+ end
107
+ address_list << address
108
+ }
109
+
110
+ subject_line = \
111
+ "Release Notes: Project #{@settings.project} - '#{project_name}' (" \
112
+ + (@settings.custom_date_range ? "Custom " : "") \
113
+ + "#{@settings.accept_from.strftime('%Y/%m/%d')} - " \
114
+ + "#{@settings.accept_to.strftime('%Y/%m/%d')}" \
115
+ + ")"
116
+
117
+ mailer.send_notes(notes_string, subject_line, address_list)
118
+
119
+ print_if_not_silent "Emailed notes to #{address_list}"
120
+ end
121
+ end
122
+
123
+ ##
124
+ # The method run by Torque on the command line. Generates the notes, writes them to file, optionally emails them
125
+ def execute
126
+
127
+ # Determines the current project
128
+
129
+ torque_info = TorqueInfoParser.new(@settings.torque_info_path)
130
+ project_manager = ProjectManager.new(torque_info)
131
+
132
+ project_manager.load_project_list
133
+ project_name = project_manager.current_project.name
134
+
135
+ # Generates the notes
136
+
137
+ print_if_not_silent "Current project: #{@settings.project} - '#{project_name}'"
138
+
139
+ print_if_not_silent "Generating release notes: " \
140
+ + (@settings.custom_date_range ? "Custom " : "") \
141
+ + @settings.accept_from.strftime(@date_format) + " - " \
142
+ + @settings.accept_to.strftime(@date_format) \
143
+ + "..."
144
+
145
+ project_html = Pivotal.new(@settings.token).get_project_stories(@settings.project)
146
+ notes_string = generate_notes(project_html, project_name)
147
+
148
+ # Writes the notes to file
149
+
150
+ root_dir_path = Pathname.new(@settings.root_dir)
151
+ rel_notes_path = Pathname.new(@settings.current_notes_path).relative_path_from(root_dir_path)
152
+ print_if_not_silent "- Writing notes > #{rel_notes_path}"
153
+
154
+ @fs.file_write("#{@settings.current_notes_path}", notes_string)
155
+
156
+ # Creates a record of the notes file
157
+
158
+ rel_record_path = Pathname.new(@settings.record_path).relative_path_from(root_dir_path)
159
+ print_if_not_silent "- Copying notes > #{rel_record_path}"
160
+
161
+ begin
162
+ @fs.file_write("#{@settings.record_path}", notes_string)
163
+ rescue => e
164
+ err_str = "WARNING: Unable to make a record of the release notes file. Could not locate records "
165
+ err_str += "directory \"#{File.expand_path(@settings.record_path)}\""
166
+ print_if_not_silent err_str
167
+ end
168
+
169
+ print_if_not_silent "Notes generated!"
170
+
171
+ # Emails the release notes to all specified addresses
172
+
173
+ email_notes(notes_string, project_name) if(@settings.email)
174
+
175
+ end
176
+
177
+
178
+ private
179
+
180
+ # Prints a message if silent is not on
181
+ def print_if_not_silent(msg)
182
+ puts msg unless @settings.silent
183
+ end
184
+
185
+ # Prints a message if verbose is on and silent is not
186
+ def print_if_verbose(msg)
187
+ print_if_not_silent msg if @settings.verbose
188
+ end
189
+
190
+ end