lib/yard/cli/yardoc.rb in yard-0.5.8 vs lib/yard/cli/yardoc.rb in yard-0.6.0
- old
+ new
@@ -1,11 +1,102 @@
require 'digest/sha1'
require 'fileutils'
module YARD
module CLI
- class Yardoc < Base
+ # Yardoc is the default YARD CLI command (+yard doc+ and historic +yardoc+
+ # executable) used to generate and output (mainly) HTML documentation given
+ # a set of source files.
+ #
+ # == Usage
+ #
+ # Main usage for this command is:
+ #
+ # $ yardoc [options] [source_files [- extra_files]]
+ #
+ # See +yardoc --help+ for details on valid options.
+ #
+ # == Options File (+.yardopts+)
+ #
+ # If a +.yardopts+ file is found in the source directory being processed,
+ # YARD will use the contents of the file as arguments to the command,
+ # treating newlines as spaces. You can use shell-style quotations to
+ # group space delimited arguments, just like on the command line.
+ #
+ # A valid +.yardopts+ file might look like:
+ #
+ # --no-private
+ # --title "My Title"
+ # --exclude foo --exclude bar
+ # lib/**/*.erb
+ # lib/**/*.rb -
+ # HACKING.rdoc LEGAL COPYRIGHT
+ #
+ # Note that Yardoc also supports the legacy RDoc style +.document+ file,
+ # though this file can only specify source globs to parse, not options.
+ #
+ # == Queries (+--query+)
+ #
+ # Yardoc supports queries to select specific code objects for which to
+ # generate documentation. For example, you might want to generate
+ # documentation only for your public API. If you've documented your public
+ # methods with +@api public+, you can use the following query to select
+ # all of these objects:
+ #
+ # --query '@api.text == "public"'
+ #
+ # Note that the syntax for queries is mostly Ruby with a few syntactic
+ # simplifications for meta-data tags. See the {Verifier} class for an
+ # overview of this syntax.
+ #
+ # == Adding Custom Ad-Hoc Meta-data Tags (+--tag+)
+ #
+ # YARD allows specification of {file:docs/Tags.md meta-data tags}
+ # programmatically via the {YARD::Tags::Library} class, but often this is not
+ # practical for users writing documentation. To make adding custom tags
+ # easier, Yardoc has a few command-line switches for creating basic tags
+ # and displaying them in generated HTML output.
+ #
+ # To specify a custom tag to be displayed in output, use any of the
+ # following:
+ #
+ # * +--tag+ TAG:TITLE
+ # * +--name-tag+ TAG:TITLE
+ # * +--type-tag+ TAG:TITLE
+ # * +--type-name-tag+ TAG:TITLE
+ # * +--title-tag+ TAG:TITLE
+ #
+ # "TAG:TITLE" is of the form: name:"Display Title", for example:
+ #
+ # --tag overload:"Overloaded Method"
+ #
+ # See +yardoc --help+ for a description of the various options.
+ #
+ # Tags added in this way are automatically displayed in output. To add
+ # a meta-data tag that does not show up in output, use +--hide-tag TAG+.
+ # Note that you can also use this option on existing tags to hide
+ # builtin tags, for instance.
+ #
+ # == Processed Data Storage (+.yardoc+ directory)
+ #
+ # When Yardoc parses a source directory, it creates a +.yardoc+ directory
+ # (by default, override with +-b+) at the root of the project. This directory
+ # contains marshal dumps for all raw object data in the source, so that
+ # you can access it later for various commands (+stats+, +graph+, etc.).
+ # This directory is also used as a cache for any future calls to +yardoc+
+ # so as to process only the files which have changed since the last call.
+ #
+ # When Yardoc uses the cache in subsequent calls to +yardoc+, methods
+ # or classes that have been deleted from source since the last parsing
+ # will not be erased from the cache (YARD never deletes objects). In such
+ # a case, you should wipe the cache and do a clean parsing of the source tree.
+ # You can do this by deleting the +.yardoc+ directory manually, or running
+ # Yardoc without +--use-cache+ (+-c+).
+ #
+ # @since 0.2.1
+ # @see Verifier
+ class Yardoc < Command
# The configuration filename to load extra options from
DEFAULT_YARDOPTS_FILE = ".yardopts"
# @return [Hash] the hash of options passed to the template.
# @see Templates::Engine#render
@@ -13,41 +104,54 @@
# @return [Array<String>] list of Ruby source files to process
attr_accessor :files
# @return [Array<String>] list of excluded paths (regexp matches)
+ # @since 0.5.3
attr_accessor :excluded
# @return [Boolean] whether to use the existing yardoc db if the
# .yardoc already exists. Also makes use of file checksums to
# parse only changed files.
attr_accessor :use_cache
- # @return [Boolean] whether to generate output incrementally (
- # implies use_cache and generate)
- attr_accessor :incremental
+ # @return [Boolean] whether to parse options from .yardopts
+ attr_accessor :use_yardopts_file
+ # @return [Boolean] whether to parse options from .document
+ attr_accessor :use_document_file
+
+ # @return [Boolean] whether objects should be serialized to .yardoc db
+ attr_accessor :save_yardoc
+
# @return [Boolean] whether to generate output
attr_accessor :generate
# @return [Boolean] whether to print a list of objects
+ # @since 0.5.5
attr_accessor :list
# The options file name (defaults to {DEFAULT_YARDOPTS_FILE})
# @return [String] the filename to load extra options from
attr_accessor :options_file
# Keep track of which visibilities are to be shown
# @return [Array<Symbol>] a list of visibilities
+ # @since 0.5.6
attr_accessor :visibilities
+
+ # @return [Array<Symbol>] a list of tags to hide from templates
+ # @since 0.6.0
+ attr_accessor :hidden_tags
- # @return [Boolean] whether to build or rebuild gems
- attr_accessor :build_gems, :rebuild_gems
+ # @return [Boolean] whether to print statistics after parsing
+ # @since 0.6.0
+ attr_accessor :statistics
- # Helper method to create an instance and run the utility
- # @see #run
- def self.run(*args) new.run(*args) end
+ # @return [Array<String>] a list of assets to copy after generation
+ # @since 0.6.0
+ attr_accessor :assets
# Creates a new instance of the commandline utility
def initialize
super
@options = SymbolHash.new(false)
@@ -61,116 +165,166 @@
:no_highlight => false,
:files => [],
:verifier => Verifier.new
)
@visibilities = [:public]
+ @assets = {}
@excluded = []
@files = []
+ @hidden_tags = []
@use_cache = false
- @build_gems = false
- @rebuild_gems = false
+ @use_yardopts_file = true
+ @use_document_file = true
@generate = true
- @incremental = false
@options_file = DEFAULT_YARDOPTS_FILE
+ @statistics = true
+ @list = false
+ @save_yardoc = true
+
+ if defined?(Encoding)
+ Encoding.default_external, Encoding.default_internal = 'utf-8', 'utf-8'
+ end
end
+ def description
+ "Generates documentation"
+ end
+
# Runs the commandline utility, parsing arguments and generating
# output if set.
#
# @param [Array<String>] args the list of arguments
# @return [void]
def run(*args)
parse_arguments(*args)
+ checksums = nil
if use_cache
Registry.load
checksums = Registry.checksums.dup
end
YARD.parse(files, excluded)
- Registry.save(use_cache)
+ Registry.save(use_cache) if save_yardoc
-
- if build_gems
- do_build_gems(rebuild_gems)
- elsif generate
- if incremental
- generate_with_cache(checksums)
- else
- Registry.load_all if use_cache
- Templates::Engine.generate(all_objects, options)
- end
+ if generate
+ run_generate(checksums)
+ copy_assets
elsif list
print_list
end
-
+
+ if !list && statistics && log.level < Logger::ERROR
+ Registry.load_all
+ log.enter_level(Logger::ERROR) do
+ Stats.new(false).run(*args)
+ end
+ end
+
true
end
# Parses commandline arguments
# @param [Array<String>] args the list of arguments
# @return [void]
+ # @since 0.5.6
def parse_arguments(*args)
- optparse(*support_rdoc_document_file!)
- optparse(*yardopts)
+ # Hack: parse out --no-yardopts, --no-document before parsing files
+ ['document', 'yardopts'].each do |file|
+ without, with = args.index("--no-#{file}") || 0, args.index("--#{file}") || 0
+ send("use_#{file}_file=", false) if without > with
+ end
+
+ # Parse files and then command line arguments
+ optparse(*support_rdoc_document_file!) if use_document_file
+ optparse(*yardopts) if use_yardopts_file
optparse(*args)
# Last minute modifications
self.files = ['lib/**/*.rb', 'ext/**/*.c'] if self.files.empty?
+ self.files.delete_if {|x| x =~ /\A\s*\Z/ } # remove empty ones
options[:readme] ||= Dir.glob('README*').first
+ if options[:onefile]
+ options[:files] << options[:readme] if options[:readme]
+ options[:readme] = Dir.glob(files.first).first
+ end
+ Tags::Library.visible_tags -= hidden_tags
add_visibility_verifier
end
# The list of all objects to process. Override this method to change
# which objects YARD should generate documentation for.
#
+ # @deprecated To hide methods use the +@private+ tag instead.
# @return [Array<CodeObjects::Base>] a list of code objects to process
def all_objects
Registry.all(:root, :module, :class)
end
# Parses the .yardopts file for default yard options
- # @return [void]
+ # @return [Array<String>] an array of options parsed from .yardopts
def yardopts
+ return [] unless use_yardopts_file
File.read_binary(options_file).shell_split
rescue Errno::ENOENT
[]
end
private
- # Generates output for changed objects in cache
+ # Generates output for objects
+ # @param [Hash, nil] checksums if supplied, a list of checkums for files.
# @return [void]
- def generate_with_cache(checksums)
- changed_files = []
- Registry.checksums.each do |file, hash|
- changed_files << file if checksums[file] != hash
+ # @since 0.5.1
+ def run_generate(checksums)
+ if checksums
+ changed_files = []
+ Registry.checksums.each do |file, hash|
+ changed_files << file if checksums[file] != hash
+ end
end
- Registry.load_all
- all_objects.each do |object|
- if object.files.any? {|f, line| changed_files.include?(f) }
+ Registry.load_all if use_cache
+ objects = run_verifier(all_objects).reject do |object|
+ serialized = !options[:serializer] || options[:serializer].exists?(object)
+ if checksums && serialized && !object.files.any? {|f, line| changed_files.include?(f) }
+ true
+ else
log.info "Re-generating object #{object.path}..."
- opts = options.merge(:object => object, :type => :layout)
- Templates::Engine.render(opts)
+ false
end
end
+ Templates::Engine.generate(objects, options)
end
+
+ # Copies any assets to the output directory
+ # @return [void]
+ # @since 0.6.0
+ def copy_assets
+ return unless options[:serializer]
+ outpath = options[:serializer].basepath
+ assets.each do |from, to|
+ to = File.join(outpath, to)
+ log.debug "Copying asset '#{from}' to '#{to}'"
+ FileUtils.cp_r(from, to)
+ end
+ end
# Prints a list of all objects
# @return [void]
+ # @since 0.5.5
def print_list
Registry.load_all
- Registry.all.
- reject {|item| options[:verifier].call(item).is_a?(FalseClass) }.
+ run_verifier(Registry.all).
sort_by {|item| [item.file, item.line]}.each do |item|
puts "#{item.file}:#{item.line}: #{item}"
end
end
# Reads a .document file in the directory to get source file globs
- # @return [void]
+ # @return [Array<String>] an array of files parsed from .document
def support_rdoc_document_file!
- IO.read(".document").gsub(/^[ \t]*#.+/m, '').split(/\s+/)
+ return [] unless use_document_file
+ File.read(".document").gsub(/^[ \t]*#.+/m, '').split(/\s+/)
rescue Errno::ENOENT
[]
end
# Adds a set of extra documentation files to be processed
@@ -210,44 +364,35 @@
self.files << file
end
end
end
- # Builds .yardoc files for all non-existing gems
- # @param [Boolean] rebuild Forces rebuild of all gems
- def do_build_gems(rebuild = false)
- require 'rubygems'
- Gem.source_index.find_name('').each do |spec|
- reload = true
- dir = Registry.yardoc_file_for_gem(spec.name)
- if dir && File.directory?(dir) && !rebuild
- log.debug "#{spec.name} index already exists at '#{dir}'"
- else
- yfile = Registry.yardoc_file_for_gem(spec.name, ">= 0", true)
- next unless yfile
- Registry.clear
- Dir.chdir(spec.full_gem_path)
- log.info "Building yardoc index for gem: #{spec.full_name}"
- Yardoc.run('-n', '-b', yfile)
- reload = false
- end
- end
- exit(0)
- end
-
# Adds verifier rule for visibilities
# @return [void]
+ # @since 0.5.6
def add_visibility_verifier
vis_expr = "object.type != :method || #{visibilities.uniq.inspect}.include?(object.visibility)"
options[:verifier].add_expressions(vis_expr)
end
+ # (see Templates::Helpers::BaseHelper#run_verifier)
+ def run_verifier(list)
+ options[:verifier] ? options[:verifier].run(list) : list
+ end
+
+ # @since 0.6.0
+ def add_tag(tag_data, factory_method = nil)
+ tag, title = *tag_data.split(':')
+ Tags::Library.define_tag(title, tag.to_sym, factory_method)
+ Tags::Library.visible_tags |= [tag.to_sym]
+ end
+
# Parses commandline options.
# @param [Array<String>] args each tokenized argument
def optparse(*args)
opts = OptionParser.new
- opts.banner = "Usage: yardoc [options] [source_files [- extra_files]]"
+ opts.banner = "Usage: yard doc [options] [source_files [- extra_files]]"
opts.separator "(if a list of source files is omitted, lib/**/*.rb ext/**/*.c is used.)"
opts.separator ""
opts.separator "Example: yardoc -o documentation/ - FAQ LICENSE"
opts.separator " The above example outputs documentation for files in"
@@ -255,98 +400,106 @@
opts.separator " FAQ and LICENSE."
opts.separator ""
opts.separator "A base set of options can be specified by adding a .yardopts"
opts.separator "file to your base path containing all extra options separated"
opts.separator "by whitespace."
+
+ general_options(opts)
+ output_options(opts)
+ tag_options(opts)
+ common_options(opts)
+ parse_options(opts, args)
+ parse_files(*args) unless args.empty?
+ end
+
+ # Adds general options
+ def general_options(opts)
opts.separator ""
opts.separator "General Options:"
- opts.on('-c', '--use-cache [FILE]',
- "Use the cached .yardoc db to generate documentation. (defaults to no cache)") do |file|
- YARD::Registry.yardoc_file = file if file
- self.use_cache = true
- end
-
opts.on('-b', '--db FILE', 'Use a specified .yardoc db to load from or save to. (defaults to .yardoc)') do |yfile|
YARD::Registry.yardoc_file = yfile
end
-
+
opts.on('-n', '--no-output', 'Only generate .yardoc database, no documentation.') do
self.generate = false
end
-
- opts.on('-e', '--load FILE', 'A Ruby script to load before the source tree is parsed.') do |file|
- if !require(file.gsub(/\.rb$/, ''))
- log.error "The file `#{file}' was already loaded, perhaps you need to specify the absolute path to avoid name collisions."
- exit
- end
- end
-
- opts.on('--incremental', 'Generates output for changed files only (implies -c)') do
- self.incremental = true
- self.generate = true
+
+ opts.on('-c', '--use-cache [FILE]',
+ "Use the cached .yardoc db to generate documentation. (defaults to no cache)") do |file|
+ YARD::Registry.yardoc_file = file if file
self.use_cache = true
end
- opts.on('--exclude REGEXP', 'Ignores a file if it matches path match (regexp)') do |path|
- self.excluded << path
+ opts.on('--no-cache', "Clear .yardoc db before parsing source.") do
+ self.use_cache = false
end
- opts.on('--legacy', 'Use old style parser and handlers. Unavailable under Ruby 1.8.x') do
- YARD::Parser::SourceParser.parser_type = :ruby18
+ opts.on('--[no-]yardopts', "If arguments should be read from .yardopts file. (defaults to yes)") do |use_yardopts|
+ self.use_yardopts_file = use_yardopts
end
+
+ opts.on('--[no-]document', "If arguments should be read from .document file. (defaults to yes)") do |use_document|
+ self.use_document_file = use_document
+ end
- opts.on('--build-gems', 'Builds .yardoc files for all gems (implies -n)') do
- self.build_gems = true
+ opts.on('--no-save', 'Do not save the parsed data to the yardoc db') do
+ self.save_yardoc = false
end
- opts.on('--re-build-gems', 'Forces building .yardoc files for all gems (implies -n)') do
- self.build_gems = true
- self.rebuild_gems = true
+ opts.on('--exclude REGEXP', 'Ignores a file if it matches path match (regexp)') do |path|
+ self.excluded << path
end
+ end
+ # Adds output options
+ def output_options(opts)
opts.separator ""
opts.separator "Output options:"
-
+
+ opts.on('--one-file', 'Generates output as a single file') do
+ options[:onefile] = true
+ end
+
+ opts.on('--list', 'List objects to standard out (implies -n)') do |format|
+ self.generate = false
+ self.list = true
+ end
+
opts.on('--no-public', "Don't show public methods. (default shows public)") do
visibilities.delete(:public)
end
- opts.on('--protected', "Show or don't show protected methods. (default hides protected)") do
+ opts.on('--protected', "Show protected methods. (default hides protected)") do
visibilities.push(:protected)
end
- opts.on('--private', "Show or don't show private methods. (default hides private)") do
+ opts.on('--private', "Show private methods. (default hides private)") do
visibilities.push(:private)
end
-
+
opts.on('--no-private', "Hide objects with @private tag") do
options[:verifier].add_expressions '!object.tag(:private) &&
(object.namespace.type == :proxy || !object.namespace.tag(:private))'
end
- opts.on('--no-highlight', "Don't highlight code in docs as Ruby.") do
+ opts.on('--no-highlight', "Don't highlight code blocks in output.") do
options[:no_highlight] = true
end
-
+
opts.on('--default-return TYPE', "Shown if method has no return type. Defaults to 'Object'") do |type|
options[:default_return] = type
end
-
+
opts.on('--hide-void-return', "Hides return types specified as 'void'. Default is shown.") do
options[:hide_void_return] = true
end
-
+
opts.on('--query QUERY', "Only show objects that match a specific query") do |query|
options[:verifier].add_expressions(query.taint)
end
- opts.on('--list', 'List objects to standard out (implies -n)') do |format|
- self.generate = false
- self.list = true
- end
-
opts.on('--title TITLE', 'Add a specific title to HTML documents') do |title|
options[:title] = title
end
opts.on('-r', '--readme FILE', '--main FILE', 'The readme file used as the title page of documentation.') do |readme|
@@ -354,33 +507,44 @@
options[:readme] = readme
else
log.warn "Could not find readme file: #{readme}"
end
end
-
+
opts.on('--files FILE1,FILE2,...', 'Any extra comma separated static files to be included (eg. FAQ)') do |files|
add_extra_files(*files.split(","))
end
+ opts.on('--asset FROM[:TO]', 'A file or directory to copy over to output directory after generating') do |asset|
+ re = /^(?:\.\.\/|\/)/
+ from, to = *asset.split(':').map {|f| File.cleanpath(f) }
+ to ||= from
+ if from =~ re || to =~ re
+ log.warn "Invalid file '#{asset}'"
+ else
+ assets[from] = to
+ end
+ end
+
+ opts.on('-o', '--output-dir PATH',
+ 'The output directory. (defaults to ./doc)') do |dir|
+ options[:serializer].basepath = dir
+ end
+
opts.on('-m', '--markup MARKUP',
'Markup style used in documentation, like textile, markdown or rdoc. (defaults to rdoc)') do |markup|
options[:markup] = markup.to_sym
end
opts.on('-M', '--markup-provider MARKUP_PROVIDER',
'Overrides the library used to process markup formatting (specify the gem name)') do |markup_provider|
options[:markup_provider] = markup_provider.to_sym
end
-
- opts.on('-o', '--output-dir PATH',
- 'The output directory. (defaults to ./doc)') do |dir|
- options[:serializer].basepath = dir
- end
-
+
opts.on('--charset ENC', 'Character set to use for HTML output (default is system locale)') do |encoding|
begin
- Encoding.default_external = encoding
+ Encoding.default_external, Encoding.default_internal = encoding, encoding
rescue ArgumentError => e
raise OptionParser::InvalidOption, e
end
end
@@ -391,18 +555,53 @@
opts.on('-p', '--template-path PATH',
'The template path to look for templates in. (used with -t).') do |path|
YARD::Templates::Engine.register_template_path(path)
end
-
+
opts.on('-f', '--format FORMAT',
'The output format for the template. (defaults to html)') do |format|
options[:format] = format.to_sym
end
+
+ opts.on('--no-stats', 'Don\'t print statistics') do
+ self.statistics = false
+ end
+ end
- common_options(opts)
- parse_options(opts, args)
- parse_files(*args) unless args.empty?
+ # Adds tag options
+ # @since 0.6.0
+ def tag_options(opts)
+ opts.separator ""
+ opts.separator "Tag options: (TAG:TITLE looks like: 'overload:Overloaded Method')"
+
+ opts.on('--tag TAG:TITLE', 'Registers a new free-form metadata @tag') do |tag|
+ add_tag(tag)
+ end
+
+ opts.on('--type-tag TAG:TITLE', 'Tag with an optional types field') do |tag|
+ add_tag(tag, :with_types)
+ end
+
+ opts.on('--type-name-tag TAG:TITLE', 'Tag with optional types and a name field') do |tag|
+ add_tag(tag, :with_types_and_name)
+ end
+
+ opts.on('--name-tag TAG:TITLE', 'Tag with a name field') do |tag|
+ add_tag(tag, :with_name)
+ end
+
+ opts.on('--title-tag TAG:TITLE', 'Tag with first line as title field') do |tag|
+ add_tag(tag, :with_title_and_text)
+ end
+
+ opts.on('--hide-tag TAG', 'Hides a previously defined tag from templates') do |tag|
+ self.hidden_tags |= [tag.to_sym]
+ end
+
+ opts.on('--transitive-tag TAG', 'Adds a transitive tag') do |tag|
+ Tags::Library.transitive_tags += [tag.to_sym]
+ end
end
end
end
end