class RDoc::Generator::Tags

A TAGS file generator for vim-style tags (based on ctags.sourceforge.net/FORMAT) and emacs-style tags. Tags is compatible with Exuberant Ctags for merging tag definitions.

This file will be automatically loaded via rdoc/discover.rb. If you wish to load this standalone, require 'rdoc/rdoc' first.

Constants

VERSION

The version of the tags generator you are using

Attributes

ctags_merge[RW]

Merge with Exuberant Ctags if true

ctags_path[RW]

Path to Exuberant Ctags

tag_style[RW]

Output tag style. See Options::TAG_STYLES for allowed values

Public Class Methods

new(store, options) click to toggle source

Creates a new tags generator

# File lib/rdoc/generator/tags.rb, line 109
def initialize store, options
  @store = store
  @options = options

  @tag_style   = options.tag_style
  @ctags_merge = options.ctags_merge
  @ctags_path  = options.ctags_path
  @dry_run     = options.dry_run
end
setup_options(options) click to toggle source

Adds tags-generator options to the RDoc::Options instance options

# File lib/rdoc/generator/tags.rb, line 66
def self.setup_options options
  options.force_output = !File.exist?('TAGS')
  options.force_update = false
  options.op_dir = File.expand_path './.rdoc'
  options.update_output_dir = true

  options.extend Options

  options.tag_style = :vim

  op = options.option_parser

  op.separator nil
  op.separator 'tags generator options:'
  op.separator nil

  op.on('--[no-]ctags-merge',
        'Merge exuberant ctags with our own?',
        'Use this for projects with C extensions') do |value|
    options.ctags_merge = value
  end

  op.separator nil

  op.on('--ctags-path=PATH',
        'Path to Exuberant Ctags',
        'This will be auto-discovered from PATH') do |value|
    options.ctags_path = value
  end

  op.separator nil

  op.on('--tag-style=TAG_STYLE', Options::TAG_STYLES,
        'Which type of TAGS file to output') do |value|
    options.tag_style = value
  end

  op.separator nil
end

Public Instance Methods

find_ctags() click to toggle source

Finds the first Exuberant Ctags in ENV by checking ctags --version. Other implementations are ignored.

# File lib/rdoc/generator/tags.rb, line 123
def find_ctags
  require 'open3'

  ENV['PATH'].split(File::PATH_SEPARATOR).each do |dir|
    ctags = File.join dir, 'ctags'
    next unless File.exist? ctags

    # other ctags implementations write to stderr, silence them
    return ctags if Open3.popen3 ctags, '--version' do |_, out, _|
      out.gets =~ /^Exuberant Ctags/
    end
  end

  nil
end
generate() click to toggle source

Generates a TAGS file

# File lib/rdoc/generator/tags.rb, line 142
def generate
  @store.save unless @dry_run

  case @tag_style
  when 'vim',   :vim   then generate_vim
  when 'emacs', :emacs then generate_emacs
  else
    raise RDoc::Error, "Unkown tag style #{@tag_style.inspect}"
  end
end
generate_emacs() click to toggle source

Generates an emacs TAGS file

# File lib/rdoc/generator/tags.rb, line 156
def generate_emacs
  # file_name => [definition, tag_name, line_number, byte_offset]
  tags = Hash.new { |h, file| h[file] = [] }

  @store.all_files.each do |top_level|
    tags[top_level.relative_name] << ['', top_level.relative_name, 0, 0]
  end

  @store.all_classes_and_modules.each do |klass|
    klass.in_files.each do |file|
      tags[file.relative_name] << [klass.definition, klass.full_name, 0, 0]
    end

    klass.each_attribute do |attr|
      tags[attr.file.relative_name] <<
        ["#{attr.definition} :#{attr.name}", attr.name, 0, 0]
    end

    klass.each_constant do |constant|
      tags[constant.file.relative_name] <<
        [constant.name, constant.name, 0, 0]
    end

    klass.each_method do |method|
      definition = if method.singleton then
                     "def self.#{method.name}"
                   else
                     "def #{method.name}"
                   end

      tags[method.file.relative_name] << [definition, method.name, 0, 0]
    end
  end

  unless @dry_run then
    write_tags_emacs tags
    merge_ctags
  end
end
generate_vim() click to toggle source

Generates a vim TAGS file

# File lib/rdoc/generator/tags.rb, line 199
def generate_vim
  tags = Hash.new { |h, name| h[name] = [] }

  @store.all_files.each do |top_level|
    tags[top_level.relative_name] << [top_level.relative_name, 0, 'F']
  end

  @store.all_classes_and_modules.each do |klass|
    kind = "class:#{klass.full_name}"

    address =
      unless RDoc::TopLevel === klass.parent then
        "/#{klass.type} \\(#{klass.parent.full_name}::\\)\\?#{klass.name}/"
      else
        "/#{klass.type} #{klass.full_name}/"
      end

    klass.in_files.each do |file|
      tags[klass.full_name] << [file.relative_name, address, 'c']
      tags[klass.name]      << [file.relative_name, address, 'c']
    end

    klass.each_attribute do |attr|
      where = [
        attr.file.relative_name,
        "/attr\\w\\*\\s\\*\\[:'\"]#{attr.name}/",
        'f',
        kind
      ]

      tags[attr.name]       << where
      tags["#{attr.name}="] << where
    end

    klass.each_constant do |constant|
      tags[constant.name] << [
        constant.file.relative_name, "/#{constant.name}\\s\\*=/", 'd', kind]
    end

    klass.each_method do |method|
      address = if method.singleton then
                  # \w doesn't appear to work in [] with nomagic
                  "/def \\[A-Za-z0-9_:]\\+.#{method.name}/"
                else
                  "/def #{method.name}/"
                end

      tags[method.name] << [
        method.file.relative_name, address, 'f', kind]
    end
  end

  unless @dry_run then
    write_tags_vim tags
    merge_ctags
  end
end
merge_ctags() click to toggle source

Merges our tags with Exuberant Ctags' tags

# File lib/rdoc/generator/tags.rb, line 260
def merge_ctags
  return unless @ctags_merge

  ctags_path = @ctags_path || find_ctags

  ctags_args = [
    '--append=yes',
    '--format=2',
    '--languages=-Ruby',
    '--recurse=yes',
    *@options.files
  ]

  ctags_args.unshift '-e' if @tag_style == :emacs

  unless @options.quiet then
    puts
    puts 'Merging with Exuberant Ctags'
    puts "#{ctags_path} #{ctags_args.join ' '}"
  end

  Dir.chdir '..' do
    system ctags_path, *ctags_args
  end
end
write_tags_emacs(tags) click to toggle source

Writes the TAGS file in emacs style using the data in tags

# File lib/rdoc/generator/tags.rb, line 289
def write_tags_emacs tags
  open '../TAGS', 'wb' do |io|
    tags.sort.each do |file, definitions|
      section = []

      definitions.sort.each do |(definition, tag_name, line, offset)|
        section << "#{definition}\x7F#{tag_name}\x01#{line},#{offset}"
      end

      section = section.join "\n"

      io << "\x0C\n#{file},#{section.length}\n#{section}\n"
    end
  end
end
write_tags_vim(tags) click to toggle source

Writes the TAGS file in vim style using the data in tags

# File lib/rdoc/generator/tags.rb, line 308
  def write_tags_vim tags
    open '../TAGS', 'w' do |io|
      io.write <<-INFO
!_TAG_FILE_FORMAT\t2\t/extended format/
!_TAG_FILE_SORTED\t1\t/sorted/
!_TAG_PROGRAM_AUTHOR\tEric Hodel\t/drbrain@segment7.net/
!_TAG_PROGRAM_NAME\trdoc-tags\t//
!_TAG_PROGRAM_URL\thttps://github.com/rdoc/rdoc-tags\t//
!_TAG_PROGRAM_VERSION\t#{VERSION}\t//
      INFO

      tags.sort.each do |name, definitions|
        definitions = definitions.uniq

        definitions = definitions.sort_by do |(file, address,*_)|
          [file, address]
        end

        definitions.each do |(file, address, *field)|
          io.write "#{name}\t#{file}\t#{address};\"\t#{field.join "\t"}\n"
        end
      end
    end
  end