module LineCache

def cache(filename, reload_on_change=false)

or nil if we can't find the file.
Return the expanded filename for it in the cache
Cache filename if it's not already cached.
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

def cached?(filename)

Return true if filename is cached
def cached?(filename)
  @@file_cache.member?(unmap_file(filename))
end

def cached_files()

Return an array of cached file names
def cached_files()
  @@file_cache.keys
end

def cached_script?(filename)

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

def checkcache(filename=nil, use_script_lines=false)

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

def clear_file_cache()

Clear the file cache entirely.
def clear_file_cache()
  @@file_cache = {}
  @@file2file_remap = {}
  @@file2file_remap_lines = {}
end

def empty?(filename)

def empty?(filename)
  filename=unmap_file(filename)
  @@file_cache[filename].lines.empty?
end

def getline(filename, line_number, reload_on_change=true)


lines = LineCache.getlines('myfile.rb')
$: << '/tmp'
# Same as above
lines = LineCache::getline('/tmp/myfile.rb')

Examples:

function will look for it in the $: array.
there was a problem. If a file named filename is not found, the
Get line +line_number+ from file named +filename+. Return nil if
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

def getlines(filename, reload_on_change=false)

if we can't get lines
previously cached use the results from the cache. Return nil
Read lines of +filename+ and cache the results. However +filename+ was
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

def path(filename)

Return full filename path for filename
def path(filename)
  filename = unmap_file(filename)
  return nil unless @@file_cache.member?(filename)
  @@file_cache[filename].path
end

def remap_file(from_file, to_file)

def remap_file(from_file, to_file)
  @@file2file_remap[to_file] = from_file
end

def remap_file_lines(from_file, to_file, range, start)

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

def sha1(filename)

Return SHA1 of filename.
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

def size(filename)

Return the number of lines in filename
def size(filename)
  filename = unmap_file(filename)
  return nil unless @@file_cache.member?(filename)
  @@file_cache[filename].lines.length
end

def stat(filename)

Return File.stat in the cache for filename.
def stat(filename)
  return nil unless @@file_cache.member?(filename)
  @@file_cache[filename].stat
end

def trace_line_numbers(filename, reload_on_change=false)

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

def unmap_file(file)

def unmap_file(file)
  @@file2file_remap[file] ? @@file2file_remap[file] : file
end

def unmap_file_line(file, line)

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

def update_cache(filename, use_script_lines=false)

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