commit
890190c0f3
2
.gitignore
vendored
2
.gitignore
vendored
@ -161,6 +161,8 @@
|
|||||||
**/vendor/bundle/ruby/*/gems/unparser-*/
|
**/vendor/bundle/ruby/*/gems/unparser-*/
|
||||||
**/vendor/bundle/ruby/*/gems/uri_template-*/
|
**/vendor/bundle/ruby/*/gems/uri_template-*/
|
||||||
**/vendor/bundle/ruby/*/gems/webrobots-*/
|
**/vendor/bundle/ruby/*/gems/webrobots-*/
|
||||||
|
**/vendor/bundle/ruby/*/gems/yard-*/
|
||||||
|
**/vendor/bundle/ruby/*/gems/yard-sorbet-*/
|
||||||
|
|
||||||
# Ignore `bin` contents (again).
|
# Ignore `bin` contents (again).
|
||||||
/bin
|
/bin
|
||||||
|
|||||||
@ -1,9 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
if defined?(Gem::VERSION) && Gem::VERSION >= "2.0."
|
|
||||||
require File.expand_path(File.dirname(__FILE__) + '/yard/rubygems/hook')
|
|
||||||
else
|
|
||||||
unless defined?(Gem::DocManager.load_yardoc)
|
|
||||||
require File.expand_path(File.dirname(__FILE__) + '/yard/rubygems/specification')
|
|
||||||
require File.expand_path(File.dirname(__FILE__) + '/yard/rubygems/doc_manager')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
# The root path for YARD source libraries
|
|
||||||
ROOT = File.expand_path(File.dirname(__FILE__))
|
|
||||||
|
|
||||||
require File.join(YARD::ROOT, 'yard', 'version')
|
|
||||||
require File.join(YARD::ROOT, 'yard', 'autoload')
|
|
||||||
|
|
||||||
# The root path for YARD builtin templates
|
|
||||||
TEMPLATE_ROOT = File.join(ROOT, '..', 'templates')
|
|
||||||
|
|
||||||
# @deprecated Use {Config::CONFIG_DIR}
|
|
||||||
CONFIG_DIR = Config::CONFIG_DIR
|
|
||||||
|
|
||||||
# An alias to {Parser::SourceParser}'s parsing method
|
|
||||||
#
|
|
||||||
# @example Parse a glob of files
|
|
||||||
# YARD.parse('lib/**/*.rb')
|
|
||||||
# @see Parser::SourceParser.parse
|
|
||||||
def self.parse(*args) Parser::SourceParser.parse(*args) end
|
|
||||||
|
|
||||||
# An alias to {Parser::SourceParser}'s parsing method
|
|
||||||
#
|
|
||||||
# @example Parse a string of input
|
|
||||||
# YARD.parse_string('class Foo; end')
|
|
||||||
# @see Parser::SourceParser.parse_string
|
|
||||||
def self.parse_string(*args) Parser::SourceParser.parse_string(*args) end
|
|
||||||
|
|
||||||
# (see YARD::Config.load_plugins)
|
|
||||||
# @deprecated Use {Config.load_plugins}
|
|
||||||
def self.load_plugins; YARD::Config.load_plugins end
|
|
||||||
|
|
||||||
# @return [Boolean] whether YARD is being run inside of Windows
|
|
||||||
def self.windows?
|
|
||||||
return @windows if defined? @windows
|
|
||||||
require 'rbconfig'
|
|
||||||
@windows =
|
|
||||||
::RbConfig::CONFIG['host_os'] =~ /mingw|win32|cygwin/ ? true : false
|
|
||||||
ensure
|
|
||||||
@windows ||= false
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Boolean] whether YARD is being run in Ruby 1.8 mode
|
|
||||||
def self.ruby18?; !ruby19? end
|
|
||||||
|
|
||||||
# @return [Boolean] whether YARD is being run in Ruby 1.9 mode
|
|
||||||
def self.ruby19?; @ruby19 ||= (RUBY_VERSION >= "1.9.1") end
|
|
||||||
|
|
||||||
# @return [Boolean] whether YARD is being run in Ruby 2.0
|
|
||||||
def self.ruby2?; @ruby2 ||= (RUBY_VERSION >= '2.0.0') end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Keep track of Ruby version for compatibility code
|
|
||||||
# @deprecated Use {YARD.ruby18?} or {YARD.ruby19?} instead.
|
|
||||||
RUBY18 = YARD.ruby18?
|
|
||||||
RUBY19 = YARD.ruby19?
|
|
||||||
|
|
||||||
# Load Ruby core extension classes
|
|
||||||
Dir.glob(File.join(YARD::ROOT, 'yard', 'core_ext', '*.rb')).each do |file|
|
|
||||||
require file
|
|
||||||
end
|
|
||||||
|
|
||||||
# Backport RubyGems SourceIndex and other classes
|
|
||||||
require File.join(YARD::ROOT, 'yard', 'rubygems', 'backports')
|
|
||||||
|
|
||||||
require File.join(YARD::ROOT, 'yard', 'globals')
|
|
||||||
|
|
||||||
# Load YARD configuration options (and plugins)
|
|
||||||
YARD::Config.load
|
|
||||||
@ -1,308 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# @private
|
|
||||||
def __p(path) File.join(YARD::ROOT, 'yard', *path.split('/')); end
|
|
||||||
|
|
||||||
module YARD
|
|
||||||
module CLI # Namespace for command-line interface components
|
|
||||||
autoload :Command, __p('cli/command')
|
|
||||||
autoload :CommandParser, __p('cli/command_parser')
|
|
||||||
autoload :Config, __p('cli/config')
|
|
||||||
autoload :Diff, __p('cli/diff')
|
|
||||||
autoload :Display, __p('cli/display')
|
|
||||||
autoload :Gems, __p('cli/gems')
|
|
||||||
autoload :Graph, __p('cli/graph')
|
|
||||||
autoload :Help, __p('cli/help')
|
|
||||||
autoload :List, __p('cli/list')
|
|
||||||
autoload :MarkupTypes, __p('cli/markup_types')
|
|
||||||
autoload :Server, __p('cli/server')
|
|
||||||
autoload :Stats, __p('cli/stats')
|
|
||||||
autoload :Yardoc, __p('cli/yardoc')
|
|
||||||
autoload :YardoptsCommand, __p('cli/yardopts_command')
|
|
||||||
autoload :YRI, __p('cli/yri')
|
|
||||||
autoload :I18n, __p('cli/i18n')
|
|
||||||
end
|
|
||||||
|
|
||||||
# A "code object" is defined as any entity in the Ruby language.
|
|
||||||
# Classes, modules, methods, class variables and constants are the
|
|
||||||
# major objects, but DSL languages can create their own by inheriting
|
|
||||||
# from {CodeObjects::Base}.
|
|
||||||
module CodeObjects
|
|
||||||
autoload :Base, __p('code_objects/base')
|
|
||||||
autoload :CodeObjectList, __p('code_objects/base')
|
|
||||||
autoload :ClassObject, __p('code_objects/class_object')
|
|
||||||
autoload :ClassVariableObject, __p('code_objects/class_variable_object')
|
|
||||||
autoload :ConstantObject, __p('code_objects/constant_object')
|
|
||||||
autoload :ExtendedMethodObject, __p('code_objects/extended_method_object')
|
|
||||||
autoload :ExtraFileObject, __p('code_objects/extra_file_object')
|
|
||||||
autoload :MacroObject, __p('code_objects/macro_object')
|
|
||||||
autoload :MethodObject, __p('code_objects/method_object')
|
|
||||||
autoload :ModuleObject, __p('code_objects/module_object')
|
|
||||||
autoload :NamespaceMapper, __p('code_objects/namespace_mapper')
|
|
||||||
autoload :NamespaceObject, __p('code_objects/namespace_object')
|
|
||||||
autoload :Proxy, __p('code_objects/proxy')
|
|
||||||
autoload :ProxyMethodError, __p('code_objects/proxy')
|
|
||||||
autoload :RootObject, __p('code_objects/root_object')
|
|
||||||
|
|
||||||
autoload :BUILTIN_ALL, __p('code_objects/base')
|
|
||||||
autoload :BUILTIN_CLASSES, __p('code_objects/base')
|
|
||||||
autoload :BUILTIN_MODULES, __p('code_objects/base')
|
|
||||||
autoload :BUILTIN_EXCEPTIONS, __p('code_objects/base')
|
|
||||||
autoload :CONSTANTMATCH, __p('code_objects/base')
|
|
||||||
autoload :CONSTANTSTART, __p('code_objects/base')
|
|
||||||
autoload :METHODMATCH, __p('code_objects/base')
|
|
||||||
autoload :METHODNAMEMATCH, __p('code_objects/base')
|
|
||||||
autoload :NAMESPACEMATCH, __p('code_objects/base')
|
|
||||||
autoload :NSEP, __p('code_objects/base')
|
|
||||||
autoload :NSEPQ, __p('code_objects/base')
|
|
||||||
autoload :ISEP, __p('code_objects/base')
|
|
||||||
autoload :ISEPQ, __p('code_objects/base')
|
|
||||||
autoload :CSEP, __p('code_objects/base')
|
|
||||||
autoload :CSEPQ, __p('code_objects/base')
|
|
||||||
end
|
|
||||||
|
|
||||||
# Handlers are called during the data processing part of YARD's
|
|
||||||
# parsing phase. This allows YARD as well as any custom extension to
|
|
||||||
# analyze source and generate {CodeObjects} to be stored for later use.
|
|
||||||
module Handlers
|
|
||||||
# Shared logic between C and Ruby handlers.
|
|
||||||
module Common
|
|
||||||
autoload :MethodHandler, __p('handlers/common/method_handler')
|
|
||||||
end
|
|
||||||
|
|
||||||
# CRuby Handlers
|
|
||||||
# @since 0.8.0
|
|
||||||
module C
|
|
||||||
autoload :Base, __p('handlers/c/base')
|
|
||||||
|
|
||||||
autoload :AliasHandler, __p('handlers/c/alias_handler')
|
|
||||||
autoload :AttributeHandler, __p('handlers/c/attribute_handler')
|
|
||||||
autoload :ClassHandler, __p('handlers/c/class_handler')
|
|
||||||
autoload :ConstantHandler, __p('handlers/c/constant_handler')
|
|
||||||
autoload :HandlerMethods, __p('handlers/c/handler_methods')
|
|
||||||
autoload :InitHandler, __p('handlers/c/init_handler')
|
|
||||||
autoload :MethodHandler, __p('handlers/c/method_handler')
|
|
||||||
autoload :MixinHandler, __p('handlers/c/mixin_handler')
|
|
||||||
autoload :ModuleHandler, __p('handlers/c/module_handler')
|
|
||||||
autoload :OverrideCommentHandler, __p('handlers/c/override_comment_handler')
|
|
||||||
autoload :PathHandler, __p('handlers/c/path_handler')
|
|
||||||
autoload :StructHandler, __p('handlers/c/struct_handler')
|
|
||||||
autoload :SymbolHandler, __p('handlers/c/symbol_handler')
|
|
||||||
end
|
|
||||||
|
|
||||||
module Ruby # All Ruby handlers
|
|
||||||
module Legacy # Handlers for old Ruby 1.8 parser
|
|
||||||
autoload :Base, __p('handlers/ruby/legacy/base')
|
|
||||||
|
|
||||||
autoload :AliasHandler, __p('handlers/ruby/legacy/alias_handler')
|
|
||||||
autoload :AttributeHandler, __p('handlers/ruby/legacy/attribute_handler')
|
|
||||||
autoload :ClassHandler, __p('handlers/ruby/legacy/class_handler')
|
|
||||||
autoload :ClassConditionHandler, __p('handlers/ruby/legacy/class_condition_handler')
|
|
||||||
autoload :ClassVariableHandler, __p('handlers/ruby/legacy/class_variable_handler')
|
|
||||||
autoload :CommentHandler, __p('handlers/ruby/legacy/comment_handler')
|
|
||||||
autoload :ConstantHandler, __p('handlers/ruby/legacy/constant_handler')
|
|
||||||
autoload :DSLHandler, __p('handlers/ruby/legacy/dsl_handler')
|
|
||||||
autoload :ExceptionHandler, __p('handlers/ruby/legacy/exception_handler')
|
|
||||||
autoload :ExtendHandler, __p('handlers/ruby/legacy/extend_handler')
|
|
||||||
autoload :MethodHandler, __p('handlers/ruby/legacy/method_handler')
|
|
||||||
autoload :MixinHandler, __p('handlers/ruby/legacy/mixin_handler')
|
|
||||||
autoload :ModuleHandler, __p('handlers/ruby/legacy/module_handler')
|
|
||||||
autoload :ModuleFunctionHandler, __p('handlers/ruby/legacy/module_function_handler')
|
|
||||||
autoload :PrivateClassMethodHandler, __p('handlers/ruby/legacy/private_class_method_handler')
|
|
||||||
autoload :PrivateConstantHandler, __p('handlers/ruby/legacy/private_constant_handler')
|
|
||||||
autoload :VisibilityHandler, __p('handlers/ruby/legacy/visibility_handler')
|
|
||||||
autoload :YieldHandler, __p('handlers/ruby/legacy/yield_handler')
|
|
||||||
end
|
|
||||||
|
|
||||||
autoload :Base, __p('handlers/ruby/base')
|
|
||||||
|
|
||||||
autoload :AliasHandler, __p('handlers/ruby/alias_handler')
|
|
||||||
autoload :AttributeHandler, __p('handlers/ruby/attribute_handler')
|
|
||||||
autoload :ClassHandler, __p('handlers/ruby/class_handler')
|
|
||||||
autoload :ClassConditionHandler, __p('handlers/ruby/class_condition_handler')
|
|
||||||
autoload :ClassVariableHandler, __p('handlers/ruby/class_variable_handler')
|
|
||||||
autoload :CommentHandler, __p('handlers/ruby/comment_handler')
|
|
||||||
autoload :ConstantHandler, __p('handlers/ruby/constant_handler')
|
|
||||||
autoload :DecoratorHandlerMethods, __p('handlers/ruby/decorator_handler_methods')
|
|
||||||
autoload :DSLHandler, __p('handlers/ruby/dsl_handler')
|
|
||||||
autoload :DSLHandlerMethods, __p('handlers/ruby/dsl_handler_methods')
|
|
||||||
autoload :ExceptionHandler, __p('handlers/ruby/exception_handler')
|
|
||||||
autoload :ExtendHandler, __p('handlers/ruby/extend_handler')
|
|
||||||
autoload :MethodHandler, __p('handlers/ruby/method_handler')
|
|
||||||
autoload :MethodConditionHandler, __p('handlers/ruby/method_condition_handler')
|
|
||||||
autoload :MixinHandler, __p('handlers/ruby/mixin_handler')
|
|
||||||
autoload :ModuleHandler, __p('handlers/ruby/module_handler')
|
|
||||||
autoload :ModuleFunctionHandler, __p('handlers/ruby/module_function_handler')
|
|
||||||
autoload :PrivateClassMethodHandler, __p('handlers/ruby/private_class_method_handler')
|
|
||||||
autoload :PrivateConstantHandler, __p('handlers/ruby/private_constant_handler')
|
|
||||||
autoload :PublicClassMethodHandler, __p('handlers/ruby/public_class_method_handler')
|
|
||||||
autoload :StructHandlerMethods, __p('handlers/ruby/struct_handler_methods')
|
|
||||||
autoload :VisibilityHandler, __p('handlers/ruby/visibility_handler')
|
|
||||||
autoload :YieldHandler, __p('handlers/ruby/yield_handler')
|
|
||||||
end
|
|
||||||
|
|
||||||
autoload :Base, __p('handlers/base')
|
|
||||||
autoload :HandlerAborted, __p('handlers/base')
|
|
||||||
autoload :NamespaceMissingError, __p('handlers/base')
|
|
||||||
autoload :Processor, __p('handlers/processor')
|
|
||||||
end
|
|
||||||
|
|
||||||
# Namespace for internationalization (i18n)
|
|
||||||
# @since 0.8.0
|
|
||||||
module I18n
|
|
||||||
autoload :Locale, __p('i18n/locale')
|
|
||||||
autoload :Message, __p('i18n/message')
|
|
||||||
autoload :Messages, __p('i18n/messages')
|
|
||||||
autoload :PotGenerator, __p('i18n/pot_generator')
|
|
||||||
autoload :Text, __p('i18n/text')
|
|
||||||
end
|
|
||||||
|
|
||||||
# The parser namespace holds all parsing engines used by YARD.
|
|
||||||
# Currently only Ruby and C (Ruby) parsers are implemented.
|
|
||||||
module Parser
|
|
||||||
module C # CRuby Parsing components
|
|
||||||
autoload :BodyStatement, __p('parser/c/statement')
|
|
||||||
autoload :Comment, __p('parser/c/statement')
|
|
||||||
autoload :CommentParser, __p('parser/c/comment_parser')
|
|
||||||
autoload :CParser, __p('parser/c/c_parser')
|
|
||||||
autoload :Statement, __p('parser/c/statement')
|
|
||||||
autoload :ToplevelStatement, __p('parser/c/statement')
|
|
||||||
end
|
|
||||||
|
|
||||||
module Ruby # Ruby parsing components.
|
|
||||||
module Legacy # Handles Ruby parsing in Ruby 1.8.
|
|
||||||
autoload :RipperParser, __p('parser/ruby/legacy/ruby_parser')
|
|
||||||
autoload :RubyParser, __p('parser/ruby/legacy/ruby_parser')
|
|
||||||
autoload :RubyToken, __p('parser/ruby/legacy/ruby_lex')
|
|
||||||
autoload :Statement, __p('parser/ruby/legacy/statement')
|
|
||||||
autoload :StatementList, __p('parser/ruby/legacy/statement_list')
|
|
||||||
autoload :TokenList, __p('parser/ruby/legacy/token_list')
|
|
||||||
end
|
|
||||||
|
|
||||||
autoload :AstNode, __p('parser/ruby/ast_node')
|
|
||||||
autoload :RubyParser, __p('parser/ruby/ruby_parser')
|
|
||||||
autoload :TokenResolver, __p('parser/ruby/token_resolver')
|
|
||||||
end
|
|
||||||
|
|
||||||
autoload :Base, __p('parser/base')
|
|
||||||
autoload :ParserSyntaxError, __p('parser/source_parser')
|
|
||||||
autoload :SourceParser, __p('parser/source_parser')
|
|
||||||
autoload :UndocumentableError, __p('parser/source_parser')
|
|
||||||
end
|
|
||||||
|
|
||||||
module Rake # Holds Rake tasks used by YARD
|
|
||||||
autoload :YardocTask, __p('rake/yardoc_task')
|
|
||||||
end
|
|
||||||
|
|
||||||
module Serializers # Namespace for components that serialize to various endpoints
|
|
||||||
autoload :Base, __p('serializers/base')
|
|
||||||
autoload :FileSystemSerializer, __p('serializers/file_system_serializer')
|
|
||||||
autoload :ProcessSerializer, __p('serializers/process_serializer')
|
|
||||||
autoload :StdoutSerializer, __p('serializers/stdout_serializer')
|
|
||||||
autoload :YardocSerializer, __p('serializers/yardoc_serializer')
|
|
||||||
end
|
|
||||||
|
|
||||||
# Namespace for classes and modules that handle serving documentation over HTTP
|
|
||||||
#
|
|
||||||
# == Implementing a Custom Server
|
|
||||||
# To customize the YARD server, see the {Adapter} and {Router} classes.
|
|
||||||
#
|
|
||||||
# == Rack Middleware
|
|
||||||
# If you want to use the YARD server as a Rack middleware, see the documentation
|
|
||||||
# in {RackMiddleware}.
|
|
||||||
#
|
|
||||||
# @since 0.6.0
|
|
||||||
module Server
|
|
||||||
require __p('server')
|
|
||||||
|
|
||||||
# Commands implement specific kinds of server responses which are routed
|
|
||||||
# to by the {Router} class. To implement a custom command, subclass {Commands::Base}.
|
|
||||||
module Commands
|
|
||||||
autoload :Base, __p('server/commands/base')
|
|
||||||
autoload :DisplayFileCommand, __p('server/commands/display_file_command')
|
|
||||||
autoload :DisplayObjectCommand, __p('server/commands/display_object_command')
|
|
||||||
autoload :FramesCommand, __p('server/commands/frames_command')
|
|
||||||
autoload :ListCommand, __p('server/commands/list_command')
|
|
||||||
autoload :LibraryCommand, __p('server/commands/library_command')
|
|
||||||
autoload :LibraryIndexCommand, __p('server/commands/library_index_command')
|
|
||||||
autoload :RootRequestCommand, __p('server/commands/root_request_command')
|
|
||||||
autoload :SearchCommand, __p('server/commands/search_command')
|
|
||||||
autoload :StaticFileCommand, __p('server/commands/static_file_command')
|
|
||||||
autoload :StaticFileHelpers, __p('server/commands/static_file_helpers')
|
|
||||||
end
|
|
||||||
|
|
||||||
autoload :Adapter, __p('server/adapter')
|
|
||||||
autoload :DocServerSerializer, __p('server/doc_server_serializer')
|
|
||||||
autoload :DocServerHelper, __p('server/doc_server_helper')
|
|
||||||
autoload :FinishRequest, __p('server/adapter')
|
|
||||||
autoload :LibraryVersion, __p('server/library_version')
|
|
||||||
autoload :NotFoundError, __p('server/adapter')
|
|
||||||
autoload :RackAdapter, __p('server/rack_adapter')
|
|
||||||
autoload :RackMiddleware, __p('server/rack_adapter')
|
|
||||||
autoload :Router, __p('server/router')
|
|
||||||
autoload :StaticCaching, __p('server/static_caching')
|
|
||||||
autoload :WebrickAdapter, __p('server/webrick_adapter')
|
|
||||||
autoload :WebrickServlet, __p('server/webrick_adapter')
|
|
||||||
end
|
|
||||||
|
|
||||||
module Tags # Namespace for Tag components
|
|
||||||
autoload :AttributeDirective, __p('tags/directives')
|
|
||||||
autoload :DefaultFactory, __p('tags/default_factory')
|
|
||||||
autoload :DefaultTag, __p('tags/default_tag')
|
|
||||||
autoload :Directive, __p('tags/directives')
|
|
||||||
autoload :EndGroupDirective, __p('tags/directives')
|
|
||||||
autoload :GroupDirective, __p('tags/directives')
|
|
||||||
autoload :Library, __p('tags/library')
|
|
||||||
autoload :MacroDirective, __p('tags/directives')
|
|
||||||
autoload :MethodDirective, __p('tags/directives')
|
|
||||||
autoload :OptionTag, __p('tags/option_tag')
|
|
||||||
autoload :OverloadTag, __p('tags/overload_tag')
|
|
||||||
autoload :ParseDirective, __p('tags/directives')
|
|
||||||
autoload :RefTag, __p('tags/ref_tag')
|
|
||||||
autoload :RefTagList, __p('tags/ref_tag_list')
|
|
||||||
autoload :ScopeDirective, __p('tags/directives')
|
|
||||||
autoload :Tag, __p('tags/tag')
|
|
||||||
autoload :TagFormatError, __p('tags/tag_format_error')
|
|
||||||
autoload :TypesExplainer, __p('tags/types_explainer')
|
|
||||||
autoload :VisibilityDirective, __p('tags/directives')
|
|
||||||
end
|
|
||||||
|
|
||||||
# Namespace for templating system
|
|
||||||
module Templates
|
|
||||||
module Helpers # Namespace for template helpers
|
|
||||||
module Markup # Namespace for markup providers
|
|
||||||
autoload :RDocMarkup, __p('templates/helpers/markup/rdoc_markup')
|
|
||||||
autoload :RDocMarkdown, __p('templates/helpers/markup/rdoc_markdown')
|
|
||||||
end
|
|
||||||
|
|
||||||
autoload :BaseHelper, __p('templates/helpers/base_helper')
|
|
||||||
autoload :FilterHelper, __p('templates/helpers/filter_helper')
|
|
||||||
autoload :HtmlHelper, __p('templates/helpers/html_helper')
|
|
||||||
autoload :HtmlSyntaxHighlightHelper, __p('templates/helpers/html_syntax_highlight_helper')
|
|
||||||
autoload :MarkupHelper, __p('templates/helpers/markup_helper')
|
|
||||||
autoload :MethodHelper, __p('templates/helpers/method_helper')
|
|
||||||
autoload :ModuleHelper, __p('templates/helpers/module_helper')
|
|
||||||
autoload :TextHelper, __p('templates/helpers/text_helper')
|
|
||||||
autoload :UMLHelper, __p('templates/helpers/uml_helper')
|
|
||||||
end
|
|
||||||
|
|
||||||
autoload :Engine, __p('templates/engine')
|
|
||||||
autoload :ErbCache, __p('templates/erb_cache')
|
|
||||||
autoload :Section, __p('templates/section')
|
|
||||||
autoload :Template, __p('templates/template')
|
|
||||||
autoload :TemplateOptions, __p('templates/template_options')
|
|
||||||
end
|
|
||||||
|
|
||||||
autoload :Config, __p('config')
|
|
||||||
autoload :Docstring, __p('docstring')
|
|
||||||
autoload :DocstringParser, __p('docstring_parser')
|
|
||||||
autoload :GemIndex, __p('gem_index')
|
|
||||||
autoload :Logger, __p('logging')
|
|
||||||
autoload :Options, __p('options')
|
|
||||||
autoload :Registry, __p('registry')
|
|
||||||
autoload :RegistryResolver, __p('registry_resolver')
|
|
||||||
autoload :RegistryStore, __p('registry_store')
|
|
||||||
autoload :StubProxy, __p('serializers/yardoc_serializer')
|
|
||||||
autoload :Verifier, __p('verifier')
|
|
||||||
end
|
|
||||||
|
|
||||||
undef __p
|
|
||||||
@ -1,85 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
require 'optparse'
|
|
||||||
|
|
||||||
module YARD
|
|
||||||
module CLI
|
|
||||||
# Abstract base class for CLI utilities. Provides some helper methods for
|
|
||||||
# the option parser
|
|
||||||
#
|
|
||||||
# @abstract
|
|
||||||
# @since 0.6.0
|
|
||||||
class Command
|
|
||||||
# Helper method to run the utility on an instance.
|
|
||||||
# @see #run
|
|
||||||
def self.run(*args) new.run(*args) end
|
|
||||||
|
|
||||||
def description; '' end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# Adds a set of common options to the tail of the OptionParser
|
|
||||||
#
|
|
||||||
# @param [OptionParser] opts the option parser object
|
|
||||||
# @return [void]
|
|
||||||
def common_options(opts)
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "Other options:"
|
|
||||||
opts.on('-e', '--load FILE', 'A Ruby script to load before running command.') do |file|
|
|
||||||
load_script(file)
|
|
||||||
end
|
|
||||||
opts.on('--plugin PLUGIN', 'Load a YARD plugin (gem with `yard-\' prefix)') do |name|
|
|
||||||
# Not actually necessary to load here, this is done at boot in YARD::Config.load_plugins
|
|
||||||
# YARD::Config.load_plugin(name)
|
|
||||||
end
|
|
||||||
opts.on('--legacy', 'Use old style Ruby parser and handlers. ',
|
|
||||||
' Always on in 1.8.x.') do
|
|
||||||
YARD::Parser::SourceParser.parser_type = :ruby18
|
|
||||||
end
|
|
||||||
opts.on('--safe', 'Enable safe mode for this instance') do
|
|
||||||
# Parsed in YARD::Config.load
|
|
||||||
end
|
|
||||||
opts.on_tail('-q', '--quiet', 'Show no warnings.') { log.level = Logger::ERROR }
|
|
||||||
opts.on_tail('--verbose', 'Show more information.') { log.level = Logger::INFO }
|
|
||||||
opts.on_tail('--debug', 'Show debugging information.') { log.level = Logger::DEBUG }
|
|
||||||
opts.on_tail('--backtrace', 'Show stack traces') { log.show_backtraces = true }
|
|
||||||
opts.on_tail('-v', '--version', 'Show version.') { log.puts "yard #{YARD::VERSION}"; exit }
|
|
||||||
opts.on_tail('-h', '--help', 'Show this help.') { log.puts opts; exit }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parses the option and gracefully handles invalid switches
|
|
||||||
#
|
|
||||||
# @param [OptionParser] opts the option parser object
|
|
||||||
# @param [Array<String>] args the arguments passed from input. This
|
|
||||||
# array will be modified.
|
|
||||||
# @return [void]
|
|
||||||
def parse_options(opts, args)
|
|
||||||
opts.parse!(args)
|
|
||||||
rescue OptionParser::ParseError => err
|
|
||||||
unrecognized_option(err)
|
|
||||||
args.shift if args.first && args.first[0, 1] != '-'
|
|
||||||
retry
|
|
||||||
end
|
|
||||||
|
|
||||||
# Loads a Ruby script. If <tt>Config.options[:safe_mode]</tt> is enabled,
|
|
||||||
# this method will do nothing.
|
|
||||||
#
|
|
||||||
# @param [String] file the path to the script to load
|
|
||||||
# @since 0.6.2
|
|
||||||
def load_script(file)
|
|
||||||
return if YARD::Config.options[:safe_mode]
|
|
||||||
load(file)
|
|
||||||
rescue LoadError => load_exception
|
|
||||||
log.error "The file `#{file}' could not be loaded:\n#{load_exception}"
|
|
||||||
exit
|
|
||||||
end
|
|
||||||
|
|
||||||
# Callback when an unrecognize option is parsed
|
|
||||||
#
|
|
||||||
# @param [OptionParser::ParseError] err the exception raised by the
|
|
||||||
# option parser
|
|
||||||
def unrecognized_option(err)
|
|
||||||
log.warn "Unrecognized/#{err.message}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module CLI
|
|
||||||
# This class parses a command name out of the +yard+ CLI command and calls
|
|
||||||
# that command in the form:
|
|
||||||
#
|
|
||||||
# $ yard command_name [options]
|
|
||||||
#
|
|
||||||
# If no command or arguments are specified, or if the arguments immediately
|
|
||||||
# begin with a +--opt+ (not +--help+), the {default_command} will be used
|
|
||||||
# (which itself defaults to +:doc+).
|
|
||||||
#
|
|
||||||
# == Adding a Command
|
|
||||||
#
|
|
||||||
# To add a custom command via plugin, create a mapping in {commands} from
|
|
||||||
# the Symbolic command name to the {Command} class that implements the
|
|
||||||
# command. To implement a command, see the documentation for the {Command}
|
|
||||||
# class.
|
|
||||||
#
|
|
||||||
# @see Command
|
|
||||||
# @see commands
|
|
||||||
# @see default_command
|
|
||||||
class CommandParser
|
|
||||||
class << self
|
|
||||||
# @return [Hash{Symbol => Command}] the mapping of command names to
|
|
||||||
# command classes to parse the user command.
|
|
||||||
attr_accessor :commands
|
|
||||||
|
|
||||||
# @return [Symbol] the default command name to use when no options
|
|
||||||
# are specified or
|
|
||||||
attr_accessor :default_command
|
|
||||||
end
|
|
||||||
|
|
||||||
self.commands = SymbolHash[
|
|
||||||
:config => Config,
|
|
||||||
:diff => Diff,
|
|
||||||
:display => Display,
|
|
||||||
:doc => Yardoc,
|
|
||||||
:gems => Gems,
|
|
||||||
:graph => Graph,
|
|
||||||
:help => Help,
|
|
||||||
:list => List,
|
|
||||||
:markups => MarkupTypes,
|
|
||||||
:ri => YRI,
|
|
||||||
:server => Server,
|
|
||||||
:stats => Stats,
|
|
||||||
:i18n => I18n
|
|
||||||
]
|
|
||||||
|
|
||||||
self.default_command = :doc
|
|
||||||
|
|
||||||
# Convenience method to create a new CommandParser and call {#run}
|
|
||||||
# @return (see #run)
|
|
||||||
def self.run(*args) new.run(*args) end
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
log.show_backtraces = false
|
|
||||||
end
|
|
||||||
|
|
||||||
# Runs the {Command} object matching the command name of the first
|
|
||||||
# argument.
|
|
||||||
# @return [void]
|
|
||||||
def run(*args)
|
|
||||||
unless args == ['--help']
|
|
||||||
if args.empty? || args.first =~ /^-/
|
|
||||||
command_name = self.class.default_command
|
|
||||||
else
|
|
||||||
command_name = args.first.to_sym
|
|
||||||
args.shift
|
|
||||||
end
|
|
||||||
if commands.key?(command_name)
|
|
||||||
return commands[command_name].run(*args)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
list_commands
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def commands; self.class.commands end
|
|
||||||
|
|
||||||
def list_commands
|
|
||||||
log.puts "Usage: yard <command> [options]"
|
|
||||||
log.puts
|
|
||||||
log.puts "Commands:"
|
|
||||||
commands.keys.sort_by(&:to_s).each do |command_name|
|
|
||||||
command = commands[command_name].new
|
|
||||||
log.puts "%-8s %s" % [command_name, command.description]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,198 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module CLI
|
|
||||||
# CLI command to view or edit configuration options
|
|
||||||
# @since 0.6.2
|
|
||||||
class Config < Command
|
|
||||||
# @return [Symbol, nil] the key to view/edit, if any
|
|
||||||
attr_accessor :key
|
|
||||||
|
|
||||||
# @return [Array, nil] the list of values to set (or single value), if modifying
|
|
||||||
attr_accessor :values
|
|
||||||
|
|
||||||
# @return [Boolean] whether to reset the {#key}
|
|
||||||
attr_accessor :reset
|
|
||||||
|
|
||||||
# @return [Boolean] whether the value being set should be inside a list
|
|
||||||
attr_accessor :as_list
|
|
||||||
|
|
||||||
# @return [Boolean] whether to append values to existing key
|
|
||||||
attr_accessor :append
|
|
||||||
|
|
||||||
# @return [String, nil] command to use when configuring ~/.gemrc file.
|
|
||||||
# If the string is nil, configuration should not occur.
|
|
||||||
attr_accessor :gem_install_cmd
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
super
|
|
||||||
self.key = nil
|
|
||||||
self.values = []
|
|
||||||
self.reset = false
|
|
||||||
self.append = false
|
|
||||||
self.as_list = false
|
|
||||||
self.gem_install_cmd = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def description
|
|
||||||
'Views or edits current global configuration'
|
|
||||||
end
|
|
||||||
|
|
||||||
def run(*args)
|
|
||||||
optparse(*args)
|
|
||||||
if gem_install_cmd
|
|
||||||
configure_gemrc
|
|
||||||
elsif key
|
|
||||||
if reset || !values.empty?
|
|
||||||
modify_item
|
|
||||||
else
|
|
||||||
view_item
|
|
||||||
end
|
|
||||||
else
|
|
||||||
list_configuration
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def configure_gemrc
|
|
||||||
return unless gem_install_cmd
|
|
||||||
|
|
||||||
require 'rubygems'
|
|
||||||
|
|
||||||
['install', :install, 'gem', :gem].find do |cmd|
|
|
||||||
conf = Gem.configuration[cmd] || ""
|
|
||||||
next if conf.empty? && cmd != :gem
|
|
||||||
|
|
||||||
conf = conf.split(/\s+/)
|
|
||||||
conf.delete_if {|c| c =~ /^--(no-)?document\b/ } # scrub doc args
|
|
||||||
conf |= ["--document=#{gem_install_cmd}"]
|
|
||||||
conf = conf.join(' ')
|
|
||||||
|
|
||||||
Gem.configuration[cmd] = conf
|
|
||||||
Gem.configuration.write
|
|
||||||
log.puts "Updated #{Gem.configuration.path || '~/.gemrc'}: '#{cmd}: #{conf}'"
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def modify_item
|
|
||||||
if reset
|
|
||||||
log.debug "Resetting #{key}"
|
|
||||||
YARD::Config.options[key] = YARD::Config::DEFAULT_CONFIG_OPTIONS[key]
|
|
||||||
else
|
|
||||||
log.debug "Setting #{key} to #{values.inspect}"
|
|
||||||
items = encode_values
|
|
||||||
current_items = YARD::Config.options[key]
|
|
||||||
items = [current_items].flatten + [items].flatten if append
|
|
||||||
YARD::Config.options[key] = items
|
|
||||||
end
|
|
||||||
YARD::Config.save
|
|
||||||
end
|
|
||||||
|
|
||||||
def view_item
|
|
||||||
log.debug "Viewing #{key}"
|
|
||||||
log.puts YARD::Config.options[key].inspect
|
|
||||||
end
|
|
||||||
|
|
||||||
def list_configuration
|
|
||||||
log.debug "Listing configuration"
|
|
||||||
require 'yaml'
|
|
||||||
log.puts YAML.dump(YARD::Config.options).sub(/\A--.*\n/, '').gsub(/\n\n/, "\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
def encode_values
|
|
||||||
if values.size == 1 && !as_list
|
|
||||||
encode_value(values.first)
|
|
||||||
else
|
|
||||||
values.map {|v| encode_value(v) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def encode_value(value)
|
|
||||||
case value
|
|
||||||
when /^-?\d+/; value.to_i
|
|
||||||
when "true"; true
|
|
||||||
when "false"; false
|
|
||||||
else value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def optparse(*args)
|
|
||||||
list = false
|
|
||||||
self.as_list = false
|
|
||||||
self.append = false
|
|
||||||
opts = OptionParser.new
|
|
||||||
opts.banner = "Usage: yard config [options] [item [value ...]]"
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "Example: yard config load_plugins true"
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "Views and sets configuration items. If an item is provided"
|
|
||||||
opts.separator "With no value, the item is viewed. If a value is provided,"
|
|
||||||
opts.separator "the item is modified. Specifying no item is equivalent to --list."
|
|
||||||
opts.separator "If you specify multiple space delimited values, these are"
|
|
||||||
opts.separator "parsed as an array of values."
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "Note that `true` and `false` are reserved words."
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "---------------------------------------------------------"
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "Configuring RubyGems support:"
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "YARD can automatically generate the YRI index or HTML"
|
|
||||||
opts.separator "documentation in a `gem install` by adding the following"
|
|
||||||
opts.separator "to your ~/.gemrc file:"
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator " gem: \"--document=yri\""
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "Note: you can add 'yard' to also generate HTML docs."
|
|
||||||
opts.separator " You can also add 'ri' to continue generating RDoc."
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "You can also run the following command to configure this"
|
|
||||||
opts.separator "behavior automatically:"
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator " $ yard config --gem-install-yri"
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "Add --gem-install-yard to also generate HTML."
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "---------------------------------------------------------"
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "General options:"
|
|
||||||
|
|
||||||
opts.on('-l', '--list', 'List current configuration') do
|
|
||||||
list = true
|
|
||||||
end
|
|
||||||
opts.on('-r', '--reset', 'Resets the specific item to default') do
|
|
||||||
self.reset = true
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "Modifying keys:"
|
|
||||||
|
|
||||||
opts.on('-a', '--append', 'Appends items to existing key values') do
|
|
||||||
self.append = true
|
|
||||||
end
|
|
||||||
opts.on('--as-list', 'Forces the value(s) to be wrapped in an array') do
|
|
||||||
self.as_list = true
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "Add RubyGems install hook:"
|
|
||||||
|
|
||||||
opts.on('--gem-install-yri', 'Configures ~/.gemrc to run yri on a gem install') do
|
|
||||||
self.gem_install_cmd = 'yri' if gem_install_cmd != 'yard'
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--gem-install-yard', 'Configures ~/.gemrc to run yard on a gem install') do
|
|
||||||
self.gem_install_cmd = 'yard'
|
|
||||||
end
|
|
||||||
|
|
||||||
common_options(opts)
|
|
||||||
parse_options(opts, args)
|
|
||||||
args = [] if list
|
|
||||||
self.key = args.shift.to_sym if args.size >= 1
|
|
||||||
self.values = args if args.size >= 1
|
|
||||||
args
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,273 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
require 'tmpdir'
|
|
||||||
require 'fileutils'
|
|
||||||
require 'open-uri'
|
|
||||||
|
|
||||||
module YARD
|
|
||||||
module CLI
|
|
||||||
# CLI command to return the objects that were added/removed from 2 versions
|
|
||||||
# of a project (library, gem, working copy).
|
|
||||||
# @since 0.6.0
|
|
||||||
class Diff < Command
|
|
||||||
def initialize
|
|
||||||
super
|
|
||||||
@list_all = false
|
|
||||||
@use_git = false
|
|
||||||
@compact = false
|
|
||||||
@modified = true
|
|
||||||
@verifier = Verifier.new
|
|
||||||
@old_git_commit = nil
|
|
||||||
@old_path = Dir.pwd
|
|
||||||
log.show_backtraces = true
|
|
||||||
end
|
|
||||||
|
|
||||||
def description
|
|
||||||
'Returns the object diff of two gems or .yardoc files'
|
|
||||||
end
|
|
||||||
|
|
||||||
def run(*args)
|
|
||||||
registry = optparse(*args).map do |gemfile|
|
|
||||||
if @use_git
|
|
||||||
load_git_commit(gemfile)
|
|
||||||
all_objects
|
|
||||||
elsif load_gem_data(gemfile)
|
|
||||||
log.info "Found #{gemfile}"
|
|
||||||
all_objects
|
|
||||||
else
|
|
||||||
log.error "Cannot find gem #{gemfile}"
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end.compact
|
|
||||||
|
|
||||||
return if registry.size != 2
|
|
||||||
|
|
||||||
first_object = nil
|
|
||||||
[["Added objects", "A", added_objects(*registry)],
|
|
||||||
["Modified objects", "M", modified_objects(*registry)],
|
|
||||||
["Removed objects", "D", removed_objects(*registry)]].each do |name, short, objects|
|
|
||||||
next if short == "M" && @modified == false
|
|
||||||
next if objects.empty?
|
|
||||||
last_object = nil
|
|
||||||
all_objects_notice = false
|
|
||||||
log.puts name + ":" unless @compact
|
|
||||||
objects.sort_by(&:path).each do |object|
|
|
||||||
if !@list_all && last_object && object.parent == last_object
|
|
||||||
log.print " (...)" unless all_objects_notice
|
|
||||||
all_objects_notice = true
|
|
||||||
next
|
|
||||||
elsif @compact
|
|
||||||
log.puts if first_object
|
|
||||||
else
|
|
||||||
log.puts
|
|
||||||
end
|
|
||||||
all_objects_notice = false
|
|
||||||
log.print "" + (@compact ? "#{short} " : " ") +
|
|
||||||
object.path + " (#{object.file}:#{object.line})"
|
|
||||||
last_object = object
|
|
||||||
first_object = true
|
|
||||||
end
|
|
||||||
unless @compact
|
|
||||||
log.puts; log.puts
|
|
||||||
end
|
|
||||||
end
|
|
||||||
log.puts if @compact
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def all_objects
|
|
||||||
return Registry.all if @verifier.expressions.empty?
|
|
||||||
@verifier.run(Registry.all)
|
|
||||||
end
|
|
||||||
|
|
||||||
def added_objects(registry1, registry2)
|
|
||||||
registry2.reject {|o| registry1.find {|o2| o2.path == o.path } }
|
|
||||||
end
|
|
||||||
|
|
||||||
def modified_objects(registry1, registry2)
|
|
||||||
registry1.select do |obj|
|
|
||||||
case obj
|
|
||||||
when CodeObjects::MethodObject
|
|
||||||
registry2.find {|o| obj == o && o.source != obj.source }
|
|
||||||
when CodeObjects::ConstantObject
|
|
||||||
registry2.find {|o| obj == o && o.value != obj.value }
|
|
||||||
end
|
|
||||||
end.compact
|
|
||||||
end
|
|
||||||
|
|
||||||
def removed_objects(registry1, registry2)
|
|
||||||
registry1.reject {|o| registry2.find {|o2| o2.path == o.path } }
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_git_commit(commit)
|
|
||||||
Registry.clear
|
|
||||||
commit_path = 'git_commit' + commit.gsub(/\W/, '_')
|
|
||||||
tmpdir = File.join(Dir.tmpdir, commit_path)
|
|
||||||
log.info "Expanding #{commit} to #{tmpdir}..."
|
|
||||||
Dir.chdir(@old_path)
|
|
||||||
FileUtils.mkdir_p(tmpdir)
|
|
||||||
FileUtils.cp_r('.', tmpdir)
|
|
||||||
Dir.chdir(tmpdir)
|
|
||||||
log.info("git says: " + `git reset --hard #{commit}`.chomp)
|
|
||||||
generate_yardoc(tmpdir)
|
|
||||||
ensure
|
|
||||||
Dir.chdir(@old_path)
|
|
||||||
cleanup(commit_path)
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_gem_data(gemfile)
|
|
||||||
require_rubygems
|
|
||||||
Registry.clear
|
|
||||||
|
|
||||||
# First check for argument as .yardoc file
|
|
||||||
[File.join(gemfile, '.yardoc'), gemfile].each do |yardoc|
|
|
||||||
log.info "Searching for .yardoc db at #{yardoc}"
|
|
||||||
next unless File.directory?(yardoc)
|
|
||||||
Registry.load_yardoc(yardoc)
|
|
||||||
Registry.load_all
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
# Next check installed RubyGems
|
|
||||||
gemfile_without_ext = gemfile.sub(/\.gem$/, '')
|
|
||||||
log.info "Searching for installed gem #{gemfile_without_ext}"
|
|
||||||
YARD::GemIndex.each.find do |spec|
|
|
||||||
next unless spec.full_name == gemfile_without_ext
|
|
||||||
yardoc = Registry.yardoc_file_for_gem(spec.name, "= #{spec.version}")
|
|
||||||
if yardoc
|
|
||||||
Registry.load_yardoc(yardoc)
|
|
||||||
Registry.load_all
|
|
||||||
else
|
|
||||||
log.enter_level(Logger::ERROR) do
|
|
||||||
olddir = Dir.pwd
|
|
||||||
Gems.run(spec.name, spec.version.to_s)
|
|
||||||
Dir.chdir(olddir)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
# Look for local .gem file
|
|
||||||
gemfile += '.gem' unless gemfile =~ /\.gem$/
|
|
||||||
log.info "Searching for local gem file #{gemfile}"
|
|
||||||
if File.exist?(gemfile)
|
|
||||||
File.open(gemfile, 'rb') do |io|
|
|
||||||
expand_and_parse(gemfile, io)
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
# Remote gemfile from rubygems.org
|
|
||||||
url = "http://rubygems.org/downloads/#{gemfile}"
|
|
||||||
log.info "Searching for remote gem file #{url}"
|
|
||||||
begin
|
|
||||||
# Note: In Ruby 2.4.x, URI.open is a private method. After
|
|
||||||
# 2.5, URI.open behaves much like Kernel#open once you've
|
|
||||||
# required 'open-uri'
|
|
||||||
OpenURI.open_uri(url) {|io| expand_and_parse(gemfile, io) }
|
|
||||||
return true
|
|
||||||
rescue OpenURI::HTTPError
|
|
||||||
nil # noop
|
|
||||||
end
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def expand_and_parse(gemfile, io)
|
|
||||||
dir = expand_gem(gemfile, io)
|
|
||||||
generate_yardoc(dir)
|
|
||||||
cleanup(gemfile)
|
|
||||||
end
|
|
||||||
|
|
||||||
def generate_yardoc(dir)
|
|
||||||
Dir.chdir(dir) do
|
|
||||||
log.enter_level(Logger::ERROR) { Yardoc.run('-n', '--no-save') }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def expand_gem(gemfile, io)
|
|
||||||
tmpdir = File.join(Dir.tmpdir, gemfile)
|
|
||||||
FileUtils.mkdir_p(tmpdir)
|
|
||||||
log.info "Expanding #{gemfile} to #{tmpdir}..."
|
|
||||||
|
|
||||||
if Gem::VERSION >= '2.0.0'
|
|
||||||
require 'rubygems/package/tar_reader'
|
|
||||||
reader = Gem::Package::TarReader.new(io)
|
|
||||||
reader.each do |pkg|
|
|
||||||
next unless pkg.full_name == 'data.tar.gz'
|
|
||||||
Zlib::GzipReader.wrap(pkg) do |gzio|
|
|
||||||
tar = Gem::Package::TarReader.new(gzio)
|
|
||||||
tar.each do |entry|
|
|
||||||
file = File.join(tmpdir, entry.full_name)
|
|
||||||
FileUtils.mkdir_p(File.dirname(file))
|
|
||||||
File.open(file, 'wb') do |out|
|
|
||||||
out.write(entry.read)
|
|
||||||
begin
|
|
||||||
out.fsync
|
|
||||||
rescue NotImplementedError
|
|
||||||
nil # noop
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
break
|
|
||||||
end
|
|
||||||
else
|
|
||||||
Gem::Package.open(io) do |pkg|
|
|
||||||
pkg.each do |entry|
|
|
||||||
pkg.extract_entry(tmpdir, entry)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
tmpdir
|
|
||||||
end
|
|
||||||
|
|
||||||
def require_rubygems
|
|
||||||
require 'rubygems'
|
|
||||||
require 'rubygems/package'
|
|
||||||
rescue LoadError => e
|
|
||||||
log.error "Missing RubyGems, cannot run this command."
|
|
||||||
raise(e)
|
|
||||||
end
|
|
||||||
|
|
||||||
def cleanup(gemfile)
|
|
||||||
dir = File.join(Dir.tmpdir, gemfile)
|
|
||||||
log.info "Cleaning up #{dir}..."
|
|
||||||
FileUtils.rm_rf(dir)
|
|
||||||
end
|
|
||||||
|
|
||||||
def optparse(*args)
|
|
||||||
opts = OptionParser.new
|
|
||||||
opts.banner = "Usage: yard diff [options] oldgem newgem"
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "Example: yard diff yard-0.5.6 yard-0.5.8"
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "If the files don't exist locally, they will be grabbed using the `gem fetch`"
|
|
||||||
opts.separator "command. If the gem is a .yardoc directory, it will be used. Finally, if the"
|
|
||||||
opts.separator "gem name matches an installed gem (full name-version syntax), that gem will be used."
|
|
||||||
|
|
||||||
opts.on('-a', '--all', 'List all objects, even if they are inside added/removed module/class') do
|
|
||||||
@list_all = true
|
|
||||||
end
|
|
||||||
opts.on('--compact', 'Show compact results') { @compact = true }
|
|
||||||
opts.on('--git', 'Compare versions from two git commit/branches') do
|
|
||||||
@use_git = true
|
|
||||||
end
|
|
||||||
opts.on('--query QUERY', 'Only diff filtered objects') do |query|
|
|
||||||
@verifier.add_expressions(query)
|
|
||||||
end
|
|
||||||
opts.on('--no-modified', 'Ignore modified objects') do
|
|
||||||
@modified = false
|
|
||||||
end
|
|
||||||
common_options(opts)
|
|
||||||
parse_options(opts, args)
|
|
||||||
unless args.size == 2
|
|
||||||
log.puts opts.banner
|
|
||||||
exit(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
args
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module CLI
|
|
||||||
# Display one object
|
|
||||||
# @since 0.8.6
|
|
||||||
class Display < Yardoc
|
|
||||||
def description; 'Displays a formatted object' end
|
|
||||||
|
|
||||||
def initialize(*args)
|
|
||||||
super
|
|
||||||
options.format = :text # default for this command
|
|
||||||
@layout = nil
|
|
||||||
@objects = []
|
|
||||||
end
|
|
||||||
|
|
||||||
# Runs the commandline utility, parsing arguments and displaying an object
|
|
||||||
# from the {Registry}.
|
|
||||||
#
|
|
||||||
# @param [Array<String>] args the list of arguments.
|
|
||||||
# @return [void]
|
|
||||||
def run(*args)
|
|
||||||
return unless parse_arguments(*args)
|
|
||||||
log.puts wrap_layout(format_objects)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [String] the output data for all formatted objects
|
|
||||||
def format_objects
|
|
||||||
@objects.inject([]) do |arr, obj|
|
|
||||||
arr.push obj.format(options)
|
|
||||||
end.join("\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
def wrap_layout(contents)
|
|
||||||
return contents unless @layout
|
|
||||||
opts = options.merge(
|
|
||||||
:contents => contents,
|
|
||||||
:object => @objects.first,
|
|
||||||
:objects => @objects
|
|
||||||
)
|
|
||||||
args = [options.template, @layout, options.format]
|
|
||||||
Templates::Engine.template(*args).run(opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parses commandline options.
|
|
||||||
# @param [Array<String>] args each tokenized argument
|
|
||||||
def parse_arguments(*args)
|
|
||||||
opts = OptionParser.new
|
|
||||||
opts.banner = "Usage: yard display [options] OBJECT [OTHER OBJECTS]"
|
|
||||||
general_options(opts)
|
|
||||||
output_options(opts)
|
|
||||||
parse_options(opts, args)
|
|
||||||
|
|
||||||
Registry.load
|
|
||||||
@objects = args.map {|o| Registry.at(o) }
|
|
||||||
|
|
||||||
# validation
|
|
||||||
return false if @objects.any?(&:nil?)
|
|
||||||
verify_markup_options
|
|
||||||
end
|
|
||||||
|
|
||||||
def output_options(opts)
|
|
||||||
super(opts)
|
|
||||||
opts.on('-l', '--layout [LAYOUT]', 'Wraps output in layout template (good for HTML)') do |layout|
|
|
||||||
@layout = layout || 'layout'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,84 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module CLI
|
|
||||||
# @since 0.6.0
|
|
||||||
class Gems < Command
|
|
||||||
def initialize
|
|
||||||
@rebuild = false
|
|
||||||
@gems = []
|
|
||||||
end
|
|
||||||
|
|
||||||
def description; "Builds YARD index for gems" end
|
|
||||||
|
|
||||||
# Runs the commandline utility, parsing arguments and generating
|
|
||||||
# YARD indexes for gems.
|
|
||||||
#
|
|
||||||
# @param [Array<String>] args the list of arguments
|
|
||||||
# @return [void]
|
|
||||||
def run(*args)
|
|
||||||
require 'rubygems'
|
|
||||||
optparse(*args)
|
|
||||||
build_gems
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Builds .yardoc files for all non-existing gems
|
|
||||||
def build_gems
|
|
||||||
require 'rubygems'
|
|
||||||
@gems.each do |spec|
|
|
||||||
ver = "= #{spec.version}"
|
|
||||||
dir = Registry.yardoc_file_for_gem(spec.name, ver)
|
|
||||||
if dir && File.directory?(dir) && !@rebuild
|
|
||||||
log.debug "#{spec.name} index already exists at '#{dir}'"
|
|
||||||
else
|
|
||||||
yfile = Registry.yardoc_file_for_gem(spec.name, ver, true)
|
|
||||||
next unless yfile
|
|
||||||
next unless File.directory?(spec.full_gem_path)
|
|
||||||
Registry.clear
|
|
||||||
Dir.chdir(spec.full_gem_path) do
|
|
||||||
log.info "Building yardoc index for gem: #{spec.full_name}"
|
|
||||||
Yardoc.run('--no-stats', '-n', '-b', yfile)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_gems(gems)
|
|
||||||
0.step(gems.size - 1, 2) do |index|
|
|
||||||
gem = gems[index]
|
|
||||||
ver_require = gems[index + 1] || ">= 0"
|
|
||||||
specs = YARD::GemIndex.find_all_by_name(gem, ver_require)
|
|
||||||
if specs.empty?
|
|
||||||
log.warn "#{gem} #{ver_require} could not be found in RubyGems index"
|
|
||||||
else
|
|
||||||
@gems += specs
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parses options
|
|
||||||
def optparse(*args)
|
|
||||||
opts = OptionParser.new
|
|
||||||
opts.banner = 'Usage: yard gems [options] [gem_name [version]]'
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "#{description}. If no gem_name is given,"
|
|
||||||
opts.separator "all gems are built."
|
|
||||||
opts.separator ""
|
|
||||||
opts.on('--rebuild', 'Rebuilds index') do
|
|
||||||
@rebuild = true
|
|
||||||
end
|
|
||||||
|
|
||||||
common_options(opts)
|
|
||||||
parse_options(opts, args)
|
|
||||||
add_gems(args)
|
|
||||||
|
|
||||||
if !args.empty? && @gems.empty?
|
|
||||||
log.error "No specified gems could be found for command"
|
|
||||||
elsif @gems.empty?
|
|
||||||
@gems += YARD::GemIndex.all if @gems.empty?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,125 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module CLI
|
|
||||||
# Options to pass to the {Graph} CLI.
|
|
||||||
class GraphOptions < Templates::TemplateOptions
|
|
||||||
# @return [:dot] the default output format
|
|
||||||
default_attr :format, :dot
|
|
||||||
|
|
||||||
# @return [Boolean] whether to list the full class diagram
|
|
||||||
attr_accessor :full
|
|
||||||
|
|
||||||
# @return [Boolean] whether to show the object dependencies
|
|
||||||
attr_accessor :dependencies
|
|
||||||
|
|
||||||
# @return [String] any contents to pass to the digraph
|
|
||||||
attr_accessor :contents
|
|
||||||
end
|
|
||||||
|
|
||||||
# A command-line utility to generate Graphviz graphs from
|
|
||||||
# a set of objects
|
|
||||||
#
|
|
||||||
# @see Graph#run
|
|
||||||
# @since 0.6.0
|
|
||||||
class Graph < YardoptsCommand
|
|
||||||
# The options parsed out of the commandline.
|
|
||||||
# Default options are:
|
|
||||||
# :format => :dot
|
|
||||||
attr_reader :options
|
|
||||||
|
|
||||||
# The set of objects to include in the graph.
|
|
||||||
attr_reader :objects
|
|
||||||
|
|
||||||
# Creates a new instance of the command-line utility
|
|
||||||
def initialize
|
|
||||||
super
|
|
||||||
@use_document_file = false
|
|
||||||
@options = GraphOptions.new
|
|
||||||
options.reset_defaults
|
|
||||||
options.serializer = YARD::Serializers::StdoutSerializer.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def description
|
|
||||||
"Graphs class diagram using Graphviz"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Runs the command-line utility.
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# grapher = Graph.new
|
|
||||||
# grapher.run('--private')
|
|
||||||
# @param [Array<String>] args each tokenized argument
|
|
||||||
def run(*args)
|
|
||||||
parse_arguments(*args)
|
|
||||||
|
|
||||||
contents = objects.map do |o|
|
|
||||||
o.format(options.merge(:serialize => false))
|
|
||||||
end.join("\n")
|
|
||||||
opts = {:type => :layout, :contents => contents}
|
|
||||||
options.update(opts)
|
|
||||||
Templates::Engine.render(options)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def unrecognized_option(err) end
|
|
||||||
|
|
||||||
# Parses commandline options.
|
|
||||||
# @param [Array<String>] args each tokenized argument
|
|
||||||
def optparse(*args)
|
|
||||||
visibilities = [:public]
|
|
||||||
opts = OptionParser.new
|
|
||||||
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "General Options:"
|
|
||||||
|
|
||||||
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('--full', 'Full class diagrams (show methods and attributes).') do
|
|
||||||
options[:full] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('-d', '--dependencies', 'Show mixins in dependency graph.') do
|
|
||||||
options[:dependencies] = 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
|
|
||||||
visibilities.push(:protected)
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--private', "Show or don't show private methods. (default hides private)") do
|
|
||||||
visibilities.push(:private)
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "Output options:"
|
|
||||||
|
|
||||||
opts.on('--dot [OPTIONS]', 'Send the results directly to `dot` with optional arguments.') do |dotopts|
|
|
||||||
options.serializer = Serializers::ProcessSerializer.new('dot ' + dotopts.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('-f', '--file [FILE]', 'Writes output to a file instead of stdout.') do |file|
|
|
||||||
options.serializer = Serializers::FileSystemSerializer.new(:basepath => '.', :extension => nil)
|
|
||||||
options.serializer.instance_eval "def serialized_path(object) #{file.inspect} end"
|
|
||||||
end
|
|
||||||
|
|
||||||
common_options(opts)
|
|
||||||
parse_options(opts, args)
|
|
||||||
|
|
||||||
Registry.load
|
|
||||||
|
|
||||||
expression = "#{visibilities.uniq.inspect}.include?(object.visibility)"
|
|
||||||
options.verifier = Verifier.new(expression)
|
|
||||||
@objects = args.first ?
|
|
||||||
args.map {|o| Registry.at(o) }.compact :
|
|
||||||
[Registry.root]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module CLI
|
|
||||||
# Handles help for commands
|
|
||||||
# @since 0.6.0
|
|
||||||
class Help < Command
|
|
||||||
def description; "Retrieves help for a command" end
|
|
||||||
|
|
||||||
def run(*args)
|
|
||||||
cmd = args.first && CommandParser.commands[args.first.to_sym]
|
|
||||||
if cmd
|
|
||||||
cmd.run('--help')
|
|
||||||
else
|
|
||||||
log.puts "Command #{args.first} not found." if args.first
|
|
||||||
CommandParser.run('--help')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
require "pathname"
|
|
||||||
|
|
||||||
module YARD
|
|
||||||
module CLI
|
|
||||||
# CLI command to support internationalization (a.k.a. i18n).
|
|
||||||
# I18n feature is based on gettext technology.
|
|
||||||
# This command generates .pot file from docstring and extra
|
|
||||||
# documentation.
|
|
||||||
#
|
|
||||||
# @since 0.8.0
|
|
||||||
# @todo Support msgminit and msgmerge features?
|
|
||||||
class I18n < Yardoc
|
|
||||||
def initialize
|
|
||||||
super
|
|
||||||
@options.serializer.basepath = "po/yard.pot"
|
|
||||||
end
|
|
||||||
|
|
||||||
def description
|
|
||||||
'Generates .pot file from source code and extra documentation'
|
|
||||||
end
|
|
||||||
|
|
||||||
def run(*args)
|
|
||||||
if args.empty? || !args.first.nil?
|
|
||||||
# fail early if arguments are not valid
|
|
||||||
return unless parse_arguments(*args)
|
|
||||||
end
|
|
||||||
|
|
||||||
YARD.parse(files, excluded)
|
|
||||||
|
|
||||||
serializer = options.serializer
|
|
||||||
pot_file_path = Pathname.new(serializer.basepath).expand_path
|
|
||||||
pot_file_dir_path, pot_file_basename = pot_file_path.split
|
|
||||||
relative_base_path = Pathname.pwd.relative_path_from(pot_file_dir_path)
|
|
||||||
serializer.basepath = pot_file_dir_path.to_s
|
|
||||||
serializer.serialize(pot_file_basename.to_s,
|
|
||||||
generate_pot(relative_base_path.to_s))
|
|
||||||
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def general_options(opts)
|
|
||||||
opts.banner = "Usage: yard i18n [options] [source_files [- extra_files]]"
|
|
||||||
opts.top.list.clear
|
|
||||||
opts.separator "(if a list of source files is omitted, "
|
|
||||||
opts.separator " {lib,app}/**/*.rb ext/**/*.{c,rb} is used.)"
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "Example: yard i18n -o yard.pot - FAQ LICENSE"
|
|
||||||
opts.separator " The above example outputs .pot file for files in"
|
|
||||||
opts.separator " lib/**/*.rb to yard.pot including the extra files"
|
|
||||||
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."
|
|
||||||
super(opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
def generate_pot(relative_base_path)
|
|
||||||
generator = YARD::I18n::PotGenerator.new(relative_base_path)
|
|
||||||
objects = run_verifier(all_objects)
|
|
||||||
generator.parse_objects(objects)
|
|
||||||
generator.parse_files(options.files || [])
|
|
||||||
generator.generate
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module CLI
|
|
||||||
# Lists all constant and method names in the codebase. Uses {Yardoc} --list.
|
|
||||||
class List < Command
|
|
||||||
def description; 'Lists all constant and methods. Uses `yard doc --list`' end
|
|
||||||
|
|
||||||
# Runs the commandline utility, parsing arguments and displaying a
|
|
||||||
# list of objects
|
|
||||||
#
|
|
||||||
# @param [Array<String>] args the list of arguments.
|
|
||||||
# @return [void]
|
|
||||||
def run(*args)
|
|
||||||
if args.include?('--help')
|
|
||||||
log.puts "Usage: yard list [yardoc_options]"
|
|
||||||
log.puts "Takes the same arguments as yardoc. See yardoc --help"
|
|
||||||
else
|
|
||||||
Yardoc.run('-c', '--list', *args)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module CLI
|
|
||||||
# Lists all markup types
|
|
||||||
# @since 0.8.6
|
|
||||||
class MarkupTypes < Command
|
|
||||||
def description; 'Lists all available markup types and libraries' end
|
|
||||||
|
|
||||||
# Runs the commandline utility, parsing arguments and displaying a
|
|
||||||
# list of markup types
|
|
||||||
#
|
|
||||||
# @param [Array<String>] args the list of arguments.
|
|
||||||
# @return [void]
|
|
||||||
def run(*args) # rubocop:disable Lint/UnusedMethodArgument
|
|
||||||
log.puts "Available markup types for `doc' command:"
|
|
||||||
log.puts
|
|
||||||
types = Templates::Helpers::MarkupHelper::MARKUP_PROVIDERS
|
|
||||||
exts = Templates::Helpers::MarkupHelper::MARKUP_EXTENSIONS
|
|
||||||
types.sort_by {|name, _| name.to_s }.each do |name, providers|
|
|
||||||
log.puts "[#{name}]"
|
|
||||||
libs = providers.map {|p| p[:lib] }.compact
|
|
||||||
log.puts " Providers: #{libs.join(" ")}" unless libs.empty?
|
|
||||||
if exts[name]
|
|
||||||
log.puts " Extensions: #{exts[name].map {|e| ".#{e}" }.join(" ")}"
|
|
||||||
end
|
|
||||||
|
|
||||||
log.puts
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,266 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module YARD
|
|
||||||
module CLI
|
|
||||||
# A local documentation server
|
|
||||||
# @since 0.6.0
|
|
||||||
class Server < Command
|
|
||||||
# @return [Hash] a list of options to pass to the doc server
|
|
||||||
attr_accessor :options
|
|
||||||
|
|
||||||
# @return [Hash] a list of options to pass to the web server
|
|
||||||
attr_accessor :server_options
|
|
||||||
|
|
||||||
# @return [Hash] a list of library names and yardoc files to serve
|
|
||||||
attr_accessor :libraries
|
|
||||||
|
|
||||||
# @return [YARD::Server::Adapter] the adapter to use for loading the web server
|
|
||||||
attr_accessor :adapter
|
|
||||||
|
|
||||||
# @return [Array<String>] a list of scripts to load
|
|
||||||
# @since 0.6.2
|
|
||||||
attr_accessor :scripts
|
|
||||||
|
|
||||||
# @return [Array<String>] a list of template paths to register
|
|
||||||
# @since 0.6.2
|
|
||||||
attr_accessor :template_paths
|
|
||||||
|
|
||||||
# Creates a new instance of the Server command line utility
|
|
||||||
def initialize
|
|
||||||
super
|
|
||||||
self.scripts = []
|
|
||||||
self.template_paths = []
|
|
||||||
self.libraries = {}
|
|
||||||
self.options = SymbolHash.new(false).update(
|
|
||||||
:single_library => true,
|
|
||||||
:caching => false
|
|
||||||
)
|
|
||||||
self.server_options = {:Port => 8808}
|
|
||||||
end
|
|
||||||
|
|
||||||
def description
|
|
||||||
"Runs a local documentation server"
|
|
||||||
end
|
|
||||||
|
|
||||||
def run(*args)
|
|
||||||
optparse(*args)
|
|
||||||
|
|
||||||
select_adapter.setup
|
|
||||||
load_scripts
|
|
||||||
load_template_paths
|
|
||||||
adapter.new(libraries, options, server_options).start
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def load_scripts
|
|
||||||
scripts.each {|file| load_script(file) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_template_paths
|
|
||||||
return if YARD::Config.options[:safe_mode]
|
|
||||||
|
|
||||||
Templates::Engine.template_paths |= template_paths
|
|
||||||
end
|
|
||||||
|
|
||||||
def select_adapter
|
|
||||||
return adapter if adapter
|
|
||||||
|
|
||||||
require 'rubygems'
|
|
||||||
require 'rack'
|
|
||||||
self.adapter = YARD::Server::RackAdapter
|
|
||||||
rescue LoadError
|
|
||||||
self.adapter = YARD::Server::WebrickAdapter
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_libraries(args)
|
|
||||||
(0...args.size).step(2) do |index|
|
|
||||||
library = args[index]
|
|
||||||
dir = args[index + 1]
|
|
||||||
|
|
||||||
libver = nil
|
|
||||||
if dir
|
|
||||||
if File.exist?(dir)
|
|
||||||
# Provided dir contains a .yardopts file
|
|
||||||
libver = create_library_version_if_yardopts_exist(library, dir)
|
|
||||||
libver ||= YARD::Server::LibraryVersion.new(library, nil, dir)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
# Check if this dir contains a .yardopts file
|
|
||||||
pwd = Dir.pwd
|
|
||||||
libver = create_library_version_if_yardopts_exist(library, pwd)
|
|
||||||
|
|
||||||
# Check default location
|
|
||||||
yfile = File.join(pwd, Registry::DEFAULT_YARDOC_FILE)
|
|
||||||
libver ||= YARD::Server::LibraryVersion.new(library, nil, yfile)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Register library
|
|
||||||
if libver
|
|
||||||
libver.yardoc_file = File.expand_path(libver.yardoc_file) if libver.yardoc_file
|
|
||||||
libver.source_path = File.expand_path(libver.source_path) if libver.source_path
|
|
||||||
libraries[library] ||= []
|
|
||||||
libraries[library] |= [libver]
|
|
||||||
else
|
|
||||||
log.warn "Cannot find yardoc db for #{library}: #{dir.inspect}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param [String] library The library name.
|
|
||||||
# @param [String, nil] dir The argument provided on the CLI after the
|
|
||||||
# library name. Is supposed to point to either a project directory
|
|
||||||
# with a Yard options file, or a yardoc db.
|
|
||||||
# @return [LibraryVersion, nil]
|
|
||||||
def create_library_version_if_yardopts_exist(library, dir)
|
|
||||||
if dir
|
|
||||||
options_file = File.join(dir, Yardoc::DEFAULT_YARDOPTS_FILE)
|
|
||||||
if File.exist?(options_file)
|
|
||||||
# Found yardopts, extract db path
|
|
||||||
yfile = extract_db_from_options_file(options_file)
|
|
||||||
db = File.expand_path(yfile, dir)
|
|
||||||
|
|
||||||
# Create libver
|
|
||||||
libver = YARD::Server::LibraryVersion.new(library, nil, db)
|
|
||||||
libver.source_path = dir
|
|
||||||
libver
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_gems
|
|
||||||
require 'rubygems'
|
|
||||||
YARD::GemIndex.each do |spec|
|
|
||||||
libraries[spec.name] ||= []
|
|
||||||
libraries[spec.name] |= [YARD::Server::LibraryVersion.new(spec.name, spec.version.to_s,
|
|
||||||
nil, :gem)]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_gems_from_gemfile(gemfile = nil)
|
|
||||||
require 'bundler'
|
|
||||||
gemfile ||= "Gemfile"
|
|
||||||
if File.exist?("#{gemfile}.lock")
|
|
||||||
Bundler::LockfileParser.new(File.read("#{gemfile}.lock")).specs.each do |spec|
|
|
||||||
libraries[spec.name] ||= []
|
|
||||||
libraries[spec.name] |= [YARD::Server::LibraryVersion.new(spec.name,
|
|
||||||
spec.version.to_s, nil, :gem)]
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.warn "Cannot find #{gemfile}.lock, ignoring --gemfile option"
|
|
||||||
end
|
|
||||||
rescue LoadError
|
|
||||||
log.error "Bundler not available, ignoring --gemfile option"
|
|
||||||
end
|
|
||||||
|
|
||||||
def optparse(*args)
|
|
||||||
opts = OptionParser.new
|
|
||||||
opts.banner = 'Usage: yard server [options] [[library yardoc_file] ...]'
|
|
||||||
opts.separator ''
|
|
||||||
opts.separator 'Example: yard server -m yard .yardoc ruby-core ../ruby/.yardoc'
|
|
||||||
opts.separator 'The above example serves documentation for YARD and Ruby-core'
|
|
||||||
opts.separator ''
|
|
||||||
opts.separator 'If no library/yardoc_file is specified, the server uses'
|
|
||||||
opts.separator 'the name of the current directory and `.yardoc` respectively'
|
|
||||||
opts.separator ''
|
|
||||||
opts.separator "General Options:"
|
|
||||||
opts.on('-m', '--multi-library', 'Serves documentation for multiple libraries') do
|
|
||||||
options[:single_library] = false
|
|
||||||
end
|
|
||||||
opts.on('-c', '--cache', 'Caches all documentation to document root (see --docroot)') do
|
|
||||||
options[:caching] = true
|
|
||||||
end
|
|
||||||
opts.on('-r', '--reload', 'Reparses the library code on each request') do
|
|
||||||
options[:incremental] = true
|
|
||||||
end
|
|
||||||
opts.on('-g', '--gems', 'Serves documentation for installed gems') do
|
|
||||||
add_gems
|
|
||||||
end
|
|
||||||
opts.on('-G', '--gemfile [GEMFILE]',
|
|
||||||
'Serves documentation for gems from Gemfile') do |gemfile|
|
|
||||||
add_gems_from_gemfile(gemfile)
|
|
||||||
end
|
|
||||||
opts.on('-t', '--template-path PATH',
|
|
||||||
'The template path to look for templates in. (used with -t).') do |path|
|
|
||||||
template_paths << path
|
|
||||||
end
|
|
||||||
opts.separator ''
|
|
||||||
opts.separator "Web Server Options:"
|
|
||||||
opts.on('-d', '--daemon', 'Daemonizes the server process') do
|
|
||||||
server_options[:daemonize] = true
|
|
||||||
end
|
|
||||||
opts.on('-B HOST', '--bind', 'The host address to bind to') do |host|
|
|
||||||
server_options[:Host] = host.to_s
|
|
||||||
end
|
|
||||||
opts.on('-p PORT', '--port', 'Serves documentation on PORT') do |port|
|
|
||||||
server_options[:Port] = port.to_i
|
|
||||||
end
|
|
||||||
opts.on('--docroot DOCROOT', 'Uses DOCROOT as document root') do |docroot|
|
|
||||||
server_options[:DocumentRoot] = File.expand_path(docroot)
|
|
||||||
end
|
|
||||||
opts.on('-a', '--adapter ADAPTER',
|
|
||||||
'Use the ADAPTER (full Ruby class) for web server') do |adapter|
|
|
||||||
self.adapter = if adapter.casecmp('webrick') == 0
|
|
||||||
YARD::Server::WebrickAdapter
|
|
||||||
elsif adapter.casecmp('rack') == 0
|
|
||||||
YARD::Server::RackAdapter
|
|
||||||
else
|
|
||||||
eval(adapter) # rubocop:disable Security/Eval
|
|
||||||
end
|
|
||||||
end
|
|
||||||
opts.on('-s', '--server TYPE',
|
|
||||||
'Use a specific server type eg. thin,mongrel,cgi (Rack specific)') do |type|
|
|
||||||
server_options[:server] = type
|
|
||||||
end
|
|
||||||
opts.on('--fork', 'Use process forking when serving requests') do
|
|
||||||
options[:use_fork] = true
|
|
||||||
end
|
|
||||||
common_options(opts)
|
|
||||||
opts.on('-e', '--load FILE',
|
|
||||||
'A Ruby script to load before the source tree is parsed.') do |file|
|
|
||||||
scripts << file
|
|
||||||
end
|
|
||||||
parse_options(opts, args)
|
|
||||||
|
|
||||||
if args.empty? && libraries.empty?
|
|
||||||
# No args - try to use current dir
|
|
||||||
add_libraries([File.basename(Dir.pwd), nil])
|
|
||||||
|
|
||||||
# Generate doc for first time
|
|
||||||
# This is not necessary but makes for a better first-run experience
|
|
||||||
libver = libraries.empty? ? nil : libraries.values.first.first
|
|
||||||
generate_doc_for_first_time(libver) if libver && !libver.ready?
|
|
||||||
else
|
|
||||||
add_libraries(args)
|
|
||||||
options[:single_library] = false if libraries.size > 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def generate_doc_for_first_time(libver)
|
|
||||||
log.enter_level(Logger::INFO) do
|
|
||||||
yardoc_file = libver.yardoc_file.sub(%r{^#{Regexp.quote Dir.pwd}[\\/]+}, '')
|
|
||||||
log.info "No yardoc db found in #{yardoc_file}, parsing source before starting server..."
|
|
||||||
end
|
|
||||||
Dir.chdir(libver.source_path) do
|
|
||||||
Yardoc.run('-n')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def extract_db_from_options_file(options_file)
|
|
||||||
args = File.read_binary(options_file).shell_split
|
|
||||||
db = YARD::Registry.yardoc_file
|
|
||||||
opts = OptionParser.new
|
|
||||||
opts.on('-b', '--db FILE') {|file| db = file }
|
|
||||||
|
|
||||||
begin
|
|
||||||
opts.parse!(args)
|
|
||||||
rescue OptionParser::ParseError
|
|
||||||
args.shift if args.first && args.first[0, 1] != '-'
|
|
||||||
retry
|
|
||||||
end
|
|
||||||
|
|
||||||
db
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,231 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module CLI
|
|
||||||
# @since 0.6.0
|
|
||||||
class Stats < Yardoc
|
|
||||||
include Templates::Helpers::BaseHelper
|
|
||||||
|
|
||||||
# Maintains the order in which +stats_for_+ statistics methods should be
|
|
||||||
# printed.
|
|
||||||
#
|
|
||||||
# @see #print_statistics
|
|
||||||
STATS_ORDER = [:files, :modules, :classes, :constants, :attributes, :methods]
|
|
||||||
|
|
||||||
# @return [Boolean] whether to parse and load registry
|
|
||||||
attr_accessor :parse
|
|
||||||
|
|
||||||
# @param [Boolean] parse whether to parse and load registry (see {#parse})
|
|
||||||
def initialize(parse = true)
|
|
||||||
super()
|
|
||||||
@parse = parse
|
|
||||||
@undoc_list = nil
|
|
||||||
@compact = false
|
|
||||||
end
|
|
||||||
|
|
||||||
def description
|
|
||||||
"Prints documentation statistics on a set of files"
|
|
||||||
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)
|
|
||||||
|
|
||||||
if use_cache
|
|
||||||
Registry.load!
|
|
||||||
elsif parse
|
|
||||||
YARD.parse(files, excluded)
|
|
||||||
Registry.save(use_cache) if save_yardoc
|
|
||||||
end
|
|
||||||
|
|
||||||
print_statistics
|
|
||||||
print_undocumented_objects
|
|
||||||
end
|
|
||||||
|
|
||||||
# Prints statistics for different object types
|
|
||||||
#
|
|
||||||
# To add statistics for a specific type, add a method +#stats_for_TYPE+
|
|
||||||
# to this class that calls {#output}.
|
|
||||||
def print_statistics
|
|
||||||
@total = 0
|
|
||||||
@undocumented = 0
|
|
||||||
meths = methods.map(&:to_s).grep(/^stats_for_/)
|
|
||||||
STATS_ORDER.each do |meth|
|
|
||||||
mname = "stats_for_#{meth}"
|
|
||||||
if meths.include?(mname)
|
|
||||||
send(mname)
|
|
||||||
meths.delete(mname)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
meths.each {|m| send(m) }
|
|
||||||
|
|
||||||
total =
|
|
||||||
if @undocumented == 0
|
|
||||||
100
|
|
||||||
elsif @total == 0
|
|
||||||
0
|
|
||||||
else
|
|
||||||
(@total - @undocumented).to_f / @total.to_f * 100
|
|
||||||
end
|
|
||||||
log.puts("% 3.2f%% documented" % total)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Prints list of undocumented objects
|
|
||||||
def print_undocumented_objects
|
|
||||||
return if !@undoc_list || @undoc_list.empty?
|
|
||||||
log.puts
|
|
||||||
log.puts "Undocumented Objects:"
|
|
||||||
|
|
||||||
# array needed for sort due to unstable sort
|
|
||||||
objects = @undoc_list.sort_by {|o| [o.file.to_s, o.path] }
|
|
||||||
max = objects.max {|a, b| a.path.length <=> b.path.length }.path.length
|
|
||||||
if @compact
|
|
||||||
objects.each do |object|
|
|
||||||
log.puts("%-#{max}s (%s)" % [object.path,
|
|
||||||
[object.file || "-unknown-", object.line].compact.join(":")])
|
|
||||||
end
|
|
||||||
else
|
|
||||||
last_file = nil
|
|
||||||
objects.each do |object|
|
|
||||||
if object.file != last_file
|
|
||||||
log.puts
|
|
||||||
log.puts "(in file: #{object.file || "-unknown-"})"
|
|
||||||
end
|
|
||||||
log.puts object.path
|
|
||||||
last_file = object.file
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Array<CodeObjects::Base>] all the parsed objects in the registry,
|
|
||||||
# removing any objects that are not visible (private, protected) depending
|
|
||||||
# on the arguments passed to the command.
|
|
||||||
def all_objects
|
|
||||||
@all_objects ||= run_verifier Registry.all
|
|
||||||
end
|
|
||||||
|
|
||||||
# Statistics for files
|
|
||||||
def stats_for_files
|
|
||||||
files = []
|
|
||||||
all_objects.each {|o| files |= [o.file] }
|
|
||||||
output "Files", files.size
|
|
||||||
end
|
|
||||||
|
|
||||||
# Statistics for modules
|
|
||||||
def stats_for_modules
|
|
||||||
output "Modules", *type_statistics(:module)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Statistics for classes
|
|
||||||
def stats_for_classes
|
|
||||||
output "Classes", *type_statistics(:class)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Statistics for constants
|
|
||||||
def stats_for_constants
|
|
||||||
output "Constants", *type_statistics(:constant)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Statistics for attributes
|
|
||||||
def stats_for_attributes
|
|
||||||
objs = all_objects.select {|m| m.type == :method && m.is_attribute? }
|
|
||||||
objs.uniq! {|m| m.name.to_s.gsub(/=$/, '') }
|
|
||||||
undoc = objs.select {|m| m.docstring.blank? }
|
|
||||||
@undoc_list |= undoc if @undoc_list
|
|
||||||
output "Attributes", objs.size, undoc.size
|
|
||||||
end
|
|
||||||
|
|
||||||
# Statistics for methods
|
|
||||||
def stats_for_methods
|
|
||||||
objs = all_objects.select {|m| m.type == :method }
|
|
||||||
objs.reject!(&:is_alias?)
|
|
||||||
objs.reject!(&:is_attribute?)
|
|
||||||
undoc = objs.select {|m| m.docstring.blank? }
|
|
||||||
@undoc_list |= undoc if @undoc_list
|
|
||||||
output "Methods", objs.size, undoc.size
|
|
||||||
end
|
|
||||||
|
|
||||||
# Prints a statistic to standard out. This method is optimized for
|
|
||||||
# getting Integer values, though it allows any data to be printed.
|
|
||||||
#
|
|
||||||
# @param [String] name the statistic name
|
|
||||||
# @param [Integer, String] data the numeric (or any) data representing
|
|
||||||
# the statistic. If +data+ is an Integer, it should represent the
|
|
||||||
# total objects of a type.
|
|
||||||
# @param [Integer, nil] undoc number of undocumented objects for the type
|
|
||||||
# @return [void]
|
|
||||||
def output(name, data, undoc = nil)
|
|
||||||
@total += data if data.is_a?(Integer) && undoc
|
|
||||||
@undocumented += undoc if undoc.is_a?(Integer)
|
|
||||||
data =
|
|
||||||
if undoc
|
|
||||||
("%5s (% 5d undocumented)" % [data, undoc])
|
|
||||||
else
|
|
||||||
"%5s" % data
|
|
||||||
end
|
|
||||||
log.puts("%-12s %s" % [name + ":", data])
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def type_statistics(type)
|
|
||||||
objs = all_objects.select {|m| m.type == type }
|
|
||||||
undoc = objs.find_all {|m| m.docstring.blank? }
|
|
||||||
@undoc_list |= undoc if @undoc_list
|
|
||||||
[objs.size, undoc.size]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parses commandline options.
|
|
||||||
# @param [Array<String>] args each tokenized argument
|
|
||||||
def optparse(*args)
|
|
||||||
opts = OptionParser.new
|
|
||||||
opts.banner = "Usage: yard stats [options] [source_files]"
|
|
||||||
|
|
||||||
opts.separator "(if a list of source files is omitted, lib/**/*.rb ext/**/*.{c,rb} is used.)"
|
|
||||||
|
|
||||||
general_options(opts)
|
|
||||||
output_options(opts)
|
|
||||||
tag_options(opts)
|
|
||||||
common_options(opts)
|
|
||||||
parse_options(opts, args)
|
|
||||||
parse_files(*args) unless args.empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
def general_options(opts)
|
|
||||||
super(opts)
|
|
||||||
|
|
||||||
opts.on('--list-undoc', 'List all undocumented objects') do
|
|
||||||
@undoc_list = []
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--compact', 'Compact undocumented objects listing') do
|
|
||||||
@compact = true
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--no-public', "Don't include public methods in statistics.") do
|
|
||||||
visibilities.delete(:public)
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--protected', "Include protected methods in statistics.") do
|
|
||||||
visibilities.push(:protected)
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--private', "Include private methods in statistics.") do
|
|
||||||
visibilities.push(:private)
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--no-private', "Don't include objects with @private tag in statistics.") do
|
|
||||||
options[:verifier].add_expressions '!object.tag(:private) &&
|
|
||||||
(object.namespace.type == :proxy || !object.namespace.tag(:private))'
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--query QUERY', "Only includes objects that match a specific query") do |query|
|
|
||||||
options[:verifier].add_expressions(query.taint)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,789 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
require 'digest/sha1'
|
|
||||||
require 'fileutils'
|
|
||||||
|
|
||||||
module YARD
|
|
||||||
module CLI
|
|
||||||
# Default options used in +yard doc+ command.
|
|
||||||
class YardocOptions < Templates::TemplateOptions
|
|
||||||
# @return [Array<CodeObjects::ExtraFileObject>]
|
|
||||||
# the list of extra files rendered along with objects
|
|
||||||
default_attr :files, lambda { [] }
|
|
||||||
|
|
||||||
# @return [String] the default title appended to each generated page
|
|
||||||
default_attr :title, "Documentation by YARD #{YARD::VERSION}"
|
|
||||||
|
|
||||||
# @return [Verifier] the default verifier object to filter queries
|
|
||||||
default_attr :verifier, lambda { Verifier.new }
|
|
||||||
|
|
||||||
# @return [Serializers::Base] the default serializer for generating output
|
|
||||||
# to disk.
|
|
||||||
default_attr :serializer, lambda { Serializers::FileSystemSerializer.new }
|
|
||||||
|
|
||||||
# @return [Symbol] the default output format (:html).
|
|
||||||
default_attr :format, :html
|
|
||||||
|
|
||||||
# @return [Boolean] whether the data should be rendered in a single page,
|
|
||||||
# if the template supports it.
|
|
||||||
default_attr :onefile, false
|
|
||||||
|
|
||||||
# @return [CodeObjects::ExtraFileObject] the README file object rendered
|
|
||||||
# along with objects
|
|
||||||
attr_accessor :readme
|
|
||||||
|
|
||||||
# @return [Array<CodeObjects::Base>] the list of code objects to render
|
|
||||||
# the templates with.
|
|
||||||
attr_accessor :objects
|
|
||||||
|
|
||||||
# @return [Numeric] An index value for rendering sequentially related templates
|
|
||||||
attr_accessor :index
|
|
||||||
|
|
||||||
# @return [CodeObjects::Base] an extra item to send to a template that is not
|
|
||||||
# the main rendered object
|
|
||||||
attr_accessor :item
|
|
||||||
|
|
||||||
# @return [CodeObjects::ExtraFileObject] the file object being rendered.
|
|
||||||
# The +object+ key is not used so that a file may be rendered in the context
|
|
||||||
# of an object's namespace (for generating links).
|
|
||||||
attr_accessor :file
|
|
||||||
|
|
||||||
# @return [String] the current locale
|
|
||||||
attr_accessor :locale
|
|
||||||
end
|
|
||||||
|
|
||||||
# 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 +yard help doc+ 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 < YardoptsCommand
|
|
||||||
# @return [Hash] the hash of options passed to the template.
|
|
||||||
# @see Templates::Engine#render
|
|
||||||
attr_reader :options
|
|
||||||
|
|
||||||
# @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 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
|
|
||||||
|
|
||||||
# Keep track of which visibilities are to be shown
|
|
||||||
# @return [Array<Symbol>] a list of visibilities
|
|
||||||
# @since 0.5.6
|
|
||||||
attr_accessor :visibilities
|
|
||||||
|
|
||||||
# Keep track of which APIs are to be shown
|
|
||||||
# @return [Array<String>] a list of APIs
|
|
||||||
# @since 0.8.1
|
|
||||||
attr_accessor :apis
|
|
||||||
|
|
||||||
# Keep track of which APIs are to be hidden
|
|
||||||
# @return [Array<String>] a list of APIs to be hidden
|
|
||||||
# @since 0.8.7
|
|
||||||
attr_accessor :hidden_apis
|
|
||||||
|
|
||||||
# @return [Array<Symbol>] a list of tags to hide from templates
|
|
||||||
# @since 0.6.0
|
|
||||||
attr_accessor :hidden_tags
|
|
||||||
|
|
||||||
# @return [Boolean] whether to print statistics after parsing
|
|
||||||
# @since 0.6.0
|
|
||||||
attr_accessor :statistics
|
|
||||||
|
|
||||||
# @return [Array<String>] a list of assets to copy after generation
|
|
||||||
# @since 0.6.0
|
|
||||||
attr_accessor :assets
|
|
||||||
|
|
||||||
# @return [Boolean] whether markup option was specified
|
|
||||||
# @since 0.7.0
|
|
||||||
attr_accessor :has_markup
|
|
||||||
|
|
||||||
# @return [Boolean] whether yard exits with error status code if a warning occurs
|
|
||||||
attr_accessor :fail_on_warning
|
|
||||||
|
|
||||||
# Creates a new instance of the commandline utility
|
|
||||||
def initialize
|
|
||||||
super
|
|
||||||
@options = YardocOptions.new
|
|
||||||
@options.reset_defaults
|
|
||||||
@visibilities = [:public]
|
|
||||||
@apis = []
|
|
||||||
@hidden_apis = []
|
|
||||||
@assets = {}
|
|
||||||
@excluded = []
|
|
||||||
@files = []
|
|
||||||
@hidden_tags = []
|
|
||||||
@use_cache = false
|
|
||||||
@generate = true
|
|
||||||
@statistics = true
|
|
||||||
@list = false
|
|
||||||
@save_yardoc = true
|
|
||||||
@has_markup = false
|
|
||||||
@fail_on_warning = false
|
|
||||||
|
|
||||||
if defined?(::Encoding) && ::Encoding.respond_to?(:default_external=)
|
|
||||||
utf8 = ::Encoding.find('utf-8')
|
|
||||||
|
|
||||||
::Encoding.default_external = utf8 unless ::Encoding.default_external == utf8
|
|
||||||
::Encoding.default_internal = utf8 unless ::Encoding.default_internal == utf8
|
|
||||||
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. If the list only
|
|
||||||
# contains a single nil value, skip calling of {#parse_arguments}
|
|
||||||
# @return [void]
|
|
||||||
def run(*args)
|
|
||||||
log.show_progress = true
|
|
||||||
if args.empty? || !args.first.nil?
|
|
||||||
# fail early if arguments are not valid
|
|
||||||
return unless parse_arguments(*args)
|
|
||||||
end
|
|
||||||
|
|
||||||
checksums = nil
|
|
||||||
if use_cache
|
|
||||||
Registry.load
|
|
||||||
checksums = Registry.checksums.dup
|
|
||||||
end
|
|
||||||
|
|
||||||
if save_yardoc
|
|
||||||
Registry.lock_for_writing do
|
|
||||||
YARD.parse(files, excluded)
|
|
||||||
Registry.save(use_cache)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
YARD.parse(files, excluded)
|
|
||||||
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
|
|
||||||
|
|
||||||
abort if fail_on_warning && log.warned
|
|
||||||
|
|
||||||
true
|
|
||||||
ensure
|
|
||||||
log.show_progress = false
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parses commandline arguments
|
|
||||||
# @param [Array<String>] args the list of arguments
|
|
||||||
# @return [Boolean] whether or not arguments are valid
|
|
||||||
# @since 0.5.6
|
|
||||||
def parse_arguments(*args)
|
|
||||||
super(*args)
|
|
||||||
|
|
||||||
# Last minute modifications
|
|
||||||
self.files = Parser::SourceParser::DEFAULT_PATH_GLOB if files.empty?
|
|
||||||
files.delete_if {|x| x =~ /\A\s*\Z/ } # remove empty ones
|
|
||||||
readme = Dir.glob('README{,*[^~]}').
|
|
||||||
sort_by {|r| [r.count('.'), r.index('.'), r] }.first
|
|
||||||
readme ||= Dir.glob(files.first).first if options.onefile && !files.empty?
|
|
||||||
options.readme ||= CodeObjects::ExtraFileObject.new(readme) if readme
|
|
||||||
options.files.unshift(options.readme).uniq! if options.readme
|
|
||||||
|
|
||||||
Tags::Library.visible_tags -= hidden_tags
|
|
||||||
add_visibility_verifier
|
|
||||||
add_api_verifier
|
|
||||||
|
|
||||||
apply_locale
|
|
||||||
|
|
||||||
# US-ASCII is invalid encoding for onefile
|
|
||||||
if defined?(::Encoding) && options.onefile
|
|
||||||
if ::Encoding.default_internal == ::Encoding::US_ASCII
|
|
||||||
log.warn "--one-file is not compatible with US-ASCII encoding, using ASCII-8BIT"
|
|
||||||
::Encoding.default_external, ::Encoding.default_internal = ['ascii-8bit'] * 2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if generate && !verify_markup_options
|
|
||||||
false
|
|
||||||
else
|
|
||||||
true
|
|
||||||
end
|
|
||||||
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
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Generates output for objects
|
|
||||||
# @param [Hash, nil] checksums if supplied, a list of checkums for files.
|
|
||||||
# @return [void]
|
|
||||||
# @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 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.debug "Re-generating object #{object.path}..."
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
Templates::Engine.generate(objects, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Verifies that the markup options are valid before parsing any code.
|
|
||||||
# Failing early is better than failing late.
|
|
||||||
#
|
|
||||||
# @return (see YARD::Templates::Helpers::MarkupHelper#load_markup_provider)
|
|
||||||
def verify_markup_options
|
|
||||||
result = false
|
|
||||||
lvl = has_markup ? log.level : Logger::FATAL
|
|
||||||
obj = Struct.new(:options).new(options)
|
|
||||||
obj.extend(Templates::Helpers::MarkupHelper)
|
|
||||||
options.files.each do |file|
|
|
||||||
markup = file.attributes[:markup] || obj.markup_for_file('', file.filename)
|
|
||||||
result = obj.load_markup_provider(markup)
|
|
||||||
return false if !result && markup != :rdoc
|
|
||||||
end
|
|
||||||
options.markup = :rdoc unless has_markup
|
|
||||||
log.enter_level(lvl) { result = obj.load_markup_provider }
|
|
||||||
if !result && !has_markup
|
|
||||||
log.warn "Could not load default RDoc formatter, " \
|
|
||||||
"ignoring any markup (install RDoc to get default formatting)."
|
|
||||||
options.markup = :none
|
|
||||||
true
|
|
||||||
else
|
|
||||||
result
|
|
||||||
end
|
|
||||||
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}'"
|
|
||||||
from += '/.' if File.directory?(from)
|
|
||||||
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
|
|
||||||
run_verifier(Registry.all).
|
|
||||||
sort_by {|item| [item.file || '', item.line || 0] }.each do |item|
|
|
||||||
log.puts "#{item.file}:#{item.line}: #{item.path}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Adds a set of extra documentation files to be processed
|
|
||||||
# @param [Array<String>] files the set of documentation files
|
|
||||||
def add_extra_files(*files)
|
|
||||||
files.map! {|f| f.include?("*") ? Dir.glob(f) : f }.flatten!
|
|
||||||
files.each do |file|
|
|
||||||
if extra_file_valid?(file)
|
|
||||||
options.files << CodeObjects::ExtraFileObject.new(file)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param file [String] the filename to validate
|
|
||||||
# @param check_exists [Boolean] whether the file should exist on disk
|
|
||||||
# @return [Boolean] whether the file is allowed to be used
|
|
||||||
def extra_file_valid?(file, check_exists = true)
|
|
||||||
if file =~ %r{^(?:\.\./|/)}
|
|
||||||
log.warn "Invalid file: #{file}"
|
|
||||||
false
|
|
||||||
elsif check_exists && !File.file?(file)
|
|
||||||
log.warn "Could not find file: #{file}"
|
|
||||||
false
|
|
||||||
else
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parses the file arguments into Ruby files and extra files, which are
|
|
||||||
# separated by a '-' element.
|
|
||||||
#
|
|
||||||
# @example Parses a set of Ruby source files
|
|
||||||
# parse_files %w(file1 file2 file3)
|
|
||||||
# @example Parses a set of Ruby files with a separator and extra files
|
|
||||||
# parse_files %w(file1 file2 - extrafile1 extrafile2)
|
|
||||||
# @param [Array<String>] files the list of files to parse
|
|
||||||
# @return [void]
|
|
||||||
def parse_files(*files)
|
|
||||||
seen_extra_files_marker = false
|
|
||||||
|
|
||||||
files.each do |file|
|
|
||||||
if file == "-"
|
|
||||||
seen_extra_files_marker = true
|
|
||||||
next
|
|
||||||
end
|
|
||||||
|
|
||||||
if seen_extra_files_marker
|
|
||||||
add_extra_files(file)
|
|
||||||
else
|
|
||||||
self.files << file
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Adds verifier rule for visibilities
|
|
||||||
# @return [void]
|
|
||||||
# @since 0.5.6
|
|
||||||
def add_visibility_verifier
|
|
||||||
vis_expr = "#{visibilities.uniq.inspect}.include?(object.visibility)"
|
|
||||||
options.verifier.add_expressions(vis_expr)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Adds verifier rule for APIs
|
|
||||||
# @return [void]
|
|
||||||
# @since 0.8.1
|
|
||||||
def add_api_verifier
|
|
||||||
no_api = true if apis.delete('')
|
|
||||||
exprs = []
|
|
||||||
|
|
||||||
exprs << "#{apis.uniq.inspect}.include?(@api.text)" unless apis.empty?
|
|
||||||
|
|
||||||
unless hidden_apis.empty?
|
|
||||||
exprs << "!#{hidden_apis.uniq.inspect}.include?(@api.text)"
|
|
||||||
end
|
|
||||||
|
|
||||||
exprs = !exprs.empty? ? [exprs.join(' && ')] : []
|
|
||||||
exprs << "!@api" if no_api
|
|
||||||
|
|
||||||
expr = exprs.join(' || ')
|
|
||||||
options.verifier.add_expressions(expr) unless expr.empty?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Applies the specified locale to collected objects
|
|
||||||
# @return [void]
|
|
||||||
# @since 0.8.3
|
|
||||||
def apply_locale
|
|
||||||
YARD::I18n::Locale.default = options.locale
|
|
||||||
options.files.each do |file|
|
|
||||||
file.locale = options.locale
|
|
||||||
end
|
|
||||||
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(':')
|
|
||||||
title ||= tag.capitalize
|
|
||||||
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: yard doc [options] [source_files [- extra_files]]"
|
|
||||||
|
|
||||||
opts.separator "(if a list of source files is omitted, "
|
|
||||||
opts.separator " {lib,app}/**/*.rb ext/**/*.{c,rb} is used.)"
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "Example: yardoc -o documentation/ - FAQ LICENSE"
|
|
||||||
opts.separator " The above example outputs documentation for files in"
|
|
||||||
opts.separator " lib/**/*.rb to documentation/ including the extra files"
|
|
||||||
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('-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('--[no-]single-db', 'Whether code objects should be stored to single',
|
|
||||||
' database file (advanced)') do |use_single_db|
|
|
||||||
Registry.single_object_db = use_single_db
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('-n', '--no-output', 'Only generate .yardoc database, no documentation.') do
|
|
||||||
self.generate = false
|
|
||||||
end
|
|
||||||
|
|
||||||
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('--no-cache', "Clear .yardoc db before parsing source.") do
|
|
||||||
self.use_cache = false
|
|
||||||
end
|
|
||||||
|
|
||||||
yardopts_options(opts)
|
|
||||||
|
|
||||||
opts.on('--no-save', 'Do not save the parsed data to the yardoc db') do
|
|
||||||
self.save_yardoc = false
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--exclude REGEXP', 'Ignores a file if it matches path match (regexp)') do |path|
|
|
||||||
excluded << path
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--fail-on-warning', 'Exit with error status code if a warning occurs') do
|
|
||||||
self.fail_on_warning = true
|
|
||||||
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 protected methods. (default hides protected)") do
|
|
||||||
visibilities.push(:protected)
|
|
||||||
end
|
|
||||||
|
|
||||||
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.is_a?(CodeObjects::Proxy) || !object.namespace.tag(:private))'
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--[no-]api API', 'Generates documentation for a given API',
|
|
||||||
'(objects which define the correct @api tag).',
|
|
||||||
'If --no-api is given, displays objects with',
|
|
||||||
'no @api tag.') do |api|
|
|
||||||
api = '' if api == false
|
|
||||||
apis.push(api)
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--hide-api API', 'Hides given @api tag from documentation') do |api|
|
|
||||||
hidden_apis.push(api)
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--embed-mixins', "Embeds mixin methods into class documentation") do
|
|
||||||
options.embed_mixins << '*'
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--embed-mixin [MODULE]', "Embeds mixin methods from a particular",
|
|
||||||
" module into class documentation") do |mod|
|
|
||||||
options.embed_mixins << mod
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--no-highlight', "Don't highlight code blocks in output.") do
|
|
||||||
options.highlight = false
|
|
||||||
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|
|
|
||||||
next if YARD::Config.options[:safe_mode]
|
|
||||||
options.verifier.add_expressions(query.taint)
|
|
||||||
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|
|
|
||||||
if extra_file_valid?(readme)
|
|
||||||
options.readme = CodeObjects::ExtraFileObject.new(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|
|
|
||||||
from, to = *asset.split(':').map {|f| File.cleanpath(f, true) }
|
|
||||||
to ||= from
|
|
||||||
if extra_file_valid?(from, false) && extra_file_valid?(to, false)
|
|
||||||
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|
|
|
||||||
self.has_markup = true
|
|
||||||
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('--charset ENC', 'Character set to use when parsing files ',
|
|
||||||
' (default is system locale)') do |encoding|
|
|
||||||
begin
|
|
||||||
if defined?(Encoding) && Encoding.respond_to?(:default_external=)
|
|
||||||
Encoding.default_external = encoding
|
|
||||||
Encoding.default_internal = encoding
|
|
||||||
end
|
|
||||||
rescue ArgumentError => e
|
|
||||||
raise OptionParser::InvalidOption, e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('-t', '--template TEMPLATE',
|
|
||||||
'The template to use. (defaults to "default")') do |template|
|
|
||||||
options.template = template.to_sym
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('-p', '--template-path PATH',
|
|
||||||
'The template path to look for templates in.',
|
|
||||||
' (used with -t).') do |path|
|
|
||||||
next if YARD::Config.options[:safe_mode]
|
|
||||||
YARD::Templates::Engine.register_template_path(File.expand_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
|
|
||||||
|
|
||||||
opts.on('--no-progress', 'Don\'t show progress bar') do
|
|
||||||
log.show_progress = false
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--locale LOCALE',
|
|
||||||
'The locale for generated documentation.',
|
|
||||||
' (defaults to en)') do |locale|
|
|
||||||
options.locale = locale
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--po-dir DIR',
|
|
||||||
'The directory that has .po files.',
|
|
||||||
" (defaults to #{YARD::Registry.po_dir})") do |dir|
|
|
||||||
YARD::Registry.po_dir = dir
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# 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', 'Marks a tag as transitive') do |tag|
|
|
||||||
Tags::Library.transitive_tags |= [tag.to_sym]
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('--non-transitive-tag TAG', 'Marks a tag as not transitive') do |tag|
|
|
||||||
Tags::Library.transitive_tags -= [tag.to_sym]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,110 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
require 'optparse'
|
|
||||||
|
|
||||||
module YARD
|
|
||||||
module CLI
|
|
||||||
# Abstract base class for command that reads .yardopts file
|
|
||||||
#
|
|
||||||
# @abstract
|
|
||||||
# @since 0.8.3
|
|
||||||
class YardoptsCommand < Command
|
|
||||||
# The configuration filename to load extra options from
|
|
||||||
DEFAULT_YARDOPTS_FILE = ".yardopts"
|
|
||||||
|
|
||||||
# @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
|
|
||||||
|
|
||||||
# The options file name (defaults to {DEFAULT_YARDOPTS_FILE})
|
|
||||||
# @return [String] the filename to load extra options from
|
|
||||||
attr_accessor :options_file
|
|
||||||
|
|
||||||
# Creates a new command that reads .yardopts
|
|
||||||
def initialize
|
|
||||||
super
|
|
||||||
@options_file = DEFAULT_YARDOPTS_FILE
|
|
||||||
@use_yardopts_file = true
|
|
||||||
@use_document_file = true
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parses commandline arguments
|
|
||||||
# @param [Array<String>] args the list of arguments
|
|
||||||
# @return [Boolean] whether or not arguments are valid
|
|
||||||
# @since 0.5.6
|
|
||||||
def parse_arguments(*args)
|
|
||||||
parse_yardopts_options(*args)
|
|
||||||
|
|
||||||
# Parse files and then command line arguments
|
|
||||||
parse_rdoc_document_file
|
|
||||||
parse_yardopts
|
|
||||||
optparse(*args)
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# Adds --[no-]yardopts / --[no-]document
|
|
||||||
def yardopts_options(opts)
|
|
||||||
opts.on('--[no-]yardopts [FILE]',
|
|
||||||
"If arguments should be read from FILE",
|
|
||||||
" (defaults to yes, FILE defaults to .yardopts)") do |use_yardopts|
|
|
||||||
if use_yardopts.is_a?(String)
|
|
||||||
self.options_file = use_yardopts
|
|
||||||
self.use_yardopts_file = true
|
|
||||||
else
|
|
||||||
self.use_yardopts_file = (use_yardopts != false)
|
|
||||||
end
|
|
||||||
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
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Parses the .yardopts file for default yard options
|
|
||||||
# @return [Array<String>] an array of options parsed from .yardopts
|
|
||||||
def yardopts(file = options_file)
|
|
||||||
return [] unless use_yardopts_file
|
|
||||||
File.read_binary(file).shell_split
|
|
||||||
rescue Errno::ENOENT
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parses out the yardopts/document options
|
|
||||||
def parse_yardopts_options(*args)
|
|
||||||
opts = OptionParser.new
|
|
||||||
opts.base.long.clear # HACK: why are --help and --version defined?
|
|
||||||
yardopts_options(opts)
|
|
||||||
begin
|
|
||||||
opts.parse(args)
|
|
||||||
rescue OptionParser::ParseError => err
|
|
||||||
idx = args.index(err.args.first)
|
|
||||||
args = args[(idx + 1)..-1]
|
|
||||||
args.shift while args.first && args.first[0, 1] != '-'
|
|
||||||
retry
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_rdoc_document_file(file = '.document')
|
|
||||||
optparse(*support_rdoc_document_file!(file)) if use_document_file
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_yardopts(file = options_file)
|
|
||||||
optparse(*yardopts(file)) if use_yardopts_file
|
|
||||||
end
|
|
||||||
|
|
||||||
# Reads a .document file in the directory to get source file globs
|
|
||||||
# @return [Array<String>] an array of files parsed from .document
|
|
||||||
def support_rdoc_document_file!(file = '.document')
|
|
||||||
return [] unless use_document_file
|
|
||||||
File.read(file).gsub(/^[ \t]*#.+/m, '').split(/\s+/)
|
|
||||||
rescue Errno::ENOENT
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,215 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
require 'rbconfig'
|
|
||||||
|
|
||||||
module YARD
|
|
||||||
module CLI
|
|
||||||
# A tool to view documentation in the console like `ri`
|
|
||||||
class YRI < Command
|
|
||||||
# The location in {YARD::CONFIG_DIR} where the YRI cache file is loaded
|
|
||||||
# from.
|
|
||||||
CACHE_FILE = File.expand_path(File.join(YARD::Config::CONFIG_DIR, 'yri_cache'))
|
|
||||||
|
|
||||||
# A file containing all paths, delimited by newlines, to search for
|
|
||||||
# yardoc databases.
|
|
||||||
# @since 0.5.1
|
|
||||||
SEARCH_PATHS_FILE = File.expand_path(File.join(YARD::Config::CONFIG_DIR, 'yri_search_paths'))
|
|
||||||
|
|
||||||
# Default search paths that should be loaded dynamically into YRI. These paths
|
|
||||||
# take precedence over all other paths ({SEARCH_PATHS_FILE} and RubyGems
|
|
||||||
# paths). To add a path, call:
|
|
||||||
#
|
|
||||||
# DEFAULT_SEARCH_PATHS.push("/path/to/.yardoc")
|
|
||||||
#
|
|
||||||
# @return [Array<String>] a list of extra search paths
|
|
||||||
# @since 0.6.0
|
|
||||||
DEFAULT_SEARCH_PATHS = []
|
|
||||||
|
|
||||||
# Helper method to run the utility on an instance.
|
|
||||||
# @see #run
|
|
||||||
def self.run(*args) new.run(*args) end
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
super
|
|
||||||
@cache = {}
|
|
||||||
@search_paths = []
|
|
||||||
add_default_paths
|
|
||||||
add_gem_paths
|
|
||||||
load_cache
|
|
||||||
@search_paths.uniq!
|
|
||||||
end
|
|
||||||
|
|
||||||
def description
|
|
||||||
"A tool to view documentation in the console like `ri`"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Runs the command-line utility.
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# YRI.new.run('String#reverse')
|
|
||||||
# @param [Array<String>] args each tokenized argument
|
|
||||||
def run(*args)
|
|
||||||
optparse(*args)
|
|
||||||
|
|
||||||
if ::RbConfig::CONFIG['host_os'] =~ /mingw|win32/
|
|
||||||
@serializer ||= YARD::Serializers::StdoutSerializer.new
|
|
||||||
else
|
|
||||||
@serializer ||= YARD::Serializers::ProcessSerializer.new('less')
|
|
||||||
end
|
|
||||||
|
|
||||||
if @name.nil? || @name.strip.empty?
|
|
||||||
print_usage
|
|
||||||
return exit(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
object = find_object(@name)
|
|
||||||
if object
|
|
||||||
print_object(object)
|
|
||||||
else
|
|
||||||
STDERR.puts "No documentation for `#{@name}'"
|
|
||||||
return exit(1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# Prints the command usage
|
|
||||||
# @return [void]
|
|
||||||
# @since 0.5.6
|
|
||||||
def print_usage
|
|
||||||
log.puts "Usage: yri [options] <Path to object>"
|
|
||||||
log.puts "See yri --help for more options."
|
|
||||||
end
|
|
||||||
|
|
||||||
# Caches the .yardoc file where an object can be found in the {CACHE_FILE}
|
|
||||||
# @return [void]
|
|
||||||
def cache_object(name, path)
|
|
||||||
return if path == Registry.yardoc_file
|
|
||||||
@cache[name] = path
|
|
||||||
|
|
||||||
File.open!(CACHE_FILE, 'w') do |file|
|
|
||||||
@cache.each do |key, value|
|
|
||||||
file.puts("#{key} #{value}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param [CodeObjects::Base] object the object to print.
|
|
||||||
# @return [String] the formatted output for an object.
|
|
||||||
def print_object(object)
|
|
||||||
if object.type == :method && object.is_alias?
|
|
||||||
tmp = P(object.namespace, (object.scope == :instance ? "#" : "") +
|
|
||||||
object.namespace.aliases[object].to_s)
|
|
||||||
object = tmp unless YARD::CodeObjects::Proxy === tmp
|
|
||||||
end
|
|
||||||
object.format(:serializer => @serializer)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Locates an object by name starting in the cached paths and then
|
|
||||||
# searching through any search paths.
|
|
||||||
#
|
|
||||||
# @param [String] name the full name of the object
|
|
||||||
# @return [CodeObjects::Base] an object if found
|
|
||||||
# @return [nil] if no object is found
|
|
||||||
def find_object(name)
|
|
||||||
@search_paths.unshift(@cache[name]) if @cache[name]
|
|
||||||
@search_paths.unshift(Registry.yardoc_file)
|
|
||||||
|
|
||||||
# Try to load it from in memory cache
|
|
||||||
log.debug "Searching for #{name} in memory"
|
|
||||||
obj = try_load_object(name, nil)
|
|
||||||
return obj if obj
|
|
||||||
|
|
||||||
log.debug "Searching for #{name} in search paths"
|
|
||||||
@search_paths.each do |path|
|
|
||||||
next unless File.exist?(path)
|
|
||||||
log.debug "Searching for #{name} in #{path}..."
|
|
||||||
Registry.load(path)
|
|
||||||
obj = try_load_object(name, path)
|
|
||||||
return obj if obj
|
|
||||||
end
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Tries to load the object with name. If successful, caches the object
|
|
||||||
# with the cache_path
|
|
||||||
#
|
|
||||||
# @param [String] name the object path
|
|
||||||
# @param [String] cache_path the location of the yardoc
|
|
||||||
# db containing the object to cache for future lookups.
|
|
||||||
# No caching is done if this is nil.
|
|
||||||
# @return [void]
|
|
||||||
def try_load_object(name, cache_path)
|
|
||||||
obj = Registry.at(name)
|
|
||||||
cache_object(name, cache_path) if obj && cache_path
|
|
||||||
obj
|
|
||||||
end
|
|
||||||
|
|
||||||
# Loads {CACHE_FILE}
|
|
||||||
# @return [void]
|
|
||||||
def load_cache
|
|
||||||
return unless File.file?(CACHE_FILE)
|
|
||||||
File.readlines(CACHE_FILE).each do |line|
|
|
||||||
line = line.strip.split(/\s+/)
|
|
||||||
@cache[line[0]] = line[1]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Adds all RubyGems yardoc files to search paths
|
|
||||||
# @return [void]
|
|
||||||
def add_gem_paths
|
|
||||||
require 'rubygems'
|
|
||||||
gem_paths = []
|
|
||||||
YARD::GemIndex.each do |spec|
|
|
||||||
yfile = Registry.yardoc_file_for_gem(spec.name)
|
|
||||||
next if yfile.nil?
|
|
||||||
|
|
||||||
if spec.name =~ /^yard-doc-/
|
|
||||||
gem_paths.unshift(yfile)
|
|
||||||
else
|
|
||||||
gem_paths.push(yfile)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@search_paths += gem_paths
|
|
||||||
rescue LoadError
|
|
||||||
nil # noop
|
|
||||||
end
|
|
||||||
|
|
||||||
# Adds paths in {SEARCH_PATHS_FILE}
|
|
||||||
# @since 0.5.1
|
|
||||||
def add_default_paths
|
|
||||||
@search_paths.concat(DEFAULT_SEARCH_PATHS)
|
|
||||||
return unless File.file?(SEARCH_PATHS_FILE)
|
|
||||||
paths = File.readlines(SEARCH_PATHS_FILE).map(&:strip)
|
|
||||||
@search_paths.concat(paths)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parses commandline options.
|
|
||||||
# @param [Array<String>] args each tokenized argument
|
|
||||||
def optparse(*args)
|
|
||||||
opts = OptionParser.new
|
|
||||||
opts.banner = "Usage: yri [options] <Path to object>"
|
|
||||||
opts.separator "Example: yri String#gsub"
|
|
||||||
opts.separator ""
|
|
||||||
opts.separator "General Options:"
|
|
||||||
|
|
||||||
opts.on('-b', '--db FILE', 'Use a specified .yardoc db to search in') do |yfile|
|
|
||||||
@search_paths.unshift(yfile)
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('-T', '--no-pager', 'No pager') do
|
|
||||||
@serializer = YARD::Serializers::StdoutSerializer.new
|
|
||||||
end
|
|
||||||
|
|
||||||
opts.on('-p PAGER', '--pager') do |pager|
|
|
||||||
@serializer = YARD::Serializers::ProcessSerializer.new(pager)
|
|
||||||
end
|
|
||||||
|
|
||||||
common_options(opts)
|
|
||||||
parse_options(opts, args)
|
|
||||||
@name = args.first
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,622 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module CodeObjects
|
|
||||||
# A list of code objects. This array acts like a set (no unique items)
|
|
||||||
# but also disallows any {Proxy} objects from being added.
|
|
||||||
class CodeObjectList < Array
|
|
||||||
# Creates a new object list associated with a namespace
|
|
||||||
#
|
|
||||||
# @param [NamespaceObject] owner the namespace the list should be associated with
|
|
||||||
# @return [CodeObjectList]
|
|
||||||
def initialize(owner = Registry.root)
|
|
||||||
@owner = owner
|
|
||||||
end
|
|
||||||
|
|
||||||
# Adds a new value to the list
|
|
||||||
#
|
|
||||||
# @param [Base] value a code object to add
|
|
||||||
# @return [CodeObjectList] self
|
|
||||||
def push(value)
|
|
||||||
value = Proxy.new(@owner, value) if value.is_a?(String) || value.is_a?(Symbol)
|
|
||||||
if value.is_a?(CodeObjects::Base) || value.is_a?(Proxy)
|
|
||||||
super(value) unless include?(value)
|
|
||||||
else
|
|
||||||
raise ArgumentError, "#{value.class} is not a valid CodeObject"
|
|
||||||
end
|
|
||||||
self
|
|
||||||
end
|
|
||||||
alias << push
|
|
||||||
end
|
|
||||||
|
|
||||||
extend NamespaceMapper
|
|
||||||
|
|
||||||
# Namespace separator
|
|
||||||
NSEP = '::'
|
|
||||||
|
|
||||||
# Regex-quoted namespace separator
|
|
||||||
NSEPQ = NSEP
|
|
||||||
|
|
||||||
# Instance method separator
|
|
||||||
ISEP = '#'
|
|
||||||
|
|
||||||
# Regex-quoted instance method separator
|
|
||||||
ISEPQ = ISEP
|
|
||||||
|
|
||||||
# Class method separator
|
|
||||||
CSEP = '.'
|
|
||||||
|
|
||||||
# Regex-quoted class method separator
|
|
||||||
CSEPQ = Regexp.quote CSEP
|
|
||||||
|
|
||||||
# Regular expression to match constant name
|
|
||||||
CONSTANTMATCH = /[A-Z]\w*/
|
|
||||||
|
|
||||||
# Regular expression to match the beginning of a constant
|
|
||||||
CONSTANTSTART = /^[A-Z]/
|
|
||||||
|
|
||||||
# Regular expression to match namespaces (const A or complex path A::B)
|
|
||||||
NAMESPACEMATCH = /(?:(?:#{NSEPQ}\s*)?#{CONSTANTMATCH})+/
|
|
||||||
|
|
||||||
# Regular expression to match a method name
|
|
||||||
METHODNAMEMATCH = %r{[a-zA-Z_]\w*[!?=]?|[-+~]\@|<<|>>|=~|===?|![=~]?|<=>|[<>]=?|\*\*|[-/+%^&*~`|]|\[\]=?}
|
|
||||||
|
|
||||||
# Regular expression to match a fully qualified method def (self.foo, Class.foo).
|
|
||||||
METHODMATCH = /(?:(?:#{NAMESPACEMATCH}|[a-z]\w*)\s*(?:#{CSEPQ}|#{NSEPQ})\s*)?#{METHODNAMEMATCH}/
|
|
||||||
|
|
||||||
# All builtin Ruby exception classes for inheritance tree.
|
|
||||||
BUILTIN_EXCEPTIONS = ["ArgumentError", "ClosedQueueError", "EncodingError",
|
|
||||||
"EOFError", "Exception", "FiberError", "FloatDomainError", "IndexError",
|
|
||||||
"Interrupt", "IOError", "KeyError", "LoadError", "LocalJumpError",
|
|
||||||
"NameError", "NoMemoryError", "NoMethodError", "NotImplementedError",
|
|
||||||
"RangeError", "RegexpError", "RuntimeError", "ScriptError", "SecurityError",
|
|
||||||
"SignalException", "StandardError", "StopIteration", "SyntaxError",
|
|
||||||
"SystemCallError", "SystemExit", "SystemStackError", "ThreadError",
|
|
||||||
"TypeError", "UncaughtThrowError", "ZeroDivisionError"]
|
|
||||||
|
|
||||||
# All builtin Ruby classes for inheritance tree.
|
|
||||||
# @note MatchingData is a 1.8.x legacy class
|
|
||||||
BUILTIN_CLASSES = ["Array", "Bignum", "Binding", "Class", "Complex",
|
|
||||||
"ConditionVariable", "Data", "Dir", "Encoding", "Enumerator", "FalseClass",
|
|
||||||
"Fiber", "File", "Fixnum", "Float", "Hash", "IO", "Integer", "MatchData",
|
|
||||||
"Method", "Module", "NilClass", "Numeric", "Object", "Proc", "Queue",
|
|
||||||
"Random", "Range", "Rational", "Regexp", "RubyVM", "SizedQueue", "String",
|
|
||||||
"Struct", "Symbol", "Thread", "ThreadGroup", "Time", "TracePoint",
|
|
||||||
"TrueClass", "UnboundMethod"] + BUILTIN_EXCEPTIONS
|
|
||||||
|
|
||||||
# All builtin Ruby modules for mixin handling.
|
|
||||||
BUILTIN_MODULES = ["Comparable", "Enumerable", "Errno", "FileTest", "GC",
|
|
||||||
"Kernel", "Marshal", "Math", "ObjectSpace", "Precision", "Process", "Signal"]
|
|
||||||
|
|
||||||
# All builtin Ruby classes and modules.
|
|
||||||
BUILTIN_ALL = BUILTIN_CLASSES + BUILTIN_MODULES
|
|
||||||
|
|
||||||
# Hash of {BUILTIN_EXCEPTIONS} as keys and true as value (for O(1) lookups)
|
|
||||||
BUILTIN_EXCEPTIONS_HASH = BUILTIN_EXCEPTIONS.inject({}) {|h, n| h.update(n => true) }
|
|
||||||
|
|
||||||
# +Base+ is the superclass of all code objects recognized by YARD. A code
|
|
||||||
# object is any entity in the Ruby language (class, method, module). A
|
|
||||||
# DSL might subclass +Base+ to create a new custom object representing
|
|
||||||
# a new entity type.
|
|
||||||
#
|
|
||||||
# == Registry Integration
|
|
||||||
# Any created object associated with a namespace is immediately registered
|
|
||||||
# with the registry. This allows the Registry to act as an identity map
|
|
||||||
# to ensure that no object is represented by more than one Ruby object
|
|
||||||
# in memory. A unique {#path} is essential for this identity map to work
|
|
||||||
# correctly.
|
|
||||||
#
|
|
||||||
# == Custom Attributes
|
|
||||||
# Code objects allow arbitrary custom attributes to be set using the
|
|
||||||
# {#[]=} assignment method.
|
|
||||||
#
|
|
||||||
# == Namespaces
|
|
||||||
# There is a special type of object called a "namespace". These are subclasses
|
|
||||||
# of the {NamespaceObject} and represent Ruby entities that can have
|
|
||||||
# objects defined within them. Classically these are modules and classes,
|
|
||||||
# though a DSL might create a custom {NamespaceObject} to describe a
|
|
||||||
# specific set of objects.
|
|
||||||
#
|
|
||||||
# == Separators
|
|
||||||
# Custom classes with different separator tokens should define their own
|
|
||||||
# separators using the {NamespaceMapper.register_separator} method. The
|
|
||||||
# standard Ruby separators have already been defined ('::', '#', '.', etc).
|
|
||||||
#
|
|
||||||
# @abstract This class should not be used directly. Instead, create a
|
|
||||||
# subclass that implements {#path}, {#sep} or {#type}. You might also
|
|
||||||
# need to register custom separators if {#sep} uses alternate separator
|
|
||||||
# tokens.
|
|
||||||
# @see Registry
|
|
||||||
# @see #path
|
|
||||||
# @see #[]=
|
|
||||||
# @see NamespaceObject
|
|
||||||
# @see NamespaceMapper.register_separator
|
|
||||||
class Base
|
|
||||||
# The files the object was defined in. To add a file, use {#add_file}.
|
|
||||||
# @return [Array<Array(String, Integer)>] a list of files
|
|
||||||
# @see #add_file
|
|
||||||
attr_reader :files
|
|
||||||
|
|
||||||
# The namespace the object is defined in. If the object is in the
|
|
||||||
# top level namespace, this is {Registry.root}
|
|
||||||
# @return [NamespaceObject] the namespace object
|
|
||||||
attr_reader :namespace
|
|
||||||
|
|
||||||
# The source code associated with the object
|
|
||||||
# @return [String, nil] source, if present, or nil
|
|
||||||
attr_reader :source
|
|
||||||
|
|
||||||
# Language of the source code associated with the object. Defaults to
|
|
||||||
# +:ruby+.
|
|
||||||
#
|
|
||||||
# @return [Symbol] the language type
|
|
||||||
attr_accessor :source_type
|
|
||||||
|
|
||||||
# The one line signature representing an object. For a method, this will
|
|
||||||
# be of the form "def meth(arguments...)". This is usually the first
|
|
||||||
# source line.
|
|
||||||
#
|
|
||||||
# @return [String] a line of source
|
|
||||||
attr_accessor :signature
|
|
||||||
|
|
||||||
# The non-localized documentation string associated with the object
|
|
||||||
# @return [Docstring] the documentation string
|
|
||||||
# @since 0.8.4
|
|
||||||
attr_reader :base_docstring
|
|
||||||
undef base_docstring
|
|
||||||
def base_docstring; @docstring end
|
|
||||||
|
|
||||||
# Marks whether or not the method is conditionally defined at runtime
|
|
||||||
# @return [Boolean] true if the method is conditionally defined at runtime
|
|
||||||
attr_accessor :dynamic
|
|
||||||
|
|
||||||
# @return [String] the group this object is associated with
|
|
||||||
# @since 0.6.0
|
|
||||||
attr_accessor :group
|
|
||||||
|
|
||||||
# Is the object defined conditionally at runtime?
|
|
||||||
# @see #dynamic
|
|
||||||
def dynamic?; @dynamic end
|
|
||||||
|
|
||||||
# @return [Symbol] the visibility of an object (:public, :private, :protected)
|
|
||||||
attr_accessor :visibility
|
|
||||||
undef visibility=
|
|
||||||
def visibility=(v) @visibility = v.to_sym end
|
|
||||||
|
|
||||||
class << self
|
|
||||||
# Allocates a new code object
|
|
||||||
# @return [Base]
|
|
||||||
# @see #initialize
|
|
||||||
def new(namespace, name, *args, &block)
|
|
||||||
raise ArgumentError, "invalid empty object name" if name.to_s.empty?
|
|
||||||
if namespace.is_a?(ConstantObject)
|
|
||||||
unless namespace.value =~ /\A#{NAMESPACEMATCH}\Z/
|
|
||||||
raise Parser::UndocumentableError, "constant mapping"
|
|
||||||
end
|
|
||||||
|
|
||||||
namespace = Proxy.new(namespace.namespace, namespace.value)
|
|
||||||
end
|
|
||||||
|
|
||||||
if name.to_s[0, 2] == NSEP
|
|
||||||
name = name.to_s[2..-1]
|
|
||||||
namespace = Registry.root
|
|
||||||
end
|
|
||||||
|
|
||||||
if name =~ /(?:#{NSEPQ})([^:]+)$/
|
|
||||||
return new(Proxy.new(namespace, $`), $1, *args, &block)
|
|
||||||
end
|
|
||||||
|
|
||||||
obj = super(namespace, name, *args)
|
|
||||||
existing_obj = Registry.at(obj.path)
|
|
||||||
obj = existing_obj if existing_obj && existing_obj.class == self
|
|
||||||
yield(obj) if block_given?
|
|
||||||
obj
|
|
||||||
end
|
|
||||||
|
|
||||||
# Compares the class with subclasses
|
|
||||||
#
|
|
||||||
# @param [Object] other the other object to compare classes with
|
|
||||||
# @return [Boolean] true if other is a subclass of self
|
|
||||||
def ===(other)
|
|
||||||
other.is_a?(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Creates a new code object
|
|
||||||
#
|
|
||||||
# @example Create a method in the root namespace
|
|
||||||
# CodeObjects::Base.new(:root, '#method') # => #<yardoc method #method>
|
|
||||||
# @example Create class Z inside namespace X::Y
|
|
||||||
# CodeObjects::Base.new(P("X::Y"), :Z) # or
|
|
||||||
# CodeObjects::Base.new(Registry.root, "X::Y")
|
|
||||||
# @param [NamespaceObject] namespace the namespace the object belongs in,
|
|
||||||
# {Registry.root} or :root should be provided if it is associated with
|
|
||||||
# the top level namespace.
|
|
||||||
# @param [Symbol, String] name the name (or complex path) of the object.
|
|
||||||
# @yield [self] a block to perform any extra initialization on the object
|
|
||||||
# @yieldparam [Base] self the newly initialized code object
|
|
||||||
# @return [Base] the newly created object
|
|
||||||
def initialize(namespace, name, *)
|
|
||||||
if namespace && namespace != :root &&
|
|
||||||
!namespace.is_a?(NamespaceObject) && !namespace.is_a?(Proxy)
|
|
||||||
raise ArgumentError, "Invalid namespace object: #{namespace}"
|
|
||||||
end
|
|
||||||
|
|
||||||
@files = []
|
|
||||||
@current_file_has_comments = false
|
|
||||||
@name = name.to_sym
|
|
||||||
@source_type = :ruby
|
|
||||||
@visibility = :public
|
|
||||||
@tags = []
|
|
||||||
@docstrings = {}
|
|
||||||
@docstring = Docstring.new!('', [], self)
|
|
||||||
@namespace = nil
|
|
||||||
self.namespace = namespace
|
|
||||||
yield(self) if block_given?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Copies all data in this object to another code object, except for
|
|
||||||
# uniquely identifying information (path, namespace, name, scope).
|
|
||||||
#
|
|
||||||
# @param [Base] other the object to copy data to
|
|
||||||
# @return [Base] the other object
|
|
||||||
# @since 0.8.0
|
|
||||||
def copy_to(other)
|
|
||||||
copyable_attributes.each do |ivar|
|
|
||||||
ivar = "@#{ivar}"
|
|
||||||
other.instance_variable_set(ivar, instance_variable_get(ivar))
|
|
||||||
end
|
|
||||||
other.docstring = @docstring.to_raw
|
|
||||||
other
|
|
||||||
end
|
|
||||||
|
|
||||||
# The name of the object
|
|
||||||
# @param [Boolean] prefix whether to show a prefix. Implement
|
|
||||||
# this in a subclass to define how the prefix is showed.
|
|
||||||
# @return [Symbol] if prefix is false, the symbolized name
|
|
||||||
# @return [String] if prefix is true, prefix + the name as a String.
|
|
||||||
# This must be implemented by the subclass.
|
|
||||||
def name(prefix = false)
|
|
||||||
prefix ? @name.to_s : (defined?(@name) && @name)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Associates a file with a code object, optionally adding the line where it was defined.
|
|
||||||
# By convention, '<stdin>' should be used to associate code that comes form standard input.
|
|
||||||
#
|
|
||||||
# @param [String] file the filename ('<stdin>' for standard input)
|
|
||||||
# @param [Fixnum, nil] line the line number where the object lies in the file
|
|
||||||
# @param [Boolean] has_comments whether or not the definition has comments associated. This
|
|
||||||
# will allow {#file} to return the definition where the comments were made instead
|
|
||||||
# of any empty definitions that might have been parsed before (module namespaces for instance).
|
|
||||||
def add_file(file, line = nil, has_comments = false)
|
|
||||||
raise(ArgumentError, "file cannot be nil or empty") if file.nil? || file == ''
|
|
||||||
obj = [file.to_s, line]
|
|
||||||
return if files.include?(obj)
|
|
||||||
if has_comments && !@current_file_has_comments
|
|
||||||
@current_file_has_comments = true
|
|
||||||
@files.unshift(obj)
|
|
||||||
else
|
|
||||||
@files << obj # back of the line
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the filename the object was first parsed at, taking
|
|
||||||
# definitions with docstrings first.
|
|
||||||
#
|
|
||||||
# @return [String] a filename
|
|
||||||
def file
|
|
||||||
@files.first ? @files.first[0] : nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the line the object was first parsed at (or nil)
|
|
||||||
#
|
|
||||||
# @return [Fixnum] the line where the object was first defined.
|
|
||||||
# @return [nil] if there is no line associated with the object
|
|
||||||
def line
|
|
||||||
@files.first ? @files.first[1] : nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Tests if another object is equal to this, including a proxy
|
|
||||||
# @param [Base, Proxy] other if other is a {Proxy}, tests if
|
|
||||||
# the paths are equal
|
|
||||||
# @return [Boolean] whether or not the objects are considered the same
|
|
||||||
def equal?(other)
|
|
||||||
if other.is_a?(Base) || other.is_a?(Proxy)
|
|
||||||
path == other.path
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
alias == equal?
|
|
||||||
alias eql? equal?
|
|
||||||
|
|
||||||
# @return [Integer] the object's hash value (for equality checking)
|
|
||||||
def hash; path.hash end
|
|
||||||
|
|
||||||
# @return [nil] this object does not turn into an array
|
|
||||||
def to_ary; nil end
|
|
||||||
|
|
||||||
# Accesses a custom attribute on the object
|
|
||||||
# @param [#to_s] key the name of the custom attribute
|
|
||||||
# @return [Object, nil] the custom attribute or nil if not found.
|
|
||||||
# @see #[]=
|
|
||||||
def [](key)
|
|
||||||
if respond_to?(key)
|
|
||||||
send(key)
|
|
||||||
elsif instance_variable_defined?("@#{key}")
|
|
||||||
instance_variable_get("@#{key}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Sets a custom attribute on the object
|
|
||||||
# @param [#to_s] key the name of the custom attribute
|
|
||||||
# @param [Object] value the value to associate
|
|
||||||
# @return [void]
|
|
||||||
# @see #[]
|
|
||||||
def []=(key, value)
|
|
||||||
if respond_to?("#{key}=")
|
|
||||||
send("#{key}=", value)
|
|
||||||
else
|
|
||||||
instance_variable_set("@#{key}", value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @overload dynamic_attr_name
|
|
||||||
# @return the value of attribute named by the method attribute name
|
|
||||||
# @raise [NoMethodError] if no method or custom attribute exists by
|
|
||||||
# the attribute name
|
|
||||||
# @see #[]
|
|
||||||
# @overload dynamic_attr_name=(value)
|
|
||||||
# @param value a value to set
|
|
||||||
# @return +value+
|
|
||||||
# @see #[]=
|
|
||||||
def method_missing(meth, *args, &block)
|
|
||||||
if meth.to_s =~ /=$/
|
|
||||||
self[meth.to_s[0..-2]] = args.first
|
|
||||||
elsif instance_variable_get("@#{meth}")
|
|
||||||
self[meth]
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Attaches source code to a code object with an optional file location
|
|
||||||
#
|
|
||||||
# @param [#source, String] statement
|
|
||||||
# the +Parser::Statement+ holding the source code or the raw source
|
|
||||||
# as a +String+ for the definition of the code object only (not the block)
|
|
||||||
def source=(statement)
|
|
||||||
if statement.respond_to?(:source)
|
|
||||||
@source = format_source(statement.source.strip)
|
|
||||||
else
|
|
||||||
@source = format_source(statement.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
if statement.respond_to?(:signature)
|
|
||||||
self.signature = statement.signature
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# The documentation string associated with the object
|
|
||||||
#
|
|
||||||
# @param [String, I18n::Locale] locale (I18n::Locale.default)
|
|
||||||
# the locale of the documentation string.
|
|
||||||
# @return [Docstring] the documentation string
|
|
||||||
def docstring(locale = I18n::Locale.default)
|
|
||||||
if locale.nil?
|
|
||||||
@docstring.resolve_reference
|
|
||||||
return @docstring
|
|
||||||
end
|
|
||||||
|
|
||||||
if locale.is_a?(String)
|
|
||||||
locale_name = locale
|
|
||||||
locale = nil
|
|
||||||
else
|
|
||||||
locale_name = locale.name
|
|
||||||
end
|
|
||||||
@docstrings[locale_name] ||=
|
|
||||||
translate_docstring(locale || Registry.locale(locale_name))
|
|
||||||
end
|
|
||||||
|
|
||||||
# Attaches a docstring to a code object by parsing the comments attached to the statement
|
|
||||||
# and filling the {#tags} and {#docstring} methods with the parsed information.
|
|
||||||
#
|
|
||||||
# @param [String, Array<String>, Docstring] comments
|
|
||||||
# the comments attached to the code object to be parsed
|
|
||||||
# into a docstring and meta tags.
|
|
||||||
def docstring=(comments)
|
|
||||||
@docstrings.clear
|
|
||||||
@docstring = Docstring === comments ?
|
|
||||||
comments : Docstring.new(comments, self)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Default type is the lowercase class name without the "Object" suffix.
|
|
||||||
# Override this method to provide a custom object type
|
|
||||||
#
|
|
||||||
# @return [Symbol] the type of code object this represents
|
|
||||||
def type
|
|
||||||
self.class.name.split('::').last.gsub(/Object$/, '').downcase.to_sym
|
|
||||||
end
|
|
||||||
|
|
||||||
# Represents the unique path of the object. The default implementation
|
|
||||||
# joins the path of {#namespace} with {#name} via the value of {#sep}.
|
|
||||||
# Custom code objects should ensure that the path is unique to the code
|
|
||||||
# object by either overriding {#sep} or this method.
|
|
||||||
#
|
|
||||||
# @example The path of an instance method
|
|
||||||
# MethodObject.new(P("A::B"), :c).path # => "A::B#c"
|
|
||||||
# @return [String] the unique path of the object
|
|
||||||
# @see #sep
|
|
||||||
def path
|
|
||||||
@path ||= if parent && !parent.root?
|
|
||||||
[parent.path, name.to_s].join(sep)
|
|
||||||
else
|
|
||||||
name.to_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
alias to_s path
|
|
||||||
|
|
||||||
# @note
|
|
||||||
# Override this method if your object has a special title that does
|
|
||||||
# not match the {#path} attribute value. This title will be used
|
|
||||||
# when linking or displaying the object.
|
|
||||||
# @return [String] the display title for an object
|
|
||||||
# @see 0.8.4
|
|
||||||
def title
|
|
||||||
path
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param [Base, String] other another code object (or object path)
|
|
||||||
# @return [String] the shortest relative path from this object to +other+
|
|
||||||
# @since 0.5.3
|
|
||||||
def relative_path(other)
|
|
||||||
other = Registry.at(other) if String === other && Registry.at(other)
|
|
||||||
same_parent = false
|
|
||||||
if other.respond_to?(:path)
|
|
||||||
same_parent = other.parent == parent
|
|
||||||
other = other.path
|
|
||||||
end
|
|
||||||
return other unless namespace
|
|
||||||
common = [path, other].join(" ").match(/^(\S*)\S*(?: \1\S*)*$/)[1]
|
|
||||||
common = path unless common =~ /(\.|::|#)$/
|
|
||||||
common = common.sub(/(\.|::|#)[^:#\.]*?$/, '') if same_parent
|
|
||||||
suffix = %w(. :).include?(common[-1, 1]) || other[common.size, 1] == '#' ?
|
|
||||||
'' : '(::|\.)'
|
|
||||||
result = other.sub(/^#{Regexp.quote common}#{suffix}/, '')
|
|
||||||
result.empty? ? other : result
|
|
||||||
end
|
|
||||||
|
|
||||||
# Renders the object using the {Templates::Engine templating system}.
|
|
||||||
#
|
|
||||||
# @example Formats a class in plaintext
|
|
||||||
# puts P('MyClass').format
|
|
||||||
# @example Formats a method in html with rdoc markup
|
|
||||||
# puts P('MyClass#meth').format(:format => :html, :markup => :rdoc)
|
|
||||||
# @param [Hash] options a set of options to pass to the template
|
|
||||||
# @option options [Symbol] :format (:text) :html, :text or another output format
|
|
||||||
# @option options [Symbol] :template (:default) a specific template to use
|
|
||||||
# @option options [Symbol] :markup (nil) the markup type (:rdoc, :markdown, :textile)
|
|
||||||
# @option options [Serializers::Base] :serializer (nil) see Serializers
|
|
||||||
# @return [String] the rendered template
|
|
||||||
# @see Templates::Engine#render
|
|
||||||
def format(options = {})
|
|
||||||
options = options.merge(:object => self)
|
|
||||||
options = options.merge(:type => type) unless options[:type]
|
|
||||||
Templates::Engine.render(options)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Inspects the object, returning the type and path
|
|
||||||
# @return [String] a string describing the object
|
|
||||||
def inspect
|
|
||||||
"#<yardoc #{type} #{path}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Sets the namespace the object is defined in.
|
|
||||||
#
|
|
||||||
# @param [NamespaceObject, :root, nil] obj the new namespace (:root
|
|
||||||
# for {Registry.root}). If obj is nil, the object is unregistered
|
|
||||||
# from the Registry.
|
|
||||||
def namespace=(obj)
|
|
||||||
if @namespace
|
|
||||||
@namespace.children.delete(self)
|
|
||||||
Registry.delete(self)
|
|
||||||
end
|
|
||||||
|
|
||||||
@namespace = (obj == :root ? Registry.root : obj)
|
|
||||||
|
|
||||||
if @namespace
|
|
||||||
reg_obj = Registry.at(path)
|
|
||||||
return if reg_obj && reg_obj.class == self.class
|
|
||||||
|
|
||||||
unless @namespace.is_a?(Proxy)
|
|
||||||
# remove prior objects from obj's children that match this one
|
|
||||||
@namespace.children.delete_if {|o| o.path == path }
|
|
||||||
@namespace.children << self
|
|
||||||
end
|
|
||||||
Registry.register(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias parent namespace
|
|
||||||
alias parent= namespace=
|
|
||||||
|
|
||||||
# Gets a tag from the {#docstring}
|
|
||||||
# @see Docstring#tag
|
|
||||||
def tag(name); docstring.tag(name) end
|
|
||||||
|
|
||||||
# Gets a list of tags from the {#docstring}
|
|
||||||
# @see Docstring#tags
|
|
||||||
def tags(name = nil); docstring.tags(name) end
|
|
||||||
|
|
||||||
# Tests if the {#docstring} has a tag
|
|
||||||
# @see Docstring#has_tag?
|
|
||||||
def has_tag?(name); docstring.has_tag?(name) end
|
|
||||||
|
|
||||||
# Add tags to the {#docstring}
|
|
||||||
# @see Docstring#add_tag
|
|
||||||
# @since 0.8.4
|
|
||||||
def add_tag(*tags)
|
|
||||||
@docstrings.clear
|
|
||||||
@docstring.add_tag(*tags)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return whether or not this object is a RootObject
|
|
||||||
def root?; false end
|
|
||||||
|
|
||||||
# Override this method with a custom component separator. For instance,
|
|
||||||
# {MethodObject} implements sep as '#' or '.' (depending on if the
|
|
||||||
# method is instance or class respectively). {#path} depends on this
|
|
||||||
# value to generate the full path in the form: namespace.path + sep + name
|
|
||||||
#
|
|
||||||
# @return [String] the component that separates the namespace path
|
|
||||||
# and the name (default is {NSEP})
|
|
||||||
def sep; NSEP end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# Override this method if your code object subclass does not allow
|
|
||||||
# copying of certain attributes.
|
|
||||||
#
|
|
||||||
# @return [Array<String>] the list of instance variable names (without
|
|
||||||
# "@" prefix) that should be copied when {#copy_to} is called
|
|
||||||
# @see #copy_to
|
|
||||||
# @since 0.8.0
|
|
||||||
def copyable_attributes
|
|
||||||
vars = instance_variables.map {|ivar| ivar.to_s[1..-1] }
|
|
||||||
vars -= %w(docstring docstrings namespace name path)
|
|
||||||
vars
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Formats source code by removing leading indentation
|
|
||||||
#
|
|
||||||
# @param [String] source the source code to format
|
|
||||||
# @return [String] formatted source
|
|
||||||
def format_source(source)
|
|
||||||
source = source.chomp
|
|
||||||
last = source.split(/\r?\n/).last
|
|
||||||
indent = last ? last[/^([ \t]*)/, 1].length : 0
|
|
||||||
source.gsub(/^[ \t]{#{indent}}/, '')
|
|
||||||
end
|
|
||||||
|
|
||||||
def translate_docstring(locale)
|
|
||||||
@docstring.resolve_reference
|
|
||||||
return @docstring if locale.nil?
|
|
||||||
|
|
||||||
text = I18n::Text.new(@docstring)
|
|
||||||
localized_text = text.translate(locale)
|
|
||||||
docstring = Docstring.new(localized_text, self)
|
|
||||||
@docstring.tags.each do |tag|
|
|
||||||
if tag.is_a?(Tags::Tag)
|
|
||||||
localized_tag = tag.clone
|
|
||||||
localized_tag.text = I18n::Text.new(tag.text).translate(locale)
|
|
||||||
docstring.add_tag(localized_tag)
|
|
||||||
else
|
|
||||||
docstring.add_tag(tag)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
docstring
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,146 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD::CodeObjects
|
|
||||||
register_separator NSEP, :class
|
|
||||||
|
|
||||||
# A ClassObject represents a Ruby class in source code. It is a {ModuleObject}
|
|
||||||
# with extra inheritance semantics through the superclass.
|
|
||||||
class ClassObject < NamespaceObject
|
|
||||||
# The {ClassObject} that this class object inherits from in Ruby source.
|
|
||||||
# @return [ClassObject] a class object that is the superclass of this one
|
|
||||||
attr_reader :superclass
|
|
||||||
|
|
||||||
# Creates a new class object in +namespace+ with +name+
|
|
||||||
#
|
|
||||||
# @see Base.new
|
|
||||||
def initialize(namespace, name, *args, &block)
|
|
||||||
super
|
|
||||||
|
|
||||||
if is_exception?
|
|
||||||
self.superclass ||= "::Exception" unless P(namespace, name) == P(:Exception)
|
|
||||||
else
|
|
||||||
case P(namespace, name).path
|
|
||||||
when "BasicObject"
|
|
||||||
nil
|
|
||||||
when "Object"
|
|
||||||
self.superclass ||= "::BasicObject"
|
|
||||||
else
|
|
||||||
self.superclass ||= "::Object"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Whether or not the class is a Ruby Exception
|
|
||||||
#
|
|
||||||
# @return [Boolean] whether the object represents a Ruby exception
|
|
||||||
def is_exception?
|
|
||||||
inheritance_tree.reverse.any? {|o| BUILTIN_EXCEPTIONS_HASH.key? o.path }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the inheritance tree of the object including self.
|
|
||||||
#
|
|
||||||
# @param [Boolean] include_mods whether or not to include mixins in the
|
|
||||||
# inheritance tree.
|
|
||||||
# @return [Array<NamespaceObject>] the list of code objects that make up
|
|
||||||
# the inheritance tree.
|
|
||||||
def inheritance_tree(include_mods = false)
|
|
||||||
list = (include_mods ? mixins(:instance, :class) : [])
|
|
||||||
if superclass.is_a?(Proxy) || superclass.respond_to?(:inheritance_tree)
|
|
||||||
list += [superclass] unless superclass == P(:Object) || superclass == P(:BasicObject)
|
|
||||||
end
|
|
||||||
[self] + list.map do |m|
|
|
||||||
next m if m == self
|
|
||||||
next m unless m.respond_to?(:inheritance_tree)
|
|
||||||
m.inheritance_tree(include_mods)
|
|
||||||
end.flatten.uniq
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the list of methods matching the options hash. Returns
|
|
||||||
# all methods if hash is empty.
|
|
||||||
#
|
|
||||||
# @param [Hash] opts the options hash to match
|
|
||||||
# @option opts [Boolean] :inherited (true) whether inherited methods should be
|
|
||||||
# included in the list
|
|
||||||
# @option opts [Boolean] :included (true) whether mixed in methods should be
|
|
||||||
# included in the list
|
|
||||||
# @return [Array<MethodObject>] the list of methods that matched
|
|
||||||
def meths(opts = {})
|
|
||||||
opts = SymbolHash[:inherited => true].update(opts)
|
|
||||||
list = super(opts)
|
|
||||||
list += inherited_meths(opts).reject do |o|
|
|
||||||
next(false) if opts[:all]
|
|
||||||
list.find {|o2| o2.name == o.name && o2.scope == o.scope }
|
|
||||||
end if opts[:inherited]
|
|
||||||
list
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns only the methods that were inherited.
|
|
||||||
#
|
|
||||||
# @return [Array<MethodObject>] the list of inherited method objects
|
|
||||||
def inherited_meths(opts = {})
|
|
||||||
inheritance_tree[1..-1].inject([]) do |list, superclass|
|
|
||||||
if superclass.is_a?(Proxy)
|
|
||||||
list
|
|
||||||
else
|
|
||||||
list += superclass.meths(opts).reject do |o|
|
|
||||||
next(false) if opts[:all]
|
|
||||||
child(:name => o.name, :scope => o.scope) ||
|
|
||||||
list.find {|o2| o2.name == o.name && o2.scope == o.scope }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the list of constants matching the options hash.
|
|
||||||
#
|
|
||||||
# @param [Hash] opts the options hash to match
|
|
||||||
# @option opts [Boolean] :inherited (true) whether inherited constant should be
|
|
||||||
# included in the list
|
|
||||||
# @option opts [Boolean] :included (true) whether mixed in constant should be
|
|
||||||
# included in the list
|
|
||||||
# @return [Array<ConstantObject>] the list of constant that matched
|
|
||||||
def constants(opts = {})
|
|
||||||
opts = SymbolHash[:inherited => true].update(opts)
|
|
||||||
super(opts) + (opts[:inherited] ? inherited_constants : [])
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns only the constants that were inherited.
|
|
||||||
#
|
|
||||||
# @return [Array<ConstantObject>] the list of inherited constant objects
|
|
||||||
def inherited_constants
|
|
||||||
inheritance_tree[1..-1].inject([]) do |list, superclass|
|
|
||||||
if superclass.is_a?(Proxy)
|
|
||||||
list
|
|
||||||
else
|
|
||||||
list += superclass.constants.reject do |o|
|
|
||||||
child(:name => o.name) || list.find {|o2| o2.name == o.name }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Sets the superclass of the object
|
|
||||||
#
|
|
||||||
# @param [Base, Proxy, String, Symbol, nil] object the superclass value
|
|
||||||
# @return [void]
|
|
||||||
def superclass=(object)
|
|
||||||
case object
|
|
||||||
when Base, Proxy, NilClass
|
|
||||||
@superclass = object
|
|
||||||
when String, Symbol
|
|
||||||
@superclass = Proxy.new(namespace, object)
|
|
||||||
else
|
|
||||||
raise ArgumentError, "superclass must be CodeObject, Proxy, String or Symbol"
|
|
||||||
end
|
|
||||||
|
|
||||||
if name == @superclass.name && namespace != YARD::Registry.root && !object.is_a?(Base)
|
|
||||||
@superclass = Proxy.new(namespace.namespace, object)
|
|
||||||
end
|
|
||||||
|
|
||||||
if @superclass == self
|
|
||||||
msg = "superclass #{@superclass.inspect} cannot be the same as the declared class #{inspect}"
|
|
||||||
@superclass = P("::Object")
|
|
||||||
raise ArgumentError, msg
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD::CodeObjects
|
|
||||||
register_separator NSEP, :class_variable
|
|
||||||
|
|
||||||
# Represents a class variable inside a namespace. The path is expressed
|
|
||||||
# in the form "A::B::@@classvariable"
|
|
||||||
class ClassVariableObject < Base
|
|
||||||
# @return [String] the class variable's value
|
|
||||||
attr_accessor :value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD::CodeObjects
|
|
||||||
register_separator NSEP, :constant
|
|
||||||
|
|
||||||
# A +ConstantObject+ represents a Ruby constant (not a module or class).
|
|
||||||
# To access the constant's (source code) value, use {#value}.
|
|
||||||
class ConstantObject < Base
|
|
||||||
# The source code representing the constant's value
|
|
||||||
# @return [String] the value the constant is set to
|
|
||||||
attr_reader :value
|
|
||||||
|
|
||||||
def value=(value)
|
|
||||||
@value = format_source(value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD::CodeObjects
|
|
||||||
# Represents an instance method of a module that was mixed into the class
|
|
||||||
# scope of another namespace.
|
|
||||||
#
|
|
||||||
# @see MethodObject
|
|
||||||
class ExtendedMethodObject
|
|
||||||
instance_methods.each {|m| undef_method(m) unless m =~ /^__/ || m.to_sym == :object_id }
|
|
||||||
|
|
||||||
# @return [Symbol] always +:class+
|
|
||||||
def scope; :class end
|
|
||||||
|
|
||||||
# Sets up a delegate for {MethodObject} obj.
|
|
||||||
#
|
|
||||||
# @param [MethodObject] obj the instance method to treat as a mixed in
|
|
||||||
# class method on another namespace.
|
|
||||||
def initialize(obj) @del = obj end
|
|
||||||
|
|
||||||
# Sends all methods to the {MethodObject} assigned in {#initialize}
|
|
||||||
# @see #initialize
|
|
||||||
# @see MethodObject
|
|
||||||
def method_missing(sym, *args, &block) @del.__send__(sym, *args, &block) end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,134 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD::CodeObjects
|
|
||||||
# An ExtraFileObject represents an extra documentation file (README or other
|
|
||||||
# file). It is not strictly a CodeObject (does not inherit from `Base`) although
|
|
||||||
# it implements `path`, `name` and `type`, and therefore should be structurally
|
|
||||||
# compatible with most CodeObject interfaces.
|
|
||||||
class ExtraFileObject
|
|
||||||
attr_accessor :filename
|
|
||||||
attr_writer :attributes
|
|
||||||
attr_accessor :name
|
|
||||||
# @since 0.8.3
|
|
||||||
attr_reader :locale
|
|
||||||
|
|
||||||
# Creates a new extra file object.
|
|
||||||
# @param [String] filename the location on disk of the file
|
|
||||||
# @param [String] contents the file contents. If not set, the contents
|
|
||||||
# will be read from disk using the +filename+.
|
|
||||||
def initialize(filename, contents = nil)
|
|
||||||
self.filename = filename
|
|
||||||
self.name = File.basename(filename).gsub(/\.[^.]+$/, '')
|
|
||||||
self.attributes = SymbolHash.new(false)
|
|
||||||
@original_contents = contents
|
|
||||||
@parsed = false
|
|
||||||
@locale = nil
|
|
||||||
ensure_parsed
|
|
||||||
end
|
|
||||||
|
|
||||||
alias path name
|
|
||||||
|
|
||||||
def attributes
|
|
||||||
ensure_parsed
|
|
||||||
@attributes
|
|
||||||
end
|
|
||||||
|
|
||||||
def title
|
|
||||||
attributes[:title] || name
|
|
||||||
end
|
|
||||||
|
|
||||||
def contents
|
|
||||||
ensure_parsed
|
|
||||||
@contents
|
|
||||||
end
|
|
||||||
|
|
||||||
def contents=(contents)
|
|
||||||
@original_contents = contents
|
|
||||||
@parsed = false
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param [String] locale the locale name to be translated.
|
|
||||||
# @return [void]
|
|
||||||
# @since 0.8.3
|
|
||||||
def locale=(locale)
|
|
||||||
@locale = locale
|
|
||||||
@parsed = false
|
|
||||||
end
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"#<yardoc #{type} #{filename} attrs=#{attributes.inspect}>"
|
|
||||||
end
|
|
||||||
alias to_s inspect
|
|
||||||
|
|
||||||
def type; :extra_file end
|
|
||||||
|
|
||||||
def ==(other)
|
|
||||||
return false unless self.class === other
|
|
||||||
other.filename == filename
|
|
||||||
end
|
|
||||||
alias eql? ==
|
|
||||||
alias equal? ==
|
|
||||||
def hash; filename.hash end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def ensure_parsed
|
|
||||||
return if @parsed
|
|
||||||
@parsed = true
|
|
||||||
@contents = parse_contents(@original_contents || File.read(@filename))
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param [String] data the file contents
|
|
||||||
def parse_contents(data)
|
|
||||||
retried = false
|
|
||||||
cut_index = 0
|
|
||||||
data = translate(data)
|
|
||||||
data = data.split("\n")
|
|
||||||
data.each_with_index do |line, index|
|
|
||||||
case line
|
|
||||||
when /^#!(\S+)\s*$/
|
|
||||||
if index == 0
|
|
||||||
attributes[:markup] = $1
|
|
||||||
else
|
|
||||||
cut_index = index
|
|
||||||
break
|
|
||||||
end
|
|
||||||
when /^\s*#\s*@(\S+)\s*(.+?)\s*$/
|
|
||||||
attributes[$1] = $2
|
|
||||||
when /^\s*<!--\s*$/, /^\s*-->\s*$/
|
|
||||||
# Ignore HTML comments
|
|
||||||
else
|
|
||||||
cut_index = index
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
data = data[cut_index..-1] if cut_index > 0
|
|
||||||
contents = data.join("\n")
|
|
||||||
|
|
||||||
if contents.respond_to?(:force_encoding) && attributes[:encoding]
|
|
||||||
begin
|
|
||||||
contents.force_encoding(attributes[:encoding])
|
|
||||||
rescue ArgumentError
|
|
||||||
log.warn "Invalid encoding `#{attributes[:encoding]}' in #{filename}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
contents
|
|
||||||
rescue ArgumentError => e
|
|
||||||
raise unless e.message =~ /invalid byte sequence/
|
|
||||||
|
|
||||||
if retried
|
|
||||||
# This should never happen.
|
|
||||||
log.warn "Could not read #{filename}, #{e.message}. You probably want to set `--charset`."
|
|
||||||
return ''
|
|
||||||
else
|
|
||||||
data.force_encoding('binary') if data.respond_to?(:force_encoding)
|
|
||||||
retried = true
|
|
||||||
retry
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def translate(data)
|
|
||||||
text = YARD::I18n::Text.new(data, :have_header => true)
|
|
||||||
text.translate(YARD::Registry.locale(locale))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,172 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
require 'ostruct'
|
|
||||||
|
|
||||||
module YARD
|
|
||||||
module CodeObjects
|
|
||||||
# A MacroObject represents a docstring defined through +@!macro NAME+ and can be
|
|
||||||
# reused by specifying the tag +@!macro NAME+. You can also provide the
|
|
||||||
# +attached+ type flag to the macro definition to have it attached to the
|
|
||||||
# specific DSL method so it will be implicitly reused.
|
|
||||||
#
|
|
||||||
# Macros are fully described in the {file:docs/Tags.md#macro Tags Overview}
|
|
||||||
# document.
|
|
||||||
#
|
|
||||||
# @example Creating a basic named macro
|
|
||||||
# # @!macro prop
|
|
||||||
# # @!method $1(${3-})
|
|
||||||
# # @return [$2] the value of the $0
|
|
||||||
# property :foo, String, :a, :b
|
|
||||||
#
|
|
||||||
# # @!macro prop
|
|
||||||
# property :bar, Numeric, :value
|
|
||||||
#
|
|
||||||
# @example Creating a macro that is attached to the method call
|
|
||||||
# # @!macro [attach] prop2
|
|
||||||
# # @!method $1(value)
|
|
||||||
# property :foo
|
|
||||||
#
|
|
||||||
# # Extra data added to docstring
|
|
||||||
# property :bar
|
|
||||||
class MacroObject < Base
|
|
||||||
MACRO_MATCH = /(\\)?\$(?:\{(-?\d+|\*)(-)?(-?\d+)?\}|(-?\d+|\*))/
|
|
||||||
|
|
||||||
class << self
|
|
||||||
# Creates a new macro and fills in the relevant properties.
|
|
||||||
# @param [String] macro_name the name of the macro, must be unique.
|
|
||||||
# @param [String] data the data the macro should expand when re-used
|
|
||||||
# @param [CodeObjects::Base] method_object an object to attach this
|
|
||||||
# macro to. If supplied, {#attached?} will be true
|
|
||||||
# @return [MacroObject] the newly created object
|
|
||||||
def create(macro_name, data, method_object = nil)
|
|
||||||
obj = new(:root, macro_name)
|
|
||||||
obj.macro_data = data
|
|
||||||
obj.method_object = method_object
|
|
||||||
obj
|
|
||||||
end
|
|
||||||
|
|
||||||
# Finds a macro using +macro_name+
|
|
||||||
# @param [#to_s] macro_name the name of the macro
|
|
||||||
# @return [MacroObject] if a macro is found
|
|
||||||
# @return [nil] if there is no registered macro by that name
|
|
||||||
def find(macro_name)
|
|
||||||
Registry.at('.macro.' + macro_name.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parses a given docstring and determines if the macro is "new" or
|
|
||||||
# not. If the macro has $variable names or if it has a @!macro tag
|
|
||||||
# with the [new] or [attached] flag, it is considered new.
|
|
||||||
#
|
|
||||||
# If a new macro is found, the macro is created and registered. Otherwise
|
|
||||||
# the macro name is searched and returned. If a macro is not found,
|
|
||||||
# nil is returned.
|
|
||||||
#
|
|
||||||
# @param [#to_s] macro_name the name of the macro
|
|
||||||
# @param [CodeObjects::Base] method_object an optional method to attach
|
|
||||||
# the macro to. Only used if the macro is being created, otherwise
|
|
||||||
# this argument is ignored.
|
|
||||||
# @return [MacroObject] the newly created or existing macro, depending
|
|
||||||
# on whether the @!macro tag was a new tag or not.
|
|
||||||
# @return [nil] if the +data+ has no macro tag or if the macro is
|
|
||||||
# not new and no macro by the macro name is found.
|
|
||||||
def find_or_create(macro_name, data, method_object = nil)
|
|
||||||
find(name) || create(macro_name, data, method_object)
|
|
||||||
end
|
|
||||||
alias create_docstring find_or_create
|
|
||||||
|
|
||||||
# Expands +macro_data+ using the interpolation parameters.
|
|
||||||
#
|
|
||||||
# Interpolation rules:
|
|
||||||
# * $0, $1, $2, ... = the Nth parameter in +call_params+
|
|
||||||
# * $* = the full statement source (excluding block)
|
|
||||||
# * Also supports $!{N-M} ranges, as well as negative indexes on N or M
|
|
||||||
# * Use \$ to escape the variable name in a macro.
|
|
||||||
#
|
|
||||||
# @!macro [new] macro.expand
|
|
||||||
# @param [Array<String>] call_params the method name and parameters
|
|
||||||
# to the method call. These arguments will fill \$0-N
|
|
||||||
# @param [String] full_source the full source line (excluding block)
|
|
||||||
# interpolated as \$*
|
|
||||||
# @param [String] block_source Currently unused. Will support
|
|
||||||
# interpolating the block data as a variable.
|
|
||||||
# @return [String] the expanded macro data
|
|
||||||
# @param [String] macro_data the macro data to expand (taken from {#macro_data})
|
|
||||||
def expand(macro_data, call_params = [], full_source = '', block_source = '') # rubocop:disable Lint/UnusedMethodArgument
|
|
||||||
macro_data = macro_data.all if macro_data.is_a?(Docstring)
|
|
||||||
macro_data.gsub(MACRO_MATCH) do
|
|
||||||
escape = $1
|
|
||||||
first = $2 || $5
|
|
||||||
last = $4
|
|
||||||
rng = $3 ? true : false
|
|
||||||
next $&[1..-1] if escape
|
|
||||||
if first == '*'
|
|
||||||
last ? $& : full_source
|
|
||||||
else
|
|
||||||
first_i = first.to_i
|
|
||||||
last_i = (last ? last.to_i : call_params.size)
|
|
||||||
last_i = first_i unless rng
|
|
||||||
params = call_params[first_i..last_i]
|
|
||||||
params ? params.join(", ") : ''
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Applies a macro on a docstring by creating any macro data inside of
|
|
||||||
# the docstring first. Equivalent to calling {find_or_create} and {apply_macro}
|
|
||||||
# on the new macro object.
|
|
||||||
#
|
|
||||||
# @param [Docstring] docstring the docstring to create a macro out of
|
|
||||||
# @!macro macro.expand
|
|
||||||
# @see find_or_create
|
|
||||||
def apply(docstring, call_params = [], full_source = '', block_source = '', _method_object = nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
||||||
docstring = docstring.all if Docstring === docstring
|
|
||||||
parser = Docstring.parser
|
|
||||||
handler = OpenStruct.new
|
|
||||||
handler.call_params = call_params[1..-1]
|
|
||||||
handler.caller_method = call_params.first
|
|
||||||
handler.statement = OpenStruct.new(:source => full_source)
|
|
||||||
parser.parse(docstring, nil, handler).to_docstring.to_raw
|
|
||||||
end
|
|
||||||
|
|
||||||
# Applies a macro to a docstring, interpolating the macro's data on the
|
|
||||||
# docstring and appending any extra local docstring data that was in
|
|
||||||
# the original +docstring+ object.
|
|
||||||
#
|
|
||||||
# @param [MacroObject] macro the macro object
|
|
||||||
# @!macro macro.expand
|
|
||||||
def apply_macro(macro, docstring, call_params = [], full_source = '', block_source = '') # rubocop:disable Lint/UnusedMethodArgument
|
|
||||||
apply(docstring, call_params, full_source, block_source)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [String] the macro data stored on the object
|
|
||||||
attr_accessor :macro_data
|
|
||||||
|
|
||||||
# @return [CodeObjects::Base] the method object that this macro is
|
|
||||||
# attached to.
|
|
||||||
attr_accessor :method_object
|
|
||||||
|
|
||||||
# @return [Boolean] whether this macro is attached to a method
|
|
||||||
def attached?; method_object ? true : false end
|
|
||||||
|
|
||||||
# Overrides {Base#path} so the macro path is ".macro.MACRONAME"
|
|
||||||
def path; '.macro.' + name.to_s end
|
|
||||||
|
|
||||||
# Overrides the separator to be '.'
|
|
||||||
def sep; '.' end
|
|
||||||
|
|
||||||
# Expands the macro using
|
|
||||||
# @param [Array<String>] call_params a list of tokens that are passed
|
|
||||||
# to the method call
|
|
||||||
# @param [String] full_source the full method call (not including the block)
|
|
||||||
# @param [String] block_source the source passed in the block of the method
|
|
||||||
# call, if there is a block.
|
|
||||||
# @example Expanding a Macro
|
|
||||||
# macro.expand(%w(property foo bar), 'property :foo, :bar', '') #=>
|
|
||||||
# "...macro data interpolating this line of code..."
|
|
||||||
# @see expand
|
|
||||||
def expand(call_params = [], full_source = '', block_source = '')
|
|
||||||
self.class.expand(macro_data, call_params, full_source, block_source)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,196 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD::CodeObjects
|
|
||||||
register_separator CSEP, :method
|
|
||||||
register_separator ISEP, :method
|
|
||||||
|
|
||||||
# Represents a Ruby method in source
|
|
||||||
class MethodObject < Base
|
|
||||||
# The scope of the method (+:class+ or +:instance+)
|
|
||||||
#
|
|
||||||
# @return [Symbol] the scope
|
|
||||||
attr_reader :scope
|
|
||||||
|
|
||||||
# Whether the object is explicitly defined in source or whether it was
|
|
||||||
# inferred by a handler. For instance, attribute methods are generally
|
|
||||||
# inferred and therefore not explicitly defined in source.
|
|
||||||
#
|
|
||||||
# @return [Boolean] whether the object is explicitly defined in source.
|
|
||||||
attr_accessor :explicit
|
|
||||||
|
|
||||||
# Returns the list of parameters parsed out of the method signature
|
|
||||||
# with their default values.
|
|
||||||
#
|
|
||||||
# @return [Array<Array(String, String)>] a list of parameter names followed
|
|
||||||
# by their default values (or nil)
|
|
||||||
attr_accessor :parameters
|
|
||||||
|
|
||||||
# Creates a new method object in +namespace+ with +name+ and an instance
|
|
||||||
# or class +scope+
|
|
||||||
#
|
|
||||||
# If scope is +:module+, this object is instantiated as a public
|
|
||||||
# method in +:class+ scope, but also creates a new (empty) method
|
|
||||||
# as a private +:instance+ method on the same class or module.
|
|
||||||
#
|
|
||||||
# @param [NamespaceObject] namespace the namespace
|
|
||||||
# @param [String, Symbol] name the method name
|
|
||||||
# @param [Symbol] scope +:instance+, +:class+, or +:module+
|
|
||||||
def initialize(namespace, name, scope = :instance, &block)
|
|
||||||
@module_function = false
|
|
||||||
@scope = nil
|
|
||||||
|
|
||||||
# handle module function
|
|
||||||
if scope == :module
|
|
||||||
other = self.class.new(namespace, name, &block)
|
|
||||||
other.visibility = :private
|
|
||||||
scope = :class
|
|
||||||
@module_function = true
|
|
||||||
end
|
|
||||||
|
|
||||||
@visibility = :public
|
|
||||||
self.scope = scope
|
|
||||||
self.parameters = []
|
|
||||||
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
# Changes the scope of an object from :instance or :class
|
|
||||||
# @param [Symbol] v the new scope
|
|
||||||
def scope=(v)
|
|
||||||
reregister = @scope ? true : false
|
|
||||||
|
|
||||||
# handle module function
|
|
||||||
if v == :module
|
|
||||||
other = self.class.new(namespace, name)
|
|
||||||
other.visibility = :private
|
|
||||||
@visibility = :public
|
|
||||||
@module_function = true
|
|
||||||
@path = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
YARD::Registry.delete(self)
|
|
||||||
@path = nil
|
|
||||||
@scope = v.to_sym
|
|
||||||
@scope = :class if @scope == :module
|
|
||||||
YARD::Registry.register(self) if reregister
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return whether or not the method is the #initialize constructor method
|
|
||||||
def constructor?
|
|
||||||
name == :initialize && scope == :instance && namespace.is_a?(ClassObject)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Boolean] whether or not this method was created as a module
|
|
||||||
# function
|
|
||||||
# @since 0.8.0
|
|
||||||
def module_function?
|
|
||||||
@module_function
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the read/writer info for the attribute if it is one
|
|
||||||
# @return [SymbolHash] if there is information about the attribute
|
|
||||||
# @return [nil] if the method is not an attribute
|
|
||||||
# @since 0.5.3
|
|
||||||
def attr_info
|
|
||||||
return nil unless namespace.is_a?(NamespaceObject)
|
|
||||||
namespace.attributes[scope][name.to_s.gsub(/=$/, '')]
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Boolean] whether the method is a writer attribute
|
|
||||||
# @since 0.5.3
|
|
||||||
def writer?
|
|
||||||
info = attr_info
|
|
||||||
info && info[:write] == self ? true : false
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Boolean] whether the method is a reader attribute
|
|
||||||
# @since 0.5.3
|
|
||||||
def reader?
|
|
||||||
info = attr_info
|
|
||||||
info && info[:read] == self ? true : false
|
|
||||||
end
|
|
||||||
|
|
||||||
# Tests if the object is defined as an attribute in the namespace
|
|
||||||
# @return [Boolean] whether the object is an attribute
|
|
||||||
def is_attribute?
|
|
||||||
info = attr_info
|
|
||||||
if info
|
|
||||||
read_or_write = name.to_s =~ /=$/ ? :write : :read
|
|
||||||
info[read_or_write] ? true : false
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Tests if the object is defined as an alias of another method
|
|
||||||
# @return [Boolean] whether the object is an alias
|
|
||||||
def is_alias?
|
|
||||||
return false unless namespace.is_a?(NamespaceObject)
|
|
||||||
namespace.aliases.key? self
|
|
||||||
end
|
|
||||||
|
|
||||||
# Tests boolean {#explicit} value.
|
|
||||||
#
|
|
||||||
# @return [Boolean] whether the method is explicitly defined in source
|
|
||||||
def is_explicit?
|
|
||||||
explicit ? true : false
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [MethodObject] the object that this method overrides
|
|
||||||
# @return [nil] if it does not override a method
|
|
||||||
# @since 0.6.0
|
|
||||||
def overridden_method
|
|
||||||
return nil if namespace.is_a?(Proxy)
|
|
||||||
meths = namespace.meths(:all => true)
|
|
||||||
meths.find {|m| m.path != path && m.name == name && m.scope == scope }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns all alias names of the object
|
|
||||||
# @return [Array<MethodObject>] the alias names
|
|
||||||
def aliases
|
|
||||||
list = []
|
|
||||||
return list unless namespace.is_a?(NamespaceObject)
|
|
||||||
namespace.aliases.each do |o, aname|
|
|
||||||
list << o if aname == name && o.scope == scope
|
|
||||||
end
|
|
||||||
list
|
|
||||||
end
|
|
||||||
|
|
||||||
# Override path handling for instance methods in the root namespace
|
|
||||||
# (they should still have a separator as a prefix).
|
|
||||||
# @return [String] the path of a method
|
|
||||||
def path
|
|
||||||
@path ||= !namespace || namespace.path == "" ? sep + super : super
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the name of the object.
|
|
||||||
#
|
|
||||||
# @example The name of an instance method (with prefix)
|
|
||||||
# an_instance_method.name(true) # => "#mymethod"
|
|
||||||
# @example The name of a class method (with prefix)
|
|
||||||
# a_class_method.name(true) # => "mymethod"
|
|
||||||
# @param [Boolean] prefix whether or not to show the prefix
|
|
||||||
# @return [String] returns {#sep} + +name+ for an instance method if
|
|
||||||
# prefix is true
|
|
||||||
# @return [Symbol] the name without {#sep} if prefix is set to false
|
|
||||||
def name(prefix = false)
|
|
||||||
prefix ? (sep == ISEP ? "#{sep}#{super}" : super.to_s) : super
|
|
||||||
end
|
|
||||||
|
|
||||||
# Override separator to differentiate between class and instance
|
|
||||||
# methods.
|
|
||||||
# @return [String] "#" for an instance method, "." for class
|
|
||||||
def sep
|
|
||||||
if scope == :class
|
|
||||||
namespace && namespace != YARD::Registry.root ? CSEP : NSEP
|
|
||||||
else
|
|
||||||
ISEP
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
def copyable_attributes
|
|
||||||
super - %w(scope module_function)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD::CodeObjects
|
|
||||||
register_separator NSEP, :module
|
|
||||||
|
|
||||||
# Represents a Ruby module.
|
|
||||||
class ModuleObject < NamespaceObject
|
|
||||||
# Returns the inheritance tree of mixins.
|
|
||||||
#
|
|
||||||
# @param [Boolean] include_mods if true, will include mixed in
|
|
||||||
# modules (which is likely what is wanted).
|
|
||||||
# @return [Array<NamespaceObject>] a list of namespace objects
|
|
||||||
def inheritance_tree(include_mods = false)
|
|
||||||
return [self] unless include_mods
|
|
||||||
[self] + mixins(:instance, :class).map do |m|
|
|
||||||
next if m == self
|
|
||||||
next m unless m.respond_to?(:inheritance_tree)
|
|
||||||
m.inheritance_tree(true)
|
|
||||||
end.compact.flatten.uniq
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,141 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module CodeObjects
|
|
||||||
# This module controls registration and accessing of namespace separators
|
|
||||||
# for {Registry} lookup.
|
|
||||||
#
|
|
||||||
# @since 0.9.1
|
|
||||||
module NamespaceMapper
|
|
||||||
# @!group Registering a Separator for a Namespace
|
|
||||||
|
|
||||||
# Registers a separator with an optional set of valid types that
|
|
||||||
# must follow the separator lexically.
|
|
||||||
#
|
|
||||||
# Calls all callbacks defined by {NamespaceMapper.on_invalidate} after
|
|
||||||
# the separator is registered.
|
|
||||||
#
|
|
||||||
# @param sep [String] the separator string for the namespace
|
|
||||||
# @param valid_types [Array<Symbol>] a list of object types that
|
|
||||||
# must follow the separator. If the list is empty, any type can
|
|
||||||
# follow the separator.
|
|
||||||
# @example Registering separators for a method object
|
|
||||||
# # Anything after a "#" denotes a method object
|
|
||||||
# register_separator "#", :method
|
|
||||||
# # Anything after a "." denotes a method object
|
|
||||||
# register_separator ".", :method
|
|
||||||
# @see .on_invalidate
|
|
||||||
def register_separator(sep, *valid_types)
|
|
||||||
NamespaceMapper.invalidate
|
|
||||||
|
|
||||||
valid_types.each do |t|
|
|
||||||
NamespaceMapper.rev_map[t] ||= []
|
|
||||||
NamespaceMapper.rev_map[t] << sep
|
|
||||||
end
|
|
||||||
|
|
||||||
NamespaceMapper.map[sep] ||= []
|
|
||||||
NamespaceMapper.map[sep] += valid_types
|
|
||||||
end
|
|
||||||
|
|
||||||
# Unregisters a separator by a type.
|
|
||||||
#
|
|
||||||
# @param type [Symbol] the type to unregister
|
|
||||||
# @see #register_separator
|
|
||||||
def unregister_separator_by_type(type)
|
|
||||||
seps = NamespaceMapper.rev_map[type]
|
|
||||||
return unless seps
|
|
||||||
|
|
||||||
seps.each {|s| NamespaceMapper.map.delete(s) }
|
|
||||||
NamespaceMapper.rev_map.delete(type)
|
|
||||||
NamespaceMapper.invalidate
|
|
||||||
end
|
|
||||||
|
|
||||||
# Clears the map of separators.
|
|
||||||
#
|
|
||||||
# @return [void]
|
|
||||||
def clear_separators
|
|
||||||
NamespaceMapper.invalidate
|
|
||||||
NamespaceMapper.map = {}
|
|
||||||
NamespaceMapper.rev_map = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Gets or sets the default separator value to use when no
|
|
||||||
# separator for the namespace can be determined.
|
|
||||||
#
|
|
||||||
# @param value [String, nil] the default separator, or nil to return the
|
|
||||||
# value
|
|
||||||
# @example
|
|
||||||
# default_separator "::"
|
|
||||||
def default_separator(value = nil)
|
|
||||||
if value
|
|
||||||
NamespaceMapper.invalidate
|
|
||||||
NamespaceMapper.default_separator = Regexp.quote value
|
|
||||||
else
|
|
||||||
NamespaceMapper.default_separator
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @!group Separator and Type Lookup Helpers
|
|
||||||
|
|
||||||
# @return [Array<String>] all of the registered separators
|
|
||||||
def separators
|
|
||||||
NamespaceMapper.map.keys
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Regexp] the regexp match of all separators
|
|
||||||
def separators_match
|
|
||||||
NamespaceMapper.map_match
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param sep [String] the separator to return types for
|
|
||||||
# @return [Array<Symbol>] a list of types registered to a separator
|
|
||||||
def types_for_separator(sep)
|
|
||||||
NamespaceMapper.map[sep] || []
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param type [String] the type to return separators for
|
|
||||||
# @return [Array<Symbol>] a list of separators registered to a type
|
|
||||||
def separators_for_type(type)
|
|
||||||
NamespaceMapper.rev_map[type] || []
|
|
||||||
end
|
|
||||||
|
|
||||||
# Internal methods to act as a singleton registry
|
|
||||||
class << self
|
|
||||||
# @!group Invalidation callbacks
|
|
||||||
|
|
||||||
# Adds a callback that triggers when a new separator is registered or
|
|
||||||
# the cache is cleared by invalidation.
|
|
||||||
def on_invalidate(&block)
|
|
||||||
(@invalidation_callbacks ||= []).push(block)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @!visibility private
|
|
||||||
|
|
||||||
# @return [Hash] a mapping of types to separators
|
|
||||||
def map
|
|
||||||
@map ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Hash] a reverse mapping of separators to types
|
|
||||||
def rev_map
|
|
||||||
@rev_map ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Invalidates all separators
|
|
||||||
# @return [void]
|
|
||||||
def invalidate
|
|
||||||
@map_match = nil
|
|
||||||
(@invalidation_callbacks || []).each(&:call)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Regexp] the full list of separators as a regexp match
|
|
||||||
def map_match
|
|
||||||
@map_match ||= map.keys.map {|k| Regexp.quote k }.join('|')
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [String] the default separator when no separator can begin
|
|
||||||
# determined.
|
|
||||||
attr_accessor :default_separator
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,200 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD::CodeObjects
|
|
||||||
register_separator NSEP, :namespace
|
|
||||||
default_separator NSEP
|
|
||||||
|
|
||||||
# A "namespace" is any object that can store other objects within itself.
|
|
||||||
# The two main Ruby objects that can act as namespaces are modules
|
|
||||||
# ({ModuleObject}) and classes ({ClassObject}).
|
|
||||||
class NamespaceObject < Base
|
|
||||||
# @return [Array<String>] a list of ordered group names inside the namespace
|
|
||||||
# @since 0.6.0
|
|
||||||
attr_accessor :groups
|
|
||||||
|
|
||||||
# The list of objects defined in this namespace
|
|
||||||
# @return [Array<Base>] a list of objects
|
|
||||||
attr_reader :children
|
|
||||||
|
|
||||||
# A hash containing two keys, class and instance, each containing
|
|
||||||
# the attribute name with a { :read, :write } hash for the read and
|
|
||||||
# write objects respectively.
|
|
||||||
#
|
|
||||||
# @example The attributes of an object
|
|
||||||
# >> Registry.at('YARD::Docstring').attributes
|
|
||||||
# => {
|
|
||||||
# :class => { },
|
|
||||||
# :instance => {
|
|
||||||
# :ref_tags => {
|
|
||||||
# :read => #<yardoc method YARD::Docstring#ref_tags>,
|
|
||||||
# :write => nil
|
|
||||||
# },
|
|
||||||
# :object => {
|
|
||||||
# :read => #<yardoc method YARD::Docstring#object>,
|
|
||||||
# :write => #<yardoc method YARD::Docstring#object=>
|
|
||||||
# },
|
|
||||||
# ...
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
# @return [Hash] a list of methods
|
|
||||||
attr_reader :attributes
|
|
||||||
|
|
||||||
# A hash containing two keys, :class and :instance, each containing
|
|
||||||
# a hash of objects and their alias names.
|
|
||||||
# @return [Hash] a list of methods
|
|
||||||
attr_reader :aliases
|
|
||||||
|
|
||||||
# Class mixins
|
|
||||||
# @return [Array<ModuleObject>] a list of mixins
|
|
||||||
attr_reader :class_mixins
|
|
||||||
|
|
||||||
# Instance mixins
|
|
||||||
# @return [Array<ModuleObject>] a list of mixins
|
|
||||||
attr_reader :instance_mixins
|
|
||||||
|
|
||||||
# Creates a new namespace object inside +namespace+ with +name+.
|
|
||||||
# @see Base#initialize
|
|
||||||
def initialize(namespace, name, *args, &block)
|
|
||||||
@children = CodeObjectList.new(self)
|
|
||||||
@class_mixins = CodeObjectList.new(self)
|
|
||||||
@instance_mixins = CodeObjectList.new(self)
|
|
||||||
@attributes = SymbolHash[:class => SymbolHash.new, :instance => SymbolHash.new]
|
|
||||||
@aliases = {}
|
|
||||||
@groups = []
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
# Only the class attributes
|
|
||||||
# @return [Hash] a list of method names and their read/write objects
|
|
||||||
# @see #attributes
|
|
||||||
def class_attributes
|
|
||||||
attributes[:class]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Only the instance attributes
|
|
||||||
# @return [Hash] a list of method names and their read/write objects
|
|
||||||
# @see #attributes
|
|
||||||
def instance_attributes
|
|
||||||
attributes[:instance]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Looks for a child that matches the attributes specified by +opts+.
|
|
||||||
#
|
|
||||||
# @example Finds a child by name and scope
|
|
||||||
# namespace.child(:name => :to_s, :scope => :instance)
|
|
||||||
# # => #<yardoc method MyClass#to_s>
|
|
||||||
# @return [Base, nil] the first matched child object, or nil
|
|
||||||
def child(opts = {})
|
|
||||||
if !opts.is_a?(Hash)
|
|
||||||
children.find {|o| o.name == opts.to_sym }
|
|
||||||
else
|
|
||||||
opts = SymbolHash[opts]
|
|
||||||
children.find do |obj|
|
|
||||||
opts.each do |meth, value|
|
|
||||||
break false unless value.is_a?(Array) ? value.include?(obj[meth]) : obj[meth] == value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns all methods that match the attributes specified by +opts+. If
|
|
||||||
# no options are provided, returns all methods.
|
|
||||||
#
|
|
||||||
# @example Finds all private and protected class methods
|
|
||||||
# namespace.meths(:visibility => [:private, :protected], :scope => :class)
|
|
||||||
# # => [#<yardoc method MyClass.privmeth>, #<yardoc method MyClass.protmeth>]
|
|
||||||
# @option opts [Array<Symbol>, Symbol] :visibility ([:public, :private,
|
|
||||||
# :protected]) the visibility of the methods to list. Can be an array or
|
|
||||||
# single value.
|
|
||||||
# @option opts [Array<Symbol>, Symbol] :scope ([:class, :instance]) the
|
|
||||||
# scope of the methods to list. Can be an array or single value.
|
|
||||||
# @option opts [Boolean] :included (true) whether to include mixed in
|
|
||||||
# methods in the list.
|
|
||||||
# @return [Array<MethodObject>] a list of method objects
|
|
||||||
def meths(opts = {})
|
|
||||||
opts = SymbolHash[
|
|
||||||
:visibility => [:public, :private, :protected],
|
|
||||||
:scope => [:class, :instance],
|
|
||||||
:included => true
|
|
||||||
].update(opts)
|
|
||||||
|
|
||||||
opts[:visibility] = [opts[:visibility]].flatten
|
|
||||||
opts[:scope] = [opts[:scope]].flatten
|
|
||||||
|
|
||||||
ourmeths = children.select do |o|
|
|
||||||
o.is_a?(MethodObject) &&
|
|
||||||
opts[:visibility].include?(o.visibility) &&
|
|
||||||
opts[:scope].include?(o.scope)
|
|
||||||
end
|
|
||||||
|
|
||||||
ourmeths + (opts[:included] ? included_meths(opts) : [])
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns methods included from any mixins that match the attributes
|
|
||||||
# specified by +opts+. If no options are specified, returns all included
|
|
||||||
# methods.
|
|
||||||
#
|
|
||||||
# @option opts [Array<Symbol>, Symbol] :visibility ([:public, :private,
|
|
||||||
# :protected]) the visibility of the methods to list. Can be an array or
|
|
||||||
# single value.
|
|
||||||
# @option opts [Array<Symbol>, Symbol] :scope ([:class, :instance]) the
|
|
||||||
# scope of the methods to list. Can be an array or single value.
|
|
||||||
# @option opts [Boolean] :included (true) whether to include mixed in
|
|
||||||
# methods in the list.
|
|
||||||
# @see #meths
|
|
||||||
def included_meths(opts = {})
|
|
||||||
opts = SymbolHash[:scope => [:instance, :class]].update(opts)
|
|
||||||
[opts[:scope]].flatten.map do |scope|
|
|
||||||
mixins(scope).inject([]) do |list, mixin|
|
|
||||||
next list if mixin.is_a?(Proxy)
|
|
||||||
arr = mixin.meths(opts.merge(:scope => :instance)).reject do |o|
|
|
||||||
next false if opts[:all]
|
|
||||||
child(:name => o.name, :scope => scope) || list.find {|o2| o2.name == o.name }
|
|
||||||
end
|
|
||||||
arr.map! {|o| ExtendedMethodObject.new(o) } if scope == :class
|
|
||||||
list + arr
|
|
||||||
end
|
|
||||||
end.flatten
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns all constants in the namespace
|
|
||||||
#
|
|
||||||
# @option opts [Boolean] :included (true) whether or not to include
|
|
||||||
# mixed in constants in list
|
|
||||||
# @return [Array<ConstantObject>] a list of constant objects
|
|
||||||
def constants(opts = {})
|
|
||||||
opts = SymbolHash[:included => true].update(opts)
|
|
||||||
consts = children.select {|o| o.is_a? ConstantObject }
|
|
||||||
consts + (opts[:included] ? included_constants : [])
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns constants included from any mixins
|
|
||||||
# @return [Array<ConstantObject>] a list of constant objects
|
|
||||||
def included_constants
|
|
||||||
instance_mixins.inject([]) do |list, mixin|
|
|
||||||
if mixin.respond_to? :constants
|
|
||||||
list += mixin.constants.reject do |o|
|
|
||||||
child(:name => o.name) || list.find {|o2| o2.name == o.name }
|
|
||||||
end
|
|
||||||
else
|
|
||||||
list
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns class variables defined in this namespace.
|
|
||||||
# @return [Array<ClassVariableObject>] a list of class variable objects
|
|
||||||
def cvars
|
|
||||||
children.select {|o| o.is_a? ClassVariableObject }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns for specific scopes. If no scopes are provided, returns all mixins.
|
|
||||||
# @param [Array<Symbol>] scopes a list of scopes (:class, :instance) to
|
|
||||||
# return mixins for. If this is empty, all scopes will be returned.
|
|
||||||
# @return [Array<ModuleObject>] a list of mixins
|
|
||||||
def mixins(*scopes)
|
|
||||||
return class_mixins if scopes == [:class]
|
|
||||||
return instance_mixins if scopes == [:instance]
|
|
||||||
class_mixins | instance_mixins
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,245 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module CodeObjects
|
|
||||||
# A special type of +NoMethodError+ when raised from a {Proxy}
|
|
||||||
class ProxyMethodError < NoMethodError; end
|
|
||||||
|
|
||||||
# @private
|
|
||||||
PROXY_MATCH = /(?:#{NSEPQ}|#{ISEPQ}|#{CSEPQ})([^#{Regexp.quote(
|
|
||||||
(NSEP + ISEP + CSEP).split('').uniq.join
|
|
||||||
)}]+)$/
|
|
||||||
|
|
||||||
# The Proxy class is a way to lazily resolve code objects in
|
|
||||||
# cases where the object may not yet exist. A proxy simply stores
|
|
||||||
# an unresolved path until a method is called on the object, at which
|
|
||||||
# point it does a lookup using {Registry.resolve}. If the object is
|
|
||||||
# not found, a warning is raised and {ProxyMethodError} might be raised.
|
|
||||||
#
|
|
||||||
# @example Creates a Proxy to the String class from a module
|
|
||||||
# # When the String class is parsed this method will
|
|
||||||
# # begin to act like the String ClassObject.
|
|
||||||
# Proxy.new(mymoduleobj, "String")
|
|
||||||
# @see Registry.resolve
|
|
||||||
# @see ProxyMethodError
|
|
||||||
class Proxy
|
|
||||||
def self.===(other) other.is_a?(self) end
|
|
||||||
|
|
||||||
attr_reader :namespace
|
|
||||||
alias parent namespace
|
|
||||||
|
|
||||||
# Creates a new Proxy
|
|
||||||
#
|
|
||||||
# @raise [ArgumentError] if namespace is not a NamespaceObject
|
|
||||||
# @return [Proxy] self
|
|
||||||
def initialize(namespace, name, type = nil)
|
|
||||||
namespace = Registry.root if !namespace || namespace == :root
|
|
||||||
|
|
||||||
if name =~ /^#{NSEPQ}/
|
|
||||||
namespace = Registry.root
|
|
||||||
name = name[2..-1]
|
|
||||||
end
|
|
||||||
|
|
||||||
if name =~ PROXY_MATCH
|
|
||||||
@orignamespace = namespace
|
|
||||||
@origname = name
|
|
||||||
@imethod = true if name.include? ISEP
|
|
||||||
namespace = Proxy.new(namespace, $`) unless $`.empty?
|
|
||||||
name = $1
|
|
||||||
else
|
|
||||||
@orignamespace = nil
|
|
||||||
@origname = nil
|
|
||||||
@imethod = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
@name = name.to_sym
|
|
||||||
@namespace = namespace
|
|
||||||
@obj = nil
|
|
||||||
@imethod ||= nil
|
|
||||||
self.type = type
|
|
||||||
|
|
||||||
if @namespace.is_a?(ConstantObject)
|
|
||||||
unless @namespace.value =~ /\A#{NAMESPACEMATCH}\Z/
|
|
||||||
raise Parser::UndocumentableError, "constant mapping for " +
|
|
||||||
"#{@origname} (type=#{type.inspect})"
|
|
||||||
end
|
|
||||||
|
|
||||||
@origname = nil # forget these for a constant
|
|
||||||
@orignamespace = nil
|
|
||||||
@namespace = Proxy.new(@namespace.namespace, @namespace.value)
|
|
||||||
end
|
|
||||||
|
|
||||||
unless @namespace.is_a?(NamespaceObject) || @namespace.is_a?(Proxy)
|
|
||||||
raise ArgumentError, "Invalid namespace object: #{namespace}"
|
|
||||||
end
|
|
||||||
|
|
||||||
# If the name begins with "::" (like "::String")
|
|
||||||
# this is definitely a root level object, so
|
|
||||||
# remove the namespace and attach it to the root
|
|
||||||
if @name =~ /^#{NSEPQ}/
|
|
||||||
@name.gsub!(/^#{NSEPQ}/, '')
|
|
||||||
@namespace = Registry.root
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# (see Base#name)
|
|
||||||
def name(prefix = false)
|
|
||||||
prefix ? "#{@imethod && ISEP}#{@name}" : @name
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a text representation of the Proxy
|
|
||||||
# @return [String] the object's #inspect method or P(OBJECTPATH)
|
|
||||||
def inspect
|
|
||||||
to_obj ? to_obj.inspect : "P(#{path})"
|
|
||||||
end
|
|
||||||
|
|
||||||
# If the proxy resolves to an object, returns its path, otherwise
|
|
||||||
# guesses at the correct path using the original namespace and name.
|
|
||||||
#
|
|
||||||
# @return [String] the assumed path of the proxy (or the real path
|
|
||||||
# of the resolved object)
|
|
||||||
def path
|
|
||||||
to_obj ? to_obj.path : proxy_path
|
|
||||||
end
|
|
||||||
alias to_s path
|
|
||||||
alias to_str path
|
|
||||||
alias title path
|
|
||||||
|
|
||||||
# @return [Boolean]
|
|
||||||
def is_a?(klass)
|
|
||||||
to_obj ? to_obj.is_a?(klass) : self.class <= klass
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Boolean]
|
|
||||||
def ===(other)
|
|
||||||
to_obj ? to_obj === other : self.class <= other.class
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Boolean]
|
|
||||||
def <=>(other)
|
|
||||||
if other.respond_to? :path
|
|
||||||
path <=> other.path
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Boolean]
|
|
||||||
def equal?(other)
|
|
||||||
if other.respond_to? :path
|
|
||||||
path == other.path
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
alias == equal?
|
|
||||||
|
|
||||||
# @return [Integer] the object's hash value (for equality checking)
|
|
||||||
def hash; path.hash end
|
|
||||||
|
|
||||||
# Returns the class name of the object the proxy is mimicking, if
|
|
||||||
# resolved. Otherwise returns +Proxy+.
|
|
||||||
# @return [Class] the resolved object's class or +Proxy+
|
|
||||||
def class
|
|
||||||
to_obj ? to_obj.class : Proxy
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the type of the proxy. If it cannot be resolved at the
|
|
||||||
# time of the call, it will either return the inferred proxy type
|
|
||||||
# (see {#type=}) or +:proxy+
|
|
||||||
# @return [Symbol] the Proxy's type
|
|
||||||
# @see #type=
|
|
||||||
def type
|
|
||||||
to_obj ? to_obj.type : @type || :proxy
|
|
||||||
end
|
|
||||||
|
|
||||||
# Allows a parser to infer the type of the proxy by its path.
|
|
||||||
# @param [#to_sym] type the proxy's inferred type
|
|
||||||
# @return [void]
|
|
||||||
def type=(type) @type = type ? type.to_sym : nil end
|
|
||||||
|
|
||||||
# @return [Boolean]
|
|
||||||
def instance_of?(klass)
|
|
||||||
self.class == klass
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Boolean]
|
|
||||||
def kind_of?(klass)
|
|
||||||
self.class <= klass
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Boolean]
|
|
||||||
def respond_to?(meth, include_private = false)
|
|
||||||
to_obj ? to_obj.respond_to?(meth, include_private) : super
|
|
||||||
end
|
|
||||||
|
|
||||||
# Dispatches the method to the resolved object.
|
|
||||||
#
|
|
||||||
# @raise [ProxyMethodError] if the proxy cannot find the real object
|
|
||||||
def method_missing(meth, *args, &block)
|
|
||||||
if to_obj
|
|
||||||
to_obj.__send__(meth, *args, &block)
|
|
||||||
else
|
|
||||||
log.warn "Load Order / Name Resolution Problem on #{path}:\n" \
|
|
||||||
"-\n" \
|
|
||||||
"Something is trying to call #{meth} on object #{path} before it has been recognized.\n" \
|
|
||||||
"This error usually means that you need to modify the order in which you parse files\n" \
|
|
||||||
"so that #{path} is parsed before methods or other objects attempt to access it.\n" \
|
|
||||||
"-\n" \
|
|
||||||
"YARD will recover from this error and continue to parse but you *may* have problems\n" \
|
|
||||||
"with your generated documentation. You should probably fix this.\n" \
|
|
||||||
"-\n"
|
|
||||||
begin
|
|
||||||
super
|
|
||||||
rescue NoMethodError
|
|
||||||
raise ProxyMethodError, "Proxy cannot call method ##{meth} on object '#{path}'"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# This class is never a root object
|
|
||||||
def root?; false end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# @note this method fixes a bug in 1.9.2: http://gist.github.com/437136
|
|
||||||
def to_ary; nil end
|
|
||||||
|
|
||||||
# Attempts to find the object that this unresolved object
|
|
||||||
# references by checking if any objects by this name are
|
|
||||||
# registered all the way up the namespace tree.
|
|
||||||
#
|
|
||||||
# @return [Base, nil] the registered code object or nil
|
|
||||||
def to_obj
|
|
||||||
return @obj if @obj
|
|
||||||
@obj = Registry.resolve(@namespace, (@imethod ? ISEP : '') + @name.to_s, false, false, @type)
|
|
||||||
if @obj
|
|
||||||
if @origname && @origname.include?("::") && !@obj.path.include?(@origname)
|
|
||||||
# the object's path should include the original proxy namespace,
|
|
||||||
# otherwise it's (probably) not the right object.
|
|
||||||
@obj = nil
|
|
||||||
else
|
|
||||||
@namespace = @obj.namespace
|
|
||||||
@name = @obj.name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@obj
|
|
||||||
end
|
|
||||||
|
|
||||||
def proxy_path
|
|
||||||
if @namespace.root?
|
|
||||||
(@imethod ? ISEP : "") + name.to_s
|
|
||||||
elsif @origname
|
|
||||||
if @origname =~ CONSTANTSTART
|
|
||||||
@origname
|
|
||||||
else
|
|
||||||
[namespace.path, @origname].join
|
|
||||||
end
|
|
||||||
elsif name.to_s =~ CONSTANTSTART
|
|
||||||
name.to_s
|
|
||||||
else # class meth?
|
|
||||||
[namespace.path, name.to_s].join(CSEP)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module CodeObjects
|
|
||||||
# Represents the root namespace object (the invisible Ruby module that
|
|
||||||
# holds all top level modules, class and other objects).
|
|
||||||
class RootObject < ModuleObject
|
|
||||||
def path; @path ||= "" end
|
|
||||||
def inspect; @inspect ||= "#<yardoc root>" end
|
|
||||||
def root?; true end
|
|
||||||
def title; 'Top Level Namespace' end
|
|
||||||
|
|
||||||
def equal?(other)
|
|
||||||
other == :root ? true : super(other)
|
|
||||||
end
|
|
||||||
|
|
||||||
def hash; :root.hash end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,270 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
# This class maintains all system-wide configuration for YARD and handles
|
|
||||||
# the loading of plugins. To access options call {options}, and to load
|
|
||||||
# a plugin use {load_plugin}. All other public methods are used by YARD
|
|
||||||
# during load time.
|
|
||||||
#
|
|
||||||
# == User Configuration Files
|
|
||||||
#
|
|
||||||
# Persistent user configuration files can be stored in the file
|
|
||||||
# +~/.yard/config+, which is read when YARD first loads. The file should
|
|
||||||
# be formatted as YAML, and should contain a map of keys and values.
|
|
||||||
#
|
|
||||||
# Although you can specify any key-value mapping in the configuration file,
|
|
||||||
# YARD defines special keys specified in {DEFAULT_CONFIG_OPTIONS}.
|
|
||||||
#
|
|
||||||
# An example of a configuration file is listed below:
|
|
||||||
#
|
|
||||||
# !!!yaml
|
|
||||||
# load_plugins: true # Auto-load plugins when YARD starts
|
|
||||||
# ignored_plugins:
|
|
||||||
# - yard-broken
|
|
||||||
# - broken2 # yard- prefix not necessary
|
|
||||||
# autoload_plugins:
|
|
||||||
# - yard-rspec
|
|
||||||
#
|
|
||||||
# == Automatic Loading of Plugins
|
|
||||||
#
|
|
||||||
# YARD 0.6.2 will no longer automatically load all plugins by default. This
|
|
||||||
# option can be reset by setting 'load_plugins' to true in the configuration
|
|
||||||
# file. In addition, you can specify a set of specific plugins to load on
|
|
||||||
# load through the 'autoload_plugins' list setting. This setting is
|
|
||||||
# independent of the 'load_plugins' value and will always be processed.
|
|
||||||
#
|
|
||||||
# == Ignored Plugins File
|
|
||||||
#
|
|
||||||
# YARD 0.5 and below used a +~/.yard/ignored_plugins+ file to specify
|
|
||||||
# plugins to be ignored at load time. Ignored plugins in 0.6.2 and above
|
|
||||||
# should now be specified in the main configuration file, though YARD
|
|
||||||
# will support the +ignored_plugins+ file until 0.7.x.
|
|
||||||
#
|
|
||||||
# == Safe Mode
|
|
||||||
#
|
|
||||||
# YARD supports running in safe-mode. By doing this, it will avoid executing
|
|
||||||
# any user code such as require files or queries. Plugins will still be
|
|
||||||
# loaded with safe mode on, because plugins are properly namespaced with
|
|
||||||
# a 'yard-' prefix, must be installed as a gem, and therefore cannot be
|
|
||||||
# touched by the user. To specify safe mode, use the +safe_mode+ key.
|
|
||||||
#
|
|
||||||
# == Plugin Specific Configuration
|
|
||||||
#
|
|
||||||
# Additional settings can be defined within the configuration file
|
|
||||||
# specifically to provide configuration for a plugin. A plugin that utilizes
|
|
||||||
# the YARD configuration is strongly encouraged to utilize namespacing of
|
|
||||||
# their configuration content.
|
|
||||||
#
|
|
||||||
# !!!yaml
|
|
||||||
# load_plugins: true # Auto-load plugins when YARD starts
|
|
||||||
# ignored_plugins:
|
|
||||||
# - yard-broken
|
|
||||||
# - broken2 # yard- prefix not necessary
|
|
||||||
# autoload_plugins:
|
|
||||||
# - yard-rspec
|
|
||||||
# # Plugin Specific Configuration
|
|
||||||
# yard-sample-plugin:
|
|
||||||
# show-results-inline: true
|
|
||||||
#
|
|
||||||
# As the configuration is available system wide, it can be
|
|
||||||
# accessed within the plugin code.
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# if YARD::Config.options['yard-sample-plugin'] and
|
|
||||||
# YARD::Config.options['yard-sample-plugin']['show-results-inline']
|
|
||||||
# # ... perform the action that places the results inline ...
|
|
||||||
# else
|
|
||||||
# # ... do the default behavior of not showing the results inline ...
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# When accessing the configuration, be aware that this file is user managed
|
|
||||||
# so configuration keys and values may not be present. Make no assumptions and
|
|
||||||
# instead ensure that you check for the existence of keys before proceeding to
|
|
||||||
# retrieve values.
|
|
||||||
#
|
|
||||||
# @since 0.6.2
|
|
||||||
# @see options
|
|
||||||
class Config
|
|
||||||
class << self
|
|
||||||
# The system-wide configuration options for YARD
|
|
||||||
# @return [SymbolHash] a map a key-value pair settings.
|
|
||||||
# @see DEFAULT_CONFIG_OPTIONS
|
|
||||||
attr_accessor :options
|
|
||||||
end
|
|
||||||
|
|
||||||
# The location where YARD stores user-specific settings
|
|
||||||
CONFIG_DIR = File.expand_path('~/.yard')
|
|
||||||
|
|
||||||
# The main configuration YAML file.
|
|
||||||
CONFIG_FILE = File.join(CONFIG_DIR, 'config')
|
|
||||||
|
|
||||||
# File listing all ignored plugins
|
|
||||||
# @deprecated Set `ignored_plugins` in the {CONFIG_FILE} instead.
|
|
||||||
IGNORED_PLUGINS = File.join(CONFIG_DIR, 'ignored_plugins')
|
|
||||||
|
|
||||||
# Default configuration options
|
|
||||||
DEFAULT_CONFIG_OPTIONS = {
|
|
||||||
:load_plugins => false, # Whether to load plugins automatically with YARD
|
|
||||||
:ignored_plugins => [], # A list of ignored plugins by name
|
|
||||||
:autoload_plugins => [], # A list of plugins to be automatically loaded
|
|
||||||
:safe_mode => false # Does not execute or eval any user-level code
|
|
||||||
}
|
|
||||||
|
|
||||||
# The prefix used for YARD plugins. Name your gem with this prefix
|
|
||||||
# to allow it to be used as a plugin.
|
|
||||||
YARD_PLUGIN_PREFIX = /^yard[-_]/
|
|
||||||
|
|
||||||
# Loads settings from {CONFIG_FILE}. This method is called by YARD at
|
|
||||||
# load time and should not be called by the user.
|
|
||||||
# @return [void]
|
|
||||||
def self.load
|
|
||||||
self.options = SymbolHash.new(false)
|
|
||||||
options.update(DEFAULT_CONFIG_OPTIONS)
|
|
||||||
options.update(read_config_file)
|
|
||||||
load_commandline_safemode
|
|
||||||
add_ignored_plugins_file
|
|
||||||
translate_plugin_names
|
|
||||||
load_plugins
|
|
||||||
rescue => e
|
|
||||||
log.error "Invalid configuration file, using default options."
|
|
||||||
log.backtrace(e)
|
|
||||||
options.update(DEFAULT_CONFIG_OPTIONS)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Saves settings to {CONFIG_FILE}.
|
|
||||||
# @return [void]
|
|
||||||
def self.save
|
|
||||||
require 'yaml'
|
|
||||||
Dir.mkdir(CONFIG_DIR) unless File.directory?(CONFIG_DIR)
|
|
||||||
File.open(CONFIG_FILE, 'w') {|f| f.write(YAML.dump(options)) }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Loads gems that match the name 'yard-*' (recommended) or 'yard_*' except
|
|
||||||
# those listed in +~/.yard/ignored_plugins+. This is called immediately
|
|
||||||
# after YARD is loaded to allow plugin support.
|
|
||||||
#
|
|
||||||
# @return [Boolean] true if all plugins loaded successfully, false otherwise.
|
|
||||||
def self.load_plugins
|
|
||||||
load_gem_plugins &&
|
|
||||||
load_autoload_plugins &&
|
|
||||||
load_commandline_plugins ? true : false
|
|
||||||
end
|
|
||||||
|
|
||||||
# Loads an individual plugin by name. It is not necessary to include the
|
|
||||||
# +yard-+ plugin prefix here.
|
|
||||||
#
|
|
||||||
# @param [String] name the name of the plugin (with or without +yard-+ prefix)
|
|
||||||
# @return [Boolean] whether the plugin was successfully loaded
|
|
||||||
def self.load_plugin(name)
|
|
||||||
name = translate_plugin_name(name)
|
|
||||||
return false if options[:ignored_plugins].include?(name)
|
|
||||||
return false if name =~ /^yard-doc-/
|
|
||||||
log.debug "Loading plugin '#{name}'..."
|
|
||||||
require name
|
|
||||||
true
|
|
||||||
rescue LoadError => e
|
|
||||||
load_plugin_failed(name, e)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Load gem plugins if :load_plugins is true
|
|
||||||
def self.load_gem_plugins
|
|
||||||
return true unless options[:load_plugins]
|
|
||||||
require 'rubygems'
|
|
||||||
result = true
|
|
||||||
YARD::GemIndex.each do |gem|
|
|
||||||
begin
|
|
||||||
next true unless gem.name =~ YARD_PLUGIN_PREFIX
|
|
||||||
load_plugin(gem.name)
|
|
||||||
rescue Gem::LoadError => e
|
|
||||||
tmp = load_plugin_failed(gem.name, e)
|
|
||||||
result = tmp unless tmp
|
|
||||||
end
|
|
||||||
end
|
|
||||||
result
|
|
||||||
rescue LoadError
|
|
||||||
log.debug "RubyGems is not present, skipping plugin loading"
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
# Load plugins set in :autoload_plugins
|
|
||||||
def self.load_autoload_plugins
|
|
||||||
options[:autoload_plugins].each {|name| load_plugin(name) }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Load plugins from {arguments}
|
|
||||||
def self.load_commandline_plugins
|
|
||||||
with_yardopts do
|
|
||||||
arguments.each_with_index do |arg, i|
|
|
||||||
next unless arg == '--plugin'
|
|
||||||
load_plugin(arguments[i + 1])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Check for command-line safe_mode switch in {arguments}
|
|
||||||
def self.load_commandline_safemode
|
|
||||||
with_yardopts do
|
|
||||||
arguments.each_with_index do |arg, _i|
|
|
||||||
options[:safe_mode] = true if arg == '--safe'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Print a warning if the plugin failed to load
|
|
||||||
# @return [false]
|
|
||||||
def self.load_plugin_failed(name, exception)
|
|
||||||
log.error "Error loading plugin '#{name}'"
|
|
||||||
log.backtrace(exception) if $DEBUG
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
# Legacy support for {IGNORED_PLUGINS}
|
|
||||||
def self.add_ignored_plugins_file
|
|
||||||
if File.file?(IGNORED_PLUGINS)
|
|
||||||
options[:ignored_plugins] += File.read(IGNORED_PLUGINS).split(/\s+/)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Translates plugin names to add yard- prefix.
|
|
||||||
def self.translate_plugin_names
|
|
||||||
options[:ignored_plugins].map! {|name| translate_plugin_name(name) }
|
|
||||||
options[:autoload_plugins].map! {|name| translate_plugin_name(name) }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Loads the YAML configuration file into memory
|
|
||||||
# @return [Hash] the contents of the YAML file from disk
|
|
||||||
# @see CONFIG_FILE
|
|
||||||
def self.read_config_file
|
|
||||||
if File.file?(CONFIG_FILE)
|
|
||||||
require 'yaml'
|
|
||||||
YAML.load_file(CONFIG_FILE)
|
|
||||||
else
|
|
||||||
{}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Sanitizes and normalizes a plugin name to include the 'yard-' prefix.
|
|
||||||
# @param [String] name the plugin name
|
|
||||||
# @return [String] the sanitized and normalized plugin name.
|
|
||||||
def self.translate_plugin_name(name)
|
|
||||||
name = name.delete('/') # Security sanitization
|
|
||||||
name = "yard-" + name unless name =~ YARD_PLUGIN_PREFIX
|
|
||||||
name
|
|
||||||
end
|
|
||||||
|
|
||||||
# Temporarily loads .yardopts file into @yardopts
|
|
||||||
def self.with_yardopts
|
|
||||||
yfile = CLI::Yardoc::DEFAULT_YARDOPTS_FILE
|
|
||||||
@yardopts = File.file?(yfile) ? File.read_binary(yfile).shell_split : []
|
|
||||||
result = yield
|
|
||||||
@yardopts = nil
|
|
||||||
result
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Array<String>] arguments from commandline and yardopts file
|
|
||||||
def self.arguments
|
|
||||||
ARGV + @yardopts
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Config.options = Config::DEFAULT_CONFIG_OPTIONS
|
|
||||||
end
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
class Array
|
|
||||||
# Places values before or after another object (by value) in
|
|
||||||
# an array. This is used in tandem with the before and after
|
|
||||||
# methods of the {Insertion} class.
|
|
||||||
#
|
|
||||||
# @example Places an item before another
|
|
||||||
# [1, 2, 3].place(4).before(3) # => [1, 2, 4, 3]
|
|
||||||
# @example Places an item after another
|
|
||||||
# [:a, :b, :c].place(:x).after(:a) # => [:a, :x, :b, :c]
|
|
||||||
# @param [Array] values value to insert
|
|
||||||
# @return [Insertion] an insertion object to
|
|
||||||
# @see Insertion#before
|
|
||||||
# @see Insertion#after
|
|
||||||
def place(*values) Insertion.new(self, values) end
|
|
||||||
end
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
require 'fileutils'
|
|
||||||
|
|
||||||
class File
|
|
||||||
RELATIVE_PARENTDIR = '..'
|
|
||||||
RELATIVE_SAMEDIR = '.'
|
|
||||||
|
|
||||||
# @group Manipulating Paths
|
|
||||||
|
|
||||||
# Turns a path +to+ into a relative path from starting
|
|
||||||
# point +from+. The argument +from+ is assumed to be
|
|
||||||
# a filename. To treat it as a directory, make sure it
|
|
||||||
# ends in +File::SEPARATOR+ ('/' on UNIX filesystems).
|
|
||||||
#
|
|
||||||
# @param [String] from the starting filename
|
|
||||||
# (or directory with +from_isdir+ set to +true+).
|
|
||||||
# @param [String] to the final path that should be made relative.
|
|
||||||
# @return [String] the relative path from +from+ to +to+.
|
|
||||||
def self.relative_path(from, to)
|
|
||||||
from = expand_path(from).split(SEPARATOR)
|
|
||||||
to = expand_path(to).split(SEPARATOR)
|
|
||||||
from.length.times do
|
|
||||||
break if from[0] != to[0]
|
|
||||||
from.shift; to.shift
|
|
||||||
end
|
|
||||||
from.pop
|
|
||||||
join(*(from.map { RELATIVE_PARENTDIR } + to))
|
|
||||||
end
|
|
||||||
|
|
||||||
# Cleans a path by removing extraneous '..', '.' and '/' characters
|
|
||||||
#
|
|
||||||
# @example Clean a path
|
|
||||||
# File.cleanpath('a/b//./c/../e') # => "a/b/e"
|
|
||||||
# @param [String] path the path to clean
|
|
||||||
# @param [Boolean] rel_root allows relative path above root value
|
|
||||||
# @return [String] the sanitized path
|
|
||||||
def self.cleanpath(path, rel_root = false)
|
|
||||||
path = path.split(SEPARATOR)
|
|
||||||
path = path.inject([]) do |acc, comp|
|
|
||||||
next acc if comp == RELATIVE_SAMEDIR
|
|
||||||
if comp == RELATIVE_PARENTDIR && !acc.empty? && acc.last != RELATIVE_PARENTDIR
|
|
||||||
acc.pop
|
|
||||||
next acc
|
|
||||||
elsif !rel_root && comp == RELATIVE_PARENTDIR && acc.empty?
|
|
||||||
next acc
|
|
||||||
end
|
|
||||||
acc << comp
|
|
||||||
end
|
|
||||||
File.join(*path)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @group Reading Files
|
|
||||||
|
|
||||||
# Forces opening a file (for writing) by first creating the file's directory
|
|
||||||
# @param [String] file the filename to open
|
|
||||||
# @since 0.5.2
|
|
||||||
def self.open!(file, *args, &block)
|
|
||||||
dir = dirname(file)
|
|
||||||
FileUtils.mkdir_p(dir) unless directory?(dir)
|
|
||||||
open(file, *args, &block)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Reads a file with binary encoding
|
|
||||||
# @return [String] the ascii-8bit encoded data
|
|
||||||
# @since 0.5.3
|
|
||||||
def self.read_binary(file)
|
|
||||||
File.open(file, 'rb', &:read)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
class Hash
|
|
||||||
class << self
|
|
||||||
def create(*args)
|
|
||||||
if args.first.is_a?(Array) && args.size == 1
|
|
||||||
obj = new
|
|
||||||
args.first.each {|k, v| obj[k] = v }
|
|
||||||
obj
|
|
||||||
else
|
|
||||||
create_186(*args)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
alias create_186 []
|
|
||||||
alias [] create
|
|
||||||
end
|
|
||||||
end if RUBY_VERSION < "1.8.7"
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# The Insertion class inserts a value before or after another
|
|
||||||
# value in a list.
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# Insertion.new([1, 2, 3], 4).before(3) # => [1, 2, 4, 3]
|
|
||||||
class Insertion
|
|
||||||
# Creates an insertion object on a list with a value to be
|
|
||||||
# inserted. To finalize the insertion, call {#before} or
|
|
||||||
# {#after} on the object.
|
|
||||||
#
|
|
||||||
# @param [Array] list the list to perform the insertion on
|
|
||||||
# @param [Object] value the value to insert
|
|
||||||
def initialize(list, value)
|
|
||||||
@list = list
|
|
||||||
@values = (Array === value ? value : [value])
|
|
||||||
end
|
|
||||||
|
|
||||||
# Inserts the value before +val+
|
|
||||||
# @param [Object] val the object the value will be inserted before
|
|
||||||
# @param [Boolean] recursive look inside sublists
|
|
||||||
def before(val, recursive = false) insertion(val, 0, recursive) end
|
|
||||||
|
|
||||||
# Inserts the value after +val+.
|
|
||||||
#
|
|
||||||
# @example If subsections are ignored
|
|
||||||
# Insertion.new([1, [2], 3], :X).after(1) # => [1, [2], :X, 3]
|
|
||||||
# @param [Object] val the object the value will be inserted after
|
|
||||||
# @param [Boolean] recursive look inside sublists
|
|
||||||
def after(val, recursive = false) insertion(val, 1, recursive) end
|
|
||||||
|
|
||||||
# Alias for {#before} with +recursive+ set to true
|
|
||||||
# @since 0.6.0
|
|
||||||
def before_any(val) insertion(val, 0, true) end
|
|
||||||
|
|
||||||
# Alias for {#after} with +recursive+ set to true
|
|
||||||
# @since 0.6.0
|
|
||||||
def after_any(val) insertion(val, 1, true) end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# This method performs the actual insertion
|
|
||||||
#
|
|
||||||
# @param [Object] val the value to insert
|
|
||||||
# @param [Fixnum] rel the relative index (0 or 1) of where the object
|
|
||||||
# should be placed
|
|
||||||
# @param [Boolean] recursive look inside sublists
|
|
||||||
# @param [Array] list the list to place objects into
|
|
||||||
def insertion(val, rel, recursive = false, list = @list)
|
|
||||||
if recursive
|
|
||||||
list.each do |item|
|
|
||||||
next unless item.is_a?(Array)
|
|
||||||
tmp = item.dup
|
|
||||||
insertion(val, rel, recursive, item)
|
|
||||||
return(list) unless item == tmp
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
index = list.index(val)
|
|
||||||
list[index + rel, 0] = @values if index
|
|
||||||
list
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
class Module
|
|
||||||
# Returns the class name of a full module namespace path
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# module A::B::C; class_name end # => "C"
|
|
||||||
# @return [String] the last part of a module path
|
|
||||||
def class_name
|
|
||||||
name.split("::").last
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,68 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
class String
|
|
||||||
# Splits text into tokens the way a shell would, handling quoted
|
|
||||||
# text as a single token. Use '\"' and "\'" to escape quotes and
|
|
||||||
# '\\' to escape a backslash.
|
|
||||||
#
|
|
||||||
# @return [Array] an array representing the tokens
|
|
||||||
def shell_split
|
|
||||||
out = [String.new("")]
|
|
||||||
state = :none
|
|
||||||
escape_next = false
|
|
||||||
quote = String.new("")
|
|
||||||
strip.split(//).each do |char|
|
|
||||||
case state
|
|
||||||
when :none, :space
|
|
||||||
case char
|
|
||||||
when /\s/
|
|
||||||
out << String.new("") unless state == :space
|
|
||||||
state = :space
|
|
||||||
escape_next = false
|
|
||||||
when "\\"
|
|
||||||
if escape_next
|
|
||||||
out.last << char
|
|
||||||
escape_next = false
|
|
||||||
else
|
|
||||||
escape_next = true
|
|
||||||
end
|
|
||||||
when '"', "'"
|
|
||||||
if escape_next
|
|
||||||
out.last << char
|
|
||||||
escape_next = false
|
|
||||||
else
|
|
||||||
state = char
|
|
||||||
quote = String.new("")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
state = :none
|
|
||||||
out.last << char
|
|
||||||
escape_next = false
|
|
||||||
end
|
|
||||||
when '"', "'"
|
|
||||||
case char
|
|
||||||
when '"', "'"
|
|
||||||
if escape_next
|
|
||||||
quote << char
|
|
||||||
escape_next = false
|
|
||||||
elsif char == state
|
|
||||||
out.last << quote
|
|
||||||
state = :none
|
|
||||||
else
|
|
||||||
quote << char
|
|
||||||
end
|
|
||||||
when '\\'
|
|
||||||
if escape_next
|
|
||||||
quote << char
|
|
||||||
escape_next = false
|
|
||||||
else
|
|
||||||
escape_next = true
|
|
||||||
end
|
|
||||||
else
|
|
||||||
quote << char
|
|
||||||
escape_next = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
out
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# A subclass of Hash where all keys are converted into Symbols, and
|
|
||||||
# optionally, all String values are converted into Symbols.
|
|
||||||
class SymbolHash < Hash
|
|
||||||
# Creates a new SymbolHash object
|
|
||||||
#
|
|
||||||
# @param [Boolean] symbolize_value converts any String values into Symbols
|
|
||||||
# if this is set to +true+.
|
|
||||||
def initialize(symbolize_value = true)
|
|
||||||
@symbolize_value = symbolize_value
|
|
||||||
end
|
|
||||||
|
|
||||||
# @overload [](hash)
|
|
||||||
# Creates a SymbolHash object from an existing Hash
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# SymbolHash['x' => 1, :y => 2] # => #<SymbolHash:0x...>
|
|
||||||
# @param [Hash] hash the hash object
|
|
||||||
# @return [SymbolHash] a new SymbolHash from a hash object
|
|
||||||
#
|
|
||||||
# @overload [](*list)
|
|
||||||
# Creates a SymbolHash from an even list of keys and values
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# SymbolHash[key1, value1, key2, value2, ...]
|
|
||||||
# @param [Array] list an even list of key followed by value
|
|
||||||
# @return [SymbolHash] a new SymbolHash object
|
|
||||||
def self.[](*hsh)
|
|
||||||
obj = new
|
|
||||||
if hsh.size == 1 && hsh.first.is_a?(Hash)
|
|
||||||
hsh.first.each {|k, v| obj[k] = v }
|
|
||||||
else
|
|
||||||
0.step(hsh.size, 2) {|n| obj[hsh[n]] = hsh[n + 1] }
|
|
||||||
end
|
|
||||||
obj
|
|
||||||
end
|
|
||||||
|
|
||||||
# Assigns a value to a symbolized key
|
|
||||||
# @param [#to_sym] key the key
|
|
||||||
# @param [Object] value the value to be assigned. If this is a String and
|
|
||||||
# values are set to be symbolized, it will be converted into a Symbol.
|
|
||||||
def []=(key, value)
|
|
||||||
super(key.to_sym, value.instance_of?(String) && @symbolize_value ? value.to_sym : value)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Accessed a symbolized key
|
|
||||||
# @param [#to_sym] key the key to access
|
|
||||||
# @return [Object] the value associated with the key
|
|
||||||
def [](key) super(key.to_sym) end
|
|
||||||
|
|
||||||
# Deleted a key and value associated with it
|
|
||||||
# @param [#to_sym] key the key to delete
|
|
||||||
# @return [void]
|
|
||||||
def delete(key) super(key.to_sym) end
|
|
||||||
|
|
||||||
# Tests if a symbolized key exists
|
|
||||||
# @param [#to_sym] key the key to test
|
|
||||||
# @return [Boolean] whether the key exists
|
|
||||||
def key?(key) super(key.to_sym) end
|
|
||||||
alias has_key? key?
|
|
||||||
|
|
||||||
# Updates the object with the contents of another Hash object.
|
|
||||||
# This method modifies the original SymbolHash object
|
|
||||||
#
|
|
||||||
# @param [Hash] hash the hash object to copy the values from
|
|
||||||
# @return [SymbolHash] self
|
|
||||||
def update(hash) hash.each {|k, v| self[k] = v }; self end
|
|
||||||
alias merge! update
|
|
||||||
|
|
||||||
# Merges the contents of another hash into a new SymbolHash object
|
|
||||||
#
|
|
||||||
# @param [Hash] hash the hash of objects to copy
|
|
||||||
# @return [SymbolHash] a new SymbolHash containing the merged data
|
|
||||||
def merge(hash) dup.merge!(hash) end
|
|
||||||
end
|
|
||||||
@ -1,386 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
# A documentation string, or "docstring" for short, encapsulates the
|
|
||||||
# comments and metadata, or "tags", of an object. Meta-data is expressed
|
|
||||||
# in the form +@tag VALUE+, where VALUE can span over multiple lines as
|
|
||||||
# long as they are indented. The following +@example+ tag shows how tags
|
|
||||||
# can be indented:
|
|
||||||
#
|
|
||||||
# # @example My example
|
|
||||||
# # a = "hello world"
|
|
||||||
# # a.reverse
|
|
||||||
# # @version 1.0
|
|
||||||
#
|
|
||||||
# Tags can be nested in a documentation string, though the {Tags::Tag}
|
|
||||||
# itself is responsible for parsing the inner tags.
|
|
||||||
class Docstring < String
|
|
||||||
class << self
|
|
||||||
# @note Plugin developers should make sure to reset this value
|
|
||||||
# after parsing finishes. This can be done via the
|
|
||||||
# {Parser::SourceParser.after_parse_list} callback. This will
|
|
||||||
# ensure that YARD can properly parse multiple projects in
|
|
||||||
# the same process.
|
|
||||||
# @return [Class<DocstringParser>] the parser class used to parse
|
|
||||||
# text and optional meta-data from docstrings. Defaults to
|
|
||||||
# {DocstringParser}.
|
|
||||||
# @see DocstringParser
|
|
||||||
# @see Parser::SourceParser.after_parse_list
|
|
||||||
attr_accessor :default_parser
|
|
||||||
|
|
||||||
# Creates a parser object using the current {default_parser}.
|
|
||||||
# Equivalent to:
|
|
||||||
# Docstring.default_parser.new(*args)
|
|
||||||
# @param args arguments are passed to the {DocstringParser}
|
|
||||||
# class. See {DocstringParser#initialize} for details on
|
|
||||||
# arguments.
|
|
||||||
# @return [DocstringParser] the parser object used to parse a
|
|
||||||
# docstring.
|
|
||||||
def parser(*args) default_parser.new(*args) end
|
|
||||||
end
|
|
||||||
|
|
||||||
self.default_parser = DocstringParser
|
|
||||||
|
|
||||||
# @return [Array<Tags::RefTag>] the list of reference tags
|
|
||||||
attr_reader :ref_tags
|
|
||||||
|
|
||||||
# @return [CodeObjects::Base] the object that owns the docstring.
|
|
||||||
attr_accessor :object
|
|
||||||
|
|
||||||
# @return [Range] line range in the {#object}'s file where the docstring was parsed from
|
|
||||||
attr_accessor :line_range
|
|
||||||
|
|
||||||
# @return [String] the raw documentation (including raw tag text)
|
|
||||||
attr_reader :all
|
|
||||||
|
|
||||||
# @return [Boolean] whether the docstring was started with "##"
|
|
||||||
attr_reader :hash_flag
|
|
||||||
def hash_flag=(v) @hash_flag = v.nil? ? false : v end
|
|
||||||
|
|
||||||
# Matches a tag at the start of a comment line
|
|
||||||
# @deprecated Use {DocstringParser::META_MATCH}
|
|
||||||
META_MATCH = DocstringParser::META_MATCH
|
|
||||||
|
|
||||||
# @group Creating a Docstring Object
|
|
||||||
|
|
||||||
# Creates a new docstring without performing any parsing through
|
|
||||||
# a {DocstringParser}. This method is called by +DocstringParser+
|
|
||||||
# when creating the new docstring object.
|
|
||||||
#
|
|
||||||
# @param [String] text the textual portion of the docstring
|
|
||||||
# @param [Array<Tags::Tag>] tags the list of tag objects in the docstring
|
|
||||||
# @param [CodeObjects::Base, nil] object the object associated with the
|
|
||||||
# docstring. May be nil.
|
|
||||||
# @param [String] raw_data the complete docstring, including all
|
|
||||||
# original formatting and any unparsed tags/directives.
|
|
||||||
# @param [CodeObjects::Base, nil] ref_object a reference object used for
|
|
||||||
# the base set of documentation / tag information.
|
|
||||||
def self.new!(text, tags = [], object = nil, raw_data = nil, ref_object = nil)
|
|
||||||
docstring = allocate
|
|
||||||
docstring.replace(text, false)
|
|
||||||
docstring.object = object
|
|
||||||
docstring.add_tag(*tags)
|
|
||||||
docstring.instance_variable_set("@unresolved_reference", ref_object)
|
|
||||||
docstring.instance_variable_set("@all", raw_data) if raw_data
|
|
||||||
docstring
|
|
||||||
end
|
|
||||||
|
|
||||||
# Creates a new docstring with the raw contents attached to an optional
|
|
||||||
# object. Parsing will be done by the {DocstringParser} class.
|
|
||||||
#
|
|
||||||
# @note To properly parse directives with proper parser context within
|
|
||||||
# handlers, you should not use this method to create a Docstring.
|
|
||||||
# Instead, use the {parser}, which takes a handler object that
|
|
||||||
# can pass parser state onto directives. If a Docstring is created
|
|
||||||
# with this method, directives do not have access to any parser
|
|
||||||
# state, and may not function as expected.
|
|
||||||
# @example
|
|
||||||
# Docstring.new("hello world\n@return Object return", someobj)
|
|
||||||
#
|
|
||||||
# @param [String] content the raw comments to be parsed into a docstring
|
|
||||||
# and associated meta-data.
|
|
||||||
# @param [CodeObjects::Base] object an object to associate the docstring
|
|
||||||
# with.
|
|
||||||
def initialize(content = '', object = nil)
|
|
||||||
@object = object
|
|
||||||
@summary = nil
|
|
||||||
@hash_flag = false
|
|
||||||
|
|
||||||
self.all = content
|
|
||||||
end
|
|
||||||
|
|
||||||
# Adds another {Docstring}, copying over tags.
|
|
||||||
#
|
|
||||||
# @param [Docstring, String] other the other docstring (or string) to
|
|
||||||
# add.
|
|
||||||
# @return [Docstring] a new docstring with both docstrings combines
|
|
||||||
def +(other)
|
|
||||||
case other
|
|
||||||
when Docstring
|
|
||||||
Docstring.new([all, other.all].join("\n"), object)
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
resolve_reference
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
# Replaces the docstring with new raw content. Called by {#all=}.
|
|
||||||
# @param [String] content the raw comments to be parsed
|
|
||||||
def replace(content, parse = true)
|
|
||||||
content = content.join("\n") if content.is_a?(Array)
|
|
||||||
@tags = []
|
|
||||||
@ref_tags = []
|
|
||||||
if parse
|
|
||||||
super(parse_comments(content))
|
|
||||||
else
|
|
||||||
@all = content
|
|
||||||
@unresolved_reference = nil
|
|
||||||
super(content)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
alias all= replace
|
|
||||||
|
|
||||||
# Deep-copies a docstring
|
|
||||||
#
|
|
||||||
# @note This method creates a new docstring with new tag lists, but does
|
|
||||||
# not create new individual tags. Modifying the tag objects will still
|
|
||||||
# affect the original tags.
|
|
||||||
# @return [Docstring] a new copied docstring
|
|
||||||
# @since 0.7.0
|
|
||||||
def dup
|
|
||||||
resolve_reference
|
|
||||||
obj = super
|
|
||||||
%w(all summary tags ref_tags).each do |name|
|
|
||||||
val = instance_variable_defined?("@#{name}") && instance_variable_get("@#{name}")
|
|
||||||
obj.instance_variable_set("@#{name}", val ? val.dup : nil)
|
|
||||||
end
|
|
||||||
obj
|
|
||||||
end
|
|
||||||
|
|
||||||
# @endgroup
|
|
||||||
|
|
||||||
# @return [Fixnum] the first line of the {#line_range}
|
|
||||||
# @return [nil] if there is no associated {#line_range}
|
|
||||||
def line
|
|
||||||
line_range ? line_range.first : nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Gets the first line of a docstring to the period or the first paragraph.
|
|
||||||
# @return [String] The first line or paragraph of the docstring; always ends with a period.
|
|
||||||
def summary
|
|
||||||
resolve_reference
|
|
||||||
return @summary if defined?(@summary) && @summary
|
|
||||||
stripped = gsub(/[\r\n](?![\r\n])/, ' ').strip
|
|
||||||
num_parens = 0
|
|
||||||
idx = length.times do |index|
|
|
||||||
case stripped[index, 1]
|
|
||||||
when "."
|
|
||||||
next_char = stripped[index + 1, 1].to_s
|
|
||||||
break index - 1 if num_parens <= 0 && next_char =~ /^\s*$/
|
|
||||||
when "\r", "\n"
|
|
||||||
next_char = stripped[index + 1, 1].to_s
|
|
||||||
if next_char =~ /^\s*$/
|
|
||||||
break stripped[index - 1, 1] == '.' ? index - 2 : index - 1
|
|
||||||
end
|
|
||||||
when "{", "(", "["
|
|
||||||
num_parens += 1
|
|
||||||
when "}", ")", "]"
|
|
||||||
num_parens -= 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@summary = stripped[0..idx]
|
|
||||||
if !@summary.empty? && @summary !~ /\A\s*\{include:.+\}\s*\Z/
|
|
||||||
@summary += '.'
|
|
||||||
end
|
|
||||||
@summary
|
|
||||||
end
|
|
||||||
|
|
||||||
# Reformats and returns a raw representation of the tag data using the
|
|
||||||
# current tag and docstring data, not the original text.
|
|
||||||
#
|
|
||||||
# @return [String] the updated raw formatted docstring data
|
|
||||||
# @since 0.7.0
|
|
||||||
# @todo Add Tags::Tag#to_raw and refactor
|
|
||||||
def to_raw
|
|
||||||
tag_data = tags.map do |tag|
|
|
||||||
case tag
|
|
||||||
when Tags::OverloadTag
|
|
||||||
tag_text = "@#{tag.tag_name} #{tag.signature}\n"
|
|
||||||
unless tag.docstring.blank?
|
|
||||||
tag_text += "\n " + tag.docstring.all.gsub(/\r?\n/, "\n ")
|
|
||||||
end
|
|
||||||
when Tags::OptionTag
|
|
||||||
tag_text = "@#{tag.tag_name} #{tag.name}"
|
|
||||||
tag_text += ' [' + tag.pair.types.join(', ') + ']' if tag.pair.types
|
|
||||||
tag_text += ' ' + tag.pair.name.to_s if tag.pair.name
|
|
||||||
tag_text += "\n " if tag.name && tag.text
|
|
||||||
tag_text += ' (' + tag.pair.defaults.join(', ') + ')' if tag.pair.defaults
|
|
||||||
tag_text += " " + tag.pair.text.strip.gsub(/\n/, "\n ") if tag.pair.text
|
|
||||||
else
|
|
||||||
tag_text = '@' + tag.tag_name
|
|
||||||
tag_text += ' [' + tag.types.join(', ') + ']' if tag.types
|
|
||||||
tag_text += ' ' + tag.name.to_s if tag.name
|
|
||||||
tag_text += "\n " if tag.name && tag.text
|
|
||||||
tag_text += ' ' + tag.text.strip.gsub(/\n/, "\n ") if tag.text
|
|
||||||
end
|
|
||||||
tag_text
|
|
||||||
end
|
|
||||||
[strip, tag_data.join("\n")].reject(&:empty?).compact.join("\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
# @group Creating and Accessing Meta-data
|
|
||||||
|
|
||||||
# Adds a tag or reftag object to the tag list. If you want to parse
|
|
||||||
# tag data based on the {Tags::DefaultFactory} tag factory, use
|
|
||||||
# {DocstringParser} instead.
|
|
||||||
#
|
|
||||||
# @param [Tags::Tag, Tags::RefTag] tags list of tag objects to add
|
|
||||||
# @return [void]
|
|
||||||
def add_tag(*tags)
|
|
||||||
tags.each_with_index do |tag, i|
|
|
||||||
case tag
|
|
||||||
when Tags::Tag
|
|
||||||
tag.object = object
|
|
||||||
@tags << tag
|
|
||||||
when Tags::RefTag, Tags::RefTagList
|
|
||||||
@ref_tags << tag
|
|
||||||
else
|
|
||||||
raise ArgumentError, "expected Tag or RefTag, got #{tag.class} (at index #{i})"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Convenience method to return the first tag
|
|
||||||
# object in the list of tag objects of that name
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# doc = Docstring.new("@return zero when nil")
|
|
||||||
# doc.tag(:return).text # => "zero when nil"
|
|
||||||
#
|
|
||||||
# @param [#to_s] name the tag name to return data for
|
|
||||||
# @return [Tags::Tag] the first tag in the list of {#tags}
|
|
||||||
def tag(name)
|
|
||||||
tags.find {|tag| tag.tag_name.to_s == name.to_s }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a list of tags specified by +name+ or all tags if +name+ is not specified.
|
|
||||||
#
|
|
||||||
# @param [#to_s] name the tag name to return data for, or nil for all tags
|
|
||||||
# @return [Array<Tags::Tag>] the list of tags by the specified tag name
|
|
||||||
def tags(name = nil)
|
|
||||||
list = stable_sort_by(@tags + convert_ref_tags, &:tag_name)
|
|
||||||
return list unless name
|
|
||||||
list.select {|tag| tag.tag_name.to_s == name.to_s }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns true if at least one tag by the name +name+ was declared
|
|
||||||
#
|
|
||||||
# @param [String] name the tag name to search for
|
|
||||||
# @return [Boolean] whether or not the tag +name+ was declared
|
|
||||||
def has_tag?(name)
|
|
||||||
tags.any? {|tag| tag.tag_name.to_s == name.to_s }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Delete all tags with +name+
|
|
||||||
# @param [String] name the tag name
|
|
||||||
# @return [void]
|
|
||||||
# @since 0.7.0
|
|
||||||
def delete_tags(name)
|
|
||||||
delete_tag_if {|tag| tag.tag_name.to_s == name.to_s }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Deletes all tags where the block returns true
|
|
||||||
# @yieldparam [Tags::Tag] tag the tag that is being tested
|
|
||||||
# @yieldreturn [Boolean] true if the tag should be deleted
|
|
||||||
# @return [void]
|
|
||||||
# @since 0.7.0
|
|
||||||
def delete_tag_if(&block)
|
|
||||||
@tags.delete_if(&block)
|
|
||||||
@ref_tags.delete_if(&block)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns true if the docstring has no content that is visible to a template.
|
|
||||||
#
|
|
||||||
# @param [Boolean] only_visible_tags whether only {Tags::Library.visible_tags}
|
|
||||||
# should be checked, or if all tags should be considered.
|
|
||||||
# @return [Boolean] whether or not the docstring has content
|
|
||||||
def blank?(only_visible_tags = true)
|
|
||||||
if only_visible_tags
|
|
||||||
empty? && !tags.any? {|tag| Tags::Library.visible_tags.include?(tag.tag_name.to_sym) }
|
|
||||||
else
|
|
||||||
empty? && @tags.empty? && @ref_tags.empty?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @endgroup
|
|
||||||
|
|
||||||
# Resolves unresolved other docstring reference if there is
|
|
||||||
# unresolved reference. Does nothing if there is no unresolved
|
|
||||||
# reference.
|
|
||||||
#
|
|
||||||
# Normally, you don't need to call this method
|
|
||||||
# explicitly. Resolving unresolved reference is done implicitly.
|
|
||||||
#
|
|
||||||
# @return [void]
|
|
||||||
def resolve_reference
|
|
||||||
loop do
|
|
||||||
return if defined?(@unresolved_reference).nil? || @unresolved_reference.nil?
|
|
||||||
return if CodeObjects::Proxy === @unresolved_reference
|
|
||||||
|
|
||||||
reference = @unresolved_reference
|
|
||||||
@unresolved_reference = nil
|
|
||||||
self.all = [reference.docstring.all, @all].join("\n")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Maps valid reference tags
|
|
||||||
#
|
|
||||||
# @return [Array<Tags::RefTag>] the list of valid reference tags
|
|
||||||
def convert_ref_tags
|
|
||||||
list = @ref_tags.reject {|t| CodeObjects::Proxy === t.owner }
|
|
||||||
|
|
||||||
@ref_tag_recurse_count ||= 0
|
|
||||||
@ref_tag_recurse_count += 1
|
|
||||||
if @ref_tag_recurse_count > 2
|
|
||||||
log.error "#{@object.file}:#{@object.line}: Detected circular reference tag in " \
|
|
||||||
"`#{@object}', ignoring all reference tags for this object " \
|
|
||||||
"(#{@ref_tags.map {|t| "@#{t.tag_name}" }.join(", ")})."
|
|
||||||
@ref_tags = []
|
|
||||||
return @ref_tags
|
|
||||||
end
|
|
||||||
list = list.map(&:tags).flatten
|
|
||||||
@ref_tag_recurse_count -= 1
|
|
||||||
list
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parses out comments split by newlines into a new code object
|
|
||||||
#
|
|
||||||
# @param [String] comments
|
|
||||||
# the newline delimited array of comments. If the comments
|
|
||||||
# are passed as a String, they will be split by newlines.
|
|
||||||
#
|
|
||||||
# @return [String] the non-metadata portion of the comments to
|
|
||||||
# be used as a docstring
|
|
||||||
def parse_comments(comments)
|
|
||||||
parser = self.class.parser
|
|
||||||
parser.parse(comments, object)
|
|
||||||
@all = parser.raw_text
|
|
||||||
@unresolved_reference = parser.reference
|
|
||||||
add_tag(*parser.tags)
|
|
||||||
parser.text
|
|
||||||
end
|
|
||||||
|
|
||||||
# A stable sort_by method.
|
|
||||||
#
|
|
||||||
# @param list [Enumerable] the list to sort.
|
|
||||||
# @return [Array] a stable sorted list.
|
|
||||||
def stable_sort_by(list)
|
|
||||||
list.each_with_index.sort_by {|tag, i| [yield(tag), i] }.map(&:first)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,345 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
require 'ostruct'
|
|
||||||
|
|
||||||
module YARD
|
|
||||||
# Parses text and creates a {Docstring} object to represent documentation
|
|
||||||
# for a {CodeObjects::Base}. To create a new docstring, you should initialize
|
|
||||||
# the parser and call {#parse} followed by {#to_docstring}.
|
|
||||||
#
|
|
||||||
# == Subclassing Notes
|
|
||||||
#
|
|
||||||
# The DocstringParser can be subclassed and subtituted during parsing by
|
|
||||||
# setting the {Docstring.default_parser} attribute with the name of the
|
|
||||||
# subclass. This allows developers to change the way docstrings are
|
|
||||||
# parsed, allowing for completely different docstring syntaxes.
|
|
||||||
#
|
|
||||||
# @example Creating a Docstring with a DocstringParser
|
|
||||||
# DocstringParser.new.parse("text here").to_docstring
|
|
||||||
# @example Creating a Custom DocstringParser
|
|
||||||
# # Parses docstrings backwards!
|
|
||||||
# class ReverseDocstringParser
|
|
||||||
# def parse_content(content)
|
|
||||||
# super(content.reverse)
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# # Set the parser as default when parsing
|
|
||||||
# YARD::Docstring.default_parser = ReverseDocstringParser
|
|
||||||
# @see #parse_content
|
|
||||||
# @since 0.8.0
|
|
||||||
class DocstringParser
|
|
||||||
# @return [String] the parsed text portion of the docstring,
|
|
||||||
# with tags removed.
|
|
||||||
attr_accessor :text
|
|
||||||
|
|
||||||
# @return [String] the complete input string to the parser.
|
|
||||||
attr_accessor :raw_text
|
|
||||||
|
|
||||||
# @return [Array<Tags::Tag>] the list of meta-data tags identified
|
|
||||||
# by the parser
|
|
||||||
attr_accessor :tags
|
|
||||||
|
|
||||||
# @return [Array<Tags::Directive>] a list of directives identified
|
|
||||||
# by the parser. This list will not be passed on to the
|
|
||||||
# Docstring object.
|
|
||||||
attr_accessor :directives
|
|
||||||
|
|
||||||
# @return [OpenStruct] any arbitrary state to be passed between
|
|
||||||
# tags during parsing. Mainly used by directives to coordinate
|
|
||||||
# behaviour (so that directives can be aware of other directives
|
|
||||||
# used in a docstring).
|
|
||||||
attr_accessor :state
|
|
||||||
|
|
||||||
# @return [CodeObjects::Base, nil] the object associated with
|
|
||||||
# the docstring being parsed. May be nil if the docstring is
|
|
||||||
# not attached to any object.
|
|
||||||
attr_accessor :object
|
|
||||||
|
|
||||||
# @return [CodeObjects::Base, nil] the object referenced by
|
|
||||||
# the docstring being parsed. May be nil if the docstring doesn't
|
|
||||||
# refer to any object.
|
|
||||||
attr_accessor :reference
|
|
||||||
|
|
||||||
# @return [Handlers::Base, nil] the handler parsing this
|
|
||||||
# docstring. May be nil if this docstring parser is not
|
|
||||||
# initialized through
|
|
||||||
attr_accessor :handler
|
|
||||||
|
|
||||||
# @return [Tags::Library] the tag library being used to
|
|
||||||
# identify registered tags in the docstring.
|
|
||||||
attr_accessor :library
|
|
||||||
|
|
||||||
# The regular expression to match the tag syntax
|
|
||||||
META_MATCH = /^@(!)?((?:\w\.?)+)(?:\s+(.*))?$/i
|
|
||||||
|
|
||||||
# @!group Creation and Conversion Methods
|
|
||||||
|
|
||||||
# Creates a new parser to parse docstring data
|
|
||||||
#
|
|
||||||
# @param [Tags::Library] library a tag library for recognizing
|
|
||||||
# tags.
|
|
||||||
def initialize(library = Tags::Library.instance)
|
|
||||||
@text = ""
|
|
||||||
@raw_text = ""
|
|
||||||
@tags = []
|
|
||||||
@directives = []
|
|
||||||
@library = library
|
|
||||||
@object = nil
|
|
||||||
@reference = nil
|
|
||||||
@handler = nil
|
|
||||||
@state = OpenStruct.new
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Docstring] translates parsed text into
|
|
||||||
# a Docstring object.
|
|
||||||
def to_docstring
|
|
||||||
Docstring.new!(text, tags, object, raw_text, reference)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @!group Parsing Methods
|
|
||||||
|
|
||||||
# Parses all content and returns itself.
|
|
||||||
#
|
|
||||||
# @param [String] content the docstring text to parse
|
|
||||||
# @param [CodeObjects::Base] object the object that the docstring
|
|
||||||
# is attached to. Will be passed to directives to act on
|
|
||||||
# this object.
|
|
||||||
# @param [Handlers::Base, nil] handler the handler object that is
|
|
||||||
# parsing this object. May be nil if this parser is not being
|
|
||||||
# called from a {Parser::SourceParser} context.
|
|
||||||
# @return [self] the parser object. To get the docstring,
|
|
||||||
# call {#to_docstring}.
|
|
||||||
# @see #to_docstring
|
|
||||||
def parse(content, object = nil, handler = nil)
|
|
||||||
@object = object
|
|
||||||
@handler = handler
|
|
||||||
@reference, @raw_text = detect_reference(content)
|
|
||||||
text = parse_content(@raw_text)
|
|
||||||
@text = text.strip
|
|
||||||
call_directives_after_parse
|
|
||||||
post_process
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parses a given block of text.
|
|
||||||
#
|
|
||||||
# @param [String] content the content to parse
|
|
||||||
# @note Subclasses can override this method to perform custom
|
|
||||||
# parsing of content data.
|
|
||||||
def parse_content(content)
|
|
||||||
content = content.split(/\r?\n/) if content.is_a?(String)
|
|
||||||
return '' if !content || content.empty?
|
|
||||||
docstring = String.new("")
|
|
||||||
|
|
||||||
indent = content.first[/^\s*/].length
|
|
||||||
last_indent = 0
|
|
||||||
orig_indent = 0
|
|
||||||
directive = false
|
|
||||||
last_line = ""
|
|
||||||
tag_name = nil
|
|
||||||
tag_buf = []
|
|
||||||
|
|
||||||
(content + ['']).each_with_index do |line, index|
|
|
||||||
indent = line[/^\s*/].length
|
|
||||||
empty = (line =~ /^\s*$/ ? true : false)
|
|
||||||
done = content.size == index
|
|
||||||
|
|
||||||
if tag_name && (((indent < orig_indent && !empty) || done ||
|
|
||||||
(indent == 0 && !empty)) || (indent <= last_indent && line =~ META_MATCH))
|
|
||||||
buf = tag_buf.join("\n")
|
|
||||||
if directive || tag_is_directive?(tag_name)
|
|
||||||
directive = create_directive(tag_name, buf)
|
|
||||||
if directive
|
|
||||||
docstring << parse_content(directive.expanded_text).chomp
|
|
||||||
end
|
|
||||||
else
|
|
||||||
create_tag(tag_name, buf)
|
|
||||||
end
|
|
||||||
tag_name = nil
|
|
||||||
tag_buf = []
|
|
||||||
directive = false
|
|
||||||
orig_indent = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
# Found a meta tag
|
|
||||||
if line =~ META_MATCH
|
|
||||||
directive = $1
|
|
||||||
tag_name = $2
|
|
||||||
tag_buf = [($3 || '')]
|
|
||||||
elsif tag_name && indent >= orig_indent && !empty
|
|
||||||
orig_indent = indent if orig_indent == 0
|
|
||||||
# Extra data added to the tag on the next line
|
|
||||||
last_empty = last_line =~ /^[ \t]*$/ ? true : false
|
|
||||||
|
|
||||||
tag_buf << '' if last_empty
|
|
||||||
tag_buf << line.gsub(/^[ \t]{#{orig_indent}}/, '')
|
|
||||||
elsif !tag_name
|
|
||||||
# Regular docstring text
|
|
||||||
docstring << line
|
|
||||||
docstring << "\n"
|
|
||||||
end
|
|
||||||
|
|
||||||
last_indent = indent
|
|
||||||
last_line = line
|
|
||||||
end
|
|
||||||
|
|
||||||
docstring
|
|
||||||
end
|
|
||||||
|
|
||||||
# @!group Parser Callback Methods
|
|
||||||
|
|
||||||
# Call post processing callbacks on parser.
|
|
||||||
# This is called implicitly by parser. Use this when
|
|
||||||
# manually configuring a {Docstring} object.
|
|
||||||
#
|
|
||||||
# @return [void]
|
|
||||||
def post_process
|
|
||||||
call_after_parse_callbacks
|
|
||||||
end
|
|
||||||
|
|
||||||
# @!group Tag Manipulation Methods
|
|
||||||
|
|
||||||
# Creates a tag from the {Tags::DefaultFactory tag factory}.
|
|
||||||
#
|
|
||||||
# To add an already created tag object, append it to {#tags}.
|
|
||||||
#
|
|
||||||
# @param [String] tag_name the tag name
|
|
||||||
# @param [String] tag_buf the text attached to the tag with newlines removed.
|
|
||||||
# @return [Tags::Tag, Tags::RefTag] a tag
|
|
||||||
def create_tag(tag_name, tag_buf = '')
|
|
||||||
if tag_buf =~ /\A\s*(?:(\S+)\s+)?\(\s*see\s+(\S+)\s*\)\s*\Z/
|
|
||||||
return create_ref_tag(tag_name, $1, $2)
|
|
||||||
end
|
|
||||||
|
|
||||||
if library.has_tag?(tag_name)
|
|
||||||
@tags += [library.tag_create(tag_name, tag_buf)].flatten
|
|
||||||
else
|
|
||||||
log.warn "Unknown tag @#{tag_name}" +
|
|
||||||
(object ? " in file `#{object.file}` near line #{object.line}" : "")
|
|
||||||
end
|
|
||||||
rescue Tags::TagFormatError
|
|
||||||
log.warn "Invalid tag format for @#{tag_name}" +
|
|
||||||
(object ? " in file `#{object.file}` near line #{object.line}" : "")
|
|
||||||
end
|
|
||||||
|
|
||||||
# Creates a {Tags::RefTag}
|
|
||||||
def create_ref_tag(tag_name, name, object_name)
|
|
||||||
@tags << Tags::RefTagList.new(tag_name, P(object, object_name), name)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Creates a new directive using the registered {#library}
|
|
||||||
# @return [Tags::Directive] the directive object that is created
|
|
||||||
def create_directive(tag_name, tag_buf)
|
|
||||||
if library.has_directive?(tag_name)
|
|
||||||
dir = library.directive_create(tag_name, tag_buf, self)
|
|
||||||
if dir.is_a?(Tags::Directive)
|
|
||||||
@directives << dir
|
|
||||||
dir
|
|
||||||
end
|
|
||||||
else
|
|
||||||
log.warn "Unknown directive @!#{tag_name}" +
|
|
||||||
(object ? " in file `#{object.file}` near line #{object.line}" : "")
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
rescue Tags::TagFormatError
|
|
||||||
log.warn "Invalid directive format for @!#{tag_name}" +
|
|
||||||
(object ? " in file `#{object.file}` near line #{object.line}" : "")
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Backward compatibility to detect old tags that should be specified
|
|
||||||
# as directives in 0.8 and onward.
|
|
||||||
def tag_is_directive?(tag_name)
|
|
||||||
list = %w(attribute endgroup group macro method scope visibility)
|
|
||||||
list.include?(tag_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Creates a callback that is called after a docstring is successfully
|
|
||||||
# parsed. Use this method to perform sanity checks on a docstring's
|
|
||||||
# tag data, or add any extra tags automatically to a docstring.
|
|
||||||
#
|
|
||||||
# @yield [parser] a block to be called after a docstring is parsed
|
|
||||||
# @yieldparam [DocstringParser] parser the docstring parser object
|
|
||||||
# with all directives and tags created.
|
|
||||||
# @yieldreturn [void]
|
|
||||||
# @return [void]
|
|
||||||
def self.after_parse(&block)
|
|
||||||
after_parse_callbacks << block
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Array<Proc>] the {after_parse} callback proc objects
|
|
||||||
def self.after_parse_callbacks
|
|
||||||
@after_parse_callbacks ||= []
|
|
||||||
end
|
|
||||||
|
|
||||||
# Define a callback to check that @param tags are properly named
|
|
||||||
after_parse do |parser|
|
|
||||||
next unless parser.object
|
|
||||||
next unless parser.object.is_a?(CodeObjects::MethodObject)
|
|
||||||
next if parser.object.is_alias?
|
|
||||||
names = parser.object.parameters.map {|l| l.first.gsub(/\W/, '') }
|
|
||||||
seen_names = []
|
|
||||||
infile_info = "\n in file `#{parser.object.file}' " \
|
|
||||||
"near line #{parser.object.line}"
|
|
||||||
parser.tags.each do |tag|
|
|
||||||
next if tag.is_a?(Tags::RefTagList) # we don't handle this yet
|
|
||||||
next unless tag.tag_name == "param"
|
|
||||||
if seen_names.include?(tag.name)
|
|
||||||
log.warn "@param tag has duplicate parameter name: " \
|
|
||||||
"#{tag.name} #{infile_info}"
|
|
||||||
elsif names.include?(tag.name)
|
|
||||||
seen_names << tag.name
|
|
||||||
else
|
|
||||||
log.warn "@param tag has unknown parameter name: " \
|
|
||||||
"#{tag.name} #{infile_info}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def namespace
|
|
||||||
object && object.namespace
|
|
||||||
end
|
|
||||||
|
|
||||||
def detect_reference(content)
|
|
||||||
if content =~ /\A\s*\(see (\S+)\s*\)(?:\s|$)/
|
|
||||||
path = $1
|
|
||||||
extra = $'
|
|
||||||
[CodeObjects::Proxy.new(namespace, path), extra]
|
|
||||||
else
|
|
||||||
[nil, content]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @!group Parser Callback Methods
|
|
||||||
|
|
||||||
# Calls the {Tags::Directive#after_parse} callback on all the
|
|
||||||
# created directives.
|
|
||||||
def call_directives_after_parse
|
|
||||||
directives.each(&:after_parse)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Calls all {after_parse} callbacks
|
|
||||||
def call_after_parse_callbacks
|
|
||||||
self.class.after_parse_callbacks.each do |cb|
|
|
||||||
cb.call(self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Define a callback to check that @see tags do not use {}.
|
|
||||||
after_parse do |parser|
|
|
||||||
next unless parser.object
|
|
||||||
|
|
||||||
parser.tags.each_with_index do |tag, i|
|
|
||||||
next if tag.is_a?(Tags::RefTagList) # we don't handle this yet
|
|
||||||
next unless tag.tag_name == "see"
|
|
||||||
next unless "#{tag.name}#{tag.text}" =~ /\A\{.*\}\Z/
|
|
||||||
infile_info = "\n in file `#{parser.object.file}' " \
|
|
||||||
"near line #{parser.object.line}"
|
|
||||||
log.warn "@see tag (##{i + 1}) should not be wrapped in {} " \
|
|
||||||
"(causes rendering issues): #{infile_info}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# Backward compatibility for gem specification lookup
|
|
||||||
# @see Gem::SourceIndex
|
|
||||||
module YARD
|
|
||||||
module GemIndex
|
|
||||||
module_function
|
|
||||||
|
|
||||||
def find_all_by_name(*args)
|
|
||||||
if defined?(Gem::Specification) && Gem::Specification.respond_to?(:find_all_by_name)
|
|
||||||
Gem::Specification.find_all_by_name(*args)
|
|
||||||
else
|
|
||||||
Gem.source_index.find_name(*args)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def each(&block)
|
|
||||||
if defined?(Gem::Specification) && Gem::Specification.respond_to?(:each)
|
|
||||||
Gem::Specification.each(&block)
|
|
||||||
else
|
|
||||||
Gem.source_index.find_name('').each(&block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def all
|
|
||||||
each.to_a
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# @group Global Convenience Methods
|
|
||||||
|
|
||||||
# Shortcut for creating a YARD::CodeObjects::Proxy via a path
|
|
||||||
#
|
|
||||||
# @see YARD::CodeObjects::Proxy
|
|
||||||
# @see YARD::Registry.resolve
|
|
||||||
def P(namespace, name = nil, type = nil) # rubocop:disable Naming/MethodName
|
|
||||||
if name.nil?
|
|
||||||
name = namespace
|
|
||||||
namespace = nil
|
|
||||||
end
|
|
||||||
YARD::Registry.resolve(namespace, name, false, true, type)
|
|
||||||
end
|
|
||||||
|
|
||||||
# The global {YARD::Logger} instance
|
|
||||||
#
|
|
||||||
# @return [YARD::Logger] the global {YARD::Logger} instance
|
|
||||||
# @see YARD::Logger
|
|
||||||
def log
|
|
||||||
YARD::Logger.instance
|
|
||||||
end
|
|
||||||
@ -1,595 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module Handlers
|
|
||||||
# Raise this error when a handler should exit before completing.
|
|
||||||
# The exception will be silenced, allowing the next handler(s) in the
|
|
||||||
# queue to be executed.
|
|
||||||
# @since 0.8.4
|
|
||||||
class HandlerAborted < ::RuntimeError; end
|
|
||||||
|
|
||||||
# Raised during processing phase when a handler needs to perform
|
|
||||||
# an operation on an object's namespace but the namespace could
|
|
||||||
# not be resolved.
|
|
||||||
class NamespaceMissingError < Parser::UndocumentableError
|
|
||||||
# The object the error occurred on
|
|
||||||
# @return [CodeObjects::Base] a code object
|
|
||||||
attr_accessor :object
|
|
||||||
|
|
||||||
def initialize(object) @object = object end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Handlers are pluggable semantic parsers for YARD's code generation
|
|
||||||
# phase. They allow developers to control what information gets
|
|
||||||
# generated by YARD, giving them the ability to, for instance, document
|
|
||||||
# any Ruby DSLs that a customized framework may use. A good example
|
|
||||||
# of this would be the ability to document and generate meta data for
|
|
||||||
# the 'describe' declaration of the RSpec testing framework by simply
|
|
||||||
# adding a handler for such a keyword. Similarly, any Ruby API that
|
|
||||||
# takes advantage of class level declarations could add these to the
|
|
||||||
# documentation in a very explicit format by treating them as first-
|
|
||||||
# class objects in any outputted documentation.
|
|
||||||
#
|
|
||||||
# == Overview of a Typical Handler Scenario
|
|
||||||
#
|
|
||||||
# Generally, a handler class will declare a set of statements which
|
|
||||||
# it will handle using the {handles} class declaration. It will then
|
|
||||||
# implement the {#process} method to do the work. The processing would
|
|
||||||
# usually involve the manipulation of the {#namespace}, {#owner}
|
|
||||||
# {CodeObjects::Base code objects} or the creation of new ones, in
|
|
||||||
# which case they should be registered by {#register}, a method that
|
|
||||||
# sets some basic attributes for the new objects.
|
|
||||||
#
|
|
||||||
# Handlers are usually simple and take up to a page of code to process
|
|
||||||
# and register a new object or add new attributes to the current +namespace+.
|
|
||||||
#
|
|
||||||
# == Setting up a Handler for Use
|
|
||||||
#
|
|
||||||
# A Handler is automatically registered when it is subclassed from the
|
|
||||||
# base class. The only other thing that needs to be done is to specify
|
|
||||||
# which statement the handler will process. This is done with the +handles+
|
|
||||||
# declaration, taking either a {Parser::Ruby::Legacy::RubyToken}, {String} or `Regexp`.
|
|
||||||
# Here is a simple example which processes module statements.
|
|
||||||
#
|
|
||||||
# class MyModuleHandler < YARD::Handlers::Base
|
|
||||||
# handles TkMODULE
|
|
||||||
#
|
|
||||||
# def process
|
|
||||||
# # do something
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# == Processing Handler Data
|
|
||||||
#
|
|
||||||
# The goal of a specific handler is really up to the developer, and as
|
|
||||||
# such there is no real guideline on how to process the data. However,
|
|
||||||
# it is important to know where the data is coming from to be able to use
|
|
||||||
# it.
|
|
||||||
#
|
|
||||||
# === +statement+ Attribute
|
|
||||||
#
|
|
||||||
# The +statement+ attribute pertains to the {Parser::Ruby::Legacy::Statement} object
|
|
||||||
# containing a set of tokens parsed in by the parser. This is the main set
|
|
||||||
# of data to be analyzed and processed. The comments attached to the statement
|
|
||||||
# can be accessed by the {Parser::Ruby::Legacy::Statement#comments} method, but generally
|
|
||||||
# the data to be processed will live in the +tokens+ attribute. This list
|
|
||||||
# can be converted to a +String+ using +#to_s+ to parse the data with
|
|
||||||
# regular expressions (or other text processing mechanisms), if needed.
|
|
||||||
#
|
|
||||||
# === +namespace+ Attribute
|
|
||||||
#
|
|
||||||
# The +namespace+ attribute is a {CodeObjects::NamespaceObject namespace object}
|
|
||||||
# which represents the current namespace that the parser is in. For instance:
|
|
||||||
#
|
|
||||||
# module SomeModule
|
|
||||||
# class MyClass
|
|
||||||
# def mymethod; end
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# If a handler was to parse the 'class MyClass' statement, it would
|
|
||||||
# be necessary to know that it belonged inside the SomeModule module.
|
|
||||||
# This is the value that +namespace+ would return when processing such
|
|
||||||
# a statement. If the class was then entered and another handler was
|
|
||||||
# called on the method, the +namespace+ would be set to the 'MyClass'
|
|
||||||
# code object.
|
|
||||||
#
|
|
||||||
# === +owner+ Attribute
|
|
||||||
#
|
|
||||||
# The +owner+ attribute is similar to the +namespace+ attribute in that
|
|
||||||
# it also follows the scope of the code during parsing. However, a namespace
|
|
||||||
# object is loosely defined as a module or class and YARD has the ability
|
|
||||||
# to parse beyond module and class blocks (inside methods, for instance),
|
|
||||||
# so the +owner+ attribute would not be limited to modules and classes.
|
|
||||||
#
|
|
||||||
# To put this into context, the example from above will be used. If a method
|
|
||||||
# handler was added to the mix and decided to parse inside the method body,
|
|
||||||
# the +owner+ would be set to the method object but the namespace would remain
|
|
||||||
# set to the class. This would allow the developer to process any method
|
|
||||||
# definitions set inside a method (def x; def y; 2 end end) by adding them
|
|
||||||
# to the correct namespace (the class, not the method).
|
|
||||||
#
|
|
||||||
# In summary, the distinction between +namespace+ and +owner+ can be thought
|
|
||||||
# of as the difference between first-class Ruby objects (namespaces) and
|
|
||||||
# second-class Ruby objects (methods).
|
|
||||||
#
|
|
||||||
# === +visibility+ and +scope+ Attributes
|
|
||||||
#
|
|
||||||
# Mainly needed for parsing methods, the +visibility+ and +scope+ attributes
|
|
||||||
# refer to the public/protected/private and class/instance values (respectively)
|
|
||||||
# of the current parsing position.
|
|
||||||
#
|
|
||||||
# == Parsing Blocks in Statements
|
|
||||||
#
|
|
||||||
# In addition to parsing a statement and creating new objects, some
|
|
||||||
# handlers may wish to continue parsing the code inside the statement's
|
|
||||||
# block (if there is one). In this context, a block means the inside
|
|
||||||
# of any statement, be it class definition, module definition, if
|
|
||||||
# statement or classic 'Ruby block'.
|
|
||||||
#
|
|
||||||
# For example, a class statement would be "class MyClass" and the block
|
|
||||||
# would be a list of statements including the method definitions inside
|
|
||||||
# the class. For a class handler, the programmer would execute the
|
|
||||||
# {#parse_block} method to continue parsing code inside the block, with
|
|
||||||
# the +namespace+ now pointing to the class object the handler created.
|
|
||||||
#
|
|
||||||
# YARD has the ability to continue into any block: class, module, method,
|
|
||||||
# even if statements. For this reason, the block parsing method must be
|
|
||||||
# invoked explicitly out of efficiency sake.
|
|
||||||
#
|
|
||||||
# @abstract Subclass this class to provide a handler for YARD to use
|
|
||||||
# during the processing phase.
|
|
||||||
#
|
|
||||||
# @see CodeObjects::Base
|
|
||||||
# @see CodeObjects::NamespaceObject
|
|
||||||
# @see handles
|
|
||||||
# @see #namespace
|
|
||||||
# @see #owner
|
|
||||||
# @see #register
|
|
||||||
# @see #parse_block
|
|
||||||
class Base
|
|
||||||
# For accessing convenience, eg. "MethodObject"
|
|
||||||
# instead of the full qualified namespace
|
|
||||||
include YARD::CodeObjects
|
|
||||||
|
|
||||||
include Parser
|
|
||||||
|
|
||||||
class << self
|
|
||||||
# Clear all registered subclasses. Testing purposes only
|
|
||||||
# @return [void]
|
|
||||||
def clear_subclasses
|
|
||||||
@@subclasses = []
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns all registered handler subclasses.
|
|
||||||
# @return [Array<Base>] a list of handlers
|
|
||||||
def subclasses
|
|
||||||
@@subclasses ||= []
|
|
||||||
end
|
|
||||||
|
|
||||||
def inherited(subclass)
|
|
||||||
@@subclasses ||= []
|
|
||||||
@@subclasses << subclass
|
|
||||||
end
|
|
||||||
|
|
||||||
# Declares the statement type which will be processed
|
|
||||||
# by this handler.
|
|
||||||
#
|
|
||||||
# A match need not be unique to a handler. Multiple
|
|
||||||
# handlers can process the same statement. However,
|
|
||||||
# in this case, care should be taken to make sure that
|
|
||||||
# {#parse_block} would only be executed by one of
|
|
||||||
# the handlers, otherwise the same code will be parsed
|
|
||||||
# multiple times and slow YARD down.
|
|
||||||
#
|
|
||||||
# @param [Parser::Ruby::Legacy::RubyToken, Symbol, String, Regexp] matches
|
|
||||||
# statements that match the declaration will be
|
|
||||||
# processed by this handler. A {String} match is
|
|
||||||
# equivalent to a +/\Astring/+ regular expression
|
|
||||||
# (match from the beginning of the line), and all
|
|
||||||
# token matches match only the first token of the
|
|
||||||
# statement.
|
|
||||||
#
|
|
||||||
def handles(*matches)
|
|
||||||
(@handlers ||= []).concat(matches)
|
|
||||||
end
|
|
||||||
|
|
||||||
# This class is implemented by {Ruby::Base} and {Ruby::Legacy::Base}.
|
|
||||||
# To implement a base handler class for another language, implement
|
|
||||||
# this method to return true if the handler should process the given
|
|
||||||
# statement object. Use {handlers} to enumerate the matchers declared
|
|
||||||
# for the handler class.
|
|
||||||
#
|
|
||||||
# @param statement a statement object or node (depends on language type)
|
|
||||||
# @return [Boolean] whether or not this handler object should process
|
|
||||||
# the given statement
|
|
||||||
def handles?(statement) # rubocop:disable Lint/UnusedMethodArgument
|
|
||||||
raise NotImplementedError, "override #handles? in a subclass"
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Array] a list of matchers for the handler object.
|
|
||||||
# @see handles?
|
|
||||||
def handlers
|
|
||||||
@handlers ||= []
|
|
||||||
end
|
|
||||||
|
|
||||||
# Declares that the handler should only be called when inside a
|
|
||||||
# {CodeObjects::NamespaceObject}, not a method body.
|
|
||||||
#
|
|
||||||
# @return [void]
|
|
||||||
def namespace_only
|
|
||||||
@namespace_only = true
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Boolean] whether the handler should only be processed inside
|
|
||||||
# a namespace.
|
|
||||||
def namespace_only?
|
|
||||||
@namespace_only ||= false
|
|
||||||
end
|
|
||||||
|
|
||||||
# Declares that a handler should only be called when inside a filename
|
|
||||||
# by its basename or a regex match for the full path.
|
|
||||||
#
|
|
||||||
# @param [String, Regexp] filename a matching filename or regex
|
|
||||||
# @return [void]
|
|
||||||
# @since 0.6.2
|
|
||||||
def in_file(filename)
|
|
||||||
(@in_files ||= []) << filename
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Boolean] whether the filename matches the declared file
|
|
||||||
# match for a handler. If no file match is specified, returns true.
|
|
||||||
# @since 0.6.2
|
|
||||||
def matches_file?(filename)
|
|
||||||
@in_files ||= nil # avoid ruby warnings
|
|
||||||
return true unless @in_files
|
|
||||||
@in_files.any? do |in_file|
|
|
||||||
case in_file
|
|
||||||
when String
|
|
||||||
File.basename(filename) == in_file
|
|
||||||
when Regexp
|
|
||||||
filename =~ in_file
|
|
||||||
else
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Generates a +process+ method, equivalent to +def process; ... end+.
|
|
||||||
# Blocks defined with this syntax will be wrapped inside an anonymous
|
|
||||||
# module so that the handler class can be extended with mixins that
|
|
||||||
# override the +process+ method without alias chaining.
|
|
||||||
#
|
|
||||||
# @!macro yard.handlers.process
|
|
||||||
# @!method process
|
|
||||||
# Main processing callback
|
|
||||||
# @return [void]
|
|
||||||
# @see #process
|
|
||||||
# @return [void]
|
|
||||||
# @since 0.5.4
|
|
||||||
def process(&block)
|
|
||||||
mod = Module.new
|
|
||||||
mod.send(:define_method, :process, &block)
|
|
||||||
include mod
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize(source_parser, stmt)
|
|
||||||
@parser = source_parser
|
|
||||||
@statement = stmt
|
|
||||||
end
|
|
||||||
|
|
||||||
# The main handler method called by the parser on a statement
|
|
||||||
# that matches the {handles} declaration.
|
|
||||||
#
|
|
||||||
# Subclasses should override this method to provide the handling
|
|
||||||
# functionality for the class.
|
|
||||||
#
|
|
||||||
# @return [Array<CodeObjects::Base>, CodeObjects::Base, Object]
|
|
||||||
# If this method returns a code object (or a list of them),
|
|
||||||
# they are passed to the +#register+ method which adds basic
|
|
||||||
# attributes. It is not necessary to return any objects and in
|
|
||||||
# some cases you may want to explicitly avoid the returning of
|
|
||||||
# any objects for post-processing by the register method.
|
|
||||||
#
|
|
||||||
# @see handles
|
|
||||||
# @see #register
|
|
||||||
#
|
|
||||||
def process
|
|
||||||
raise NotImplementedError, "#{self} did not implement a #process method for handling."
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parses the semantic "block" contained in the statement node.
|
|
||||||
#
|
|
||||||
# @abstract Subclasses should call {Processor#process parser.process}
|
|
||||||
def parse_block(*)
|
|
||||||
raise NotImplementedError, "#{self} did not implement a #parse_block method for handling"
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Processor] the processor object that manages all global state
|
|
||||||
# during handling.
|
|
||||||
attr_reader :parser
|
|
||||||
|
|
||||||
# @return [Object] the statement object currently being processed. Usually
|
|
||||||
# refers to one semantic language statement, though the strict definition
|
|
||||||
# depends on the parser used.
|
|
||||||
attr_reader :statement
|
|
||||||
|
|
||||||
# (see Processor#owner)
|
|
||||||
attr_accessor :owner
|
|
||||||
|
|
||||||
# (see Processor#namespace)
|
|
||||||
attr_accessor :namespace
|
|
||||||
|
|
||||||
# (see Processor#visibility)
|
|
||||||
attr_accessor :visibility
|
|
||||||
|
|
||||||
# (see Processor#scope)
|
|
||||||
attr_accessor :scope
|
|
||||||
|
|
||||||
# (see Processor#globals)
|
|
||||||
attr_reader :globals
|
|
||||||
|
|
||||||
# (see Processor#extra_state)
|
|
||||||
attr_reader :extra_state
|
|
||||||
|
|
||||||
undef owner, owner=, namespace, namespace=
|
|
||||||
undef visibility, visibility=, scope, scope=
|
|
||||||
undef globals, extra_state
|
|
||||||
|
|
||||||
def owner; parser.owner end
|
|
||||||
def owner=(v) parser.owner = v end
|
|
||||||
def namespace; parser.namespace end
|
|
||||||
def namespace=(v); parser.namespace = v end
|
|
||||||
def visibility; parser.visibility end
|
|
||||||
def visibility=(v); parser.visibility = v end
|
|
||||||
def scope; parser.scope end
|
|
||||||
def scope=(v); parser.scope = v end
|
|
||||||
def globals; parser.globals end
|
|
||||||
def extra_state; parser.extra_state end
|
|
||||||
|
|
||||||
# Aborts a handler by raising {Handlers::HandlerAborted}.
|
|
||||||
# An exception will only be logged in debugging mode for
|
|
||||||
# this kind of handler exit.
|
|
||||||
#
|
|
||||||
# @since 0.8.4
|
|
||||||
def abort!
|
|
||||||
raise Handlers::HandlerAborted
|
|
||||||
end
|
|
||||||
|
|
||||||
# Executes a given block with specific state values for {#owner},
|
|
||||||
# {#namespace} and {#scope}.
|
|
||||||
#
|
|
||||||
# @option opts [CodeObjects::NamespaceObject] :namespace (value of #namespace)
|
|
||||||
# the namespace object that {#namespace} will be equal to for the
|
|
||||||
# duration of the block.
|
|
||||||
# @option opts [Symbol] :scope (:instance)
|
|
||||||
# the scope for the duration of the block.
|
|
||||||
# @option opts [CodeObjects::Base] :owner (value of #owner)
|
|
||||||
# the owner object (method) for the duration of the block
|
|
||||||
# @yield a block to execute with the given state values.
|
|
||||||
def push_state(opts = {})
|
|
||||||
opts = {
|
|
||||||
:namespace => namespace,
|
|
||||||
:scope => :instance,
|
|
||||||
:owner => owner || namespace,
|
|
||||||
:visibility => nil
|
|
||||||
}.update(opts)
|
|
||||||
|
|
||||||
ns = namespace
|
|
||||||
vis = visibility
|
|
||||||
sc = scope
|
|
||||||
oo = owner
|
|
||||||
self.namespace = opts[:namespace]
|
|
||||||
self.visibility = opts[:visibility] || :public
|
|
||||||
self.scope = opts[:scope]
|
|
||||||
self.owner = opts[:owner]
|
|
||||||
|
|
||||||
yield
|
|
||||||
|
|
||||||
self.namespace = ns
|
|
||||||
self.visibility = vis
|
|
||||||
self.scope = sc
|
|
||||||
self.owner = oo
|
|
||||||
end
|
|
||||||
|
|
||||||
# Do some post processing on a list of code objects.
|
|
||||||
# Adds basic attributes to the list of objects like
|
|
||||||
# the filename, line number, {CodeObjects::Base#dynamic},
|
|
||||||
# source code and {CodeObjects::Base#docstring},
|
|
||||||
# but only if they don't exist.
|
|
||||||
#
|
|
||||||
# @param [Array<CodeObjects::Base>] objects
|
|
||||||
# the list of objects to post-process.
|
|
||||||
#
|
|
||||||
# @return [CodeObjects::Base, Array<CodeObjects::Base>]
|
|
||||||
# returns whatever is passed in, for chainability.
|
|
||||||
#
|
|
||||||
def register(*objects)
|
|
||||||
objects.flatten.each do |object|
|
|
||||||
next unless object.is_a?(CodeObjects::Base)
|
|
||||||
register_ensure_loaded(object)
|
|
||||||
yield(object) if block_given?
|
|
||||||
register_file_info(object)
|
|
||||||
register_source(object)
|
|
||||||
register_visibility(object)
|
|
||||||
register_docstring(object)
|
|
||||||
register_group(object)
|
|
||||||
register_dynamic(object)
|
|
||||||
register_module_function(object)
|
|
||||||
end
|
|
||||||
objects.size == 1 ? objects.first : objects
|
|
||||||
end
|
|
||||||
|
|
||||||
# Ensures that the object's namespace is loaded before attaching it
|
|
||||||
# to the namespace.
|
|
||||||
#
|
|
||||||
# @param [CodeObjects::Base] object the object to register
|
|
||||||
# @return [void]
|
|
||||||
# @since 0.8.0
|
|
||||||
def register_ensure_loaded(object)
|
|
||||||
ensure_loaded!(object.namespace)
|
|
||||||
object.namespace.children << object
|
|
||||||
rescue NamespaceMissingError
|
|
||||||
nil # noop
|
|
||||||
end
|
|
||||||
|
|
||||||
# Registers the file/line of the declaration with the object
|
|
||||||
#
|
|
||||||
# @param [CodeObjects::Base] object the object to register
|
|
||||||
# @return [void]
|
|
||||||
# @since 0.8.0
|
|
||||||
def register_file_info(object, file = parser.file, line = statement.line, comments = statement.comments)
|
|
||||||
object.add_file(file, line, comments)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Registers any docstring found for the object and expands macros
|
|
||||||
#
|
|
||||||
# @param [CodeObjects::Base] object the object to register
|
|
||||||
# @return [void]
|
|
||||||
# @since 0.8.0
|
|
||||||
def register_docstring(object, docstring = statement.comments, stmt = statement)
|
|
||||||
docstring = docstring.join("\n") if Array === docstring
|
|
||||||
parser = Docstring.parser
|
|
||||||
parser.parse(docstring || "", object, self)
|
|
||||||
|
|
||||||
if object && docstring
|
|
||||||
object.docstring = parser.to_docstring
|
|
||||||
|
|
||||||
# Add hash_flag/line_range
|
|
||||||
if stmt
|
|
||||||
object.docstring.hash_flag = stmt.comments_hash_flag
|
|
||||||
object.docstring.line_range = stmt.comments_range
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
register_transitive_tags(object)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Registers the object as being inside a specific group
|
|
||||||
#
|
|
||||||
# @param [CodeObjects::Base] object the object to register
|
|
||||||
# @return [void]
|
|
||||||
# @since 0.8.0
|
|
||||||
def register_group(object, group = extra_state.group)
|
|
||||||
if group
|
|
||||||
unless object.namespace.is_a?(Proxy)
|
|
||||||
object.namespace.groups |= [group]
|
|
||||||
end
|
|
||||||
object.group = group
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Registers any transitive tags from the namespace on the object
|
|
||||||
#
|
|
||||||
# @param [CodeObjects::Base, nil] object the object to register
|
|
||||||
# @return [void]
|
|
||||||
# @since 0.8.0
|
|
||||||
def register_transitive_tags(object)
|
|
||||||
return unless object && !object.namespace.is_a?(Proxy)
|
|
||||||
Tags::Library.transitive_tags.each do |tag|
|
|
||||||
next unless object.namespace.has_tag?(tag)
|
|
||||||
next if object.has_tag?(tag)
|
|
||||||
object.add_tag(*object.namespace.tags(tag))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @param [CodeObjects::Base] object the object to register
|
|
||||||
# @return [void]
|
|
||||||
# @since 0.8.0
|
|
||||||
def register_source(object, source = statement, type = parser.parser_type)
|
|
||||||
return unless object.is_a?(MethodObject)
|
|
||||||
object.source ||= source
|
|
||||||
object.source_type = type
|
|
||||||
end
|
|
||||||
|
|
||||||
# Registers visibility on a method object. If the object does not
|
|
||||||
# respond to setting visibility, nothing is done.
|
|
||||||
#
|
|
||||||
# @param [#visibility=] object the object to register
|
|
||||||
# @param [Symbol] visibility the visibility to set on the object
|
|
||||||
# @since 0.8.0
|
|
||||||
def register_visibility(object, visibility = self.visibility)
|
|
||||||
return unless object.respond_to?(:visibility=)
|
|
||||||
return if object.is_a?(NamespaceObject)
|
|
||||||
object.visibility = visibility
|
|
||||||
end
|
|
||||||
|
|
||||||
# Registers the same method information on the module function, if
|
|
||||||
# the object was defined as a module function.
|
|
||||||
#
|
|
||||||
# @param [CodeObjects::Base] object the possible module function object
|
|
||||||
# to copy data for
|
|
||||||
# @since 0.8.0
|
|
||||||
def register_module_function(object)
|
|
||||||
return unless object.is_a?(MethodObject)
|
|
||||||
return unless object.module_function?
|
|
||||||
modobj = MethodObject.new(object.namespace, object.name)
|
|
||||||
object.copy_to(modobj)
|
|
||||||
modobj.visibility = :private # rubocop:disable Lint/UselessSetterCall
|
|
||||||
end
|
|
||||||
|
|
||||||
# Registers the object as dynamic if the object is defined inside
|
|
||||||
# a method or block (owner != namespace)
|
|
||||||
#
|
|
||||||
# @param [CodeObjects::Base] object the object to register
|
|
||||||
# @return [void]
|
|
||||||
# @since 0.8.0
|
|
||||||
def register_dynamic(object)
|
|
||||||
object.dynamic = true if owner != namespace
|
|
||||||
end
|
|
||||||
|
|
||||||
# Ensures that a specific +object+ has been parsed and loaded into the
|
|
||||||
# registry. This is necessary when adding data to a namespace, for instance,
|
|
||||||
# since the namespace may not have been processed yet (it can be located
|
|
||||||
# in a file that has not been handled).
|
|
||||||
#
|
|
||||||
# Calling this method defers the handler until all other files have been
|
|
||||||
# processed. If the object gets resolved, the rest of the handler continues,
|
|
||||||
# otherwise an exception is raised.
|
|
||||||
#
|
|
||||||
# @example Adding a mixin to the String class programmatically
|
|
||||||
# ensure_loaded! P('String')
|
|
||||||
# # "String" is now guaranteed to be loaded
|
|
||||||
# P('String').mixins << P('MyMixin')
|
|
||||||
#
|
|
||||||
# @param [Proxy, CodeObjects::Base] object the object to resolve.
|
|
||||||
# @param [Integer] max_retries the number of times to defer the handler
|
|
||||||
# before raising a +NamespaceMissingError+.
|
|
||||||
# @raise [NamespaceMissingError] if the object is not resolved within
|
|
||||||
# +max_retries+ attempts, this exception is raised and the handler
|
|
||||||
# finishes processing.
|
|
||||||
def ensure_loaded!(object, max_retries = 1)
|
|
||||||
return if object.root?
|
|
||||||
return object unless object.is_a?(Proxy)
|
|
||||||
|
|
||||||
retries = 0
|
|
||||||
while object.is_a?(Proxy)
|
|
||||||
raise NamespaceMissingError, object if retries > max_retries
|
|
||||||
log.debug "Missing object #{object} in file `#{parser.file}', moving it to the back of the line."
|
|
||||||
parser.parse_remaining_files
|
|
||||||
retries += 1
|
|
||||||
end
|
|
||||||
object
|
|
||||||
end
|
|
||||||
|
|
||||||
# @group Macro Support
|
|
||||||
|
|
||||||
# @abstract Implement this method to return the parameters in a method call
|
|
||||||
# statement. It should return an empty list if the statement is not a
|
|
||||||
# method call.
|
|
||||||
# @return [Array<String>] a list of argument names
|
|
||||||
def call_params
|
|
||||||
raise NotImplementedError
|
|
||||||
end
|
|
||||||
|
|
||||||
# @abstract Implement this method to return the method being called in
|
|
||||||
# a method call. It should return nil if the statement is not a method
|
|
||||||
# call.
|
|
||||||
# @return [String] the method name being called
|
|
||||||
# @return [nil] if the statement is not a method call
|
|
||||||
def caller_method
|
|
||||||
raise NotImplementedError
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
class YARD::Handlers::C::AliasHandler < YARD::Handlers::C::Base
|
|
||||||
MATCH = /rb_define_alias
|
|
||||||
\s*\(\s*([\w\.]+),
|
|
||||||
\s*"([^"]+)",
|
|
||||||
\s*"([^"]+)"\s*\)/xm
|
|
||||||
handles MATCH
|
|
||||||
statement_class BodyStatement
|
|
||||||
|
|
||||||
process do
|
|
||||||
statement.source.scan(MATCH) do |var_name, new_name, old_name|
|
|
||||||
var_name = "rb_cObject" if var_name == "rb_mKernel"
|
|
||||||
handle_alias(var_name, new_name, old_name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
class YARD::Handlers::C::AttributeHandler < YARD::Handlers::C::Base
|
|
||||||
MATCH = /rb_define_attr\s*\(\s*([\w\.]+),\s*"([^"]+)",\s*(0|1)\s*,\s*(0|1)\s*\)/
|
|
||||||
handles MATCH
|
|
||||||
|
|
||||||
process do
|
|
||||||
return if ToplevelStatement == statement
|
|
||||||
return if Comment === statement && statement.type != :multi
|
|
||||||
statement.source.scan(MATCH) do |var_name, name, read, write|
|
|
||||||
handle_attribute(var_name, name, read, write)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,164 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module Handlers
|
|
||||||
module C
|
|
||||||
class Base < Handlers::Base
|
|
||||||
include YARD::Parser::C
|
|
||||||
include HandlerMethods
|
|
||||||
|
|
||||||
# @return [Boolean] whether the handler handles this statement
|
|
||||||
def self.handles?(statement, processor)
|
|
||||||
processor.globals.cruby_processed_files ||= {}
|
|
||||||
processor.globals.cruby_processed_files[processor.file] = true
|
|
||||||
|
|
||||||
src = statement.respond_to?(:declaration) ?
|
|
||||||
statement.declaration : statement.source
|
|
||||||
|
|
||||||
handlers.any? do |a_handler|
|
|
||||||
statement_class >= statement.class &&
|
|
||||||
case a_handler
|
|
||||||
when String
|
|
||||||
src == a_handler
|
|
||||||
when Regexp
|
|
||||||
src =~ a_handler
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.statement_class(type = nil)
|
|
||||||
if type
|
|
||||||
@statement_class = type
|
|
||||||
else
|
|
||||||
(defined?(@statement_class) && @statement_class) || Statement
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @group Registering objects
|
|
||||||
|
|
||||||
def register_docstring(object, docstring = nil, stmt = nil)
|
|
||||||
super(object, docstring, stmt) if docstring
|
|
||||||
end
|
|
||||||
|
|
||||||
def register_file_info(object, file = nil, line = nil, comments = nil)
|
|
||||||
super(object, file, line, comments) if file
|
|
||||||
end
|
|
||||||
|
|
||||||
def register_source(object, source = nil, type = nil)
|
|
||||||
super(object, source, type) if source
|
|
||||||
end
|
|
||||||
|
|
||||||
def register_visibility(object, visibility = nil)
|
|
||||||
super(object, visibility) if visibility
|
|
||||||
end
|
|
||||||
|
|
||||||
# @group Looking up Symbol and Var Values
|
|
||||||
|
|
||||||
def symbols
|
|
||||||
globals.cruby_symbols ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def override_comments
|
|
||||||
globals.cruby_override_comments ||= []
|
|
||||||
end
|
|
||||||
|
|
||||||
def namespace_for_variable(var)
|
|
||||||
return namespaces[var] if namespaces[var]
|
|
||||||
|
|
||||||
# The global variables for Ruby's core error classes does not
|
|
||||||
# represent their Ruby name. So we need to look up these names.
|
|
||||||
name = ERROR_CLASS_NAMES[var]
|
|
||||||
return P(name) if name
|
|
||||||
|
|
||||||
# Otherwise the name is inferred from the C variable name.
|
|
||||||
var = remove_var_prefix(var)
|
|
||||||
var.empty? ? nil : P(var)
|
|
||||||
end
|
|
||||||
|
|
||||||
def ensure_variable_defined!(var, max_retries = 1)
|
|
||||||
retries = 0
|
|
||||||
object = nil
|
|
||||||
|
|
||||||
loop do
|
|
||||||
object = namespace_for_variable(var)
|
|
||||||
break unless object.is_a?(Proxy)
|
|
||||||
|
|
||||||
raise NamespaceMissingError, object if retries > max_retries
|
|
||||||
log.debug "Missing namespace variable #{var} in file `#{parser.file}', moving it to the back of the line."
|
|
||||||
parser.parse_remaining_files
|
|
||||||
retries += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
object
|
|
||||||
end
|
|
||||||
|
|
||||||
def namespaces
|
|
||||||
globals.cruby_namespaces ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def processed_files
|
|
||||||
globals.cruby_processed_files ||= {}
|
|
||||||
end
|
|
||||||
|
|
||||||
# @group Parsing an Inner Block
|
|
||||||
|
|
||||||
def parse_block(opts = {})
|
|
||||||
return if !statement.block || statement.block.empty?
|
|
||||||
push_state(opts) do
|
|
||||||
parser.process(statement.block)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @group Processing other files
|
|
||||||
|
|
||||||
def process_file(file, object)
|
|
||||||
file = File.cleanpath(file)
|
|
||||||
return if processed_files[file]
|
|
||||||
processed_files[file] = file
|
|
||||||
begin
|
|
||||||
log.debug "Processing embedded call to C source #{file}..."
|
|
||||||
globals.ordered_parser.files.delete(file) if globals.ordered_parser
|
|
||||||
parser.process(Parser::C::CParser.new(File.read(file), file).parse)
|
|
||||||
rescue Errno::ENOENT
|
|
||||||
log.warn "Missing source file `#{file}' when parsing #{object}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @endgroup
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Generated by update_error_map.rb (Copy+past results)
|
|
||||||
ERROR_CLASS_NAMES = {
|
|
||||||
'rb_eArgError' => 'ArgumentError',
|
|
||||||
'rb_eEncodingError' => 'EncodingError',
|
|
||||||
'rb_eException' => 'Exception',
|
|
||||||
'rb_eFatal' => 'fatal',
|
|
||||||
'rb_eFrozenError' => 'FrozenError',
|
|
||||||
'rb_eIndexError' => 'IndexError',
|
|
||||||
'rb_eInterrupt' => 'Interrupt',
|
|
||||||
'rb_eKeyError' => 'KeyError',
|
|
||||||
'rb_eLoadError' => 'LoadError',
|
|
||||||
'rb_eNameError' => 'NameError',
|
|
||||||
'rb_eNoMatchingPatternError' => 'NoMatchingPatternError',
|
|
||||||
'rb_eNoMemError' => 'NoMemoryError',
|
|
||||||
'rb_eNoMethodError' => 'NoMethodError',
|
|
||||||
'rb_eNotImpError' => 'NotImplementedError',
|
|
||||||
'rb_eRangeError' => 'RangeError',
|
|
||||||
'rb_eRuntimeError' => 'RuntimeError',
|
|
||||||
'rb_eScriptError' => 'ScriptError',
|
|
||||||
'rb_eSecurityError' => 'SecurityError',
|
|
||||||
'rb_eSignal' => 'SignalException',
|
|
||||||
'rb_eStandardError' => 'StandardError',
|
|
||||||
'rb_eSyntaxError' => 'SyntaxError',
|
|
||||||
'rb_eSystemCallError' => 'SystemCallError',
|
|
||||||
'rb_eSystemExit' => 'SystemExit',
|
|
||||||
'rb_eTypeError' => 'TypeError',
|
|
||||||
}
|
|
||||||
|
|
||||||
def remove_var_prefix(var)
|
|
||||||
var.gsub(/^rb_[mc]|^[a-z_]+/, '')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
class YARD::Handlers::C::ClassHandler < YARD::Handlers::C::Base
|
|
||||||
MATCH1 = /([\w\.]+)\s* = \s*(?:rb_define_class|boot_defclass)\s*
|
|
||||||
\(
|
|
||||||
\s*"([\w:]+)",
|
|
||||||
\s*(\w+|0)\s*
|
|
||||||
\)/mx
|
|
||||||
|
|
||||||
MATCH2 = /([\w\.]+)\s* = \s*rb_define_class_under\s*
|
|
||||||
\(
|
|
||||||
\s*(\w+),
|
|
||||||
\s*"(\w+)"(?:,
|
|
||||||
\s*([\w\*\s\(\)\.\->]+)\s*)? # for SWIG
|
|
||||||
\s*\)/mx
|
|
||||||
handles MATCH1
|
|
||||||
handles MATCH2
|
|
||||||
statement_class BodyStatement
|
|
||||||
|
|
||||||
process do
|
|
||||||
statement.source.scan(MATCH1) do |var_name, class_name, parent|
|
|
||||||
handle_class(var_name, class_name, parent)
|
|
||||||
end
|
|
||||||
statement.source.scan(MATCH2) do |var_name, in_module, class_name, parent|
|
|
||||||
handle_class(var_name, class_name, parent.strip, in_module)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
class YARD::Handlers::C::ConstantHandler < YARD::Handlers::C::Base
|
|
||||||
MATCH = /\brb_define_((?:readonly_)?variable|(?:global_)?const)
|
|
||||||
\s*\((?:\s*(\w+),)?\s*"(\w+)",\s*(.*?)\s*\)\s*;/xm
|
|
||||||
handles MATCH
|
|
||||||
statement_class BodyStatement
|
|
||||||
|
|
||||||
process do
|
|
||||||
statement.source.scan(MATCH) do |type, var_name, const_name, value|
|
|
||||||
handle_constants(type, var_name, const_name, value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,212 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module Handlers
|
|
||||||
module C
|
|
||||||
module HandlerMethods
|
|
||||||
include Parser::C
|
|
||||||
include CodeObjects
|
|
||||||
include Common::MethodHandler
|
|
||||||
|
|
||||||
def handle_class(var_name, class_name, parent, in_module = nil)
|
|
||||||
parent = nil if parent == "0"
|
|
||||||
namespace = in_module ? ensure_variable_defined!(in_module) : Registry.root
|
|
||||||
if namespace.nil?
|
|
||||||
raise Parser::UndocumentableError,
|
|
||||||
"class #{class_name}. Cannot find definition for parent namespace."
|
|
||||||
end
|
|
||||||
|
|
||||||
register ClassObject.new(namespace, class_name) do |obj|
|
|
||||||
if parent
|
|
||||||
parent_class = namespace_for_variable(parent)
|
|
||||||
if parent_class.is_a?(Proxy)
|
|
||||||
obj.superclass = "::#{parent_class.path}"
|
|
||||||
obj.superclass.type = :class
|
|
||||||
else
|
|
||||||
obj.superclass = parent_class
|
|
||||||
end
|
|
||||||
end
|
|
||||||
namespaces[var_name] = obj
|
|
||||||
register_file_info(obj, statement.file, statement.line)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_module(var_name, module_name, in_module = nil)
|
|
||||||
namespace = in_module ? ensure_variable_defined!(in_module) : Registry.root
|
|
||||||
if namespace.nil?
|
|
||||||
raise Parser::UndocumentableError,
|
|
||||||
"module #{module_name}. Cannot find definition for parent namespace."
|
|
||||||
end
|
|
||||||
|
|
||||||
register ModuleObject.new(namespace, module_name) do |obj|
|
|
||||||
namespaces[var_name] = obj
|
|
||||||
register_file_info(obj, statement.file, statement.line)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_method(scope, var_name, name, func_name, _source_file = nil)
|
|
||||||
visibility = :public
|
|
||||||
case scope
|
|
||||||
when "singleton_method"; scope = :class
|
|
||||||
when "module_function"; scope = :module
|
|
||||||
when "private_method"; scope = :instance; visibility = :private
|
|
||||||
else; scope = :instance
|
|
||||||
end
|
|
||||||
|
|
||||||
namespace = namespace_for_variable(var_name)
|
|
||||||
|
|
||||||
# Is this method being defined on a core Ruby class or module?
|
|
||||||
if namespace.is_a?(Proxy)
|
|
||||||
if var_name =~ /^rb_c(\w+)/ && YARD::CodeObjects::BUILTIN_CLASSES.include?($1)
|
|
||||||
namespace = namespaces[var_name] = YARD::CodeObjects::ClassObject.new(:root, $1)
|
|
||||||
elsif var_name =~ /^rb_m(\w+)/ && YARD::CodeObjects::BUILTIN_MODULES.include?($1)
|
|
||||||
namespace = namespaces[var_name] = YARD::CodeObjects::ModuleObject.new(:root, $1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return if namespace.nil? # XXX: raise UndocumentableError might be too noisy.
|
|
||||||
register MethodObject.new(namespace, name, scope) do |obj|
|
|
||||||
register_visibility(obj, visibility)
|
|
||||||
find_method_body(obj, func_name)
|
|
||||||
obj.explicit = true
|
|
||||||
add_predicate_return_tag(obj) if name =~ /\?$/
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_attribute(var_name, name, read, write)
|
|
||||||
values = {:read => read.to_i, :write => write.to_i}
|
|
||||||
{:read => name, :write => "#{name}="}.each do |type, meth_name|
|
|
||||||
next unless values[type] > 0
|
|
||||||
obj = handle_method(:instance, var_name, meth_name, nil)
|
|
||||||
register_file_info(obj, statement.file, statement.line)
|
|
||||||
obj.namespace.attributes[:instance][name] ||= SymbolHash[:read => nil, :write => nil]
|
|
||||||
obj.namespace.attributes[:instance][name][type] = obj
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_alias(var_name, new_name, old_name)
|
|
||||||
namespace = namespace_for_variable(var_name)
|
|
||||||
return if namespace.nil?
|
|
||||||
new_meth = new_name.to_sym
|
|
||||||
old_meth = old_name.to_sym
|
|
||||||
old_obj = namespace.child(:name => old_meth, :scope => :instance)
|
|
||||||
new_obj = register MethodObject.new(namespace, new_meth, :instance) do |o|
|
|
||||||
register_visibility(o, visibility)
|
|
||||||
register_file_info(o, statement.file, statement.line)
|
|
||||||
end
|
|
||||||
|
|
||||||
if old_obj
|
|
||||||
new_obj.signature = old_obj.signature
|
|
||||||
new_obj.source = old_obj.source
|
|
||||||
new_obj.docstring = old_obj.docstring
|
|
||||||
new_obj.docstring.object = new_obj
|
|
||||||
else
|
|
||||||
new_obj.signature = "def #{new_meth}" # this is all we know.
|
|
||||||
end
|
|
||||||
|
|
||||||
namespace.aliases[new_obj] = old_meth
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_constants(type, var_name, const_name, value)
|
|
||||||
return unless type =~ /^const$|^global_const$/
|
|
||||||
namespace = type == 'global_const' ?
|
|
||||||
:root : namespace_for_variable(var_name)
|
|
||||||
register ConstantObject.new(namespace, const_name) do |obj|
|
|
||||||
obj.source_type = :c
|
|
||||||
obj.value = value
|
|
||||||
register_file_info(obj, statement.file, statement.line)
|
|
||||||
find_constant_docstring(obj)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def find_constant_docstring(object)
|
|
||||||
comment = nil
|
|
||||||
|
|
||||||
# look inside overrides for declaration value
|
|
||||||
override_comments.each do |name, override_comment|
|
|
||||||
next unless override_comment.file == statement.file
|
|
||||||
just_const_name = name.gsub(/\A.+::/, '')
|
|
||||||
if object.path == name || object.name.to_s == just_const_name
|
|
||||||
comment = override_comment.source
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# use any comments on this statement as a last resort
|
|
||||||
if comment.nil? && statement.comments && statement.comments.source =~ /\S/
|
|
||||||
comment = statement.comments.source
|
|
||||||
stmt = statement.comments
|
|
||||||
end
|
|
||||||
|
|
||||||
# In the case of rb_define_const, the definition and comment are in
|
|
||||||
# "/* definition: comment */" form. The literal ':' and '\' characters
|
|
||||||
# can be escaped with a backslash.
|
|
||||||
if comment
|
|
||||||
comment.scan(/\A\s*(.*?[^\s\\]):\s*(.+)/m) do |new_value, new_comment|
|
|
||||||
object.value = new_value.gsub(/\\:/, ':')
|
|
||||||
comment = new_comment
|
|
||||||
end
|
|
||||||
register_docstring(object, comment, stmt)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_method_body(object, symbol)
|
|
||||||
file = statement.file
|
|
||||||
in_file = false
|
|
||||||
if statement.comments && statement.comments.source =~ /\A\s*in (\S+)\Z/
|
|
||||||
file = $1
|
|
||||||
in_file = true
|
|
||||||
process_file(file, object)
|
|
||||||
end
|
|
||||||
|
|
||||||
src_stmt = symbols[symbol]
|
|
||||||
if src_stmt
|
|
||||||
register_file_info(object, src_stmt.file, src_stmt.line, true)
|
|
||||||
register_source(object, src_stmt)
|
|
||||||
record_parameters(object, symbol, src_stmt)
|
|
||||||
unless src_stmt.comments.nil? || src_stmt.comments.source.empty?
|
|
||||||
register_docstring(object, src_stmt.comments.source, src_stmt)
|
|
||||||
return # found docstring
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# found source (possibly) but no docstring
|
|
||||||
# so look in overrides
|
|
||||||
return if override_comments.any? do |name, override_comment|
|
|
||||||
next unless override_comment.file == file
|
|
||||||
name = name.gsub(/::([^:\.#]+?)\Z/, '.\1')
|
|
||||||
|
|
||||||
# explicit namespace in override comment
|
|
||||||
path = (name =~ /\.|#/ ? object.path : object.name.to_s)
|
|
||||||
if path == name || path == name.sub(/new$/, 'initialize') || path == name.sub('.', '#')
|
|
||||||
register_docstring(object, override_comment.source, override_comment)
|
|
||||||
true
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# use any comments on this statement as a last resort
|
|
||||||
if !in_file && statement.comments && statement.comments.source =~ /\S/
|
|
||||||
register_docstring(object, statement.comments.source, statement)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def record_parameters(object, symbol, src)
|
|
||||||
# use regex to extract comma-delimited list of parameters from cfunc definition
|
|
||||||
if src.source =~ /VALUE\s+#{symbol}\(([^)]*)\)\s*\{/m
|
|
||||||
params = $~[1].split(/\s*,\s*/) # rubocop:disable Style/SpecialGlobalVars
|
|
||||||
# cfunc for a "varargs" method has params "int argc, VALUE *argv"
|
|
||||||
if params[0] =~ /int\s+argc/ && params[1] =~ /VALUE\s*\*\s*argv/
|
|
||||||
object.parameters = [['*args', nil]]
|
|
||||||
else
|
|
||||||
# the first cfunc argument is the 'self' argument, we don't need that
|
|
||||||
object.parameters = params.drop(1).map {|s| [s[/VALUE\s+(\S+)/, 1], nil] }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Handles the Init_Libname() method
|
|
||||||
class YARD::Handlers::C::InitHandler < YARD::Handlers::C::Base
|
|
||||||
MATCH = /\A\s*(?:\S+\s+)*void\s+(?:[Ii]nit_)?(\w+)\s*/
|
|
||||||
handles MATCH
|
|
||||||
statement_class ToplevelStatement
|
|
||||||
|
|
||||||
process do
|
|
||||||
parse_block
|
|
||||||
decl = statement.declaration[MATCH, 1]
|
|
||||||
if decl
|
|
||||||
ns = namespace_for_variable(decl)
|
|
||||||
if ns.is_a?(YARD::CodeObjects::NamespaceObject) && ns.docstring.blank?
|
|
||||||
if statement.comments
|
|
||||||
register_docstring(ns, statement.comments.source, statement)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
class YARD::Handlers::C::MethodHandler < YARD::Handlers::C::Base
|
|
||||||
MATCH1 = /rb_define_
|
|
||||||
(
|
|
||||||
singleton_method |
|
|
||||||
method |
|
|
||||||
module_function |
|
|
||||||
private_method
|
|
||||||
)
|
|
||||||
\s*\(\s*([\w\.]+)\s*,
|
|
||||||
\s*"([^"]+)"\s*,
|
|
||||||
\s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\(|\(\w+\))?(\w+)\)?\s*,
|
|
||||||
\s*(-?\w+)\s*\)/xm
|
|
||||||
MATCH2 = /rb_define_global_function\s*\(
|
|
||||||
\s*"([^"]+)",
|
|
||||||
\s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\(|\(\w+\))?(\w+)\)?,
|
|
||||||
\s*(-?\w+)\s*\)/xm
|
|
||||||
MATCH3 = /define_filetest_function\s*\(
|
|
||||||
\s*"([^"]+)",
|
|
||||||
\s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\(|\(\w+\))?(\w+)\)?,
|
|
||||||
\s*(-?\w+)\s*\)/xm
|
|
||||||
handles MATCH1
|
|
||||||
handles MATCH2
|
|
||||||
handles MATCH3
|
|
||||||
statement_class BodyStatement
|
|
||||||
|
|
||||||
process do
|
|
||||||
statement.source.scan(MATCH1) do |type, var_name, name, func_name, _param_count|
|
|
||||||
break if var_name == "ruby_top_self"
|
|
||||||
break if var_name == "nstr"
|
|
||||||
break if var_name == "envtbl"
|
|
||||||
|
|
||||||
var_name = "rb_cObject" if var_name == "rb_mKernel"
|
|
||||||
handle_method(type, var_name, name, func_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
statement.source.scan(MATCH2) do |name, func_name, _param_count|
|
|
||||||
handle_method("method", "rb_mKernel", name, func_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
statement.source.scan(MATCH3) do |name, func_name, _param_count|
|
|
||||||
handle_method("singleton_method", "rb_cFile", name, func_name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
class YARD::Handlers::C::MixinHandler < YARD::Handlers::C::Base
|
|
||||||
MATCH = /rb_include_module\s*\(\s*(\w+?),\s*(\w+?)\s*\)/
|
|
||||||
handles MATCH
|
|
||||||
statement_class BodyStatement
|
|
||||||
|
|
||||||
process do
|
|
||||||
statement.source.scan(MATCH) do |klass_var, mixin_var|
|
|
||||||
namespace = namespace_for_variable(klass_var)
|
|
||||||
ensure_loaded!(namespace)
|
|
||||||
|
|
||||||
var = namespace_for_variable(mixin_var)
|
|
||||||
if var
|
|
||||||
namespace.mixins(:instance) << var
|
|
||||||
else
|
|
||||||
raise YARD::Parser::UndocumentableError,
|
|
||||||
"CRuby mixin for unrecognized variable '#{mixin_var}'"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
class YARD::Handlers::C::ModuleHandler < YARD::Handlers::C::Base
|
|
||||||
MATCH1 = /([\w\.]+)\s* = \s*rb_define_module\s*\(\s*"([\w:]+)"\s*\)/mx
|
|
||||||
MATCH2 = /([\w\.]+)\s* = \s*rb_define_module_under\s*\(\s*(\w+),\s*"(\w+)"\s*\)/mx
|
|
||||||
handles MATCH1
|
|
||||||
handles MATCH2
|
|
||||||
statement_class BodyStatement
|
|
||||||
|
|
||||||
process do
|
|
||||||
statement.source.scan(MATCH1) do |var_name, module_name|
|
|
||||||
handle_module(var_name, module_name)
|
|
||||||
end
|
|
||||||
statement.source.scan(MATCH2) do |var_name, in_module, module_name|
|
|
||||||
handle_module(var_name, module_name, in_module)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Parses comments
|
|
||||||
class YARD::Handlers::C::OverrideCommentHandler < YARD::Handlers::C::Base
|
|
||||||
handles(/./)
|
|
||||||
statement_class Comment
|
|
||||||
|
|
||||||
process do
|
|
||||||
return if statement.overrides.empty?
|
|
||||||
statement.overrides.each do |type, name|
|
|
||||||
override_comments << [name, statement]
|
|
||||||
obj = nil
|
|
||||||
case type
|
|
||||||
when :class
|
|
||||||
name, superclass = *name.split(/\s*<\s*/)
|
|
||||||
obj = YARD::CodeObjects::ClassObject.new(:root, name)
|
|
||||||
obj.superclass = "::#{superclass}" if superclass
|
|
||||||
when :module
|
|
||||||
obj = YARD::CodeObjects::ModuleObject.new(:root, name)
|
|
||||||
end
|
|
||||||
register(obj)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def register_docstring(object, docstring = statement.source, stmt = statement)
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
def register_file_info(object, file = parser.file, line = statement.line, comments = statement.comments)
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
class YARD::Handlers::C::PathHandler < YARD::Handlers::C::Base
|
|
||||||
MATCH = /([\w\.]+)\s* = \s*rb_path2class\s*\(\s*"([\w:]+)"\)/mx
|
|
||||||
handles MATCH
|
|
||||||
|
|
||||||
process do
|
|
||||||
statement.source.scan(MATCH) do |var_name, path|
|
|
||||||
namespaces[var_name] = P(path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
class YARD::Handlers::C::StructHandler < YARD::Handlers::C::Base
|
|
||||||
MATCH = /([\w\.]+)\s*=\s*(?:rb_struct_define_without_accessor)\s*
|
|
||||||
\(\s*"([\w:]+)"\s*,\s*(\w+)\s*/mx
|
|
||||||
handles MATCH
|
|
||||||
statement_class BodyStatement
|
|
||||||
|
|
||||||
process do
|
|
||||||
statement.source.scan(MATCH) do |var_name, class_name, parent|
|
|
||||||
handle_class(var_name, class_name, parent)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Keeps track of function bodies for symbol lookup during Ruby method declarations
|
|
||||||
class YARD::Handlers::C::SymbolHandler < YARD::Handlers::C::Base
|
|
||||||
MATCH = /\A\s*(?:(?:\w+)\s+)?(?:intern\s+)?VALUE\s+(\w+)\s*\(/
|
|
||||||
handles MATCH
|
|
||||||
statement_class ToplevelStatement
|
|
||||||
process { symbols[statement.source[MATCH, 1]] = statement }
|
|
||||||
end
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module YARD::Handlers
|
|
||||||
module Common
|
|
||||||
# Shared functionality between Ruby and C method handlers.
|
|
||||||
module MethodHandler
|
|
||||||
# @param [MethodObject] obj
|
|
||||||
def add_predicate_return_tag(obj)
|
|
||||||
if obj.tag(:return) && (obj.tag(:return).types || []).empty?
|
|
||||||
obj.tag(:return).types = ['Boolean']
|
|
||||||
elsif obj.tag(:return).nil?
|
|
||||||
unless obj.tags(:overload).any? {|overload| overload.tag(:return) }
|
|
||||||
obj.add_tag(YARD::Tags::Tag.new(:return, "", "Boolean"))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,200 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
require 'ostruct'
|
|
||||||
|
|
||||||
module YARD
|
|
||||||
module Handlers
|
|
||||||
# Iterates over all statements in a file and delegates them to the
|
|
||||||
# {Handlers::Base} objects that are registered to handle the statement.
|
|
||||||
#
|
|
||||||
# This class is passed to each handler and keeps overall processing state.
|
|
||||||
# For example, if the {#visibility} is set in a handler, all following
|
|
||||||
# statements will have access to this state. This allows "public",
|
|
||||||
# "protected" and "private" statements to be handled in classes and modules.
|
|
||||||
# In addition, the {#namespace} can be set during parsing to control
|
|
||||||
# where objects are being created from. You can also access extra stateful
|
|
||||||
# properties that any handler can set during the duration of the post
|
|
||||||
# processing of a file from {#extra_state}. If you need to access state
|
|
||||||
# across different files, look at {#globals}.
|
|
||||||
#
|
|
||||||
# @see Handlers::Base
|
|
||||||
class Processor
|
|
||||||
class << self
|
|
||||||
# Registers a new namespace for handlers of the given type.
|
|
||||||
# @since 0.6.0
|
|
||||||
def register_handler_namespace(type, ns)
|
|
||||||
namespace_for_handler[type] = ns
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Hash] a list of registered parser type extensions
|
|
||||||
# @private
|
|
||||||
# @since 0.6.0
|
|
||||||
attr_reader :namespace_for_handler
|
|
||||||
undef namespace_for_handler
|
|
||||||
def namespace_for_handler; @@parser_type_extensions ||= {} end
|
|
||||||
end
|
|
||||||
|
|
||||||
register_handler_namespace :ruby, Ruby
|
|
||||||
register_handler_namespace :ruby18, Ruby::Legacy
|
|
||||||
register_handler_namespace :c, C
|
|
||||||
|
|
||||||
# @return [String] the filename
|
|
||||||
attr_accessor :file
|
|
||||||
|
|
||||||
# @return [CodeObjects::NamespaceObject] the current namespace
|
|
||||||
attr_accessor :namespace
|
|
||||||
|
|
||||||
# @return [Symbol] the current visibility (public, private, protected)
|
|
||||||
attr_accessor :visibility
|
|
||||||
|
|
||||||
# @return [Symbol] the current scope (class, instance)
|
|
||||||
attr_accessor :scope
|
|
||||||
|
|
||||||
# @return [CodeObjects::Base, nil] unlike the namespace, the owner
|
|
||||||
# is a non-namespace object that should be stored between statements.
|
|
||||||
# For instance, when parsing a method body, the {CodeObjects::MethodObject}
|
|
||||||
# is set as the owner, in case any extra method information is processed.
|
|
||||||
attr_accessor :owner
|
|
||||||
|
|
||||||
# @return [Symbol] the parser type (:ruby, :ruby18, :c)
|
|
||||||
attr_accessor :parser_type
|
|
||||||
|
|
||||||
# Handlers can share state for the entire post processing stage through
|
|
||||||
# this attribute. Note that post processing stage spans multiple files.
|
|
||||||
# To share state only within a single file, use {#extra_state}
|
|
||||||
#
|
|
||||||
# @example Sharing state among two handlers
|
|
||||||
# class Handler1 < YARD::Handlers::Ruby::Base
|
|
||||||
# handles :class
|
|
||||||
# process { globals.foo = :bar }
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# class Handler2 < YARD::Handlers::Ruby::Base
|
|
||||||
# handles :method
|
|
||||||
# process { puts globals.foo }
|
|
||||||
# end
|
|
||||||
# @return [OpenStruct] global shared state for post-processing stage
|
|
||||||
# @see #extra_state
|
|
||||||
attr_accessor :globals
|
|
||||||
|
|
||||||
# Share state across different handlers inside of a file.
|
|
||||||
# This attribute is similar to {#visibility}, {#scope}, {#namespace}
|
|
||||||
# and {#owner}, in that they all maintain state across all handlers
|
|
||||||
# for the entire source file. Use this attribute to store any data
|
|
||||||
# your handler might need to save during the parsing of a file. If
|
|
||||||
# you need to save state across files, see {#globals}.
|
|
||||||
#
|
|
||||||
# @return [OpenStruct] an open structure that can store arbitrary data
|
|
||||||
# @see #globals
|
|
||||||
attr_accessor :extra_state
|
|
||||||
|
|
||||||
# Creates a new Processor for a +file+.
|
|
||||||
# @param [Parser::SourceParser] parser the parser used to initialize the processor
|
|
||||||
def initialize(parser)
|
|
||||||
@file = parser.file || "(stdin)"
|
|
||||||
@namespace = YARD::Registry.root
|
|
||||||
@visibility = :public
|
|
||||||
@scope = :instance
|
|
||||||
@owner = @namespace
|
|
||||||
@parser_type = parser.parser_type
|
|
||||||
@handlers_loaded = {}
|
|
||||||
@globals = parser.globals || OpenStruct.new
|
|
||||||
@extra_state = OpenStruct.new
|
|
||||||
load_handlers
|
|
||||||
end
|
|
||||||
|
|
||||||
# Processes a list of statements by finding handlers to process each
|
|
||||||
# one.
|
|
||||||
#
|
|
||||||
# @param [Array] statements a list of statements
|
|
||||||
# @return [void]
|
|
||||||
def process(statements)
|
|
||||||
statements.each_with_index do |stmt, _index|
|
|
||||||
find_handlers(stmt).each do |handler|
|
|
||||||
begin
|
|
||||||
handler.new(self, stmt).process
|
|
||||||
rescue HandlerAborted
|
|
||||||
log.debug "#{handler} cancelled from #{caller.last}"
|
|
||||||
log.debug "\tin file '#{file}':#{stmt.line}:\n\n" + stmt.show + "\n"
|
|
||||||
rescue NamespaceMissingError => missingerr
|
|
||||||
log.warn "The #{missingerr.object.type} #{missingerr.object.path} has not yet been recognized.\n" \
|
|
||||||
"If this class/method is part of your source tree, this will affect your documentation results.\n" \
|
|
||||||
"You can correct this issue by loading the source file for this object before `#{file}'\n"
|
|
||||||
rescue Parser::UndocumentableError => undocerr
|
|
||||||
log.warn "in #{handler}: Undocumentable #{undocerr.message}\n" \
|
|
||||||
"\tin file '#{file}':#{stmt.line}:\n\n" + stmt.show + "\n"
|
|
||||||
rescue => e
|
|
||||||
log.error "Unhandled exception in #{handler}:\n" \
|
|
||||||
" in `#{file}`:#{stmt.line}:\n\n#{stmt.show}\n"
|
|
||||||
log.backtrace(e)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Continue parsing the remainder of the files in the +globals.ordered_parser+
|
|
||||||
# object. After the remainder of files are parsed, processing will continue
|
|
||||||
# on the current file.
|
|
||||||
#
|
|
||||||
# @return [void]
|
|
||||||
# @see Parser::OrderedParser
|
|
||||||
def parse_remaining_files
|
|
||||||
if globals.ordered_parser
|
|
||||||
globals.ordered_parser.parse
|
|
||||||
log.debug("Re-processing #{@file}...")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Searches for all handlers in {Base.subclasses} that match the +statement+
|
|
||||||
#
|
|
||||||
# @param statement the statement object to match.
|
|
||||||
# @return [Array<Base>] a list of handlers to process the statement with.
|
|
||||||
def find_handlers(statement)
|
|
||||||
Base.subclasses.find_all do |handler|
|
|
||||||
handler_base_class > handler &&
|
|
||||||
(handler.namespace_only? ? owner.is_a?(CodeObjects::NamespaceObject) : true) &&
|
|
||||||
handles?(handler, statement)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def handles?(handler, statement)
|
|
||||||
return false unless handler.matches_file?(file)
|
|
||||||
if handler.method(:handles?).arity == 1
|
|
||||||
handler.handles?(statement)
|
|
||||||
elsif [-1, 2].include?(handler.method(:handles?).arity)
|
|
||||||
handler.handles?(statement, self)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns the handler base class
|
|
||||||
# @return [Base] the base class
|
|
||||||
def handler_base_class
|
|
||||||
handler_base_namespace.const_get(:Base)
|
|
||||||
end
|
|
||||||
|
|
||||||
# The module holding the handlers to be loaded
|
|
||||||
#
|
|
||||||
# @return [Module] the module containing the handlers depending on
|
|
||||||
# {#parser_type}.
|
|
||||||
def handler_base_namespace
|
|
||||||
self.class.namespace_for_handler[parser_type]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Loads handlers from {#handler_base_namespace}. This ensures that
|
|
||||||
# Ruby1.9 handlers are never loaded into 1.8; also lowers the amount
|
|
||||||
# of modules that are loaded
|
|
||||||
# @return [void]
|
|
||||||
def load_handlers
|
|
||||||
return if @handlers_loaded[parser_type]
|
|
||||||
handler_base_namespace.constants.each do |c|
|
|
||||||
const = handler_base_namespace.const_get(c)
|
|
||||||
unless Handlers::Base.subclasses.include?(const)
|
|
||||||
Handlers::Base.subclasses << const
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@handlers_loaded[parser_type] = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Handles alias and alias_method calls
|
|
||||||
class YARD::Handlers::Ruby::AliasHandler < YARD::Handlers::Ruby::Base
|
|
||||||
handles :alias, method_call(:alias_method)
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
names = []
|
|
||||||
if statement.type == :alias
|
|
||||||
nodes = [:ident, :op, :kw, :const, :tstring_content, :string_content]
|
|
||||||
names = statement.map {|o| o.jump(*nodes).source }
|
|
||||||
elsif statement.call?
|
|
||||||
statement.parameters(false).each do |obj|
|
|
||||||
case obj.type
|
|
||||||
when :symbol_literal
|
|
||||||
names << obj.jump(:ident, :op, :kw, :const).source
|
|
||||||
when :string_literal, :dyna_symbol
|
|
||||||
names << obj.jump(:string_content, :tstring_content).source
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
raise YARD::Parser::UndocumentableError, "alias/alias_method" if names.size != 2
|
|
||||||
|
|
||||||
new_meth = names[0].to_sym
|
|
||||||
old_meth = names[1].to_sym
|
|
||||||
old_obj = namespace.child(:name => old_meth, :scope => scope)
|
|
||||||
new_obj = register MethodObject.new(namespace, new_meth, scope) do |o|
|
|
||||||
o.add_file(parser.file, statement.line)
|
|
||||||
end
|
|
||||||
namespace.aliases[new_obj] = old_meth
|
|
||||||
|
|
||||||
if old_obj
|
|
||||||
new_obj.signature = old_obj.signature
|
|
||||||
new_obj.source = old_obj.source
|
|
||||||
comments = [old_obj.docstring.to_raw, statement.comments].join("\n")
|
|
||||||
doc = YARD::Docstring.parser.parse(comments, new_obj, self)
|
|
||||||
new_obj.docstring = doc.to_docstring
|
|
||||||
new_obj.docstring.line_range = statement.comments_range
|
|
||||||
new_obj.docstring.hash_flag = statement.comments_hash_flag
|
|
||||||
new_obj.docstring.object = new_obj
|
|
||||||
else
|
|
||||||
new_obj.signature = "def #{new_meth}" # this is all we know.
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Handles +attr_*+ statements in modules/classes
|
|
||||||
class YARD::Handlers::Ruby::AttributeHandler < YARD::Handlers::Ruby::Base
|
|
||||||
handles method_call(:attr)
|
|
||||||
handles method_call(:attr_reader)
|
|
||||||
handles method_call(:attr_writer)
|
|
||||||
handles method_call(:attr_accessor)
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
return if statement.type == :var_ref || statement.type == :vcall
|
|
||||||
read = true
|
|
||||||
write = false
|
|
||||||
params = statement.parameters(false).dup
|
|
||||||
|
|
||||||
# Change read/write based on attr_reader/writer/accessor
|
|
||||||
case statement.method_name(true)
|
|
||||||
when :attr
|
|
||||||
# In the case of 'attr', the second parameter (if given) isn't a symbol.
|
|
||||||
if params.size == 2
|
|
||||||
write = true if params.pop == s(:var_ref, s(:kw, "true"))
|
|
||||||
end
|
|
||||||
when :attr_accessor
|
|
||||||
write = true
|
|
||||||
when :attr_reader
|
|
||||||
# change nothing
|
|
||||||
when :attr_writer
|
|
||||||
read = false
|
|
||||||
write = true
|
|
||||||
end
|
|
||||||
|
|
||||||
# Add all attributes
|
|
||||||
validated_attribute_names(params).each do |name|
|
|
||||||
namespace.attributes[scope][name] ||= SymbolHash[:read => nil, :write => nil]
|
|
||||||
|
|
||||||
# Show their methods as well
|
|
||||||
{:read => name, :write => "#{name}="}.each do |type, meth|
|
|
||||||
if type == :read ? read : write
|
|
||||||
o = MethodObject.new(namespace, meth, scope)
|
|
||||||
if type == :write
|
|
||||||
o.parameters = [['value', nil]]
|
|
||||||
src = "def #{meth}(value)"
|
|
||||||
full_src = "#{src}\n @#{name} = value\nend"
|
|
||||||
doc = "Sets the attribute #{name}\n@param value the value to set the attribute #{name} to."
|
|
||||||
else
|
|
||||||
src = "def #{meth}"
|
|
||||||
full_src = "#{src}\n @#{name}\nend"
|
|
||||||
doc = "Returns the value of attribute #{name}."
|
|
||||||
end
|
|
||||||
o.source ||= full_src
|
|
||||||
o.signature ||= src
|
|
||||||
register(o)
|
|
||||||
o.docstring = doc if o.docstring.blank?(false)
|
|
||||||
|
|
||||||
# Regsiter the object explicitly
|
|
||||||
namespace.attributes[scope][name][type] = o
|
|
||||||
else
|
|
||||||
obj = namespace.children.find {|other| other.name == meth.to_sym && other.scope == scope }
|
|
||||||
|
|
||||||
# register an existing method as attribute
|
|
||||||
namespace.attributes[scope][name][type] = obj if obj
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# Strips out any non-essential arguments from the attr statement.
|
|
||||||
#
|
|
||||||
# @param [Array<Parser::Ruby::AstNode>] params a list of the parameters
|
|
||||||
# in the attr call.
|
|
||||||
# @return [Array<String>] the validated attribute names
|
|
||||||
# @raise [Parser::UndocumentableError] if the arguments are not valid.
|
|
||||||
def validated_attribute_names(params)
|
|
||||||
params.map do |obj|
|
|
||||||
case obj.type
|
|
||||||
when :symbol_literal
|
|
||||||
obj.jump(:ident, :op, :kw, :const).source
|
|
||||||
when :string_literal
|
|
||||||
obj.jump(:string_content).source
|
|
||||||
else
|
|
||||||
raise YARD::Parser::UndocumentableError, obj.source
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,165 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module Handlers
|
|
||||||
module Ruby
|
|
||||||
# To implement a custom handler matcher, subclass this class and implement
|
|
||||||
# {#matches?} to return whether a node matches the handler.
|
|
||||||
#
|
|
||||||
# @example A Custom Handler Matcher Extension
|
|
||||||
# # Implements a handler that checks for a specific string
|
|
||||||
# # in the node's source.
|
|
||||||
# class MyExtension < HandlesExtension
|
|
||||||
# def matches?(node) node.source.include?(name) end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# # This handler will handle any node where the source includes 'foo'
|
|
||||||
# class MyHandler < Handlers::Ruby::Base
|
|
||||||
# handles MyExtension.new('foo')
|
|
||||||
# end
|
|
||||||
class HandlesExtension
|
|
||||||
# Creates a new extension with a specific matcher value +name+
|
|
||||||
# @param [Object] name the matcher value to check against {#matches?}
|
|
||||||
def initialize(name) @name = name end
|
|
||||||
|
|
||||||
# Tests if the node matches the handler
|
|
||||||
# @param [Parser::Ruby::AstNode] node a Ruby node
|
|
||||||
# @return [Boolean] whether the +node+ matches the handler
|
|
||||||
def matches?(node) # rubocop:disable Lint/UnusedMethodArgument
|
|
||||||
raise NotImplementedError
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# @return [String] the extension matcher value
|
|
||||||
attr_reader :name
|
|
||||||
end
|
|
||||||
|
|
||||||
class MethodCallWrapper < HandlesExtension
|
|
||||||
def matches?(node)
|
|
||||||
case node.type
|
|
||||||
when :var_ref
|
|
||||||
if !node.parent || node.parent.type == :list
|
|
||||||
return true if node[0].type == :ident && (name.nil? || node[0][0] == name)
|
|
||||||
end
|
|
||||||
when :fcall, :command, :vcall
|
|
||||||
return true if name.nil? || node[0][0] == name
|
|
||||||
when :call, :command_call
|
|
||||||
return true if name.nil? || node[2][0] == name
|
|
||||||
end
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class TestNodeWrapper < HandlesExtension
|
|
||||||
def matches?(node) !node.send(name).is_a?(FalseClass) end
|
|
||||||
end
|
|
||||||
|
|
||||||
# This is the base handler class for the new-style (1.9) Ruby parser.
|
|
||||||
# All handlers that subclass this base class will be used when the
|
|
||||||
# new-style parser is used. For implementing legacy handlers, see
|
|
||||||
# {Legacy::Base}.
|
|
||||||
#
|
|
||||||
# @abstract See {Handlers::Base} for subclassing information.
|
|
||||||
# @see Handlers::Base
|
|
||||||
# @see Legacy::Base
|
|
||||||
class Base < Handlers::Base
|
|
||||||
class << self
|
|
||||||
include Parser::Ruby
|
|
||||||
|
|
||||||
# @group Statement Matcher Extensions
|
|
||||||
|
|
||||||
# Matcher for handling any type of method call. Method calls can
|
|
||||||
# be expressed by many {AstNode} types depending on the syntax
|
|
||||||
# with which it is called, so YARD allows you to use this matcher
|
|
||||||
# to simplify matching a method call.
|
|
||||||
#
|
|
||||||
# @example Match the "describe" method call
|
|
||||||
# handles method_call(:describe)
|
|
||||||
#
|
|
||||||
# # The following will be matched:
|
|
||||||
# # describe(...)
|
|
||||||
# # object.describe(...)
|
|
||||||
# # describe "argument" do ... end
|
|
||||||
#
|
|
||||||
# @param [#to_s] name matches the method call of this name
|
|
||||||
# @return [void]
|
|
||||||
def method_call(name = nil)
|
|
||||||
MethodCallWrapper.new(name ? name.to_s : nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Matcher for handling a node with a specific meta-type. An {AstNode}
|
|
||||||
# has a {AstNode#type} to define its type but can also be associated
|
|
||||||
# with a set of types. For instance, +:if+ and +:unless+ are both
|
|
||||||
# of the meta-type +:condition+.
|
|
||||||
#
|
|
||||||
# A meta-type is any method on the {AstNode} class ending in "?",
|
|
||||||
# though you should not include the "?" suffix in your declaration.
|
|
||||||
# Some examples are: "condition", "call", "literal", "kw", "token",
|
|
||||||
# "ref".
|
|
||||||
#
|
|
||||||
# @example Handling any conditional statement (if, unless)
|
|
||||||
# handles meta_type(:condition)
|
|
||||||
# @param [Symbol] type the meta-type to match. A meta-type can be
|
|
||||||
# any method name + "?" that {AstNode} responds to.
|
|
||||||
# @return [void]
|
|
||||||
def meta_type(type)
|
|
||||||
TestNodeWrapper.new(type.to_s + "?")
|
|
||||||
end
|
|
||||||
|
|
||||||
# @group Testing for a Handler
|
|
||||||
|
|
||||||
# @return [Boolean] whether or not an {AstNode} object should be
|
|
||||||
# handled by this handler
|
|
||||||
def handles?(node)
|
|
||||||
handlers.any? do |a_handler|
|
|
||||||
case a_handler
|
|
||||||
when Symbol
|
|
||||||
a_handler == node.type
|
|
||||||
when String
|
|
||||||
node.source == a_handler
|
|
||||||
when Regexp
|
|
||||||
node.source =~ a_handler
|
|
||||||
when Parser::Ruby::AstNode
|
|
||||||
a_handler == node
|
|
||||||
when HandlesExtension
|
|
||||||
a_handler.matches?(node)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
include Parser::Ruby
|
|
||||||
|
|
||||||
# @group Parsing an Inner Block
|
|
||||||
|
|
||||||
def parse_block(inner_node, opts = {})
|
|
||||||
push_state(opts) do
|
|
||||||
nodes = inner_node.type == :list ? inner_node.children : [inner_node]
|
|
||||||
parser.process(nodes)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# @group Macro Handling
|
|
||||||
|
|
||||||
def call_params
|
|
||||||
return [] unless statement.respond_to?(:parameters)
|
|
||||||
statement.parameters(false).compact.map do |param|
|
|
||||||
if param.type == :list
|
|
||||||
param.map {|n| n.jump(:ident, :kw, :tstring_content).source }
|
|
||||||
else
|
|
||||||
param.jump(:ident, :kw, :tstring_content).source
|
|
||||||
end
|
|
||||||
end.flatten
|
|
||||||
end
|
|
||||||
|
|
||||||
def caller_method
|
|
||||||
if statement.call? || statement.def?
|
|
||||||
statement.method_name(true).to_s
|
|
||||||
elsif statement.type == :var_ref || statement.type == :vcall
|
|
||||||
statement[0].jump(:ident, :kw).source
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Matches if/unless conditions inside classes and attempts to process only
|
|
||||||
# one branch (by evaluating the condition if possible).
|
|
||||||
#
|
|
||||||
# @example A simple class conditional
|
|
||||||
# class Foo
|
|
||||||
# if 0
|
|
||||||
# # This method is ignored
|
|
||||||
# def xyz; end
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
class YARD::Handlers::Ruby::ClassConditionHandler < YARD::Handlers::Ruby::Base
|
|
||||||
handles meta_type(:condition)
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
condition = parse_condition
|
|
||||||
if condition.nil?
|
|
||||||
# Parse both blocks if we're unsure of the condition
|
|
||||||
parse_then_block
|
|
||||||
parse_else_block
|
|
||||||
elsif condition
|
|
||||||
parse_then_block
|
|
||||||
else
|
|
||||||
parse_else_block
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# Parses the condition part of the if/unless statement
|
|
||||||
#
|
|
||||||
# @return [true, false, nil] true if the condition can be definitely
|
|
||||||
# parsed to true, false if not, and nil if the condition cannot be
|
|
||||||
# parsed with certainty (it's dynamic)
|
|
||||||
def parse_condition
|
|
||||||
condition = nil
|
|
||||||
|
|
||||||
# Right now we can handle very simple unary conditions like:
|
|
||||||
# if true
|
|
||||||
# if false
|
|
||||||
# if 0
|
|
||||||
# if 100 (not 0)
|
|
||||||
# if defined? SOME_CONSTANT
|
|
||||||
#
|
|
||||||
# The last case will do a lookup in the registry and then one
|
|
||||||
# in the Ruby world (using eval).
|
|
||||||
case statement.condition.type
|
|
||||||
when :int
|
|
||||||
condition = statement.condition[0] != "0"
|
|
||||||
when :defined
|
|
||||||
# defined? keyword used, let's see if we can look up the name
|
|
||||||
# in the registry, then we'll try using Ruby's powers. eval() is not
|
|
||||||
# *too* dangerous here since code is not actually executed.
|
|
||||||
arg = statement.condition.first
|
|
||||||
|
|
||||||
if arg.type == :var_ref
|
|
||||||
name = arg.source
|
|
||||||
obj = YARD::Registry.resolve(namespace, name, true)
|
|
||||||
|
|
||||||
begin
|
|
||||||
condition = true if obj || (name && Object.instance_eval("defined? #{name}"))
|
|
||||||
rescue SyntaxError, NameError
|
|
||||||
condition = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
when :var_ref
|
|
||||||
var = statement.condition[0]
|
|
||||||
if var == s(:kw, "true")
|
|
||||||
condition = true
|
|
||||||
elsif var == s(:kw, "false")
|
|
||||||
condition = false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Invert an unless condition
|
|
||||||
if statement.type == :unless || statement.type == :unless_mod
|
|
||||||
condition = !condition unless condition.nil?
|
|
||||||
end
|
|
||||||
condition
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_then_block
|
|
||||||
parse_block(statement.then_block, :visibility => visibility)
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_else_block
|
|
||||||
if statement.else_block
|
|
||||||
parse_block(statement.else_block, :visibility => visibility)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,119 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Handles class declarations
|
|
||||||
class YARD::Handlers::Ruby::ClassHandler < YARD::Handlers::Ruby::Base
|
|
||||||
include YARD::Handlers::Ruby::StructHandlerMethods
|
|
||||||
handles :class, :sclass
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
classname = statement[0].source.gsub(/\s/, '')
|
|
||||||
if statement.type == :class
|
|
||||||
superclass = parse_superclass(statement[1])
|
|
||||||
if superclass == "Struct"
|
|
||||||
is_a_struct = true
|
|
||||||
superclass = struct_superclass_name(statement[1]) # refine the superclass if possible
|
|
||||||
create_struct_superclass(superclass, statement[1])
|
|
||||||
end
|
|
||||||
undocsuper = statement[1] && superclass.nil?
|
|
||||||
klass = register ClassObject.new(namespace, classname) do |o|
|
|
||||||
o.superclass = superclass if superclass
|
|
||||||
o.superclass.type = :class if o.superclass.is_a?(Proxy)
|
|
||||||
end
|
|
||||||
if is_a_struct
|
|
||||||
parse_struct_superclass(klass, statement[1])
|
|
||||||
elsif klass
|
|
||||||
create_attributes(klass, members_from_tags(klass))
|
|
||||||
end
|
|
||||||
parse_block(statement[2], :namespace => klass)
|
|
||||||
|
|
||||||
if undocsuper
|
|
||||||
raise YARD::Parser::UndocumentableError, 'superclass (class was added without superclass)'
|
|
||||||
end
|
|
||||||
elsif statement.type == :sclass
|
|
||||||
if statement[0] == s(:var_ref, s(:kw, "self"))
|
|
||||||
parse_block(statement[1], :namespace => namespace, :scope => :class)
|
|
||||||
else
|
|
||||||
proxy = Proxy.new(namespace, classname)
|
|
||||||
|
|
||||||
# Allow constants to reference class names
|
|
||||||
if ConstantObject === proxy
|
|
||||||
if proxy.value =~ /\A#{NAMESPACEMATCH}\Z/
|
|
||||||
proxy = Proxy.new(namespace, proxy.value)
|
|
||||||
else
|
|
||||||
raise YARD::Parser::UndocumentableError, "constant class reference '#{classname}'"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if classname[0, 1] =~ /[A-Z]/
|
|
||||||
register ClassObject.new(namespace, classname) if Proxy === proxy
|
|
||||||
parse_block(statement[1], :namespace => proxy, :scope => :class)
|
|
||||||
else
|
|
||||||
raise YARD::Parser::UndocumentableError, "class '#{classname}'"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
sig_end = (statement[1] ? statement[1].source_end : statement[0].source_end) - statement.source_start
|
|
||||||
raise YARD::Parser::UndocumentableError, "class: #{statement.source[0..sig_end]}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Extract the parameters from the Struct.new AST node, returning them as a list
|
|
||||||
# of strings
|
|
||||||
#
|
|
||||||
# @param [MethodCallNode] superclass the AST node for the Struct.new call
|
|
||||||
# @return [Array<String>] the member names to generate methods for
|
|
||||||
def extract_parameters(superclass)
|
|
||||||
members = superclass.parameters.select {|x| x && x.type == :symbol_literal }
|
|
||||||
members.map! {|x| x.source.strip[1..-1] }
|
|
||||||
members
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_struct_superclass(superclass, superclass_def)
|
|
||||||
return if superclass == "Struct"
|
|
||||||
the_super = register ClassObject.new(P("Struct"), superclass[8..-1]) do |o|
|
|
||||||
o.superclass = "Struct"
|
|
||||||
end
|
|
||||||
parse_struct_superclass(the_super, superclass_def)
|
|
||||||
the_super
|
|
||||||
end
|
|
||||||
|
|
||||||
def struct_superclass_name(superclass)
|
|
||||||
if superclass.call?
|
|
||||||
first = superclass.parameters.first
|
|
||||||
if first.type == :string_literal && first[0].type == :string_content && first[0].size == 1
|
|
||||||
return "Struct::#{first[0][0][0]}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
"Struct"
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_struct_superclass(klass, superclass)
|
|
||||||
return unless superclass.call? && superclass.parameters
|
|
||||||
members = extract_parameters(superclass)
|
|
||||||
create_attributes(klass, members)
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_superclass(superclass)
|
|
||||||
return nil unless superclass
|
|
||||||
|
|
||||||
case superclass.type
|
|
||||||
when :var_ref
|
|
||||||
return namespace.path if superclass.first == s(:kw, "self")
|
|
||||||
return superclass.source if superclass.first.type == :const
|
|
||||||
when :const, :const_ref, :const_path_ref, :top_const_ref
|
|
||||||
return superclass.source
|
|
||||||
when :fcall, :command
|
|
||||||
methname = superclass.method_name.source
|
|
||||||
return superclass.parameters.first.source if methname == "DelegateClass"
|
|
||||||
return methname if superclass.method_name.type == :const
|
|
||||||
when :call, :command_call
|
|
||||||
cname = superclass.namespace.source
|
|
||||||
if cname =~ /^O?Struct$/ && superclass.method_name(true) == :new
|
|
||||||
return cname
|
|
||||||
end
|
|
||||||
end
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Handles a class variable (@@variable)
|
|
||||||
class YARD::Handlers::Ruby::ClassVariableHandler < YARD::Handlers::Ruby::Base
|
|
||||||
handles :assign
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
if statement[0].type == :var_field && statement[0][0].type == :cvar
|
|
||||||
name = statement[0][0][0]
|
|
||||||
value = statement[1].source
|
|
||||||
register ClassVariableObject.new(namespace, name) do |o|
|
|
||||||
o.source = statement
|
|
||||||
o.value = value
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Handles any lone comment statement in a Ruby file
|
|
||||||
class YARD::Handlers::Ruby::CommentHandler < YARD::Handlers::Ruby::Base
|
|
||||||
handles :comment, :void_stmt
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
register_docstring(nil)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,55 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Handles any constant assignment
|
|
||||||
class YARD::Handlers::Ruby::ConstantHandler < YARD::Handlers::Ruby::Base
|
|
||||||
include YARD::Handlers::Ruby::StructHandlerMethods
|
|
||||||
handles :assign
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
if statement[1].call? && statement[1][0][0] == s(:const, "Struct") &&
|
|
||||||
statement[1][2] == s(:ident, "new")
|
|
||||||
process_structclass(statement)
|
|
||||||
elsif statement[0].type == :var_field && statement[0][0].type == :const
|
|
||||||
process_constant(statement)
|
|
||||||
elsif statement[0].type == :const_path_field
|
|
||||||
process_constant(statement)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def process_constant(statement)
|
|
||||||
name = statement[0].source
|
|
||||||
value = statement[1].source
|
|
||||||
obj = P(namespace, name)
|
|
||||||
if obj.is_a?(NamespaceObject) && obj.namespace == namespace
|
|
||||||
raise YARD::Parser::UndocumentableError, "constant for existing #{obj.type} #{obj}"
|
|
||||||
else
|
|
||||||
ensure_loaded! obj.parent
|
|
||||||
register ConstantObject.new(namespace, name) {|o| o.source = statement; o.value = value.strip }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def process_structclass(statement)
|
|
||||||
lhs = statement[0][0]
|
|
||||||
if lhs.type == :const
|
|
||||||
klass = create_class(lhs[0], P(:Struct))
|
|
||||||
create_attributes(klass, extract_parameters(statement[1]))
|
|
||||||
parse_block(statement[1].block[1], :namespace => klass) unless statement[1].block.nil?
|
|
||||||
else
|
|
||||||
raise YARD::Parser::UndocumentableError, "Struct assignment to #{statement[0].source}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Extract the parameters from the Struct.new AST node, returning them as a list
|
|
||||||
# of strings
|
|
||||||
#
|
|
||||||
# @param [MethodCallNode] superclass the AST node for the Struct.new call
|
|
||||||
# @return [Array<String>] the member names to generate methods for
|
|
||||||
def extract_parameters(superclass)
|
|
||||||
return [] unless superclass.parameters
|
|
||||||
members = superclass.parameters.select {|x| x && x.type == :symbol_literal }
|
|
||||||
members.map! {|x| x.source.strip[1..-1] }
|
|
||||||
members
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,123 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Helper methods to assist with processing decorators.
|
|
||||||
module YARD::Handlers::Ruby::DecoratorHandlerMethods
|
|
||||||
# @overload process_decorator(*nodes, opts = {}, &block)
|
|
||||||
# Takes care of parsing method definitions passed to decorators
|
|
||||||
# as parameters, as well as parsing chained decorators.
|
|
||||||
#
|
|
||||||
# Use this in a handler's process block.
|
|
||||||
#
|
|
||||||
# @yieldparam method [YARD::CodeObjects::MethodObject] Method being decorated.
|
|
||||||
# @yieldparam node [YARD::Parser::Ruby::AstNode] AST node of the decorated method.
|
|
||||||
# @yieldparam name [Symbol] Name of the decorated method.
|
|
||||||
# @return [Array<Hash>] Array of hashes containing :method, :node, :name.
|
|
||||||
# See yield params.
|
|
||||||
#
|
|
||||||
# @param nodes [YARD::Parser::Ruby::AstNode] AST nodes that refer to decorated
|
|
||||||
# methods, like indexes of statement.parameter. Defaults to all parameters.
|
|
||||||
# Pass nil to specify zero parameters.
|
|
||||||
#
|
|
||||||
# @option opts [:instance, :class] :scope (:instance) Scope to use for each
|
|
||||||
# MethodObject.
|
|
||||||
#
|
|
||||||
# @option opts [true, false] :transfer_docstring Set false to disable
|
|
||||||
# transferring the decorator docstring to method definitions passed to the
|
|
||||||
# decorator as parameters.
|
|
||||||
#
|
|
||||||
# @option opts [true, false] :transfer_source Set false to disable
|
|
||||||
# transferring the decorator source code string to method definitions
|
|
||||||
# passed to the decorator as parameters.
|
|
||||||
#
|
|
||||||
# @example Basic Usage
|
|
||||||
# # Simply pass the method docs through to the method definition.
|
|
||||||
# process do
|
|
||||||
# process_decorator
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# @example Setting a method's visibility to private.
|
|
||||||
# process do
|
|
||||||
# process_decorator :scope => :class do |method|
|
|
||||||
# method.visibility = :private if method.respond_to? :visibility
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
def process_decorator(*nodes, &block)
|
|
||||||
opts = nodes.last.is_a?(Hash) ? nodes.pop : {}
|
|
||||||
|
|
||||||
all_nodes = statement.parameters.select do |p|
|
|
||||||
p.is_a? YARD::Parser::Ruby::AstNode
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parse decorator parameters (decorator chain).
|
|
||||||
all_nodes.each do |param|
|
|
||||||
parse_block param if param.call? || param.def?
|
|
||||||
end
|
|
||||||
|
|
||||||
selected_nodes =
|
|
||||||
if nodes.empty?
|
|
||||||
all_nodes
|
|
||||||
elsif nodes.count == 1 && nodes.first.nil?
|
|
||||||
[]
|
|
||||||
else
|
|
||||||
nodes
|
|
||||||
end
|
|
||||||
|
|
||||||
decorated_methods = selected_nodes.map do |param|
|
|
||||||
process_decorator_parameter param, opts, &block
|
|
||||||
end.flatten
|
|
||||||
|
|
||||||
# Store method nodes in decorator node.
|
|
||||||
statement.define_singleton_method :decorators do
|
|
||||||
decorated_methods.map {|h| h[:node] }
|
|
||||||
end
|
|
||||||
|
|
||||||
decorated_methods
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def process_decorator_parameter(node, opts = {}, &block)
|
|
||||||
scope = opts.fetch :scope, :instance
|
|
||||||
transfer_docstring = opts.fetch :transfer_docstring, true
|
|
||||||
transfer_source = opts.fetch :transfer_source, true
|
|
||||||
|
|
||||||
name = nil
|
|
||||||
|
|
||||||
if node.call?
|
|
||||||
if node.respond_to? :decorators
|
|
||||||
return node.decorators.map do |n|
|
|
||||||
process_decorator_parameter n, opts, &block
|
|
||||||
end
|
|
||||||
end
|
|
||||||
elsif node.def?
|
|
||||||
name = node.jump(:def).method_name.source
|
|
||||||
else
|
|
||||||
name = node.jump(:ident, :string_content, :const).source
|
|
||||||
end
|
|
||||||
|
|
||||||
if name.nil?
|
|
||||||
raise YARD::Parser::UndocumentableError, 'statement, cannot determine method name'
|
|
||||||
end
|
|
||||||
|
|
||||||
method = YARD::CodeObjects::Proxy.new(
|
|
||||||
namespace,
|
|
||||||
(scope == :instance ? '#' : '.') + name.to_s,
|
|
||||||
:method
|
|
||||||
)
|
|
||||||
|
|
||||||
# Transfer source to methods passed to the helper as parameters.
|
|
||||||
method.source = statement.source if transfer_source && node.def?
|
|
||||||
|
|
||||||
# Transfer decorator docstring to methods passed to the helper as parameters.
|
|
||||||
if transfer_docstring && node.def? &&
|
|
||||||
statement.docstring && method.docstring.empty?
|
|
||||||
tags = method.tags if method.respond_to? :tags
|
|
||||||
tags ||= []
|
|
||||||
method.docstring = statement.docstring
|
|
||||||
tags.each {|t| method.add_tag t }
|
|
||||||
end
|
|
||||||
|
|
||||||
yield method, node, name.to_sym if block_given?
|
|
||||||
|
|
||||||
[{:method => method, :node => node, :name => name.to_sym}]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module Handlers
|
|
||||||
module Ruby
|
|
||||||
# Handles automatic detection of dsl-style methods
|
|
||||||
class DSLHandler < Base
|
|
||||||
include CodeObjects
|
|
||||||
include DSLHandlerMethods
|
|
||||||
handles method_call
|
|
||||||
namespace_only
|
|
||||||
process { handle_comments }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module Handlers
|
|
||||||
module Ruby
|
|
||||||
module DSLHandlerMethods
|
|
||||||
include CodeObjects
|
|
||||||
include Parser
|
|
||||||
|
|
||||||
IGNORE_METHODS = Hash[*%w(alias alias_method autoload attr attr_accessor
|
|
||||||
attr_reader attr_writer extend include module_function public private
|
|
||||||
protected private_constant private_class_method public_class_method).
|
|
||||||
map {|n| [n, true] }.flatten]
|
|
||||||
|
|
||||||
def handle_comments
|
|
||||||
return if IGNORE_METHODS[caller_method]
|
|
||||||
|
|
||||||
@docstring = statement.comments || ""
|
|
||||||
@docstring = @docstring.join("\n") if @docstring.is_a?(Array)
|
|
||||||
|
|
||||||
attaching = false
|
|
||||||
if @docstring =~ /^@!?macro\s+\[[^\]]*attach/
|
|
||||||
register_docstring(nil)
|
|
||||||
@docstring = ""
|
|
||||||
attaching = true
|
|
||||||
end
|
|
||||||
|
|
||||||
macro = find_attached_macro
|
|
||||||
if macro
|
|
||||||
txt = macro.expand([caller_method, *call_params], statement.source)
|
|
||||||
@docstring += "\n" + txt
|
|
||||||
|
|
||||||
# macro may have a directive
|
|
||||||
return register_docstring(nil) if !attaching && txt.match(/^\s*@!/)
|
|
||||||
elsif !statement.comments_hash_flag && !implicit_docstring?
|
|
||||||
return register_docstring(nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
# ignore DSL definitions if @method/@attribute directive is used
|
|
||||||
if @docstring =~ /^@!?(method|attribute)\b/
|
|
||||||
return register_docstring(nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
register MethodObject.new(namespace, method_name, scope) do |o|
|
|
||||||
o.signature = method_signature
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def register_docstring(object, docstring = @docstring, stmt = statement)
|
|
||||||
super
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def implicit_docstring?
|
|
||||||
tags = %w(method attribute overload visibility scope return)
|
|
||||||
tags.any? {|tag| @docstring =~ /^@!?#{tag}\b/ }
|
|
||||||
end
|
|
||||||
|
|
||||||
def method_name
|
|
||||||
name = call_params.first || ""
|
|
||||||
if name =~ /^#{CodeObjects::METHODNAMEMATCH}$/
|
|
||||||
name
|
|
||||||
else
|
|
||||||
raise UndocumentableError, "method, missing name"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def method_signature
|
|
||||||
"def #{method_name}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_attached_macro
|
|
||||||
Registry.all(:macro).each do |macro|
|
|
||||||
next unless macro.method_object
|
|
||||||
next unless macro_name_matches(macro)
|
|
||||||
(namespace.inheritance_tree(true) + [P('Object')]).each do |obj|
|
|
||||||
return macro if obj == macro.method_object.namespace
|
|
||||||
end
|
|
||||||
end
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# @return [Boolean] whether caller method matches a macro or
|
|
||||||
# its alias names.
|
|
||||||
def macro_name_matches(macro)
|
|
||||||
objs = [macro.method_object]
|
|
||||||
if objs.first.type != :proxy && objs.first.respond_to?(:aliases)
|
|
||||||
objs.concat(objs.first.aliases)
|
|
||||||
end
|
|
||||||
|
|
||||||
objs.any? {|obj| obj.name.to_s == caller_method.to_s }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Handles 'raise' calls inside methods
|
|
||||||
class YARD::Handlers::Ruby::ExceptionHandler < YARD::Handlers::Ruby::Base
|
|
||||||
handles method_call(:raise)
|
|
||||||
|
|
||||||
process do
|
|
||||||
return unless owner.is_a?(MethodObject) # Only methods yield
|
|
||||||
return if [:command_call, :call].include? statement.type
|
|
||||||
return if owner.has_tag?(:raise)
|
|
||||||
|
|
||||||
klass = nil
|
|
||||||
if statement.call?
|
|
||||||
params = statement.parameters(false)
|
|
||||||
if params.size == 1
|
|
||||||
if params.first.ref? && params.first.first.type != :ident
|
|
||||||
klass = params.first.source
|
|
||||||
elsif params.first.call? && params.first.method_name(true) == :new
|
|
||||||
klass = params.first.namespace.source
|
|
||||||
end
|
|
||||||
elsif params.size > 1
|
|
||||||
klass = params.first.source
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
owner.add_tag YARD::Tags::Tag.new(:raise, '', klass) if klass
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Handles 'extend' call to include modules into the class scope of another
|
|
||||||
# @see MixinHandler
|
|
||||||
class YARD::Handlers::Ruby::ExtendHandler < YARD::Handlers::Ruby::MixinHandler
|
|
||||||
handles method_call(:extend)
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
def scope; :class end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def process_mixin(mixin)
|
|
||||||
if mixin == s(:var_ref, s(:kw, "self"))
|
|
||||||
if namespace.is_a?(ClassObject)
|
|
||||||
raise UndocumentableError, "extend(self) statement on class"
|
|
||||||
end
|
|
||||||
namespace.mixins(scope) << namespace
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# (see Ruby::AliasHandler)
|
|
||||||
class YARD::Handlers::Ruby::Legacy::AliasHandler < YARD::Handlers::Ruby::Legacy::Base
|
|
||||||
handles(/\Aalias(_method)?(\s|\()/)
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
if TkALIAS === statement.tokens.first
|
|
||||||
tokens = statement.tokens[2..-1].to_s.split(/\s+/)
|
|
||||||
names = [tokens[0], tokens[1]].map {|t| t.gsub(/^:(['"])?(.+?)\1?$|^(:)(.+)/, '\2') }
|
|
||||||
else
|
|
||||||
names = tokval_list(statement.tokens[2..-1], :attr)
|
|
||||||
end
|
|
||||||
raise YARD::Parser::UndocumentableError, statement.tokens.first.text if names.size != 2
|
|
||||||
|
|
||||||
names = names.map {|n| Symbol === n ? n.to_s.delete('"') : n }
|
|
||||||
new_meth = names[0].to_sym
|
|
||||||
old_meth = names[1].to_sym
|
|
||||||
old_obj = namespace.child(:name => old_meth, :scope => scope)
|
|
||||||
new_obj = register MethodObject.new(namespace, new_meth, scope) do |o|
|
|
||||||
o.add_file(parser.file, statement.tokens.first.line_no, statement.comments)
|
|
||||||
end
|
|
||||||
|
|
||||||
if old_obj
|
|
||||||
new_obj.signature = old_obj.signature
|
|
||||||
new_obj.source = old_obj.source
|
|
||||||
new_obj.docstring = old_obj.docstring + YARD::Docstring.new(statement.comments)
|
|
||||||
new_obj.docstring.line_range = statement.comments_range
|
|
||||||
new_obj.docstring.hash_flag = statement.comments_hash_flag
|
|
||||||
new_obj.docstring.object = new_obj
|
|
||||||
else
|
|
||||||
new_obj.signature = "def #{new_meth}" # this is all we know.
|
|
||||||
end
|
|
||||||
|
|
||||||
namespace.aliases[new_obj] = old_meth
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# (see Ruby::AttributeHandler)
|
|
||||||
class YARD::Handlers::Ruby::Legacy::AttributeHandler < YARD::Handlers::Ruby::Legacy::Base
|
|
||||||
handles(/\Aattr(?:_(?:reader|writer|accessor))?(?:\s|\()/)
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
begin
|
|
||||||
attr_type = statement.tokens.first.text.to_sym
|
|
||||||
symbols = tokval_list statement.tokens[2..-1], :attr, TkTRUE, TkFALSE
|
|
||||||
read = true
|
|
||||||
write = false
|
|
||||||
rescue SyntaxError
|
|
||||||
raise YARD::Parser::UndocumentableError, attr_type
|
|
||||||
end
|
|
||||||
|
|
||||||
# Change read/write based on attr_reader/writer/accessor
|
|
||||||
case attr_type
|
|
||||||
when :attr
|
|
||||||
# In the case of 'attr', the second parameter (if given) isn't a symbol.
|
|
||||||
write = symbols.pop if symbols.size == 2
|
|
||||||
when :attr_accessor
|
|
||||||
write = true
|
|
||||||
when :attr_reader
|
|
||||||
# change nothing
|
|
||||||
when :attr_writer
|
|
||||||
read = false
|
|
||||||
write = true
|
|
||||||
end
|
|
||||||
|
|
||||||
# Add all attributes
|
|
||||||
symbols.each do |name|
|
|
||||||
namespace.attributes[scope][name] = SymbolHash[:read => nil, :write => nil]
|
|
||||||
|
|
||||||
# Show their methods as well
|
|
||||||
{:read => name, :write => "#{name}="}.each do |type, meth|
|
|
||||||
if type == :read ? read : write
|
|
||||||
o = MethodObject.new(namespace, meth, scope)
|
|
||||||
if type == :write
|
|
||||||
o.parameters = [['value', nil]]
|
|
||||||
src = "def #{meth}(value)"
|
|
||||||
full_src = "#{src}\n @#{name} = value\nend"
|
|
||||||
doc = "Sets the attribute #{name}\n@param value the value to set the attribute #{name} to."
|
|
||||||
else
|
|
||||||
src = "def #{meth}"
|
|
||||||
full_src = "#{src}\n @#{name}\nend"
|
|
||||||
doc = "Returns the value of attribute #{name}"
|
|
||||||
end
|
|
||||||
o.source ||= full_src
|
|
||||||
o.signature ||= src
|
|
||||||
register(o)
|
|
||||||
o.docstring = doc if o.docstring.blank?(false)
|
|
||||||
|
|
||||||
# Regsiter the object explicitly
|
|
||||||
namespace.attributes[scope][name][type] = o
|
|
||||||
else
|
|
||||||
obj = namespace.children.find {|other| other.name == meth.to_sym && other.scope == scope }
|
|
||||||
|
|
||||||
# register an existing method as attribute
|
|
||||||
namespace.attributes[scope][name][type] = obj if obj
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,245 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module Handlers
|
|
||||||
module Ruby::Legacy
|
|
||||||
# This is the base handler for the legacy parser. To implement a legacy
|
|
||||||
# handler, subclass this class.
|
|
||||||
#
|
|
||||||
# @abstract (see Ruby::Base)
|
|
||||||
class Base < Handlers::Base
|
|
||||||
# For tokens like TkDEF, TkCLASS, etc.
|
|
||||||
include YARD::Parser::Ruby::Legacy::RubyToken
|
|
||||||
|
|
||||||
# @return [Boolean] whether or not a {Parser::Ruby::Legacy::Statement} object should be handled
|
|
||||||
# by this handler.
|
|
||||||
def self.handles?(stmt)
|
|
||||||
handlers.any? do |a_handler|
|
|
||||||
case a_handler
|
|
||||||
when String
|
|
||||||
stmt.tokens.first.text == a_handler
|
|
||||||
when Regexp
|
|
||||||
stmt.tokens.to_s =~ a_handler
|
|
||||||
else
|
|
||||||
a_handler == stmt.tokens.first.class
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Parses a statement's block with a set of state values. If the
|
|
||||||
# statement has no block, nothing happens. A description of state
|
|
||||||
# values can be found at {Handlers::Base#push_state}
|
|
||||||
#
|
|
||||||
# @param [Hash] opts State options
|
|
||||||
# @option opts (see Handlers::Base#push_state)
|
|
||||||
# @see Handlers::Base#push_state #push_state
|
|
||||||
def parse_block(opts = {})
|
|
||||||
push_state(opts) do
|
|
||||||
if statement.block
|
|
||||||
blk = Parser::Ruby::Legacy::StatementList.new(statement.block)
|
|
||||||
parser.process(blk)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def call_params
|
|
||||||
if statement.tokens.first.is_a?(TkDEF)
|
|
||||||
extract_method_details.last.map(&:first)
|
|
||||||
else
|
|
||||||
tokens = statement.tokens[1..-1]
|
|
||||||
tokval_list(tokens, :attr, :identifier, TkId).map(&:to_s)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def caller_method
|
|
||||||
if statement.tokens.first.is_a?(TkIDENTIFIER)
|
|
||||||
statement.tokens.first.text
|
|
||||||
elsif statement.tokens.first.is_a?(TkDEF)
|
|
||||||
extract_method_details.first
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Extracts method information for macro expansion only
|
|
||||||
#
|
|
||||||
# @todo This is a duplicate implementation of {MethodHandler}. Refactor.
|
|
||||||
# @return [Array<String,Array<Array<String>>>] the method name followed by method
|
|
||||||
# arguments (name and optional value)
|
|
||||||
def extract_method_details
|
|
||||||
if statement.tokens.to_s =~ /^def\s+(#{METHODMATCH})(?:(?:\s+|\s*\()(.*)(?:\)\s*$)?)?/m
|
|
||||||
meth = $1
|
|
||||||
args = $2
|
|
||||||
meth.gsub!(/\s+/, '')
|
|
||||||
args = tokval_list(Parser::Ruby::Legacy::TokenList.new(args), :all)
|
|
||||||
args.map! {|a| k, v = *a.split('=', 2); [k.strip, (v ? v.strip : nil)] } if args
|
|
||||||
meth = $` if meth =~ /(?:#{NSEPQ}|#{CSEPQ})([^#{NSEP}#{CSEPQ}]+)$/
|
|
||||||
[meth, args]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# The string value of a token. For example, the return value for the symbol :sym
|
|
||||||
# would be :sym. The return value for a string +"foo #{ bar}"+ would be the literal
|
|
||||||
# +"foo #{ bar}"+ without any interpolation. The return value of the identifier
|
|
||||||
# 'test' would be the same value: 'test'. Here is a list of common types and
|
|
||||||
# their return values:
|
|
||||||
#
|
|
||||||
# @example
|
|
||||||
# tokval(TokenList.new('"foo"').first) => "foo"
|
|
||||||
# tokval(TokenList.new(':foo').first) => :foo
|
|
||||||
# tokval(TokenList.new('CONSTANT').first, RubyToken::TkId) => "CONSTANT"
|
|
||||||
# tokval(TokenList.new('identifier').first, RubyToken::TkId) => "identifier"
|
|
||||||
# tokval(TokenList.new('3.25').first) => 3.25
|
|
||||||
# tokval(TokenList.new('/xyz/i').first) => /xyz/i
|
|
||||||
#
|
|
||||||
# @param [Token] token The token of the class
|
|
||||||
#
|
|
||||||
# @param [Array<Class<Token>>, Symbol] accepted_types
|
|
||||||
# The allowed token types that this token can be. Defaults to [{TkVal}].
|
|
||||||
# A list of types would be, for example, [+TkSTRING+, +TkSYMBOL+], to return
|
|
||||||
# the token's value if it is either of those types. If +TkVal+ is accepted,
|
|
||||||
# +TkNode+ is also accepted.
|
|
||||||
#
|
|
||||||
# Certain symbol keys are allowed to specify multiple types in one fell swoop.
|
|
||||||
# These symbols are:
|
|
||||||
# :string => +TkSTRING+, +TkDSTRING+, +TkDXSTRING+ and +TkXSTRING+
|
|
||||||
# :attr => +TkSYMBOL+ and +TkSTRING+
|
|
||||||
# :identifier => +TkIDENTIFIER, +TkFID+ and +TkGVAR+.
|
|
||||||
# :number => +TkFLOAT+, +TkINTEGER+
|
|
||||||
#
|
|
||||||
# @return [Object] if the token is one of the accepted types, in its real value form.
|
|
||||||
# It should be noted that identifiers and constants are kept in String form.
|
|
||||||
# @return [nil] if the token is not any of the specified accepted types
|
|
||||||
def tokval(token, *accepted_types)
|
|
||||||
accepted_types = [TkVal] if accepted_types.empty?
|
|
||||||
accepted_types.push(TkNode) if accepted_types.include? TkVal
|
|
||||||
|
|
||||||
if accepted_types.include?(:attr)
|
|
||||||
accepted_types.push(TkSTRING, TkSYMBOL)
|
|
||||||
end
|
|
||||||
|
|
||||||
if accepted_types.include?(:string)
|
|
||||||
accepted_types.push(TkSTRING, TkDSTRING, TkXSTRING, TkDXSTRING)
|
|
||||||
end
|
|
||||||
|
|
||||||
if accepted_types.include?(:identifier)
|
|
||||||
accepted_types.push(TkIDENTIFIER, TkFID, TkGVAR)
|
|
||||||
end
|
|
||||||
|
|
||||||
if accepted_types.include?(:number)
|
|
||||||
accepted_types.push(TkFLOAT, TkINTEGER)
|
|
||||||
end
|
|
||||||
|
|
||||||
return unless accepted_types.any? {|t| t === token }
|
|
||||||
|
|
||||||
case token
|
|
||||||
when TkSTRING, TkDSTRING, TkXSTRING, TkDXSTRING
|
|
||||||
token.text[1..-2]
|
|
||||||
when TkSYMBOL
|
|
||||||
token.text[1..-1].to_sym
|
|
||||||
when TkFLOAT
|
|
||||||
token.text.to_f
|
|
||||||
when TkINTEGER
|
|
||||||
token.text.to_i
|
|
||||||
when TkREGEXP
|
|
||||||
token.text =~ %r{\A/(.+)/([^/])\Z}
|
|
||||||
Regexp.new($1, $2)
|
|
||||||
when TkTRUE
|
|
||||||
true
|
|
||||||
when TkFALSE
|
|
||||||
false
|
|
||||||
when TkNIL
|
|
||||||
nil
|
|
||||||
else
|
|
||||||
token.text
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a list of symbols or string values from a statement.
|
|
||||||
# The list must be a valid comma delimited list, and values
|
|
||||||
# will only be returned to the end of the list only.
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
# attr_accessor :a, 'b', :c, :d => ['a', 'b', 'c', 'd']
|
|
||||||
# attr_accessor 'a', UNACCEPTED_TYPE, 'c' => ['a', 'c']
|
|
||||||
#
|
|
||||||
# The tokval list of a {Parser::Ruby::Legacy::TokenList} of the above
|
|
||||||
# code would be the {#tokval} value of :a, 'b',
|
|
||||||
# :c and :d.
|
|
||||||
#
|
|
||||||
# It should also be noted that this function stops immediately at
|
|
||||||
# any ruby keyword encountered:
|
|
||||||
# "attr_accessor :a, :b, :c if x == 5" => ['a', 'b', 'c']
|
|
||||||
#
|
|
||||||
# @param [TokenList] tokenlist The list of tokens to process.
|
|
||||||
# @param [Array<Class<Token>>] accepted_types passed to {#tokval}
|
|
||||||
# @return [Array<String>] the list of tokvalues in the list.
|
|
||||||
# @return [Array<EMPTY>] if there are no symbols or Strings in the list
|
|
||||||
# @see #tokval
|
|
||||||
def tokval_list(tokenlist, *accepted_types)
|
|
||||||
return [] unless tokenlist
|
|
||||||
out = [[]]
|
|
||||||
parencount = 0
|
|
||||||
beforeparen = 0
|
|
||||||
needcomma = false
|
|
||||||
seen_comma = true
|
|
||||||
tokenlist.each do |token|
|
|
||||||
tokval = accepted_types == [:all] ? token.text : tokval(token, *accepted_types)
|
|
||||||
parencond = !out.last.empty? && !tokval.nil?
|
|
||||||
# puts "#{seen_comma.inspect} #{parencount} #{token.class.class_name} #{out.inspect}"
|
|
||||||
case token
|
|
||||||
when TkCOMMA
|
|
||||||
if parencount == 0
|
|
||||||
out << [] unless out.last.empty?
|
|
||||||
needcomma = false
|
|
||||||
seen_comma = true
|
|
||||||
elsif parencond
|
|
||||||
out.last << token.text
|
|
||||||
end
|
|
||||||
when TkLPAREN
|
|
||||||
if seen_comma
|
|
||||||
beforeparen += 1
|
|
||||||
else
|
|
||||||
parencount += 1
|
|
||||||
out.last << token.text if parencond
|
|
||||||
end
|
|
||||||
when TkRPAREN
|
|
||||||
if beforeparen > 0
|
|
||||||
beforeparen -= 1
|
|
||||||
else
|
|
||||||
out.last << token.text if parencount > 0 && !tokval.nil?
|
|
||||||
parencount -= 1
|
|
||||||
end
|
|
||||||
when TkLBRACE, TkLBRACK, TkDO
|
|
||||||
parencount += 1
|
|
||||||
out.last << token.text unless tokval.nil?
|
|
||||||
when TkRBRACE, TkRBRACK, TkEND
|
|
||||||
out.last << token.text unless tokval.nil?
|
|
||||||
parencount -= 1
|
|
||||||
else
|
|
||||||
break if TkKW === token && ![TkTRUE, TkFALSE, TkSUPER, TkSELF, TkNIL].include?(token.class)
|
|
||||||
|
|
||||||
seen_comma = false unless TkWhitespace === token
|
|
||||||
if parencount == 0
|
|
||||||
next if needcomma
|
|
||||||
next if TkWhitespace === token
|
|
||||||
if !tokval.nil?
|
|
||||||
out.last << tokval
|
|
||||||
else
|
|
||||||
out.last.clear
|
|
||||||
needcomma = true
|
|
||||||
end
|
|
||||||
elsif parencond
|
|
||||||
needcomma = true
|
|
||||||
out.last << token.text
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
break if beforeparen == 0 && parencount < 0
|
|
||||||
end
|
|
||||||
# Flatten any single element lists
|
|
||||||
out.map {|e| e.empty? ? nil : (e.size == 1 ? e.pop : e.flatten.join) }.compact
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,83 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# (see Ruby::ClassConditionHandler)
|
|
||||||
# @since 0.5.4
|
|
||||||
class YARD::Handlers::Ruby::Legacy::ClassConditionHandler < YARD::Handlers::Ruby::Legacy::Base
|
|
||||||
namespace_only
|
|
||||||
handles TkIF, TkELSIF, TkUNLESS
|
|
||||||
|
|
||||||
process do
|
|
||||||
condition = parse_condition
|
|
||||||
if condition.nil?
|
|
||||||
# Parse both blocks if we're unsure of the condition
|
|
||||||
parse_then_block
|
|
||||||
parse_else_block
|
|
||||||
elsif condition
|
|
||||||
parse_then_block
|
|
||||||
else
|
|
||||||
parse_else_block
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
# Parses the condition part of the if/unless statement
|
|
||||||
#
|
|
||||||
# @return [true, false, nil] true if the condition can be definitely
|
|
||||||
# parsed to true, false if not, and nil if the condition cannot be
|
|
||||||
# parsed with certainty (it's dynamic)
|
|
||||||
# @since 0.5.5
|
|
||||||
def parse_condition
|
|
||||||
condition = nil
|
|
||||||
|
|
||||||
# Right now we can handle very simple unary conditions like:
|
|
||||||
# if true
|
|
||||||
# if false
|
|
||||||
# if 0
|
|
||||||
# if 100 (not 0)
|
|
||||||
# if defined? SOME_CONSTANT
|
|
||||||
#
|
|
||||||
# The last case will do a lookup in the registry and then one
|
|
||||||
# in the Ruby world (using eval).
|
|
||||||
case statement.tokens[1..-1].to_s.strip
|
|
||||||
when /^(\d+)$/
|
|
||||||
condition = $1 != "0"
|
|
||||||
when /^defined\?\s*\(?\s*([A-Za-z0-9:_]+?)\s*\)?$/
|
|
||||||
# defined? keyword used, let's see if we can look up the name
|
|
||||||
# in the registry, then we'll try using Ruby's powers. eval() is not
|
|
||||||
# *too* dangerous here since code is not actually executed.
|
|
||||||
name = $1
|
|
||||||
obj = YARD::Registry.resolve(namespace, name, true)
|
|
||||||
begin
|
|
||||||
condition = true if obj || Object.instance_eval("defined? #{name}")
|
|
||||||
rescue SyntaxError, NameError
|
|
||||||
condition = false
|
|
||||||
end
|
|
||||||
when "true"
|
|
||||||
condition = true
|
|
||||||
when "false"
|
|
||||||
condition = false
|
|
||||||
end
|
|
||||||
|
|
||||||
if TkUNLESS === statement.tokens.first
|
|
||||||
condition = !condition unless condition.nil?
|
|
||||||
end
|
|
||||||
condition
|
|
||||||
end
|
|
||||||
|
|
||||||
# @since 0.5.5
|
|
||||||
def parse_then_block
|
|
||||||
parse_block(:visibility => visibility)
|
|
||||||
end
|
|
||||||
|
|
||||||
# @since 0.5.5
|
|
||||||
def parse_else_block
|
|
||||||
return unless statement.block
|
|
||||||
stmtlist = YARD::Parser::Ruby::Legacy::StatementList
|
|
||||||
stmtlist.new(statement.block).each do |stmt|
|
|
||||||
next unless TkELSE === stmt.tokens.first
|
|
||||||
push_state(:visibility => visibility) do
|
|
||||||
parser.process(stmtlist.new(stmt.block))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# (see Ruby::ClassHandler)
|
|
||||||
class YARD::Handlers::Ruby::Legacy::ClassHandler < YARD::Handlers::Ruby::Legacy::Base
|
|
||||||
include YARD::Handlers::Ruby::StructHandlerMethods
|
|
||||||
handles TkCLASS
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
if statement.tokens.to_s =~ /^class\s+(#{NAMESPACEMATCH})\s*(?:<\s*(.+)|\Z)/m
|
|
||||||
classname = $1
|
|
||||||
superclass_def = $2
|
|
||||||
superclass = parse_superclass($2)
|
|
||||||
classname = classname.gsub(/\s/, '')
|
|
||||||
if superclass == "Struct"
|
|
||||||
is_a_struct = true
|
|
||||||
superclass = struct_superclass_name(superclass_def)
|
|
||||||
create_struct_superclass(superclass, superclass_def)
|
|
||||||
end
|
|
||||||
undocsuper = superclass_def && superclass.nil?
|
|
||||||
|
|
||||||
klass = register ClassObject.new(namespace, classname) do |o|
|
|
||||||
o.superclass = superclass if superclass
|
|
||||||
o.superclass.type = :class if o.superclass.is_a?(Proxy)
|
|
||||||
end
|
|
||||||
if is_a_struct
|
|
||||||
parse_struct_subclass(klass, superclass_def)
|
|
||||||
elsif klass
|
|
||||||
create_attributes(klass, members_from_tags(klass))
|
|
||||||
end
|
|
||||||
parse_block(:namespace => klass)
|
|
||||||
|
|
||||||
if undocsuper
|
|
||||||
raise YARD::Parser::UndocumentableError, 'superclass (class was added without superclass)'
|
|
||||||
end
|
|
||||||
elsif statement.tokens.to_s =~ /^class\s*<<\s*([\w\:\s]+)/
|
|
||||||
classname = $1.gsub(/\s/, '')
|
|
||||||
proxy = Proxy.new(namespace, classname)
|
|
||||||
|
|
||||||
# Allow constants to reference class names
|
|
||||||
if ConstantObject === proxy
|
|
||||||
if proxy.value =~ /\A#{NAMESPACEMATCH}\Z/
|
|
||||||
proxy = Proxy.new(namespace, proxy.value)
|
|
||||||
else
|
|
||||||
raise YARD::Parser::UndocumentableError, "constant class reference '#{classname}'"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if classname == "self"
|
|
||||||
parse_block(:namespace => namespace, :scope => :class)
|
|
||||||
elsif classname[0, 1] =~ /[A-Z]/
|
|
||||||
register ClassObject.new(namespace, classname) if Proxy === proxy
|
|
||||||
parse_block(:namespace => proxy, :scope => :class)
|
|
||||||
else
|
|
||||||
raise YARD::Parser::UndocumentableError, "class '#{classname}'"
|
|
||||||
end
|
|
||||||
else
|
|
||||||
raise YARD::Parser::UndocumentableError, "class: #{statement.tokens}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# Extracts the parameter list from the Struct.new declaration and returns it
|
|
||||||
# formatted as a list of member names. Expects the user will have used symbols
|
|
||||||
# to define the struct member names
|
|
||||||
#
|
|
||||||
# @param [String] superstring the string declaring the superclass
|
|
||||||
# @return [Array<String>] a list of member names
|
|
||||||
def extract_parameters(superstring)
|
|
||||||
paramstring = superstring.match(/\A(O?Struct)\.new\((.*?)\)/)[2]
|
|
||||||
paramstring.split(",").select {|x| x.strip[0, 1] == ":" }.map {|x| x.strip[1..-1] } # the 1..-1 chops the leading :
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_struct_superclass(superclass, superclass_def)
|
|
||||||
return if superclass == "Struct"
|
|
||||||
the_super = register ClassObject.new(P("Struct"), superclass[8..-1]) do |o|
|
|
||||||
o.superclass = "Struct"
|
|
||||||
end
|
|
||||||
parse_struct_subclass(the_super, superclass_def)
|
|
||||||
the_super
|
|
||||||
end
|
|
||||||
|
|
||||||
def struct_superclass_name(superclass)
|
|
||||||
match = superclass.match(/\A(Struct)\.new\((.*?)\)/)
|
|
||||||
if match
|
|
||||||
paramstring = match[2].split(",")
|
|
||||||
first = paramstring.first.strip
|
|
||||||
if first[0, 1] =~ /['"]/ && first[-1, 1] =~ /['"]/ && first !~ /\#\{/
|
|
||||||
return "Struct::#{first[1..-2]}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
"Struct"
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_struct_subclass(klass, superclass_def)
|
|
||||||
# Bounce if there's no parens
|
|
||||||
return unless superclass_def =~ /O?Struct\.new\((.*?)\)/
|
|
||||||
members = extract_parameters(superclass_def)
|
|
||||||
create_attributes(klass, members)
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_superclass(superclass)
|
|
||||||
case superclass
|
|
||||||
when /\A(#{NAMESPACEMATCH})(?:\s|\Z)/,
|
|
||||||
/\A(Struct|OStruct)\.new/,
|
|
||||||
/\ADelegateClass\((.+?)\)\s*\Z/,
|
|
||||||
/\A(#{NAMESPACEMATCH})\(/
|
|
||||||
$1
|
|
||||||
when "self"
|
|
||||||
namespace.path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# (see Ruby::ClassVariableHandler)
|
|
||||||
class YARD::Handlers::Ruby::Legacy::ClassVariableHandler < YARD::Handlers::Ruby::Legacy::Base
|
|
||||||
HANDLER_MATCH = /\A@@\w+\s*=\s*/m
|
|
||||||
handles HANDLER_MATCH
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
name, value = *statement.tokens.to_s.split(/\s*=\s*/, 2)
|
|
||||||
register ClassVariableObject.new(namespace, name) do |o|
|
|
||||||
o.source = statement
|
|
||||||
o.value = value.strip
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# (see Ruby::CommentHandler)
|
|
||||||
class YARD::Handlers::Ruby::Legacy::CommentHandler < YARD::Handlers::Ruby::Legacy::Base
|
|
||||||
handles TkCOMMENT
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
register_docstring(nil)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# (see Ruby::ConstantHandler)
|
|
||||||
class YARD::Handlers::Ruby::Legacy::ConstantHandler < YARD::Handlers::Ruby::Legacy::Base
|
|
||||||
include YARD::Handlers::Ruby::StructHandlerMethods
|
|
||||||
HANDLER_MATCH = /\A[A-Z]\w*\s*=[^=]\s*/m
|
|
||||||
handles HANDLER_MATCH
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
name, value = *statement.tokens.to_s.split(/\s*=\s*/, 2)
|
|
||||||
if value =~ /\A\s*Struct.new(?:\s*\(?|\b)/
|
|
||||||
process_structclass(name, $')
|
|
||||||
else
|
|
||||||
register ConstantObject.new(namespace, name) {|o| o.source = statement; o.value = value.strip }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def process_structclass(classname, parameters)
|
|
||||||
klass = create_class(classname, P(:Struct))
|
|
||||||
create_attributes(klass, extract_parameters(parameters))
|
|
||||||
end
|
|
||||||
|
|
||||||
def extract_parameters(parameters)
|
|
||||||
members = tokval_list(YARD::Parser::Ruby::Legacy::TokenList.new(parameters), TkSYMBOL)
|
|
||||||
members.map(&:to_s)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
module YARD
|
|
||||||
module Handlers
|
|
||||||
module Ruby
|
|
||||||
module Legacy
|
|
||||||
# (see Ruby::DSLHandler)
|
|
||||||
class DSLHandler < Base
|
|
||||||
include CodeObjects
|
|
||||||
include DSLHandlerMethods
|
|
||||||
handles TkIDENTIFIER
|
|
||||||
namespace_only
|
|
||||||
process { handle_comments }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# (see Ruby::ExceptionHandler)
|
|
||||||
class YARD::Handlers::Ruby::Legacy::ExceptionHandler < YARD::Handlers::Ruby::Legacy::Base
|
|
||||||
handles(/\Araise(\s|\(|\Z)/)
|
|
||||||
|
|
||||||
process do
|
|
||||||
return unless owner.is_a?(MethodObject) # Only methods yield
|
|
||||||
return if owner.has_tag?(:raise)
|
|
||||||
|
|
||||||
klass = statement.tokens.to_s[/^raise[\(\s]*(#{NAMESPACEMATCH})\s*(?:\)|,|\s(?:if|unless|until)|;|(?:(?:\.|\:\:)\s*)?new|$)/, 1]
|
|
||||||
owner.add_tag YARD::Tags::Tag.new(:raise, '', klass) if klass
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# (see Ruby::ExtendHandler)
|
|
||||||
class YARD::Handlers::Ruby::Legacy::ExtendHandler < YARD::Handlers::Ruby::Legacy::MixinHandler
|
|
||||||
handles(/\Aextend(\s|\()/)
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
def scope; :class end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def process_mixin(mixin)
|
|
||||||
if mixin == "self"
|
|
||||||
if namespace.is_a?(ClassObject)
|
|
||||||
raise UndocumentableError, "extend(self) statement on class"
|
|
||||||
end
|
|
||||||
namespace.mixins(scope) << namespace
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,90 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# (see Ruby::MethodHandler)
|
|
||||||
class YARD::Handlers::Ruby::Legacy::MethodHandler < YARD::Handlers::Ruby::Legacy::Base
|
|
||||||
handles TkDEF
|
|
||||||
|
|
||||||
process do
|
|
||||||
nobj = namespace
|
|
||||||
mscope = scope
|
|
||||||
|
|
||||||
if statement.tokens.to_s =~ /^def\s+(#{METHODMATCH})(?:(?:\s+|\s*\()(.*)(?:\)\s*$)?)?/m
|
|
||||||
meth = $1
|
|
||||||
args = $2
|
|
||||||
meth.gsub!(/\s+/, '')
|
|
||||||
args = tokval_list(YARD::Parser::Ruby::Legacy::TokenList.new(args), :all)
|
|
||||||
args.map! do |a|
|
|
||||||
k, v, r = *a.split(/(:)|=/, 2)
|
|
||||||
if r
|
|
||||||
k += v
|
|
||||||
v = r
|
|
||||||
end
|
|
||||||
[k.strip, (v ? v.strip : nil)]
|
|
||||||
end if args
|
|
||||||
else
|
|
||||||
raise YARD::Parser::UndocumentableError, "method: invalid name"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Class method if prefixed by self(::|.) or Module(::|.)
|
|
||||||
if meth =~ /(?:#{NSEPQ}|#{CSEPQ})([^#{NSEP}#{CSEPQ}]+)$/
|
|
||||||
mscope = :class
|
|
||||||
meth = $1
|
|
||||||
prefix = $`
|
|
||||||
if prefix =~ /^[a-z]/ && prefix != "self"
|
|
||||||
raise YARD::Parser::UndocumentableError, 'method defined on object instance'
|
|
||||||
end
|
|
||||||
nobj = P(namespace, prefix) unless prefix == "self"
|
|
||||||
end
|
|
||||||
|
|
||||||
nobj = P(namespace, nobj.value) while nobj.type == :constant
|
|
||||||
obj = register MethodObject.new(nobj, meth, mscope) do |o|
|
|
||||||
o.explicit = true
|
|
||||||
o.parameters = args
|
|
||||||
end
|
|
||||||
|
|
||||||
# delete any aliases referencing old method
|
|
||||||
nobj.aliases.each do |aobj, name|
|
|
||||||
next unless name == obj.name
|
|
||||||
nobj.aliases.delete(aobj)
|
|
||||||
end if nobj.is_a?(NamespaceObject)
|
|
||||||
|
|
||||||
if mscope == :instance && meth == "initialize"
|
|
||||||
unless obj.has_tag?(:return)
|
|
||||||
obj.add_tag(YARD::Tags::Tag.new(:return,
|
|
||||||
"a new instance of #{namespace.name}", namespace.name.to_s))
|
|
||||||
end
|
|
||||||
elsif mscope == :class && obj.docstring.blank? && %w(inherited included
|
|
||||||
extended method_added method_removed method_undefined).include?(meth)
|
|
||||||
obj.add_tag(YARD::Tags::Tag.new(:private, nil))
|
|
||||||
elsif meth.to_s =~ /\?$/
|
|
||||||
if obj.tag(:return) && (obj.tag(:return).types || []).empty?
|
|
||||||
obj.tag(:return).types = ['Boolean']
|
|
||||||
elsif obj.tag(:return).nil?
|
|
||||||
unless obj.tags(:overload).any? {|overload| overload.tag(:return) }
|
|
||||||
obj.add_tag(YARD::Tags::Tag.new(:return, "", "Boolean"))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if obj.has_tag?(:option)
|
|
||||||
# create the options parameter if its missing
|
|
||||||
obj.tags(:option).each do |option|
|
|
||||||
expected_param = option.name
|
|
||||||
unless obj.tags(:param).find {|x| x.name == expected_param }
|
|
||||||
new_tag = YARD::Tags::Tag.new(:param, "a customizable set of options", "Hash", expected_param)
|
|
||||||
obj.add_tag(new_tag)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
info = obj.attr_info
|
|
||||||
if info
|
|
||||||
if meth.to_s =~ /=$/ # writer
|
|
||||||
info[:write] = obj if info[:read]
|
|
||||||
elsif info[:write]
|
|
||||||
info[:read] = obj
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
parse_block(:owner => obj) # mainly for yield/exceptions
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# (see Ruby::MixinHandler)
|
|
||||||
class YARD::Handlers::Ruby::Legacy::MixinHandler < YARD::Handlers::Ruby::Legacy::Base
|
|
||||||
handles(/\Ainclude(\s|\()/)
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
errors = []
|
|
||||||
statement.tokens[1..-1].to_s.split(/\s*,\s*/).reverse.each do |mixin|
|
|
||||||
mixin = mixin.strip
|
|
||||||
begin
|
|
||||||
process_mixin(mixin)
|
|
||||||
rescue YARD::Parser::UndocumentableError => err
|
|
||||||
errors << err.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
unless errors.empty?
|
|
||||||
msg = errors.size == 1 ? ": #{errors[0]}" : "s: #{errors.join(", ")}"
|
|
||||||
raise YARD::Parser::UndocumentableError, "mixin#{msg} for class #{namespace.path}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def process_mixin(mixin)
|
|
||||||
mixmatch = mixin[/\A(#{NAMESPACEMATCH})/, 1]
|
|
||||||
raise YARD::Parser::UndocumentableError unless mixmatch
|
|
||||||
|
|
||||||
case obj = Proxy.new(namespace, mixmatch)
|
|
||||||
when ConstantObject # If a constant is included, use its value as the real object
|
|
||||||
obj = Proxy.new(namespace, obj.value, :module)
|
|
||||||
else
|
|
||||||
obj = Proxy.new(namespace, mixmatch, :module)
|
|
||||||
end
|
|
||||||
|
|
||||||
namespace.mixins(scope).unshift(obj) unless namespace.mixins(scope).include?(obj)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# (see Ruby::ModuleFunctionHandler)
|
|
||||||
class YARD::Handlers::Ruby::Legacy::ModuleFunctionHandler < YARD::Handlers::Ruby::Legacy::Base
|
|
||||||
handles(/\A(module_function)(\s|\(|$)/)
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
if statement.tokens.size == 1
|
|
||||||
self.scope = :module
|
|
||||||
else
|
|
||||||
tokval_list(statement.tokens[2..-1], :attr).each do |name|
|
|
||||||
instance_method = MethodObject.new(namespace, name)
|
|
||||||
class_method = MethodObject.new(namespace, name, :module)
|
|
||||||
instance_method.copy_to(class_method)
|
|
||||||
class_method.visibility = :public
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# (see Ruby::ModuleHandler)
|
|
||||||
class YARD::Handlers::Ruby::Legacy::ModuleHandler < YARD::Handlers::Ruby::Legacy::Base
|
|
||||||
handles TkMODULE
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
modname = statement.tokens.to_s[/^module\s+(#{NAMESPACEMATCH})/, 1]
|
|
||||||
mod = register ModuleObject.new(namespace, modname)
|
|
||||||
parse_block(:namespace => mod)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# (see Ruby::PrivateClassMethodHandler)
|
|
||||||
class YARD::Handlers::Ruby::Legacy::PrivateClassMethodHandler < YARD::Handlers::Ruby::Legacy::Base
|
|
||||||
handles(/\Aprivate_class_method(\s|\(|$)/)
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
tokval_list(statement.tokens[2..-1], :attr).each do |name|
|
|
||||||
privatize_class_method name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def privatize_class_method(name)
|
|
||||||
method = Proxy.new(namespace, name)
|
|
||||||
ensure_loaded!(method)
|
|
||||||
method.visibility = :private
|
|
||||||
rescue YARD::Handlers::NamespaceMissingError
|
|
||||||
raise UndocumentableError, "private visibility set on unrecognized method: #{name}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# (see Ruby::PrivateConstantHandler)
|
|
||||||
class YARD::Handlers::Ruby::Legacy::PrivateConstantHandler < YARD::Handlers::Ruby::Legacy::Base
|
|
||||||
handles(/\Aprivate_constant(\s|\(|$)/)
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
tokval_list(statement.tokens[2..-1], :attr, TkCONSTANT).each do |name|
|
|
||||||
privatize_constant name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def privatize_constant(name)
|
|
||||||
const = Proxy.new(namespace, name)
|
|
||||||
ensure_loaded!(const)
|
|
||||||
const.visibility = :private
|
|
||||||
rescue NamespaceMissingError
|
|
||||||
raise UndocumentableError, "private visibility set on unrecognized constant: #{name}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# (see Ruby::VisibilityHandler)
|
|
||||||
class YARD::Handlers::Ruby::Legacy::VisibilityHandler < YARD::Handlers::Ruby::Legacy::Base
|
|
||||||
handles(/\A(protected|private|public)(\s|\(|$)/)
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
vis = statement.tokens.first.text
|
|
||||||
if statement.tokens.size == 1
|
|
||||||
self.visibility = vis
|
|
||||||
else
|
|
||||||
tokval_list(statement.tokens[2..-1], :attr).each do |name|
|
|
||||||
MethodObject.new(namespace, name, scope) {|o| o.visibility = vis }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# (see Ruby::YieldHandler)
|
|
||||||
class YARD::Handlers::Ruby::Legacy::YieldHandler < YARD::Handlers::Ruby::Legacy::Base
|
|
||||||
handles TkYIELD
|
|
||||||
|
|
||||||
process do
|
|
||||||
return unless owner.is_a?(MethodObject) # Only methods yield
|
|
||||||
return if owner.has_tag? :yield # Don't override yield tags
|
|
||||||
return if owner.has_tag? :yieldparam # Same thing.
|
|
||||||
|
|
||||||
yieldtag = YARD::Tags::Tag.new(:yield, "", [])
|
|
||||||
tokval_list(statement.tokens[2..-1], Token).each do |item|
|
|
||||||
item = item.inspect unless item.is_a?(String)
|
|
||||||
if item == "self"
|
|
||||||
yieldtag.types << '_self'
|
|
||||||
owner.add_tag YARD::Tags::Tag.new(:yieldparam,
|
|
||||||
"the object that the method was called on", owner.namespace.path, '_self')
|
|
||||||
elsif item == "super"
|
|
||||||
yieldtag.types << '_super'
|
|
||||||
owner.add_tag YARD::Tags::Tag.new(:yieldparam,
|
|
||||||
"the result of the method from the superclass", nil, '_super')
|
|
||||||
else
|
|
||||||
yieldtag.types << item
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
owner.add_tag(yieldtag) unless yieldtag.types.empty?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Handles a conditional inside a method
|
|
||||||
class YARD::Handlers::Ruby::MethodConditionHandler < YARD::Handlers::Ruby::Base
|
|
||||||
handles :if_mod, :unless_mod
|
|
||||||
|
|
||||||
process do
|
|
||||||
parse_block(statement.then_block, :owner => owner)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Handles a method definition
|
|
||||||
class YARD::Handlers::Ruby::MethodHandler < YARD::Handlers::Ruby::Base
|
|
||||||
include YARD::Handlers::Common::MethodHandler
|
|
||||||
|
|
||||||
handles :def, :defs
|
|
||||||
|
|
||||||
process do
|
|
||||||
meth = statement.method_name(true).to_s
|
|
||||||
args = format_args
|
|
||||||
blk = statement.block
|
|
||||||
nobj = namespace
|
|
||||||
mscope = scope
|
|
||||||
if statement.type == :defs
|
|
||||||
if statement[0][0].type == :ident
|
|
||||||
raise YARD::Parser::UndocumentableError, 'method defined on object instance'
|
|
||||||
end
|
|
||||||
nobj = P(namespace, statement[0].source) if statement[0][0].type == :const
|
|
||||||
mscope = :class
|
|
||||||
end
|
|
||||||
|
|
||||||
nobj = P(namespace, nobj.value) while nobj.type == :constant
|
|
||||||
obj = register MethodObject.new(nobj, meth, mscope) do |o|
|
|
||||||
o.explicit = true
|
|
||||||
o.parameters = args
|
|
||||||
end
|
|
||||||
|
|
||||||
# delete any aliases referencing old method
|
|
||||||
nobj.aliases.each do |aobj, name|
|
|
||||||
next unless name == obj.name
|
|
||||||
nobj.aliases.delete(aobj)
|
|
||||||
end if nobj.is_a?(NamespaceObject)
|
|
||||||
|
|
||||||
if obj.constructor?
|
|
||||||
unless obj.has_tag?(:return)
|
|
||||||
obj.add_tag(YARD::Tags::Tag.new(:return,
|
|
||||||
"a new instance of #{namespace.name}", namespace.name.to_s))
|
|
||||||
end
|
|
||||||
elsif mscope == :class && obj.docstring.blank? && %w(inherited included
|
|
||||||
extended method_added method_removed method_undefined).include?(meth)
|
|
||||||
obj.add_tag(YARD::Tags::Tag.new(:private, nil))
|
|
||||||
elsif meth.to_s =~ /\?$/
|
|
||||||
add_predicate_return_tag(obj)
|
|
||||||
end
|
|
||||||
|
|
||||||
if obj.has_tag?(:option)
|
|
||||||
# create the options parameter if its missing
|
|
||||||
obj.tags(:option).each do |option|
|
|
||||||
expected_param = option.name
|
|
||||||
unless obj.tags(:param).find {|x| x.name == expected_param }
|
|
||||||
new_tag = YARD::Tags::Tag.new(:param, "a customizable set of options", "Hash", expected_param)
|
|
||||||
obj.add_tag(new_tag)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
info = obj.attr_info
|
|
||||||
if info
|
|
||||||
if meth.to_s =~ /=$/ # writer
|
|
||||||
info[:write] = obj if info[:read]
|
|
||||||
elsif info[:write]
|
|
||||||
info[:read] = obj
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
parse_block(blk, :owner => obj) # mainly for yield/exceptions
|
|
||||||
end
|
|
||||||
|
|
||||||
def format_args
|
|
||||||
args = statement.parameters
|
|
||||||
|
|
||||||
params = []
|
|
||||||
|
|
||||||
if args.unnamed_required_params
|
|
||||||
params += args.unnamed_required_params.map {|a| [a.source, nil] }
|
|
||||||
end
|
|
||||||
|
|
||||||
if args.unnamed_optional_params
|
|
||||||
params += args.unnamed_optional_params.map do |a|
|
|
||||||
[a[0].source, a[1].source]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
params << ['*' + args.splat_param.source, nil] if args.splat_param
|
|
||||||
|
|
||||||
if args.unnamed_end_params
|
|
||||||
params += args.unnamed_end_params.map {|a| [a.source, nil] }
|
|
||||||
end
|
|
||||||
|
|
||||||
if args.named_params
|
|
||||||
params += args.named_params.map do |a|
|
|
||||||
[a[0].source, a[1] ? a[1].source : nil]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if args.double_splat_param
|
|
||||||
params << ['**' + args.double_splat_param.source, nil]
|
|
||||||
end
|
|
||||||
|
|
||||||
params << ['&' + args.block_param.source, nil] if args.block_param
|
|
||||||
|
|
||||||
params
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Handles the 'include' statement to mixin a module in the instance scope
|
|
||||||
class YARD::Handlers::Ruby::MixinHandler < YARD::Handlers::Ruby::Base
|
|
||||||
handles method_call(:include)
|
|
||||||
handles method_call(:prepend)
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
errors = []
|
|
||||||
statement.parameters(false).reverse.each do |mixin|
|
|
||||||
begin
|
|
||||||
process_mixin(mixin)
|
|
||||||
rescue YARD::Parser::UndocumentableError => err
|
|
||||||
errors << err.message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
unless errors.empty?
|
|
||||||
msg = errors.size == 1 ? ": #{errors[0]}" : "s: #{errors.join(", ")}"
|
|
||||||
raise YARD::Parser::UndocumentableError, "mixin#{msg} for class #{namespace.path}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
def process_mixin(mixin)
|
|
||||||
raise YARD::Parser::UndocumentableError unless mixin.ref?
|
|
||||||
raise YARD::Parser::UndocumentableError if mixin.first.type == :ident
|
|
||||||
|
|
||||||
case obj = Proxy.new(namespace, mixin.source)
|
|
||||||
when ConstantObject # If a constant is included, use its value as the real object
|
|
||||||
obj = Proxy.new(namespace, obj.value, :module)
|
|
||||||
else
|
|
||||||
obj = Proxy.new(namespace, mixin.source, :module)
|
|
||||||
end
|
|
||||||
|
|
||||||
rec = recipient(mixin)
|
|
||||||
return if rec.nil? || rec.mixins(scope).include?(obj)
|
|
||||||
|
|
||||||
shift = statement.method_name(true) == :include ? :unshift : :push
|
|
||||||
rec.mixins(scope).send(shift, obj)
|
|
||||||
end
|
|
||||||
|
|
||||||
def recipient(mixin)
|
|
||||||
if statement[0].type == :var_ref && statement[0][0] != s(:kw, "self")
|
|
||||||
statement[0][0].type == :const ?
|
|
||||||
Proxy.new(namespace, statement.namespace.source) :
|
|
||||||
nil
|
|
||||||
else
|
|
||||||
namespace
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Handles module_function calls to turn methods into public class methods.
|
|
||||||
# Also creates a private instance copy of the method.
|
|
||||||
class YARD::Handlers::Ruby::ModuleFunctionHandler < YARD::Handlers::Ruby::Base
|
|
||||||
handles method_call(:module_function)
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
return if statement.jump(:ident) == statement
|
|
||||||
case statement.type
|
|
||||||
when :var_ref, :vcall
|
|
||||||
self.scope = :module
|
|
||||||
when :fcall, :command
|
|
||||||
statement[1].traverse do |node|
|
|
||||||
case node.type
|
|
||||||
when :symbol; name = node.first.source
|
|
||||||
when :string_content; name = node.source
|
|
||||||
else next
|
|
||||||
end
|
|
||||||
instance_method = MethodObject.new(namespace, name)
|
|
||||||
class_method = MethodObject.new(namespace, name, :module)
|
|
||||||
instance_method.copy_to(class_method)
|
|
||||||
class_method.visibility = :public
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Handles the declaration of a module
|
|
||||||
class YARD::Handlers::Ruby::ModuleHandler < YARD::Handlers::Ruby::Base
|
|
||||||
handles :module
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
modname = statement[0].source
|
|
||||||
mod = register ModuleObject.new(namespace, modname)
|
|
||||||
parse_block(statement[1], :namespace => mod)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
# Sets visibility of a class method to private.
|
|
||||||
class YARD::Handlers::Ruby::PrivateClassMethodHandler < YARD::Handlers::Ruby::Base
|
|
||||||
include YARD::Handlers::Ruby::DecoratorHandlerMethods
|
|
||||||
|
|
||||||
handles method_call(:private_class_method)
|
|
||||||
namespace_only
|
|
||||||
|
|
||||||
process do
|
|
||||||
process_decorator :scope => :class do |method|
|
|
||||||
method.visibility = :private if method.respond_to? :visibility=
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user