class Grizzled::FileUtil::Includer

Introduction

An Includer object preprocesses a text file, resolve include references. The Includer is an Enumerable, allowing iteration over the lines of the resulting expanded file.

Include Syntax

The include syntax is defined by a regular expression; any line that matches the regular expression is treated as an include directive. The default regular expression matches include directives

like this

%include “/absolute/path/to/file” %include “../relative/path/to/file” %include “local_reference” %include “localhost/path/to/my.config

Relative and local file references are relative to the including file or URL. That, if an Includer is processing file “/home/bmc/foo.txt” and encounters an attempt to include file “bar.txt”, it will assume “bar.txt” is to be found in “/home/bmc”.

Similarly, if an Includer is processing URL “localhost/bmc/foo.txt” and encounters an attempt to include file “bar.txt”, it will assume “bar.txt” is to be found at “localhost/bmc/bar.txt”.

Nested includes are permitted; that is, an included file may, itself, include other files. The maximum recursion level is configurable and defaults to 100.

The include syntax can be changed by passing a different regular expression to the Includer class constructor.

Supported Methods

Includer supports all the methods of the File class and can be used the same way as a File object is used.

Examples

Preprocess a file containing include directives, then read the result:

require 'grizzled/fileutil/includer'
include Grizzled::FileUtil

inc = Includer.new(path)
inc.each do |line|
  puts(line)
end

Attributes

max_nesting[R]
name[R]

Public Class Methods

new(source, options={}) click to toggle source

Initialize a new Includer.

Parameters:

source

A string, representing a file name or URL (http, https or ftp), a File object, or an object with an each_line method that returns individual lines of input.

options

Various processing options. See below.

Options:

NOTE: Options are symbols (e.g., :recursive).

max_nesting

Maximum include nesting level. Default: 100

include_pattern

String regex pattern to match include directives. Must have a single regex group for the file name or URL. Default: ^%includes“(+)”

allow_glob

For file names, allow and expand glob expressions. Doesn’t apply to URLs.

sort_glob: true to force a sort of the globbed expression

(default), false not to sort.
# File lib/grizzled/fileutil/includer.rb, line 179
def initialize(source, options={})
  @max_nesting = options.fetch(:max_nesting, 100)
  inc_pattern = options.fetch(:include_pattern, '^%include\s"([^"]+)"')
  @include_re = /#{inc_pattern}/
  @allow_glob = options.fetch(:allow_glob, false)
  @sort_glob = options.fetch(:sort_glob, true)
  includer_source = source_to_includer_source source
  @source_uri = includer_source.uri
  @temp = preprocess includer_source
  @input = File.open @temp.path
  forward_to @input
end

Public Instance Methods

close() click to toggle source

Force the underlying resource to be closed.

# File lib/grizzled/fileutil/includer.rb, line 200
def close
  @input.close
  @temp.unlink
end
path() click to toggle source

Return the path of the original include file, if defined. If the original source was a URL, the URL is returned. If the source was a string, nil is returned.

# File lib/grizzled/fileutil/includer.rb, line 195
def path
  @source_uri.path
end

Private Instance Methods

do_read(input, temp, level) click to toggle source
# File lib/grizzled/fileutil/includer.rb, line 221
def do_read(input, temp, level)
  input.reader.each_line do |line|
    if m = @include_re.match(line)
      if level >= @max_nesting
        raise IncludeException.new("Too many nested includes " +
                                   "(#{level.to_s}), at: " +
                                   "\"#{line.chomp}\"")
      end
      new_input = process_include(m[1], input)
      do_read(new_input, temp, level + 1)
    else
      temp.write(line)
    end
  end
end
open_path(path) click to toggle source
# File lib/grizzled/fileutil/includer.rb, line 289
def open_path(path)
  if @allow_glob
    globs = Dir.glob(path)
    globs.sort! if @sort_glob
    FileIterator.new(globs)
  else
    File.open(path)
  end
end
open_source(uri, source) click to toggle source

Open an input source, based on a parsed URI.

# File lib/grizzled/fileutil/includer.rb, line 275
def open_source(uri, source)
  case uri.scheme
    when nil then     f = open_path(source)  # assume file/path
    when 'file' then  f = open(uri.path)  # open path directly
    when 'http' then  f = open(uri.to_s)  # open-uri will handle it
    when 'https' then f = open(uri.to_s)  # open-uri will handle it
    when 'ftp' then   f = open(uri.to_s)  # open-uri will handle it

    else raise IncludeException.new("Don't know how to open #{uri.to_s}")
  end

  IncludeSource.new(f, uri)
end
preprocess(includer_source) click to toggle source
# File lib/grizzled/fileutil/includer.rb, line 219
def preprocess(includer_source)

  def do_read(input, temp, level)
    input.reader.each_line do |line|
      if m = @include_re.match(line)
        if level >= @max_nesting
          raise IncludeException.new("Too many nested includes " +
                                     "(#{level.to_s}), at: " +
                                     "\"#{line.chomp}\"")
        end
        new_input = process_include(m[1], input)
        do_read(new_input, temp, level + 1)
      else
        temp.write(line)
      end
    end
  end


  temp = Tempfile.new('grizzled_includer')
  begin
    do_read(includer_source, temp, 1)
  ensure
    temp.close
  end

  temp
end
process_include(source, parent_input) click to toggle source

Handle an include reference.

# File lib/grizzled/fileutil/includer.rb, line 249
def process_include(source, parent_input)
  cur_uri = parent_input.uri
  begin
    uri = URI::parse(source)
  rescue URI::InvalidURIError
    uri = FakeURI.new(source)
  end
  if (cur_uri != nil) and (uri.scheme == nil)
    # Could be a relative path. Should be relative to the parent input.
    pathname = Pathname.new(source)
    if not pathname.absolute?
      # Not an absolute path, and the including source has a path
      # (i.e., wasn't a string). Make this one relative to the path.
      parent_path = cur_uri.path
      abs = File.absolute_path(
        File.join(::File.dirname(cur_uri.path), source)
      )
      uri = FakeURI.new(abs)
      source = abs
    end
  end

  open_source(uri, source)
end
source_to_includer_source(source) click to toggle source
# File lib/grizzled/fileutil/includer.rb, line 207
def source_to_includer_source(source)
  if source.class == String
    open_source(URI::parse(source), source)
  elsif source.class == File
    open_source(URI::parse(source.path), source.path)
  elsif source.respond_to? :each_line
    IncludeSource.new(source, nil)
  else
    raise IncludeException.new("Bad input of class #{source.class}")
  end
end