text: #
  # ################ #
  # refreshvcproj.py #
  # ################ #
  #
  # Replaces the "Files" node in the specified Visual C++ project file with
  # a new "Files" node that includes files in the specified directories
  # (and its sub-directories).
  #
  # Originally written on January 15, 2010 at the Undergraduate Capstone Open
  # Source Projects (UCOSP) Code Sprint for the Thunderbird team.
  #
  # HOW TO USE:
  #  1. Create an empty Visual C++ project in Visual Studio. This script has
  #     been tested on vcproj files for Visual Studio 2008 and 2010 Beta 2.
  #  2. Run this script specifying the vcproj file created in step 1 as the
  #     target vcproj file to refresh and a list of directories containing
  #     the files you want to include in the projet.
  #
  # USAGE EXAMPLE:
  #  $ python refreshvcproj.py --vcproj "d:\projects\thunderbird.vcproj"
  #    --dir "d:\repos\mozilla\comm-central\mail"
  #    --dir "d:\repos\mozilla\comm-central\mozilla"
  #
  # WARNING:
  #  The "mozilla" directory is very big. Including all its subdirectories
  #  and files will result in a project file that will take a relatively
  #  long time to load.
  #
  
  import os
  import sys
  import xml.dom.minidom
  from optparse import OptionParser
  
  #
  # Creates and returns an OptionParser instance configured with our options.
  #
  def initOptionParser():
      parser = OptionParser()
  
      parser.add_option("--vcproj", dest="vcproj", type="string")
      parser.add_option("--dir", dest="dirList", action="append", type="string")
  
      return parser
  
  #
  # Parses the command line and returns an object containing values for all of
  # our options.
  #
  def parseArgs(parser):
      if len(sys.argv) <= 1:
          parser.print_help()
          sys.exit(1)
  
      # options: an object containing values for all of our options
      #
      # leftoverArgs: the list of positional arguments leftover after parsing
      #               options
      #
      # See http://docs.python.org/library/optparse.html
      (options, leftoverArgs) = parser.parse_args()
  
      return options
  
  #
  # Ensures that all required options are specified and validates the values for
  # all specified options.
  #
  def validateOptions(options):
      # check for required options here. optparse doesn't give us much help at it
      if None == options.vcproj:
          print "--vcproj option is required."
          sys.exit(1)
  
      if None == options.dirList:
          print "Must specify at least one --dir option."
          sys.exit(1)
  
      # ensure the specified vcproj file exists
      options.vcproj = os.path.abspath(options.vcproj)
      if not os.path.isfile(options.vcproj):
          print "Cannot find vcproj file " + options.vcproj
          sys.exit(1)
  
      # ensure every specified source directory exist
      for dir in options.dirList:
          dirPath = os.path.abspath(dir)
          if not os.path.isdir(dirPath):
              print "Cannot find directory " + dirPath
              sys.exit(1)
  
  ###############################################################################
  
  #
  # Populates the specified "Filter" node with child "Filter" nodes representing
  # sub-directories and child "File" nodes representing files in the specified
  # directory.
  #
  # This function calls itself recursively.
  #
  def populateFilterNode(vcprojDoc, filterNode, dir):
      fileCount = 0
  
      for item in os.listdir(dir):
          itemPath = os.path.join(dir, item)
  
          # item can be a (sub-)directory or file
          if os.path.isdir(itemPath):
              # create a child "Filter" node for this sub-directory
              childFilterNode = vcprojDoc.createElement("Filter")
              childFilterNode.setAttribute("Name", item)
  
              # populate this node by calling ourself recursively
              childFileCount = populateFilterNode(vcprojDoc, childFilterNode, itemPath)
  
              # we only keep the non-empty child nodes
              if childFileCount > 0:
                  filterNode.appendChild(childFilterNode)
              else:
                  childFilterNode = None
  
              fileCount = fileCount + childFileCount
  
          elif os.path.isfile(itemPath):
  
              # create a child "File" node for this file
              childFileNode = vcprojDoc.createElement("File")
  
              # use the full path so the vcproj file can be moved anywhere on
              # disk
              childFileNode.setAttribute("RelativePath", itemPath)
  
              filterNode.appendChild(childFileNode)
  
              fileCount = fileCount + 1
  
      return fileCount
  
  #
  # Saves the document to the specified file.
  #
  def saveDoc(doc, filepath):
      file = open(filepath, "w")
      file.write(doc.toxml())
      file.close()
  
  #
  # Refreshes the "Files" node in the vcproj file specified by options.vcproj.
  #
  def refreshVCProj(options):
      # load the specified vcproj file
      vcprojDoc = xml.dom.minidom.parse(options.vcproj)
  
      # get the "Files" node. assume there's only one
      filesNode = vcprojDoc.getElementsByTagName("Files").item(0)
  
      # create a new "Files" node
      newFilesNode = vcprojDoc.createElement("Files")
  
      # populate the new "Files" node
      fileCount = 0
      for dir in options.dirList:
          filterNode = vcprojDoc.createElement("Filter")
          filterNode.setAttribute("Name", os.path.basename(dir))
          newFilesNode.appendChild(filterNode)
          # populate this "Filter" node
          fileCount = fileCount + populateFilterNode(vcprojDoc, filterNode, dir)
  
      # replace the old node with the new one
      filesNode.parentNode.replaceChild(newFilesNode, filesNode)
  
      # update the file on disk
      saveDoc(vcprojDoc, options.vcproj)
  
      print fileCount, "source files in project file", options.vcproj
  
  ###############################################################################
  
  #
  # This script's entry point
  #    
  def main():
      parser = initOptionParser()
  
      options = parseArgs(parser)
      validateOptions(options)
  
      refreshVCProj(options)
  
  main()