br-approvals 0.0.22
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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +15 -0
- data/CHANGELOG.md +71 -0
- data/Gemfile +6 -0
- data/License.txt +22 -0
- data/README.md +263 -0
- data/Rakefile +6 -0
- data/approvals.gemspec +29 -0
- data/bin/approvals +8 -0
- data/ext/mkrf_conf.rb +22 -0
- data/lib/approvals.rb +33 -0
- data/lib/approvals/approval.rb +144 -0
- data/lib/approvals/cli.rb +36 -0
- data/lib/approvals/configuration.rb +29 -0
- data/lib/approvals/dotfile.rb +33 -0
- data/lib/approvals/dsl.rb +7 -0
- data/lib/approvals/error.rb +21 -0
- data/lib/approvals/executable.rb +18 -0
- data/lib/approvals/extensions/rspec.rb +13 -0
- data/lib/approvals/extensions/rspec/dsl.rb +44 -0
- data/lib/approvals/filter.rb +45 -0
- data/lib/approvals/namers/default_namer.rb +20 -0
- data/lib/approvals/namers/directory_namer.rb +30 -0
- data/lib/approvals/namers/rspec_namer.rb +32 -0
- data/lib/approvals/reporters.rb +14 -0
- data/lib/approvals/reporters/diff_reporter/diffmerge_reporter.rb +18 -0
- data/lib/approvals/reporters/diff_reporter/opendiff_reporter.rb +18 -0
- data/lib/approvals/reporters/diff_reporter/tortoisediff_reporter.rb +18 -0
- data/lib/approvals/reporters/diff_reporter/vimdiff_reporter.rb +18 -0
- data/lib/approvals/reporters/filelauncher_reporter.rb +18 -0
- data/lib/approvals/reporters/first_working_reporter.rb +21 -0
- data/lib/approvals/reporters/image_reporter.rb +13 -0
- data/lib/approvals/reporters/image_reporter/html_image_reporter.rb +35 -0
- data/lib/approvals/reporters/image_reporter/image_magick_reporter.rb +20 -0
- data/lib/approvals/reporters/launcher.rb +49 -0
- data/lib/approvals/reporters/reporter.rb +30 -0
- data/lib/approvals/rspec.rb +4 -0
- data/lib/approvals/scrubber.rb +43 -0
- data/lib/approvals/system_command.rb +13 -0
- data/lib/approvals/version.rb +3 -0
- data/lib/approvals/writer.rb +39 -0
- data/lib/approvals/writers/array_writer.rb +17 -0
- data/lib/approvals/writers/binary_writer.rb +49 -0
- data/lib/approvals/writers/hash_writer.rb +20 -0
- data/lib/approvals/writers/html_writer.rb +15 -0
- data/lib/approvals/writers/json_writer.rb +31 -0
- data/lib/approvals/writers/text_writer.rb +21 -0
- data/lib/approvals/writers/xml_writer.rb +15 -0
- data/spec/approvals_spec.rb +172 -0
- data/spec/configuration_spec.rb +28 -0
- data/spec/dotfile_spec.rb +22 -0
- data/spec/executable_spec.rb +17 -0
- data/spec/extensions/rspec_approvals_spec.rb +105 -0
- data/spec/filter_spec.rb +123 -0
- data/spec/fixtures/approvals/approvals_custom_writer_verifies_a_complex_object.approved.txt +1 -0
- data/spec/fixtures/approvals/approvals_passes_approved_files_through_erb.approved.txt +1 -0
- data/spec/fixtures/approvals/approvals_passes_the_received_files_through_erb.approved.txt +1 -0
- data/spec/fixtures/approvals/approvals_supports_excluded_keys_option_also_supports_an_array_of_hashes.approved.json +10 -0
- data/spec/fixtures/approvals/approvals_supports_excluded_keys_option_supports_the_array_writer.approved.txt +1 -0
- data/spec/fixtures/approvals/approvals_supports_excluded_keys_option_supports_the_hash_writer.approved.txt +1 -0
- data/spec/fixtures/approvals/approvals_supports_excluded_keys_option_verifies_json_with_excluded_keys.approved.json +8 -0
- data/spec/fixtures/approvals/approvals_verifies_a_complex_object.approved.txt +1 -0
- data/spec/fixtures/approvals/approvals_verifies_a_hash.approved.txt +6 -0
- data/spec/fixtures/approvals/approvals_verifies_a_malformed_html_fragment.approved.html +11 -0
- data/spec/fixtures/approvals/approvals_verifies_a_string.approved.txt +1 -0
- data/spec/fixtures/approvals/approvals_verifies_an_array.approved.txt +4 -0
- data/spec/fixtures/approvals/approvals_verifies_an_array_as_json_when_format_is_set_to_json.approved.json +10 -0
- data/spec/fixtures/approvals/approvals_verifies_an_executable.approved.txt +1 -0
- data/spec/fixtures/approvals/approvals_verifies_html.approved.html +11 -0
- data/spec/fixtures/approvals/approvals_verifies_json.approved.json +7 -0
- data/spec/fixtures/approvals/approvals_verifies_json_and_is_newline_agnostic.approved.json +7 -0
- data/spec/fixtures/approvals/approvals_verifies_xml.approved.xml +9 -0
- data/spec/fixtures/approvals/verifications_a_string.approved.txt +1 -0
- data/spec/fixtures/approvals/verifies_a_complex_object.approved.txt +1 -0
- data/spec/fixtures/approvals/verifies_a_failure.approved.txt +1 -0
- data/spec/fixtures/approvals/verifies_a_failure_diff.approved.txt +1 -0
- data/spec/fixtures/approvals/verifies_a_string.approved.txt +1 -0
- data/spec/fixtures/approvals/verifies_an_array.approved.txt +4 -0
- data/spec/fixtures/approvals/verifies_an_executable.approved.txt +1 -0
- data/spec/fixtures/approvals/verifies_directory/a_complex_object.approved.txt +1 -0
- data/spec/fixtures/approvals/verifies_directory/a_failure.approved.txt +0 -0
- data/spec/fixtures/approvals/verifies_directory/a_failure_diff.approved.txt +0 -0
- data/spec/fixtures/approvals/verifies_directory/a_string.approved.txt +1 -0
- data/spec/fixtures/approvals/verifies_directory/an_array.approved.txt +4 -0
- data/spec/fixtures/approvals/verifies_directory/an_executable.approved.txt +1 -0
- data/spec/fixtures/approvals/verifies_directory/html.approved.html +11 -0
- data/spec/fixtures/approvals/verifies_directory/json.approved.json +7 -0
- data/spec/fixtures/approvals/verifies_directory/xml.approved.xml +9 -0
- data/spec/fixtures/approvals/verifies_html.approved.html +11 -0
- data/spec/fixtures/approvals/verifies_json.approved.json +7 -0
- data/spec/fixtures/approvals/verifies_xml.approved.xml +9 -0
- data/spec/fixtures/one.png +0 -0
- data/spec/fixtures/one.txt +1 -0
- data/spec/fixtures/two.png +0 -0
- data/spec/fixtures/two.txt +1 -0
- data/spec/namers/default_namer_spec.rb +37 -0
- data/spec/namers/directory_namer_spec.rb +31 -0
- data/spec/namers/rspec_namer_spec.rb +30 -0
- data/spec/namers_spec.rb +16 -0
- data/spec/reporters/first_working_reporter_spec.rb +30 -0
- data/spec/reporters/html_image_reporter_spec.rb +22 -0
- data/spec/reporters/image_magick_reporter_spec.rb +16 -0
- data/spec/reporters/launcher_spec.rb +24 -0
- data/spec/reporters/opendiff_reporter_spec.rb +15 -0
- data/spec/reporters/reporter_spec.rb +21 -0
- data/spec/scrubber_spec.rb +26 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/system_command_spec.rb +13 -0
- metadata +196 -0
data/ext/mkrf_conf.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rubygems/dependency_installer'
|
2
|
+
|
3
|
+
# This is how we can depend on a different version of the same gem for
|
4
|
+
# different Ruby versions.
|
5
|
+
# See https://en.wikibooks.org/wiki/Ruby_Programming/RubyGems
|
6
|
+
|
7
|
+
installer = Gem::DependencyInstaller.new
|
8
|
+
|
9
|
+
begin
|
10
|
+
if RUBY_VERSION >= '2.0'
|
11
|
+
installer.install 'json', '~> 2.0'
|
12
|
+
else
|
13
|
+
installer.install 'json', '~> 1.8'
|
14
|
+
end
|
15
|
+
rescue
|
16
|
+
exit(1)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Write fake Rakefile for rake since Makefile isn't used
|
20
|
+
File.open(File.join(File.dirname(__FILE__), 'Rakefile'), 'w') do |f|
|
21
|
+
f.write("task :default\n")
|
22
|
+
end
|
data/lib/approvals.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'approvals/version'
|
5
|
+
require 'approvals/configuration'
|
6
|
+
require 'approvals/approval'
|
7
|
+
require 'approvals/dsl'
|
8
|
+
require 'approvals/error'
|
9
|
+
require 'approvals/system_command'
|
10
|
+
require 'approvals/scrubber'
|
11
|
+
require 'approvals/dotfile'
|
12
|
+
require 'approvals/executable'
|
13
|
+
require 'approvals/reporters'
|
14
|
+
require 'approvals/filter'
|
15
|
+
require 'approvals/writer'
|
16
|
+
require 'approvals/namers/default_namer'
|
17
|
+
|
18
|
+
module Approvals
|
19
|
+
extend DSL
|
20
|
+
|
21
|
+
class << self
|
22
|
+
|
23
|
+
def project_dir
|
24
|
+
@project_dir ||= FileUtils.pwd
|
25
|
+
end
|
26
|
+
|
27
|
+
def reset
|
28
|
+
Dotfile.reset
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Approvals.reset
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'erb' # It is referenced on line 69
|
2
|
+
module Approvals
|
3
|
+
class Approval
|
4
|
+
class << self
|
5
|
+
attr_accessor :namer
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :subject, :namer, :failure
|
9
|
+
def initialize(subject, options = {})
|
10
|
+
@subject = subject
|
11
|
+
@namer = options[:namer] || default_namer(options[:name])
|
12
|
+
@format = options[:format] || identify_format
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_namer(name)
|
16
|
+
Approvals::Approval.namer || Namers::DefaultNamer.new(name)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Add a Proc that tests if subject is a kind of format
|
20
|
+
IDENTITIES = {
|
21
|
+
hash: Proc.new(){|subject|subject.respond_to? :each_pair},
|
22
|
+
array: Proc.new(){|subject|subject.respond_to? :each_with_index},
|
23
|
+
}
|
24
|
+
|
25
|
+
def identify_format
|
26
|
+
IDENTITIES.each_pair do |format, id_test|
|
27
|
+
return format if id_test.call(subject)
|
28
|
+
end
|
29
|
+
# otherwise
|
30
|
+
return :txt
|
31
|
+
end
|
32
|
+
|
33
|
+
def writer
|
34
|
+
@writer ||= Writer.for(@format)
|
35
|
+
end
|
36
|
+
|
37
|
+
def verify
|
38
|
+
unless File.exist?(namer.output_dir)
|
39
|
+
FileUtils.mkdir_p(namer.output_dir)
|
40
|
+
end
|
41
|
+
|
42
|
+
writer.write(subject, received_path)
|
43
|
+
|
44
|
+
unless approved?
|
45
|
+
fail_with "Approval file \"#{approved_path}\" not found."
|
46
|
+
end
|
47
|
+
|
48
|
+
@approved_content, @received_content = read_content
|
49
|
+
|
50
|
+
unless received_matches?
|
51
|
+
fail_with "Received file does not match approved:\n"+
|
52
|
+
"#{received_path}\n#{approved_path}\n#{diff_preview}"
|
53
|
+
end
|
54
|
+
|
55
|
+
success!
|
56
|
+
end
|
57
|
+
|
58
|
+
def diff_preview
|
59
|
+
approved, received = diff_lines
|
60
|
+
return unless approved and received
|
61
|
+
diff_index =
|
62
|
+
approved.each_char.with_index.find_index do |approved_char, i|
|
63
|
+
approved_char != received[i]
|
64
|
+
end
|
65
|
+
"approved fragment: #{approved[diff_index - 10 .. diff_index + 30]}\n"+
|
66
|
+
"received fragment: #{received[diff_index - 10 .. diff_index + 30]}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def diff_lines
|
70
|
+
approved = @approved_content.split("\n")
|
71
|
+
received = @received_content.split("\n")
|
72
|
+
approved.each_with_index do |line, i|
|
73
|
+
return line, received[i] unless line == received[i]
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def success!
|
78
|
+
File.delete received_path
|
79
|
+
end
|
80
|
+
|
81
|
+
def approved?
|
82
|
+
File.exist? approved_path
|
83
|
+
end
|
84
|
+
|
85
|
+
BINARY_FORMATS = [:binary]
|
86
|
+
|
87
|
+
def read_content
|
88
|
+
if BINARY_FORMATS.include?(@format) # Read without ERB
|
89
|
+
[IO.read(approved_path).chomp,
|
90
|
+
IO.read(received_path).chomp]
|
91
|
+
else
|
92
|
+
[ERB.new(IO.read(approved_path).chomp).result,
|
93
|
+
ERB.new(IO.read(received_path).chomp).result]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def received_matches?
|
98
|
+
@approved_content == @received_content
|
99
|
+
end
|
100
|
+
|
101
|
+
def fail_with(message)
|
102
|
+
Dotfile.append(diff_path)
|
103
|
+
|
104
|
+
if subject.respond_to?(:on_failure)
|
105
|
+
subject.on_failure.call(approved_text) if approved?
|
106
|
+
subject.on_failure.call(received_text)
|
107
|
+
end
|
108
|
+
|
109
|
+
error = ApprovalError.new("Approval Error: #{message}")
|
110
|
+
error.approved_path = approved_path
|
111
|
+
error.received_path = received_path
|
112
|
+
|
113
|
+
raise error
|
114
|
+
end
|
115
|
+
|
116
|
+
def diff_path
|
117
|
+
"#{approved_path} #{received_path}"
|
118
|
+
end
|
119
|
+
|
120
|
+
def full_path(state)
|
121
|
+
"#{namer.output_dir}#{namer.name}.#{state}.#{writer.extension}"
|
122
|
+
end
|
123
|
+
|
124
|
+
def name
|
125
|
+
namer.name
|
126
|
+
end
|
127
|
+
|
128
|
+
def approved_path
|
129
|
+
full_path('approved')
|
130
|
+
end
|
131
|
+
|
132
|
+
def received_path
|
133
|
+
full_path('received')
|
134
|
+
end
|
135
|
+
|
136
|
+
def approved_text
|
137
|
+
File.read(approved_path).chomp
|
138
|
+
end
|
139
|
+
|
140
|
+
def received_text
|
141
|
+
File.read(received_path).chomp
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# don't require the approvals library here, as it will reset the dotfile.
|
2
|
+
# or find a better way to reset the dotfile before a run.
|
3
|
+
module Approvals
|
4
|
+
class CLI < Thor
|
5
|
+
|
6
|
+
desc "verify", "Go through all failing approvals with a diff tool"
|
7
|
+
method_option :diff, :type => :string, :default => 'diff -N', :aliases => '-d', :desc => 'The difftool to use. e.g. opendiff, vimdiff, etc.'
|
8
|
+
method_option :ask, :type => :boolean, :default => true, :aliases => "-a", :desc => 'Offer to approve the received file for you.'
|
9
|
+
def verify
|
10
|
+
approvals = File.read('.approvals').split("\n")
|
11
|
+
|
12
|
+
rejected = []
|
13
|
+
approvals.each do |approval|
|
14
|
+
approved, received = approval.split(/\s+/)
|
15
|
+
if received.include?(".approved.")
|
16
|
+
received, approved = approved, received
|
17
|
+
end
|
18
|
+
|
19
|
+
diff_command = "#{options[:diff]} #{approved} #{received}"
|
20
|
+
puts diff_command
|
21
|
+
system(diff_command)
|
22
|
+
|
23
|
+
if options[:ask] && yes?("Approve? [y/N] ")
|
24
|
+
system("mv #{received} #{approved}")
|
25
|
+
else
|
26
|
+
rejected << approval
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
File.open('.approvals', 'w') do |f|
|
31
|
+
f.write rejected.join("\n")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Approvals
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def configure(&block)
|
7
|
+
block.call Approvals::Configuration.instance
|
8
|
+
end
|
9
|
+
|
10
|
+
def configuration
|
11
|
+
Approvals::Configuration.instance
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Configuration
|
16
|
+
include Singleton
|
17
|
+
|
18
|
+
attr_writer :approvals_path
|
19
|
+
attr_writer :excluded_json_keys
|
20
|
+
|
21
|
+
def approvals_path
|
22
|
+
@approvals_path ||= 'fixtures/approvals/'
|
23
|
+
end
|
24
|
+
|
25
|
+
def excluded_json_keys
|
26
|
+
@excluded_json_keys ||= {}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Approvals
|
2
|
+
|
3
|
+
class Dotfile
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def reset
|
7
|
+
File.truncate(path, 0) if File.exist?(path)
|
8
|
+
end
|
9
|
+
|
10
|
+
def append(text)
|
11
|
+
unless includes?(text)
|
12
|
+
write text
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def path
|
19
|
+
File.join(Approvals.project_dir, '.approvals')
|
20
|
+
end
|
21
|
+
|
22
|
+
def includes?(text)
|
23
|
+
system("cat #{path} 2> /dev/null | grep -q \"^#{text}$\"")
|
24
|
+
end
|
25
|
+
|
26
|
+
def write(text)
|
27
|
+
File.open(path, 'a+') do |f|
|
28
|
+
f.write "#{text}\n"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Approvals
|
2
|
+
class ApprovalError < Exception
|
3
|
+
attr_accessor :received_path, :approved_path
|
4
|
+
|
5
|
+
def received_exists?
|
6
|
+
received_path && File.exist?(received_path)
|
7
|
+
end
|
8
|
+
|
9
|
+
def received_text
|
10
|
+
received_exists? && IO.read(received_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def approved_exists?
|
14
|
+
approved_path && File.exist?(approved_path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def approved_text
|
18
|
+
approved_exists? && IO.read(approved_path)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Approvals
|
2
|
+
class Executable
|
3
|
+
|
4
|
+
attr_accessor :command, :on_failure
|
5
|
+
def initialize(command, &block)
|
6
|
+
self.command = command
|
7
|
+
self.on_failure = block
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
inspect
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
command
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
if defined? RSpec
|
2
|
+
require 'approvals/extensions/rspec/dsl'
|
3
|
+
require 'approvals/namers/rspec_namer'
|
4
|
+
require 'approvals/namers/directory_namer'
|
5
|
+
|
6
|
+
RSpec.configure do |c|
|
7
|
+
c.include Approvals::RSpec::DSL
|
8
|
+
c.add_setting :approvals_path, :default => 'spec/fixtures/approvals/'
|
9
|
+
c.add_setting :approvals_namer_class, :default => Approvals::Namers::DirectoryNamer
|
10
|
+
c.add_setting :diff_on_approval_failure, :default => false
|
11
|
+
c.add_setting :approvals_default_format, :default => nil
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rspec/expectations'
|
2
|
+
|
3
|
+
module Approvals
|
4
|
+
module RSpec
|
5
|
+
module DSL
|
6
|
+
def executable(command, &block)
|
7
|
+
Approvals::Executable.new(command, &block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def verify(options = {}, &block)
|
11
|
+
# Workaround to support both Rspec 2 and 3
|
12
|
+
# RSpec.current_example is the Rspec 3 way
|
13
|
+
fetch_current_example = ::RSpec.respond_to?(:current_example) ? proc { ::RSpec.current_example } : proc { |context| context.example }
|
14
|
+
# /Workaround
|
15
|
+
|
16
|
+
group = eval "self", block.binding
|
17
|
+
namer = ::RSpec.configuration.approvals_namer_class.new(fetch_current_example.call(group))
|
18
|
+
defaults = {
|
19
|
+
:namer => namer
|
20
|
+
}
|
21
|
+
format = ::RSpec.configuration.approvals_default_format
|
22
|
+
defaults[:format] = format if format
|
23
|
+
Approvals.verify(block.call, defaults.merge(options))
|
24
|
+
rescue ApprovalError => e
|
25
|
+
if diff_on_approval_failure?
|
26
|
+
::RSpec::Expectations.fail_with(e.message, e.approved_text, e.received_text)
|
27
|
+
else
|
28
|
+
raise e
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def diff_on_approval_failure?
|
35
|
+
# Workaround to support both RSpec 2 and 3
|
36
|
+
fetch_current_example = ::RSpec.respond_to?(:current_example) ? proc { ::RSpec.current_example } : proc { |context| context.example }
|
37
|
+
# /Workaround
|
38
|
+
|
39
|
+
::RSpec.configuration.diff_on_approval_failure? ||
|
40
|
+
fetch_current_example.call(self).metadata[:diff_on_approval_failure]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Approvals
|
2
|
+
class Filter
|
3
|
+
attr_reader :filters
|
4
|
+
|
5
|
+
def initialize(filters)
|
6
|
+
@filters = filters
|
7
|
+
@placeholder = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def apply hash_or_array
|
11
|
+
if @filters.any?
|
12
|
+
censored(hash_or_array)
|
13
|
+
else
|
14
|
+
hash_or_array
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def censored value, key=nil
|
19
|
+
if value.nil?
|
20
|
+
nil
|
21
|
+
elsif key && placeholder_for(key)
|
22
|
+
"<#{placeholder_for(key)}>"
|
23
|
+
else
|
24
|
+
case value
|
25
|
+
when Array
|
26
|
+
value.map { |item| censored(item) }
|
27
|
+
when Hash
|
28
|
+
Hash[value.map { |inner_key, inner_value| [inner_key, censored(inner_value, inner_key)] }]
|
29
|
+
else
|
30
|
+
value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def placeholder_for key
|
36
|
+
return @placeholder[key] if @placeholder.key? key
|
37
|
+
|
38
|
+
applicable_filters = filters.select do |placeholder, pattern|
|
39
|
+
pattern && key.match(pattern)
|
40
|
+
end
|
41
|
+
|
42
|
+
@placeholder[key] = applicable_filters.keys.last
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|