| |
| require 'rdoc/parser/ruby' |
| require 'rdoc/known_classes' |
| |
| ## |
| # RDoc::Parser::SWIG attempts to parse SWIG extension files. It looks for |
| # the standard patterns that you find in extensions: <tt>%extend, %define, |
| # %rename, %alias</tt> and so on. It tries to find the corresponding |
| # C source for the methods and extract comments, but if we fail |
| # we don't worry too much. |
| # |
| # The comments associated with a Ruby method are extracted from the SWIG |
| # comment block associated with the routine that _implements_ that |
| # method |
| # |
| # The comment blocks may include special directives: |
| # |
| # [Document-class: +name+] |
| # Documentation for the named class. |
| # |
| # [Document-module: +name+] |
| # Documentation for the named module. |
| # |
| # [Document-const: +name+] |
| # Documentation for the named +rb_define_const+. |
| # |
| # [Document-global: +name+] |
| # Documentation for the named +rb_define_global_const+ |
| # |
| # [Document-variable: +name+] |
| # Documentation for the named +rb_define_variable+ |
| # |
| # [Document-method: +method_name+] |
| # Documentation for the named method. Use this when the method name is |
| # unambiguous. |
| # |
| # [Document-method: <tt>ClassName::method_name<tt>] |
| # Documentation for a singleton method in the given class. Use this when |
| # the method name alone is ambiguous. |
| # |
| # [Document-method: <tt>ClassName#method_name<tt>] |
| # Documentation for a instance method in the given class. Use this when the |
| # method name alone is ambiguous. |
| # |
| # [Document-attr: +name+] |
| # Documentation for the named attribute. |
| # |
| # [call-seq: <i>text up to an empty line</i>] |
| # Because C source doesn't give descriptive names to Ruby-level parameters, |
| # you need to document the calling sequence explicitly |
| # |
| # |
| # As an example, we might have an extension that defines multiple classes |
| # in its Init_xxx method. We could document them using |
| # |
| # /* |
| # * Document-class: MyClass |
| # * |
| # * Encapsulate the writing and reading of the configuration |
| # * file. ... |
| # */ |
| # |
| # /* |
| # * Document-method: read_value |
| # * |
| # * call-seq: |
| # * cfg.read_value(key) -> value |
| # * cfg.read_value(key} { |key| } -> value |
| # * |
| # * Return the value corresponding to +key+ from the configuration. |
| # * In the second form, if the key isn't found, invoke the |
| # * block and return its value. |
| # */ |
| |
| class RDoc::Parser::SWIG < RDoc::Parser |
| |
| parse_files_matching(/\.i\z/) |
| |
| include RDoc::Text |
| |
| ## |
| # SWIG file the parser is parsing |
| |
| attr_accessor :content |
| |
| |
| ## |
| # Maps C type names to names of ruby classes (andsingleton classes) |
| |
| attr_reader :known_classes |
| |
| ## |
| # Maps C type names to names of ruby singleton classes |
| |
| attr_reader :singleton_classes |
| |
| ## |
| # Resets cross-file state. Call when parsing different projects that need |
| # separate documentation. |
| |
| def self.reset |
| @@enclosure_classes = {} |
| @@known_bodies = {} |
| end |
| |
| reset |
| |
| ## |
| # Prepare to parse a SWIG file |
| |
| def initialize(top_level, file_name, content, options, stats) |
| super |
| |
| @known_classes = RDoc::KNOWN_CLASSES.dup |
| @content = handle_tab_width handle_ifdefs_in(@content) |
| @renames = {} # maps old_name => [ new_name, args ] |
| @aliases = {} # maps name => [ alias_name, args ] |
| @classes = {} |
| @singleton_classes = {} |
| @file_dir = File.dirname(@file_name) |
| end |
| |
| ## |
| # Scans #content for %alias |
| |
| def do_aliases |
| @content.scan(/%alias\s+(\w+)\s+"([^"]+)";/) do |old_name, new_name| # " |
| # class_name = @known_classes[var_name] |
| # |
| # unless class_name then |
| # warn "Enclosing class/module %p for alias %s %s not known" % [ |
| # var_name, new_name, old_name] |
| # next |
| # end |
| # |
| # class_obj = find_class var_name, class_name |
| |
| al = RDoc::Alias.new '', old_name, new_name, '' |
| # al.singleton = @singleton_classes.key? var_name |
| |
| # comment = find_alias_comment var_name, new_name, old_name |
| # comment = strip_stars comment |
| # al.comment = comment |
| |
| al.record_location @top_level |
| |
| # class_obj.add_alias al |
| @stats.add_alias al |
| end |
| end |
| |
| ## |
| # Scans #content for rb_attr and rb_define_attr |
| |
| def do_attrs |
| @content.scan(/rb_attr\s*\( |
| \s*(\w+), |
| \s*([\w"()]+), |
| \s*([01]), |
| \s*([01]), |
| \s*\w+\);/xm) do |var_name, attr_name, read, write| |
| handle_attr var_name, attr_name, read, write |
| end |
| |
| @content.scan(%r%rb_define_attr\( |
| \s*([\w\.]+), |
| \s*"([^"]+)", |
| \s*(\d+), |
| \s*(\d+)\s*\); |
| %xm) do |var_name, attr_name, read, write| |
| handle_attr var_name, attr_name, read, write |
| end |
| end |
| |
| ## |
| # Scans #content for %rename |
| |
| def do_renames |
| @content.scan(/\s*%rename\s*\(\s*"?([\w=]+)"?\s*\)\s*(\w+)\s*\(?([\w,\*\s]+)?\s*\)?;/) do |
| |new_name, old_name, args| |
| @renames[old_name] = [new_name, args] |
| end |
| end |
| |
| ## |
| # Scans #content for first(!) %module |
| |
| def do_modules |
| @content.scan(/%module\s+(\w+)/) do |match_data| |
| module_name = match_data[0] |
| @module = handle_class_module(nil, "module", module_name, @top_level) |
| break # first %module only |
| end |
| @@module ||= @module |
| @module = @@module |
| end |
| |
| ## |
| # Scans #content for %extend |
| |
| def do_classes content |
| found = false |
| # puts "do_classes #{content.inspect}" |
| content.scan(/%extend\s+(\w+)(.*)/m) do |class_name, ext_content| |
| real_name = (@renames[class_name]) ? @renames[class_name][0] : nil |
| klass = handle_class_module(class_name, "class", real_name, @module) |
| # now check if we have multiple %extend, the regexp above is greedy and will match all of them |
| while ext_content =~ /%extend/ |
| do_classes $& + $' # ' add the %extend back in |
| ext_content = $` # real content is everything before the embedded %extend |
| end |
| # puts "%extend #{class_name}: #{ext_content.inspect}" |
| do_methods klass, class_name, ext_content |
| found = true |
| end |
| found |
| end |
| |
| ## |
| # Scans #content for #define and %constant |
| |
| def do_constants |
| # %constant <type>[*] <name> = <c-constant>; |
| |
| @content.scan(/(\/\*.*?\*\/)?\s*%constant\s+(\w+)([\s\*]+)(\w+)\s*=\s*(\w+);/) do |comment, type, pointer, const_name, definition| |
| # puts "\nConst #{const_name} : #{comment.inspect}" |
| handle_constants comment, const_name, definition |
| end |
| end |
| |
| ## |
| # Scans #content for %mixin |
| |
| def do_includes |
| @content.scan(/%mixin/) do |c,m| |
| if cls = @classes[c] |
| m = @known_classes[m] || m |
| incl = cls.add_include RDoc::Include.new(m, "") |
| incl.record_location @top_level |
| end |
| end |
| end |
| |
| ## |
| # Scans content for methods |
| # klass is a RDoc::NormalClass |
| # name is the 'C' (type) name |
| # content is what's enclosed in %extend <name> { ... } |
| |
| def do_methods klass, name, content |
| # Find class constructor as 'new' |
| content.scan(/^\s+#{name}\s*\(([\w\,\*\s]*)\)\s*\{/m) do |args| |
| handle_method("method", klass.name, "initialize", name, (args.to_s.split(",")||[]).size, content) |
| end |
| content.scan(%r{static\s+(const\s+)?(\w+)([\s\*]+)(\w+)\s*\(([\w\,\*\s]*)\)\s*;}) do |
| |const,type,pointer,meth_name,args| |
| handle_method("method", klass.name, meth_name, nil, (args.split(",")||[]).size, content) |
| end |
| content.scan(/(const\s+)?(\w+) # <const>? <type> |
| ([ \t\*]+) # <pointer>? |
| (\w+) # <meth> |
| \s*\(([\w\,\*\s]*)\) # args |
| \s*\{/xm) do # function def start |
| |const,type,pointer,meth_name,args| |
| # STDERR.puts "do_methods klass #{klass}, name #{name}: const #{const.inspect}, type #{type.inspect}, pointer #{pointer.inspect}, meth_name #{meth_name.inspect}, args #{args.inspect}" |
| next unless meth_name |
| next if meth_name =~ /~/ |
| type = "string" if type =~ /char/ && pointer =~ /\*/ |
| handle_method("method", klass.name, meth_name, nil, (args.split(",")||[]).size, content) |
| end |
| end |
| |
| ## |
| # Finds the comment for an alias on +class_name+ from +new_name+ to |
| # +old_name+ |
| |
| def find_alias_comment class_name, new_name, old_name |
| content =~ %r%((?>/\*.*?\*/\s+)) |
| rb_define_alias\(\s*#{Regexp.escape class_name}\s*, |
| \s*"#{Regexp.escape new_name}"\s*, |
| \s*"#{Regexp.escape old_name}"\s*\);%xm |
| |
| $1 || '' |
| end |
| |
| ## |
| # Finds a comment for rb_define_attr, rb_attr or Document-attr. |
| # |
| # +var_name+ is the C class variable the attribute is defined on. |
| # +attr_name+ is the attribute's name. |
| # |
| # +read+ and +write+ are the read/write flags ('1' or '0'). Either both or |
| # neither must be provided. |
| |
| def find_attr_comment var_name, attr_name, read = nil, write = nil |
| attr_name = Regexp.escape attr_name |
| |
| rw = if read and write then |
| /\s*#{read}\s*,\s*#{write}\s*/xm |
| else |
| /.*?/m |
| end |
| |
| if @content =~ %r%((?>/\*.*?\*/\s+)) |
| rb_define_attr\((?:\s*#{var_name},)?\s* |
| "#{attr_name}"\s*, |
| #{rw}\)\s*;%xm then |
| $1 |
| elsif @content =~ %r%((?>/\*.*?\*/\s+)) |
| rb_attr\(\s*#{var_name}\s*, |
| \s*#{attr_name}\s*, |
| #{rw},.*?\)\s*;%xm then |
| $1 |
| elsif @content =~ %r%Document-attr:\s#{attr_name}\s*?\n |
| ((?>.*?\*/))%xm then |
| $1 |
| else |
| '' |
| end |
| end |
| |
| ## |
| # Find the C code corresponding to a Ruby method |
| |
| def find_body class_name, meth_name, meth_obj, file_content, quiet = false |
| # puts "\n\tfind_body #{meth_obj.c_function.inspect}[#{meth_name.inspect}]" if meth_obj.name == "new" |
| # puts "\n\tfind_body #{meth_name}[#{meth_obj.name}] ?" |
| # puts "\n\t#{file_content}" |
| case file_content |
| # when %r%(/\*.*\*/\s*#{meth_obj.c_function})%xm |
| when %r%((?>/\*.*?\*/\s+)?) |
| ((?:(?:static\s*)?(?:\s*const)?(?:\s*unsigned)?(?:\s*struct)?\s+)? |
| (VALUE|\w+)(\s+\*|\*\s+|\s+)#{meth_name} |
| \s*(\([^)]*\))([^;]|$))%xm, |
| %r%(/\*.*?\*/\s+) |
| #{meth_name} |
| \s*(\([^)]*\))([^;]|$)%xm then |
| # puts "\n found! [1:#{$1.inspect},2:#{$2.inspect},3:#{$3.inspect},#{$4.inspect},#{$5.inspect},#{$6.inspect}]" if meth_obj.name == "detail" |
| |
| comment = $1 |
| body = $2 |
| # type = $3 |
| return false if $3 == "define" # filter out SWIG_Exception |
| return false if $3.nil? && meth_name != "initialize" # constructor has no type |
| # ptr = $4 |
| offset = $~.offset(2).first |
| |
| remove_private_comments comment if comment |
| |
| # try to find the whole body |
| body = $& if /#{Regexp.escape body}[^(]*?\{.*?^\}/m =~ file_content |
| |
| # puts "\n\tfind_body #{meth_name} -> #{body}" |
| # The comment block may have been overridden with a 'Document-method' |
| # block. This happens in the interpreter when multiple methods are |
| # vectored through to the same C method but those methods are logically |
| # distinct (for example Kernel.hash and Kernel.object_id share the same |
| # implementation |
| |
| override_comment = find_override_comment class_name, meth_obj |
| comment = override_comment if override_comment |
| |
| find_modifiers comment, meth_obj if comment |
| |
| #meth_obj.params = params |
| meth_obj.start_collecting_tokens |
| begin |
| RDoc::const_get "RubyToken" |
| tk = RDoc::RubyToken::Token.new nil, 1, 1 |
| tk.set_text body |
| meth_obj.offset = offset |
| rescue NameError |
| # rdoc 2.5 |
| tk = { :line_no => 1, :char_no => 1, :text => body } |
| end |
| meth_obj.add_token tk |
| meth_obj.comment = strip_stars comment |
| meth_obj.line = file_content[0, offset].count("\n") + 1 |
| |
| body |
| when %r%((?>/\*.*?\*/\s*))^\s*(\#\s*define\s+#{meth_name}\s+(\w+))%m then |
| comment = $1 |
| body = $2 |
| offset = $~.offset(2).first |
| |
| find_body class_name, $3, meth_obj, file_content, true |
| find_modifiers comment, meth_obj |
| |
| meth_obj.start_collecting_tokens |
| begin |
| RDoc::const_get "RubyToken" |
| tk = RDoc::RubyToken::Token.new nil, 1, 1 |
| tk.set_text body |
| meth_obj.offset = offset |
| rescue NameError |
| # rdoc 2.5 |
| tk = { :line_no => 1, :char_no => 1, :text => body } |
| end |
| meth_obj.add_token tk |
| meth_obj.comment = strip_stars(comment) + meth_obj.comment.to_s |
| meth_obj.line = file_content[0, offset].count("\n") + 1 |
| |
| body |
| when %r%^\s*\#\s*define\s+#{meth_name}\s+(\w+)%m then |
| # with no comment we hope the aliased definition has it and use it's |
| # definition |
| |
| body = find_body(class_name, $1, meth_obj, file_content, true) |
| |
| return body if body |
| |
| warn "No definition for #{meth_name}" if @options.verbosity > 1 |
| false |
| else # No body, but might still have an override comment |
| comment = find_override_comment class_name, meth_obj |
| |
| if comment then |
| find_modifiers comment, meth_obj |
| meth_obj.comment = strip_stars comment |
| |
| '' |
| else |
| warn "No definition for #{meth_name}" if @options.verbosity > 1 |
| false |
| end |
| end |
| end |
| |
| ## |
| # Finds a RDoc::NormalClass or RDoc::NormalModule for +raw_name+ |
| |
| def find_class(raw_name, name) |
| unless @classes[raw_name] |
| if raw_name =~ /^rb_m/ |
| container = @top_level.add_module RDoc::NormalModule, name |
| else |
| container = @top_level.add_class RDoc::NormalClass, name |
| end |
| |
| container.record_location @top_level |
| @classes[raw_name] = container |
| end |
| @classes[raw_name] |
| end |
| |
| ## |
| # Look for class or module documentation above Init_+class_name+(void), |
| # in a Document-class +class_name+ (or module) comment or above an |
| # rb_define_class (or module). If a comment is supplied above a matching |
| # Init_ and a rb_define_class the Init_ comment is used. |
| # |
| # /* |
| # * This is a comment for Foo |
| # */ |
| # Init_Foo(void) { |
| # VALUE cFoo = rb_define_class("Foo", rb_cObject); |
| # } |
| # |
| # /* |
| # * Document-class: Foo |
| # * This is a comment for Foo |
| # */ |
| # Init_foo(void) { |
| # VALUE cFoo = rb_define_class("Foo", rb_cObject); |
| # } |
| # |
| # /* |
| # * This is a comment for Foo |
| # */ |
| # VALUE cFoo = rb_define_class("Foo", rb_cObject); |
| |
| def find_class_comment(class_name, class_mod) |
| comment = nil |
| |
| if @content =~ %r%Document-(?:class|module):\s+#{class_name}\s*? |
| (?:<\s+[:,\w]+)?\n((?>.*?\*/))%xm then |
| comment = $1 |
| elsif @content =~ %r% |
| ((?>/\*.*?\*/\s+)) |
| (static\s+)? |
| void\s+ |
| Init_#{class_name}\s*(?:_\(\s*)?\(\s*(?:void\s*)?\)%xmi then |
| comment = $1.sub(%r%Document-(?:class|module):\s+#{class_name}%, '') |
| end |
| |
| return unless comment |
| |
| comment = strip_stars( "/* " + comment ) |
| |
| comment = look_for_directives_in class_mod, comment |
| |
| class_mod.add_comment comment, @top_level |
| end |
| |
| ## |
| # Handles modifiers in +comment+ and updates +meth_obj+ as appropriate. |
| # |
| # If <tt>:nodoc:</tt> is found, documentation on +meth_obj+ is suppressed. |
| # |
| # If <tt>:yields:</tt> is followed by an argument list it is used for the |
| # #block_params of +meth_obj+. |
| # |
| # If the comment block contains a <tt>call-seq:</tt> section like: |
| # |
| # call-seq: |
| # ARGF.readlines(sep=$/) -> array |
| # ARGF.readlines(limit) -> array |
| # ARGF.readlines(sep, limit) -> array |
| # |
| # ARGF.to_a(sep=$/) -> array |
| # ARGF.to_a(limit) -> array |
| # ARGF.to_a(sep, limit) -> array |
| # |
| # it is used for the parameters of +meth_obj+. |
| |
| def find_modifiers comment, meth_obj |
| # we must handle situations like the above followed by an unindented first |
| # comment. The difficulty is to make sure not to match lines starting |
| # with ARGF at the same indent, but that are after the first description |
| # paragraph. |
| |
| if comment =~ /call-seq:(.*?(?:\S|\*\/?).*?)^\s*(?:\*\/?)?\s*$/m then |
| all_start, all_stop = $~.offset(0) |
| seq_start, seq_stop = $~.offset(1) |
| |
| # we get the following lines that start with the leading word at the |
| # same indent, even if they have blank lines before |
| if $1 =~ /(^\s*\*?\s*\n)+^(\s*\*?\s*\w+)/m then |
| leading = $2 # ' * ARGF' in the example above |
| re = %r% |
| \A( |
| (^\s*\*?\s*\n)+ |
| (^#{Regexp.escape leading}.*?\n)+ |
| )+ |
| ^\s*\*?\s*$ |
| %xm |
| if comment[seq_stop..-1] =~ re then |
| all_stop = seq_stop + $~.offset(0).last |
| seq_stop = seq_stop + $~.offset(1).last |
| end |
| end |
| |
| seq = comment[seq_start..seq_stop] |
| seq.gsub!(/^(\s*\*?\s*?)(\S|\n)/m, '\2') |
| comment.slice! all_start...all_stop |
| meth_obj.call_seq = seq |
| elsif comment.sub!(/\A\/\*\s*call-seq:(.*?)\*\/\Z/, '') then |
| meth_obj.call_seq = $1.strip |
| end |
| |
| look_for_directives_in meth_obj, comment |
| end |
| |
| ## |
| # Finds a <tt>Document-method</tt> override for +meth_obj+ on +class_name+ |
| |
| def find_override_comment class_name, meth_obj |
| name = Regexp.escape meth_obj.name |
| prefix = Regexp.escape meth_obj.name_prefix |
| |
| if @content =~ %r%Document-method:\s+#{class_name}#{prefix}#{name}\s*?\n((?>.*?\*/))%m then |
| $1 |
| elsif @content =~ %r%Document-method:\s#{name}\s*?\n((?>.*?\*/))%m then |
| $1 |
| end |
| end |
| |
| ## |
| # Creates a new RDoc::Attr +attr_name+ on class +var_name+ that is either |
| # +read+, +write+ or both |
| |
| def handle_attr(var_name, attr_name, read, write) |
| rw = '' |
| rw << 'R' if '1' == read |
| rw << 'W' if '1' == write |
| |
| class_name = @known_classes[var_name] |
| |
| return unless class_name |
| |
| class_obj = find_class var_name, class_name |
| |
| return unless class_obj |
| |
| comment = find_attr_comment var_name, attr_name |
| comment = strip_stars comment |
| |
| name = attr_name.gsub(/rb_intern\("([^"]+)"\)/, '\1') # " |
| |
| attr = RDoc::Attr.new '', name, rw, comment |
| |
| attr.record_location @top_level |
| class_obj.add_attribute attr |
| @stats.add_attribute attr |
| end |
| |
| ## |
| # Creates a new RDoc::NormalClass or RDoc::NormalModule based on +type+ |
| # named +class_name+ in +parent+ which was assigned to the C +var_name+. |
| |
| def handle_class_module(type_name, type, class_name, enclosure) |
| enclosure ||= @top_level |
| |
| if type == "class" then |
| full_name = if RDoc::ClassModule === enclosure then |
| enclosure.full_name + "::#{class_name}" |
| else |
| class_name |
| end |
| |
| if @content =~ %r%Document-class:\s+#{full_name}\s*<\s+([:,\w]+)% then |
| parent_name = $1 |
| end |
| |
| cm = enclosure.add_class RDoc::NormalClass, class_name, parent_name |
| else |
| cm = enclosure.add_module RDoc::NormalModule, class_name |
| end |
| |
| cm.record_location enclosure.top_level |
| |
| find_class_comment class_name, cm |
| |
| case cm |
| when RDoc::NormalClass |
| @stats.add_class cm |
| when RDoc::NormalModule |
| @stats.add_module cm |
| end |
| |
| @classes[type_name] = cm |
| @@enclosure_classes[class_name] = cm |
| @known_classes[class_name] = cm.full_name |
| cm |
| end |
| |
| ## |
| # Adds constants. |
| # |
| |
| def handle_constants(comment, const_name, definition) |
| class_obj = @module |
| |
| unless class_obj then |
| warn "Enclosing class/module #{const_name.inspect} not known" |
| return |
| end |
| |
| if comment |
| comment = strip_stars comment |
| comment = normalize_comment comment |
| else |
| comment = "" |
| end |
| |
| con = RDoc::Constant.new const_name, definition, comment |
| con.record_location @top_level |
| @stats.add_constant con |
| class_obj.add_constant con |
| end |
| |
| ## |
| # Removes #ifdefs that would otherwise confuse us |
| |
| def handle_ifdefs_in(body) |
| result = "" |
| loop do |
| body.match(/^#if ((\s*\|\|\s*)?defined\(SWIG([\w]+)\))+/) do |
| result << $` # copy everything before #if |
| after = $' #' |
| if $1 =~ /RUBY/ # if defined(SWIGRUBY) |
| raise "Unmatched #if SWIGRUBY\n#{after}" unless after.match(/^#(if|else|endif)/) |
| result << $` # copy everything between SWIGRUBY and #if/else/endif |
| case $1 |
| when "if" |
| result << handle_ifdefs_in($& + $') #' |
| when "else" |
| after = $' #' |
| raise "Unclosed #else" unless after.match(/^#endif/) |
| result << handle_ifdefs_in($') #' |
| when "endif" |
| result << handle_ifdefs_in($') #' |
| end |
| else # if defined(SWIG...) |
| # throw away everything between SIWG... and #if/else/endif |
| raise "Unmatched #if" unless after.match(/^#(if|else|endif)/) |
| case $1 |
| when "if" |
| result << handle_ifdefs_in($& + $') #' |
| when "else" |
| after = $' #' |
| raise "Unclosed #else" unless after.match(/^#endif/) |
| result << $` # copy everything between #else and #endif |
| result << handle_ifdefs_in($') #' |
| when "endif" |
| result << handle_ifdefs_in($') #' |
| end |
| end |
| end |
| break |
| end |
| result.empty? ? body : result |
| end |
| |
| ## |
| # Adds an RDoc::AnyMethod +meth_name+ defined on a class or module assigned |
| # to +var_name+. +type+ is the type of method definition function used. |
| # +singleton_method+ and +module_function+ create a singleton method. |
| |
| def handle_method(type, klass_name, meth_name, function, param_count, content = nil, |
| source_file = nil) |
| ruby_name = (@renames[meth_name]) ? @renames[meth_name][0] : meth_name |
| # STDERR.puts "\n\thandle_method #{type},#{klass_name},#{meth_name}[#{ruby_name}],#{function},#{param_count} args" # if meth_name == "initialize" |
| class_name = @known_classes[klass_name] |
| singleton = @singleton_classes.key? klass_name |
| |
| return unless class_name |
| |
| class_obj = find_class klass_name, class_name |
| # puts "\n\tclass_obj #{class_obj.inspect}" #if meth_name == "initialize" |
| |
| if class_obj then |
| if meth_name == "initialize" then |
| ruby_name = 'new' |
| singleton = true |
| type = 'method' # force public |
| end |
| |
| function ||= meth_name |
| meth_obj = RDoc::AnyMethod.new '', ruby_name |
| meth_obj.c_function = function |
| meth_obj.singleton = singleton |
| |
| p_count = Integer(param_count) rescue -1 |
| |
| if source_file then |
| file_name = File.join @file_dir, source_file |
| |
| if File.exist? file_name then |
| file_content = (@@known_bodies[file_name] ||= File.read(file_name)) |
| else |
| warn "unknown source #{source_file} for #{meth_name} in #{@file_name}" |
| end |
| else |
| file_content = content || @content |
| end |
| |
| body = find_body class_name, function, meth_obj, file_content |
| # puts "find_body #{class_name}##{function} -> #{body}" |
| if body and meth_obj.document_self then |
| meth_obj.params = if p_count < -1 then # -2 is Array |
| '(*args)' |
| elsif p_count == -1 then # argc, argv |
| rb_scan_args body |
| else |
| "(#{(1..p_count).map { |i| "p#{i}" }.join ', '})" |
| end |
| |
| |
| meth_obj.record_location @top_level |
| class_obj.add_method meth_obj |
| @stats.add_method meth_obj |
| meth_obj.visibility = :private if 'private_method' == type |
| end |
| end |
| end |
| |
| ## |
| # Registers a singleton class +sclass_var+ as a singleton of +class_var+ |
| |
| def handle_singleton sclass_var, class_var |
| class_name = @known_classes[class_var] |
| |
| @known_classes[sclass_var] = class_name |
| @singleton_classes[sclass_var] = class_name |
| end |
| |
| ## |
| # Normalizes tabs in +body+ |
| |
| def handle_tab_width(body) |
| if /\t/ =~ body |
| tab_width = @options.tab_width |
| body.split(/\n/).map do |line| |
| 1 while line.gsub!(/\t+/) do |
| ' ' * (tab_width * $&.length - $`.length % tab_width) |
| end && $~ |
| line |
| end.join "\n" |
| else |
| body |
| end |
| end |
| |
| ## |
| # Look for directives in a normal comment block: |
| # |
| # /* |
| # * :title: My Awesome Project |
| # */ |
| # |
| # This method modifies the +comment+ |
| |
| def look_for_directives_in context, comment |
| @preprocess.handle comment, context do |directive, param| |
| case directive |
| when 'main' then |
| @options.main_page = param |
| '' |
| when 'title' then |
| @options.default_title = param if @options.respond_to? :default_title= |
| '' |
| end |
| end |
| |
| comment |
| end |
| |
| ## |
| # Extracts parameters from the +method_body+ and returns a method |
| # parameter string. Follows 1.9.3dev's scan-arg-spec, see README.EXT |
| |
| def rb_scan_args method_body |
| method_body =~ /rb_scan_args\((.*?)\)/m |
| return '(*args)' unless $1 |
| |
| $1.split(/,/)[2] =~ /"(.*?)"/ # format argument |
| format = $1.split(//) |
| |
| lead = opt = trail = 0 |
| |
| if format.first =~ /\d/ then |
| lead = $&.to_i |
| format.shift |
| if format.first =~ /\d/ then |
| opt = $&.to_i |
| format.shift |
| if format.first =~ /\d/ then |
| trail = $&.to_i |
| format.shift |
| block_arg = true |
| end |
| end |
| end |
| |
| if format.first == '*' and not block_arg then |
| var = true |
| format.shift |
| if format.first =~ /\d/ then |
| trail = $&.to_i |
| format.shift |
| end |
| end |
| |
| if format.first == ':' then |
| hash = true |
| format.shift |
| end |
| |
| if format.first == '&' then |
| block = true |
| format.shift |
| end |
| |
| # if the format string is not empty there's a bug in the C code, ignore it |
| |
| args = [] |
| position = 1 |
| |
| (1...(position + lead)).each do |index| |
| args << "p#{index}" |
| end |
| |
| position += lead |
| |
| (position...(position + opt)).each do |index| |
| args << "p#{index} = v#{index}" |
| end |
| |
| position += opt |
| |
| if var then |
| args << '*args' |
| position += 1 |
| end |
| |
| (position...(position + trail)).each do |index| |
| args << "p#{index}" |
| end |
| |
| position += trail |
| |
| if hash then |
| args << "p#{position} = {}" |
| position += 1 |
| end |
| |
| args << '&block' if block |
| |
| "(#{args.join ', '})" |
| end |
| |
| ## |
| # Removes private comments from +comment+ |
| |
| def remove_private_comments(comment) |
| comment.gsub!(/\/?\*--\n(.*?)\/?\*\+\+/m, '') |
| comment.sub!(/\/?\*--\n.*/m, '') |
| end |
| |
| ## |
| # Extracts the classes, modules, methods, attributes, constants and aliases |
| # from a SWIG file and returns an RDoc::TopLevel for this file |
| |
| def scan |
| do_modules |
| do_renames |
| do_aliases |
| do_constants |
| have_classes = do_classes @content # -> do_methods |
| do_methods @module, @module.name, @content unless have_classes # file without %extend |
| # do_includes |
| # do_attrs |
| @top_level |
| end |
| |
| end |
| |