make doxygen comments in Obj-C/C/C++ files

清华大佬耗费三个月吐血整理的几百G的资源,免费分享!....>>>

#!/usr/bin/env ruby

#
# v2.0
# This script helps you make doxygen comments in Obj-C/C/C++ files in XCode
#
# Created by Fred McCann on 03/16/2010 - and Edwin.
# http://www.duckrowing.com
#
# Adapted for ThisService by Martin Pichlmair 03/29/2011
#
# Modified for Objectiv-c by Dake 07/22/2012
# http://glade.tk
#

module Duckrowing
  # TAILMATCH1 = /[\s*;.*]/
  # Convenience class to hold name and type information  
  class Argument
    def initialize(type = nil, name = nil)
      self.type = type
      self.name = name
    end
    
    def name
      @name
    end
    
    def name=(name)
      if name != nil
        name.gsub!(/^&/,'')
        name.gsub!(/^\*/,'')
        name.gsub!(/\[.*$/,'')
        name.gsub!(/,$/,'')
        name.gsub!(/;$/,'')
        name.gsub!(/^\s*/,'')
        name.gsub!(/\s*$/,'')
      end
      if name == '...'
        @name = 'vararg_list'
      else
        @name = name
      end
    end

    def type
      @type
    end

    def type=(type)
      if type != nil
        type.gsub!(/&$/,'')
        type.gsub!(/\s*\*$/,'')
        type.gsub!(/^\s*/,'')
        type.gsub!(/\s*$/,'')
      end
      @type = type
    end
  end
  
  # Base implementation of commenter
  class BaseCommenter
    # Creates a new commenter object
    def initialize(indent, code)
      @indent = indent
      @code = code
      @arguments = []
      @returns = false
    end
    
    # Creates an opening comment
    def start_comment(description = 'Description')
      #str = "/**\n"
      str = "#{@indent}/**\n"
      str += "#{@indent} *\t@brief\t<##{description}#>\n"
      str      
    end
    
    def arguments_comment
	  str = ''
      @arguments.each do |arg|
		if str == '' 
			str += "#{@indent} *\n"
		end
        str += "#{@indent} *\t@param \t#{arg.name} [IN|OUT] \t<##{arg.name} description#>\n"
      end
      str
    end
    
    def return_comment
      return '' if !@returns
      "#{@indent} *\n#{@indent} *\t@return\t<#return value description#>\n"
    end

    # Creates closing comment
    def end_comment()
      "#{@indent} */\n"
      #"#{@indent} */\n#{@indent}"
    end
    
    # Convenience method to detect multiline statements
    def is_multiline?
      @code =~ /\n/
    end
    
    # Adds inline comments to a comma delimited list
    def comment_list(list, base_indent='')
      commented_list = ""
      matches = list.scan(/\@p\S*/)
      if matches.size > 0
        # class contains @protected @private @public
        list.insert(matches[0].to_s.length+1, "\t")
      end

      # 获取类型标识符
      # prefix = list.scan(/^\s*(.+?)[\s\*+]/)
      # puts prefix
                
      # 分隔符
      seperator = ','

      ids = list.split(/,/)
      ids.each do |id|
        if matches.size > 0
          # class contains @protected...
          id.gsub!(/\040*$/, '')
          id.gsub!(/^\040*/, '')
        # enum、struct、define
        else 
          id.gsub!(/\s*$/, '')
          id.gsub!(/^\s*/, '')
          # seperator = ','
        end

        if id != "" 
          list_id = "#{id}"
          list_id += seperator if id != ids.last

          id.gsub!(/\=.*$/, '') 
          id.gsub!(/\[.*\]/, '')                   
          id.gsub!(/\s*$/, '')
          id.gsub!(/^\s*/, '')              
          id.gsub!(/;/, '') 
          id.gsub!(/\s*\:\s*\d+/,'') 

          doc_id = id.split(/\s/).last
          doc_id.gsub!(/\*/, '')

          commented_list += "\t" if id != ids.first && base_indent.length == 0      
          commented_list += "#{@indent}#{base_indent}#{list_id} /**< <##{doc_id} description#> */"
          commented_list += "\n" if id != ids.last 
        end
      end
      commented_list.chomp
    end
    
    # Parses a comma delimited list into an array of Argument objects
    def parse_c_style_argument_list(str)
      arguments = []
      str.split(/,/).each do |a|
        arg = Argument.new
        parts = a.split(/\s+/)
        arg.name = parts.last
        parts.delete_at(parts.size - 1)
        arg.type = parts.join(" ")
        @arguments << arg
      end            
    end

    # def parse_c_style_argument_list(str)
    #   arguments = []
    #   matches = str.scan(/\s*(.*?)\s+(\w+)?/)
    #   matches.each do |m|
    #     # puts m
    #     next if m.size != 2
    #     arg = Argument.new
    #     arg.type = m[0].to_s.gsub(TAILMATCH1, '')
    #     arg.name = m[1].to_s.gsub(TAILMATCH1, '')
    #     puts arg.type
    #     puts arg.name
    #     @arguments << arg
    #   end
    # end
    
    # Add Xcode selection markup to first editable field
    def select_first_field(str)
      # Add PBX selection to first field
      matches = str.scan(/\<\#.*\#\>/)
      if matches.size > 0
        first_field = matches[0].to_s
        # str.gsub!(/#{first_field}/, "%%%{PBXSelection}%%%#{first_field}%%%{PBXSelection}%%%")
        str.gsub!(/#{first_field}/, "#{first_field}")
      end
      
      str
    end
    
    # Returns a comment above the code and the original section of commented code
    def document
      str = start_comment()
      str += arguments_comment()
      str += return_comment()
      str += end_comment()
      str += "#{@code}"
      select_first_field(str)
    end    
  end
  
  class VariableCommenter < BaseCommenter
    # Adds a basic comment above individual variables and rewrites multiple
    # declaritions into an inline commented list
    def document
      matches = @code.scan(/;+\s*\\*/)
      if matches.size > 0
        comment_variable_list(@code)
      elsif @code.gsub(/\n/, ' ') =~ /^([^\{]+\,)/
        # "int i,j,k;"style
        commented_code = comment_list(@code, "")
        commented_code.sub!(/^\s*/,@indent);
        select_first_field("#{commented_code}")
      else
        super
      end
    end 

    def comment_variable_list(list)
      commented_list = ""
      ids = list.gsub(/^\s*/,'').gsub(/\s*$/,'').split(/;/)
      ids.each do |id|
        id.gsub!(/\s*$/, '')
        id.gsub!(/^\s*/, '')
        list_id = "#{id};"        
        commented_list += "#{comment_list(list_id, "")}\n"        
      end
      commented_list.chomp
    end 

  end

  class IfWhileForDoCommenter < BaseCommenter
    # Adds a basic comment above individual properties
  end

  class PropertyCommenter < BaseCommenter
    def document
      comment_property_list(@code)
    end 

    def comment_property_list(list)
      commented_list = ""
      ids = list.gsub(/^\s*/,'').gsub(/\s*$/,'').split(/\n/)
      ids.each do |id|
        id.gsub!(/\s*$/, '')
        id.gsub!(/^\s*/, '')
        # puts id

        if id =~ /^\@property\s*\([^<>]*\)/
          matches = id.scan(/(\@property\s*\(.*\))(.+;)/)
        else
          matches = id.scan(/(\@property)(.+;)/)
        end

        matches.each do |m|
          next if m.size != 2
          # puts m[0]
          # puts m[1]     
          list_id = m[1]        
          commented_list += m[0].gsub!(/\s*/, '')+" #{comment_list(list_id, '')}\n"
        end        
      end
      commented_list.chomp
    end 
  end

  # class IntrefaceCommenter < BaseCommenter
  # # Comments semicolon delimited list of interface members
  #   def comment_interface_list(list)
  #     commented_list = ""
  #     ids = list.gsub(/^\s*/,'').gsub(/\s*$/,'').split(/;/)
  #     ids.each do |id|
  #       id.gsub!(/\s*$/, '')
  #       id.gsub!(/^\s*/, '')
  #       list_id = "#{id};"        
  #       base_indent = "\t"
  #       commented_list += "#{comment_list(list_id, base_indent)}\n"        
  #     end
  #     commented_list   
  #   end

  class MacroCommenter < BaseCommenter
    # Parse out args for inclusion in comment
    def capture_args
      matches = @code.scan(/\(([^\(\)]*)\)/)
      parse_c_style_argument_list(matches[0].to_s)
      @returns = true
    end

    def comment_list(list2, base_indent='')
      commented_list2 = ""
      doc_id = ""
      commented_list2 += "#{base_indent}#{list2} /**< <##{doc_id} description#> */"
    end
    
    def comment_macro_list(list)
      commented_list = ""
      ids = list.gsub(/^\s*/,'').gsub(/\s*$/,'').split(/\n/)
      ids.each do |id|
        id.gsub!(/\s*$/, '')
        id.gsub!(/^\s*/, '')
        list_id = "#{id}"        
        commented_list += "#{comment_list(list_id, "")}\n"        
      end
      commented_list.chomp
    end 

    # Adds a basic comment above individual variables and rewrites multiple
    # declaritions into an inline commented list
    def document
      # 带参宏
      if @code =~ /\#define\s*\w+\(/
        capture_args 
        super
      else
        comment_macro_list(@code)
      end
    end    
  end
  
  # Implementation of commenter to comment C style enums
  class EnumCommenter < BaseCommenter    
    # Comments identifiers in the code block
    def comment_code
      block_match = /\{([^\{\}]*)\}/
      matches = @code.scan(block_match)
      return if matches.size != 1
      
      block = matches[0].to_s
      @code.gsub!(block_match, "{\n#{comment_list(block, "\t")}\n#{@indent}}")
    end
    
    # Comments the enum. This will write comments next to each name for a multiline
    # statement. It will not for single line enumerations.
    def document
      comment_code if is_multiline?
      super
    end
  end

  # Implementation of commenter to comment C style enums
  class StructCommenter < BaseCommenter
    # Comments semicolon delimited list of struct members
    def comment_struct_list(list)
      commented_list = ""
      ids = list.gsub(/^\s*/,'').gsub(/\s*$/,'').split(/;/)
      ids.each do |id|
        id.gsub!(/\s*$/, '')
        id.gsub!(/^\s*/, '')
        list_id = "#{id};"        
        commented_list += "#{comment_list(list_id, "\t")}\n"        
      end
      commented_list   
    end
    
    # Comments identifiers in the code block
    def comment_code
      block_match = /\{([^\{\}]*)\}/
      matches = @code.scan(block_match)
      return if matches.size != 1
      
      block = matches[0].to_s
      @code.gsub!(block_match, "{\n#{comment_struct_list(block)}#{@indent}}")
    end
    
    # Adds inline comments for members and a comment for the entire struct
    def document
      comment_code
      super
    end
  end
  
  class FunctionCommenter < BaseCommenter
    # Parse out args for inclusion in comment
    def capture_args
      matches = @code.scan(/\((.*)\)/)
      parse_c_style_argument_list(matches[0].to_s)
      # parse_c_style_argument_list(@code)
    end
    
    # Decides whether or not to add a returns tag to comment
    def capture_return
      @returns = @code.split(/\(/).first !~ /void/ 
    end
    
    # Adds a basic comment above individual variables and rewrites multiple
    # declaritions into an inline commented list
    def document
      capture_args
      capture_return
      super
    end        
  end

  class MethodCommenter < BaseCommenter
    TAILMATCH = /[\s*;.*]/
    
    # Find the return type
    def capture_return_type
      matches = @code.scan(/^\s*[+-]\s*\(([^\(\)]*)\)/)
      return nil if matches.size != 1
      type = matches[0].to_s.gsub(TAILMATCH, '')

      if type == 'void' || type == 'IBAction'
        @returns = nil
      else
        @returns = type
      end
    end
    
    # Parse out params
    def capture_parameters
      params = []
      matches = @code.scan(/\s*\:\s*\(+\s*(.*?)\s*\)\s*(\w+)/)
      matches.each do |m|
        next if m.size != 2
        arg = Argument.new
        arg.type = m[0].to_s.gsub(TAILMATCH, '')
        arg.name = m[1].to_s.gsub(TAILMATCH, '')
        # puts arg.type
        # puts arg.name
        @arguments << arg        
      end
    end
        
    # Adds a basic comment above individual variables and rewrites multiple
    # declaritions into an inline commented list
    def document
      capture_parameters
      capture_return_type
      super
    end        
  end
  
  class Documenter    
    def document(code)
      # 此句刷格式缩进了
      #code.gsub!(/\s*$/, '')
      indent = base_indentation(code)     
      
      klass = nil
      
      if is_objc_property?(code)
        klass = PropertyCommenter
      elsif is_objc_interface?(code)
        klass = StructCommenter
      elsif is_objc_implementation?(code)
        klass = StructCommenter
      elsif is_objc_method?(code)
        klass = MethodCommenter 
      elsif is_if_while_for_do?(code)
        klass = IfWhileForDoCommenter         
      elsif is_function?(code)
        klass = FunctionCommenter
      elsif is_macro?(code)
        klass = MacroCommenter
      elsif is_struct?(code)
        klass = StructCommenter
      elsif is_union?(code)
        klass = StructCommenter
      elsif is_enum?(code)
        klass = EnumCommenter
      else
        klass = VariableCommenter
      end
      
      # puts "USE --> #{klass}"
      commenter = klass.new(indent, code)
      commenter.document
    end
    
    private
    def is_objc_interface?(code)
      code =~ /^\s*\@interface/
    end

    def is_objc_implementation?(code)
      code =~ /^\s*\@implementation/
    end

    def is_objc_method?(code)
      # code =~ /^\s*[+-]{1,1}\s*\(+/ 
      matches = code.scan(/^\s*[+-]{1,1}\s*\(+/)
      return nil if matches.size == 0
      matches.size  
    end

    def is_objc_property?(code)
      code =~ /^\s*\@property/      
    end

    def is_if_while_for_do?(code)
      code =~ /^\s*if\s*[\({]|for\s*[\({]|while\s*[\({]|do\s*[\({]/ 
    end

    def is_function?(code)
      !is_macro?(code) && !is_objc_method?(code) && !is_if_while_for_do?(code) && code =~ /\(/
    end
    
    def is_macro?(code)
      code =~ /^\s*\#define/      
    end

    def is_enum?(code)
      code.gsub(/\n/, ' ') =~ /^\s*(\w+\s)?enum.*\{.*\}/      
    end
    
    def is_struct?(code)
      code.gsub(/\n/, ' ') =~ /^\s*(\w+\s)?struct.*\{.*\}/      
    end

    def is_union?(code)
      code.gsub(/\n/, ' ') =~ /^\s*(\w+\s)?union.*\{.*\}/      
    end
    
    def base_indentation(code)
      matches = code.scan(/^(\s*)/)
      return '' if matches.size == 0
      matches[0].to_s
    end
  end
end


# Unicode considerations:
#  Set $KCODE to 'u'. This makes STDIN and STDOUT both act as containing UTF-8.
$KCODE = 'u'

#  Since any Ruby version before 1.9 doesn't much care for Unicode,
#  patch in a new String#utf8_length method that returns the correct length
#  for UTF-8 strings.
UNICODE_COMPETENT = ((RUBY_VERSION)[0..2].to_f > 1.8)

unless UNICODE_COMPETENT # lower than 1.9
  class String
    def utf8_length
      i = 0
      i = self.scan(/(.)/).length
      i
    end
  end
else # 1.9 and above
  class String
    alias_method :utf8_length, :length
  end
end

input = STDIN.gets nil
# input now contains the contents of STDIN.
# Write your script here. 
# Be sure to print anything you want the service to output.
documenter = Duckrowing::Documenter.new
replacement = documenter.document(input)
print replacement