rfs 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +7 -0
- data/Rakefile.rb +193 -0
- data/bin/RenameFileSet.rbw +14 -0
- data/bin/rfs.rb +214 -0
- data/bin/rfsd.rb +8 -0
- data/description.txt +24 -0
- data/lib/RenameFileSet.bak.rb +372 -0
- data/lib/errors.rb +15 -0
- data/lib/filters.rb +68 -0
- data/lib/gui.rb +216 -0
- data/lib/innate/array.rb +104 -0
- data/lib/innate/coverage.rb +315 -0
- data/lib/innate/debug.rb +12 -0
- data/lib/innate/debugger.rb +944 -0
- data/lib/innate/file.rb +5 -0
- data/lib/innate/filelines.rb +148 -0
- data/lib/innate/kernel.rb +41 -0
- data/lib/innate/metaid.rb +30 -0
- data/lib/innate/mkdirs.rb +12 -0
- data/lib/innate/regexp.rb +80 -0
- data/lib/innate/reload.rb +11 -0
- data/lib/innate/roman.rb +72 -0
- data/lib/innate/scriptlines.rb +60 -0
- data/lib/innate/string.rb +56 -0
- data/lib/innate/test/all_tests.rb +11 -0
- data/lib/innate/test/files/mkdirs_dummy.file +1 -0
- data/lib/innate/test/files/reloadtarget.rb +5 -0
- data/lib/innate/test/files/reloadtarget1.rb +5 -0
- data/lib/innate/test/files/reloadtarget2.rb +5 -0
- data/lib/innate/test/test_coverage.rb +13 -0
- data/lib/innate/test/testarray.rb +98 -0
- data/lib/innate/test/testfile.rb +15 -0
- data/lib/innate/test/testfilelines.rb +106 -0
- data/lib/innate/test/testkernel.rb +30 -0
- data/lib/innate/test/testmkdirs.rb +19 -0
- data/lib/innate/test/testregexp.rb +86 -0
- data/lib/innate/test/testreload.rb +61 -0
- data/lib/innate/test/testroman.rb +54 -0
- data/lib/innate/test/testscriptlines.rb +39 -0
- data/lib/innate/test/teststring.rb +52 -0
- data/lib/innate/test/testtitlecase.rb +20 -0
- data/lib/innate/titlecase.rb +64 -0
- data/lib/innate/tracerequire.rb +22 -0
- data/lib/namesource.rb +53 -0
- data/lib/options.rb +64 -0
- data/lib/providers.rb +93 -0
- data/lib/regexp.rb +56 -0
- data/lib/rename_functions.rb +142 -0
- data/lib/renamer.rb +210 -0
- data/lib/results.rb +83 -0
- data/lib/test/test_helper.rb +147 -0
- data/tests/dir/file1 +0 -0
- data/tests/dir/file2 +0 -0
- data/tests/dir/file3 +0 -0
- data/tests/test_helper.rb +147 -0
- data/tests/test_rename_functions.rb +605 -0
- 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
|
data/lib/innate/array.rb
ADDED
@@ -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(/&/, "&").gsub(/\"/, """).gsub(/>/, ">").gsub(/</, "<")
|
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
|
+
" ", " ", " ",
|
111
|
+
"<tt>#{percent}%</tt>",
|
112
|
+
" ", " ", " ",
|
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
|