lib/yard/parser/source_parser.rb in yard-0.5.5 vs lib/yard/parser/source_parser.rb in yard-0.5.6
- old
+ new
@@ -20,26 +20,23 @@
# Responsible for parsing a source file into the namespace. Parsing
# also invokes handlers to process the parsed statements and generate
# any code objects that may be recognized.
#
+ # == Custom Parsers
+ # SourceParser allows custom parsers to be registered and called when
+ # a certain filetype is recognized. To register a parser and hook it
+ # up to a set of file extensions, call {register_parser_type}
+ #
+ # @see register_parser_type
# @see Handlers::Base
# @see CodeObjects::Base
class SourceParser
class << self
- # The default parser type to use for blocks of code. Change this
- # attribute to apply a new parser type for all newly parsed
- # blocks of code.
- #
- # @return [Symbol] the parser type
- attr_accessor :parser_type
- undef parser_type=
+ # @return [Symbol] the default parser type (defaults to :ruby)
+ attr_reader :parser_type
- # Sets the parser and makes sure it's a valid type
- #
- # @param [Symbol] value the new parser type
- # @return [void]
def parser_type=(value)
@parser_type = validated_parser_type(value)
end
# Parses a path or set of paths
@@ -84,19 +81,62 @@
# @return [Array] a list of tokens
def tokenize(content, ptype = parser_type)
new(ptype).tokenize(content)
end
+ # Registers a new parser type.
+ #
+ # @example Registering a parser for "java" files
+ # SourceParser.register_parser_type :java, JavaParser, 'java'
+ # @param [Symbol] type a symbolic name for the parser type
+ # @param [Base] parser_klass a class that implements parsing and tokenization
+ # @param [Array<String>, String, Regexp] extensions a list of extensions or a
+ # regex to match against the file extension
+ # @return [void]
+ # @see Parser::Base
+ def register_parser_type(type, parser_klass, extensions = nil)
+ unless Base > parser_klass
+ raise ArgumentError, "expecting parser_klass to be a subclass of YARD::Parser::Base"
+ end
+ parser_type_extensions[type.to_sym] = extensions if extensions
+ parser_types[type.to_sym] = parser_klass
+ end
+
+ # @return [Hash{Symbol=>Object}] a list of registered parser types
+ def parser_types; @@parser_types ||= {} end
+ def parser_types=(value) @@parser_types = value end
+
+ # @return [Hash] a list of registered parser type extensions
+ def parser_type_extensions; @@parser_type_extensions ||= {} end
+ def parser_type_extensions=(value) @@parser_type_extensions = value end
+
+ # Finds a parser type that is registered for the extension. If no
+ # type is found, the default Ruby type is returned.
+ #
+ # @return [Symbol] the parser type to be used for the extension
+ def parser_type_for_extension(extension)
+ type = parser_type_extensions.find do |t, exts|
+ if exts.is_a?(Array)
+ exts.include?(extension)
+ elsif exts.is_a?(String)
+ exts == extension
+ elsif exts.is_a?(Regexp)
+ extension =~ exts
+ end
+ end
+ validated_parser_type(type ? type.first : :ruby)
+ end
+
# Returns the validated parser type. Basically, enforces that :ruby
# type is never set from Ruby 1.8
#
# @param [Symbol] type the parser type to set
# @return [Symbol] the validated parser type
def validated_parser_type(type)
RUBY18 && type == :ruby ? :ruby18 : type
end
-
+
private
# Parses a list of files in a queue. If a {LoadOrderError} is caught,
# the file is moved to the back of the queue with a Continuation object
# that can continue processing the file.
@@ -122,10 +162,14 @@
end
end
self.parser_type = :ruby
+ register_parser_type :ruby, Ruby::RubyParser if RUBY19
+ register_parser_type :ruby18, Ruby::Legacy::RubyParser
+ register_parser_type :c, CParser, ['c', 'cc', 'cxx', 'cpp']
+
# The filename being parsed by the parser.
attr_reader :file
# The parser type associated with the parser instance. This should
# be set by the {#initialize constructor}.
@@ -161,11 +205,12 @@
self.parser_type = parser_type_for_filename(file)
else
content = content.read if content.respond_to? :read
end
- @parser = parse_statements(content)
+ @parser = parser_class.new(content, file)
+ @parser.parse
post_process
@parser
rescue ArgumentError, NotImplementedError => e
log.warn("Cannot parse `#{file}': #{e.message}")
rescue ParserSyntaxError => e
@@ -175,20 +220,12 @@
# Tokenizes but does not parse the block of code using the current {#parser_type}
#
# @param [String] content the block of code to tokenize
# @return [Array] a list of tokens
def tokenize(content)
- case parser_type
- when :c
- raise NotImplementedError, "no support for C/C++ files"
- when :ruby18
- Ruby::Legacy::TokenList.new(content)
- when :ruby
- Ruby::RubyParser.parse(content).tokens
- else
- raise ArgumentError, "invalid parser type or unrecognized file"
- end
+ @parser = parser_class.new(content, file)
+ @parser.tokenize
end
private
# Searches for encoding line and forces encoding
@@ -203,12 +240,13 @@
# Runs a {Handlers::Processor} object to post process the parsed statements.
# @return [void]
def post_process
return unless @parser.respond_to? :enumerator
+ return unless enumerator = @parser.enumerator
post = Handlers::Processor.new(@file, @load_order_errors, @parser_type)
- post.process(@parser.enumerator)
+ post.process(enumerator)
end
def parser_type=(value)
@parser_type = self.class.validated_parser_type(value)
end
@@ -216,32 +254,18 @@
# Guesses the parser type to use depending on the file extension.
#
# @param [String] filename the filename to use to guess the parser type
# @return [Symbol] a parser type that matches the filename
def parser_type_for_filename(filename)
- case (File.extname(filename)[1..-1] || "").downcase
- when "c", "cpp", "cxx"
- :c
- else # when "rb", "rbx", "erb"
- parser_type == :ruby18 ? :ruby18 : :ruby
- end
+ ext = (File.extname(filename)[1..-1] || "").downcase
+ type = self.class.parser_type_for_extension(ext)
+ parser_type == :ruby18 && type == :ruby ? :ruby18 : type
end
- # Selects a parser to use to parse +content+.
- #
- # @param [String] content the block of code to parse
- # @return the resulting parser object
- def parse_statements(content)
- case parser_type
- when :c
- CParser.new(content, file).parse
- when :ruby18
- Ruby::Legacy::StatementList.new(content)
- when :ruby
- Ruby::RubyParser.parse(content, file)
- else
- raise ArgumentError, "invalid parser type or unrecognized file"
- end
+ def parser_class
+ klass = self.class.parser_types[parser_type]
+ raise ArgumentError, "invalid parser type '#{parser_type}' or unrecognized file", caller[1..-1] if !klass
+ klass
end
end
end
end