module LineCache

module LineCache

A module to read and cache lines of a Ruby program.

Constants

LineCacheInfo

Public Class Methods

cache(filename, reload_on_change=false) click to toggle source

Cache filename if it's not already cached. Return the expanded filename for it in the cache or nil if we can't find the file.

# File lib/linecache19.rb, line 143
def cache(filename, reload_on_change=false)
  if @@file_cache.member?(filename)
    checkcache(filename) if reload_on_change
  else
    update_cache(filename, true)
  end
  if @@file_cache.member?(filename)
    @@file_cache[filename].path
  else
    nil
  end
end
cached?(filename) click to toggle source

Return true if filename is cached

# File lib/linecache19.rb, line 158
def cached?(filename)
  @@file_cache.member?(unmap_file(filename))
end
cached_files() click to toggle source

Return an array of cached file names

# File lib/linecache19.rb, line 101
def cached_files()
  @@file_cache.keys
end
cached_script?(filename) click to toggle source
# File lib/linecache19.rb, line 163
def cached_script?(filename)
  # In 1.8.6, the SCRIPT_LINES__ filename key can be unqualified
  # In 1.9.1 it's the fully qualified name
  if RUBY_VERSION < "1.9"
    SCRIPT_LINES__.member?(unmap_file(filename))
  else
    SCRIPT_LINES__.member?(File.expand_path(unmap_file(filename)))
  end
end
checkcache(filename=nil, use_script_lines=false) click to toggle source

Discard cache entries that are out of date. If filename is nil all entries in the file cache +@@file_cache+ are checked. If we don't have stat information about a file, which can happen if the file was read from SCRIPT_LINES__ but no corresponding file is found, it will be kept. Return a list of invalidated filenames. nil is returned if a filename was given but not found cached.

# File lib/linecache19.rb, line 112
def checkcache(filename=nil, use_script_lines=false)

  if !filename
    filenames = @@file_cache.keys()
  elsif @@file_cache.member?(filename)
    filenames = [filename]
  else
    return nil
  end

  result = []
  for filename in filenames
    next unless @@file_cache.member?(filename)
    path = @@file_cache[filename].path
    if File.exist?(path)
      cache_info = @@file_cache[filename].stat
      stat = File.stat(path)
      if stat &&
          (cache_info.size != stat.size or cache_info.mtime != stat.mtime)
        result << filename
        update_cache(filename, use_script_lines)
      end
    end
  end
  return result
end
clear_file_cache() click to toggle source

Clear the file cache entirely.

# File lib/linecache19.rb, line 93
def clear_file_cache()
  @@file_cache = {}
  @@file2file_remap = {}
  @@file2file_remap_lines = {}
end
empty?(filename) click to toggle source
# File lib/linecache19.rb, line 174
def empty?(filename)
  filename=unmap_file(filename)
  @@file_cache[filename].lines.empty?
end
getline(filename, line_number, reload_on_change=true) click to toggle source

Get line line_number from file named filename. Return nil if there was a problem. If a file named filename is not found, the function will look for it in the $: array.

Examples:

lines = LineCache::getline('/tmp/myfile.rb')
# Same as above
$: << '/tmp'
lines = LineCache.getlines('myfile.rb')
# File lib/linecache19.rb, line 191
def getline(filename, line_number, reload_on_change=true)
  filename = unmap_file(filename)
  filename, line_number = unmap_file_line(filename, line_number)
  lines = getlines(filename, reload_on_change)
  if lines and (1..lines.size) === line_number
      return lines[line_number-1]
  else
      return nil
  end
end
getlines(filename, reload_on_change=false) click to toggle source

Read lines of filename and cache the results. However filename was previously cached use the results from the cache. Return nil if we can't get lines

# File lib/linecache19.rb, line 206
def getlines(filename, reload_on_change=false)
  filename = unmap_file(filename)
  checkcache(filename) if reload_on_change
  if @@file_cache.member?(filename)
    return @@file_cache[filename].lines
  else
    update_cache(filename, true)
    return @@file_cache[filename].lines if @@file_cache.member?(filename)
  end
end
path(filename) click to toggle source

Return full filename path for filename

# File lib/linecache19.rb, line 219
def path(filename)
  filename = unmap_file(filename)
  return nil unless @@file_cache.member?(filename)
  @@file_cache[filename].path
end
remap_file(from_file, to_file) click to toggle source
# File lib/linecache19.rb, line 226
def remap_file(from_file, to_file)
  @@file2file_remap[to_file] = from_file
end
remap_file_lines(from_file, to_file, range, start) click to toggle source
# File lib/linecache19.rb, line 231
def remap_file_lines(from_file, to_file, range, start)
  range = (range..range) if range.is_a?(Fixnum)
  to_file = from_file unless to_file
  if @@file2file_remap_lines[to_file]
    # FIXME: need to check for overwriting ranges: whether
    # they intersect or one encompasses another.
    @@file2file_remap_lines[to_file] << [from_file, range, start]
  else
    @@file2file_remap_lines[to_file]  = [[from_file, range, start]]
  end
end
sha1(filename) click to toggle source

Return SHA1 of filename.

# File lib/linecache19.rb, line 245
def sha1(filename)
  filename = unmap_file(filename)
  return nil unless @@file_cache.member?(filename)
  return @@file_cache[filename].sha1.hexdigest if
    @@file_cache[filename].sha1
  sha1 = Digest::SHA1.new
  @@file_cache[filename].lines.each do |line|
    sha1 << line
  end
  @@file_cache[filename].sha1 = sha1
  sha1.hexdigest
end
size(filename) click to toggle source

Return the number of lines in filename

# File lib/linecache19.rb, line 260
def size(filename)
  filename = unmap_file(filename)
  return nil unless @@file_cache.member?(filename)
  @@file_cache[filename].lines.length
end
stat(filename) click to toggle source

Return File.stat in the cache for filename.

# File lib/linecache19.rb, line 268
def stat(filename)
  return nil unless @@file_cache.member?(filename)
  @@file_cache[filename].stat
end
trace_line_numbers(filename, reload_on_change=false) click to toggle source

Return an Array of breakpoints in filename. The list will contain an entry for each distinct line event call so it is possible (and possibly useful) for a line number appear more than once.

# File lib/linecache19.rb, line 278
def trace_line_numbers(filename, reload_on_change=false)
  fullname = cache(filename, reload_on_change)
  return nil unless fullname
  e = @@file_cache[filename]
  unless e.line_numbers
    e.line_numbers =
      TraceLineNumbers.lnums_for_str_array(e.lines)
    e.line_numbers = false unless e.line_numbers
  end
  e.line_numbers
end
unmap_file(file) click to toggle source
# File lib/linecache19.rb, line 291
def unmap_file(file)
  @@file2file_remap[file] ? @@file2file_remap[file] : file
end
unmap_file_line(file, line) click to toggle source
# File lib/linecache19.rb, line 296
def unmap_file_line(file, line)
  if @@file2file_remap_lines[file]
    @@file2file_remap_lines[file].each do |from_file, range, start|
      if range === line
        from_file = from_file || file
        return [from_file, start+line-range.begin]
      end
    end
  end
  return [file, line]
end
update_cache(filename, use_script_lines=false) click to toggle source

Update a cache entry. If something's wrong, return nil. Return true if the cache was updated and false if not. If use_script_lines is true, use that as the source for the lines of the file

# File lib/linecache19.rb, line 313
def update_cache(filename, use_script_lines=false)

  return nil unless filename

  @@file_cache.delete(filename)
  path = File.expand_path(filename)

  if use_script_lines
    list = [filename]
    list << @@file2file_remap[path] if @@file2file_remap[path]
    list.each do |name|
      if !SCRIPT_LINES__[name].nil? && SCRIPT_LINES__[name] != true
        begin
          stat = File.stat(name)
        rescue
          stat = nil
        end
        lines = SCRIPT_LINES__[name]
        if "ruby19".respond_to?(:force_encoding)
          lines.each{|l| l.force_encoding(Encoding.default_external) }
        end
        @@file_cache[filename] = LineCacheInfo.new(stat, nil, lines, path, nil)
        @@file2file_remap[path] = filename
        return true
      end
    end
  end

  if File.exist?(path)
    stat = File.stat(path)
  elsif File.basename(filename) == filename
    # try looking through the search path.
    stat = nil
    for dirname in $:
      path = File.join(dirname, filename)
      if File.exist?(path)
          stat = File.stat(path)
          break
      end
    end
    return false unless stat
  end
  begin
    fp = File.open(path, 'r')
    lines = fp.readlines()
    fp.close()
  rescue
    ##  print '*** cannot open', path, ':', msg
    return nil
  end
  @@file_cache[filename] = LineCacheInfo.new(File.stat(path), nil, lines,
                                             path, nil)
  @@file2file_remap[path] = filename
  return true
end

Private Instance Methods

cache(filename, reload_on_change=false) click to toggle source

Cache filename if it's not already cached. Return the expanded filename for it in the cache or nil if we can't find the file.

# File lib/linecache19.rb, line 143
def cache(filename, reload_on_change=false)
  if @@file_cache.member?(filename)
    checkcache(filename) if reload_on_change
  else
    update_cache(filename, true)
  end
  if @@file_cache.member?(filename)
    @@file_cache[filename].path
  else
    nil
  end
end
cached?(filename) click to toggle source

Return true if filename is cached

# File lib/linecache19.rb, line 158
def cached?(filename)
  @@file_cache.member?(unmap_file(filename))
end
cached_files() click to toggle source

Return an array of cached file names

# File lib/linecache19.rb, line 101
def cached_files()
  @@file_cache.keys
end
cached_script?(filename) click to toggle source
# File lib/linecache19.rb, line 163
def cached_script?(filename)
  # In 1.8.6, the SCRIPT_LINES__ filename key can be unqualified
  # In 1.9.1 it's the fully qualified name
  if RUBY_VERSION < "1.9"
    SCRIPT_LINES__.member?(unmap_file(filename))
  else
    SCRIPT_LINES__.member?(File.expand_path(unmap_file(filename)))
  end
end
checkcache(filename=nil, use_script_lines=false) click to toggle source

Discard cache entries that are out of date. If filename is nil all entries in the file cache +@@file_cache+ are checked. If we don't have stat information about a file, which can happen if the file was read from SCRIPT_LINES__ but no corresponding file is found, it will be kept. Return a list of invalidated filenames. nil is returned if a filename was given but not found cached.

# File lib/linecache19.rb, line 112
def checkcache(filename=nil, use_script_lines=false)

  if !filename
    filenames = @@file_cache.keys()
  elsif @@file_cache.member?(filename)
    filenames = [filename]
  else
    return nil
  end

  result = []
  for filename in filenames
    next unless @@file_cache.member?(filename)
    path = @@file_cache[filename].path
    if File.exist?(path)
      cache_info = @@file_cache[filename].stat
      stat = File.stat(path)
      if stat &&
          (cache_info.size != stat.size or cache_info.mtime != stat.mtime)
        result << filename
        update_cache(filename, use_script_lines)
      end
    end
  end
  return result
end
clear_file_cache() click to toggle source

Clear the file cache entirely.

# File lib/linecache19.rb, line 93
def clear_file_cache()
  @@file_cache = {}
  @@file2file_remap = {}
  @@file2file_remap_lines = {}
end
empty?(filename) click to toggle source
# File lib/linecache19.rb, line 174
def empty?(filename)
  filename=unmap_file(filename)
  @@file_cache[filename].lines.empty?
end
getline(filename, line_number, reload_on_change=true) click to toggle source

Get line line_number from file named filename. Return nil if there was a problem. If a file named filename is not found, the function will look for it in the $: array.

Examples:

lines = LineCache::getline('/tmp/myfile.rb')
# Same as above
$: << '/tmp'
lines = LineCache.getlines('myfile.rb')
# File lib/linecache19.rb, line 191
def getline(filename, line_number, reload_on_change=true)
  filename = unmap_file(filename)
  filename, line_number = unmap_file_line(filename, line_number)
  lines = getlines(filename, reload_on_change)
  if lines and (1..lines.size) === line_number
      return lines[line_number-1]
  else
      return nil
  end
end
getlines(filename, reload_on_change=false) click to toggle source

Read lines of filename and cache the results. However filename was previously cached use the results from the cache. Return nil if we can't get lines

# File lib/linecache19.rb, line 206
def getlines(filename, reload_on_change=false)
  filename = unmap_file(filename)
  checkcache(filename) if reload_on_change
  if @@file_cache.member?(filename)
    return @@file_cache[filename].lines
  else
    update_cache(filename, true)
    return @@file_cache[filename].lines if @@file_cache.member?(filename)
  end
end
path(filename) click to toggle source

Return full filename path for filename

# File lib/linecache19.rb, line 219
def path(filename)
  filename = unmap_file(filename)
  return nil unless @@file_cache.member?(filename)
  @@file_cache[filename].path
end
remap_file(from_file, to_file) click to toggle source
# File lib/linecache19.rb, line 226
def remap_file(from_file, to_file)
  @@file2file_remap[to_file] = from_file
end
remap_file_lines(from_file, to_file, range, start) click to toggle source
# File lib/linecache19.rb, line 231
def remap_file_lines(from_file, to_file, range, start)
  range = (range..range) if range.is_a?(Fixnum)
  to_file = from_file unless to_file
  if @@file2file_remap_lines[to_file]
    # FIXME: need to check for overwriting ranges: whether
    # they intersect or one encompasses another.
    @@file2file_remap_lines[to_file] << [from_file, range, start]
  else
    @@file2file_remap_lines[to_file]  = [[from_file, range, start]]
  end
end
sha1(filename) click to toggle source

Return SHA1 of filename.

# File lib/linecache19.rb, line 245
def sha1(filename)
  filename = unmap_file(filename)
  return nil unless @@file_cache.member?(filename)
  return @@file_cache[filename].sha1.hexdigest if
    @@file_cache[filename].sha1
  sha1 = Digest::SHA1.new
  @@file_cache[filename].lines.each do |line|
    sha1 << line
  end
  @@file_cache[filename].sha1 = sha1
  sha1.hexdigest
end
size(filename) click to toggle source

Return the number of lines in filename

# File lib/linecache19.rb, line 260
def size(filename)
  filename = unmap_file(filename)
  return nil unless @@file_cache.member?(filename)
  @@file_cache[filename].lines.length
end
stat(filename) click to toggle source

Return File.stat in the cache for filename.

# File lib/linecache19.rb, line 268
def stat(filename)
  return nil unless @@file_cache.member?(filename)
  @@file_cache[filename].stat
end
trace_line_numbers(filename, reload_on_change=false) click to toggle source

Return an Array of breakpoints in filename. The list will contain an entry for each distinct line event call so it is possible (and possibly useful) for a line number appear more than once.

# File lib/linecache19.rb, line 278
def trace_line_numbers(filename, reload_on_change=false)
  fullname = cache(filename, reload_on_change)
  return nil unless fullname
  e = @@file_cache[filename]
  unless e.line_numbers
    e.line_numbers =
      TraceLineNumbers.lnums_for_str_array(e.lines)
    e.line_numbers = false unless e.line_numbers
  end
  e.line_numbers
end
unmap_file(file) click to toggle source
# File lib/linecache19.rb, line 291
def unmap_file(file)
  @@file2file_remap[file] ? @@file2file_remap[file] : file
end
unmap_file_line(file, line) click to toggle source
# File lib/linecache19.rb, line 296
def unmap_file_line(file, line)
  if @@file2file_remap_lines[file]
    @@file2file_remap_lines[file].each do |from_file, range, start|
      if range === line
        from_file = from_file || file
        return [from_file, start+line-range.begin]
      end
    end
  end
  return [file, line]
end
update_cache(filename, use_script_lines=false) click to toggle source

Update a cache entry. If something's wrong, return nil. Return true if the cache was updated and false if not. If use_script_lines is true, use that as the source for the lines of the file

# File lib/linecache19.rb, line 313
def update_cache(filename, use_script_lines=false)

  return nil unless filename

  @@file_cache.delete(filename)
  path = File.expand_path(filename)

  if use_script_lines
    list = [filename]
    list << @@file2file_remap[path] if @@file2file_remap[path]
    list.each do |name|
      if !SCRIPT_LINES__[name].nil? && SCRIPT_LINES__[name] != true
        begin
          stat = File.stat(name)
        rescue
          stat = nil
        end
        lines = SCRIPT_LINES__[name]
        if "ruby19".respond_to?(:force_encoding)
          lines.each{|l| l.force_encoding(Encoding.default_external) }
        end
        @@file_cache[filename] = LineCacheInfo.new(stat, nil, lines, path, nil)
        @@file2file_remap[path] = filename
        return true
      end
    end
  end

  if File.exist?(path)
    stat = File.stat(path)
  elsif File.basename(filename) == filename
    # try looking through the search path.
    stat = nil
    for dirname in $:
      path = File.join(dirname, filename)
      if File.exist?(path)
          stat = File.stat(path)
          break
      end
    end
    return false unless stat
  end
  begin
    fp = File.open(path, 'r')
    lines = fp.readlines()
    fp.close()
  rescue
    ##  print '*** cannot open', path, ':', msg
    return nil
  end
  @@file_cache[filename] = LineCacheInfo.new(File.stat(path), nil, lines,
                                             path, nil)
  @@file2file_remap[path] = filename
  return true
end