     :                         # Colon terminates identifier
   ///g
   commentText: /^\s*#[ ]?(.*)/gm
+  content: ///
+    \r?\n                     # Start of a line
+    \s\s                      # Required indent of two spaces
+    inherited                 # Expected identifier "inherited"
+    :                         # Colon
+    (                         # Group captures content (and other base setters)
+      (?:                     # Non-capturing group for each line of content
+        \r?\n                 # Line break
+        \s\s\s\s              # Required indent of at least four spaces
+        .*                    # Additional whitespace and/or real content
+      )+
+    )
+  ///
+  referencedClasses: ///
+    \r?\n                     # Start of a line
+    \s+                       # Whitespace
+    control                   # Expected identifer "control"
+    :                         # Colon
+    \s+                       # Whitespace
+    "                         # Open quote
+    (                         # Group captures referenced class name
+      \w+                     # Class name
+    )
+    "                         # Close quote
+  ///g
   requiresClasses: ///
     _requiresClasses          # Expected identifer "_requiresClasses"
     :                         # Colon


class DocsExtractor

  constructor: ( regexes ) ->
    @regexBaseClassName = regexes.baseClassName
    @regexClassName = regexes.className
    @regexComments = regexes.comments
    @regexCommentText = regexes.commentText
    @regexRequiresClasses = regexes.requiresClasses

  extract: ( source ) ->
    baseClassName: @baseClassName source


  # Return the name for the control class defined in the source text.
  baseClassName: ( source ) ->
    match = @regexes.baseClassName.exec source
    match?[1]

  # Remove the comment indicator (e.g., "*") on interior block comment lines.
  commentText: ( commentBlock ) ->
    text = ""
    match = @regexes.commentText.exec commentBlock
    while match != null
      [ full, lineText ] = match
      if text.length > 0
        text += "\n"
      text += lineText
      match = @regexes.commentText.exec commentBlock
    text

  # Return the text used to define the control's content and other property
  # setters invoked on the base class.
  content: ( source ) ->
    match = @regexes.content.exec source
    match?[1]

  # Return the name for the control class defined in the source text.
  controlClassName: ( source ) ->
    match = @regexes.className.exec source
    match?[1]

  # Return the array of other control classes explicitly required by this class.
  explicitRequiredClasses: ( source ) ->
    match = @regexes.requiresClasses.exec source
    if match?
      classes = JSON.parse match[1] # Parse the text into a real array
      @_unique classes

  # Return the array of other control classes implicitly required by this class
  # because they're used in the class' content or other base class setters.
  implicitRequiredClasses: ( source ) ->
    content = @content source
    if content
      referencedClasses = @_matches @regexes.referencedClasses, content
      @_unique referencedClasses

  # Return the comments found for members defined in the source text.
  memberComments: ( source ) ->
    comments = null
    match = @regexes.comments.exec source
    while match != null
      [ full, commentBlock, identifier ] = match
      commentText = @commentText commentBlock
      if commentText.length > 0
        comments = comments || {}
        comments[ identifier ] = commentText
      match = @regexes.comments.exec source
    comments

  # Return the array of classes both implicitly and explicitly required.
  requiresClasses: ( source ) ->
    implicit = @implicitRequiredClasses source
    explicit = @explicitRequiredClasses source
    requiresClasses = implicit.concat explicit
    unique = @_unique requiresClasses
    unique.sort()

  # Return the first capture group for each match of a regex against source text.
  _matches: ( regex, source ) ->
    matches = []
    match = regex.exec source
    while match?
      matches.push match[1]
      match = regex.exec source
    matches

  # Return the given array with duplicates removed.
  _unique: (array) ->
    results = []
    for item in array
      results.push item if results.indexOf( item ) < 0
    results

# Extracts docs from CoffeeScript controls
coffeeDocsExtractor = new DocsExtractor
