ngmoco-request-log-analyzer 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. data/.gitignore +10 -0
  2. data/DESIGN.rdoc +41 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/Rakefile +8 -0
  6. data/bin/request-log-analyzer +114 -0
  7. data/lib/cli/command_line_arguments.rb +301 -0
  8. data/lib/cli/database_console.rb +26 -0
  9. data/lib/cli/database_console_init.rb +43 -0
  10. data/lib/cli/progressbar.rb +213 -0
  11. data/lib/cli/tools.rb +46 -0
  12. data/lib/request_log_analyzer.rb +44 -0
  13. data/lib/request_log_analyzer/aggregator.rb +49 -0
  14. data/lib/request_log_analyzer/aggregator/database_inserter.rb +83 -0
  15. data/lib/request_log_analyzer/aggregator/echo.rb +29 -0
  16. data/lib/request_log_analyzer/aggregator/summarizer.rb +175 -0
  17. data/lib/request_log_analyzer/controller.rb +332 -0
  18. data/lib/request_log_analyzer/database.rb +102 -0
  19. data/lib/request_log_analyzer/database/base.rb +115 -0
  20. data/lib/request_log_analyzer/database/connection.rb +38 -0
  21. data/lib/request_log_analyzer/database/request.rb +22 -0
  22. data/lib/request_log_analyzer/database/source.rb +13 -0
  23. data/lib/request_log_analyzer/database/warning.rb +14 -0
  24. data/lib/request_log_analyzer/file_format.rb +160 -0
  25. data/lib/request_log_analyzer/file_format/amazon_s3.rb +71 -0
  26. data/lib/request_log_analyzer/file_format/apache.rb +141 -0
  27. data/lib/request_log_analyzer/file_format/merb.rb +67 -0
  28. data/lib/request_log_analyzer/file_format/rack.rb +11 -0
  29. data/lib/request_log_analyzer/file_format/rails.rb +176 -0
  30. data/lib/request_log_analyzer/file_format/rails_development.rb +12 -0
  31. data/lib/request_log_analyzer/filter.rb +30 -0
  32. data/lib/request_log_analyzer/filter/anonymize.rb +39 -0
  33. data/lib/request_log_analyzer/filter/field.rb +42 -0
  34. data/lib/request_log_analyzer/filter/timespan.rb +45 -0
  35. data/lib/request_log_analyzer/line_definition.rb +111 -0
  36. data/lib/request_log_analyzer/log_processor.rb +99 -0
  37. data/lib/request_log_analyzer/mailer.rb +62 -0
  38. data/lib/request_log_analyzer/output.rb +113 -0
  39. data/lib/request_log_analyzer/output/fixed_width.rb +220 -0
  40. data/lib/request_log_analyzer/output/html.rb +184 -0
  41. data/lib/request_log_analyzer/request.rb +175 -0
  42. data/lib/request_log_analyzer/source.rb +72 -0
  43. data/lib/request_log_analyzer/source/database_loader.rb +87 -0
  44. data/lib/request_log_analyzer/source/log_parser.rb +274 -0
  45. data/lib/request_log_analyzer/tracker.rb +206 -0
  46. data/lib/request_log_analyzer/tracker/duration.rb +104 -0
  47. data/lib/request_log_analyzer/tracker/frequency.rb +95 -0
  48. data/lib/request_log_analyzer/tracker/hourly_spread.rb +107 -0
  49. data/lib/request_log_analyzer/tracker/timespan.rb +81 -0
  50. data/lib/request_log_analyzer/tracker/traffic.rb +106 -0
  51. data/request-log-analyzer.gemspec +40 -0
  52. data/spec/database.yml +23 -0
  53. data/spec/fixtures/apache_combined.log +5 -0
  54. data/spec/fixtures/apache_common.log +10 -0
  55. data/spec/fixtures/decompression.log +12 -0
  56. data/spec/fixtures/decompression.log.bz2 +0 -0
  57. data/spec/fixtures/decompression.log.gz +0 -0
  58. data/spec/fixtures/decompression.log.zip +0 -0
  59. data/spec/fixtures/decompression.tar.gz +0 -0
  60. data/spec/fixtures/decompression.tgz +0 -0
  61. data/spec/fixtures/header_and_footer.log +6 -0
  62. data/spec/fixtures/merb.log +84 -0
  63. data/spec/fixtures/merb_prefixed.log +9 -0
  64. data/spec/fixtures/multiple_files_1.log +5 -0
  65. data/spec/fixtures/multiple_files_2.log +2 -0
  66. data/spec/fixtures/rails.db +0 -0
  67. data/spec/fixtures/rails_1x.log +59 -0
  68. data/spec/fixtures/rails_22.log +12 -0
  69. data/spec/fixtures/rails_22_cached.log +10 -0
  70. data/spec/fixtures/rails_unordered.log +24 -0
  71. data/spec/fixtures/syslog_1x.log +5 -0
  72. data/spec/fixtures/test_file_format.log +13 -0
  73. data/spec/fixtures/test_language_combined.log +14 -0
  74. data/spec/fixtures/test_order.log +16 -0
  75. data/spec/integration/command_line_usage_spec.rb +84 -0
  76. data/spec/integration/munin_plugins_rails_spec.rb +58 -0
  77. data/spec/integration/scout_spec.rb +151 -0
  78. data/spec/lib/helpers.rb +52 -0
  79. data/spec/lib/macros.rb +18 -0
  80. data/spec/lib/matchers.rb +77 -0
  81. data/spec/lib/mocks.rb +76 -0
  82. data/spec/lib/testing_format.rb +46 -0
  83. data/spec/spec_helper.rb +24 -0
  84. data/spec/unit/aggregator/database_inserter_spec.rb +93 -0
  85. data/spec/unit/aggregator/summarizer_spec.rb +26 -0
  86. data/spec/unit/controller/controller_spec.rb +41 -0
  87. data/spec/unit/controller/log_processor_spec.rb +18 -0
  88. data/spec/unit/database/base_class_spec.rb +183 -0
  89. data/spec/unit/database/connection_spec.rb +34 -0
  90. data/spec/unit/database/database_spec.rb +133 -0
  91. data/spec/unit/file_format/amazon_s3_format_spec.rb +49 -0
  92. data/spec/unit/file_format/apache_format_spec.rb +203 -0
  93. data/spec/unit/file_format/file_format_api_spec.rb +69 -0
  94. data/spec/unit/file_format/line_definition_spec.rb +75 -0
  95. data/spec/unit/file_format/merb_format_spec.rb +52 -0
  96. data/spec/unit/file_format/rails_format_spec.rb +164 -0
  97. data/spec/unit/filter/anonymize_filter_spec.rb +21 -0
  98. data/spec/unit/filter/field_filter_spec.rb +66 -0
  99. data/spec/unit/filter/filter_spec.rb +17 -0
  100. data/spec/unit/filter/timespan_filter_spec.rb +58 -0
  101. data/spec/unit/mailer_spec.rb +30 -0
  102. data/spec/unit/request_spec.rb +111 -0
  103. data/spec/unit/source/log_parser_spec.rb +119 -0
  104. data/spec/unit/tracker/duration_tracker_spec.rb +130 -0
  105. data/spec/unit/tracker/frequency_tracker_spec.rb +88 -0
  106. data/spec/unit/tracker/hourly_spread_spec.rb +79 -0
  107. data/spec/unit/tracker/timespan_tracker_spec.rb +73 -0
  108. data/spec/unit/tracker/tracker_api_spec.rb +124 -0
  109. data/spec/unit/tracker/traffic_tracker_spec.rb +107 -0
  110. data/tasks/github-gem.rake +323 -0
  111. data/tasks/request_log_analyzer.rake +26 -0
  112. metadata +220 -0
@@ -0,0 +1,79 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe RequestLogAnalyzer::Tracker::HourlySpread do
4
+
5
+ before(:each) do
6
+ @tracker = RequestLogAnalyzer::Tracker::HourlySpread.new
7
+ @tracker.prepare
8
+ end
9
+
10
+ it "should store timestamps correctly" do
11
+ @tracker.update(request(:timestamp => 20090102000000))
12
+ @tracker.update(request(:timestamp => 20090101000000))
13
+ @tracker.update(request(:timestamp => 20090103000000))
14
+
15
+ @tracker.hour_frequencies[0].should eql(3)
16
+ end
17
+
18
+ it "should count the number of timestamps correctly" do
19
+ @tracker.update(request(:timestamp => 20090102000000))
20
+ @tracker.update(request(:timestamp => 20090101000000))
21
+ @tracker.update(request(:timestamp => 20090103000000))
22
+ @tracker.update(request(:timestamp => 20090103010000))
23
+
24
+ @tracker.total_requests.should eql(4)
25
+ end
26
+
27
+ it "should set the first request timestamp correctly" do
28
+ @tracker.update(request(:timestamp => 20090102000000))
29
+ @tracker.update(request(:timestamp => 20090101000000))
30
+ @tracker.update(request(:timestamp => 20090103000000))
31
+
32
+ @tracker.first_timestamp.should == DateTime.parse('Januari 1, 2009 00:00:00')
33
+ end
34
+
35
+ it "should set the last request timestamp correctly" do
36
+ @tracker.update(request(:timestamp => 20090102000000))
37
+ @tracker.update(request(:timestamp => 20090101000000))
38
+ @tracker.update(request(:timestamp => 20090103000000))
39
+
40
+ @tracker.last_timestamp.should == DateTime.parse('Januari 3, 2009 00:00:00')
41
+ end
42
+
43
+ it "should return the correct timespan in days when multiple requests are given" do
44
+ @tracker.update(request(:timestamp => 20090102000000))
45
+ @tracker.update(request(:timestamp => 20090101000000))
46
+ @tracker.update(request(:timestamp => 20090103000000))
47
+
48
+ @tracker.timespan.should == 2
49
+ end
50
+
51
+ end
52
+
53
+ describe RequestLogAnalyzer::Tracker::HourlySpread, 'reporting' do
54
+
55
+ before(:each) do
56
+ @tracker = RequestLogAnalyzer::Tracker::HourlySpread.new
57
+ @tracker.prepare
58
+ end
59
+
60
+ it "should generate a report without errors when no request was tracked" do
61
+ lambda { @tracker.report(mock_output) }.should_not raise_error
62
+ end
63
+
64
+ it "should generate a report without errors when multiple requests were tracked" do
65
+ @tracker.update(request(:timestamp => 20090102000000))
66
+ @tracker.update(request(:timestamp => 20090101000000))
67
+ @tracker.update(request(:timestamp => 20090103000000))
68
+ @tracker.update(request(:timestamp => 20090103010000))
69
+ lambda { @tracker.report(mock_output) }.should_not raise_error
70
+ end
71
+
72
+ it "should generate a YAML output" do
73
+ @tracker.update(request(:timestamp => 20090102000000))
74
+ @tracker.update(request(:timestamp => 20090101000000))
75
+ @tracker.update(request(:timestamp => 20090103000000))
76
+ @tracker.update(request(:timestamp => 20090103010000))
77
+ @tracker.to_yaml_object.should == {"22:00 - 23:00"=>0, "9:00 - 10:00"=>0, "7:00 - 8:00"=>0, "2:00 - 3:00"=>0, "12:00 - 13:00"=>0, "11:00 - 12:00"=>0, "16:00 - 17:00"=>0, "15:00 - 16:00"=>0, "19:00 - 20:00"=>0, "3:00 - 4:00"=>0, "21:00 - 22:00"=>0, "20:00 - 21:00"=>0, "14:00 - 15:00"=>0, "13:00 - 14:00"=>0, "4:00 - 5:00"=>0, "10:00 - 11:00"=>0, "18:00 - 19:00"=>0, "17:00 - 18:00"=>0, "8:00 - 9:00"=>0, "6:00 - 7:00"=>0, "5:00 - 6:00"=>0, "1:00 - 2:00"=>1, "0:00 - 1:00"=>3, "23:00 - 24:00"=>0}
78
+ end
79
+ end
@@ -0,0 +1,73 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe RequestLogAnalyzer::Tracker::Timespan do
4
+
5
+ before(:each) do
6
+ @tracker = RequestLogAnalyzer::Tracker::Timespan.new
7
+ @tracker.prepare
8
+ end
9
+
10
+ it "should set the first request timestamp correctly" do
11
+ @tracker.update(request(:timestamp => 20090102000000))
12
+ @tracker.update(request(:timestamp => 20090101000000))
13
+ @tracker.update(request(:timestamp => 20090103000000))
14
+
15
+ @tracker.first_timestamp.should == DateTime.parse('Januari 1, 2009 00:00:00')
16
+ end
17
+
18
+ it "should set the last request timestamp correctly" do
19
+ @tracker.update(request(:timestamp => 20090102000000))
20
+ @tracker.update(request(:timestamp => 20090101000000))
21
+ @tracker.update(request(:timestamp => 20090103000000))
22
+
23
+ @tracker.last_timestamp.should == DateTime.parse('Januari 3, 2009 00:00:00')
24
+ end
25
+
26
+ it "should return the correct timespan in days when multiple requests are given" do
27
+ @tracker.update(request(:timestamp => 20090102000000))
28
+ @tracker.update(request(:timestamp => 20090101000000))
29
+ @tracker.update(request(:timestamp => 20090103000000))
30
+
31
+ @tracker.timespan.should == 2
32
+ end
33
+
34
+ it "should return a timespan of 0 days when only one timestamp is set" do
35
+ @tracker.update(request(:timestamp => 20090103000000))
36
+ @tracker.timespan.should == 0
37
+ end
38
+
39
+ it "should raise an error when no timestamp is set" do
40
+ lambda { @tracker.timespan }.should raise_error
41
+ end
42
+ end
43
+
44
+ describe RequestLogAnalyzer::Tracker::Timespan, 'reporting' do
45
+
46
+ before(:each) do
47
+ @tracker = RequestLogAnalyzer::Tracker::Timespan.new
48
+ @tracker.prepare
49
+ end
50
+
51
+ it "should have a title" do
52
+ @tracker.title.should_not eql("")
53
+ end
54
+
55
+ it "should generate a report without errors when no request was tracked" do
56
+ lambda { @tracker.report(mock_output) }.should_not raise_error
57
+ end
58
+
59
+ it "should generate a report without errors when multiple requests were tracked" do
60
+ @tracker.update(request(:category => 'a', :timestamp => 20090102000000))
61
+ @tracker.update(request(:category => 'a', :timestamp => 20090101000000))
62
+ @tracker.update(request(:category => 'a', :timestamp => 20090103000000))
63
+ lambda { @tracker.report(mock_output) }.should_not raise_error
64
+ end
65
+
66
+ it "should generate a YAML output" do
67
+ @tracker.update(request(:category => 'a', :timestamp => 20090102000000))
68
+ @tracker.update(request(:category => 'a', :timestamp => 20090101000000))
69
+ @tracker.update(request(:category => 'a', :timestamp => 20090103000000))
70
+ @tracker.to_yaml_object.should == { :first => DateTime.parse('20090101000000'), :last => DateTime.parse('20090103000000')}
71
+ end
72
+
73
+ end
@@ -0,0 +1,124 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe RequestLogAnalyzer::Tracker::Base do
4
+
5
+ describe 'API' do
6
+
7
+ before(:each) do
8
+ @tracker = Class.new(RequestLogAnalyzer::Tracker::Base).new
9
+
10
+ @summarizer = RequestLogAnalyzer::Aggregator::Summarizer.new(mock_source)
11
+ @summarizer.trackers << @tracker
12
+ end
13
+
14
+ it "should receive :prepare when the summarizer is preparing" do
15
+ @tracker.should_receive(:prepare).once
16
+ @summarizer.prepare
17
+ end
18
+
19
+ it "should receive :update for every request for which should_update? returns true" do
20
+ @tracker.should_receive(:should_update?).twice.and_return(true)
21
+ @tracker.should_receive(:update).twice
22
+
23
+ @summarizer.aggregate(testing_format.request(:field => 'value1'))
24
+ @summarizer.aggregate(testing_format.request(:field => 'value2'))
25
+ end
26
+
27
+ it "should not :update for every request for which should_update? returns false" do
28
+ @tracker.should_receive(:should_update?).twice.and_return(false)
29
+ @tracker.should_not_receive(:update)
30
+
31
+ @summarizer.aggregate(testing_format.request(:field => 'value1'))
32
+ @summarizer.aggregate(testing_format.request(:field => 'value2'))
33
+ end
34
+
35
+ it "should receive :report when the summary report is being built" do
36
+ @tracker.should_receive(:report).with(anything).once
37
+ @summarizer.report(mock_output)
38
+ end
39
+
40
+ it "should receieve :finalize when the summarizer is finalizing" do
41
+ @tracker.should_receive(:finalize).once
42
+ @summarizer.finalize
43
+ end
44
+ end
45
+
46
+ describe '#should_update?' do
47
+ before(:each) do
48
+ @tracker_class = Class.new(RequestLogAnalyzer::Tracker::Base)
49
+ end
50
+
51
+ it "should return true by default, when no checks are installed" do
52
+ tracker = @tracker_class.new
53
+ tracker.should_update?(testing_format.request).should be_true
54
+ end
55
+
56
+ it "should return false if the line type is not in the request" do
57
+ tracker = @tracker_class.new(:line_type => :not_there)
58
+ tracker.should_update?(request(:line_type => :different)).should be_false
59
+ end
60
+
61
+ it "should return true if the line type is in the request" do
62
+ tracker = @tracker_class.new(:line_type => :there)
63
+ tracker.should_update?(request(:line_type => :there)).should be_true
64
+ end
65
+
66
+ it "should return true if a field name is given to :if and it is in the request" do
67
+ tracker = @tracker_class.new(:if => :field)
68
+ tracker.should_update?(request(:field => 'anything')).should be_true
69
+ end
70
+
71
+ it "should return false if a field name is given to :if and it is not the request" do
72
+ tracker = @tracker_class.new(:if => :field)
73
+ tracker.should_update?(request(:other_field => 'anything')).should be_false
74
+ end
75
+
76
+ it "should return false if a field name is given to :unless and it is in the request" do
77
+ tracker = @tracker_class.new(:unless => :field)
78
+ tracker.should_update?(request(:field => 'anything')).should be_false
79
+ end
80
+
81
+ it "should return true if a field name is given to :unless and it is not the request" do
82
+ tracker = @tracker_class.new(:unless => :field)
83
+ tracker.should_update?(request(:other_field => 'anything')).should be_true
84
+ end
85
+
86
+ it "should return the value of the block if one is given to the :if option" do
87
+ tracker = @tracker_class.new(:if => lambda { false } )
88
+ tracker.should_update?(request(:field => 'anything')).should be_false
89
+ end
90
+
91
+ it "should return the inverse value of the block if one is given to the :if option" do
92
+ tracker = @tracker_class.new(:unless => lambda { false } )
93
+ tracker.should_update?(request(:field => 'anything')).should be_true
94
+ end
95
+
96
+ it "should return false if any of the checks fail" do
97
+ tracker = @tracker_class.new(:if => :field, :unless => lambda { false }, :line_type => :not_present )
98
+ tracker.should_update?(request(:line_type => :present, :field => 'anything')).should be_false
99
+ end
100
+
101
+ it "should return true if all of the checks succeed" do
102
+ tracker = @tracker_class.new(:if => :field, :unless => lambda { false }, :line_type => :present )
103
+ tracker.should_update?(request(:line_type => :present, :field => 'anything')).should be_true
104
+ end
105
+
106
+
107
+ end
108
+
109
+ describe '#to_yaml_object' do
110
+
111
+ before(:each) do
112
+ @tracker = Class.new(RequestLogAnalyzer::Tracker::Base).new
113
+
114
+ @summarizer = RequestLogAnalyzer::Aggregator::Summarizer.new(mock_source)
115
+ @summarizer.trackers << @tracker
116
+ end
117
+
118
+ it "should receive :to_yaml object when finalizing" do
119
+ @summarizer.options[:yaml] = temp_output_file(:yaml)
120
+ @tracker.should_receive(:to_yaml_object).once
121
+ @summarizer.to_yaml
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,107 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe RequestLogAnalyzer::Tracker::Traffic do
4
+
5
+ context 'using a field-based category' do
6
+ before(:each) do
7
+ @tracker = RequestLogAnalyzer::Tracker::Traffic.new(:traffic => :traffic, :category => :category)
8
+ @tracker.prepare
9
+
10
+ @tracker.update(request(:category => 'a', :traffic => 1))
11
+ @tracker.update(request(:category => 'b', :traffic => 2))
12
+ @tracker.update(request(:category => 'b', :traffic => 3))
13
+ end
14
+
15
+ it "should register a request in the right category using the category field" do
16
+ @tracker.categories.should include('a', 'b')
17
+ end
18
+
19
+ it "should register requests under the correct category" do
20
+ @tracker.hits('a').should == 1
21
+ @tracker.hits('b').should == 2
22
+ end
23
+ end
24
+
25
+ context 'using a dynamic category' do
26
+
27
+ before(:each) do
28
+ @categorizer = lambda { |request| request[:traffic] < 2 ? 'few' : 'lots' }
29
+ @tracker = RequestLogAnalyzer::Tracker::Traffic.new(:traffic => :traffic, :category => @categorizer)
30
+ @tracker.prepare
31
+
32
+ @tracker.update(request(:category => 'a', :traffic => 1))
33
+ @tracker.update(request(:category => 'b', :traffic => 2))
34
+ @tracker.update(request(:category => 'b', :traffic => 3))
35
+ end
36
+
37
+ it "should use the categorizer to determine the category" do
38
+ @tracker.categories.should include('few', 'lots')
39
+ end
40
+
41
+ it "should register requests under the correct category using the categorizer" do
42
+ @tracker.hits('few').should == 1
43
+ @tracker.hits('lots').should == 2
44
+ end
45
+ end
46
+
47
+ describe '#update' do
48
+
49
+ before(:each) do
50
+ @tracker = RequestLogAnalyzer::Tracker::Traffic.new(:traffic => :traffic, :category => :category)
51
+ @tracker.prepare
52
+
53
+ @tracker.update(request(:category => 'a', :traffic => 2))
54
+ @tracker.update(request(:category => 'a', :traffic => 1))
55
+ @tracker.update(request(:category => 'a', :traffic => 3))
56
+ end
57
+
58
+ it "should calculate the total traffic correctly" do
59
+ @tracker.sum('a').should == 6
60
+ end
61
+
62
+ it "should calculate the traffic variance correctly" do
63
+ @tracker.variance('a').should == 1.0
64
+ end
65
+
66
+ it "should calculate the traffic standard deviation correctly" do
67
+ @tracker.stddev('a').should == 1.0
68
+ end
69
+
70
+ it "should calculate the average traffic correctly" do
71
+ @tracker.mean('a').should == 2.0
72
+ end
73
+
74
+ it "should calculate the overall mean traffic correctly" do
75
+ @tracker.mean_overall.should == 2.0
76
+ end
77
+
78
+ it "should set min and max traffic correctly" do
79
+ @tracker.min('a').should == 1
80
+ @tracker.max('a').should == 3
81
+ end
82
+ end
83
+
84
+ describe '#report' do
85
+ before(:each) do
86
+ @tracker = RequestLogAnalyzer::Tracker::Traffic.new(:category => :category, :traffic => :traffic)
87
+ @tracker.prepare
88
+ end
89
+
90
+ it "should generate a report without errors when one category is present" do
91
+ @tracker.update(request(:category => 'a', :traffic => 2))
92
+ @tracker.report(mock_output)
93
+ lambda { @tracker.report(mock_output) }.should_not raise_error
94
+ end
95
+
96
+ it "should generate a report without errors when no category is present" do
97
+ lambda { @tracker.report(mock_output) }.should_not raise_error
98
+ end
99
+
100
+ it "should generate a report without errors when multiple categories are present" do
101
+ @tracker.update(request(:category => 'a', :traffic => 2))
102
+ @tracker.update(request(:category => 'b', :traffic => 2))
103
+ lambda { @tracker.report(mock_output) }.should_not raise_error
104
+ end
105
+
106
+ end
107
+ end
@@ -0,0 +1,323 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/tasklib'
4
+ require 'date'
5
+ require 'git'
6
+
7
+ module GithubGem
8
+
9
+ # Detects the gemspc file of this project using heuristics.
10
+ def self.detect_gemspec_file
11
+ FileList['*.gemspec'].first
12
+ end
13
+
14
+ # Detects the main include file of this project using heuristics
15
+ def self.detect_main_include
16
+ if detect_gemspec_file =~ /^(\.*)\.gemspec$/ && File.exist?("lib/#{$1}.rb")
17
+ "lib/#{$1}.rb"
18
+ elsif FileList['lib/*.rb'].length == 1
19
+ FileList['lib/*.rb'].first
20
+ else
21
+ nil
22
+ end
23
+ end
24
+
25
+ class RakeTasks
26
+
27
+ attr_reader :gemspec, :modified_files, :git
28
+ attr_accessor :gemspec_file, :task_namespace, :main_include, :root_dir, :spec_pattern, :test_pattern, :remote, :remote_branch, :local_branch
29
+
30
+ # Initializes the settings, yields itself for configuration
31
+ # and defines the rake tasks based on the gemspec file.
32
+ def initialize(task_namespace = :gem)
33
+ @gemspec_file = GithubGem.detect_gemspec_file
34
+ @task_namespace = task_namespace
35
+ @main_include = GithubGem.detect_main_include
36
+ @modified_files = []
37
+ @root_dir = Dir.pwd
38
+ @test_pattern = 'test/**/*_test.rb'
39
+ @spec_pattern = 'spec/**/*_spec.rb'
40
+ @local_branch = 'master'
41
+ @remote = 'origin'
42
+ @remote_branch = 'master'
43
+
44
+ yield(self) if block_given?
45
+
46
+ @git = Git.open(@root_dir)
47
+ load_gemspec!
48
+ define_tasks!
49
+ end
50
+
51
+ protected
52
+
53
+ # Define Unit test tasks
54
+ def define_test_tasks!
55
+ require 'rake/testtask'
56
+
57
+ namespace(:test) do
58
+ Rake::TestTask.new(:basic) do |t|
59
+ t.pattern = test_pattern
60
+ t.verbose = true
61
+ t.libs << 'test'
62
+ end
63
+ end
64
+
65
+ desc "Run all unit tests for #{gemspec.name}"
66
+ task(:test => ['test:basic'])
67
+ end
68
+
69
+ # Defines RSpec tasks
70
+ def define_rspec_tasks!
71
+ require 'spec/rake/spectask'
72
+
73
+ namespace(:spec) do
74
+ desc "Verify all RSpec examples for #{gemspec.name}"
75
+ Spec::Rake::SpecTask.new(:basic) do |t|
76
+ t.spec_files = FileList[spec_pattern]
77
+ end
78
+
79
+ desc "Verify all RSpec examples for #{gemspec.name} and output specdoc"
80
+ Spec::Rake::SpecTask.new(:specdoc) do |t|
81
+ t.spec_files = FileList[spec_pattern]
82
+ t.spec_opts << '--format' << 'specdoc' << '--color'
83
+ end
84
+
85
+ desc "Run RCov on specs for #{gemspec.name}"
86
+ Spec::Rake::SpecTask.new(:rcov) do |t|
87
+ t.spec_files = FileList[spec_pattern]
88
+ t.rcov = true
89
+ t.rcov_opts = ['--exclude', '"spec/*,gems/*"', '--rails']
90
+ end
91
+ end
92
+
93
+ desc "Verify all RSpec examples for #{gemspec.name} and output specdoc"
94
+ task(:spec => ['spec:specdoc'])
95
+ end
96
+
97
+ # Defines the rake tasks
98
+ def define_tasks!
99
+
100
+ define_test_tasks! if has_tests?
101
+ define_rspec_tasks! if has_specs?
102
+
103
+ namespace(@task_namespace) do
104
+ desc "Updates the filelist in the gemspec file"
105
+ task(:manifest) { manifest_task }
106
+
107
+ desc "Builds the .gem package"
108
+ task(:build => :manifest) { build_task }
109
+
110
+ desc "Sets the version of the gem in the gemspec"
111
+ task(:set_version => [:check_version, :check_current_branch]) { version_task }
112
+ task(:check_version => :fetch_origin) { check_version_task }
113
+
114
+ task(:fetch_origin) { fetch_origin_task }
115
+ task(:check_current_branch) { check_current_branch_task }
116
+ task(:check_clean_status) { check_clean_status_task }
117
+ task(:check_not_diverged => :fetch_origin) { check_not_diverged_task }
118
+
119
+ checks = [:check_current_branch, :check_clean_status, :check_not_diverged, :check_version]
120
+ checks.unshift('spec:basic') if has_specs?
121
+ checks.unshift('test:basic') if has_tests?
122
+ checks.push << [:check_rubyforge] if gemspec.rubyforge_project
123
+
124
+ desc "Perform all checks that would occur before a release"
125
+ task(:release_checks => checks)
126
+
127
+ release_tasks = [:release_checks, :set_version, :build, :github_release]
128
+ release_tasks << [:rubyforge_release] if gemspec.rubyforge_project
129
+
130
+ desc "Release a new verison of the gem"
131
+ task(:release => release_tasks) { release_task }
132
+
133
+ task(:check_rubyforge) { check_rubyforge_task }
134
+ task(:rubyforge_release) { rubyforge_release_task }
135
+ task(:github_release => [:commit_modified_files, :tag_version]) { github_release_task }
136
+ task(:tag_version) { tag_version_task }
137
+ task(:commit_modified_files) { commit_modified_files_task }
138
+
139
+ desc "Updates the gem release tasks with the latest version on Github"
140
+ task(:update_tasks) { update_tasks_task }
141
+ end
142
+ end
143
+
144
+ # Updates the files list and test_files list in the gemspec file using the list of files
145
+ # in the repository and the spec/test file pattern.
146
+ def manifest_task
147
+ # Load all the gem's files using "git ls-files"
148
+ repository_files = git.ls_files.keys
149
+ test_files = Dir[test_pattern] + Dir[spec_pattern]
150
+
151
+ update_gemspec(:files, repository_files)
152
+ update_gemspec(:test_files, repository_files & test_files)
153
+ end
154
+
155
+ # Builds the gem
156
+ def build_task
157
+ sh "gem build -q #{gemspec_file}"
158
+ Dir.mkdir('pkg') unless File.exist?('pkg')
159
+ sh "mv #{gemspec.name}-#{gemspec.version}.gem pkg/#{gemspec.name}-#{gemspec.version}.gem"
160
+ end
161
+
162
+ # Updates the version number in the gemspec file, the VERSION constant in the main
163
+ # include file and the contents of the VERSION file.
164
+ def version_task
165
+ update_gemspec(:version, ENV['VERSION']) if ENV['VERSION']
166
+ update_gemspec(:date, Date.today)
167
+
168
+ update_version_file(gemspec.version)
169
+ update_version_constant(gemspec.version)
170
+ end
171
+
172
+ def check_version_task
173
+ raise "#{ENV['VERSION']} is not a valid version number!" if ENV['VERSION'] && !Gem::Version.correct?(ENV['VERSION'])
174
+ proposed_version = Gem::Version.new(ENV['VERSION'] || gemspec.version)
175
+ # Loads the latest version number using the created tags
176
+ newest_version = git.tags.map { |tag| tag.name.split('-').last }.compact.map { |v| Gem::Version.new(v) }.max
177
+ raise "This version (#{proposed_version}) is not higher than the highest tagged version (#{newest_version})" if newest_version && newest_version >= proposed_version
178
+ end
179
+
180
+ # Checks whether the current branch is not diverged from the remote branch
181
+ def check_not_diverged_task
182
+ raise "The current branch is diverged from the remote branch!" if git.log.between('HEAD', git.remote(remote).branch(remote_branch).gcommit).any?
183
+ end
184
+
185
+ # Checks whether the repository status ic clean
186
+ def check_clean_status_task
187
+ raise "The current working copy contains modifications" if git.status.changed.any?
188
+ end
189
+
190
+ # Checks whether the current branch is correct
191
+ def check_current_branch_task
192
+ raise "Currently not on #{local_branch} branch!" unless git.branch.name == local_branch.to_s
193
+ end
194
+
195
+ # Fetches the latest updates from Github
196
+ def fetch_origin_task
197
+ git.fetch('origin')
198
+ end
199
+
200
+ # Commits every file that has been changed by the release task.
201
+ def commit_modified_files_task
202
+ if modified_files.any?
203
+ modified_files.each { |file| git.add(file) }
204
+ git.commit("Released #{gemspec.name} gem version #{gemspec.version}")
205
+ end
206
+ end
207
+
208
+ # Adds a tag for the released version
209
+ def tag_version_task
210
+ git.add_tag("#{gemspec.name}-#{gemspec.version}")
211
+ end
212
+
213
+ # Pushes the changes and tag to github
214
+ def github_release_task
215
+ git.push(remote, remote_branch, true)
216
+ end
217
+
218
+ # Checks whether Rubyforge is configured properly
219
+ def check_rubyforge_task
220
+ # Login no longer necessary when using rubyforge 2.0.0 gem
221
+ # raise "Could not login on rubyforge!" unless `rubyforge login 2>&1`.strip.empty?
222
+ output = `rubyforge names`.split("\n")
223
+ raise "Rubyforge group not found!" unless output.any? { |line| %r[^groups\s*\:.*\b#{Regexp.quote(gemspec.rubyforge_project)}\b.*] =~ line }
224
+ raise "Rubyforge package not found!" unless output.any? { |line| %r[^packages\s*\:.*\b#{Regexp.quote(gemspec.name)}\b.*] =~ line }
225
+ end
226
+
227
+ # Task to release the .gem file toRubyforge.
228
+ def rubyforge_release_task
229
+ sh 'rubyforge', 'add_release', gemspec.rubyforge_project, gemspec.name, gemspec.version.to_s, "pkg/#{gemspec.name}-#{gemspec.version}.gem"
230
+ end
231
+
232
+ # Gem release task.
233
+ # All work is done by the task's dependencies, so just display a release completed message.
234
+ def release_task
235
+ puts
236
+ puts '------------------------------------------------------------'
237
+ puts "Released #{gemspec.name} version #{gemspec.version}"
238
+ end
239
+
240
+ private
241
+
242
+ # Checks whether this project has any RSpec files
243
+ def has_specs?
244
+ FileList[spec_pattern].any?
245
+ end
246
+
247
+ # Checks whether this project has any unit test files
248
+ def has_tests?
249
+ FileList[test_pattern].any?
250
+ end
251
+
252
+ # Loads the gemspec file
253
+ def load_gemspec!
254
+ @gemspec = eval(File.read(@gemspec_file))
255
+ end
256
+
257
+ # Updates the VERSION file with the new version
258
+ def update_version_file(version)
259
+ if File.exists?('VERSION')
260
+ File.open('VERSION', 'w') { |f| f << version.to_s }
261
+ modified_files << 'VERSION'
262
+ end
263
+ end
264
+
265
+ # Updates the VERSION constant in the main include file if it exists
266
+ def update_version_constant(version)
267
+ if main_include && File.exist?(main_include)
268
+ file_contents = File.read(main_include)
269
+ if file_contents.sub!(/^(\s+VERSION\s*=\s*)[^\s].*$/) { $1 + version.to_s.inspect }
270
+ File.open(main_include, 'w') { |f| f << file_contents }
271
+ modified_files << main_include
272
+ end
273
+ end
274
+ end
275
+
276
+ # Updates an attribute of the gemspec file.
277
+ # This function will open the file, and search/replace the attribute using a regular expression.
278
+ def update_gemspec(attribute, new_value, literal = false)
279
+
280
+ unless literal
281
+ new_value = case new_value
282
+ when Array then "%w(#{new_value.join(' ')})"
283
+ when Hash, String then new_value.inspect
284
+ when Date then new_value.strftime('%Y-%m-%d').inspect
285
+ else raise "Cannot write value #{new_value.inspect} to gemspec file!"
286
+ end
287
+ end
288
+
289
+ spec = File.read(gemspec_file)
290
+ regexp = Regexp.new('^(\s+\w+\.' + Regexp.quote(attribute.to_s) + '\s*=\s*)[^\s].*$')
291
+ if spec.sub!(regexp) { $1 + new_value }
292
+ File.open(gemspec_file, 'w') { |f| f << spec }
293
+ modified_files << gemspec_file
294
+
295
+ # Reload the gemspec so the changes are incorporated
296
+ load_gemspec!
297
+ end
298
+ end
299
+
300
+ # Updates the tasks file using the latest file found on Github
301
+ def update_tasks_task
302
+ require 'net/http'
303
+
304
+ server = 'github.com'
305
+ path = '/wvanbergen/github-gem/raw/master/tasks/github-gem.rake'
306
+
307
+ Net::HTTP.start(server) do |http|
308
+ response = http.get(path)
309
+ open(__FILE__, "w") { |file| file.write(response.body) }
310
+ end
311
+
312
+ relative_file = File.expand_path(__FILE__).sub(%r[^#{git.dir.path}/], '')
313
+ if git.status[relative_file] && git.status[relative_file].type == 'M'
314
+ git.add(relative_file)
315
+ git.commit("Updated to latest gem release management tasks.")
316
+ puts "Updated to latest version of gem release management tasks."
317
+ else
318
+ puts "Release managament tasks already are at the latest version."
319
+ end
320
+ end
321
+
322
+ end
323
+ end