duvet 0.3.3 → 0.4.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/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