Package mrv :: Module mdepparse
[hide private]
[frames] | no frames]

Source Code for Module mrv.mdepparse

  1  # -*- coding: utf-8 -*- 
  2  """Contains parser allowing to retrieve dependency information from maya ascii files 
  3  and convert it into an easy-to-use networkx graph with convenience methods. 
  4  """ 
  5  __docformat__ = "restructuredtext" 
  6   
  7  from networkx import DiGraph, NetworkXError 
  8  from util import iterNetworkxGraph 
  9  from path import make_path 
 10   
 11  import sys 
 12  import os 
 13  import re 
 14   
 15  import logging 
 16  log = logging.getLogger("mrv.mdepparse") 
17 18 -class MayaFileGraph( DiGraph ):
19 """Contains dependnecies between maya files including utility functions 20 allowing to more easily find what you are looking for""" 21 kAffects,kAffectedBy = range( 2 ) 22 23 refpathregex = re.compile( '.*-r .*"(.*)";' ) 24 25 invalidNodeID = "__invalid__" 26 invalidPrefix = ":_iv_:" 27 28 #{ Edit 29 @classmethod
30 - def createFromFiles( cls, fileList, **kwargs ):
31 """:return: MayaFileGraph providing dependency information about the files 32 in fileList and their subReference. 33 :param fileList: iterable providing the filepaths to be parsed and added 34 to this graph 35 :param kwargs: alll arguemnts of `addFromFiles` are supported """ 36 graph = cls( ) 37 graph.addFromFiles( fileList, **kwargs ) 38 return graph
39 40
41 - def _addInvalid( self, invalidfile ):
42 """Add an invalid file to our special location 43 :note: we prefix it to assure it does not popup in our results""" 44 self.add_edge( self.invalidNodeID, self.invalidPrefix + str( invalidfile ) )
45 46 @classmethod
47 - def _parseReferences( cls, mafile, allPaths = False ):
48 """:return: list of reference strings parsed from the given maya ascii file 49 :raise IOError: if the file could not be read""" 50 outrefs = list() 51 filehandle = open( os.path.expandvars( mafile ), "r" ) 52 53 num_rdi_paths = 0 # amount of -rdi paths we found - lateron we have to remove the items 54 # at the respective position as both lists match 55 56 # parse depends 57 for line in filehandle: 58 59 # take the stupid newlines into account ! 60 line = line.strip() 61 if not line.endswith( ";" ): 62 try: 63 line = line + filehandle.next() 64 except StopIteration: 65 break 66 # END newline special handling 67 68 match = cls.refpathregex.match( line ) 69 70 if match: 71 outrefs.append( match.group(1) ) 72 # END -r path match 73 74 # see whether we can abort early 75 if not allPaths and line.startswith( "requires" ): 76 break 77 # END for each line 78 79 filehandle.close() 80 81 return outrefs
82
83 - def _parseDepends( self, mafile, allPaths ):
84 """:return: list of filepath as parsed from the given mafile. 85 :param allPaths: if True, the whole file will be parsed, if False, only 86 the reference section will be parsed""" 87 outdepends = list() 88 log.info("Parsing %s" % ( mafile )) 89 90 try: 91 outdepends = self._parseReferences( mafile, allPaths ) 92 except IOError,e: 93 # store as invalid 94 self._addInvalid( mafile ) 95 log.warn("Parsing Failed: %s" % str( e )) 96 # END exception handlign 97 return outdepends
98 99
100 - def addFromFiles( self, mafiles, parse_all_paths = False, 101 to_os_path = lambda f: make_path(f).expandvars(), 102 os_path_to_db_key = lambda f: f):
103 """Parse the dependencies from the given maya ascii files and add them to 104 this graph 105 106 :note: the more files are given, the more efficient the method can be 107 :param parse_all_paths: if True, default False, all paths found in the file will be used. 108 This will slow down the parsing as the whole file will be searched for references 109 instead of just the header of the file 110 :param to_os_path: functor returning an MA file from given posssibly parsed file 111 that should be existing on the system parsing the files. 112 The passed in file could also be an mb file ( which cannot be parsed ), thus it 113 would be advantageous to return a corresponding ma file 114 This is required as references can have environment variables inside of them 115 :param os_path_to_db_key: converts the given path as used in the filesystem into 116 a path to be used as key in the database. It should be general. 117 Ideally, os_path_to_db_key is the inverse as to_os_path. 118 :note: if the parsed path contain environment variables you must start the 119 tool such that these can be resolved by the system. Otherwise files might 120 not be found 121 :todo: parse_all_paths still to be implemented""" 122 files_parsed = set() # assure we do not duplicate work 123 for mafile in mafiles: 124 depfiles = [ mafile.strip() ] 125 while depfiles: 126 curfile = to_os_path( depfiles.pop() ) 127 128 # ASSURE MA FILE 129 if os.path.splitext( curfile )[1] != ".ma": 130 log.info( "Skipped non-ma file: %s" % curfile ) 131 continue 132 # END assure ma file 133 134 if curfile in files_parsed: 135 continue 136 137 curfiledepends = self._parseDepends( curfile, parse_all_paths ) 138 files_parsed.add( curfile ) 139 140 # create edges 141 curfilestr = str( curfile ) 142 valid_depends = list() 143 for depfile in curfiledepends: 144 print depfile 145 # only valid files may be adjusted - we keep them as is otherwise 146 dbdepfile = to_os_path( depfile ) 147 print dbdepfile 148 if os.path.exists( dbdepfile ): 149 valid_depends.append( depfile ) # store the orig path - it will be converted later 150 dbdepfile = os_path_to_db_key( dbdepfile ) # make it db key path 151 else: 152 dbdepfile = depfile # invalid - revert it 153 self._addInvalid( depfile ) # store it as invalid, no further processing 154 155 self.add_edge( dbdepfile, os_path_to_db_key( curfilestr ) ) 156 157 # add to stack and go on 158 depfiles.extend( valid_depends )
159 # END dependency loop 160 # END for each file to parse 161 162 #} END edit 163 164 #{ Query
165 - def depends( self, filePath, direction = kAffects, 166 to_os_path = lambda f: os.path.expandvars( f ), 167 os_path_to_db_key = lambda f: f, return_unresolved = False, 168 invalid_only = False, **kwargs ):
169 """:return: list of paths ( converted to os paths ) that are related to 170 the given filePath 171 :param direction: specifies search direction, either : 172 kAffects = Files that filePath affects 173 kAffectedBy = Files that affect filePath 174 :param return_unresolved: if True, the output paths will not be translated to 175 an os paths and you get the paths as stored in the graph. 176 Please not that the to_os_path function is still needed to generate 177 a valid key, depending on the format of filepaths stored in this graph 178 :param invalid_only: if True, only invalid dependencies will be returned, all 179 including the invalid ones otherwise 180 :param to_os_path: see `addFromFiles` 181 :param os_path_to_db_key: see `addFromFiles` 182 :param kwargs: passed to `iterNetworkxGraph`""" 183 kwargs[ 'direction' ] = direction 184 kwargs[ 'ignore_startitem' ] = 1 # default 185 kwargs[ 'branch_first' ] = 1 # default 186 187 keypath = os_path_to_db_key( to_os_path( filePath ) ) # convert key 188 invalid = set( self.invalidFiles() ) 189 190 if return_unresolved: 191 to_os_path = lambda f: f 192 193 outlist = list() 194 195 try: 196 for d, f in iterNetworkxGraph( self, keypath, **kwargs ): 197 is_valid = f not in invalid 198 f = to_os_path( f ) # remap only valid paths 199 200 if is_valid and invalid_only: # skip valid ones ? 201 continue 202 203 outlist.append( f ) 204 # END for each file in dependencies 205 except NetworkXError: 206 log.debug( "Skipped Path %s ( %s ): unknown to dependency graph" % ( filePath, keypath ) ) 207 208 return outlist
209
210 - def invalidFiles( self ):
211 """ 212 :return: list of filePaths that could not be parsed, most probably 213 because they could not be found by the system""" 214 lenp = len( self.invalidPrefix ) 215 216 try: 217 return [ iv[ lenp : ] for iv in self.successors( self.invalidNodeID ) ] 218 except NetworkXError: 219 return list()
220 # END no invalid found exception handling 221 #} END query 222