garnierjm-dry-report 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+