Class: CachedNestedFileReader

Inherits:
Object
  • Object
show all
Includes:
Exceptions
Defined in:
lib/cached_nested_file_reader.rb

Overview

The CachedNestedFileReader class provides functionality to read file lines with the ability to process ‘#import filename’ directives. When such a directive is encountered in a file, the corresponding ‘filename’ is read and its contents are inserted at that location. This class caches read files to avoid re-reading the same file multiple times. It allows clients to read lines with or without providing a block.

Instance Method Summary collapse

Methods included from Exceptions

error_handler, warn_format

Constructor Details

#initialize(import_directive_line_pattern:, import_directive_parameter_scan:, import_parameter_variable_assignment:, shell:, shell_block_name:, symbol_command_substitution:, symbol_evaluated_expression:, symbol_force_quoted_literal:, symbol_raw_literal:, symbol_variable_reference:) ⇒ CachedNestedFileReader

Returns a new instance of CachedNestedFileReader.



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/cached_nested_file_reader.rb', line 26

def initialize(
  import_directive_line_pattern:,
  import_directive_parameter_scan:,
  import_parameter_variable_assignment:,
  shell:,
  shell_block_name:,
  symbol_command_substitution:,
  symbol_evaluated_expression:,
  symbol_force_quoted_literal:,
  symbol_raw_literal:,
  symbol_variable_reference:
)
  @file_cache = {}
  @import_directive_line_pattern = import_directive_line_pattern
  @import_directive_parameter_scan = import_directive_parameter_scan
  @import_parameter_variable_assignment = import_parameter_variable_assignment
  @shell = shell
  @shell_block_name = shell_block_name
  @symbol_command_substitution = symbol_command_substitution
  @symbol_evaluated_expression = symbol_evaluated_expression
  @symbol_force_quoted_literal = symbol_force_quoted_literal
  @symbol_raw_literal = symbol_raw_literal
  @symbol_variable_reference = symbol_variable_reference
end

Instance Method Details

#error_handler(name = '', opts = {}) ⇒ Object



51
52
53
54
55
56
# File 'lib/cached_nested_file_reader.rb', line 51

def error_handler(name = '', opts = {})
  Exceptions.error_handler(
    "CachedNestedFileReader.#{name} -- #{$!}",
    opts
  )
end

#readlines(filename, depth = 0, context: '', import_paths: nil, indention: '', substitutions: {}, use_template_delimiters: false, clear_cache: true, read_cache: false, &block) ⇒ Object

yield each line to the block return the processed lines



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/cached_nested_file_reader.rb', line 67

def readlines(
  filename, depth = 0, context: '', import_paths: nil,
  indention: '', substitutions: {}, use_template_delimiters: false,
  clear_cache: true,
  read_cache: false,
  &block
)
  # clear cache if requested
  @file_cache.clear if clear_cache

  cache_key = build_cache_key(filename, substitutions)
  if @file_cache.key?(cache_key)
    return ["# dup #{cache_key}"] unless read_cache

    @file_cache[cache_key].each(&block) if block
    return @file_cache[cache_key]

    # do not return duplicates per filename and substitutions
    # return an indicator that the file was already read

  end
  raise Errno::ENOENT, filename unless filename

  directory_path = File.dirname(filename)
  processed_lines = []
  continued_line = nil # continued import directive
  File.readlines(filename, chomp: true).each.with_index do |segment, ind|
    wwt :readline, 'depth:', depth, 'filename:', filename, 'ind:', ind,
        'segment:', segment

    if continued_line || (Regexp.new(@import_directive_line_pattern) =~ segment)
      line = (continued_line || '') + segment
      # if segment ends in a continuation, prepend to next line
      if line.end_with?('\\')
        continued_line = line.chomp('\\')
        next
      end

      continued_line = nil

      # apply substitutions to the @import line
      line_sub1 = apply_line_substitutions(line, substitutions,
                                                  use_template_delimiters)

      # parse the @import line
      Regexp.new(@import_directive_line_pattern) =~ line_sub1
      name_strip = $~[:name].strip
      params_string = $~[:params] || ''
      import_indention = indention + $~[:indention]
      # Parse parameters for text substitution
      import_substitutions, add_code = parse_import_params(params_string)

      if add_code
        # strings as NestedLines
        add_lines = add_code.map.with_index do |line2, ind2|
          nested_line = NestedLine.new(
            line2,
            depth + 1,
            import_indention,
            filename,
            ind2
          )
          block&.call(nested_line)

          nested_line
        end
        ww 'add_lines:', add_lines
        processed_lines += add_lines
      end
      merged_substitutions = substitutions.merge(import_substitutions)

      included_file_path =
        if name_strip =~ %r{^/}
          name_strip
        elsif import_paths
          find_files(name_strip,
                     import_paths + [directory_path])&.first
        else
          File.join(directory_path, name_strip)
        end

      raise Errno::ENOENT, name_strip unless included_file_path

      # Create a cache key for the imported file that includes both filename and parameters
      imported_cache_key = build_import_cache_key(included_file_path,
                                                  name_strip, params_string, merged_substitutions)

      # Check if we've already loaded this specific import
      if @file_cache.key?(imported_cache_key)
        imported_lines = @file_cache[imported_cache_key]
      else
        imported_lines = readlines(
          included_file_path, depth + 1,
          context: "#{filename}:#{ind + 1}",
          import_paths: import_paths,
          indention: import_indention,
          substitutions: merged_substitutions,
          use_template_delimiters: use_template_delimiters,
          clear_cache: false,
          &block
        )

        # Cache the imported lines with the specific import cache key
        @file_cache[imported_cache_key] = imported_lines
      end

      # Apply text substitutions to imported content
      processed_imported_lines = apply_substitutions(
        imported_lines,
        import_substitutions, use_template_delimiters
      )
      processed_lines += processed_imported_lines
    else
      # Apply substitutions to the current line
      substituted_line = apply_line_substitutions(segment, substitutions,
                                                  use_template_delimiters)
      nested_line = NestedLine.new(substituted_line, depth, indention,
                                   filename, ind)
      processed_lines.push(nested_line)
      block&.call(nested_line)
    end
  end

  wwt :read_document_code, 'processed_lines:', processed_lines
  @file_cache[cache_key] = processed_lines
rescue Errno::ENOENT => err
  warn_format('readlines', "#{err} @@ #{context}",
              { abort: true })
rescue StandardError
  wwe $!
end

#warn_format(name, message, opts = {}) ⇒ Object



58
59
60
61
62
63
# File 'lib/cached_nested_file_reader.rb', line 58

def warn_format(name, message, opts = {})
  Exceptions.warn_format(
    "CachedNestedFileReader.#{name} -- #{message}",
    opts
  )
end