duvet 0.3.2 → 0.3.3

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/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
+