public
Description: PLEASE GO TO http://yard.soen.ca FOR IMPORTANT NEWS ABOUT YARD / THE AUTHOR!!!
Homepage: http://yard.soen.ca / IRC: #yard on irc.freenode.net
Clone URL: git://github.com/lsegal/yard.git
Click here to lend your support to: yard and make a donation at www.pledgie.com !
wycats (author)
Wed May 14 18:36:00 -0700 2008
commit  be82233018b2fc1eb0dfd8596f927a22e3e5e6f9
tree    a88232db49ead8366e7ccd72971f78021b511144
parent  5792b67d3a0b116f66d73e366caee02d80af94d7
yard / lib / yard / code_objects / base.rb
100644 240 lines (206 sloc) 7.241 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
require "delegate"
 
module YARD
  module CodeObjects
    class CodeObjectList < Delegator
      def initialize(owner)
        @owner = owner
        @list = []
        class << @list
          undef :unshift
        end
      end
      
      def __getobj__
        @list
      end
      
      def <<(value)
        if value.is_a?(CodeObjects::Base) || value.is_a?(Proxy)
          @list << value unless @list.include?(value)
        elsif value.is_a?(String) || value.is_a?(Symbol)
          @list << P(@owner, value) unless @list.include?(P(@owner, value))
        else
          raise ArgumentError, "#{value.class} is not a valid CodeObject"
        end
        self
      end
      
      def push(value)
        self << value
      end
    end
    
    NSEP = '::'
    ISEP = '#'
    
    class Base
      attr_reader :name
      attr_accessor :namespace
      attr_accessor :source, :file, :line, :docstring
      attr_reader :tags
      
      class << self
        attr_accessor :instances
        
        def new(namespace, name, *args, &block)
          if name =~ /(?:#{NSEP}|#{ISEP})([^#{NSEP}#{ISEP}]+)$/
            return new(Registry.resolve(namespace, $`, true), $1, *args, &block)
          end
          
          self.instances ||= {}
          keyname = "#{namespace && namespace.respond_to?(:path) ? namespace.path : ''}+#{name.inspect}"
          if obj = Registry.objects[keyname]
            obj
          else
            Registry.objects[keyname] = super(namespace, name, *args, &block)
          end
        end
      end
          
      def initialize(namespace, name, *args)
        if namespace && namespace != :root &&
            !namespace.is_a?(NamespaceObject) && !namespace.is_a?(Proxy)
          raise ArgumentError, "Invalid namespace object: #{namespace}"
        end
 
        @name = name.to_sym
        @tags = []
        @docstring = ""
        self.namespace = namespace
        yield(self) if block_given?
      end
      
      def [](key)
        if respond_to?(key)
          send(key)
        else
          instance_variable_get("@#{key}")
        end
      end
      
      def []=(key, value)
        if respond_to?("#{key}=")
          send("#{key}=", value)
        else
          instance_variable_set("@#{key}", value)
        end
      end
 
      ##
      # Attaches source code to a code object with an optional file location
      #
      # @param [Parser::Statement, 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.is_a? Parser::Statement
          @source = statement.tokens.to_s + (statement.block ? statement.block.to_s : "")
          self.line = statement.tokens.first.line_no
        else
          @source = statement.to_s
        end
      end
 
      ##
      # Attaches a docstring to a code oject by parsing the comments attached to the statement
      # and filling the {#tags} and {#docstring} methods with the parsed information.
      #
      # @param [String, Array<String>] comments
      # the comments attached to the code object to be parsed
      # into a docstring and meta tags.
      def docstring=(comments)
        parse_comments(comments) if comments
      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(/#{NSEP}/).last.gsub(/Object$/, '').downcase.to_sym
      end
    
      def path
        if parent && parent != Registry.root
          [parent.path, name.to_s].join(sep)
        else
          name.to_s
        end
      end
      alias_method :inspect, :path
    
      def namespace=(obj)
        if @namespace
          @namespace.children.delete(self)
          Registry.delete(self)
        end
        
        @namespace = (obj == :root ? Registry.root : obj)
      
        if @namespace
          @namespace.children << self unless @namespace.is_a?(Proxy)
          Registry.register(self)
        end
      end
    
      alias_method :parent, :namespace
      alias_method :parent=, :namespace=
 
      ##
      # Convenience method to return the first tag
      # object in the list of tag objects of that name
      #
      # Example:
      # doc = YARD::Documentation.new("@return zero when nil")
      # doc.tag("return").text # => "zero when nil"
      #
      # @param [#to_s] name the tag name to return data for
      # @return [BaseTag] the first tag in the list of {#tags}
      def tag(name)
        name = name.to_s
        @tags.find {|tag| tag.tag_name == name }
      end
 
      ##
      # Returns a list of tags specified by +name+ or all tags if +name+ is not specified.
      #
      # @param name the tag name to return data for, or nil for all tags
      # @return [Array<BaseTag>] the list of tags by the specified tag name
      def tags(name = nil)
        return @tags if name.nil?
        name = name.to_s
        @tags.select {|tag| tag.tag_name == name }
      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)
        name = name.to_s
        @tags.any? {|tag| tag.tag_name == name }
      end
 
      protected
    
      def sep; NSEP end
 
      private
 
      ##
      # Parses out comments split by newlines into a new code object
      #
      # @param [Array<String>, String] comments
      # the newline delimited array of comments. If the comments
      # are passed as a String, they will be split by newlines.
      def parse_comments(comments)
        return if comments.empty?
        meta_match = /^\s*@(\S+)\s*(.*)/
        comments = comments.split(/\r?\n/) if comments.is_a? String
        @tags, @docstring = [], ""
 
        indent, last_indent = comments.first[/^\s*/].length, 0
        tag_name, tag_klass, tag_buf = nil, nil, ""
 
        # Add an extra line to catch a meta directive on the last line
        (comments+['']).each do |line|
          indent = line[/^\s*/].length
 
          if (indent < last_indent && tag_name) || line == '' || line =~ meta_match
            tag_method = "#{tag_name}_tag"
            if tag_name && Tags::Library.respond_to?(tag_method)
              @tags << Tags::Library.send(tag_method, tag_buf.squeeze(" "))
            end
            tag_name, tag_buf = nil, ''
          end
 
          # Found a meta tag
          if line =~ meta_match
            tag_name, tag_buf = $1, $2
          elsif indent >= last_indent && tag_name
            # Extra data added to the tag on the next line
            tag_buf << line
          else
            # Regular docstring text
            @docstring << line << "\n"
          end
 
          last_indent = indent
        end
 
        # Remove trailing/leading whitespace / newlines
        @docstring.gsub!(/\A[\r\n\s]+|[\r\n\s]+\Z/, '')
      end
    end
  end
end