deep-cover 0.6.2 → 0.6.3.pre

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.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.rspec +2 -1
  4. data/.rspec_all +2 -1
  5. data/.rubocop.yml +8 -9
  6. data/Gemfile +2 -0
  7. data/Rakefile +32 -6
  8. data/bin/cov +3 -3
  9. data/deep_cover.gemspec +3 -16
  10. data/exe/deep-cover +5 -0
  11. data/lib/deep_cover/cli/debugger.rb +1 -1
  12. data/lib/deep_cover/cli/exec.rb +1 -1
  13. data/lib/deep_cover/cli/instrumented_clone_reporter.rb +5 -3
  14. data/lib/deep_cover/cli/runner.rb +2 -2
  15. data/lib/deep_cover/{tools/dump_covered_code.rb → dump_covered_code.rb} +2 -0
  16. metadata +10 -203
  17. data/lib/deep-cover.rb +0 -3
  18. data/lib/deep_cover.rb +0 -22
  19. data/lib/deep_cover/analyser.rb +0 -23
  20. data/lib/deep_cover/analyser/base.rb +0 -104
  21. data/lib/deep_cover/analyser/branch.rb +0 -41
  22. data/lib/deep_cover/analyser/covered_code_source.rb +0 -21
  23. data/lib/deep_cover/analyser/function.rb +0 -14
  24. data/lib/deep_cover/analyser/node.rb +0 -54
  25. data/lib/deep_cover/analyser/per_char.rb +0 -38
  26. data/lib/deep_cover/analyser/per_line.rb +0 -41
  27. data/lib/deep_cover/analyser/ruby25_like_branch.rb +0 -211
  28. data/lib/deep_cover/analyser/statement.rb +0 -33
  29. data/lib/deep_cover/analyser/stats.rb +0 -54
  30. data/lib/deep_cover/analyser/subset.rb +0 -27
  31. data/lib/deep_cover/auto_run.rb +0 -71
  32. data/lib/deep_cover/autoload_tracker.rb +0 -215
  33. data/lib/deep_cover/backports.rb +0 -22
  34. data/lib/deep_cover/base.rb +0 -117
  35. data/lib/deep_cover/basics.rb +0 -22
  36. data/lib/deep_cover/builtin_takeover.rb +0 -10
  37. data/lib/deep_cover/config.rb +0 -104
  38. data/lib/deep_cover/config_setter.rb +0 -33
  39. data/lib/deep_cover/core_ext/autoload_overrides.rb +0 -112
  40. data/lib/deep_cover/core_ext/coverage_replacement.rb +0 -61
  41. data/lib/deep_cover/core_ext/exec_callbacks.rb +0 -27
  42. data/lib/deep_cover/core_ext/instruction_sequence_load_iseq.rb +0 -32
  43. data/lib/deep_cover/core_ext/load_overrides.rb +0 -19
  44. data/lib/deep_cover/core_ext/require_overrides.rb +0 -28
  45. data/lib/deep_cover/coverage.rb +0 -125
  46. data/lib/deep_cover/coverage/analysis.rb +0 -42
  47. data/lib/deep_cover/coverage/persistence.rb +0 -84
  48. data/lib/deep_cover/covered_code.rb +0 -145
  49. data/lib/deep_cover/custom_requirer.rb +0 -187
  50. data/lib/deep_cover/flag_comment_associator.rb +0 -68
  51. data/lib/deep_cover/load.rb +0 -66
  52. data/lib/deep_cover/memoize.rb +0 -48
  53. data/lib/deep_cover/module_override.rb +0 -39
  54. data/lib/deep_cover/node.rb +0 -23
  55. data/lib/deep_cover/node/arguments.rb +0 -51
  56. data/lib/deep_cover/node/assignments.rb +0 -273
  57. data/lib/deep_cover/node/base.rb +0 -155
  58. data/lib/deep_cover/node/begin.rb +0 -27
  59. data/lib/deep_cover/node/block.rb +0 -61
  60. data/lib/deep_cover/node/branch.rb +0 -32
  61. data/lib/deep_cover/node/case.rb +0 -113
  62. data/lib/deep_cover/node/collections.rb +0 -31
  63. data/lib/deep_cover/node/const.rb +0 -12
  64. data/lib/deep_cover/node/def.rb +0 -40
  65. data/lib/deep_cover/node/empty_body.rb +0 -32
  66. data/lib/deep_cover/node/exceptions.rb +0 -79
  67. data/lib/deep_cover/node/if.rb +0 -73
  68. data/lib/deep_cover/node/keywords.rb +0 -86
  69. data/lib/deep_cover/node/literals.rb +0 -100
  70. data/lib/deep_cover/node/loops.rb +0 -74
  71. data/lib/deep_cover/node/mixin/can_augment_children.rb +0 -65
  72. data/lib/deep_cover/node/mixin/check_completion.rb +0 -18
  73. data/lib/deep_cover/node/mixin/child_can_be_empty.rb +0 -27
  74. data/lib/deep_cover/node/mixin/executed_after_children.rb +0 -15
  75. data/lib/deep_cover/node/mixin/execution_location.rb +0 -66
  76. data/lib/deep_cover/node/mixin/filters.rb +0 -47
  77. data/lib/deep_cover/node/mixin/flow_accounting.rb +0 -71
  78. data/lib/deep_cover/node/mixin/has_child.rb +0 -145
  79. data/lib/deep_cover/node/mixin/has_child_handler.rb +0 -75
  80. data/lib/deep_cover/node/mixin/has_tracker.rb +0 -46
  81. data/lib/deep_cover/node/mixin/is_statement.rb +0 -20
  82. data/lib/deep_cover/node/mixin/rewriting.rb +0 -35
  83. data/lib/deep_cover/node/mixin/wrapper.rb +0 -15
  84. data/lib/deep_cover/node/module.rb +0 -66
  85. data/lib/deep_cover/node/root.rb +0 -20
  86. data/lib/deep_cover/node/send.rb +0 -161
  87. data/lib/deep_cover/node/short_circuit.rb +0 -42
  88. data/lib/deep_cover/node/splat.rb +0 -15
  89. data/lib/deep_cover/node/variables.rb +0 -16
  90. data/lib/deep_cover/parser_ext/range.rb +0 -21
  91. data/lib/deep_cover/problem_with_diagnostic.rb +0 -63
  92. data/lib/deep_cover/reporter.rb +0 -10
  93. data/lib/deep_cover/reporter/base.rb +0 -68
  94. data/lib/deep_cover/reporter/html.rb +0 -15
  95. data/lib/deep_cover/reporter/html/base.rb +0 -14
  96. data/lib/deep_cover/reporter/html/index.rb +0 -59
  97. data/lib/deep_cover/reporter/html/site.rb +0 -70
  98. data/lib/deep_cover/reporter/html/source.rb +0 -136
  99. data/lib/deep_cover/reporter/html/template/assets/32px.png +0 -0
  100. data/lib/deep_cover/reporter/html/template/assets/40px.png +0 -0
  101. data/lib/deep_cover/reporter/html/template/assets/deep_cover.css.sass +0 -336
  102. data/lib/deep_cover/reporter/html/template/assets/jquery-3.2.1.min.js +0 -4
  103. data/lib/deep_cover/reporter/html/template/assets/jquery-3.2.1.min.map +0 -1
  104. data/lib/deep_cover/reporter/html/template/assets/jstree.css +0 -1108
  105. data/lib/deep_cover/reporter/html/template/assets/jstree.js +0 -8424
  106. data/lib/deep_cover/reporter/html/template/assets/jstreetable.js +0 -1069
  107. data/lib/deep_cover/reporter/html/template/assets/throbber.gif +0 -0
  108. data/lib/deep_cover/reporter/html/template/index.html.erb +0 -75
  109. data/lib/deep_cover/reporter/html/template/source.html.erb +0 -35
  110. data/lib/deep_cover/reporter/istanbul.rb +0 -184
  111. data/lib/deep_cover/reporter/text.rb +0 -58
  112. data/lib/deep_cover/reporter/tree/util.rb +0 -86
  113. data/lib/deep_cover/tools.rb +0 -22
  114. data/lib/deep_cover/tools/blank.rb +0 -25
  115. data/lib/deep_cover/tools/builtin_coverage.rb +0 -55
  116. data/lib/deep_cover/tools/camelize.rb +0 -13
  117. data/lib/deep_cover/tools/content_tag.rb +0 -11
  118. data/lib/deep_cover/tools/covered.rb +0 -9
  119. data/lib/deep_cover/tools/execute_sample.rb +0 -34
  120. data/lib/deep_cover/tools/format.rb +0 -18
  121. data/lib/deep_cover/tools/format_char_cover.rb +0 -19
  122. data/lib/deep_cover/tools/format_generated_code.rb +0 -27
  123. data/lib/deep_cover/tools/indent_string.rb +0 -26
  124. data/lib/deep_cover/tools/merge.rb +0 -16
  125. data/lib/deep_cover/tools/number_lines.rb +0 -22
  126. data/lib/deep_cover/tools/our_coverage.rb +0 -11
  127. data/lib/deep_cover/tools/profiling.rb +0 -68
  128. data/lib/deep_cover/tools/render_template.rb +0 -13
  129. data/lib/deep_cover/tools/require_relative_dir.rb +0 -12
  130. data/lib/deep_cover/tools/scan_match_datas.rb +0 -10
  131. data/lib/deep_cover/tools/silence_warnings.rb +0 -18
  132. data/lib/deep_cover/tools/slice.rb +0 -9
  133. data/lib/deep_cover/tools/strip_heredoc.rb +0 -18
  134. data/lib/deep_cover/tools/truncate_backtrace.rb +0 -32
  135. data/lib/deep_cover/tracker_bucket.rb +0 -50
  136. data/lib/deep_cover/tracker_hits_per_path.rb +0 -35
  137. data/lib/deep_cover/tracker_storage.rb +0 -76
  138. data/lib/deep_cover/tracker_storage_per_path.rb +0 -34
  139. data/lib/deep_cover/version.rb +0 -5
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::Blank
5
- BLANK_RE = /\A[[:space:]]*\z/
6
-
7
- # Homemade poor-man's blank?
8
- # Based, but modified, on https://github.com/rails/rails/blob/5-0-stable/activesupport/lib/active_support/core_ext/object/blank.rb
9
- def blank?(obj)
10
- if obj.is_a?(String)
11
- obj.empty? || obj =~ BLANK_RE
12
- else
13
- obj.respond_to?(:empty?) ? !!obj.empty? : !obj
14
- end
15
- end
16
-
17
- def present?(obj)
18
- !blank?(obj)
19
- end
20
-
21
- def presence(obj)
22
- obj if present?(obj)
23
- end
24
- end
25
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::BuiltinCoverage
5
- def builtin_coverage(source, filename, lineno)
6
- require 'coverage'
7
- filename = File.absolute_path(File.expand_path(filename))
8
- ::Coverage.start
9
- begin
10
- Tools.silence_warnings do
11
- execute_sample -> { run_with_line_coverage(source, filename, lineno) }
12
- end
13
- ensure
14
- result = ::Coverage.result
15
- end
16
- unshift_coverage(result.fetch(filename), lineno)
17
- end
18
-
19
- if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
20
- # Executes the source as if it was in the specified file while
21
- # builtin coverage information is still captured
22
- def run_with_line_coverage(source, filename = nil, lineno = 1)
23
- source = shift_source(source, lineno)
24
- Object.to_java.getRuntime.executeScript(source, filename)
25
- end
26
- else
27
- # In ruby 2.0 and 2.1, using 2, 3 or 4 as lineno with RubyVM::InstructionSequence.compile
28
- # will cause the coverage result to be truncated.
29
- # 1: [1,2,nil,1]
30
- # 2: [nil,1,2,nil]
31
- # 3: [nil,nil,1,2]
32
- # 4: [nil,nil,nil,1]
33
- # 5: [nil,nil,nil,nil,1,2,nil,1]
34
- # Using 1 and 5 or more do not seem to show this issue.
35
- # The workaround is to create the fake lines manually and always use the default lineno
36
-
37
- # Executes the source as if it was in the specified file while
38
- # builtin coverage information is still captured
39
- def run_with_line_coverage(source, filename = nil, lineno = 1)
40
- source = shift_source(source, lineno)
41
- RubyVM::InstructionSequence.compile(source, filename).eval
42
- end
43
- end
44
-
45
- private
46
-
47
- def shift_source(source, lineno)
48
- "\n" * (lineno - 1) + source
49
- end
50
-
51
- def unshift_coverage(coverage, lineno)
52
- coverage[(lineno - 1)..-1]
53
- end
54
- end
55
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools
5
- module Camelize
6
- extend self # Loaded before bootstrap
7
- # Poor man's camelize. 'an_example' => 'AnExample'
8
- def camelize(string)
9
- string.to_s.gsub(/([a-z\d]*)[_?!]?/) { Regexp.last_match(1).capitalize }
10
- end
11
- end
12
- end
13
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::ContentTag
5
- # Poor man's content_tag. No HTML escaping included
6
- def content_tag(tag, content, **options)
7
- attrs = options.map { |kind, value| %{ #{kind}="#{value}"} }.join
8
- "<#{tag}#{attrs}>#{content}</#{tag}>"
9
- end
10
- end
11
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::Covered
5
- def covered?(runs)
6
- runs && runs > 0
7
- end
8
- end
9
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::ExecuteSample
5
- class ExceptionInSample < StandardError
6
- end
7
-
8
- # Returns true if the code would have continued, false if the rescue was triggered.
9
- def execute_sample(to_execute, source: nil)
10
- # Disable some annoying warning by ruby. We are testing edge cases, so warnings are to be expected.
11
- Tools.silence_warnings do
12
- if to_execute.is_a?(CoveredCode)
13
- to_execute.execute_code
14
- else
15
- to_execute.call
16
- end
17
- end
18
- true
19
- rescue StandardError => e
20
- # In our samples, a simple `raise` is expected and doesn't need to be rescued
21
- return false if e.is_a?(RuntimeError) && e.message.empty?
22
-
23
- source = to_execute.covered_source if to_execute.is_a?(CoveredCode)
24
- raise unless source
25
-
26
- inner_msg = Tools.indent_string("#{e.class.name}: #{e.message}", 4)
27
- source = Tools.indent_string(source, 4)
28
- msg = "Exception when executing the sample:\n#{inner_msg}\n*Code follows*\n#{source}"
29
- new_exc = ExceptionInSample.new(msg)
30
- new_exc.set_backtrace(e.backtrace)
31
- raise new_exc
32
- end
33
- end
34
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::Format
5
- CONVERT = Hash.new(' ')
6
- CONVERT[0] = 'x '
7
- CONVERT[nil] = '- '
8
-
9
- def format(*results, filename: nil, source: nil)
10
- source ||= File.read(filename)
11
- results.map! { |counts| counts.map { |c| CONVERT[c] } }
12
- [*results, source.lines].transpose.map do |parts|
13
- *line_results, line = parts
14
- Term::ANSIColor.white(line_results.join) + line.to_s
15
- end
16
- end
17
- end
18
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::FormatCharCover
5
- COLOR = {'x' => :red, ' ' => :green, '-' => :faint}.freeze
6
- WHITESPACE_MAP = Hash.new { |_, v| v }.merge!(' ' => '·', "\t" => '→ ')
7
- def format_char_cover(covered_code, show_whitespace: false, **options)
8
- bc = covered_code.char_cover(**options)
9
- covered_code.buffer.source_lines.map.with_index do |line, line_index|
10
- next line if line.strip =~ /^#[ >]/
11
- line.chars.map.with_index do |c, c_index|
12
- color = COLOR[bc[line_index][c_index]]
13
- c = WHITESPACE_MAP[c] if show_whitespace
14
- Term::ANSIColor.send(color, c)
15
- end.join
16
- end
17
- end
18
- end
19
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::FormatGeneratedCode
5
- def format_generated_code(covered_code)
6
- inserts = []
7
- generated_code = covered_code.instrument_source do |inserted, _node, expr_limit|
8
- inserts << [expr_limit, inserted.size]
9
- Term::ANSIColor.yellow(inserted)
10
- end
11
-
12
- inserts = inserts.sort_by { |exp, _| [exp.line, exp.column] }.reverse
13
- generated_lines = generated_code.split("\n")
14
-
15
- inserts.each do |exp_limit, size|
16
- # Line index starts at 1, so array index returns the next line
17
- comment_line = generated_lines[exp_limit.line]
18
- next if Tools.blank?(comment_line)
19
- next unless comment_line.start_with?('#>')
20
- next if comment_line.start_with?('#>X')
21
- next unless comment_line.size >= exp_limit.column
22
- comment_line.insert(exp_limit.column, ' ' * size)
23
- end
24
- generated_lines.join("\n")
25
- end
26
- end
27
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::IndentString
5
- # In-place implementation copied from active-support.
6
- IMPLEMENTATION = ->(amount, indent_string = nil, indent_empty_lines = false) do
7
- indent_string = indent_string || self[/^[ \t]/] || ' '
8
- re = indent_empty_lines ? /^/ : /^(?!$)/
9
- gsub!(re, indent_string * amount)
10
- end
11
-
12
- # Same as #indent! from active-support
13
- # https://github.com/rails/rails/blob/10e1f1f9a129f2f197a44009a99b73b8ff9dbc0d/activesupport/lib/active_support/core_ext/string/indent.rb#L7
14
- def indent_string!(string, *args)
15
- string.instance_exec(*args, &IMPLEMENTATION)
16
- end
17
-
18
- # Same as #indent from active-support
19
- # https://github.com/rails/rails/blob/10e1f1f9a129f2f197a44009a99b73b8ff9dbc0d/activesupport/lib/active_support/core_ext/string/indent.rb#L42
20
- def indent_string(string, *args)
21
- string = string.dup
22
- indent_string!(string, *args)
23
- string
24
- end
25
- end
26
- end
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::Merge
5
- def merge(*hashes)
6
- if hashes.last.is_a?(Symbol)
7
- oper = hashes.pop
8
- merge(*hashes) { |a, b| a.public_send(oper, b) }
9
- elsif !block_given?
10
- merge(*hashes, &:last)
11
- else
12
- hashes.inject { |result, h| result.merge(h) { |key, a, b| yield [a, b] } }
13
- end
14
- end
15
- end
16
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::NumberLines
5
- def number_lines(lines, lineno: 1, bad_linenos: [])
6
- max_lineno = lineno + lines.size - 1
7
- nb_lineno_digits = max_lineno.to_s.size
8
- lines.map.with_index do |line, i|
9
- cur_lineno = lineno + i
10
- cur_lineno_s = cur_lineno.to_s.rjust(nb_lineno_digits)
11
- if bad_linenos.include?(cur_lineno)
12
- cur_lineno_s = "*#{cur_lineno_s}" unless bad_linenos.empty?
13
- prefix = Term::ANSIColor.red("#{cur_lineno_s} | ")
14
- else
15
- cur_lineno_s = " #{cur_lineno_s}" unless bad_linenos.empty?
16
- prefix = Term::ANSIColor.white("#{cur_lineno_s} | ")
17
- end
18
- "#{prefix}#{line}"
19
- end
20
- end
21
- end
22
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::OurCoverage
5
- def our_coverage(source, filename, lineno, **options)
6
- covered_code = CoveredCode.new(source: source, path: filename, lineno: lineno)
7
- Tools.execute_sample(covered_code)
8
- covered_code.line_coverage(options)[(lineno - 1)..-1]
9
- end
10
- end
11
- end
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'delegate'
4
-
5
- module DeepCover
6
- module Tools::Profiling
7
- # Simple forwarding to implementation
8
- def start
9
- profiler.start
10
- end
11
-
12
- def stop
13
- @results = profiler.stop
14
- end
15
-
16
- def pause
17
- profiler.pause
18
- end
19
-
20
- def resume
21
- profiler.resume
22
- end
23
-
24
- def report
25
- profiler.report(@results)
26
- end
27
-
28
- # Basic utilities using forwarding methods
29
- def profile(do_start = true)
30
- return yield unless do_start
31
- start
32
- yield
33
- stop
34
- report
35
- end
36
-
37
- def dont_profile
38
- pause if profiler_loaded?
39
- yield
40
- ensure
41
- resume if profiler_loaded?
42
- end
43
-
44
- def profiler_loaded?
45
- !!@profiler
46
- end
47
-
48
- private
49
-
50
- # Dependency injection
51
- def profiler
52
- @profiler = RubyProfProfiler.new
53
- end
54
-
55
- class RubyProfProfiler < SimpleDelegator
56
- def initialize
57
- raise 'Profiling is not available when using JRuby' if RUBY_PLATFORM == 'java'
58
- require 'ruby-prof'
59
- super(RubyProf)
60
- end
61
-
62
- def report(results)
63
- printer = RubyProf::GraphPrinter.new(results)
64
- printer.print(STDOUT, {})
65
- end
66
- end
67
- end
68
- end
@@ -1,13 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::RenderTemplate
5
- def render_template(template, bound_object)
6
- require 'erb'
7
- caller_path = Pathname.new(caller(1..1).first.partition(/\.rb:\d/).first).dirname
8
- template = caller_path.join("template/#{template}.html.erb").read
9
- erb = ERB.new(template)
10
- erb.result(bound_object.instance_eval { binding })
11
- end
12
- end
13
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::RequireRelativeDir
5
- def require_relative_dir(dir_name, except: [])
6
- dir = File.dirname(caller(1..1).first.partition(/\.rb:\d/).first)
7
- Dir["#{dir}/#{dir_name}/*.rb"].sort.each do |file|
8
- require file unless except.include? File.basename(file, '.rb')
9
- end
10
- end
11
- end
12
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::RequireRelativeDir
5
- # Like String#scan, but return the MatchData object instead
6
- def scan_match_datas(source, matcher)
7
- source.to_enum(:scan, matcher).map { Regexp.last_match }
8
- end
9
- end
10
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::SilenceWarnings
5
- # copied from: activesupport/lib/active_support/core_ext/kernel/reporting.rb
6
- def silence_warnings
7
- with_warnings(nil) { yield }
8
- end
9
-
10
- def with_warnings(flag)
11
- old_verbose = $VERBOSE
12
- $VERBOSE = flag
13
- yield
14
- ensure
15
- $VERBOSE = old_verbose
16
- end
17
- end
18
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeepCover
4
- module Tools::Slice
5
- def slice(hash, *keys)
6
- keys.each_with_object({}) { |k, h| h[k] = hash[k] if hash.has_key?(k) }
7
- end
8
- end
9
- end