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.
- 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
|