duvet 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/duvet.rb ADDED
@@ -0,0 +1,77 @@
1
+ # Add this to test/helper.rb at the before __anything__ else
2
+ #
3
+ # require 'duvet'
4
+ # Duvet.start
5
+ #
6
+
7
+ require 'coverage'
8
+ require 'pathname'
9
+ require 'erubis'
10
+ require 'sass'
11
+
12
+ require 'duvet/core_ext'
13
+ require 'duvet/covs'
14
+ require 'duvet/cov'
15
+ require 'duvet/version'
16
+
17
+ module Duvet
18
+
19
+ attr_accessor :opts
20
+
21
+ DEFAULTS = {:dir => 'cov', :style => 'rcov'}
22
+
23
+ TEMPLATE_PATH = Pathname.new(__FILE__).dirname + '..' + 'templates'
24
+
25
+ TEMPLATE_HASH = {
26
+ 'time' => Time.now,
27
+ 'version' => VERSION,
28
+ 'name' => 'duvet'
29
+ }
30
+
31
+ # Start tracking
32
+ def self.start(opts={})
33
+ @opts = DEFAULTS.merge(opts)
34
+
35
+ Coverage.start
36
+ @running = true
37
+ end
38
+
39
+ # Get result
40
+ def self.result
41
+ cov = Coverage.result if running?
42
+ if @opts[:filter]
43
+ filtered = {}
44
+ @opts[:filter] = /#{@opts[:filter]}/ unless @opts[:filter].is_a?(Regexp)
45
+ cov.each do |k, v|
46
+ if @opts[:filter] =~ k
47
+ filtered[k] = v
48
+ end
49
+ end
50
+ cov = filtered
51
+ end
52
+ @result ||= Duvet::Covs.new(cov)
53
+ ensure
54
+ @running = false
55
+ end
56
+
57
+ # Write results
58
+ def self.write
59
+ self.result.write(Pathname.new(@opts[:dir])) if running?
60
+ end
61
+
62
+ # Proc to call when exiting
63
+ # @todo Allow user to override block used
64
+ def self.at_exit
65
+ Proc.new { self.write }
66
+ end
67
+
68
+ # @return [Boolean] whether coverage is running
69
+ def self.running?
70
+ @running
71
+ end
72
+
73
+ end
74
+
75
+ at_exit do
76
+ Duvet.at_exit.call
77
+ end
@@ -0,0 +1,17 @@
1
+ class Pathname
2
+
3
+ # Gets just the extension of the pathname
4
+ #
5
+ # @return [String] the extension of the path, without the '.'
6
+ def extension
7
+ self.extname[1..-1]
8
+ end
9
+
10
+ # Gets the name of the file without the extension
11
+ #
12
+ # @return [String] name of the file
13
+ def file_name
14
+ self.basename.to_s[0...-(self.extension.size+1)]
15
+ end
16
+
17
+ end
data/lib/duvet/cov.rb ADDED
@@ -0,0 +1,100 @@
1
+ module Duvet
2
+ class Cov
3
+ attr_accessor :path
4
+
5
+ def initialize(path, cov)
6
+ @path = Pathname.new(path)
7
+ if @path.to_s.include?(Dir.pwd)
8
+ @path = @path.relative_path_from(Pathname.getwd)
9
+ end
10
+
11
+ @cov = cov
12
+ end
13
+
14
+ # @return [Array] lines in file
15
+ def lines
16
+ @cov
17
+ end
18
+
19
+ # @return [Array] all lines which can be executed
20
+ def code_lines
21
+ @cov.reject {|i| i.nil?}
22
+ end
23
+
24
+ # @return [Array] all lines which have been ran
25
+ def ran_lines
26
+ @cov.reject {|i| i.nil? || i.zero?}
27
+ end
28
+
29
+ # Gives a fraction from 0 to 1 of how many lines of code have
30
+ # been executed. It ignores all lines that couldn't be executed
31
+ # such as comments.
32
+ #
33
+ # @return [Float] lines of code executed as a fraction
34
+ def code_coverage
35
+ return 0.0 if code_lines.size.zero?
36
+ ran_lines.size.to_f / code_lines.size.to_f
37
+ end
38
+
39
+ # Similar to #code_coverage but counts all lines, executable
40
+ # or not.
41
+ #
42
+ # @return [Integer] lines executed as a fraction
43
+ def total_coverage
44
+ return 0.0 if lines.size.zero?
45
+ ran_lines.size.to_f / lines.size.to_f
46
+ end
47
+
48
+ # @return [String] #code_coverage as ??.??%
49
+ def code_coverage_percent
50
+ "%.2f%" % (code_coverage*100)
51
+ end
52
+
53
+ # @return [String] #total_coverage as ??.??%
54
+ def total_coverage_percent
55
+ "%.2f%" % (total_coverage*100)
56
+ end
57
+
58
+ # @return [String]
59
+ def report
60
+ str = "#{@path}\n"
61
+ str << " total: #{total_coverage_percent}\n"
62
+ str << " code: #{code_coverage_percent}\n\n"
63
+ str
64
+ end
65
+
66
+ # @return [Hash] a hash of data for templating
67
+ def data
68
+ {
69
+ "file" => {
70
+ "path" => @path.to_s,
71
+ "url" => @path.file_name + '.html',
72
+ "source" => @path.readlines,
73
+ "lines" => lines.size,
74
+ "lines_code" => code_lines.size,
75
+ "lines_ran" => ran_lines.size
76
+ },
77
+ "coverage" => {
78
+ "code" => code_coverage_percent,
79
+ "total" => total_coverage_percent,
80
+ "lines" => lines
81
+ }
82
+ }
83
+ end
84
+
85
+ # Formats the coverage for the file to be written to a html
86
+ # file, then viewed in a web browser.
87
+ #
88
+ # @return [String]
89
+ def format
90
+ template = (TEMPLATE_PATH + 'html' + 'file.erb').read
91
+ Erubis::Eruby.new(template).result(TEMPLATE_HASH.merge(self.data))
92
+ end
93
+
94
+ def write(dir)
95
+ path = (dir + @path.file_name).to_s + '.html'
96
+ File.open(path, 'w') {|f| f.write(self.format) }
97
+ end
98
+
99
+ end
100
+ end
data/lib/duvet/covs.rb ADDED
@@ -0,0 +1,49 @@
1
+ module Duvet
2
+ class Covs < Array
3
+
4
+ def initialize(cov)
5
+ self.replace []
6
+ cov.each do |path, c|
7
+ self << Cov.new(path, c)
8
+ end
9
+ end
10
+
11
+ def report
12
+ map {|i| i.report }.join('')
13
+ end
14
+
15
+ def data
16
+ { 'files' => map {|i| i.data } }
17
+ end
18
+
19
+ def format
20
+ template = (TEMPLATE_PATH + 'html' + 'index.erb').read
21
+ Erubis::Eruby.new(template).result(TEMPLATE_HASH.merge(self.data))
22
+ end
23
+
24
+ def write(dir)
25
+ if size > 0
26
+ FileUtils.mkdir_p(dir)
27
+ File.open(dir + 'index.html', 'w') {|f| f.write(format) }
28
+
29
+ each {|c| c.write(dir) }
30
+ write_resources(dir)
31
+ else
32
+ warn "No files to create coverage for."
33
+ end
34
+ end
35
+
36
+ def write_resources(dir)
37
+ Pathname.glob(TEMPLATE_PATH + 'css' + '*').each do |i|
38
+ f = File.new(dir + 'styles.css', 'w')
39
+ f.write Sass::Engine.new(i.read).render
40
+ end
41
+
42
+ Pathname.glob(TEMPLATE_PATH + 'js' + '*').each do |i|
43
+ f = File.new(dir + i.basename, 'w')
44
+ f.write(i.read)
45
+ end
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ module Duvet
2
+ VERSION = '0.3.3'
3
+ end
@@ -0,0 +1,178 @@
1
+ $text: rgb(0, 0, 0)
2
+
3
+ $dark-grey: #333
4
+ $light-grey: #ccc
5
+ $white: #fff
6
+
7
+ $excluded: #e0dedb
8
+ $unran: #ce8b8c
9
+ $ran: #bed2be
10
+
11
+ // Main
12
+ html
13
+ margin: 0
14
+ padding: 0
15
+ width: 100%
16
+
17
+ body
18
+ background: #f4f2ed
19
+ font: 12px/1.3em Helvetica
20
+ margin: 8px
21
+
22
+ a
23
+ text-decoration: none
24
+
25
+
26
+ // Header
27
+ header
28
+ margin: 2em .5em
29
+ text-shadow: 0 1px 0 rgba(255, 255, 255, .5)
30
+
31
+ h1, h1 a
32
+ color: $dark-grey
33
+
34
+ h2
35
+ color: lighten($dark-grey, 40%)
36
+ font-size: 16px
37
+
38
+ // Filter/Search Box
39
+ #filter
40
+ position: absolute
41
+ top: 20px
42
+ right: 8px
43
+ border: 1px solid rgb(120, 120, 120)
44
+ background: rgb(240, 240, 240)
45
+ -webkit-border-radius: 3px
46
+ -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, .1), 0 1px 0 white
47
+ -webkit-transition: all .2s
48
+ padding: 4px
49
+ width: 180px
50
+
51
+ &:focus
52
+ outline: none
53
+ background: rgb(244, 244, 244)
54
+ border: 1px solid rgb(100, 100, 100)
55
+
56
+
57
+ // Table
58
+ .table-wrapper
59
+ min-width: 700px
60
+
61
+ table
62
+ border: 1px solid grey
63
+ background: $excluded
64
+ border-collapse: collapse
65
+ width: 100%
66
+ margin-bottom: 1em
67
+ -webkit-box-shadow: 0 1px 0 white
68
+
69
+ table.source, pre, code
70
+ font: 11px/1.2em Menlo
71
+
72
+ td pre
73
+ margin: 0
74
+
75
+
76
+ // Summary
77
+ .summary
78
+ margin-bottom: 1em
79
+ background: $excluded
80
+
81
+ tbody
82
+ .name, .name a
83
+ color: $text
84
+ font-weight: bold
85
+
86
+ tbody tr:hover
87
+ background: darken($excluded, 10%)
88
+
89
+ thead
90
+ background: $dark-grey
91
+ color: $light-grey
92
+
93
+ th
94
+ cursor: pointer
95
+
96
+ tr
97
+ text-align: left
98
+
99
+ th, td
100
+ padding: .5em
101
+
102
+ .header::after
103
+ font-size: 10px
104
+ margin-left: 10px
105
+
106
+ .descending::after
107
+ content: '▼'
108
+
109
+ .ascending::after
110
+ content: '▲'
111
+
112
+ .lines, .loc, .ran, .cov, .code
113
+ width: 100px
114
+
115
+ // Totals
116
+ .totals
117
+ border-top: 1px solid grey
118
+ background: darken($excluded, 10%)
119
+ font-weight: bold
120
+
121
+
122
+ // Source
123
+ .source tr
124
+ pre
125
+ margin-left: 3px
126
+
127
+ .count
128
+ background: $white
129
+ -webkit-border-radius: 4px
130
+ -webkit-border-top-left-radius: 0px
131
+ -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .2)
132
+ border: 1px solid black
133
+ display: block
134
+ padding: 3px
135
+ position: absolute
136
+ margin-top: -1.3em
137
+ right: 15px
138
+ opacity: 0
139
+
140
+ .no
141
+ color: $light-grey
142
+ background: $dark-grey
143
+ padding: 0 0 0 3px
144
+ width: 25px
145
+
146
+ &:hover
147
+ .count
148
+ opacity: 1
149
+
150
+ .no
151
+ color: $white
152
+
153
+ // line colouring
154
+ &.excluded
155
+ background: $excluded
156
+ &:hover
157
+ background: darken($excluded, 10%)
158
+
159
+ &.unran
160
+ background: $unran
161
+ &:hover
162
+ background: darken($unran, 10%)
163
+
164
+ &.ran
165
+ background: $ran
166
+ &:hover
167
+ background: darken($ran, 10%)
168
+
169
+
170
+ // Footer
171
+ footer
172
+ color: rgba(0, 0, 0, .6)
173
+ text-shadow: 0 1px 0 rgba(255, 255, 255, .5)
174
+
175
+ a
176
+ color: rgb(0, 0, 0)
177
+
178
+
@@ -0,0 +1,71 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title><%= file['path'] %></title>
6
+ <link rel="stylesheet" href="styles.css" type="text/css" />
7
+ <script type="text/javascript" src="jquery.js"></script>
8
+ <script type="text/javascript" src="main.js"></script>
9
+ <script type="text/javascript" src="plugins.js"></script>
10
+ </head>
11
+
12
+ <body>
13
+ <header>
14
+ <h1><a href="index.html">Coverage</a></h1>
15
+ <h2><%= file['path'] %></h2>
16
+ </header>
17
+
18
+ <div class="table-wrapper">
19
+ <table class="summary" border="none">
20
+ <thead>
21
+ <tr>
22
+ <th>Name</th>
23
+ <th>Total Lines</th>
24
+ <th>Lines of Code</th>
25
+ <th>Lines Ran</th>
26
+ <th>Total Coverage</th>
27
+ <th>Code Coverage</th>
28
+ </tr>
29
+ </thead>
30
+ <tbody>
31
+ <tr>
32
+ <td class="name"><%= file['path'] %></td>
33
+ <td><%= file['lines'] %></td>
34
+ <td><%= file['lines_code']%></td>
35
+ <td><%= file['lines_ran']%></td>
36
+ <td><%= coverage['total']%></td>
37
+ <td><%= coverage['code']%></td>
38
+ </tr>
39
+ </tbody>
40
+ </table>
41
+ </div>
42
+
43
+ <div class="table-wrapper">
44
+ <table class="source" border="none">
45
+ <% file['source'].zip(coverage['lines']).each_with_index do |(line, count), i| %>
46
+ <% if count.nil? %>
47
+ <tr class="excluded">
48
+ <td class="no"><%= i %></td>
49
+ <td><pre><code><%= line %></code></pre></td>
50
+ </tr>
51
+
52
+ <% else %>
53
+ <tr class="<%= count.zero? ? "unran" : "ran" %>">
54
+ <td class="no"><%= i %></td>
55
+ <td><pre><code><%= line %></code></pre><span class="count"><%= count %></span></td>
56
+ </tr>
57
+ <% end %>
58
+ <% end %>
59
+ </table>
60
+ </div>
61
+
62
+
63
+ <footer>
64
+ <span>
65
+ Generated at <%= time %> with
66
+ <a href="http://github.com/hawx/duvet"><%= name %> <%= version %></a>
67
+ </span>
68
+ </footer>
69
+ </body>
70
+ </html>
71
+