garnierjm-dont_repeat_yourself 0.3.0

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.txt ADDED
@@ -0,0 +1,20 @@
1
+ = dry-report gem
2
+
3
+ Up to date documentation is online on github:
4
+ http://github.com/garnierjm/dry-report/wikis
5
+
6
+ Based on Simian (Similarity Analyser) by Simon Harris from RedHill Consulting, see http://www.redhillconsulting.com.au/products/simian/
7
+ Copyright (c) 2003-08 RedHill Consulting Pty. Ltd. All rights reserved.
8
+
9
+ Report duplicate lines in your code, integrated with Textmate and Netbeans.
10
+
11
+ == License dry-report gem
12
+
13
+ MIT-LICENSE
14
+
15
+ === License Simian
16
+
17
+ See ./SIMIAN-LICENSE file
18
+
19
+ Simon Harris had the same idea as me and also wrote a Rails plugin.
20
+ More information in http://www.redhillonrails.org/#simian
data/Rakefile ADDED
@@ -0,0 +1,117 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'spec/rake/spectask'
4
+
5
+ #-------- Deployment tasks
6
+ begin
7
+ require "hoe"
8
+ rescue LoadError
9
+ puts "This Rakefile requires the 'hoe' RubyGem."
10
+ puts "Installation: (sudo) gem install hoe -y"
11
+ exit
12
+ end
13
+
14
+ $:.unshift(File.join(File.dirname(__FILE__), 'lib'))
15
+ require 'dont_repeat_yourself/version'
16
+
17
+ # FIXME In ordert to remove hoe from gemspec, DOES NOT WORK!
18
+ class Hoe
19
+ def extra_deps
20
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
21
+ @extra_deps
22
+ end
23
+ end
24
+
25
+ AUTHOR = 'Jean-Michel Garnier' # can also be an array of Authors
26
+ EMAIL = 'jm AT 21croissants dot com'
27
+ DESCRIPTION = 'Generate duplicate lines report'
28
+ GEM_NAME = 'dont_repeat_yourself' # what ppl will type to install your gem
29
+ HOMEPATH = "http://github.com/garnierjm/dont_repeat_yourself"
30
+ VERS = DontRepeatYourself::VERSION::STRING
31
+
32
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
33
+ p.developer(AUTHOR, EMAIL)
34
+ p.description = DESCRIPTION
35
+ p.summary = DESCRIPTION
36
+ p.url = HOMEPATH
37
+ p.clean_globs |= ['.project', '.gitignore', '**/.*.sw?', '*.gem', '.config', '**/.DS_Store', '**/*.class'] #An array of file patterns to delete on clean.
38
+
39
+ # == Optional
40
+
41
+ # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
42
+ p.extra_deps = [ ['syntax', ">= 1.0.0"] ]
43
+
44
+ end
45
+
46
+ desc "Refresh #{GEM_NAME}.gemspec to include ALL files"
47
+ task :refresh_gemspec => 'refresh_manifest' do
48
+ File.open("#{GEM_NAME}.gemspec", 'w') {|io| io.write(hoe.spec.to_ruby)}
49
+ end
50
+
51
+ desc 'Recreate Manifest.txt to include ALL files'
52
+ task :refresh_manifest do
53
+ manifest_files = %w(SIMIAN-LICENSE MIT-LICENSE README.txt Rakefile) + Dir.glob("{bin,lib}/**/*")
54
+ File.open("Manifest.txt", 'w') {|io| io.write(manifest_files.join("\n"))}
55
+ end
56
+
57
+
58
+ desc 'Install the package as a gem, without generating documentation(ri/rdoc)'
59
+ task :install_gem_no_doc => [:clean, :package] do
60
+ sh "#{'sudo ' unless Hoe::WINDOZE }gem install pkg/*.gem --no-rdoc --no-ri"
61
+ end
62
+
63
+ namespace :manifest do
64
+ desc 'Recreate Manifest.txt to include ALL files'
65
+ task :refresh do
66
+ `rake check_manifest | patch -p0 > Manifest.txt`
67
+ end
68
+ end
69
+
70
+ #-------------- Testing taks
71
+ desc 'Default: run specs.'
72
+ task :default => :spec
73
+
74
+ desc "Run all specs"
75
+ Spec::Rake::SpecTask.new do |t|
76
+ t.spec_files = FileList['spec/**/*_spec.rb']
77
+ t.spec_opts = ['--options', 'spec/spec.opts']
78
+ end
79
+
80
+ desc "Generate documentation for the dont_repeat_yourself plugin and store html output in doc.html"
81
+ Spec::Rake::SpecTask.new('doc') do |t|
82
+ t.spec_files = FileList['spec/**/*_spec.rb']
83
+ t.spec_opts = ['--format html:./doc.html']
84
+ # t.spec_opts = ['--format specdoc:./doc.txt']
85
+ end
86
+
87
+ desc "Run all examples with RCov and generate specs coverage report"
88
+ Spec::Rake::SpecTask.new('coverage') do |t|
89
+ t.spec_files = FileList['spec/**/*_spec.rb']
90
+ t.rcov = true
91
+ t.rcov_opts = ['--exclude', 'spec,boot.rb']
92
+ end
93
+
94
+ require 'cucumber/rake/task'
95
+ desc "Run User Acceptance tests with cucumber"
96
+ Cucumber::Rake::Task.new(:features) do |t|
97
+ t.cucumber_opts = "--format pretty"
98
+ end
99
+
100
+ def egrep(pattern)
101
+ Dir['**/*.rb'].each do |fn|
102
+ count = 0
103
+ open(fn) do |f|
104
+ while line = f.gets
105
+ count += 1
106
+ if line =~ pattern
107
+ puts "#{fn}:#{count}:#{line}"
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ desc "Look for TODO and FIXME tags in the code"
115
+ task :todo do
116
+ egrep /(FIXME|TODO|TBD)/
117
+ 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/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
+ require 'dont_repeat_yourself/unit_testing_helpers'
5
+
6
+ module DontRepeatYourself
7
+ end
@@ -0,0 +1,68 @@
1
+ require File.dirname(__FILE__) + '/reporter'
2
+ require 'optparse'
3
+
4
+ module DontRepeatYourself
5
+ class CLI
6
+
7
+ class << self
8
+
9
+ def execute
10
+ parse(ARGV).execute!
11
+ end
12
+
13
+ def parse(args)
14
+ cli = new
15
+ cli.parse_options!(args)
16
+ cli
17
+ end
18
+ end
19
+
20
+ attr_reader :options
21
+ FORMATS = DontRepeatYourself::REPORT_TYPES.map{|format| format.downcase}
22
+
23
+ def initialize
24
+
25
+ end
26
+
27
+ def parse_options!(args)
28
+ args.extend(OptionParser::Arguable)
29
+
30
+ @options = { :basedir => './', :format => 'default' }
31
+ args.options do |opts|
32
+ opts.banner = "Usage: dry-report [options] "
33
+
34
+ opts.on("-d BASEDIR", "--basedir BASEDIR", "set up the base directory of your ruby project, current directory by default") do |b|
35
+ @options[:basedir] = b
36
+ end
37
+
38
+ opts.on("-f FORMAT", "--format FORMAT", "report format (default is plain text)",
39
+ "Available formats: #{FORMATS.join(", ")}") do |v|
40
+ unless FORMATS.index(v)
41
+ STDERR.puts "Invalid format: #{v}\n"
42
+ STDERR.puts opts.help
43
+ exit 1
44
+ end
45
+ @options[:format] = v
46
+ end
47
+
48
+ opts.on("-t THRESHOLD", "--threshold THRESHOLD", "threshold of duplicate lines") do |t|
49
+ @options[:threshold] = t.to_i
50
+ end
51
+
52
+ opts.on_tail("--help", "You're looking at it. Any question? http://21croissants.blogspot.com/2008/10/dry.html") do
53
+ puts opts.help
54
+ exit
55
+ end
56
+ end.parse!
57
+
58
+ end
59
+
60
+ def execute!
61
+ ruby_project_reporter = DontRepeatYourself::RubyProjectReporter.new(@options[:basedir])
62
+ ruby_project_reporter.with_threshold_of_duplicate_lines(@options[:threshold]) if @options.has_key?(:threshold)
63
+ ruby_project_reporter.send("with_#{@options[:format]}_reporting") if @options.has_key?(:format)
64
+ STDOUT.print(ruby_project_reporter.report)
65
+ end
66
+ end # class
67
+
68
+ 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,134 @@
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 =
10
+ "Default", "HTML", "Netbeans", "TextMate" unless defined?(DontRepeatYourself::DEFAULT_REPORT)
11
+
12
+ DEFAULT_REPORT_DESC = "display the default plain report"
13
+ NETBEANS_REPORT_DESC = "display the report in the Output window of the Netbeans IDE (Ctrl+4)"
14
+ HTML_REPORT_DESC = "generate an DRY_report.html file in the project root folder"
15
+ TEXTMATE_REPORT_DESC = "to generate an html report with links which open files in the Textmate editor"
16
+
17
+ REPORT_TYPES = [DEFAULT_REPORT, NETBEANS_REPORT, HTML_REPORT, TEXTMATE_REPORT] unless defined?(DontRepeatYourself::REPORT_TYPES)
18
+
19
+ class ProjectReporterBase
20
+ attr_reader :name, :maximum_number_of_duplicate_lines_i_want_in_my_project, :report_type
21
+
22
+ def initialize(name)
23
+ @name = name
24
+ @simian_runner = DontRepeatYourself::SimianRunner.new
25
+
26
+ # Default values
27
+ @maximum_number_of_duplicate_lines_i_want_in_my_project = 0
28
+ @report_type = DontRepeatYourself::DEFAULT_REPORT
29
+ end
30
+
31
+ def patterns_of_directories_to_search_for_duplicate_lines
32
+ @simian_runner.patterns_of_directories_to_search_for_duplicate_lines
33
+ end
34
+
35
+ # Fluent interface methods
36
+ def with_threshold_of_duplicate_lines(threshold)
37
+ @simian_runner.threshold = threshold
38
+ return self
39
+ end
40
+
41
+ # with_<REPORT_TYPE>_reporting fluent methods
42
+ REPORT_TYPES.each do |report_type|
43
+ define_method("with_#{report_type.downcase}_reporting") do
44
+ @report_type = eval("DontRepeatYourself::#{report_type.upcase}_REPORT")
45
+ self
46
+ end
47
+ end
48
+
49
+ def ignoring_the_file(path)
50
+ @simian_runner.ignore_file(path)
51
+ self
52
+ end
53
+
54
+ def ignoring_the_directory(path)
55
+ @simian_runner.ignore_directory(path)
56
+ self
57
+ end
58
+
59
+ def basedir
60
+ @simian_runner.basedir
61
+ end
62
+
63
+ def report
64
+ DontRepeatYourself::FormatterFactory.create_report(@report_type, run_simian)
65
+ end
66
+
67
+ # TODO Not very readable: you have to read the code of run_simian to understand
68
+ def is_dry?
69
+ run_simian.duplicate_line_count <= @maximum_number_of_duplicate_lines_i_want_in_my_project
70
+ end
71
+
72
+ def description
73
+ "DRY\n" << " - with a threshold of #{@simian_runner.threshold} duplicate lines"
74
+ end
75
+
76
+ def failure_message
77
+ "expected #{@name} to have less or equal #{@maximum_number_of_duplicate_lines_i_want_in_my_project} duplicate lines :\n
78
+ DRY Report:\n#{report}\n"
79
+ end
80
+
81
+ protected
82
+
83
+ def run_simian
84
+ return @simian_results if @simian_results # so we don't run simian if we already have results
85
+ self.configure_simian
86
+ results_in_yaml_format = @simian_runner.run
87
+ @simian_results = DontRepeatYourself::SimianResults.new(results_in_yaml_format)
88
+ end
89
+
90
+ def basename_of_directory(directory)
91
+ Pathname.new(directory).basename.to_s
92
+ end
93
+
94
+ def folder_exists?(folder)
95
+ File.directory?(@simian_runner.basedir + "/" + folder)
96
+ end
97
+ end
98
+
99
+ # TODO Rename to RubyGemProjectReporter. we might add a "java" project reporter at some point 1 day ;-)
100
+ class RubyProjectReporter < ProjectReporterBase
101
+ def initialize(project_path)
102
+ super(basename_of_directory(project_path))
103
+ @simian_runner.basedir = project_path
104
+ end
105
+
106
+ def configure_simian
107
+ # Only add the "lib" directoy if exists
108
+ @simian_runner.add_ruby_directory_to_search_for_duplicate_lines("lib")
109
+ @simian_runner.add_ruby_directory_to_search_for_duplicate_lines("test") if folder_exists?("test")
110
+ @simian_runner.add_ruby_directory_to_search_for_duplicate_lines("spec") if folder_exists?("spec")
111
+ end
112
+ end
113
+
114
+ class RailsProjectReporter < RubyProjectReporter
115
+ def configure_simian
116
+ @simian_runner.add_ruby_directory_to_search_for_duplicate_lines("app")
117
+ @simian_runner.add_ruby_directory_to_search_for_duplicate_lines("lib")
118
+ @simian_runner.add_html_directory_to_search_for_duplicate_lines("app/views")
119
+ end
120
+ end
121
+
122
+ class RailsPluginProjectReporter < ProjectReporterBase
123
+ def initialize(plugin_name)
124
+ super(plugin_name )
125
+ end
126
+
127
+ def configure_simian
128
+ @simian_runner.basedir = "#{RAILS_ROOT}/vendor/plugins/#{@name}"
129
+ @simian_runner.add_ruby_directory_to_search_for_duplicate_lines("lib")
130
+ # @project_name = project_basename + " plugin"
131
+ end
132
+ end
133
+
134
+ 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,135 @@
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.24.jar.xyz')
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
+ # TODO if the next line usefull ?
48
+ @patterns_of_directories_to_search_for_duplicate_lines << (path + "/*.rb")
49
+ @patterns_of_directories_to_search_for_duplicate_lines << (path + "/**/*.rb")
50
+ end
51
+
52
+ def add_html_directory_to_search_for_duplicate_lines(path)
53
+ valid_directory_path(path)
54
+ @patterns_of_directories_to_search_for_duplicate_lines << (path + "/**/*.*html")
55
+ end
56
+
57
+ def ignore_file(path)
58
+ valid_file_path(path)
59
+ @patterns_of_directories_to_exclude_for_duplicate_lines << "/" + path
60
+ end
61
+
62
+ def ignore_directory(path)
63
+ valid_directory_path(path)
64
+ @patterns_of_directories_to_exclude_for_duplicate_lines << add_ruby_pattern(path)
65
+ end
66
+
67
+ def run
68
+ run_java
69
+ results_yaml = YAML.load(simian_output_with_header_removed)
70
+ delete_simian_log_file
71
+ results_yaml
72
+ end
73
+
74
+ private
75
+
76
+ def add_ruby_pattern(path)
77
+ "#{path}/**/*.rb"
78
+ end
79
+
80
+ def parameter_threshold
81
+ "-threshold=#{@threshold}"
82
+ end
83
+
84
+ def parameter_includes
85
+ generate_commandline_parameter("includes", @patterns_of_directories_to_search_for_duplicate_lines)
86
+ end
87
+
88
+ def parameter_excludes
89
+ generate_commandline_parameter("excludes", @patterns_of_directories_to_exclude_for_duplicate_lines)
90
+ end
91
+
92
+ def generate_commandline_parameter(paramater_name, parameter_list)
93
+ parameter_list.map { |pattern|
94
+ "-#{paramater_name}=#{File.join(@basedir, pattern)}"
95
+ } * ' '
96
+ end
97
+
98
+ def command_line
99
+ "#{@executable} #{parameter_threshold} #{@formatter_option} #{parameter_includes} #{parameter_excludes} > #{@simian_log_file}"
100
+ end
101
+
102
+ # TODO Add return code processing
103
+ def run_java
104
+ system(command_line)
105
+ end
106
+
107
+ def simian_output_with_header_removed
108
+ # Remove the Simian text header
109
+ log = IO.read(@simian_log_file)
110
+ log[/simian:.*/m] # m <=> Multiline mode
111
+ end
112
+
113
+ def delete_simian_log_file
114
+ File.delete @simian_log_file if File.file?(@simian_log_file)
115
+ end
116
+
117
+ def valid_directory_path(path)
118
+ absolute_path = File.join(@basedir, "/" + path)
119
+ if !File.directory?(absolute_path)
120
+ raise ArgumentError.new(absolute_path << " does not exist, path should be relative to #{@basedir} and not start neither end with '/' ")
121
+ end
122
+ absolute_path
123
+ end
124
+
125
+ def valid_file_path(path)
126
+ absolute_path = File.join(@basedir, "/" + path)
127
+ if !File.file?(absolute_path)
128
+ raise ArgumentError.new(absolute_path << " file does not exist, path should be relative to #{@basedir} and not start neither end with '/' ")
129
+ end
130
+ absolute_path
131
+ end
132
+
133
+ end
134
+
135
+ 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,76 @@
1
+ require File.dirname(__FILE__) + '/reporter'
2
+ require 'spec'
3
+
4
+ # TODO separate the file!
5
+ require 'test/unit/assertions'
6
+
7
+ module DontRepeatYourself
8
+
9
+ module UnitTestingHelpers
10
+
11
+ module RubyProjectHelpers
12
+ def ruby_project(project_path)
13
+ DontRepeatYourself::RubyProjectReporter.new(project_path)
14
+ end
15
+ def ruby_code_in_rails_plugin(plugin_name)
16
+ DontRepeatYourself::RailsPluginProjectReporter.new(plugin_name)
17
+ end
18
+
19
+ def rails_application(rails_root = RAILS_ROOT)
20
+ DontRepeatYourself::RailsProjectReporter.new(rails_root)
21
+ end
22
+ end
23
+
24
+ module TestUnitExtension
25
+ include DontRepeatYourself::UnitTestingHelpers::RubyProjectHelpers
26
+
27
+ def assert_dry(project)
28
+ assert(project.is_dry?, project.failure_message)
29
+ end
30
+ end
31
+
32
+ module RSpecMatchers
33
+
34
+ include DontRepeatYourself::UnitTestingHelpers::RubyProjectHelpers
35
+
36
+ class BeDRYMatcher
37
+
38
+ def matches?(project)
39
+ @project = project
40
+ @project.is_dry?
41
+ end
42
+
43
+ # TODO Do we really need this? It does not make a lot of sense
44
+ def negative_failure_message
45
+ "expected #{@project.name} to have more than #{@project.maximum_number_of_duplicate_lines_i_want_in_my_project} duplicate lines :\n but found the following:\n "
46
+ end
47
+
48
+ def description
49
+ "follow the 'Don't Repeat Yourself' principle " << @project.description
50
+ end
51
+
52
+ def failure_message
53
+ @project.failure_message
54
+ end
55
+
56
+ end
57
+
58
+ # Custom expectation matcher
59
+ def follow_the_dry_principle
60
+ DontRepeatYourself::UnitTestingHelpers::RSpecMatchers::BeDRYMatcher.new
61
+ end
62
+
63
+ end
64
+ end
65
+ end
66
+
67
+ # Automatically includes assert_dry
68
+ module Test
69
+ module Unit
70
+ class TestCase #:nodoc:
71
+ include DontRepeatYourself::UnitTestingHelpers::RubyProjectHelpers
72
+ include DontRepeatYourself::UnitTestingHelpers::TestUnitExtension
73
+ end
74
+ end
75
+ end
76
+
@@ -0,0 +1,9 @@
1
+ module DontRepeatYourself #:nodoc:
2
+ class VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 3
5
+ TINY = 0
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
Binary file
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: garnierjm-dont_repeat_yourself
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Jean-Michel Garnier
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-19 00:00:00 -08: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
+ - !ruby/object:Gem::Dependency
25
+ name: hoe
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 1.8.2
32
+ version:
33
+ description: Generate duplicate lines report
34
+ email:
35
+ - jm AT 21croissants dot com
36
+ executables:
37
+ - dry-report
38
+ extensions: []
39
+
40
+ extra_rdoc_files:
41
+ - README.txt
42
+ files:
43
+ - SIMIAN-LICENSE
44
+ - MIT-LICENSE
45
+ - README.txt
46
+ - Rakefile
47
+ - bin/dry-report
48
+ - lib/assets
49
+ - lib/assets/dry.css
50
+ - lib/assets/dry.js
51
+ - lib/dont_repeat_yourself
52
+ - lib/dont_repeat_yourself/formatter.rb
53
+ - lib/dont_repeat_yourself/reporter.rb
54
+ - lib/dont_repeat_yourself/simian_results.rb
55
+ - lib/dont_repeat_yourself/simian_runner.rb
56
+ - lib/dont_repeat_yourself/snippet_extractor.rb
57
+ - lib/dont_repeat_yourself/unit_testing_helpers.rb
58
+ - lib/dont_repeat_yourself/cli.rb
59
+ - lib/dont_repeat_yourself/version.rb
60
+ - lib/jars
61
+ - lib/jars/simian-2.2.24.jar.xyz
62
+ - lib/dont_repeat_yourself.rb
63
+ has_rdoc: true
64
+ homepage: http://github.com/garnierjm/dont_repeat_yourself
65
+ post_install_message:
66
+ rdoc_options:
67
+ - --main
68
+ - README.txt
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ version:
83
+ requirements: []
84
+
85
+ rubyforge_project: dont_repeat_yourself
86
+ rubygems_version: 1.2.0
87
+ signing_key:
88
+ specification_version: 2
89
+ summary: Generate duplicate lines report
90
+ test_files: []
91
+