duvet 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Joshua Hawxwell
1
+ Copyright (c) 2010-12 Joshua Hawxwell
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Duvet
2
2
 
3
- Install with
4
-
3
+ Install with:
4
+
5
5
  ``` bash
6
6
  $ gem install duvet
7
7
  ```
@@ -13,23 +13,40 @@ require 'duvet'
13
13
  Duvet.start
14
14
  ```
15
15
 
16
- You can change the defaults by passing an options hash to Duvet.start, eg
16
+ Because `duvet` won't work for Ruby 1.8 you may want to rescue the error and
17
+ move on,
18
+
19
+ ``` ruby
20
+ begin
21
+ require 'duvet'
22
+ Duvet.start
23
+ rescue LoadError
24
+ # ignore error
25
+ end
26
+ ```
27
+
28
+ You can change the defaults by passing options to `Duvet.start`, for example:
17
29
 
18
30
  ``` ruby
19
31
  Duvet.start :dir => 'coverage', :filter => 'app/lib'
20
32
  ```
21
33
 
22
- `:dir` is the directory to write the coverage to.
23
- `:filter` allows you to display only coverage for files that include the string, or you can
24
- match against a regular expression for more control.
34
+ Where `:dir` is the directory to write the coverage to and `:filter` is a string
35
+ that a files path must match against. A regular expression can be used for more
36
+ control, but most of the time a simple string will suffice.
25
37
 
26
- You can see what the output of running duvet on itself is [here](http://hawx.github.com/duvet).
38
+ You can see the output of running `duvet` on itself [here][covg].
27
39
 
28
40
 
29
41
  ## Credits
30
42
 
31
- This gem was created because I read this blog post <http://engineering.attinteractive.com/2010/08/code-coverage-in-ruby-1-9/>.
43
+ This gem was created because I read this [blog post][post] on the AT&T
44
+ engineering site by Aaron Patterson.
32
45
 
33
46
  ## Copyright
34
47
 
35
- Copyright (c) 2010 Joshua Hawxwell. See LICENSE for details.
48
+ Copyright (c) 2010-11 Joshua Hawxwell. See LICENSE for details.
49
+
50
+
51
+ [covg]: http://hawx.github.com/duvet
52
+ [post]: http://engineering.attinteractive.com/2010/08/code-coverage-in-ruby-1-9/
data/Rakefile CHANGED
@@ -1,9 +1,20 @@
1
1
  require 'rake/testtask'
2
2
 
3
3
  Rake::TestTask.new(:test) do |t|
4
- t.libs << 'lib' << 'test'
5
- t.pattern = 'test/**/test_*.rb'
4
+ t.libs << 'lib' << 'spec'
5
+ t.pattern = 'spec/**/*_spec.rb'
6
6
  t.verbose = true
7
7
  end
8
8
 
9
+
10
+ task :build do
11
+ require 'sass'
12
+ path = File.dirname(__FILE__) + '/templates/css/'
13
+ content = Sass::Engine.new(IO.read(path + 'styles.sass')).render
14
+
15
+ File.open path + 'styles.css', 'w' do |f|
16
+ f.write content
17
+ end
18
+ end
19
+
9
20
  task :default => :test
@@ -6,72 +6,130 @@
6
6
 
7
7
  require 'coverage'
8
8
  require 'pathname'
9
- require 'erubis'
9
+ require 'erb'
10
10
  require 'sass'
11
11
 
12
- require 'duvet/core_ext'
13
12
  require 'duvet/covs'
14
13
  require 'duvet/cov'
15
14
  require 'duvet/version'
16
15
 
17
16
  module Duvet
17
+ extend self
18
18
 
19
19
  attr_accessor :opts
20
-
21
- DEFAULTS = {:dir => 'cov', :style => 'rcov'}
22
-
23
- TEMPLATE_PATH = Pathname.new(__FILE__).dirname + '..' + 'templates'
24
-
20
+
21
+ DEFAULTS = {
22
+ dir: 'cov',
23
+ style: 'rcov',
24
+ filter: ''
25
+ }
26
+
25
27
  TEMPLATE_HASH = {
26
- 'time' => Time.now,
27
- 'version' => VERSION,
28
- 'name' => 'duvet'
28
+ name: 'duvet',
29
+ time: Time.now,
30
+ version: VERSION
29
31
  }
30
32
 
31
- # Start tracking
32
- def self.start(opts={})
33
+ TEMPLATE_PATH = Pathname.new(__FILE__).dirname + '..' + 'templates'
34
+
35
+ # Start coverage tracking, needs to be called before any files are loaded.
36
+ #
37
+ # @param opts [Hash]
38
+ # @option opts [String, Pathname] :dir Directory to write coverage to
39
+ # @option opts [String, Regexp] :filter Pattern files must match to be written
40
+ def start(opts={})
33
41
  @opts = DEFAULTS.merge(opts)
34
-
42
+ @opts[:filter] = Regexp.new(@opts[:filter])
43
+ @opts[:dir] = Pathname.new(@opts[:dir])
44
+
35
45
  Coverage.start
36
- @running = true
37
46
  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
47
+
48
+ # Gets the results from the Coverage, filtered against the patterm given to
49
+ # #start.
50
+ #
51
+ # @return [Covs]
52
+ def result
53
+ cov = Coverage.result.find_all do |path, c|
54
+ @opts[:filter] =~ path
55
+ end
56
+
57
+ Duvet::Covs.from_data cov
58
+ end
59
+
60
+ # Creates a class with methods for rendering an ERB template.
61
+ #
62
+ # @param obj [Object] Object to create context for
63
+ # @return [#get_binding] Object which gives a binding for ERB templates
64
+ def context_for(obj)
65
+ case obj
66
+ when Array
67
+ obj.map {|i| context_for(i) }
68
+ when Hash
69
+ klass = Class.new
70
+ klass.send(:define_method, :get_binding) { binding }
71
+
72
+ obj.each do |k,v|
73
+ klass.send(:define_method, k) { Duvet.context_for(v) }
49
74
  end
50
- cov = filtered
75
+
76
+ klass.new
77
+ else
78
+ obj
51
79
  end
52
- @result ||= Duvet::Covs.new(cov)
53
- ensure
54
- @running = false
55
80
  end
56
-
57
- # Write results
58
- def self.write
59
- self.result.write(Pathname.new(@opts[:dir])) if running?
81
+
82
+ # Renders a template with the data given.
83
+ #
84
+ # @param data [Hash] Data to insert into template
85
+ # @param template [String] Path to template from TEMPLATE_PATH
86
+ def format(data, template)
87
+ t = (TEMPLATE_PATH + template).read
88
+ vars = context_for TEMPLATE_HASH.merge(data)
89
+
90
+ ERB.new(t).result(vars.get_binding)
60
91
  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 }
92
+
93
+ # Renders then writes a file based on the url in the data given.
94
+ #
95
+ # @param data [Hash] Data to insert into template
96
+ # @param template [String] Path to template from TEMPLATE_PATH
97
+ def write(data, template)
98
+ write_file format(data, template), data[:file][:url]
66
99
  end
67
-
68
- # @return [Boolean] whether coverage is running
69
- def self.running?
70
- @running
100
+
101
+ # Writes a file, creating directories where necessary.
102
+ #
103
+ # @param text [String] Text to write to file
104
+ # @param path [String] Path to write file to
105
+ def write_file(text, path)
106
+ write_to = @opts[:dir] + path
107
+
108
+ FileUtils.mkdir_p write_to.dirname
109
+ File.open write_to, 'w' do |f|
110
+ f.write text
111
+ end
112
+ end
113
+
114
+ # Writes all the resources to the correct directory.
115
+ def write_resources
116
+ paths = %w(css/styles.css js/main.js js/jquery.js js/plugins.js)
117
+
118
+ paths.each do |path|
119
+ path = Pathname.new(TEMPLATE_PATH + path)
120
+ write_file path.read, path.basename
121
+ end
122
+ end
123
+
124
+ # Call this on exit so that coverage is written.
125
+ def finish
126
+ FileUtils.mkdir_p @opts[:dir]
127
+ result.write
128
+ write_resources
71
129
  end
72
130
 
73
131
  end
74
132
 
75
133
  at_exit do
76
- Duvet.at_exit.call
134
+ Duvet.finish
77
135
  end
@@ -1,100 +1,93 @@
1
1
  module Duvet
2
+
3
+ # A single file along with coverage data.
2
4
  class Cov
3
5
  attr_accessor :path
4
-
6
+
5
7
  def initialize(path, cov)
6
8
  @path = Pathname.new(path)
7
9
  if @path.to_s.include?(Dir.pwd)
8
10
  @path = @path.relative_path_from(Pathname.getwd)
9
11
  end
10
-
12
+
11
13
  @cov = cov
12
14
  end
13
-
14
- # @return [Array] lines in file
15
+
16
+ # @return [Array] Coverage data of lines in the file
15
17
  def lines
16
18
  @cov
17
19
  end
18
-
19
- # @return [Array] all lines which can be executed
20
+
21
+ # @return [Array] Coverage data for all the lines which can be executed
20
22
  def code_lines
21
- @cov.reject {|i| i.nil?}
23
+ lines.reject {|i| i.nil?}
22
24
  end
23
-
24
- # @return [Array] all lines which have been ran
25
+
26
+ # @return [Array] Coverage data for all the lines which have been ran
25
27
  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
28
+ code_lines.reject {|i| i.zero?}
37
29
  end
38
30
 
39
- # Similar to #code_coverage but counts all lines, executable
40
- # or not.
31
+ # Gives a real number between 0 and 1 indicating how many lines have been
32
+ # executed.
41
33
  #
42
- # @return [Integer] lines executed as a fraction
34
+ # @return [Float] lines executed as a fraction
43
35
  def total_coverage
44
36
  return 0.0 if lines.size.zero?
45
37
  ran_lines.size.to_f / lines.size.to_f
46
38
  end
47
-
48
- # @return [String] #code_coverage as ??.??%
49
- def code_coverage_percent
50
- "%.2f%" % (code_coverage*100)
39
+
40
+ # Gives a real number between 0 and 1 indicating how many lines of code have
41
+ # been executed. It ignores all lines that can not be executed such as
42
+ # comments.
43
+ #
44
+ # @return [Float] lines of code executed as a fraction
45
+ def code_coverage
46
+ return 0.0 if code_lines.size.zero?
47
+ ran_lines.size.to_f / code_lines.size.to_f
51
48
  end
52
-
53
- # @return [String] #total_coverage as ??.??%
54
- def total_coverage_percent
55
- "%.2f%" % (total_coverage*100)
49
+
50
+ # @param [Float] Number to format
51
+ # @return [String] The number formatted as a percentage, ??.??%
52
+ def percent(num)
53
+ "%.2f%" % (num * 100)
56
54
  end
57
-
58
- # @return [String]
55
+
56
+ # @return [String] A simple text report of coverage
59
57
  def report
60
- str = "#{@path}\n"
61
- str << " total: #{total_coverage_percent}\n"
62
- str << " code: #{code_coverage_percent}\n\n"
63
- str
58
+ <<EOS
59
+ #{@path}
60
+ total: #{percent(total_coverage)}
61
+ code: #{percent(code_coverage)}
62
+ EOS
64
63
  end
65
-
66
- # @return [Hash] a hash of data for templating
64
+
65
+ # @return [Hash] Data for templating
67
66
  def data
68
67
  {
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
68
+ file: {
69
+ path: @path.to_s,
70
+ url: @path.to_s[0..-@path.extname.size] + 'html',
71
+ root: '../' * @path.to_s.count('/'),
72
+ source: @path.readlines,
73
+ },
74
+ lines: {
75
+ total: lines.size,
76
+ code: code_lines.size,
77
+ ran: ran_lines.size
76
78
  },
77
- "coverage" => {
78
- "code" => code_coverage_percent,
79
- "total" => total_coverage_percent,
80
- "lines" => lines
79
+ coverage: {
80
+ code: percent(code_coverage),
81
+ total: percent(total_coverage),
82
+ lines: lines
81
83
  }
82
84
  }
83
85
  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) }
86
+
87
+ # Writes the file
88
+ def write
89
+ Duvet.write data, 'html/file.erb'
97
90
  end
98
-
91
+
99
92
  end
100
- end
93
+ end
@@ -1,49 +1,37 @@
1
1
  module Duvet
2
+
3
+ # A list of Cov objects.
2
4
  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
5
+
6
+ # Creates a new Covs array from the data given by Coverage.
7
+ #
8
+ # @param data [Hash] Data given by Coverage
9
+ def self.from_data(data)
10
+ new data.map {|p,c| Cov.new(p, c) }
9
11
  end
10
-
12
+
13
+ # @return [String] A simple text report of coverage
11
14
  def report
12
- map {|i| i.report }.join('')
15
+ map(&:report).join("\n") + "\n"
13
16
  end
14
-
17
+
18
+ # @return [Hash] Data used for templating
15
19
  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
20
+ {
21
+ files: map(&:data),
22
+ file: {
23
+ url: 'index.html'
24
+ }
25
+ }
34
26
  end
35
27
 
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
28
+ # Writes the index and individual files.
29
+ def write
30
+ warn "No files to create coverage for." if empty?
41
31
 
42
- Pathname.glob(TEMPLATE_PATH + 'js' + '*').each do |i|
43
- f = File.new(dir + i.basename, 'w')
44
- f.write(i.read)
45
- end
32
+ each &:write
33
+ Duvet.write data, 'html/index.erb'
46
34
  end
47
-
35
+
48
36
  end
49
37
  end