# -*- coding: us-ascii -*-# frozen_string_literal: true### A parser is simple a class that subclasses RDoc::Parser and implements #scan# to fill in an RDoc::TopLevel with parsed data.## The initialize method takes an RDoc::TopLevel to fill with parsed content,# the name of the file to be parsed, the content of the file, an RDoc::Options# object and an RDoc::Stats object to inform the user of parsed items. The# scan method is then called to parse the file and must return the# RDoc::TopLevel object. By calling super these items will be set for you.## In order to be used by RDoc the parser needs to register the file extensions# it can parse. Use ::parse_files_matching to register extensions.## require 'rdoc'## class RDoc::Parser::Xyz < RDoc::Parser# parse_files_matching /\.xyz$/## def initialize top_level, file_name, content, options, stats# super## # extra initialization if needed# end## def scan# # parse file and fill in @top_level# end# endclassRDoc::Parser@parsers=[]class<<self### An Array of arrays that maps file extension (or name) regular# expressions to parser classes that will parse matching filenames.## Use parse_files_matching to register a parser's file extensions.attr_reader:parsersend### The name of the file being parsedattr_reader:file_name### Alias an extension to another extension. After this call, files ending# "new_ext" will be parsed using the same parser as "old_ext"defself.alias_extension(old_ext,new_ext)old_ext=old_ext.sub(/^\.(.*)/,'\1')new_ext=new_ext.sub(/^\.(.*)/,'\1')parser=can_parse_by_name"xxx.#{old_ext}"returnfalseunlessparserRDoc::Parser.parsers.unshift[/\.#{new_ext}$/,parser]trueend### Determines if the file is a "binary" file which basically means it has# content that an RDoc parser shouldn't try to consume.defself.binary?(file)returnfalseiffile=~/\.(rdoc|txt)$/s=File.read(file,1024)orreturnfalsereturntrueifs[0,2]==Marshal.dump('')[0,2]ors.index("\x00")mode='r:utf-8'# default source encoding has been changed to utf-8s.sub!(/\A#!.*\n/,'')# assume shebang line isn't longer than 1024.encoding=s[/^\s*\#\s*(?:-\*-\s*)?(?:en)?coding:\s*([^\s;]+?)(?:-\*-|[\s;])/,1]mode="rb:#{encoding}"ifencodings=File.open(file,mode){|f|f.gets(nil,1024)}nots.valid_encoding?end### Checks if +file+ is a zip file in disguise. Signatures from# http://www.garykessler.net/library/file_sigs.htmldefself.zip?filezip_signature=File.readfile,4zip_signature=="PK\x03\x04"orzip_signature=="PK\x05\x06"orzip_signature=="PK\x07\x08"rescuefalseend### Return a parser that can handle a particular extensiondefself.can_parsefile_nameparser=can_parse_by_namefile_name# HACK Selenium hides a jar file using a .txt extensionreturnifparser==RDoc::Parser::Simpleandzip?file_nameparserend### Returns a parser that can handle the extension for +file_name+. This does# not depend upon the file being readable.defself.can_parse_by_namefile_name_,parser=RDoc::Parser.parsers.find{|regexp,|regexp=~file_name}# The default parser must not parse binary filesext_name=File.extnamefile_namereturnparserifext_name.empty?ifparser==RDoc::Parser::Simpleandext_name!~/txt|rdoc/thencasemode=check_modeline(file_name)whennil,'rdoc'then# continueelseRDoc::Parser.parsers.find{|_,p|returnpifmode.casecmp?(p.name[/\w+\z/])}returnnilendendparserrescueErrno::EACCESend### Returns the file type from the modeline in +file_name+defself.check_modelinefile_nameline=File.openfile_namedo|io|io.getsend/-\*-\s*(.*?\S)\s*-\*-/=~linereturnnilunlesstype=$1if/;/=~typethenreturnnilunless/(?:\s|\A)mode:\s*([^\s;]+)/i=~typetype=$1endreturnnilif/coding:/i=~typetype.downcaserescueArgumentErrorrescueEncoding::InvalidByteSequenceError# invalid byte sequenceend### Finds and instantiates the correct parser for the given +file_name+ and# +content+.defself.fortop_level,content,options,statsfile_name=top_level.absolute_namereturnifbinary?file_nameparser=use_markupcontentunlessparserthenparse_name=file_name# If no extension, look for shebangiffile_name!~/\.\w+$/&&content=~%r{\A#!(.+)}thenshebang=$1caseshebangwhen%r{env\s+ruby},%r{/ruby}parse_name='dummy.rb'endendparser=can_parseparse_nameendreturnunlessparsercontent=remove_modelinecontentparser.newtop_level,content,options,statsrescueSystemCallErrornilend### Record which file types this parser can understand.## It is ok to call this multiple times.defself.parse_files_matching(regexp)RDoc::Parser.parsers.unshift[regexp,self]end### Removes an emacs-style modeline from the first line of the documentdefself.remove_modelinecontentcontent.sub(/\A.*-\*-\s*(.*?\S)\s*-\*-.*\r?\n/,'')end### If there is a <tt>markup: parser_name</tt> comment at the front of the# file, use it to determine the parser. For example:## # markup: rdoc# # Class comment can go here## class C# end## The comment should appear as the first line of the +content+.## If the content contains a shebang or editor modeline the comment may# appear on the second or third line.## Any comment style may be used to hide the markup comment.defself.use_markupcontentmarkup=content.lines.first(3).grep(/markup:\s+(\w+)/){$1}.firstreturnunlessmarkup# TODO Ruby should be returned only when the filename is correctreturnRDoc::Parser::Rubyif%w[tomdoc markdown].include?markupmarkup=Regexp.escapemarkup_,selected=RDoc::Parser.parsers.finddo|_,parser|/^#{markup}$/i=~parser.name.sub(/.*:/,'')endselectedend### Creates a new Parser storing +top_level+, +file_name+, +content+,# +options+ and +stats+ in instance variables. In +@preprocess+ an# RDoc::Markup::PreProcess object is created which allows processing of# directives.definitializetop_level,content,options,stats@top_level=top_level@top_level.parser=self.class@store=@top_level.store@file_name=top_level.absolute_name@content=content@options=options@stats=stats@preprocess=RDoc::Markup::PreProcess.new@file_name,@options.rdoc_include@preprocess.options=@optionsendautoload:RubyTools,"#{__dir__}/parser/ruby_tools"autoload:Text,"#{__dir__}/parser/text"### Normalizes tabs in +body+defhandle_tab_width(body)if/\t/=~bodytab_width=@options.tab_widthbody.split(/\n/).mapdo|line|1whileline.gsub!(/\t+/)dob,e=$~.offset(0)' '*(tab_width*(e-b)-b%tab_width)endlineend.join"\n"elsebodyendendend# simple must come first in order to show up last in the parsers listrequire_relative'parser/simple'require_relative'parser/c'require_relative'parser/changelog'require_relative'parser/markdown'require_relative'parser/rd'require_relative'parser/ruby'