deep-cover 0.1.16 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +3 -8
  4. data/.travis.yml +4 -4
  5. data/CHANGELOG.md +10 -0
  6. data/Gemfile +3 -1
  7. data/README.md +9 -4
  8. data/Rakefile +6 -3
  9. data/deep_cover.gemspec +2 -2
  10. data/lib/deep_cover.rb +10 -0
  11. data/lib/deep_cover/analyser.rb +1 -2
  12. data/lib/deep_cover/analyser/base.rb +32 -0
  13. data/lib/deep_cover/analyser/branch.rb +19 -4
  14. data/lib/deep_cover/analyser/node.rb +52 -0
  15. data/lib/deep_cover/analyser/per_char.rb +18 -1
  16. data/lib/deep_cover/analyser/stats.rb +54 -0
  17. data/lib/deep_cover/backports.rb +1 -0
  18. data/lib/deep_cover/base.rb +17 -1
  19. data/lib/deep_cover/builtin_takeover.rb +5 -0
  20. data/lib/deep_cover/cli/deep_cover.rb +2 -1
  21. data/lib/deep_cover/cli/instrumented_clone_reporter.rb +12 -10
  22. data/lib/deep_cover/config.rb +22 -8
  23. data/lib/deep_cover/core_ext/coverage_replacement.rb +22 -18
  24. data/lib/deep_cover/coverage.rb +3 -203
  25. data/lib/deep_cover/coverage/analysis.rb +36 -0
  26. data/lib/deep_cover/coverage/base.rb +91 -0
  27. data/lib/deep_cover/coverage/istanbul.rb +34 -0
  28. data/lib/deep_cover/coverage/persistence.rb +93 -0
  29. data/lib/deep_cover/covered_code.rb +12 -22
  30. data/lib/deep_cover/custom_requirer.rb +6 -2
  31. data/lib/deep_cover/node/base.rb +1 -1
  32. data/lib/deep_cover/node/case.rb +13 -2
  33. data/lib/deep_cover/node/exceptions.rb +2 -2
  34. data/lib/deep_cover/node/if.rb +21 -2
  35. data/lib/deep_cover/node/mixin/flow_accounting.rb +1 -0
  36. data/lib/deep_cover/node/send.rb +9 -2
  37. data/lib/deep_cover/node/short_circuit.rb +10 -0
  38. data/lib/deep_cover/parser_ext/range.rb +4 -4
  39. data/lib/deep_cover/reporter/html.rb +15 -0
  40. data/lib/deep_cover/reporter/html/base.rb +14 -0
  41. data/lib/deep_cover/reporter/html/index.rb +78 -0
  42. data/lib/deep_cover/reporter/html/site.rb +78 -0
  43. data/lib/deep_cover/reporter/html/source.rb +136 -0
  44. data/lib/deep_cover/reporter/html/template/assets/32px.png +0 -0
  45. data/lib/deep_cover/reporter/html/template/assets/40px.png +0 -0
  46. data/lib/deep_cover/reporter/html/template/assets/deep_cover.css.sass +338 -0
  47. data/lib/deep_cover/reporter/html/template/assets/jquery-3.2.1.min.js +4 -0
  48. data/lib/deep_cover/reporter/html/template/assets/jquery-3.2.1.min.map +1 -0
  49. data/lib/deep_cover/reporter/html/template/assets/jstree.css +1108 -0
  50. data/lib/deep_cover/reporter/html/template/assets/jstree.js +8424 -0
  51. data/lib/deep_cover/reporter/html/template/assets/jstreetable.js +1069 -0
  52. data/lib/deep_cover/reporter/html/template/assets/throbber.gif +0 -0
  53. data/lib/deep_cover/reporter/html/template/index.html.erb +75 -0
  54. data/lib/deep_cover/reporter/html/template/source.html.erb +35 -0
  55. data/lib/deep_cover/reporter/html/tree.rb +55 -0
  56. data/lib/deep_cover/tools/content_tag.rb +11 -0
  57. data/lib/deep_cover/tools/covered.rb +9 -0
  58. data/lib/deep_cover/tools/merge.rb +16 -0
  59. data/lib/deep_cover/tools/render_template.rb +13 -0
  60. data/lib/deep_cover/tools/transform_keys.rb +9 -0
  61. data/lib/deep_cover/version.rb +1 -1
  62. metadata +33 -7
  63. data/lib/deep_cover/analyser/ignore_uncovered.rb +0 -21
  64. data/lib/deep_cover/analyser/optionally_covered.rb +0 -19
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ module Reporter::HTML::Base
5
+ include Tools::ContentTag
6
+ def setup
7
+ DeepCover::Config::DEFAULTS.keys.map do |setting|
8
+ value = options[setting]
9
+ value = value.join(', ') if value.respond_to? :join
10
+ content_tag :span, value, class: setting
11
+ end.join
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ module Reporter
5
+ require_relative 'tree'
6
+ require_relative 'base'
7
+
8
+ class HTML::Index < Struct.new(:analysis, :options)
9
+ def initialize(analysis, **options)
10
+ raise ArgumentError unless analysis.is_a? Coverage::Analysis
11
+ super
12
+ end
13
+
14
+ include HTML::Tree
15
+ include HTML::Base
16
+
17
+ def stats_to_data
18
+ @map = Tools.transform_keys(analysis.stat_map, &:name)
19
+ tree = paths_to_tree(@map.keys)
20
+ transform_data(populate(tree))
21
+ end
22
+
23
+ def columns
24
+ _covered_code, analyser_map = analysis.analyser_map.first
25
+ columns = analyser_map.flat_map do |type, analyser|
26
+ [{
27
+ value: type,
28
+ header: analyser.class.human_name,
29
+ }, {
30
+ value: :"#{type}_percent",
31
+ header: '%',
32
+ },
33
+ ]
34
+ end
35
+ columns.unshift(width: 400, header: 'Path')
36
+ columns
37
+ end
38
+
39
+ private
40
+
41
+ # {a: {}} => [{text: a, data: stat_map[a]}]
42
+ # {b: {...}} => [{text: b, data: sum(stats), children: [...]}]
43
+ def populate(tree, dir = '')
44
+ tree.map do |path, children_hash|
45
+ full_path = [dir, path].join
46
+ if children_hash.empty?
47
+ {
48
+ text: %{<a href="#{full_path}.html">#{path}</a>},
49
+ data: @map[full_path],
50
+ }
51
+ else
52
+ children = populate(children_hash, "#{full_path}/")
53
+ data = Tools.merge(*children.map { |c| c[:data] }, :+)
54
+ {
55
+ text: path,
56
+ data: data,
57
+ children: children,
58
+ state: {opened: true},
59
+ }
60
+ end
61
+ end
62
+ end
63
+
64
+ # Modifies in place the tree:
65
+ # {per_char: Stat, ...} => {per_char: {ignored: ...}, per_char_percent: 55.55, ...}
66
+ def transform_data(tree)
67
+ return unless tree
68
+ tree.each do |node|
69
+ node[:data] = Tools.merge(
70
+ node[:data].transform_values(&:to_h),
71
+ *node[:data].map { |type, stat| {:"#{type}_percent" => stat.percent_covered} }
72
+ )
73
+ transform_data(node[:children])
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ require_relative 'index'
5
+ require_relative 'source'
6
+
7
+ module Reporter::HTML
8
+ class Site < Struct.new(:covered_codes, :options)
9
+ def initialize(covered_codes, **options)
10
+ raise ArgumentError unless covered_codes.all? { |c| c.is_a? CoveredCode }
11
+ super
12
+ end
13
+
14
+ include Memoize
15
+ memoize :analysis
16
+
17
+ def analysis
18
+ Coverage::Analysis.new(covered_codes, **options)
19
+ end
20
+
21
+ def path
22
+ Pathname(options[:output])
23
+ end
24
+
25
+ def save
26
+ clear
27
+ save_assets
28
+ save_index
29
+ save_pages
30
+ end
31
+
32
+ def clear
33
+ path.mkpath
34
+ path.rmtree
35
+ path.mkpath
36
+ end
37
+
38
+ def compile_stylesheet(source, dest)
39
+ Bundler.with_clean_env do
40
+ `sass #{source} #{dest}`
41
+ end
42
+ end
43
+
44
+ def render_index
45
+ Tools.render_template(:index, Index.new(analysis, **options))
46
+ end
47
+
48
+ def save_index
49
+ path.join('index.html').write(render_index)
50
+ end
51
+
52
+ def save_assets
53
+ require 'fileutils'
54
+ src = "#{__dir__}/template/assets"
55
+ dest = path.join('assets')
56
+ FileUtils.cp_r(src, dest)
57
+ compile_stylesheet "#{src}/deep_cover.css.sass", dest.join('deep_cover.css')
58
+ dest.join('deep_cover.css.sass').delete
59
+ end
60
+
61
+ def render_source(covered_code)
62
+ Tools.render_template(:source, Source.new(analysis.analyser_map.fetch(covered_code)))
63
+ end
64
+
65
+ def save_pages
66
+ covered_codes.each do |covered_code|
67
+ dest = path.join("#{covered_code.name}.html")
68
+ dest.dirname.mkpath
69
+ dest.write(render_source(covered_code))
70
+ end
71
+ end
72
+
73
+ def self.save(covered_codes, output: raise, **options)
74
+ Site.new(covered_codes, output: output, **options).save
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeepCover
4
+ module Reporter
5
+ require_relative 'base'
6
+
7
+ class HTML::Source < Struct.new(:analyser_map)
8
+ include Tools::Covered
9
+
10
+ def initialize(analyser_map)
11
+ raise ArgumentError unless analyser_map.values.all? { |a| a.is_a?(Analyser) }
12
+ super
13
+ end
14
+
15
+ include HTML::Base
16
+
17
+ def format_source
18
+ lines = convert_source.split("\n")
19
+ lines.map { |line| content_tag(:td, line) }
20
+ rows = lines.map.with_index do |line, i|
21
+ nb = content_tag(:td, i + 1, id: "L#{i + 1}", class: :nb)
22
+ content_tag(:tr, nb + content_tag(:td, line))
23
+ end
24
+ content_tag(:table, rows.join, class: :source)
25
+ end
26
+
27
+ def convert_source
28
+ @rewriter = ::Parser::Source::TreeRewriter.new(covered_code.buffer)
29
+ insert_node_tags
30
+ insert_branch_tags
31
+ html_escape
32
+ @rewriter.process
33
+ end
34
+
35
+ def root_path
36
+ Pathname('.').relative_path_from(Pathname(covered_code.name).dirname)
37
+ end
38
+
39
+ def stats
40
+ cells = analyser_map.map do |type, analyser|
41
+ data = analyser.stats
42
+ f = ->(kind) { content_tag(:span, data.public_send(kind), class: kind, title: kind) }
43
+ [content_tag(:th, analyser.class.human_name, class: type),
44
+ content_tag(:td, "#{f[:executed]} #{f[:ignored] if data.ignored > 0} / #{f[:potentially_executable]}", class: type),
45
+ ]
46
+ end
47
+ rows = cells.transpose.map { |line| content_tag(:tr, line.join) }
48
+ content_tag(:table, rows.join)
49
+ end
50
+
51
+ def analyser
52
+ analyser_map[:per_char]
53
+ end
54
+
55
+ def covered_code
56
+ analyser.covered_code
57
+ end
58
+
59
+ private
60
+
61
+ RUNS_CLASS = Hash.new('run').merge!(0 => 'not-run', nil => 'ignored')
62
+ RUNS_TITLE = Hash.new { |k, runs| "#{runs}x" }.merge!(0 => 'never run', nil => 'ignored')
63
+
64
+ def node_span(node, kind)
65
+ runs = analyser.node_runs(node)
66
+ %{<span class="node-#{node.type} kind-#{kind} #{RUNS_CLASS[runs]}" title="#{RUNS_TITLE[runs]}">}
67
+ end
68
+
69
+ def insert_node_tags
70
+ analyser.each_node do |node|
71
+ h = node.executed_loc_hash
72
+ h.each do |kind, range|
73
+ wrap(range, node_span(node, kind), '</span>')
74
+ end
75
+ exp = node.expression
76
+ if (exp.nil? || exp.empty?) && !analyser.node_covered?(node) && !node.parent.is_a?(Node::Branch) # Not executed empty bodies must show!
77
+ replace(exp, icon(:empty, 'empty node never run'))
78
+ wrap(exp, node_span(node, :empty))
79
+ end
80
+ end
81
+ end
82
+
83
+ ICONS = {
84
+ fork: 'code-fork',
85
+ empty: 'code',
86
+ }.freeze
87
+ def icon(type, title)
88
+ %{<i class="#{type}-icon fa fa-#{ICONS[type]}" aria-hidden="true" title="#{title}"></i>}
89
+ end
90
+
91
+ def fork_span(node, kind, id, title: nil, klass: nil)
92
+ runs = analyser_map[:branch].node_runs(node)
93
+ title ||= RUNS_TITLE[runs]
94
+ %{<span class="fork fork-#{kind} fork-#{RUNS_CLASS[runs]} #{klass}" data-fork-id="#{id}">#{icon(:fork, title)}}
95
+ end
96
+
97
+ def insert_branch_tags
98
+ analyser_map[:branch].each_node.with_index do |node, id|
99
+ node.branches.each do |branch|
100
+ exp = branch.expression
101
+ wrap(exp, fork_span(branch, :branch, id), '</span>') if exp
102
+ end
103
+ runs = analyser_map[:branch].node_runs(node)
104
+ if !covered?(runs) && analyser.node_covered?(node)
105
+ jumps_missing = node.branches.reject { |jump| analyser.node_covered?(jump) }
106
+ title = "#{node.branches_summary(jumps_missing)} not covered"
107
+ klass = 'fork-with-uncovered-branches'
108
+ end
109
+ wrap(node.expression, fork_span(node, :whole, id, title: title, klass: klass))
110
+ end
111
+ end
112
+
113
+ def replace(range, with)
114
+ @rewriter.replace(range, with) rescue binding.pry
115
+ end
116
+
117
+ def wrap(range, before, after = '</span>')
118
+ line = @rewriter.source_buffer.line_range(range.first_line)
119
+ pinned = range.with(end_pos: [range, line].map(&:end_pos).min)
120
+ @rewriter.wrap(pinned, before, after) rescue binding.pry
121
+ end
122
+
123
+ def html_escape
124
+ buffer = analyser.covered_code.buffer
125
+ source = buffer.source
126
+ {'<' => '&lt;', '>' => '&gt;', '&' => '&amp;'}.each do |char, escaped|
127
+ source.scan(char) do
128
+ m = Regexp.last_match
129
+ range = ::Parser::Source::Range.new(buffer, m.begin(0), m.end(0))
130
+ @rewriter.replace(range, escaped)
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,338 @@
1
+ // Adapted from GitHub's scheme https://raw.githubusercontent.com/primer/github-syntax-light/master/lib/github-light.css
2
+ $pl-c: #6a737d /* comment punctuation.definition.comment string.comment */
3
+
4
+ /* constant entity.name.constant variable.other.constant variable.language support meta.property-name support.constant support.variable meta.module-reference markup.raw meta.diff.header meta.output */
5
+ $pl-c1: #005cc5 /* string variable */
6
+
7
+ $pl-en: #6f42c1 /* entity.name */
8
+
9
+ /* variable.parameter.function storage.modifier.package storage.modifier.import storage.type.java variable.other */
10
+ $pl-s1: #24292e /* string source */
11
+
12
+
13
+ $pl-ent: #22863a /* entity.name.tag markup.quote */
14
+
15
+
16
+ $pl-k: #d73a49 /* keyword storage storage.type */
17
+ $pl-k: #a419ea /* override */
18
+
19
+ /* string */
20
+ /* punctuation.definition.string source.regexp string.regexp.character-class */
21
+ /* string punctuation.section.embedded source */
22
+ /* string.regexp */
23
+ /* string.regexp constant.character.escape */
24
+ /* string.regexp source.ruby.embedded */
25
+ $pl-s: #032f62 /* string.regexp string.regexp.arbitrary-repitition */
26
+
27
+ /* variable */
28
+ $pl-v: #e36209 /* sublimelinter.mark.warning */
29
+
30
+ $pl-bu: #b31d28 /* invalid.broken invalid.deprecated invalid.unimplemented message.error brackethighlighter.unmatched sublimelinter.mark.error */
31
+
32
+ $pl-ii: #fafbfc /* invalid.illegal */
33
+ $pl-ii-background: #b31d28
34
+
35
+ $pl-sr: #22863a /* string.regexp constant.character.escape */
36
+
37
+ $pl-ml: #735c0f /* markup.list */
38
+
39
+ /* markup.heading */
40
+ /* markup.heading entity.name */
41
+ $pl-mh: #005cc5 /* meta.separator */
42
+
43
+ $pl-mi2: #f6f8fa /* markup.ignored markup.untracked */
44
+ $pl-mi2-background: #005cc5
45
+
46
+
47
+ // http://meyerweb.com/eric/tools/css/reset/
48
+ // v2.0 | 20110126
49
+ // License: none (public domain)
50
+
51
+
52
+ html, body, div, span, applet, object, iframe,
53
+ h1, h2, h3, h4, h5, h6, p, blockquote, pre,
54
+ a, abbr, acronym, address, big, cite, code,
55
+ del, dfn, em, img, ins, kbd, q, s, samp,
56
+ small, strike, strong, sub, sup, tt, var,
57
+ b, u, i, center,
58
+ dl, dt, dd, ol, ul, li,
59
+ fieldset, form, label, legend,
60
+ table, caption, tbody, tfoot, thead, tr, th, td,
61
+ article, aside, canvas, details, embed,
62
+ figure, figcaption, footer, header, hgroup,
63
+ menu, nav, output, ruby, section, summary,
64
+ time, mark, audio, video
65
+ margin: 0
66
+ padding: 0
67
+ border: 0
68
+ font-size: 100%
69
+ // font: inherit
70
+ vertical-align: baseline
71
+
72
+ // HTML5 display-role reset for older browsers
73
+ article, aside, details, figcaption, figure,
74
+ footer, header, hgroup, menu, nav, section
75
+ display: block
76
+
77
+ body
78
+ line-height: 1
79
+
80
+ ol, ul
81
+ list-style: none
82
+
83
+ blockquote, q
84
+ quotes: none
85
+
86
+ blockquote:before, blockquote:after,
87
+ q:before, q:after
88
+ content: ''
89
+ content: none
90
+
91
+ table
92
+ border-collapse: collapse
93
+ border-spacing: 0
94
+
95
+
96
+ // Global settings
97
+ *
98
+ box-sizing: border-box
99
+ border-collapse: collapse
100
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif
101
+
102
+ // *** DeepCover Source ***
103
+
104
+ $dark-green: #25712b
105
+ $light-green: #74b770
106
+ $light-gray: #e1e4e8
107
+
108
+ %light-border
109
+ border: 1px solid $light-gray
110
+
111
+ .fork-branch > .fork-icon
112
+ display: none
113
+ %show-fork-icon
114
+ .fork-branch > .fork-icon
115
+ display: inherit
116
+ %center
117
+ width: 980px
118
+ margin: 0 auto 0 auto
119
+
120
+ $toggled: $light-green
121
+ $not-run: hsl(hue(#f00), 100%, 85%)
122
+ $ignored: hsl(0, 0, 85%)
123
+
124
+ // *** Common template ***
125
+ main
126
+ @extend %center
127
+ padding-top: 10px
128
+ .overlay
129
+ width: 100%
130
+ position: fixed
131
+ top: 0
132
+ z-index: 9999
133
+ .center
134
+ position: relative
135
+ @extend %center
136
+
137
+ header
138
+ width: 100%
139
+ position: relative
140
+ top: 0
141
+ background: #24292e
142
+ color: rgba(255,255,255,0.75)
143
+ padding-bottom: 4px
144
+
145
+ .content
146
+ position: relative
147
+ @extend %center
148
+
149
+ .info
150
+ padding: 4px 0 4px 0
151
+ color: #88d483
152
+ .deep-cover
153
+ transition: opacity 0.4s
154
+ opacity: 0.3
155
+ font-size: 24px
156
+ font-weight: 900
157
+ .deep
158
+ color: #74b770
159
+ .version
160
+ transition: opacity 0.4s
161
+ opacity: 0.1
162
+ font-size: 12px
163
+ font-weight: lighter
164
+ &:hover
165
+ .deep-cover
166
+ opacity: 0.8
167
+ .version
168
+ opacity: 0.6
169
+
170
+ // *** DeepCover Source ***
171
+ body.source &
172
+ .nav
173
+ a
174
+ color: white
175
+ body.source
176
+ .stats
177
+ th, td
178
+ padding: 6px
179
+ border-right: 1px solid $light-gray
180
+ td
181
+ padding-top: 0
182
+ table
183
+ @extend %light-border
184
+ position: absolute
185
+ background: white
186
+ right: 20px
187
+ top: 15px
188
+ color: $pl-s
189
+ .ignored
190
+ opacity: 0.7
191
+ &::before
192
+ content: "[+"
193
+ &::after
194
+ content: "]"
195
+
196
+ main
197
+ white-space: pre
198
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace
199
+ font-size: 12px
200
+ line-height: 20px
201
+
202
+ table.source
203
+ @extend %light-border
204
+ width: 100%
205
+
206
+ td
207
+ vertical-align: top
208
+
209
+ .nb
210
+ color: rgba(27,31,35,0.3)
211
+ text-align: right
212
+ width: 50px
213
+ padding-right: 10px
214
+ padding-left: 10px
215
+ // * Color scheme *
216
+
217
+ // Default color, for unexecutable code
218
+ color: $pl-c
219
+
220
+ // Executed code
221
+ .run
222
+ color: $pl-s
223
+
224
+ .not-run
225
+ background-color: $not-run
226
+ padding: 2px 0
227
+
228
+ .ignored
229
+ background-color: $ignored
230
+ padding: 2px 0
231
+
232
+ .kind-keyword, .kind-else
233
+ color: $pl-k
234
+
235
+ .node-def.kind-name
236
+ color: #3e920c
237
+
238
+ // Literals:
239
+ .kind-expression, .node-const
240
+ color: $pl-c1
241
+
242
+ .node-send
243
+ color: $dark-green
244
+
245
+ .node-str
246
+ color: $pl-s
247
+
248
+ // Icons
249
+ .icon-not-run
250
+ background-color: $not-run
251
+ border-color: red
252
+ .icon-ignored
253
+ background-color: $ignored
254
+ border-color: darken($ignored, 20%)
255
+ .icon
256
+ position: absolute
257
+ left: -2.2ex
258
+ width: 2ex
259
+ height: 1.5em
260
+ line-height: 1.5em
261
+ text-align: center
262
+ background-color: white
263
+ padding: 0
264
+ @extend %light-border
265
+
266
+ .kind-empty
267
+ position: relative
268
+ & > .empty-icon
269
+ @extend .icon
270
+ left: -2.5ex
271
+ width: 2.3ex
272
+ &.not-run > .empty-icon
273
+ @extend .icon-not-run
274
+
275
+ // Branches
276
+ .fork
277
+ position: relative
278
+ .fork-icon
279
+ @extend .icon
280
+ .fork-whole
281
+ & > .fork-icon
282
+ z-index: 1
283
+ &:hover
284
+ @extend %show-fork-icon
285
+ background: $light-gray
286
+ padding: 2px 0
287
+
288
+ .fork-with-uncovered-branches
289
+ &.fork-not-run > .fork-icon
290
+ @extend .icon-not-run
291
+ &.fork-ignored > .fork-icon
292
+ @extend .icon-ignored
293
+
294
+ &.show-forks
295
+ @extend %show-fork-icon
296
+ .fork-whole
297
+ background: $light-gray
298
+ .stats .branch
299
+ background: $toggled
300
+
301
+ // *** Index ***
302
+
303
+ .tree
304
+ // Overrides
305
+ .jstree-table-cell
306
+ padding-left: 0
307
+ span
308
+ padding-right: 4px
309
+ .jstree-table-header-cell
310
+ // Leave space for sort arrows:
311
+ padding-right: 17px
312
+ font-style: italic
313
+ .jstree-table-sort-icon
314
+ right: 4px
315
+ top: 8px
316
+ left: inherit
317
+ .jstree-table-separator
318
+ border: none
319
+
320
+ .jstree-table-column
321
+ text-align: right
322
+ border: gray 1px #ddd
323
+ .jstree-table-column-0
324
+ text-align: left
325
+
326
+ .percent-100
327
+ color: green
328
+ .percent-90
329
+ color: blue
330
+ .percent-80
331
+ color: brown
332
+ .percent-60, .percent-70
333
+ color: orange
334
+ .percent-00, .percent-10, .percent-20, .percent-30, .percent-40, .percent-50
335
+ color: red
336
+
337
+ footer
338
+ display: none