garnierjm-dry-report 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2005-2007 The RSpec Development Team
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 NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,17 @@
1
+ = dry-report gem and Don't Repeat Yourself plugin
2
+
3
+ Based on Simian (Similarity Analyser) by Simon Harris from RedHill Consulting, see http://www.redhillconsulting.com.au/products/simian/
4
+ Copyright (c) 2003-08 RedHill Consulting Pty. Ltd. All rights reserved.
5
+
6
+ Report duplicate lines in your code, integrated with Textmate and Netbeans.
7
+
8
+ == License dry-report gem
9
+
10
+ MIT-LICENSE
11
+
12
+ === License Simian
13
+
14
+ See ./SIMIAN-LICENSE file
15
+
16
+ Simon Harris had the same idea as me and also wrote a Rails plugin.
17
+ More information in http://www.redhillonrails.org/#simian
data/Rakefile ADDED
@@ -0,0 +1,73 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/clean'
4
+ require 'rake/gempackagetask'
5
+ require 'spec/rake/spectask'
6
+
7
+ spec = Gem::Specification.new do |s|
8
+ s.name = 'dont_repeat_yourself'
9
+ s.version = '0.0.1'
10
+ s.has_rdoc = false
11
+ s.summary = 'Generate duplicate lines report'
12
+ s.description = s.summary
13
+ s.author = 'Jean-Michel Garnier'
14
+ s.email = 'jm AT 21croissants dot com'
15
+ s.executables = ['dry-report']
16
+ s.files = %w(SIMIAN-LICENSE MIT-LICENSE README TODO Rakefile) + Dir.glob("{bin,lib}/**/*")
17
+ s.require_path = "lib"
18
+ s.bindir = "bin"
19
+ end
20
+
21
+ Rake::GemPackageTask.new(spec) do |p|
22
+ p.gem_spec = spec
23
+ p.need_tar = true
24
+ p.need_zip = true
25
+ end
26
+
27
+ desc 'Default: run specs.'
28
+ task :default => :spec
29
+
30
+ desc "Run all specs"
31
+ Spec::Rake::SpecTask.new do |t|
32
+ t.spec_files = FileList['spec/**/*_spec.rb']
33
+ t.spec_opts = ['--options', 'spec/spec.opts']
34
+ end
35
+
36
+ desc "Generate documentation for the dont_repeat_yourself plugin and store html output in doc.html"
37
+ Spec::Rake::SpecTask.new('doc') do |t|
38
+ t.spec_files = FileList['spec/**/*_spec.rb']
39
+ # t.spec_opts = ['--format html:./doc.html']
40
+ t.spec_opts = ['--format specdoc:./doc.txt']
41
+ end
42
+
43
+ desc "Run all examples with RCov and generate specs coverage report"
44
+ Spec::Rake::SpecTask.new('coverage') do |t|
45
+ t.spec_files = FileList['spec/**/*_spec.rb']
46
+ t.rcov = true
47
+ t.rcov_opts = ['--exclude', 'spec,boot.rb']
48
+ end
49
+
50
+ require 'cucumber/rake/task'
51
+ desc "Run User Acceptance tests with cucumber"
52
+ Cucumber::Rake::Task.new(:features) do |t|
53
+ t.cucumber_opts = "--format pretty"
54
+ end
55
+
56
+ def egrep(pattern)
57
+ Dir['**/*.rb'].each do |fn|
58
+ count = 0
59
+ open(fn) do |f|
60
+ while line = f.gets
61
+ count += 1
62
+ if line =~ pattern
63
+ puts "#{fn}:#{count}:#{line}"
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ desc "Look for TODO and FIXME tags in the code"
71
+ task :todo do
72
+ egrep /(FIXME|TODO|TBD)/
73
+ end
data/SIMIAN-LICENSE ADDED
@@ -0,0 +1,101 @@
1
+ SIMIAN VERSION 2.2.21 SOFTWARE LICENSE AGREEMENT
2
+
3
+ 1. Licenses and Software
4
+
5
+ RedHill Consulting, Pty. Ltd., an Australian Proprietary Limited Company ("REDHILL") hereby grants to the purchaser
6
+ (the "LICENSEE") a limited, revocable, worldwide, non-exclusive, nontransferable, non-sublicensable license to use the
7
+ Simian version 2 (two) software (the "SOFTWARE"), including any minor upgrades thereof during the Term (hereinafter
8
+ defined) up to, but not including the next major version of the Software. The Licensee shall not, or knowingly allow
9
+ others to, reverse engineer, decompile, disassemble, modify, adapt, create derivative works from or otherwise attempt
10
+ to derive source code from the Software provided. And, in accordance with the terms and conditions of this Software
11
+ License Agreement (the "AGREEMENT"), the Software shall be used solely by the Licensee in accordance with the following
12
+ specific conditions:
13
+
14
+ A. Personal/SOHO License
15
+
16
+ A Personal/SOHO License entitles the Licensee to use the Software on one (1) machine only. A Personal/SOHO
17
+ License does not permit the generation of reports for distribution nor use by other than the licensee.
18
+
19
+ B. Project License
20
+
21
+ A Project License entitles the Licensee to use the Software on any number of machines solely for the licensed
22
+ project.
23
+
24
+ C. Enterprise License
25
+
26
+ An Enterprise License entitles the Licensee to use the Software on any number of machines. Reports generated are
27
+ strictly for use by the Licensee only.
28
+
29
+ 2. License Fee
30
+
31
+ In exchange for the License(s), the Licensee shall pay to RedHill a one-time, up front, non-refundable license fee.
32
+ At the sole discretion of RedHill, this fee will be waived for non-commercial/non-government projects and for evaluation
33
+ purposes for a period of fifteen (15) days only. The Licensee is also entitled to minor upgrades up to, but not
34
+ including the next major version of the Software at no charge. Notwithstanding the Licensee's payment of the License
35
+ Fee, RedHill reserves the right to terminate the License if RedHill discovers that the Licensee and/or the Licensee's
36
+ use of the Software is in breach of this Agreement.
37
+
38
+ 3. Proprietary Rights
39
+
40
+ RedHill will retain all right, title and interest in and to the Software, all copies thereof, and RedHill website(s),
41
+ software, and other intellectual property, including, but not limited to, ownership of all copyrights, look and feel,
42
+ trademark rights, design rights, trade secret rights and any and all other intellectual property and other proprietary
43
+ rights therein. The Licensee will not directly or indirectly obtain or attempt to obtain at any time, any right, title
44
+ or interest by registration or otherwise in or to the trademarks, service marks, copyrights, trade names, symbols,
45
+ logos or designations or other intellectual property rights owned or used by RedHill. All technical manuals or other
46
+ information provided by RedHill to the Licensee shall be the sole property of RedHill.
47
+
48
+ 4. Term and Termination
49
+
50
+ Subject to the other provisions hereof, this Agreement shall commence upon the Licensee's opting into this Agreement
51
+ and continue until the Licensee discontinues use of the Software or the Agreement terminates automatically upon the
52
+ Licensee's breach of any term or condition of this Agreement (the "Term"). Upon any such termination, the Licensee will
53
+ delete the Software immediately.
54
+
55
+ 5. Copying & Transfer
56
+
57
+ The Licensee may copy the Software to use the Software and for back-up purposes only. The Licensee may not assign or
58
+ otherwise transfer the Software to any third party unless each of the following conditions is met:
59
+
60
+ 1. Redistributions are made at no charge and in accordance with these License terms.
61
+
62
+ A. Redistributions are made by and for non-commercial/non-government Licensee(s) or for evaluation purposes set
63
+ forth as Article 2.
64
+
65
+ B. Redistributions must faithfully reproduce all accompanying materials, including these License terms, and the
66
+ disclaimer/limitation of liability set forth as Article 6, in the documentation and/or other materials provided
67
+ with the distribution.
68
+
69
+ 6. Specific Disclaimer of Warranty and Limitation of Liability
70
+
71
+ THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
72
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL REDHILL
73
+ CONSULTING OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
74
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
75
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
76
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
77
+ POSSIBILITY OF SUCH DAMAGE.
78
+
79
+ 7. Warranties and Representations
80
+
81
+ Indemnification. The Licensee warrants and represents that the Licensee's actions with regard to the Software will be
82
+ in compliance with all applicable laws; and the Licensee will indemnify, defend, and hold RedHill harmless from and
83
+ against any and all liabilities, damages, losses, claims, costs, and expenses (including legal fees) arising out of or
84
+ resulting from the Licensee's failure to observe the use restrictions set forth herein.
85
+
86
+ 8. Governing Law
87
+
88
+ This Agreement shall be governed by the laws of Victoria, Australia.
89
+
90
+ 9. Independent Contractors
91
+
92
+ Assignment: The parties are independent contractors with respect to each other, and nothing in this Agreement shall be
93
+ construed as creating an employer-employee relationship, a partnership, agency relationship or a joint venture between
94
+ the parties. This Agreement is not assignable or transferable by the Licensee.
95
+
96
+ 10. Entire Agreement
97
+
98
+ This Agreement constitutes the entire agreement between the parties concerning the Licensee's use of the Software. This
99
+ Agreement supersedes any prior verbal understanding between the parties and any Licensee purchase order or other
100
+ ordering document, regardless of whether such document is received by RedHill before or after execution of this
101
+ Agreement. This Agreement may be amended only in writing by RedHill.
data/TODO ADDED
@@ -0,0 +1,33 @@
1
+ Must Have:
2
+
3
+ Dev Tasks:
4
+ - add gemspec syntax with dependencies
5
+ - publish verion 0.1 on github with http://gems.github.com/ ,
6
+ - add specs for Rails plugin
7
+ - delete project on rubyforge and communicate about it.
8
+ - update http://github.com/garnierjm/dry-report/wikis/home
9
+ - Add an option for separating HTML/CSS related reports, from JS ones and from Ruby ones.
10
+ - add geminstaller dependency check (phillipe style) in the rake file:
11
+ hoe, syntax, rspec, rake, java
12
+
13
+ Communication Tasks:
14
+ - new screencast with Netbeans + Autotest + DRY
15
+ - publicize in blogs and plugin sites:
16
+ . Rails wiki
17
+ . Rails mailing list, ruby mailing list, citcon mailing list
18
+ . 2 other Rails plugins sites
19
+ . wikipedia DRY (also add Simian)
20
+ . blogs : http://www.google.com/search?q=DRY+Rails&esrch=BetaShortcu
21
+
22
+ Nice to have:
23
+
24
+ - UML Class & Sequence Diagrams (generated?)
25
+ - Aptana RadRails report, vim report, other report
26
+ - Use RR for mocks Reg exp to convert RSpec mocks to RR mocks: dont_allow(@user) do |m| m.$1 end
27
+ - Add support for bacon expectation & XXX frameworks
28
+ - add to rails statistics: number of duplicate lines ;-)
29
+ - run "java -version" to check java is installed
30
+ - set up a configuration file called dry.yml to your config folder for setting up rake tasks
31
+ - compile specs and use them for doc (?)
32
+ - Play with the chain syntax for with_the_default_value_of
33
+ - generate this todo from rake todo ;-), add pending specs
data/bin/dry-report ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.dirname(__FILE__) + '/../lib/') unless $:.include?(File.dirname(__FILE__) + '/../lib/')
3
+ require 'dont_repeat_yourself/cli'
4
+ DontRepeatYourself::CLI.execute
@@ -0,0 +1,127 @@
1
+ #rspec-header {
2
+ background: #65C400; color: #fff;
3
+ }
4
+
5
+ .rspec-report h1 {
6
+ margin: 0px 10px 0px 10px;
7
+ padding: 10px;
8
+ font-family: "Lucida Grande", Helvetica, sans-serif;
9
+ font-size: 1.8em;
10
+ }
11
+
12
+ #summary {
13
+ margin: 0; padding: 5px 10px;
14
+ font-family: "Lucida Grande", Helvetica, sans-serif;
15
+ text-align: right;
16
+ position: absolute;
17
+ top: 0px;
18
+ right: 0px;
19
+ }
20
+
21
+ #summary p {
22
+ margin: 0 0 0 2px;
23
+ }
24
+
25
+ #summary #totals {
26
+ font-size: 1.2em;
27
+ }
28
+
29
+ .behaviour {
30
+ margin: 0 10px 5px;
31
+ background: #fff;
32
+ }
33
+
34
+ dl {
35
+ margin: 0; padding: 0 0 5px;
36
+ font: normal 11px "Lucida Grande", Helvetica, sans-serif;
37
+ }
38
+
39
+ dt {
40
+ padding: 3px;
41
+ background: #65C400;
42
+ color: #fff;
43
+ font-weight: bold;
44
+ }
45
+
46
+ dd {
47
+ margin: 5px 0 5px 5px;
48
+ padding: 3px 3px 3px 18px;
49
+ }
50
+
51
+ dd.spec.passed {
52
+ border-left: 5px solid #65C400;
53
+ border-bottom: 1px solid #65C400;
54
+ background: #DBFFB4; color: #3D7700;
55
+ }
56
+
57
+ dd.spec.failed {
58
+ border-left: 5px solid #C20000;
59
+ border-bottom: 1px solid #C20000;
60
+ color: #C20000; background: #FFFBD3;
61
+ }
62
+
63
+ dd.spec.not_implemented {
64
+ border-left: 5px solid #FAF834;
65
+ border-bottom: 1px solid #FAF834;
66
+ background: #FCFB98; color: #131313;
67
+ }
68
+
69
+ dd.spec.pending_fixed {
70
+ border-left: 5px solid #0000C2;
71
+ border-bottom: 1px solid #0000C2;
72
+ color: #0000C2; background: #D3FBFF;
73
+ }
74
+
75
+ .backtrace {
76
+ color: #000;
77
+ font-size: 12px;
78
+ }
79
+
80
+ a {
81
+ color: #BE5C00;
82
+ }
83
+
84
+ div.rspec-report div.dyn-source {
85
+ background:#FFFFEE none repeat scroll 0%;
86
+ border:1px dotted black;
87
+ color:#000000;
88
+ display:none;
89
+ margin:0.5em 2em;
90
+ padding:0.5em;
91
+ }
92
+
93
+ /* Ruby code, style similar to vibrant ink */
94
+ .ruby {
95
+ font-size: 12px;
96
+ font-family: monospace;
97
+ color: white;
98
+ background-color: black;
99
+ padding: 0.1em 0 0.2em 0;
100
+ }
101
+
102
+ .ruby .keyword { color: #FF6600; }
103
+ .ruby .constant { color: #339999; }
104
+ .ruby .attribute { color: white; }
105
+ .ruby .global { color: white; }
106
+ .ruby .module { color: white; }
107
+ .ruby .class { color: white; }
108
+ .ruby .string { color: #66FF00; }
109
+ .ruby .ident { color: white; }
110
+ .ruby .method { color: #FFCC00; }
111
+ .ruby .number { color: white; }
112
+ .ruby .char { color: white; }
113
+ .ruby .comment { color: #9933CC; }
114
+ .ruby .symbol { color: white; }
115
+ .ruby .regex { color: #44B4CC; }
116
+ .ruby .punct { color: white; }
117
+ .ruby .escape { color: white; }
118
+ .ruby .interp { color: white; }
119
+ .ruby .expr { color: white; }
120
+
121
+ .ruby .offending { background-color: gray; }
122
+ .ruby .linenum {
123
+ width: 75px;
124
+ padding: 0.1em 1em 0.2em 0;
125
+ color: #000000;
126
+ background-color: #FFFBD3;
127
+ }
data/lib/assets/dry.js ADDED
@@ -0,0 +1,29 @@
1
+ // Lifted from Ruby RDoc
2
+ function toggleSource( id ) {
3
+ var elem
4
+ var link
5
+
6
+ if( document.getElementById )
7
+ {
8
+ elem = document.getElementById( id )
9
+ link = document.getElementById( "l_" + id )
10
+ }
11
+ else if ( document.all )
12
+ {
13
+ elem = eval( "document.all." + id )
14
+ link = eval( "document.all.l_" + id )
15
+ }
16
+ else
17
+ return false;
18
+
19
+ if( elem.style.display == "block" )
20
+ {
21
+ elem.style.display = "none"
22
+ link.innerHTML = "Show duplicate lines source code"
23
+ }
24
+ else
25
+ {
26
+ elem.style.display = "block"
27
+ link.innerHTML = "Hide duplicate lines source code"
28
+ }
29
+ }
@@ -0,0 +1,7 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+
3
+ require 'dont_repeat_yourself/cli'
4
+
5
+ module DontRepeatYourself
6
+
7
+ end
@@ -0,0 +1,74 @@
1
+ require File.dirname(__FILE__) + '/reporter'
2
+ require 'optparse'
3
+
4
+ module DontRepeatYourself
5
+ class CLI
6
+
7
+ ### Define methods for generating a DRY *_rails_report for Rails project where plugin is installed
8
+ DEFAULT_REPORT_DESC = "display the default plain report"
9
+ NETBEANS_REPORT_DESC = "display the report in the Output window of the Netbeans IDE (Ctrl+4)"
10
+ HTML_REPORT_DESC = "generate an DRY_report.html file in the project root folder"
11
+ TEXTMATE_REPORT_DESC = "to generate an html report with links which open files in the Textmate editor"
12
+
13
+ class << self
14
+
15
+ def execute
16
+ parse(ARGV).execute!
17
+ end
18
+
19
+ def parse(args)
20
+ cli = new
21
+ cli.parse_options!(args)
22
+ cli
23
+ end
24
+ end
25
+
26
+ attr_reader :options
27
+ FORMATS = DontRepeatYourself::REPORT_TYPES.map{|format| format.downcase}
28
+
29
+ def initialize
30
+
31
+ end
32
+
33
+ def parse_options!(args)
34
+ args.extend(OptionParser::Arguable)
35
+
36
+ @options = { :basedir => './', :format => 'default' }
37
+ args.options do |opts|
38
+ opts.banner = "Usage: dry-report [options] "
39
+
40
+ opts.on("-d BASEDIR", "--basedir BASEDIR", "set up the base directory of your ruby project, current directory by default") do |b|
41
+ @options[:basedir] = b
42
+ end
43
+
44
+ opts.on("-f FORMAT", "--format FORMAT", "report format (default is plain text)",
45
+ "Available formats: #{FORMATS.join(", ")}") do |v|
46
+ unless FORMATS.index(v)
47
+ STDERR.puts "Invalid format: #{v}\n"
48
+ STDERR.puts opts.help
49
+ exit 1
50
+ end
51
+ @options[:format] = v
52
+ end
53
+
54
+ opts.on("-t THRESHOLD", "--threshold THRESHOLD", "threshold of duplicate lines") do |t|
55
+ @options[:threshold] = t.to_i
56
+ end
57
+
58
+ opts.on_tail("--help", "You're looking at it. Any question? http://21croissants.blogspot.com/2008/10/dry.html") do
59
+ puts opts.help
60
+ exit
61
+ end
62
+ end.parse!
63
+
64
+ end
65
+
66
+ def execute!
67
+ ruby_project_reporter = DontRepeatYourself::RubyProjectReporter.new(@options[:basedir])
68
+ ruby_project_reporter.with_threshold_of_duplicate_lines(@options[:threshold]) if @options.has_key?(:threshold)
69
+ ruby_project_reporter.send("with_#{@options[:format]}_reporting") if @options.has_key?(:format)
70
+ STDOUT.print(ruby_project_reporter.report)
71
+ end
72
+ end # class
73
+
74
+ end
@@ -0,0 +1,139 @@
1
+ require File.dirname(__FILE__) + '/simian_results'
2
+ require File.dirname(__FILE__) + '/snippet_extractor'
3
+
4
+ module DontRepeatYourself
5
+
6
+ class FormatterFactory
7
+
8
+ # TODO Use a kind of Dependency Injection here, the plugin should not know about this class
9
+ @@snippet_extractor = DontRepeatYourself::SnippetExtractor.new
10
+
11
+ def self.create_report(report_type, simian_results)
12
+ formatter_class = DontRepeatYourself.const_get("#{report_type}Formatter")
13
+ return formatter_class.new(@@snippet_extractor, simian_results).report
14
+ end
15
+ end
16
+
17
+ class DefaultFormatter
18
+
19
+ attr_reader :simian_results
20
+
21
+ # Inject dependency thtough
22
+ def initialize(snippet_extractor, simian_results)
23
+ @snippet_extractor = snippet_extractor
24
+ @simian_results = simian_results
25
+ end
26
+
27
+ def report
28
+ report_body.gsub(/TWO_SPACE_CHARS/, " ")
29
+ end
30
+
31
+ # Protected methods to be used by formatters
32
+ # protected
33
+
34
+ def report_body
35
+ body = ""
36
+ body << @simian_results.sentence_processed_a_total_of_x_significant_lines_in_y_files << "\n"
37
+ body << @simian_results.sentence_found_x_duplicate_lines_in_y_blocks_in_z_files << "\n\n"
38
+ @simian_results.sets.each{|set|
39
+ body << set.sentence_found_x_duplicate_lines_in_the_following_files << "\n"
40
+ set.blocks.each{ |block|
41
+ body << "TWO_SPACE_CHARS" << format_sentence_between_lines_x_and_y_in_filepath(block) << "\n"
42
+ }
43
+ body << "TWO_SPACE_CHARS" << format_duplicate_lines_snippet(set.blocks.last) << "\n"
44
+ }
45
+ body
46
+ end
47
+
48
+ # Default, return the sentence
49
+ def format_sentence_between_lines_x_and_y_in_filepath(block)
50
+ block.sentence_between_lines_x_and_y_in_filepath
51
+ end
52
+
53
+ def format_duplicate_lines_snippet(block)
54
+ snippet = "Duplicate lines:\n"
55
+ snippet << @snippet_extractor.plain_source_code(block.line_number_of_first_duplicate_line, block.line_number_of_last_duplicate_line, block.file_path)
56
+ end
57
+
58
+ end
59
+
60
+ class NetbeansFormatter < DefaultFormatter
61
+ def format_sentence_between_lines_x_and_y_in_filepath(block)
62
+ block.sentence_between_lines_x_and_y_in_filepath << ":#{block.line_number_of_first_duplicate_line}:"
63
+ end
64
+ end
65
+
66
+ class HTMLFormatter < DefaultFormatter
67
+
68
+ def report
69
+ report = report_header
70
+ report << report_body.gsub(/TWO_SPACE_CHARS/, "&nbsp;&nbsp;").gsub(/\n/, "</br>\n")
71
+ report << report_footer
72
+ end
73
+
74
+ def format_duplicate_lines_snippet(block)
75
+ starts = block.line_number_of_first_duplicate_line
76
+ ends = block.line_number_of_last_duplicate_line
77
+ file_path = block.file_path
78
+ html_source_code = @snippet_extractor.snippet(starts, ends, file_path)
79
+
80
+ source_id = "#{File.basename(file_path)}_#{starts}_#{ends}"
81
+ source_code_div = " <div>&nbsp;&nbsp;[<a id=\"l_#{source_id}\" href=\"javascript:toggleSource('#{source_id}')\">Show duplicate lines source code</a>]</div>"
82
+ source_code_div << " <div id=\"#{source_id}\" class=\"dyn-source\"><pre class=\"ruby\"><code>#{html_source_code}</code></pre></div>"
83
+ end
84
+
85
+ # TODO use erb to generate the report?
86
+ def get_asset(asset)
87
+ IO.read(File.dirname(__FILE__) + '/../assets/' + asset)
88
+ end
89
+
90
+ def report_header
91
+ global_scripts = get_asset('dry.js')
92
+ global_styles = get_asset('/dry.css')
93
+ # TODO use erb.html ;-)
94
+ <<-EOF
95
+ <html>
96
+ <head>
97
+ <script type="text/javascript">
98
+ // <![CDATA[
99
+ #{global_scripts}
100
+ // ]]>
101
+ </script>
102
+ <style type="text/css">
103
+ #{global_styles}
104
+ </style>
105
+ </head>
106
+
107
+ <body>
108
+ <div class="rspec-report">
109
+
110
+ <div id="rspec-header">
111
+ <h1>Don't Repeat Yourself report Result</h1>
112
+ </div>
113
+
114
+ <div class="results">
115
+ EOF
116
+ end
117
+
118
+ def report_footer
119
+ <<-EOF
120
+ </div>
121
+ </div>
122
+ </body>
123
+ </html>
124
+ EOF
125
+ end
126
+ end
127
+
128
+ class TextMateFormatter < HTMLFormatter
129
+
130
+ def format_sentence_between_lines_x_and_y_in_filepath(block)
131
+ starts = block.line_number_of_first_duplicate_line
132
+ file_path = block.file_path
133
+ sentence = block.sentence_between_lines_x_and_y_in_filepath
134
+ "<a href='txmt://open?url=file://#{file_path}&line=#{starts}'>#{sentence}</a>"
135
+ end
136
+
137
+ end
138
+
139
+ end
@@ -0,0 +1,97 @@
1
+ require File.dirname(__FILE__) + '/simian_runner'
2
+ require File.dirname(__FILE__) + '/simian_results'
3
+ require File.dirname(__FILE__) + '/formatter'
4
+ require 'pathname'
5
+
6
+ module DontRepeatYourself
7
+
8
+ # TODO Refactor: Use an enum gem / plugin here?
9
+ DEFAULT_REPORT, HTML_REPORT, NETBEANS_REPORT, TEXTMATE_REPORT = "Default", "HTML", "Netbeans", "TextMate"
10
+ REPORT_TYPES = [DEFAULT_REPORT, NETBEANS_REPORT, HTML_REPORT, TEXTMATE_REPORT]
11
+
12
+ class ProjectReporterBase
13
+ attr_reader :name, :maximum_number_of_duplicate_lines_i_want_in_my_project, :report_type
14
+
15
+ def initialize(name)
16
+ @name = name
17
+ @simian_runner = DontRepeatYourself::SimianRunner.new
18
+
19
+ # Default values
20
+ @maximum_number_of_duplicate_lines_i_want_in_my_project = 0
21
+ @report_type = DontRepeatYourself::DEFAULT_REPORT
22
+ end
23
+
24
+ def patterns_of_directories_to_search_for_duplicate_lines
25
+ @simian_runner.patterns_of_directories_to_search_for_duplicate_lines
26
+ end
27
+
28
+ # Fluent interface methods
29
+ def with_threshold_of_duplicate_lines(threshold)
30
+ @simian_runner.threshold = threshold
31
+ return self
32
+ end
33
+
34
+ # with_<REPORT_TYPE>_reporting fluent methods
35
+ REPORT_TYPES.each do |report_type|
36
+ define_method("with_#{report_type.downcase}_reporting") do
37
+ @report_type = eval("DontRepeatYourself::#{report_type.upcase}_REPORT")
38
+ self
39
+ end
40
+ end
41
+
42
+ def ignoring_the_file(path)
43
+ @simian_runner.ignoring_file(path)
44
+ self
45
+ end
46
+
47
+ def report
48
+ DontRepeatYourself::FormatterFactory.create_report(@report_type, run_simian)
49
+ end
50
+
51
+ # TODO Not very readable: you have to read the code of run_simian to understand
52
+ def dry?
53
+ run_simian.duplicate_line_count <= @maximum_number_of_duplicate_lines_i_want_in_my_project
54
+ end
55
+
56
+ def description
57
+ "DRY\n" << " - with a threshold of #{@simian_runner.threshold} duplicate lines"
58
+ end
59
+
60
+ def failure_message
61
+ "expected #{@name} to have less or equal #{@maximum_number_of_duplicate_lines_i_want_in_my_project} duplicate lines :\n
62
+ DRY Report:\n#{report}\n"
63
+ end
64
+
65
+ protected
66
+
67
+ def run_simian
68
+ return @simian_results if @simian_results # so we don't run simian if we already have results
69
+ self.configure_simian
70
+ results_in_yaml_format = @simian_runner.run
71
+ @simian_results = DontRepeatYourself::SimianResults.new(results_in_yaml_format)
72
+ end
73
+
74
+ def basename_of_directory(directory)
75
+ Pathname.new(directory).basename.to_s
76
+ end
77
+
78
+ def folder_exists?(folder)
79
+ File.directory?(@simian_runner.basedir + "/" + folder)
80
+ end
81
+ end
82
+
83
+ class RubyProjectReporter < ProjectReporterBase
84
+ def initialize(project_path)
85
+ super(basename_of_directory(project_path))
86
+ @simian_runner.basedir = project_path
87
+ end
88
+
89
+ def configure_simian
90
+ # Only add the "lib" directoy if exists
91
+ @simian_runner.add_ruby_directory_to_search_for_duplicate_lines("lib")
92
+ @simian_runner.add_ruby_directory_to_search_for_duplicate_lines("test") if folder_exists?("test")
93
+ @simian_runner.add_ruby_directory_to_search_for_duplicate_lines("spec") if folder_exists?("spec")
94
+ end
95
+ end
96
+
97
+ end
@@ -0,0 +1,92 @@
1
+ module DontRepeatYourself
2
+ class SimianResults
3
+ attr_reader :duplicate_file_count,
4
+ :duplicate_line_count,
5
+ :duplicate_block_count,
6
+ :total_significant_line_count,
7
+ :total_raw_line_count,
8
+ :total_file_count,
9
+ :sets
10
+
11
+ def initialize(simian_log_yaml)
12
+ @simian_log_yaml = simian_log_yaml
13
+
14
+ # TODO Use some code to generate this boring code!
15
+ @duplicate_file_count = summary['duplicateFileCount']
16
+ @duplicate_line_count = summary['duplicateLineCount']
17
+ @duplicate_block_count = summary['duplicateBlockCount']
18
+
19
+ @total_significant_line_count = summary['totalSignificantLineCount']
20
+ @total_raw_line_count = summary['totalRawLineCount']
21
+ @total_file_count = summary['totalFileCount']
22
+
23
+ sets =@simian_log_yaml["simian"]["checks"][0]["sets"]
24
+ if sets.nil?
25
+ @sets = []
26
+ else
27
+ @sets = @simian_log_yaml["simian"]["checks"][0]["sets"].collect{ |original_set|
28
+ DontRepeatYourself::SimianResults::DuplicateLinesSet.new(original_set)
29
+ }
30
+ end
31
+ end
32
+
33
+ # ignoreCurlyBraces false boolean Curly braces are ignored.
34
+ #ignoreIdentifiers false boolean Completely ignores all identfiers.
35
+ #ignoreIdentifierCase true boolean Matches identifiers irrespective of case. Eg. MyVariableName and myvariablename would both match.
36
+ #ignoreStrings false boolean MyVariable and myvariablewould both match.
37
+ #ignoreStringCase J true boolean "Hello, World" and "HELLO, WORLD" would both match.
38
+ #ignoreNumbers false boolean int x = 1; and int x = 576; would both match.
39
+ #ignoreCharacters false boolean 'A' and 'Z'would both match.
40
+ #ignoreCharacterCase true boolean 'A' and 'a'would both match.
41
+ #ignoreLiterals false boolean 'A', "one" and 27.8would all match.
42
+ #balanceParentheses false boolean Ensures that expressions inside parenthesis that are split across multiple physical lines are considered as one.
43
+ #balanceCurlyBraces false boolean Ensures that expressions inside curly braces that are split across multiple physical lines are considered as one.
44
+ #balanceSquareBrackets false boolean Ensures that expressions inside square brackets that are split across multiple physical lines are considered as one. Defaults to false.
45
+
46
+ def sentence_found_x_duplicate_lines_in_y_blocks_in_z_files
47
+ "Found #{self.duplicate_line_count} duplicate lines in #{self.duplicate_block_count} blocks in #{self.duplicate_file_count} files"
48
+ end
49
+
50
+ def sentence_processed_a_total_of_x_significant_lines_in_y_files
51
+ "Processed a total of #{self.total_significant_line_count} significant (#{self.total_raw_line_count} raw) lines in #{self.total_file_count} files"
52
+ end
53
+
54
+ class DuplicateLinesSet
55
+ attr_reader :number_of_duplicate_lines, :blocks
56
+ def initialize(original_set)
57
+ @number_of_duplicate_lines = original_set["lineCount"]
58
+ @blocks = original_set["blocks"].collect{ |original_block|
59
+ DontRepeatYourself::SimianResults::DuplicateLinesBlock.new(original_block)
60
+ }
61
+ end
62
+
63
+ def sentence_found_x_duplicate_lines_in_the_following_files
64
+ "Found #{@number_of_duplicate_lines} duplicate lines in the following files:"
65
+ end
66
+
67
+ end
68
+
69
+ class DuplicateLinesBlock
70
+ attr_reader :line_number_of_first_duplicate_line,
71
+ :line_number_of_last_duplicate_line,
72
+ :file_path
73
+
74
+ def initialize(original_block)
75
+ @line_number_of_first_duplicate_line = original_block["startLineNumber"]
76
+ @line_number_of_last_duplicate_line = original_block["endLineNumber"]
77
+ @file_path = original_block["sourceFile"]
78
+ end
79
+
80
+ def sentence_between_lines_x_and_y_in_filepath
81
+ "Between lines #{self.line_number_of_first_duplicate_line} and #{self.line_number_of_last_duplicate_line} in #{self.file_path}"
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def summary
88
+ @simian_log_yaml["simian"]["checks"][0]["summary"]
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,127 @@
1
+ require 'yaml'
2
+
3
+ module DontRepeatYourself
4
+
5
+ # See Simian doc in http://www.redhillconsulting.com.au/products/simian/installation.html#cli
6
+ class SimianRunner
7
+
8
+ attr_reader :basedir,
9
+ :patterns_of_directories_to_search_for_duplicate_lines,
10
+ :formatter_option,
11
+ :simian_jar_path,
12
+ :executable,
13
+ :simian_log_file,
14
+ :threshold
15
+
16
+ DEFAULT_THRESHOLD = 3
17
+ SIMIAN_LOGFILE_NAME = "simian_log.yaml"
18
+
19
+ def initialize()
20
+ @threshold = DEFAULT_THRESHOLD
21
+ @patterns_of_directories_to_search_for_duplicate_lines, @patterns_of_directories_to_exclude_for_duplicate_lines = [], []
22
+ @formatter_option = "-formatter=yaml"
23
+
24
+ # extension is .txt because the selenium_on_rails project had a problem with jar files that could be
25
+ # downloaded from the rubygems repository
26
+ # In order to prevent this kind of problem, I decided to use another suffix as they did ...
27
+ @simian_jar_path = File.join(File.dirname(__FILE__), '..', 'jars', 'simian-2.2.22.jar.txt')
28
+
29
+ @executable = "java -jar #{@simian_jar_path}".freeze
30
+
31
+ end
32
+
33
+ def threshold=(threshold)
34
+ raise ArgumentError.new("Error: Threshold can't be less that 2") if threshold < 2
35
+ @threshold = threshold
36
+ end
37
+
38
+ def basedir=(basedir)
39
+ # TODO Check if Validatable has some generic code for this. I keep copy-pasting here !!!
40
+ raise ArgumentError.new(basedir << " does not exist") if !File.directory?(basedir)
41
+ @basedir = basedir
42
+ @simian_log_file = @basedir + "/" + DontRepeatYourself::SimianRunner::SIMIAN_LOGFILE_NAME
43
+ end
44
+
45
+ def add_ruby_directory_to_search_for_duplicate_lines(path)
46
+ valid_directory_path(path)
47
+ @patterns_of_directories_to_search_for_duplicate_lines << (path + "/*.rb")
48
+ @patterns_of_directories_to_search_for_duplicate_lines << (path + "/**/*.rb")
49
+ end
50
+
51
+ def add_html_directory_to_search_for_duplicate_lines(path)
52
+ valid_directory_path(path)
53
+ @patterns_of_directories_to_search_for_duplicate_lines << (path + "/**/*.*html")
54
+ end
55
+
56
+ def ignoring_file(path)
57
+ valid_file_path(path)
58
+ @patterns_of_directories_to_exclude_for_duplicate_lines << "/" + path
59
+ end
60
+
61
+ def run
62
+ run_java
63
+ results_yaml = YAML.load(simian_output_with_header_removed)
64
+ delete_simian_log_file
65
+ results_yaml
66
+ end
67
+
68
+ private
69
+
70
+ def parameter_threshold
71
+ "-threshold=#{@threshold}"
72
+ end
73
+
74
+ def parameter_includes
75
+ generate_commandline_parameter("includes", @patterns_of_directories_to_search_for_duplicate_lines)
76
+ end
77
+
78
+ def parameter_excludes
79
+ generate_commandline_parameter("excludes", @patterns_of_directories_to_exclude_for_duplicate_lines)
80
+ end
81
+
82
+ def generate_commandline_parameter(paramater_name, parameter_list)
83
+ parameter_list.map { |pattern|
84
+ "-#{paramater_name}=#{File.join(@basedir, pattern)}"
85
+ } * ' '
86
+ end
87
+
88
+ def command_line
89
+ "#{@executable} #{parameter_threshold} #{@formatter_option} #{parameter_includes} #{parameter_excludes} > #{@simian_log_file}"
90
+ end
91
+
92
+ # TODO Add return code processing
93
+ def run_java
94
+ system(command_line)
95
+ end
96
+
97
+ def simian_output_with_header_removed
98
+ # Remove the Simian text header
99
+ log = IO.read(@simian_log_file)
100
+ header_to_remove = "Similarity Analyser 2.2.22 - http://www.redhillconsulting.com.au/products/simian/index.html\nCopyright (c) 2003-08 RedHill Consulting Pty. Ltd. All rights reserved.\nSimian is not free unless used solely for non-commercial or evaluation purposes.\n---\n"
101
+ cleaned_yaml = log.gsub(header_to_remove, '')
102
+ cleaned_yaml
103
+ end
104
+
105
+ def delete_simian_log_file
106
+ File.delete @simian_log_file if File.file?(@simian_log_file)
107
+ end
108
+
109
+ def valid_directory_path(path)
110
+ absolute_path = File.join(@basedir, "/" + path)
111
+ if !File.directory?(absolute_path)
112
+ raise ArgumentError.new(absolute_path << " does not exist, path should be relative to #{@basedir} and not start neither end with '/' ")
113
+ end
114
+ absolute_path
115
+ end
116
+
117
+ def valid_file_path(path)
118
+ absolute_path = File.join(@basedir, "/" + path)
119
+ if !File.file?(absolute_path)
120
+ raise ArgumentError.new(absolute_path << " file does not exist, path should be relative to #{@basedir} and not start neither end with '/' ")
121
+ end
122
+ absolute_path
123
+ end
124
+
125
+ end
126
+
127
+ end
@@ -0,0 +1,31 @@
1
+ module DontRepeatYourself
2
+
3
+ class SnippetExtractor #:nodoc:
4
+ class NullConverter; def convert(code, pre); code; end; end #:nodoc:
5
+ begin
6
+ require 'rubygems'
7
+ require 'syntax/convertors/html'
8
+ @@converter = Syntax::Convertors::HTML.for_syntax "ruby"
9
+ rescue LoadError
10
+ @@converter = NullConverter.new
11
+ end
12
+
13
+ def plain_source_code(starts, ends, file_path)
14
+ if File.file?(file_path)
15
+ lines = File.open(file_path).read.split("\n")
16
+ lines[starts-1..ends-1].join("\n")
17
+ else
18
+ "# Couldn't get snippet for #{file_path}"
19
+ end
20
+ end
21
+
22
+ def snippet(starts, ends, file_path)
23
+ raw_code = plain_source_code(starts, ends, file_path)
24
+ highlighted = @@converter.convert(raw_code, false)
25
+ highlighted << "\n<span class=\"comment\"># gem install syntax to get syntax highlighting</span>" if @@converter.is_a?(NullConverter)
26
+ highlighted
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,87 @@
1
+ require File.dirname(__FILE__) + '/reporter'
2
+ require 'spec'
3
+ require 'test/unit/assertions'
4
+
5
+ module DontRepeatYourself
6
+
7
+ module UnitTestingHelpers
8
+
9
+ # module RubyAndRailsProjectHelpers
10
+ module RubyProjectHelpers
11
+ # Helpers
12
+ # def ruby_code_in_rails_plugin(plugin_name)
13
+ # DontRepeatYourself::RailsPluginProjectReporter.new(plugin_name)
14
+ # end
15
+ #
16
+ # def rails_application
17
+ # DontRepeatYourself::RailsProjectReporter.new
18
+ # end
19
+
20
+ def ruby_project(project_path)
21
+ DontRepeatYourself::RubyProjectReporter.new(project_path)
22
+ end
23
+
24
+ end
25
+ # Test::Unit extension
26
+
27
+ module TestUnitExtension
28
+ include DontRepeatYourself::UnitTestingHelpers::RubyProjectHelpers
29
+
30
+ def assert_dry(project)
31
+ assert(project.dry?, project.failure_message)
32
+ end
33
+
34
+ end
35
+
36
+ # RSpec Custom Matcher
37
+
38
+ module RSpecMatchers
39
+
40
+ include DontRepeatYourself::UnitTestingHelpers::RubyProjectHelpers
41
+
42
+ class BeDRY
43
+
44
+ def matches?(project)
45
+ @project = project
46
+ @project.dry?
47
+ end
48
+
49
+ # TODO Do we really need this? It does not make a lot of sense
50
+ def negative_failure_message
51
+ "expected #{@project.name} to have more than #{@project.max_number_of_duplicate_lines_in_project} duplicate lines :\n but found the following:\n "
52
+ end
53
+
54
+ def description
55
+ "be " << @project.description
56
+ end
57
+
58
+ def failure_message
59
+ @project.failure_message
60
+ end
61
+
62
+ end
63
+
64
+ # Custom expectation matcher
65
+ def be_dry
66
+ DontRepeatYourself::UnitTestingHelpers::RSpecMatchers::BeDRY.new
67
+ end
68
+
69
+ end
70
+ end
71
+ end
72
+
73
+ # Automatically includes assert_dry
74
+ module Test
75
+ module Unit
76
+ class TestCase #:nodoc:
77
+ include DontRepeatYourself::UnitTestingHelpers::TestUnitExtension
78
+ end
79
+ end
80
+ end
81
+
82
+ # Add this matcher to RSpec default matchers
83
+ module Spec
84
+ module Matchers
85
+ include DontRepeatYourself::UnitTestingHelpers::RSpecMatchers
86
+ end
87
+ end
Binary file
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: garnierjm-dry-report
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - Jean-Michel Garnier
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-10-14 00:00:00 -07:00
13
+ default_executable: dry-report
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: syntax
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.0.0
23
+ version:
24
+ description: Report duplicate lines in your code, integrated with Textmate and Netbeans.
25
+ email:
26
+ - "jean-michel AT 21croissants DOT com "
27
+ executables:
28
+ - dry-report
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README
33
+ files:
34
+ - SIMIAN-LICENSE
35
+ - MIT-LICENSE
36
+ - README
37
+ - TODO
38
+ - Rakefile
39
+ - bin/dry-report
40
+ - lib/assets
41
+ - lib/assets/dry.css
42
+ - lib/assets/dry.js
43
+ - lib/dont_repeat_yourself
44
+ - lib/dont_repeat_yourself/formatter.rb
45
+ - lib/dont_repeat_yourself/reporter.rb
46
+ - lib/dont_repeat_yourself/simian_results.rb
47
+ - lib/dont_repeat_yourself/simian_runner.rb
48
+ - lib/dont_repeat_yourself/snippet_extractor.rb
49
+ - lib/dont_repeat_yourself/unit_testing_helpers.rb
50
+ - lib/dont_repeat_yourself/cli.rb
51
+ - lib/jars
52
+ - lib/jars/simian-2.2.22.jar.txt
53
+ - lib/dont_repeat_yourself.rb
54
+ has_rdoc: false
55
+ homepage: http://github.com/garnierjm/dry-report
56
+ post_install_message: |
57
+
58
+ For more information on dry-report, see http://github.com/garnierjm/dry-report/wikis
59
+
60
+ rdoc_options: []
61
+
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ requirements: []
77
+
78
+ rubyforge_project:
79
+ rubygems_version: 1.2.0
80
+ signing_key:
81
+ specification_version: 2
82
+ summary: Report duplicate lines in your code, integrated with Textmate and Netbeans.
83
+ test_files: []
84
+