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 +20 -0
- data/README.txt +20 -0
- data/Rakefile +117 -0
- data/SIMIAN-LICENSE +101 -0
- data/bin/dry-report +4 -0
- data/lib/assets/dry.css +127 -0
- data/lib/assets/dry.js +29 -0
- data/lib/dont_repeat_yourself.rb +7 -0
- data/lib/dont_repeat_yourself/cli.rb +68 -0
- data/lib/dont_repeat_yourself/formatter.rb +139 -0
- data/lib/dont_repeat_yourself/reporter.rb +134 -0
- data/lib/dont_repeat_yourself/simian_results.rb +92 -0
- data/lib/dont_repeat_yourself/simian_runner.rb +135 -0
- data/lib/dont_repeat_yourself/snippet_extractor.rb +31 -0
- data/lib/dont_repeat_yourself/unit_testing_helpers.rb +76 -0
- data/lib/dont_repeat_yourself/version.rb +9 -0
- data/lib/jars/simian-2.2.24.jar.xyz +0 -0
- metadata +91 -0
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
data/lib/assets/dry.css
ADDED
@@ -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,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/, " ").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> [<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
|
+
|
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
|
+
|