rfs 0.1

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.
Files changed (57) hide show
  1. data/MIT-LICENSE +7 -0
  2. data/Rakefile.rb +193 -0
  3. data/bin/RenameFileSet.rbw +14 -0
  4. data/bin/rfs.rb +214 -0
  5. data/bin/rfsd.rb +8 -0
  6. data/description.txt +24 -0
  7. data/lib/RenameFileSet.bak.rb +372 -0
  8. data/lib/errors.rb +15 -0
  9. data/lib/filters.rb +68 -0
  10. data/lib/gui.rb +216 -0
  11. data/lib/innate/array.rb +104 -0
  12. data/lib/innate/coverage.rb +315 -0
  13. data/lib/innate/debug.rb +12 -0
  14. data/lib/innate/debugger.rb +944 -0
  15. data/lib/innate/file.rb +5 -0
  16. data/lib/innate/filelines.rb +148 -0
  17. data/lib/innate/kernel.rb +41 -0
  18. data/lib/innate/metaid.rb +30 -0
  19. data/lib/innate/mkdirs.rb +12 -0
  20. data/lib/innate/regexp.rb +80 -0
  21. data/lib/innate/reload.rb +11 -0
  22. data/lib/innate/roman.rb +72 -0
  23. data/lib/innate/scriptlines.rb +60 -0
  24. data/lib/innate/string.rb +56 -0
  25. data/lib/innate/test/all_tests.rb +11 -0
  26. data/lib/innate/test/files/mkdirs_dummy.file +1 -0
  27. data/lib/innate/test/files/reloadtarget.rb +5 -0
  28. data/lib/innate/test/files/reloadtarget1.rb +5 -0
  29. data/lib/innate/test/files/reloadtarget2.rb +5 -0
  30. data/lib/innate/test/test_coverage.rb +13 -0
  31. data/lib/innate/test/testarray.rb +98 -0
  32. data/lib/innate/test/testfile.rb +15 -0
  33. data/lib/innate/test/testfilelines.rb +106 -0
  34. data/lib/innate/test/testkernel.rb +30 -0
  35. data/lib/innate/test/testmkdirs.rb +19 -0
  36. data/lib/innate/test/testregexp.rb +86 -0
  37. data/lib/innate/test/testreload.rb +61 -0
  38. data/lib/innate/test/testroman.rb +54 -0
  39. data/lib/innate/test/testscriptlines.rb +39 -0
  40. data/lib/innate/test/teststring.rb +52 -0
  41. data/lib/innate/test/testtitlecase.rb +20 -0
  42. data/lib/innate/titlecase.rb +64 -0
  43. data/lib/innate/tracerequire.rb +22 -0
  44. data/lib/namesource.rb +53 -0
  45. data/lib/options.rb +64 -0
  46. data/lib/providers.rb +93 -0
  47. data/lib/regexp.rb +56 -0
  48. data/lib/rename_functions.rb +142 -0
  49. data/lib/renamer.rb +210 -0
  50. data/lib/results.rb +83 -0
  51. data/lib/test/test_helper.rb +147 -0
  52. data/tests/dir/file1 +0 -0
  53. data/tests/dir/file2 +0 -0
  54. data/tests/dir/file3 +0 -0
  55. data/tests/test_helper.rb +147 -0
  56. data/tests/test_rename_functions.rb +605 -0
  57. metadata +105 -0
data/lib/gui.rb ADDED
@@ -0,0 +1,216 @@
1
+ require 'rubygems'
2
+ require_gem 'fxruby'
3
+ require 'stringio'
4
+ require 'results'
5
+
6
+ include Fox
7
+
8
+ #TO DO:
9
+ # change pattern to combobox with some useful patterns
10
+ # \d.+(\d+).+
11
+ # (\d+[AB])
12
+
13
+ #user interface layout
14
+ class RenameWindow < FXMainWindow
15
+ attr_accessor :renamer
16
+
17
+ def initialize(app)
18
+ super(app, 'Rename File Set', nil, nil, DECOR_ALL, 20, 100, 1000, 600)
19
+
20
+ FXHorizontalFrame.new(self, LAYOUT_FILL | PACK_UNIFORM_WIDTH) { |mainFrame|
21
+ # navigation on left
22
+ @dirList = FXDirList.new mainFrame, nil, 0, LAYOUT_FILL | FRAME_NORMAL
23
+ # controls in centre
24
+ FXVerticalFrame.new(mainFrame, LAYOUT_FILL | PACK_UNIFORM_WIDTH) { |controls|
25
+ FXLabel.new controls, 'Search (replace first capture):', nil, LAYOUT_FILL_X | JUSTIFY_LEFT
26
+ @search = FXTextField.new controls, 0, nil, LAYOUT_FILL
27
+
28
+ FXGroupBox.new(controls, 'Replacement Method', LAYOUT_FILL | FRAME_GROOVE | JUSTIFY_LEFT ) { |group|
29
+ @selReplace = FXRadioButton.new(group, 'Replace with:') { |r|
30
+ connectRadio r
31
+ }
32
+ @replace = FXTextField.new group, 30, nil, PACK_UNIFORM_WIDTH
33
+ @selAdd = FXRadioButton.new(group, 'Add to captured digits: ', nil, LAYOUT_FILL_Y) { |r|
34
+ connectRadio r
35
+ }
36
+ @add = FXSpinner.new(group, 10, nil, FRAME_NORMAL) { |s| s.range = (-500..500)}
37
+ @selRoman = FXRadioButton.new(group, 'Convert to Roman Numerals', nil, LAYOUT_FILL_X ) { |r|
38
+ connectRadio r
39
+ }
40
+ @selTapeNumbers = FXRadioButton.new(group, 'Convert Tape Numbers', nil, LAYOUT_FILL_X) { |r|
41
+ connectRadio r
42
+ }
43
+ @selFill = FXRadioButton.new(group, 'Replace capture with last match capture:', nil, LAYOUT_FILL_X) { |r|
44
+ connectRadio r
45
+ }
46
+ @fill = FXTextField.new group, 30, nil, PACK_UNIFORM_WIDTH
47
+ @selFile = FXRadioButton.new(group, 'Replace from files.txt', nil, LAYOUT_FILL_X) { |r|
48
+ connectRadio r
49
+ }
50
+ @selCount = FXRadioButton.new(group, 'Count', nil, LAYOUT_FILL_X) { |r|
51
+ connectRadio r
52
+ }
53
+ @selMarkChange = FXRadioButton.new(group, 'Mark Changes', nil, LAYOUT_FILL_X) { |r|
54
+ connectRadio r
55
+ }
56
+ @selFromFullName = FXRadioButton.new(group, 'Replace with capture in full name:', nil, LAYOUT_FILL_X) { |r|
57
+ connectRadio r
58
+ }
59
+ @fullNamePattern = FXTextField.new group, 30, nil, PACK_UNIFORM_WIDTH
60
+ @selMoveUp = FXRadioButton.new(group, 'Move to Parent Directory', nil, LAYOUT_FILL_X) { |r|
61
+ connectRadio r
62
+ }
63
+ @selCapitalize = FXRadioButton.new(group, 'Capitalize', nil, LAYOUT_FILL_X) { |r|
64
+ connectRadio r
65
+ }
66
+ }
67
+
68
+ FXButton.new(controls, '&List Files', nil, nil, 0, LAYOUT_FILL_X | BUTTON_NORMAL) { |b|
69
+ b.connect SEL_COMMAND, method(:onListClick)
70
+ }
71
+ FXButton.new(controls, '&Preview', nil, nil, 0, LAYOUT_FILL_X | FRAME_RAISED | FRAME_THICK) { |b|
72
+ b.connect SEL_COMMAND, method(:onPreviewClick)
73
+ }
74
+ FXButton.new(controls, '&Apply', nil, nil, 0, LAYOUT_FILL_X | FRAME_RAISED | FRAME_THICK) { |b|
75
+ b.connect SEL_COMMAND, method(:onReplaceClick)
76
+ }
77
+ }
78
+ # results on right
79
+ @results = FXText.new mainFrame, nil, 0, LAYOUT_FILL | FRAME_NORMAL | TEXT_READONLY
80
+ }
81
+
82
+ @selReplace.checkState = TRUE
83
+ onRadioUpdate @selReplace, nil, nil
84
+ end
85
+
86
+ def connectRadio(r)
87
+ r.connect SEL_COMMAND, method(:onRadioUpdate)
88
+ end
89
+
90
+ def onRadioUpdate(sender, sel, ptr)
91
+ sender.parent.each_child do |c|
92
+ if FXRadioButton === c and c != sender
93
+ c.checkState = FALSE
94
+ end
95
+ end
96
+ @selReplace.checked? ? @replace.enable : @replace.disable
97
+ @selAdd.checked? ? @add.enable : @add.disable
98
+ @selFill.checked? ? @fill.enable : @fill.disable
99
+ @selFromFullName.checked? ? @fullNamePattern.enable : @fullNamePattern.disable
100
+ end
101
+ end
102
+
103
+ #connections to main renaming worker
104
+ class RenameWindow
105
+ attr_accessor :renamer
106
+
107
+ def onPreviewClick(sender, sel, ptr)
108
+ start :preview
109
+ end
110
+
111
+ def onReplaceClick(sender, sel, ptr)
112
+ start :commit
113
+ end
114
+
115
+ def onListClick(sender, sel, ptr)
116
+ start :list
117
+ @dirList.setFocus
118
+ end
119
+
120
+ def selected_function
121
+ if @selReplace.checked?
122
+ :rename_replace
123
+ elsif @selFile.checked?
124
+ :rename_files_txt
125
+ elsif @selAdd.checked?
126
+ :rename_add
127
+ elsif @selCount.checked?
128
+ :rename_count
129
+ elsif @selFromFullName.checked?
130
+ :rename_from_full_name
131
+ elsif @selMoveUp.checked?
132
+ :rename_move_up
133
+ elsif @selRoman.checked?
134
+ :rename_roman
135
+ elsif @selMarkChange.checked?
136
+ :rename_mark_change
137
+ elsif @selTapeNumbers.checked?
138
+ :rename_tape_numbers
139
+ elsif @selCapitalize.checked?
140
+ :rename_capitalize
141
+ elsif @selFill.checked?
142
+ :rename_fill
143
+ else
144
+ throw "No operation selected, or selection unknown."
145
+ end
146
+ end
147
+
148
+ def start(action)
149
+ s = StringIO.new
150
+ @r = CollectResults.new s
151
+ @r.lineways = true
152
+ @r.list = action == :list
153
+
154
+ begin
155
+ fn = selected_function
156
+ if methods.include? fn.to_s
157
+ method(fn).call action
158
+ else
159
+ rename fn, :action => action
160
+ end
161
+ rescue => e
162
+ @r.output(:error, e)
163
+ end
164
+ @r.display
165
+ s.rewind
166
+ @results.text = s.read.lstrip
167
+ end
168
+
169
+ def rename(name, args = {})
170
+ args[:search] ||= PrepareRegexp.new @search.text
171
+ args[:filter] = Filter.add(args[:filter], Filter.new(/^\.+$/))
172
+ args[:file_provider] = Provider::File::NonRecursive.new(Provider::Folder::Tree.new(@dirList))
173
+
174
+ PrepareRegexp.each(args) do |k, v|
175
+ args[k] = t = v.create(false, false, true) do |mode, output|
176
+ @r.output(mode, output)
177
+ end
178
+ end
179
+
180
+ @renamer.run name, args do |*a|
181
+ @r.output(*a)
182
+ end
183
+ end
184
+
185
+ #special functions
186
+
187
+ def rename_replace action
188
+ rename :rename_replace, :replace => @replace.text, :action => action
189
+ end
190
+
191
+ def rename_add action
192
+ rename :rename_add, :add => @add.value, :action => action
193
+ end
194
+
195
+ def rename_fill action
196
+ rename :rename_fill, :fill => PrepareRegexp.new(@fill.text), :action => action
197
+ end
198
+
199
+ def rename_from_full_name action
200
+ rename :rename_from_full_name, :pattern => PrepareRegexp.new(@fullNamePattern.text), :action => action
201
+ end
202
+
203
+ def rename_mark_change action
204
+ rename(:rename_mark_change,
205
+ :replace_pattern => PrepareRegexp.new('()$/'), # I could add boxes for these options
206
+ :replace_text => '--changed--',
207
+ :action => action)
208
+ end
209
+
210
+ def rename_files_txt action
211
+ rename(:rename_name_source,
212
+ :source => NameFileSource.new('files.txt'),
213
+ :filter => Filter.new(/^files.txt$/),
214
+ :action => action)
215
+ end
216
+ end
@@ -0,0 +1,104 @@
1
+ class Array
2
+ def match? string
3
+ find do |i|
4
+ if i.is_a? String
5
+ i == string
6
+ elsif i.is_a? Regexp
7
+ i =~ string
8
+ end
9
+ end
10
+ end
11
+
12
+ def rows(width = 80, spaces = 2, spacer = ' ')
13
+ return '' if length == 0
14
+ best = nil
15
+ bestcw = nil
16
+ sorted = self # changed my mind: don't sort
17
+
18
+ 1.upto(width) do |columns|
19
+ space = spacer.length * spaces * (columns - 1)
20
+ break if columns + space > width
21
+ t = []
22
+ rows = []
23
+ cw = [0] * columns
24
+ result = sorted.each do |item|
25
+ if t.length == columns
26
+ break :fail unless check_cols cw, rows, t, width, space, columns
27
+ t = []
28
+ end
29
+ t << item.to_s
30
+ end
31
+ t += [''] * (columns - t.length)
32
+ unless result == :fail
33
+ result = :fail unless check_cols cw, rows, t, width, space, columns
34
+ end
35
+ if result != :fail
36
+ best = rows
37
+ bestcw = cw
38
+ end
39
+ end
40
+ fs = bestcw.collect {|w| "%#{w}-s" }.join(spacer * spaces)
41
+ best.collect {|row| format(fs, *row) }.join "\n"
42
+ end
43
+
44
+ def columns(width = 80, spaces = 2, spacer = ' ')
45
+ return '' if length == 0
46
+ best = nil
47
+ bestcw = nil
48
+ sorted = self # changed my mind: don't sort
49
+
50
+ 1.upto(width) do |columns|
51
+ space = spacer.length * spaces * (columns - 1)
52
+ rows = []
53
+ cols = []
54
+ cw = [0] * columns
55
+ col_len = length / columns
56
+ col_len += 1 if length > (columns * col_len)
57
+
58
+ temp = nil
59
+ if length % col_len > 0
60
+ temp = sorted + ([''] * (col_len - length % col_len))
61
+ else
62
+ temp = sorted
63
+ end
64
+
65
+ columns.times do |c|
66
+ t = temp[(col_len * c)...(col_len * (c + 1))]
67
+ cols << t
68
+ end
69
+ next if cols.last == [] or cols.last == nil
70
+
71
+ cols = cols.transpose
72
+ result = cols.each do |row|
73
+ break :fail unless check_cols cw, rows, row, width, space, columns
74
+ end
75
+ if result != :fail
76
+ best = rows
77
+ bestcw = cw
78
+ end
79
+ break if col_len == 1
80
+ end
81
+ fs = bestcw.collect {|w| "%#{w}-s" }.join(spacer * spaces)
82
+ best.collect {|row| format(fs, *row) }.join "\n"
83
+ end
84
+
85
+ def pc(w = 130, s = 2, sr = ' ')
86
+ puts sort.columns(w, s, sr)
87
+ end
88
+
89
+ def pr(w = 130, s = 2, sr = ' ')
90
+ puts sort.rows(w, s, sr)
91
+ end
92
+ end
93
+
94
+ #refactorings
95
+ class Array
96
+ private
97
+ def check_cols(cw, rows, t, width, space, columns)
98
+ idx = 0
99
+ cw.collect! {|w| r = (t[idx].length > w ? t[idx].length : w); idx += 1; r }
100
+ row_width = cw.inject(0){|tot, n| tot + n} + space
101
+ rows << t
102
+ (row_width < width) || columns == 1
103
+ end
104
+ end
@@ -0,0 +1,315 @@
1
+ # module COVERAGE__ originally (c) NAKAMURA Hiroshi, under Ruby's license
2
+ # module PrettyCoverage originally (c) Simon Strandgaard, under Ruby's license
3
+ # minor modifications by Mauricio Julio Fern�ndez Pradier
4
+
5
+ require 'fileutils'
6
+ require 'rbconfig'
7
+
8
+ include Config
9
+
10
+ module PrettyCoverage
11
+
12
+ class HTML
13
+ def write_page(body, title, css, filename)
14
+ html = <<EOHTML
15
+ <?xml version="1.0" encoding="ISO-8859-1"?>
16
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
17
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
18
+ <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
19
+ <head><title>#{title}</title>
20
+ <style type="text/css">#{css}</style></head>
21
+ <body>#{body}</body></html>
22
+ EOHTML
23
+ File.open(filename, "w+") {|f| f.write(html) }
24
+ end
25
+
26
+ def escape(text)
27
+ text.gsub(/&/, "&amp;").gsub(/\"/, "&quot;").gsub(/>/, "&gt;").gsub(/</, "&lt;")
28
+ end
29
+
30
+ def initialize
31
+ @files = {}
32
+ @files_codeonly = {}
33
+ @filenames = {}
34
+ @sidebar = ""
35
+ end
36
+
37
+ def output_dir(&block)
38
+ dir = "coverage"
39
+ if FileTest.directory?(dir)
40
+ FileUtils.rm_rf(dir)
41
+ end
42
+ FileUtils.mkdir(dir)
43
+ FileUtils.cd(dir) { block.call(dir) }
44
+ end
45
+
46
+ def load_sidebar
47
+ return unless FileTest.file?("coverage.sidebar")
48
+ data = nil
49
+ File.open("coverage.sidebar", "r") {|f| data = f.read}
50
+ @sidebar = "<div class=\"sidebar\">#{data}</div>"
51
+ end
52
+
53
+ def build_filenames
54
+ duplicates = Hash.new(0)
55
+ @files.keys.each do |filename, marked|
56
+ base = File.basename(filename)
57
+ absolute = File.expand_path(filename)
58
+ n = duplicates[base]
59
+ duplicates[base] += 1
60
+ if n > 0
61
+ base += n.to_s
62
+ end
63
+ @filenames[filename] = [base, absolute]
64
+ end
65
+ #p @filenames
66
+ end
67
+
68
+ def execute
69
+ puts "execute"
70
+ build_filenames
71
+ load_sidebar
72
+ output_dir do
73
+ create_file_index
74
+ @files.each do |file, line_marked|
75
+ create_file(file, line_marked, @files_codeonly[file])
76
+ end
77
+ end
78
+ end
79
+
80
+
81
+ def mk_filename(name)
82
+ base_absolute = @filenames[name]
83
+ raise "should not happen" unless base_absolute
84
+ base, absolute = base_absolute
85
+ return nil if absolute =~ /\A#{Regexp.escape(CONFIG["libdir"])}/
86
+ return nil if base =~ /test_/
87
+ [base + ".html", base, absolute]
88
+ end
89
+
90
+ def create_file_index
91
+ output_filename = "index.html"
92
+ rows = []
93
+ filestats = {}
94
+ filestats_code = {}
95
+ @files.sort_by{|k,v| k}.each do|file, line_marked|
96
+ url_filename = mk_filename(file)
97
+ next unless url_filename
98
+ percent = "%02.1f" % calc_coverage(line_marked)
99
+ percent2 = "%02.1f" % calc_coverage(@files_codeonly[file])
100
+ numlines = line_marked.transpose[1].size
101
+ if numlines
102
+ filestats[file] = [calc_coverage(line_marked), numlines]
103
+ filestats_code[file] = [calc_coverage(@files_codeonly[file]),
104
+ numlines]
105
+ end
106
+ url, filename, abspath = url_filename
107
+ cells = [
108
+ "<a href=\"#{url}\">#{filename}</a>",
109
+ "<tt>#{numlines}</tt>",
110
+ "&nbsp;", "&nbsp;", "&nbsp;",
111
+ "<tt>#{percent}%</tt>",
112
+ "&nbsp;", "&nbsp;", "&nbsp;",
113
+ "<tt>#{percent2}%</tt>"
114
+ ]
115
+ rows << cells.map{|cell| "<td>#{cell}</td>"}
116
+ end
117
+ rows.map!{|row| "<tr>#{row}</tr>"}
118
+ table = "<table>#{rows.join}</table>"
119
+ total_cov = 1.0 *
120
+ filestats.inject(0){|a,(k,v)| a + v[0] * v[1]} /
121
+ filestats.inject(0){|a,(k,v)| a + v[1]}
122
+ total_code_cov = 1.0 *
123
+ filestats_code.inject(0) {|a,(k,v)| a + v[0] * v[1]} /
124
+ filestats_code.inject(0){|a,(k,v)| a + v[1]}
125
+ body = "<h1>Average (with comments): %02.1f%%</h1>" % total_cov
126
+ body << "<h1>Average (code only): %02.1f%%</h1>" % total_code_cov
127
+ body << @sidebar
128
+ body << table
129
+ title = "coverage"
130
+ css = <<-EOCSS.gsub(/^\s*/, "")
131
+ body {
132
+ background-color: rgb(180, 180, 180);
133
+ }
134
+ span.marked {
135
+ background-color: rgb(185, 200, 200);
136
+ display: block;
137
+ }
138
+ div.overview {
139
+ border-bottom: 8px solid black;
140
+ }
141
+ div.sidebar {
142
+ float: right;
143
+ width: 300px;
144
+ border: 2px solid black;
145
+ margin-left: 10px;
146
+ padding-left: 10px;
147
+ padding-right: 10px;
148
+ margin-right: -10px;
149
+ background-color: rgb(185, 200, 200);
150
+ }
151
+ EOCSS
152
+ write_page(body, title, css, output_filename)
153
+ end
154
+
155
+ def add_file(file, line_marked)
156
+ percent = calc_coverage(line_marked)
157
+ path = File.expand_path(file)
158
+ return nil if path =~ /\A#{Regexp.escape(CONFIG["rubylibdir"])}/
159
+ return nil if path =~ /\A#{Regexp.escape(CONFIG["sitelibdir"])}/
160
+ #printf("file #{file} coverage=%02.1f%\n", percent)
161
+
162
+ # comments and empty lines.. we must
163
+ # propagate marked-value backwards
164
+ line_marked << ["", false]
165
+ (line_marked.size).downto(1) do |index|
166
+ line, marked = line_marked[index-1]
167
+ next_line, next_marked = line_marked[index]
168
+ if line =~ /^\s*(#|$)/ and marked == false
169
+ marked = next_marked
170
+ line_marked[index-1] = [line, marked]
171
+ end
172
+ end
173
+ line_marked.pop
174
+ @files[file] = line_marked
175
+ @files_codeonly[file] = line_marked.select do |(line, marked)|
176
+ line !~ /^\s*(#|$)/
177
+ end
178
+ end
179
+
180
+ def calc_coverage(line_marked)
181
+ marked = line_marked.transpose[1]
182
+ if marked
183
+ n = marked.inject(0) {|r, i| (i) ? (r+1) : r }
184
+ percent = n.to_f * 100 / marked.size
185
+ else
186
+ -1.0
187
+ end
188
+ end
189
+
190
+ def format_overview(file, line_marked, code_marked)
191
+ percent = "%02.1f" % calc_coverage(line_marked)
192
+ percent2 = "%02.1f" % calc_coverage(code_marked)
193
+ html = <<-EOHTML.gsub(/^\s*/, "")
194
+ <div class="overview">
195
+ <table>
196
+ <tr><td>filename</td><td><tt>#{file}</tt></td></tr>
197
+ <tr><td>total coverage</td><td>#{percent}%</td></tr>
198
+ <tr><td>code coverage</td><td>#{percent2}%</td></tr>
199
+ </table>
200
+ </div>
201
+ EOHTML
202
+ html
203
+ end
204
+
205
+ def format_lines(line_marked)
206
+ result = ""
207
+ last = nil
208
+ end_of_span = ""
209
+ format_line = "%#{line_marked.size.to_s.size}d"
210
+ line_no = 1
211
+ line_marked.each do |(line, marked)|
212
+ if marked != last
213
+ result += end_of_span
214
+ case marked
215
+ when Fixnum
216
+ if marked < 10
217
+ result += "<span class=\"marked\">"
218
+ else
219
+ result += "<span class=\"highmarks\">"
220
+ end
221
+ end_of_span = "</span>"
222
+ when false
223
+ end_of_span = ""
224
+ end
225
+ end
226
+ result += (format_line % line_no) + " " + escape(line) + "\n"
227
+ last = marked
228
+ line_no += 1
229
+ end
230
+ result += end_of_span
231
+ "<pre>#{result}</pre>"
232
+ end
233
+
234
+ def create_file(file, line_marked, code_marked)
235
+ url_filename = mk_filename(file)
236
+ return unless url_filename
237
+ output_filename, filename, abspath = url_filename
238
+ puts "outputting #{output_filename.inspect}"
239
+ body = format_overview(abspath, line_marked, code_marked) +
240
+ format_lines(line_marked)
241
+ title = filename + " - coverage"
242
+ css = <<-EOCSS.gsub(/^\s*/, "")
243
+ body {
244
+ background-color: rgb(180, 180, 180);
245
+ }
246
+ span.marked {
247
+ background-color: rgb(185, 200, 200);
248
+ display: block;
249
+ }
250
+ span.highmarks {
251
+ background-color: rgb(220, 200, 200);
252
+ display: block;
253
+ }
254
+ div.overview {
255
+ border-bottom: 8px solid black;
256
+ }
257
+ EOCSS
258
+ write_page(body, title, css, output_filename)
259
+ end
260
+ end # class HTML
261
+
262
+ end # module PrettyCoverage
263
+
264
+
265
+ SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
266
+
267
+
268
+ module COVERAGE__
269
+ COVER = Hash.new do |h, k|
270
+ a = [0]
271
+ if File.exists? k
272
+ File.open(k) do |f|
273
+ f.each_line {
274
+ a.push 0
275
+ }
276
+ end
277
+ else
278
+ a = [0] * 5000
279
+ end
280
+ h[k] = a
281
+ end
282
+
283
+ def self.trace_func(event, file, line, id, binding, klass)
284
+ case event
285
+ when 'c-call', 'c-return', 'class'
286
+ return
287
+ end
288
+ COVER[file][line] += 1
289
+ end
290
+
291
+ END {
292
+ set_trace_func(nil)
293
+ printer = PrettyCoverage::HTML.new
294
+ COVER.each do |file, lines|
295
+ next if SCRIPT_LINES__.has_key?(file) == false
296
+ lines = SCRIPT_LINES__[file]
297
+ covers = COVER[file]
298
+ line_status = []
299
+ 0.upto(lines.size - 1) do |c|
300
+ line = lines[c].chomp
301
+ if covers[c + 1] > 0
302
+ elsif /^\s*(?:begin\s*(?:#.*)?|ensure\s*(?:#.*)?|else\s*(?:#.*)?)$/ =~ line and covers[c + 2] > 0
303
+ covers[c + 1] = covers[c + 2]
304
+ elsif /^\s*(?:end|\})\s*$/ =~ line && covers[c] > 0
305
+ covers[c + 1] = covers[c]
306
+ end
307
+ line_status << [line, covers[c+1].nonzero? || false]
308
+ end
309
+ printer.add_file(file, line_status) unless line_status == []
310
+ end
311
+ printer.execute
312
+ } # END
313
+
314
+ set_trace_func(COVERAGE__.method(:trace_func).to_proc)
315
+ end