Class: Literate::Attempt

Inherits:
BasicObject
Defined in:
lib/y_support/literate.rb

Overview

Represents a commented attempt to perform a risky operation, which may result in errors. The operation code is supplied as a block. Method #comment defined by this class helps to increase the informative value of error messages.

Constant Summary collapse

CONSTRUCT =

String construction closures are defined below.

-> string, prefix: '', postfix: '' do
  s = string.to_s
  if s.empty? then '' else prefix + s + postfix end
end
TRANSITIVE =

Hash of transitive verb forms.

::Hash.new do |_, key| "#{key}ing %s" end
.update( is: "being %s", has: "having %s" )
STATE =

Hash for construction of statements (error message parts).

::Hash.new do |, key| "#{key} %s" end
.update( is: "%s", has: "has %s" )

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(subject: nil, text: nil, &block) ⇒ Attempt

Attempt constructor expects two parameters (:subject and :text) and one block. Argument of the :subject parameter is the main subject of the risky operation attempted inside the block. Argument of the :text parameter is the natural language textual description of the risky opration.



81
82
83
84
85
86
87
88
89
90
# File 'lib/y_support/literate.rb', line 81

def initialize( subject: nil, text: nil, &block )
  @__subject__ = subject
  @__text__ = text
  @__block__ = block
  # Knowledge base is a list of subjects and facts known
  # about them.
  @__knowledge_base__ = ::Hash.new do |hash, missing_key|
    hash[ missing_key ] = [ {} ]
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(symbol, *args) ⇒ Object

Method missing delegates all methods not recognized by Literate::Attempt class (which is a subclass of BasicObject) to the subject of the attempt.



183
184
185
# File 'lib/y_support/literate.rb', line 183

def method_missing symbol, *args
  __subject__.send symbol, *args
end

Instance Attribute Details

#__block__Object (readonly)

Block that performs the attempt.



71
72
73
# File 'lib/y_support/literate.rb', line 71

def __block__
  @__block__
end

#__knowledge_base__Object (readonly)

Returns the value of attribute knowledge_base.



73
74
75
# File 'lib/y_support/literate.rb', line 73

def __knowledge_base__
  @__knowledge_base__
end

#__subject__Object (readonly)

An Attempt instance has 4 properties.



70
71
72
# File 'lib/y_support/literate.rb', line 70

def __subject__
  @__subject__
end

#__text__Object (readonly)

NL description of the attempt.



72
73
74
# File 'lib/y_support/literate.rb', line 72

def __text__
  @__text__
end

Instance Method Details

#__circumstances__Object

Produces description of the circumstances, that is, concatenated descriptions of all facts known to the Attempt instance except those about the main subject of the attempt.



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/y_support/literate.rb', line 213

def __circumstances__
  # Start with all facts known to the Attempt instance.
  base = __knowledge_base__
  # Ignore the facts about the main subject.
  circumstances = base.reject { |s, _| s == __subject__ } 
  # Construct descriptive strings of the remaining facts.
  fact_strings = circumstances.map { |subject, _|
    subject, statements = __describe__( subject )
    statements = statements.map { |verb, object|
      TRANSITIVE[ verb ] % object
    }.join( ', ' )
    # Create the fact string.
    subject + CONSTRUCT.( statements, prefix: ' ' )
  }
  # Concatenate the fact strings and return the result.
  return fact_strings.join( ', ' )
end

#__describe__(subject) ⇒ Object

Produces a description of the subject supplied to the method.



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/y_support/literate.rb', line 189

def __describe__ subject
  # Start with the facts known about the subject.
  facts = __knowledge_base__[ subject ].dup
  # FIXME: I wonder what this method is *really* doing.
  # It seems that I wrote this library too quickly and didn't
  # bother with properly defining its list of facts.
  # I did not define what is a "fact", what is a "statement"
  # etc., I just go around using the words and hoping I will
  # understand it after myself later. I found it's quite hard.
  statements = if facts.last.is_a? ::Hash then
                 facts.pop
               else {} end
  fs = facts.join ', '
  if statements.empty? then
    return fs, statements
  else
    return facts.empty? ? subject.to_s : fs, statements
  end
end

#__error_message__(error) ⇒ Object

Facilitating error messages is the main purpose of Literate. This method is invoked when an error occurs inside the block run by Literate::Attempt instance and it constructs a verbose error message using the facts the Attempt instance knows.



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/y_support/literate.rb', line 149

def __error_message__ error
  # Write the first part of the error message.
  part1 = "When trying #{__text__}"
  # Get the description of the main subject.
  subject, statements = __describe__( __subject__ )
  # Write the 2nd part of the error message.
  part2 = CONSTRUCT.( subject, prefix: ' ' )
  # Construct the descriptive string of the main subject.
  subject_description = statements.map { |verb, object|
    # Generate the statement string.
    STATE[ verb ] % object
  }.join ', ' # join the statement strings with commas
  # Write the third part of the error message.
  part3 = CONSTRUCT.( subject_description,
                     prefix: ' (', postfix: ')' )
  # Write the fourth part of the error message.
  part4 = CONSTRUCT.( __circumstances__, prefix: ', ' )
  # Write the fifth part of the error message.
  part5 = ": #{error}"
  part6 = ['.', '!', '?'].include?( part5[-1] ) ? '' : '!'
  return [ part1, part2, part3, part4, part5, part6 ].join
end

#__run__(*args) ⇒ Object

Runs the attempt.



135
136
137
138
139
140
141
142
# File 'lib/y_support/literate.rb', line 135

def __run__ *args
  begin
    instance_exec *args, &__block__
  rescue ::StandardError => error
    # Error has occured. Show time for Literate::Attempt.
    raise error, __error_message__( error )
  end
end

#note(*subjects, **statements, &block) ⇒ Object Also known as: »

Method #note is available inside the #try block

note "Concatenation of Foo and Bar",
     is: "FooBar",
     has: "6 letters"


98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/y_support/literate.rb', line 98

def note *subjects, **statements, &block
  if statements.empty? then
    # No statements were supplied to #note.
    subjects.each { |subject|
      # Fill in the knowledge base ...
      # FIXME: I wonder how the code here works.
      __knowledge_base__[ subject ].push_ordered subject
    }
    # FIXME: I wonder whether returning this is OK.
    return subjects
  end
  # Here, we know that variable statements is not empty.
  # If subjects variable is empty, assume main subject.
  subjects << __subject__ if subjects.empty?
  # Fill the knowledge base ...
  # FIXME: I wonder how the code here works.
  subjects.each { |subject|
    statements.each do |verb, object|
      __knowledge_base__[ subject ].push_named verb => object
    end
  }
  # Return the second element of the first statement.
  return statements.first[ 1 ]
end

#try(*args, &block) ⇒ Object

Inside the block, +#try method is delegated to the subject of the attempt.



175
176
177
# File 'lib/y_support/literate.rb', line 175

def try *args, &block
  __subject__.try *args, &block
end