garnierjm-dry-report 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README +17 -0
- data/Rakefile +73 -0
- data/SIMIAN-LICENSE +101 -0
- data/TODO +33 -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 +74 -0
- data/lib/dont_repeat_yourself/formatter.rb +139 -0
- data/lib/dont_repeat_yourself/reporter.rb +97 -0
- data/lib/dont_repeat_yourself/simian_results.rb +92 -0
- data/lib/dont_repeat_yourself/simian_runner.rb +127 -0
- data/lib/dont_repeat_yourself/snippet_extractor.rb +31 -0
- data/lib/dont_repeat_yourself/unit_testing_helpers.rb +87 -0
- data/lib/jars/simian-2.2.22.jar.txt +0 -0
- metadata +84 -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
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
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,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/, " ").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,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
|
+
|