silhouette 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +10 -1
- data/Rakefile +4 -2
- data/bin/silhouette +2 -29
- data/bin/silhouette_coverage +70 -0
- data/bin/silrun +83 -0
- data/lib/silhouette.rb +5 -13
- data/lib/silhouette/converter.rb +102 -0
- data/lib/silhouette/coverage.rb +604 -0
- data/lib/silhouette/default.css +68 -0
- data/lib/silhouette/emitters.rb +176 -0
- data/lib/silhouette/finder.rb +138 -0
- data/lib/silhouette/light.css +63 -0
- data/lib/silhouette/process.rb +3 -0
- data/lib/silhouette/processor.rb +8 -279
- data/lib/silhouette/setup.rb +102 -0
- data/silhouette_ext.c +66 -23
- data/test/test.rb +8 -1
- metadata +54 -22
- data/test/silhouette.out +0 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
TABLE {
|
2
|
+
FONT-SIZE: 11px
|
3
|
+
}
|
4
|
+
|
5
|
+
TABLE.code {
|
6
|
+
border:black 1px solid;
|
7
|
+
}
|
8
|
+
|
9
|
+
BODY {
|
10
|
+
FONT-FAMILY: sans-serif;
|
11
|
+
}
|
12
|
+
.coverageHit {
|
13
|
+
border-right:#dcdcdc 1px solid;
|
14
|
+
background-color:#c8c8f0;
|
15
|
+
}
|
16
|
+
.coverageCount, .lineCount { border-right:#dcdcdc 1px solid; }
|
17
|
+
.coverageMissing, .coverageMissed {
|
18
|
+
border-right:#dcdcdc 1px solid;
|
19
|
+
background-color:#300;
|
20
|
+
}
|
21
|
+
.coverageMissed { border-right:#dcdcdc 1px solid; }
|
22
|
+
.lineNonCode {
|
23
|
+
border-right:#dcdcdc 1px solid;
|
24
|
+
background-color:#f0f0f0;
|
25
|
+
}
|
26
|
+
.lineCount {
|
27
|
+
border-right:#dcdcdc 1px solid;
|
28
|
+
background-color:#c8c8f0;
|
29
|
+
}
|
30
|
+
.sourceLineHighlight {
|
31
|
+
background-color:#200;
|
32
|
+
}
|
33
|
+
|
34
|
+
.sourceLineGoodHighlight {
|
35
|
+
background-color:#023103;
|
36
|
+
}
|
37
|
+
|
38
|
+
.coverageMethod {
|
39
|
+
background-color:#1a4400;
|
40
|
+
}
|
41
|
+
|
42
|
+
.sourceLinePartialHighlight {
|
43
|
+
background-color:#202020;
|
44
|
+
}
|
45
|
+
|
46
|
+
.sourceLine {
|
47
|
+
color:#ffffff;
|
48
|
+
background-color:#171717;
|
49
|
+
}
|
50
|
+
|
51
|
+
.normal { color: #ffffff; }
|
52
|
+
.comment { color: #99462e; }
|
53
|
+
.keyword { color: #79a8df; }
|
54
|
+
.method { color: #b3b3b3; }
|
55
|
+
.class { color: #6486c7; }
|
56
|
+
.module { color: #6486c7; }
|
57
|
+
.punct { color: #cdcdcd; }
|
58
|
+
.symbol { color: #2368ff; }
|
59
|
+
.string { color: #b6ac99; }
|
60
|
+
.char { color: #F07; }
|
61
|
+
.ident { color: #ffffff; }
|
62
|
+
.constant { color: #fffa00; }
|
63
|
+
.regex { color: #dd585d; }
|
64
|
+
.number { color: #2368ff; }
|
65
|
+
.attribute { color: #3d93a9; }
|
66
|
+
.global { color: #7FB; }
|
67
|
+
.expr { color: #e2e2e2; }
|
68
|
+
.escape { color: #277; }
|
@@ -0,0 +1,176 @@
|
|
1
|
+
module Silhouette
|
2
|
+
class InvalidFormat < Exception; end
|
3
|
+
|
4
|
+
def self.emitters
|
5
|
+
out = []
|
6
|
+
constants.each do |name|
|
7
|
+
con = const_get(name)
|
8
|
+
if Class === con and con.superclass == Emitter
|
9
|
+
out << con
|
10
|
+
end
|
11
|
+
end
|
12
|
+
out
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.find_emitter(file)
|
16
|
+
|
17
|
+
if !File.exists? file
|
18
|
+
bz = "#{file}.bz2"
|
19
|
+
file = bz if File.exists?(bz)
|
20
|
+
end
|
21
|
+
|
22
|
+
if !File.exists? file
|
23
|
+
gz = "#{file}.gz"
|
24
|
+
file = gz if File.exists?(gz)
|
25
|
+
end
|
26
|
+
|
27
|
+
if file == "-"
|
28
|
+
io = STDIN
|
29
|
+
|
30
|
+
elsif /.bz2$/.match(file)
|
31
|
+
io = IO.popen("bzip2 -dc '#{file}'")
|
32
|
+
elsif /.gz$/.match(file)
|
33
|
+
io = IO.popen("gzip -dc '#{file}'")
|
34
|
+
else
|
35
|
+
raise "Unknown file" unless File.exists?(io)
|
36
|
+
io = File.open(file)
|
37
|
+
end
|
38
|
+
|
39
|
+
emitters.each do |em|
|
40
|
+
begin
|
41
|
+
return em.new(io)
|
42
|
+
rescue InvalidFormat
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
raise InvalidFormat, "Unable to find valid emitter"
|
47
|
+
end
|
48
|
+
|
49
|
+
class Emitter
|
50
|
+
def initialize(processor=nil)
|
51
|
+
@processor = processor
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_accessor :processor
|
55
|
+
end
|
56
|
+
|
57
|
+
class BinaryEmitter < Emitter
|
58
|
+
MAGIC = "<>"
|
59
|
+
|
60
|
+
def initialize(file, processor=nil)
|
61
|
+
if file.kind_of? String
|
62
|
+
raise "Unknown file" unless File.exists?(file)
|
63
|
+
@io = File.open(file)
|
64
|
+
else
|
65
|
+
@io = file
|
66
|
+
end
|
67
|
+
|
68
|
+
magic = @io.read(2)
|
69
|
+
raise InvalidFormat unless magic == MAGIC
|
70
|
+
|
71
|
+
@method_size = "S"
|
72
|
+
@file_size = "S"
|
73
|
+
@ret_call_fmt = "i#{@method_size}#{@file_size}ii"
|
74
|
+
@ret_call_size = 16
|
75
|
+
super(processor)
|
76
|
+
end
|
77
|
+
|
78
|
+
class DoneParsing < Exception; end
|
79
|
+
|
80
|
+
def parse
|
81
|
+
begin
|
82
|
+
loop { emit }
|
83
|
+
rescue DoneParsing
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
FIXED_SIZE = {
|
88
|
+
?c => 20,
|
89
|
+
?r => 20,
|
90
|
+
?@ => 8
|
91
|
+
}
|
92
|
+
|
93
|
+
def next_cmd
|
94
|
+
str = @io.read(1)
|
95
|
+
raise DoneParsing unless str
|
96
|
+
|
97
|
+
cmd = str[0]
|
98
|
+
if [?!, ?*, ?&].include? cmd
|
99
|
+
size = @io.read(4).unpack("i").first
|
100
|
+
else
|
101
|
+
size = FIXED_SIZE[cmd]
|
102
|
+
end
|
103
|
+
|
104
|
+
[cmd, size, @io.read(size)]
|
105
|
+
end
|
106
|
+
|
107
|
+
def parse
|
108
|
+
begin
|
109
|
+
# Use "while true" instead of "loop" because loop is
|
110
|
+
# really a method call.
|
111
|
+
while true
|
112
|
+
data = @io.read(1)
|
113
|
+
raise DoneParsing unless data
|
114
|
+
|
115
|
+
cmd = data[0]
|
116
|
+
|
117
|
+
# These are hardcoded in here for speed.
|
118
|
+
if cmd == ?r or cmd == ?c or cmd == ?l
|
119
|
+
size = @ret_call_size
|
120
|
+
elsif cmd == ?@
|
121
|
+
size = 8
|
122
|
+
else
|
123
|
+
size = @io.read(4).unpack("i").first
|
124
|
+
end
|
125
|
+
|
126
|
+
proc = @processor
|
127
|
+
data = @io.read(size)
|
128
|
+
|
129
|
+
case cmd
|
130
|
+
when ?r
|
131
|
+
parts = data.unpack(@ret_call_fmt)
|
132
|
+
@processor.process_return(*parts)
|
133
|
+
# return [:return, *parts]
|
134
|
+
when ?c
|
135
|
+
parts = data.unpack(@ret_call_fmt)
|
136
|
+
@processor.process_call(*parts)
|
137
|
+
when ?l
|
138
|
+
parts = data.unpack(@ret_call_fmt)
|
139
|
+
@processor.process_line(*parts)
|
140
|
+
# return [:call, *parts]
|
141
|
+
when ?!
|
142
|
+
parts = data.unpack("Z#{size - 8}ii")
|
143
|
+
@processor.process_start(*parts)
|
144
|
+
# return [:start, *parts]
|
145
|
+
when ?@
|
146
|
+
parts = data.unpack("ii")
|
147
|
+
@processor.process_end(*parts)
|
148
|
+
# return [:end, *parts]
|
149
|
+
when ?&
|
150
|
+
parts = data.unpack("ia#{size - 4}")
|
151
|
+
parts += parts.pop.split("\0")
|
152
|
+
@processor.process_method(*parts)
|
153
|
+
# return [:method, *parts]
|
154
|
+
when ?*
|
155
|
+
parts = data.unpack("iZ#{size - 4}")
|
156
|
+
@processor.process_file(*parts)
|
157
|
+
# return [:file, *parts]
|
158
|
+
when ?(
|
159
|
+
@method_size = "I"
|
160
|
+
@ret_call_size += 2
|
161
|
+
@ret_call_fmt = "i#{@method_size}#{@file_size}ii"
|
162
|
+
when ?)
|
163
|
+
@file_size = "I"
|
164
|
+
@ret_call_size += 2
|
165
|
+
@ret_call_fmt = "i#{@method_size}#{@file_size}ii"
|
166
|
+
else
|
167
|
+
raise "Unknown type '#{cmd.chr}'"
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# This means we're done.
|
172
|
+
rescue DoneParsing
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'syntax/convertors/html'
|
2
|
+
|
3
|
+
module Silhouette
|
4
|
+
class MethodFinder < Syntax::Convertors::HTML
|
5
|
+
class MethodPosition
|
6
|
+
def initialize(name, klass)
|
7
|
+
@name = name
|
8
|
+
@klass = klass
|
9
|
+
@misses = 0
|
10
|
+
@loc = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def total
|
14
|
+
@last_line - @first_line
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_accessor :first_line, :last_line, :name, :klass, :misses, :loc
|
18
|
+
|
19
|
+
def coverage
|
20
|
+
loc_hit = @loc - @misses
|
21
|
+
((loc_hit / @loc.to_f) * 100).to_i
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class ClassPosition
|
26
|
+
def initialize(name)
|
27
|
+
@name = name
|
28
|
+
@misses = 0
|
29
|
+
end
|
30
|
+
|
31
|
+
def total
|
32
|
+
@last_line - @first_line
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_accessor :first_line, :last_line, :name, :misses
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(tok)
|
39
|
+
super
|
40
|
+
@line_no = 1
|
41
|
+
@inner_ends = 0
|
42
|
+
@class_path = []
|
43
|
+
@class_stack = []
|
44
|
+
@modules = []
|
45
|
+
@methods = []
|
46
|
+
@method_stack = []
|
47
|
+
@current_method = nil
|
48
|
+
@html = ""
|
49
|
+
@line_start = true
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_reader :methods, :modules
|
53
|
+
|
54
|
+
HAS_END = %w!if begin while for until case unless!
|
55
|
+
|
56
|
+
def process_token(tok)
|
57
|
+
# p [tok, tok.group]
|
58
|
+
case tok.group
|
59
|
+
when :normal, :string, :constant
|
60
|
+
nls = tok.count("\n")
|
61
|
+
@line_no += nls
|
62
|
+
@line_start = true if nls > 0
|
63
|
+
when :class, :module
|
64
|
+
# How to handle classes inside conditions? Don't count them.
|
65
|
+
return unless @inner_ends == 0
|
66
|
+
@class_path << tok.to_s
|
67
|
+
klass = ClassPosition.new(@class_path.join("::"))
|
68
|
+
klass.first_line = @line_no
|
69
|
+
@modules << klass
|
70
|
+
@class_stack << klass
|
71
|
+
# puts "start class: #{@class_path.inspect}"
|
72
|
+
when :method
|
73
|
+
return unless @inner_ends == 0
|
74
|
+
# Handle nested methods by just ignoring the nested ones.
|
75
|
+
if @class_path.last == :def
|
76
|
+
@inner_ends += 1
|
77
|
+
else
|
78
|
+
meth = MethodPosition.new(tok, @class_path.join("::"))
|
79
|
+
meth.first_line = @line_no
|
80
|
+
@methods << meth
|
81
|
+
@method_stack << meth
|
82
|
+
@class_path << :def
|
83
|
+
# puts "start meth: #{@class_path.inspect}, #{tok}"
|
84
|
+
end
|
85
|
+
when :keyword
|
86
|
+
if HAS_END.include?(tok.to_s) and @line_start
|
87
|
+
@inner_ends += 1
|
88
|
+
return
|
89
|
+
elsif tok == "do"
|
90
|
+
@inner_ends += 1
|
91
|
+
return
|
92
|
+
end
|
93
|
+
|
94
|
+
if tok == "end"
|
95
|
+
if @inner_ends == 0
|
96
|
+
was_in = @class_path.pop
|
97
|
+
if was_in == :def
|
98
|
+
# puts "Done with method: #{@class_path.inspect}"
|
99
|
+
@method_stack.last.last_line = @line_no
|
100
|
+
@method_stack.pop
|
101
|
+
else
|
102
|
+
fin = @class_stack.pop
|
103
|
+
if fin
|
104
|
+
fin.last_line = @line_no
|
105
|
+
end
|
106
|
+
end
|
107
|
+
else
|
108
|
+
@inner_ends -= 1
|
109
|
+
# puts "pending ends: #{@inner_ends}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
if tok.group != :normal
|
115
|
+
@line_start = false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def find(text)
|
120
|
+
@tokenizer.tokenize(text) do |tok|
|
121
|
+
process_token tok
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
def html_escape(tok)
|
128
|
+
process_token(tok)
|
129
|
+
super
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
require 'pp'
|
134
|
+
if $0 == __FILE__
|
135
|
+
con = Silhouette::MethodFinder.for_syntax "ruby"
|
136
|
+
con.convert ARGF.read
|
137
|
+
pp con
|
138
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
TABLE {
|
2
|
+
FONT-SIZE: 11px
|
3
|
+
}
|
4
|
+
|
5
|
+
TABLE.code {
|
6
|
+
border:black 1px solid;
|
7
|
+
}
|
8
|
+
|
9
|
+
BODY {
|
10
|
+
FONT-FAMILY: sans-serif;
|
11
|
+
}
|
12
|
+
.coverageHit {
|
13
|
+
border-right:#dcdcdc 1px solid;
|
14
|
+
background-color:#c8c8f0;
|
15
|
+
}
|
16
|
+
.coverageCount, .lineCount { border-right:#dcdcdc 1px solid; }
|
17
|
+
.coverageMissing, .coverageMissed {
|
18
|
+
border-right:#dcdcdc 1px solid;
|
19
|
+
background-color:#F0C8C8;
|
20
|
+
}
|
21
|
+
.coverageMissed { border-right:#dcdcdc 1px solid; }
|
22
|
+
.lineNonCode {
|
23
|
+
border-right:#dcdcdc 1px solid;
|
24
|
+
background-color:#f0f0f0;
|
25
|
+
}
|
26
|
+
.lineCount {
|
27
|
+
border-right:#dcdcdc 1px solid;
|
28
|
+
background-color:#c8c8f0;
|
29
|
+
}
|
30
|
+
.sourceLineHighlight {
|
31
|
+
background-color:#F0C8C8;
|
32
|
+
}
|
33
|
+
|
34
|
+
.sourceLineGoodHighlight {
|
35
|
+
background-color:#c1ffd1;
|
36
|
+
}
|
37
|
+
|
38
|
+
.coverageMethod {
|
39
|
+
background-color:#c1ffd1;
|
40
|
+
}
|
41
|
+
|
42
|
+
.sourceLinePartialHighlight {
|
43
|
+
background-color:#f1f1f1;
|
44
|
+
}
|
45
|
+
|
46
|
+
.normal {}
|
47
|
+
.comment { color: #005; font-style: italic; }
|
48
|
+
.keyword { color: #0000dd; }
|
49
|
+
.method { color: #077; }
|
50
|
+
.class { color: #074; }
|
51
|
+
.module { color: #050; }
|
52
|
+
.punct { color: #447; font-weight: bold; }
|
53
|
+
.symbol { color: #099; }
|
54
|
+
.string { color: #b58401; }
|
55
|
+
.char { color: #F07; }
|
56
|
+
.ident { color: #004; }
|
57
|
+
.constant { color: #07F; }
|
58
|
+
.regex { color: #B66; background: #FEF; }
|
59
|
+
.number { color: #e15858;; }
|
60
|
+
.attribute { color: #3d93a9; }
|
61
|
+
.global { color: #7FB; }
|
62
|
+
.expr { color: #227; }
|
63
|
+
.escape { color: #277; }
|