silhouette 1.0.0 → 2.0.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/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; }
|