wvanbergen-request-log-analyzer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Willem van Bergen / Bart ten Brinke
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,119 @@
1
+ Request log analyzer
2
+ --------------------------------
3
+
4
+ This is a simple command line tool to analyze request log files. At this moment,
5
+ it only supports Rails log files, but Merb log files are planned to be supported
6
+ as well. Its purpose is to find what actions are best candidates for optimization.
7
+
8
+ This tool will parse all requests in the logfile and aggregate the
9
+ information. Once it is finished parsing the log file, it will show the
10
+ requests that take op most server time. Different metrics are used (cumulative
11
+ time, average time, blockers, DB time, etc)
12
+
13
+
14
+ Installation
15
+ --------------------------------
16
+ gem sources -a http://gems.github.com
17
+ sudo gem install wvanbergen-request-log-analyzer
18
+
19
+ Usage
20
+ --------------------------------
21
+
22
+ Usage: request-log-analyzer [FILE] [OPTION]
23
+ Analyze the given log FILE with the given OPTION
24
+ Example: request-log-analyzer mongrel.log
25
+
26
+ --fast, -t: Only use completed requests
27
+ --guess-database-time, -g: Guesses the database duration of requests if they are not in the log
28
+ --output, -o: Comma-separated list of reports to show
29
+ --amount, -c: Displays the top <amount> elements in the reports
30
+ --colorize, -z: Fancy bash coloring
31
+
32
+
33
+
34
+ Example
35
+ --------------------------------
36
+
37
+ Note that this example was shortened for your viewing pleasure.
38
+ $ request-log-analyzer /var/log/my_app.log
39
+
40
+ Request log analyzer, by Willem van Bergen and Bart ten Brinke
41
+
42
+ Processing all log lines...
43
+ ========================================================================
44
+ Successfully analyzed 58908 requests from log file
45
+
46
+ Timestamp first request: 2008-07-13T06:25:58+00:00
47
+ Timestamp last request: 2008-07-20T06:18:53+00:00
48
+ Total time analyzed: 7 days
49
+
50
+ Top 10 most requested actions
51
+ ========================================================================
52
+ /overview/:date/ : 19359 requests
53
+ /overview/day/:date/ : 6365 requests
54
+ /overview/:date/set/ : 5589 requests
55
+ /overview/ : 3985 requests
56
+ /clients/:id/ : 1976 requests
57
+ ........
58
+
59
+ Top 10 actions by time - cumulative
60
+ ========================================================================
61
+ /overview/:date/ : 9044.582s [19359 requests]
62
+ /overview/ : 8478.767s [3985 requests]
63
+ /overview/:date/set/ : 3309.041s [5589 requests]
64
+ /clients/:id/products/:id/ : 1479.911s [924 requests]
65
+ /clients/:id/ : 750.080s [1976 requests]
66
+ ........
67
+
68
+ Top 10 actions by time - per request mean
69
+ ========================================================================
70
+ /overview/ : 2.128s [3985 requests]
71
+ /clients/:id/products/:id/ : 1.602s [924 requests]
72
+ /overview/:date/set/ : 0.592s [5589 requests]
73
+ /overview/:date/ : 0.467s [19359 requests]
74
+ /clients/:id/ : 0.380s [1976 requests]
75
+ ........
76
+
77
+ Top 10 worst DB offenders - cumulative time
78
+ ========================================================================
79
+ /overview/:date/ : 8773.993s [19359 requests]
80
+ /overview/ : 8394.754s [3985 requests]
81
+ /overview/:date/set/ : 3307.928s [5589 requests]
82
+ /clients/:id/products/:id/ : 1425.220s [924 requests]
83
+ /clients/:id/ : 535.229s [1976 requests]
84
+ ........
85
+
86
+ Top 10 worst DB offenders - mean time
87
+ ========================================================================
88
+ /overview/:id/:id/:id/print/ : 6.994s [448 requests]
89
+ /overview/ : 2.128s [3985 requests]
90
+ /clients/:id/products/:id/ : 1.602s [924 requests]
91
+ /overview/:date/set/ : 0.592s [5589 requests]
92
+ /overview/:date/ : 0.467s [19359 requests]
93
+ ........
94
+
95
+ Mongrel process blockers (> 1.0 seconds) - frequency
96
+ ========================================================================
97
+ /overview/:date/ : 7494.233s [3144 requests]
98
+ /overview/ : 8320.293s [1549 requests]
99
+ /overview/:date/set/ : 1149.235s [803 requests]
100
+ /overview/:id/:id/:id/print/new/ : 613.693s [341 requests]
101
+ /clients/:id/products/:id/ : 1370.693s [313 requests]
102
+ ........
103
+
104
+ Requests graph - per hour
105
+ ========================================================================
106
+ ........
107
+ 7:00 - 2731 : XXXXXXX
108
+ 8:00 - 6139 : XXXXXXXXXXXXXXXX
109
+ 9:00 - 7465 : XXXXXXXXXXXXXXXXXXXX
110
+ 10:00 - 7118 : XXXXXXXXXXXXXXXXXXX
111
+ 11:00 - 7409 : XXXXXXXXXXXXXXXXXXX
112
+ 12:00 - 6450 : XXXXXXXXXXXXXXXXX
113
+ 13:00 - 5377 : XXXXXXXXXXXXXX
114
+ 14:00 - 6058 : XXXXXXXXXXXXXXXX
115
+ 15:00 - 4156 : XXXXXXXXXXX
116
+ 16:00 - 2767 : XXXXXXX
117
+ 17:00 - 1598 : XXXX
118
+ 18:00 - 792 : XX
119
+ ........
data/Rakefile ADDED
@@ -0,0 +1,43 @@
1
+ require 'rubygems'
2
+
3
+ load 'test/tasks.rake'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+
9
+ namespace :gem do
10
+
11
+ desc "Builds a ruby gem for request-log-analyzer"
12
+ task :build => [:manifest] do
13
+ system %[gem build request-log-analyzer.gemspec]
14
+ end
15
+
16
+ desc %{Update ".manifest" with the latest list of project filenames. Respect\
17
+ .gitignore by excluding everything that git ignores. Update `files` and\
18
+ `test_files` arrays in "*.gemspec" file if it's present.}
19
+ task :manifest do
20
+ list = Dir['**/*'].sort
21
+ spec_file = Dir['*.gemspec'].first
22
+ list -= [spec_file] if spec_file
23
+
24
+ File.read('.gitignore').each_line do |glob|
25
+ glob = glob.chomp.sub(/^\//, '')
26
+ list -= Dir[glob]
27
+ list -= Dir["#{glob}/**/*"] if File.directory?(glob) and !File.symlink?(glob)
28
+ puts "excluding #{glob}"
29
+ end
30
+
31
+ if spec_file
32
+ spec = File.read spec_file
33
+ spec.gsub! /^(\s* s.(test_)?files \s* = \s* )( \[ [^\]]* \] | %w\( [^)]* \) )/mx do
34
+ assignment = $1
35
+ bunch = $2 ? list.grep(/^test.*_test\.rb$/) : list
36
+ '%s%%w(%s)' % [assignment, bunch.join(' ')]
37
+ end
38
+
39
+ File.open(spec_file, 'w') {|f| f << spec }
40
+ end
41
+ File.open('.manifest', 'w') {|f| f << list.join("\n") }
42
+ end
43
+ end
data/TODO ADDED
@@ -0,0 +1,20 @@
1
+ TODO items for Rails-log-analyzer
2
+ =================================
3
+ Contact willem AT vanbergen DOT org if you want to help out with the development.
4
+
5
+ Summarizer:
6
+ - Look at request types (GET / POST)
7
+
8
+ Database:
9
+ - Add query functionality for the resulting database file (interactive reports?)
10
+ - Link request processing line to request completed line
11
+
12
+ Rails integration:
13
+ - Create script that calls request-log-analyzer
14
+ - Optionally use local or specific routes.rb file to parse URLs
15
+ - Add rake tasks to Rails application when included
16
+
17
+ General:
18
+ - Add useful rake tasks
19
+ - Add more tests
20
+ - World domination
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/ruby
2
+ require File.dirname(__FILE__) + '/../lib/command_line/arguments'
3
+ require File.dirname(__FILE__) + '/../lib/rails_analyzer/log_parser'
4
+ require File.dirname(__FILE__) + '/../lib/rails_analyzer/summarizer'
5
+ require File.dirname(__FILE__) + '/../lib/bashcolorizer'
6
+ require File.dirname(__FILE__) + '/../lib/ruby-progressbar/progressbar.rb'
7
+
8
+ puts "Request log analyzer, by Willem van Bergen and Bart ten Brinke\n\n"
9
+
10
+ # Substitutes variable elements in a url (like the id field) with a fixed string (like ":id")
11
+ # This is used to aggregate simular requests.
12
+ # <tt>request</tt> The request to evaluate.
13
+ # Returns uniformed url string.
14
+ # Raises on mailformed request.
15
+ def request_hasher(request)
16
+ if request[:url]
17
+ url = request[:url].downcase.split(/^http[s]?:\/\/[A-z0-9\.-]+/).last.split('?').first # only the relevant URL part
18
+ url << '/' if url[-1] != '/'[0] && url.length > 1 # pad a trailing slash for consistency
19
+
20
+ url.gsub!(/\/\d+-\d+-\d+/, '/:date') # Combine all (year-month-day) queries
21
+ url.gsub!(/\/\d+-\d+/, '/:month') # Combine all date (year-month) queries
22
+ url.gsub!(/\/\d+/, '/:id') # replace identifiers in URLs
23
+
24
+ return url
25
+ elsif request[:controller] && request[:action]
26
+ return "#{request[:controller]}##{request[:action]}"
27
+ else
28
+ raise 'Cannot hash this request! ' + request.inspect
29
+ end
30
+ end
31
+
32
+ # Print results using a ASCII table.
33
+ # <tt>summarizer</tt> The summarizer containg information to draw the table.
34
+ # <tt>field</tt> The field containing the data to be printed
35
+ # <tt>amount</tt> The length of the table (defaults to 20)
36
+ def print_table(summarizer, field, amount = 20)
37
+ summarizer.sort_actions_by(field).reverse[0, amount.to_i].each do |a|
38
+ # As we show count by default, show totaltime if we sort by count
39
+ field = :total_time if field == :count
40
+
41
+ puts "#{a[0].ljust(50)}: %10.03fs [#{green("%d requests")}]" % [a[1][field], a[1][:count]]
42
+ end
43
+ end
44
+
45
+ # Parse the arguments given via commandline
46
+ begin
47
+ $arguments = CommandLine::Arguments.parse do |command_line|
48
+ command_line.switch(:guess_database_time, :g)
49
+ command_line.switch(:fast, :f)
50
+ command_line.switch(:colorize, :z)
51
+ command_line.flag(:output, :alias => :o)
52
+ command_line.flag(:amount, :alias => :c)
53
+ command_line.required_files = 1
54
+ end
55
+
56
+ rescue CommandLine::Error => e
57
+ puts "ARGUMENT ERROR: " + e.message
58
+ puts
59
+ load File.dirname(__FILE__) + "/../output/usage.rb"
60
+ exit(0)
61
+ end
62
+
63
+ $summarizer = RailsAnalyzer::Summarizer.new(:calculate_database => $arguments[:guess_database_time])
64
+ $summarizer.blocker_duration = 1.0
65
+
66
+ line_types = $arguments[:fast] ? [:completed] : [:started, :completed, :failed]
67
+
68
+ # Walk through al the files given via the arguments.
69
+ $arguments.files.each do |log_file|
70
+ puts "Processing #{line_types.join(', ')} log lines from #{log_file}..."
71
+ parser = RailsAnalyzer::LogParser.new(log_file)
72
+
73
+ # add progress bar
74
+ unless $arguments[:fast]
75
+ pbar = ProgressBar.new(green(log_file), File.size(log_file))
76
+ parser.progress { |pos, total| (pos == :finished) ? pbar.finish : pbar.set(pos) }
77
+ end
78
+
79
+ parser.each(*line_types) do |request|
80
+ $summarizer.group(request) { |r| request_hasher(r) }
81
+ end
82
+ end
83
+
84
+ # Select the reports to output and generate them.
85
+ output_reports = $arguments[:output].split(',') rescue [:timespan, :most_requested, :total_time, :mean_time, :total_db_time, :mean_db_time, :mean_rendering_time, :blockers, :hourly_spread, :errors]
86
+
87
+ output_reports.each do |report|
88
+ report_location = "#{File.dirname(__FILE__)}/../output/#{report}.rb"
89
+
90
+ if File.exist?(report_location)
91
+ load report_location
92
+ else
93
+ puts "\nERROR: Output report #{report} not found!"
94
+ end
95
+ end
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/command_line/arguments'
4
+ require File.dirname(__FILE__) + '/../lib/rails_analyzer/log_parser'
5
+ require File.dirname(__FILE__) + '/../lib/rails_analyzer/record_inserter'
6
+ require File.dirname(__FILE__) + '/../lib/bashcolorizer'
7
+ require File.dirname(__FILE__) + '/../lib/ruby-progressbar/progressbar.rb'
8
+
9
+
10
+ puts "Rails log parser, by Willem van Bergen and Bart ten Brinke\n\n"
11
+
12
+ begin
13
+
14
+ $arguments = CommandLine::Arguments.parse do |command_line|
15
+ command_line.switch(:guess_database_time, :g)
16
+ command_line.switch(:reset_database, :r)
17
+ command_line.flag(:database, :alias => :d, :required => false)
18
+ command_line.required_files = 1
19
+ end
20
+
21
+ rescue CommandLine::Error => e
22
+ puts "ARGUMENT ERROR: " + e.message
23
+ puts
24
+ puts "Usage: ruby parsetodb.rb [LOGFILES*] <OPTIONS>"
25
+ puts
26
+ puts "Options:"
27
+ puts " --database, -t: The database file to use"
28
+ puts " --reset-database, -r: Resets the database before inserting new records"
29
+ puts " --guess-database-time, -g: Guesses the database duration of requests"
30
+ puts
31
+ puts "Examples:"
32
+ puts " ./parsetodb.rb development.log"
33
+ puts " ./parsetodb.rb mongrel.0.log mongrel.1.log mongrel.2.log -g -d mongrel.db"
34
+ puts
35
+
36
+ exit(0)
37
+ end
38
+
39
+ log_files = $arguments.files
40
+ db_file = $arguments[:database] || log_files.first + '.db'
41
+
42
+ if $arguments[:reset_database] && File.exist?(db_file)
43
+ File.delete(db_file)
44
+ puts "Database file cleared."
45
+ end
46
+
47
+ records_inserted = 0
48
+ inserter = RailsAnalyzer::RecordInserter.insert_batch_into(db_file) do |db|
49
+ log_files.each do |log_file|
50
+
51
+ puts "Processing all log lines from #{log_file}..."
52
+ parser = RailsAnalyzer::LogParser.new(log_file)
53
+
54
+ pbar = ProgressBar.new(green(log_file), File.size(log_file))
55
+ parser.progress { |pos, total| (pos == :finished) ? pbar.finish : pbar.set(pos) }
56
+
57
+ parser.each do |request|
58
+ db.insert(request)
59
+ records_inserted += 1
60
+ end
61
+ end
62
+
63
+ if $arguments[:guess_database_time]
64
+ puts "Calculating database times..."
65
+ db.calculate_db_durations!
66
+ end
67
+ end
68
+
69
+ started = inserter.count(:started)
70
+ completed = inserter.count(:completed)
71
+ failed = inserter.count(:failed)
72
+
73
+ puts
74
+ puts "Inserted #{records_inserted} records from #{log_files.length} files."
75
+ puts "Parse warnings: #{inserter.warning_count}. Check the parse_warnings table in the database for details."
76
+ puts
77
+ puts "Requests started: #{started}"
78
+ puts "Requests completed: #{completed}"
79
+ puts "Requests failed: #{failed}"
@@ -0,0 +1,60 @@
1
+ # Colorize a text output with the given color if.
2
+ # <tt>text</tt> The text to colorize.
3
+ # <tt>color_code</tt> The color code string to set
4
+ # <tt>color</tt> Does not color if false. Defaults to ($arguments && $arguments[:colorize])
5
+ def colorize(text, color_code, color = $arguments && $arguments[:colorize])
6
+ color ? "#{color_code}#{text}\e[0m" : text
7
+ end
8
+
9
+ # Draw a red line of text
10
+ def red(text)
11
+ colorize(text, "\e[31m")
12
+ end
13
+
14
+ # Draw a Green line of text
15
+ def green(text)
16
+ colorize(text, "\e[32m")
17
+ end
18
+
19
+ # Draw a Yellow line of text
20
+ def yellow(text)
21
+ colorize(text, "\e[33m")
22
+ end
23
+
24
+ # Draw a Yellow line of text
25
+ def blue(text)
26
+ colorize(text, "\e[34m")
27
+ end
28
+
29
+ def white(text)
30
+ colorize(text, "\e[37m")
31
+ end
32
+
33
+
34
+ #STYLE = {
35
+ # :default => “33[0m”,
36
+ # # styles
37
+ # :bold => “33[1m”,
38
+ # :underline => “33[4m”,
39
+ # :blink => “33[5m”,
40
+ # :reverse => “33[7m”,
41
+ # :concealed => “33[8m”,
42
+ # # font colors
43
+ # :black => “33[30m”,
44
+ # :red => “33[31m”,
45
+ # :green => “33[32m”,
46
+ # :yellow => “33[33m”,
47
+ # :blue => “33[34m”,
48
+ # :magenta => “33[35m”,
49
+ # :cyan => “33[36m”,
50
+ # :white => “33[37m”,
51
+ # # background colors
52
+ # :on_black => “33[40m”,
53
+ # :on_red => “33[41m”,
54
+ # :on_green => “33[42m”,
55
+ # :on_yellow => “33[43m”,
56
+ # :on_blue => “33[44m”,
57
+ # :on_magenta => “33[45m”,
58
+ # :on_cyan => “33[46m”,
59
+ # :on_white => “33[47m” }
60
+ #
@@ -0,0 +1,129 @@
1
+ require "#{File.dirname(__FILE__)}/flag"
2
+ require "#{File.dirname(__FILE__)}/exceptions"
3
+
4
+ # Module used to parse commandline arguments
5
+ module CommandLine
6
+
7
+ # Parse argument lists and return an argument object containing all set flags and switches.
8
+ class Arguments
9
+
10
+ FLAG_REGEXP = /^--?[A-z0-9]/
11
+
12
+ attr_reader :flag_definitions
13
+
14
+ attr_reader :flags
15
+ attr_reader :files
16
+ attr_reader :command
17
+
18
+ attr_accessor :required_files
19
+
20
+ # Initializer.
21
+ # <tt>arguments</tt> The arguments which are going to be parsed (defaults to $*).
22
+ def initialize(arguments = $*, &block)
23
+ @arguments = arguments
24
+ @flag_definitions = {}
25
+ @begins_with_command = false
26
+ end
27
+
28
+ # Parse a list of arguments. Intatiates a Argument object with the given arguments and yeilds
29
+ # it so that flags and switches can be set by the application.
30
+ # <tt>arguments</tt> The arguments which are going to be parsed (defaults to $*).
31
+ # Returns the arguments object.parse!
32
+ def self.parse(arguments = $*, &block)
33
+ cla = Arguments.new(arguments)
34
+ yield(cla)
35
+ return cla.parse!
36
+ end
37
+
38
+ # Handle argument switches for the application
39
+ # <tt>switch</tt> A switch symbol like :fast
40
+ # <tt>switch_alias</tt> An short alias for the same switch (:f).
41
+ def switch(switch, switch_alias = nil)
42
+ return self.flag(switch, :alias => switch_alias, :expects => nil)
43
+ end
44
+
45
+ # Handle argument flags for the application
46
+ # <tt>flag</tt> A flag symbol like :fast
47
+ # Options
48
+ # * <tt>:expects</tt> Expects a value after the flag
49
+ def flag(flag, options)
50
+ options[:expects] = String unless options.has_key?(:expects)
51
+ argument = Flag.new(flag, options)
52
+ @flag_definitions[argument.to_argument] = argument
53
+ @flag_definitions[argument.to_alias] = argument if argument.has_alias?
54
+ return argument
55
+ end
56
+
57
+ # If called argument list must begin with a command.
58
+ # <tt>begins_w_command</tt> Defaults to true.
59
+ def begins_with_command!(begins_w_command=true)
60
+ @begins_with_command = begins_w_command
61
+ end
62
+
63
+ # Unknown flags will be silently ignored.
64
+ # <tt>ignore</tt> Defaults to true.
65
+ def ignore_unknown_flags!(ignore = true)
66
+ @ignore_unknown_flags = ignore
67
+ end
68
+
69
+ def [](name)
70
+ return flags[name.to_s.gsub(/_/, '-').to_sym]
71
+ end
72
+
73
+ # Parse the flags and switches set by the application.
74
+ # Returns an arguments object containing the flags and switches found in the commandline.
75
+ def parse!
76
+ @flags = {}
77
+ @files = []
78
+
79
+ i = 0
80
+ while @arguments.length > i do
81
+ arg = @arguments[i]
82
+ if FLAG_REGEXP =~ arg
83
+ if @flag_definitions.has_key?(arg)
84
+ flag = @flag_definitions[arg]
85
+ if flag.expects_argument?
86
+
87
+ if @arguments.length > (i + 1) && @arguments[i + 1]
88
+ @flags[flag.name] = @arguments[i + 1]
89
+ i += 1
90
+ else
91
+ raise CommandLine::FlagExpectsArgument.new(arg)
92
+ end
93
+
94
+ else
95
+ @flags[flag.name] = true
96
+ end
97
+ else
98
+ raise CommandLine::UnknownFlag.new(arg) unless @ignore_unknown_flags
99
+ end
100
+ else
101
+ if @begins_with_command && @command.nil?
102
+ @command = arg
103
+ else
104
+ @files << arg
105
+ end
106
+ end
107
+ i += 1
108
+ end
109
+
110
+ check_parsed_arguments!
111
+
112
+ return self
113
+ end
114
+
115
+ # Check if the parsed arguments meet their requirements.
116
+ # Raises CommandLineexception on error.
117
+ def check_parsed_arguments!
118
+ if @begins_with_command && @command.nil?
119
+ raise CommandLine::CommandMissing.new
120
+ end
121
+
122
+ if @required_files && @files.length < @required_files
123
+ raise CommandLine::FileMissing.new("You need at least #{@required_files} files")
124
+ end
125
+
126
+ end
127
+ end
128
+
129
+ end
@@ -0,0 +1,37 @@
1
+ module CommandLine
2
+
3
+ # Commandline parsing errors and exceptions
4
+ class Error < Exception
5
+ end
6
+
7
+ # Missing a required flag
8
+ class FlagMissing < CommandLine::Error
9
+ end
10
+
11
+ # Missing a required file
12
+ class FileMissing < CommandLine::Error
13
+ end
14
+
15
+ # Missing a required flag argument
16
+ class FlagExpectsArgument < CommandLine::Error
17
+ def initialize(flag)
18
+ super("#{flag} expects an argument!")
19
+ end
20
+ end
21
+
22
+ # Missing a required command
23
+ class CommandMissing < CommandLine::Error
24
+ def initialize(msg = "A command is missing")
25
+ super(msg)
26
+ end
27
+
28
+ end
29
+
30
+ # Encountered an unkown flag
31
+ class UnknownFlag < CommandLine::Error
32
+ def initialize(flag)
33
+ super("#{flag} not recognized as a valid flag!")
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,51 @@
1
+ module CommandLine
2
+
3
+ # Argument flag handling.
4
+ class Flag
5
+
6
+ attr_reader :name
7
+ attr_reader :alias
8
+ attr_reader :argument
9
+
10
+ # Initialize new Flag
11
+ # <tt>name</tt> The name of the flag
12
+ # <tt>definition</tt> The definition of the flag.
13
+ def initialize(name, definition)
14
+ @name = name.to_s.gsub(/_/, '-').to_sym
15
+ @alias = definition[:alias].to_sym if definition[:alias]
16
+ @required = definition.has_key?(:required) && definition[:required] == true
17
+ @argument = definition[:expects] if definition[:expects]
18
+ end
19
+
20
+ # Argument representation of the flag (--fast)
21
+ def to_argument
22
+ "--#{@name}"
23
+ end
24
+
25
+ # Argument alias representation of the flag (-f)
26
+ def to_alias
27
+ "-#{@alias}"
28
+ end
29
+
30
+ # Check if flag has an alias
31
+ def has_alias?
32
+ !@alias.nil?
33
+ end
34
+
35
+ # Check if flag is optional
36
+ def optional?
37
+ !@required
38
+ end
39
+
40
+ # Check if flag is required
41
+ def required?
42
+ @required
43
+ end
44
+
45
+ # Check if flag expects an argument (Are you talking to me?)
46
+ def expects_argument?
47
+ !@argument.nil?
48
+ end
49
+ end
50
+
51
+ end