clarion-given 1.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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/lib/given/assertions.rb +71 -0
  3. data/lib/given/binary_operation.rb +34 -0
  4. data/lib/given/core.rb +11 -0
  5. data/lib/given/evaluator.rb +52 -0
  6. data/lib/given/ext/numeric.rb +31 -0
  7. data/lib/given/extensions.rb +277 -0
  8. data/lib/given/failure.rb +80 -0
  9. data/lib/given/failure_matcher.rb +100 -0
  10. data/lib/given/file_cache.rb +18 -0
  11. data/lib/given/fuzzy_number.rb +68 -0
  12. data/lib/given/fuzzy_shortcuts.rb +1 -0
  13. data/lib/given/line_extractor.rb +57 -0
  14. data/lib/given/minitest/all.rb +12 -0
  15. data/lib/given/minitest/before_extension.rb +36 -0
  16. data/lib/given/minitest/configure.rb +7 -0
  17. data/lib/given/minitest/context_extension.rb +3 -0
  18. data/lib/given/minitest/failure_must_raise.rb +13 -0
  19. data/lib/given/minitest/framework.rb +37 -0
  20. data/lib/given/minitest/new_assertions.rb +22 -0
  21. data/lib/given/module_methods.rb +83 -0
  22. data/lib/given/natural_assertion.rb +191 -0
  23. data/lib/given/rspec/all.rb +29 -0
  24. data/lib/given/rspec/before_extensions.rb +15 -0
  25. data/lib/given/rspec/configure.rb +20 -0
  26. data/lib/given/rspec/framework.rb +46 -0
  27. data/lib/given/rspec/have_failed.rb +37 -0
  28. data/lib/given/rspec/have_failed_212.rb +39 -0
  29. data/lib/given/rspec/have_failed_pre212.rb +22 -0
  30. data/lib/given/rspec/monkey.rb +42 -0
  31. data/lib/given/rspec/use_natural_assertions.rb +10 -0
  32. data/lib/given/version.rb +9 -0
  33. data/lib/given.rb +2 -0
  34. data/lib/minitest/given.rb +1 -0
  35. data/lib/minitest-given.rb +9 -0
  36. data/lib/rspec/given.rb +1 -0
  37. data/lib/rspec-given.rb +9 -0
  38. metadata +96 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3840491d2082a82d4fdcf8c2006cb192b754f7264096259f2133defd6360ef0e
4
+ data.tar.gz: 0c487c45718dd66c3b68033d676174358a97d971d9b413bbcb30240d3d3a0be1
5
+ SHA512:
6
+ metadata.gz: a1d23a9499b1856f62e7b9510b8eba10412dac9f4ebfa646ceee331d0a0852b925329a06e190a6361c8296f47e4d2f81c9273986e5cdec82909da6c8c89fbe73
7
+ data.tar.gz: 6cba9eba9739126417bd1513572399131525989b7b26d1b5ad8af1db73a07398ee93f5fdb1d56e8b092354f98f9f86fd4784e3a2a5d9a893b4c2e879f6ee3095
@@ -0,0 +1,71 @@
1
+ require 'given/natural_assertion'
2
+ require 'given/line_extractor'
3
+
4
+ module Given
5
+ module Assertions
6
+
7
+ def self.enable_asserts(enabled=true)
8
+ @enable_asserts = enabled
9
+ end
10
+
11
+ def self.enable_preconditions(enabled=true)
12
+ @enable_preconditions = enabled
13
+ end
14
+
15
+ def self.enable_postconditions(enabled=true)
16
+ @enable_postconditions = enabled
17
+ end
18
+
19
+ def self.asserts?
20
+ @enable_asserts
21
+ end
22
+
23
+ def self.preconditions?
24
+ @enable_preconditions
25
+ end
26
+
27
+ def self.postconditions?
28
+ @enable_postconditions
29
+ end
30
+
31
+ def self.enable_all(enabled=true)
32
+ enable_asserts enabled
33
+ enable_preconditions enabled
34
+ enable_postconditions enabled
35
+ end
36
+
37
+ enable_all
38
+
39
+ AssertError = Class.new(StandardError)
40
+ PreconditionError = Class.new(AssertError)
41
+ PostconditionError = Class.new(AssertError)
42
+
43
+ def Assert(&block)
44
+ return nil unless Given::Assertions.asserts?
45
+ unless block.call
46
+ na = Given::NaturalAssertion.new("Assert", block, self, Given::LineExtractor.new)
47
+ raise AssertError, na.message
48
+ end
49
+ end
50
+
51
+ def Precondition(&block)
52
+ return nil unless Given::Assertions.preconditions?
53
+ if block.call
54
+ true
55
+ else
56
+ na = Given::NaturalAssertion.new("Precondition", block, self, Given::LineExtractor.new)
57
+ raise PreconditionError, na.message
58
+ end
59
+ end
60
+
61
+ def Postcondition(&block)
62
+ return nil unless Given::Assertions.preconditions?
63
+ if block.call
64
+ true
65
+ else
66
+ na = Given::NaturalAssertion.new("Postcondition", block, self, Given::LineExtractor.new)
67
+ raise PostconditionError, na.message
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,34 @@
1
+ module Given
2
+
3
+ class BinaryOperation
4
+ BINARY_EXPLAINATIONS = {
5
+ :== => "to equal",
6
+ :!= => "to not equal",
7
+ :< => "to be less than",
8
+ :<= => "to be less or equal to",
9
+ :> => "to be greater than",
10
+ :>= => "to be greater or equal to",
11
+ :=~ => "to match",
12
+ :!~ => "to not match",
13
+ :=== => "to be matched by",
14
+ }
15
+
16
+ attr_reader :left, :operator, :right
17
+
18
+ def initialize(left, operator, right)
19
+ @left = left
20
+ @operator = operator
21
+ @right = right
22
+ end
23
+
24
+ def explain
25
+ BINARY_EXPLAINATIONS[operator]
26
+ end
27
+
28
+ def self.parse(sexp)
29
+ return nil unless sexp.first == :binary
30
+ new(sexp[1], sexp[2], sexp[3])
31
+ end
32
+ end
33
+
34
+ end
data/lib/given/core.rb ADDED
@@ -0,0 +1,11 @@
1
+
2
+ # Require all the core features
3
+
4
+ require 'given/version'
5
+ require 'given/module_methods'
6
+ require 'given/file_cache'
7
+ require 'given/line_extractor'
8
+ require 'given/extensions'
9
+ require 'given/fuzzy_number'
10
+ require 'given/failure'
11
+ require 'given/failure_matcher'
@@ -0,0 +1,52 @@
1
+
2
+ module Given
3
+ class EvalErr
4
+ def initialize(str)
5
+ @string = str
6
+ end
7
+ def size
8
+ inspect.size
9
+ end
10
+ def to_s
11
+ @string
12
+ end
13
+ def inspect
14
+ @string
15
+ end
16
+ end
17
+
18
+ class Evaluator
19
+ def initialize(example, block)
20
+ @example = example
21
+ @block = block
22
+ end
23
+
24
+ def eval_string(exp_string)
25
+ limit_length(eval_in_context(exp_string).inspect)
26
+ rescue StandardError => ex
27
+ EvalErr.new("#{ex.class}: #{ex.message}")
28
+ end
29
+
30
+ def location
31
+ Given.location_of(@block)
32
+ end
33
+
34
+ private
35
+
36
+ MAX_INSPECT_SIZE = 2000
37
+
38
+ def limit_length(string)
39
+ if string.size > MAX_INSPECT_SIZE
40
+ string[0..MAX_INSPECT_SIZE] + " (...truncated...)"
41
+ else
42
+ string
43
+ end
44
+ end
45
+
46
+ def eval_in_context(exp_string)
47
+ exp_proc = "proc { #{exp_string} }"
48
+ blk = eval(exp_proc, @block.binding)
49
+ @example.instance_eval(&blk)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,31 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Given
4
+ module Ext
5
+ module Numeric
6
+
7
+ def ±(del=nil)
8
+ result = Given::Fuzzy::FuzzyNumber.new(self)
9
+ result.delta(del) if del
10
+ result
11
+ end
12
+
13
+ def ‰(percentage=nil)
14
+ result = Given::Fuzzy::FuzzyNumber.new(self)
15
+ result.percent(percentage) if percentage
16
+ result
17
+ end
18
+
19
+ def €(neps=nil)
20
+ result = Given::Fuzzy::FuzzyNumber.new(self)
21
+ result.epsilon(neps) if neps
22
+ result
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+
29
+ class Numeric
30
+ include Given::Ext::Numeric
31
+ end
@@ -0,0 +1,277 @@
1
+ require 'given/module_methods'
2
+ require 'given/natural_assertion'
3
+ require 'given/failure'
4
+
5
+ module Given
6
+
7
+ # Provide run-time instance methods to support Given infrastructure.
8
+ # All the methods in this module are considered private and
9
+ # implementation-specific, and should not be directly called by the
10
+ # application developer.
11
+ #
12
+ # By convention, these private instance specific methods are
13
+ # prefixed with _gvn_ to avoid name collisions with application
14
+ # methods defined in a spec.
15
+ #
16
+ # (Note that private class methods are prefixed with _Gvn_ and
17
+ # private instance methods are prefixed with _gvn_).
18
+ #
19
+ module InstanceExtensions # :nodoc:
20
+
21
+ # List of containing contexts in order from innermost to
22
+ # outermost.
23
+ def _gvn_inner_contexts # :nodoc:
24
+ self.class.ancestors.select { |context|
25
+ context.respond_to?(:_Gvn_givens)
26
+ }
27
+ end
28
+
29
+ # List of containing contexts in order from outermost to
30
+ # innermost.
31
+ def _gvn_contexts # :nodoc:
32
+ _gvn_inner_contexts.reverse
33
+ end
34
+
35
+ # Return the context information for keyword from the innermost
36
+ # defining context.
37
+ def _gvn_info(keyword) # :nodoc:
38
+ _gvn_inner_contexts.each do |context|
39
+ h = context._Gvn_context_info
40
+ if h.has_key?(keyword)
41
+ return h[keyword]
42
+ end
43
+ end
44
+ nil
45
+ end
46
+
47
+ # Should a natural assertion failure message be generated?
48
+ #
49
+ # A natural assertion failure message is generated if the
50
+ # assertion has non-empty content. The configuration options for
51
+ # natural assertions are checked and applied accordingly.
52
+ #
53
+ def _gvn_need_na_message?(nassert) # :nodoc:
54
+ return false unless nassert.has_content?
55
+ _gvn_na_configured?
56
+ end
57
+
58
+ # Return the configuration value for natural assertions.
59
+ #
60
+ # If natural assertions are not configured in the contexts, use
61
+ # the global configuration value.
62
+ def _gvn_na_configured? # :nodoc:
63
+ info_value = _gvn_info(:natural_assertions_enabled)
64
+ info_value.nil? ? Given.natural_assertions_enabled? : info_value
65
+ end
66
+
67
+ # Establish all the Given preconditions the current and
68
+ # surrounding describe/context blocks, starting with the
69
+ # outermost context.
70
+ def _gvn_establish_givens # :nodoc:
71
+ return if defined?(@_gvn_ran) && @_gvn_ran
72
+ @_gvn_ran = true
73
+ _gvn_contexts.each do |context|
74
+ context._Gvn_givens.each do |block|
75
+ instance_eval(&block)
76
+ end
77
+ end
78
+ end
79
+
80
+ # Check all the invariants in the current and surrounding
81
+ # describe/context blocks, starting with the outermost context.
82
+ def _gvn_check_invariants # :nodoc:
83
+ _gvn_contexts.each do |context|
84
+ context._Gvn_invariants.each do |block|
85
+ _gvn_evaluate("Invariant", block)
86
+ end
87
+ end
88
+ end
89
+
90
+ def _gvn_check_ands # :nodoc:
91
+ return if self.class._Gvn_context_info[:and_ran]
92
+ self.class._Gvn_and_blocks.each do |block|
93
+ _gvn_evaluate("And", block)
94
+ end
95
+ self.class._Gvn_context_info[:and_ran] = true
96
+ end
97
+
98
+ # Implement the run-time semantics of the Then clause.
99
+ def _gvn_then(&block) # :nodoc:
100
+ _gvn_establish_givens
101
+ _gvn_check_invariants
102
+ _gvn_evaluate("Then", block)
103
+ _gvn_check_ands
104
+ end
105
+
106
+ # Determine of the natural assertion pass/fail status of the block
107
+ def _gvn_block_passed?(block) # :nodoc:
108
+ passed = instance_eval(&block)
109
+ passed = passed.to_bool if passed.respond_to?(:to_bool)
110
+ passed
111
+ end
112
+
113
+ # Evaluate a Then, And, or Invariant assertion.
114
+ def _gvn_evaluate(clause_type, block) # :nodoc:
115
+ Given.start_evaluation
116
+ passed = _gvn_block_passed?(block)
117
+ if ! Given.explicit_assertions? && _gvn_na_configured?
118
+ _gvn_naturally_assert(clause_type, block, passed)
119
+ end
120
+ end
121
+
122
+ # Naturally assert the block (based on +passed+).
123
+ def _gvn_naturally_assert(clause_type, block, passed)
124
+ Given.count_assertion
125
+ unless passed
126
+ nassert = NaturalAssertion.new(clause_type, block, self, self.class._Gvn_lines)
127
+ Given.fail_with nassert.message if _gvn_need_na_message?(nassert)
128
+ end
129
+ end
130
+ end
131
+
132
+ # Provide run-time class methods to support Given infrastructure.
133
+ # Methods that begin with _Gvn_ are considered private and
134
+ # implementation specific, and should not be directly called by
135
+ # appliation code. Other methods without the _Gvn_ prefix are public
136
+ # and intended for use by the application developer.
137
+ #
138
+ # (Note that private class methods are prefixed with _Gvn_ and
139
+ # private instance methods are prefixed with _gvn_).
140
+ #
141
+ module ClassExtensions
142
+
143
+ # List of all givens directly in the current describe/context
144
+ # block.
145
+ def _Gvn_givens # :nodoc:
146
+ @_Gvn_givens ||= []
147
+ end
148
+
149
+ # List of all invariants directly in the current
150
+ # describe/context block.
151
+ def _Gvn_invariants # :nodoc:
152
+ @_Gvn_invariants ||= []
153
+ end
154
+
155
+ # List of the and blocks directly in the current describe/context
156
+ # block.
157
+ def _Gvn_and_blocks # :nodoc:
158
+ @_Gvn_and_blocks ||= []
159
+ end
160
+
161
+ # Context information ofr the current describe/context block.
162
+ def _Gvn_context_info # :nodoc:
163
+ @_Gvn_context_info ||= {}
164
+ end
165
+
166
+ # Line extractor for the context.
167
+ def _Gvn_lines # :nodoc:
168
+ @_Gvn_lines ||= LineExtractor.new
169
+ end
170
+
171
+ # Trigger the evaluation of a Given! block by referencing its
172
+ # name.
173
+ def _Gvn_trigger_given(name) # :nodoc:
174
+ Proc.new { send(name) }
175
+ end
176
+
177
+ # Declare a "given" of the current specification. If the given
178
+ # is named, the block will be lazily evaluated the first time
179
+ # the given is mentioned by name in the specification. If the
180
+ # given is unnamed, the block is evaluated for side effects
181
+ # every time the specification is executed.
182
+ #
183
+ # :call-seq:
184
+ # Given(:name) { ... code ... }
185
+ # Given { ... code ... }
186
+ #
187
+ def Given(*args, &block)
188
+ if args.first.is_a?(Symbol)
189
+ let(args.first, &block)
190
+ else
191
+ _Gvn_givens << block
192
+ end
193
+ end
194
+
195
+ # Declare a named given of the current specification. Similar
196
+ # to the named version of the "Given" command, except that the
197
+ # block is always evaluated.
198
+ #
199
+ # :call-seq:
200
+ # Given!(:name) { ... code ... }
201
+ #
202
+ def Given!(name, &block)
203
+ let(name, &block)
204
+ _Gvn_givens << _Gvn_trigger_given(name)
205
+ end
206
+
207
+ # Declare the code that is under test.
208
+ #
209
+ # :call-seq:
210
+ # When(:named_result) { ... code_under_test ... }
211
+ # When { ... code_under_test ... }
212
+ #
213
+ def When(*args, &block)
214
+ if args.first.is_a?(Symbol)
215
+ _Gvn_when_actions_with_capture(args.first, block)
216
+ else
217
+ _Gvn_when_actions(block)
218
+ end
219
+ end
220
+
221
+ # Normal When clause actions.
222
+ def _Gvn_when_actions(block) # :nodoc:
223
+ _Gvn_before do
224
+ _gvn_establish_givens
225
+ instance_eval(&block)
226
+ end
227
+ end
228
+
229
+ # Normal When clause actions except that exceptions are captured
230
+ # in a Failure object.
231
+ def _Gvn_when_actions_with_capture(name, block) # :nodoc:
232
+ let(name) do
233
+ Failure.capture(Given.pending_error) do
234
+ _gvn_establish_givens
235
+ instance_eval(&block)
236
+ end
237
+ end
238
+ _Gvn_before do __send__(name) end
239
+ end
240
+
241
+ # Provide an assertion about the specification.
242
+ #
243
+ # Then supplies an assertion that should be true after all the
244
+ # Given and When blocks have been run. All invariants in scope
245
+ # will be checked before the Then block is run.
246
+ #
247
+ # :call-seq:
248
+ # Then { ... assertion ... }
249
+ #
250
+ def Then(opts={}, &block)
251
+ on_eval = opts.fetch(:on_eval, "_gvn_then")
252
+ file, line = Given.location_of(block)
253
+ description = _Gvn_lines.line(file, line) unless Given.source_caching_disabled
254
+ cmd = description ? "it(description)" : "specify"
255
+ eval %{#{cmd} do #{on_eval}(&block) end}, binding, file, line
256
+ _Gvn_context_info[:then_defined] = true
257
+ end
258
+
259
+ # Establish an invariant that must be true for all Then blocks
260
+ # in the current (and nested) scopes.
261
+ def Invariant(&block)
262
+ _Gvn_invariants << block
263
+ end
264
+
265
+ # Provide an assertion that shares setup with a peer Then command.
266
+ def And(&block)
267
+ fail "And defined without a Then" unless _Gvn_context_info[:then_defined]
268
+ _Gvn_and_blocks << block
269
+ end
270
+
271
+ # Configure the use of natural assertions in this context.
272
+ def use_natural_assertions(enabled=true)
273
+ Given.ok_to_use_natural_assertions(enabled)
274
+ _Gvn_context_info[:natural_assertions_enabled] = enabled
275
+ end
276
+ end
277
+ end
@@ -0,0 +1,80 @@
1
+
2
+ module Given
3
+
4
+ # Failure objects will raise the given exception whenever you try
5
+ # to send it *any* message.
6
+ class Failure < BasicObject
7
+ undef_method :==, :!=, :!
8
+
9
+ # Evaluate a block. If an exception is raised, capture it in a
10
+ # Failure object. Explicitly listed exceptions are passed thru
11
+ # without capture.
12
+ def self.capture(*exceptions)
13
+ begin
14
+ yield
15
+ rescue *exceptions => ex
16
+ raise
17
+ rescue ::Exception => ex
18
+ new(ex)
19
+ end
20
+ end
21
+
22
+ # Create a failure object that will rethrow the given exception
23
+ # whenever an undefined method is called.
24
+ def initialize(exception)
25
+ @exception = exception
26
+ end
27
+
28
+ # Failure objects will respond to #is_a?.
29
+ def is_a?(klass)
30
+ klass == Failure
31
+ end
32
+
33
+ # Failure objects may be compared for equality. If the comparison
34
+ # object is not a matcher, then the exception is re-raised.
35
+ def ==(other)
36
+ if failure_matcher?(other)
37
+ other.matches?(self)
38
+ else
39
+ die
40
+ end
41
+ end
42
+
43
+ # Failure objects may be compared for in-equality. If the comparison
44
+ # object is not a matcher, then the exception is re-raised.
45
+ def !=(other)
46
+ if failure_matcher?(other)
47
+ other.does_not_match?(self)
48
+ else
49
+ die
50
+ end
51
+ end
52
+
53
+ # Most methods will just re-raise the captured exception.
54
+ def method_missing(sym, *args, &block)
55
+ die
56
+ end
57
+
58
+ # Report that we respond to a limited number of methods.
59
+ def respond_to?(method_symbol)
60
+ method_symbol == :call ||
61
+ method_symbol == :== ||
62
+ method_symbol == :!= ||
63
+ method_symbol == :is_a? ||
64
+ method_symbol == :to_bool
65
+ end
66
+
67
+ private
68
+
69
+ # Re-raise the captured exception.
70
+ def die
71
+ ::Kernel.raise @exception
72
+ end
73
+
74
+ # Is the comparison object a failure matcher?
75
+ def failure_matcher?(other)
76
+ other.respond_to?(:matches?) && other.respond_to?(:does_not_match?)
77
+ end
78
+ end
79
+
80
+ end
@@ -0,0 +1,100 @@
1
+ module Given
2
+
3
+ class FailureMatcher
4
+ def initialize(exception_class, message_pattern)
5
+ @no_pattern = false
6
+ @expected_exception_class = exception_class
7
+ @expected_message_pattern = message_pattern
8
+ if @expected_message_pattern.nil?
9
+ @expected_message_pattern = //
10
+ @no_pattern = true
11
+ elsif @expected_message_pattern.is_a?(String)
12
+ @expected_message_pattern =
13
+ Regexp.new("\\A" + Regexp.quote(@expected_message_pattern) + "\\z")
14
+ end
15
+ end
16
+
17
+ def ==(other)
18
+ if other.respond_to?(:call)
19
+ matches?(other)
20
+ else
21
+ super
22
+ end
23
+ end
24
+
25
+ def !=(other)
26
+ if other.respond_to?(:call)
27
+ does_not_match?(other)
28
+ else
29
+ super
30
+ end
31
+ end
32
+
33
+ def matches?(possible_failure)
34
+ if possible_failure.respond_to?(:call)
35
+ match_or_fail(possible_failure)
36
+ else
37
+ Given.fail_with("#{description}, but nothing failed")
38
+ end
39
+ end
40
+
41
+ def does_not_match?(possible_failure)
42
+ if possible_failure.respond_to?(:call)
43
+ mismatch_or_fail(possible_failure)
44
+ else
45
+ true
46
+ end
47
+ end
48
+
49
+ def inspect
50
+ result = "<Failure on #{@expected_exception_class}"
51
+ result << " matching #{@expected_message_pattern.inspect}" unless @no_pattern
52
+ result << ">"
53
+ end
54
+
55
+ private
56
+
57
+ def match_or_fail(possible_failure)
58
+ ex = extract_exception(possible_failure)
59
+ match_exception(ex) ||
60
+ Given.fail_with("#{description}, but got #{ex.inspect}")
61
+ end
62
+
63
+ def mismatch_or_fail(possible_failure)
64
+ ex = extract_exception(possible_failure)
65
+ (! match_exception(ex)) ||
66
+ Given.fail_with("#{unexpected_description}, but got #{ex.inspect}")
67
+ end
68
+
69
+ def match_exception(ex)
70
+ ex.is_a?(@expected_exception_class) && @expected_message_pattern =~ ex.message
71
+ end
72
+
73
+ def extract_exception(possible_failure)
74
+ possible_failure.call
75
+ Given.fail_with("Expected an exception")
76
+ return nil
77
+ rescue Exception => ex
78
+ return ex
79
+ end
80
+
81
+ def description
82
+ result = "Expected failure with #{@expected_exception_class}"
83
+ result << " matching #{@expected_message_pattern.inspect}" unless @no_pattern
84
+ result
85
+ end
86
+
87
+ def unexpected_description
88
+ result = "Did not expect failure with #{@expected_exception_class}"
89
+ result << " matching #{@expected_message_pattern.inspect}" unless @no_pattern
90
+ result
91
+ end
92
+ end
93
+
94
+ module FailureMethod
95
+ def Failure(exception_class=Exception, message_pattern=nil)
96
+ FailureMatcher.new(exception_class, message_pattern)
97
+ end
98
+ end
99
+
100
+ end
@@ -0,0 +1,18 @@
1
+
2
+ module Given
3
+ class FileCache
4
+ def initialize
5
+ @lines = {}
6
+ end
7
+
8
+ def get(file_name)
9
+ @lines[file_name] ||= read_lines(file_name)
10
+ end
11
+
12
+ private
13
+
14
+ def read_lines(file_name)
15
+ open(file_name, "r:UTF-8") { |f| f.readlines }
16
+ end
17
+ end
18
+ end