3
"""Module containing the commandline interface for the Maya Depdendency Parser"""
4
# may not be imported directly
6
# assure we have the main module initialized
10
from networkx.readwrite import gpickle
12
from itertools import chain
18
def main( fileList, **kwargs ):
19
"""Called if this module is called directly, creating a file containing
20
dependency information
22
:param kwargs: will be passed directly to `createFromFiles`"""
23
return MayaFileGraph.createFromFiles( fileList, **kwargs )
26
def _usageAndExit( msg = None ):
27
sys.stdout.write("""bpython mdepparse.py [-shortflags ] [--longflags] file_to_parse.ma [file_to_parse, ...]
31
All actual information goes to stdout, everything else to stderr
35
-t Target file used to store the parsed dependency information
36
If not given, the command will automatically be in query mode.
37
The file format is simply a pickle of the underlying Networkx graph
39
-s Source dependency file previously written with -t. If specified, this file
40
will be read to quickly be read for queries. If not given, the information
41
will be parsed first. Thus it is recommended to have a first run storing
42
the dependencies and do all queries just reading in the dependencies using
45
-i if given, a list of input files will be read from stdin. The tool will start
46
parsing the files as the come through the pipe
48
-a if given, all paths will be parsed from the input files. This will take longer
49
than just parsing references as the whole file needs to be read
50
TODO: actual implementation
53
map one part of the path to another in order to make it a valid path
54
in the filesystem, i.e:
55
--to-fs-map source=target[=...]
56
--to-fs-map c:\\=/mnt/data/
57
sort it with the longest remapping first to assure no accidential matches.
58
Should be used if environment variables are used which are not set in the system
59
or if there are other path inconsistencies
62
map one part of the fs path previously remapped by --to-fs-map to a
63
more general one suitable to be a key in the dependency database.
64
The format is equal to the one used in --to-fs-map
66
-o output the dependency database as dot file at the given path, so it can
67
be read by any dot reader and interpreted that way.
68
If input arguments are given, only the affected portions of the database
69
will be available in the dot file. Also, the depths of the dependency information
70
is lost, thus there are only direct connections, although it might in
71
fact be a sub-reference.
75
All values returned in query mode will be new-line separated file paths
76
--affects retrieve all files that are affected by the input files
77
--affected-by retrieve all files that are affect the input files
79
-l if set, only leaf paths, thus paths being at the end of the chain
81
If not given, all paths, i.e. all intermediate references, will
84
-d int if not set, all references and subreferences will be retrieved
85
if 1, only direct references will be returned
86
if > 1, also sub[sub...] references will returned
88
-b if set and no input arg exists, return all bad or invalid files stored in the database
89
if an input argument is given, it acts as a filter and only returns
90
filepaths that are marked invalid
92
-e return full edges instead of only the successors/predecessors.
93
This allows tools to parse the output and make more sense of it
94
Will be ignored in nice mode
96
-n nice output, designed to be human-readable
98
-v enable more verbose output
102
sys.stdout.write(msg+"\n")
108
def tokensToRemapFunc( tokenstring ):
109
"""Return a function applying remapping as defined by tokenstring
111
:note: it also applies a mapping from mb to ma, no matter what.
112
Thus we currently only store .ma files as keys even though it might be mb files"""
113
tokens = tokenstring.split( "=" )
114
if len( tokens ) % 2 != 0:
115
raise ValueError( "Invalid map format: %s" % tokenstring )
117
remap_tuples = zip( tokens[0::2], tokens[1::2] )
119
def path_replace( f ):
120
for source, dest in remap_tuples:
121
f = f.replace( source, dest )
128
# COMMAND LINE INTERFACE
129
############################
130
if __name__ == "__main__":
131
# parse the arguments as retrieved from the command line !
133
opts, rest = getopt.getopt( sys.argv[1:], "iat:s:ld:benvo:", [ "affects", "affected-by",
134
"to-fs-map=","to-db-map=" ] )
135
except getopt.GetoptError,e:
136
_usageAndExit( str( e ) )
139
if not opts and not rest:
143
fromstdin = "-i" in opts
145
# PREPARE KWARGS_CREATEGRAPH
146
#####################
147
allpaths = "-a" in opts
148
kwargs_creategraph = dict( ( ( "parse_all_paths", allpaths ), ) )
149
kwargs_query = dict()
153
# prepare ma to mb conversion
154
# by default, we convert from mb to ma hoping there is a corresponding
155
# ma file in the same directory
156
for kw,flag in ( "to_os_path","--to-fs-map" ),( "os_path_to_db_key", "--to-db-map" ):
160
remap_func = tokensToRemapFunc( opts.get( flag ) )
161
kwargs_creategraph[ kw ] = remap_func
162
kwargs_query[ kw ] = remap_func # required in query mode as well
163
# END for each kw,flag pair
170
filelist = chain( sys.stdin, rest )
173
targetFile = opts.get( "-t", None )
174
sourceFile = opts.get( "-s", None )
180
verbose = "-v" in opts
183
graph = main( filelist, **kwargs_creategraph )
186
sys.stdout.write("Reading dependencies from: %s\n" % sourceFile)
187
graph = gpickle.read_gpickle( sourceFile )
191
# SAVE ALL DEPENDENCIES ?
192
#########################
193
# save to target file
196
sys.stdout.write("Saving dependencies to %s\n" % targetFile)
197
gpickle.write_gpickle( graph, targetFile )
202
return_invalid = "-b" in opts
203
depth = int( opts.get( "-d", -1 ) )
204
as_edge = "-e" in opts
205
nice_mode = "-n" in opts
207
dotOutputFile = opts.get( "-o", None )
208
kwargs_query[ 'invalid_only' ] = return_invalid # if given, filtering for invalid only is enabled
211
dotgraph = MayaFileGraph()
213
queried_files = False
214
for flag, direction in ( ( "--affects", MayaFileGraph.kAffects ),
215
("--affected-by",MayaFileGraph.kAffectedBy ) ):
219
# PREPARE LEAF FUNCTION
220
prune = lambda i,g: False
222
degreefunc = ( ( direction == MayaFileGraph.kAffects ) and MayaFileGraph.out_degree ) or MayaFileGraph.in_degree
223
prune = lambda i,g: degreefunc( g, i ) != 0
225
listcopy = list() # as we read from iterators ( stdin ), its required to copy it to iterate it again
228
# write information to stdout
229
for filepath in filelist:
230
listcopy.append( filepath )
231
queried_files = True # used as flag to determine whether filers have been applied or not
232
filepath = filepath.strip() # could be from stdin
233
depends = graph.depends( filepath, direction = direction, prune = prune,
234
visit_once=1, branch_first=1, depth=depth,
235
return_unresolved=0, **kwargs_query )
241
# FILTERED DOT OUTPUT ?
242
#########################
243
if dotgraph is not None:
245
dotgraph.add_edge( ( filepath, dep ) )
247
# match with invalid files if required
249
depthstr = "unlimited"
251
depthstr = str( depth )
253
affectsstr = "is affected by: "
254
if direction == MayaFileGraph.kAffects:
255
affectsstr = "affects: "
257
headline = "\n%s ( depth = %s, invalid only = %i )\n" % ( filepath, depthstr, return_invalid )
258
sys.stdout.write( headline )
259
sys.stdout.write( "-" * len( headline ) + "\n" )
261
sys.stdout.write( affectsstr + "\n" )
262
sys.stdout.writelines( "\t - " + dep + "\n" for dep in depends )
266
prefix = "%s->" % filepath
267
sys.stdout.writelines( ( prefix + dep + "\n" for dep in depends ) )
268
# END if not nice modd
269
# END for each file in file list
271
# use copy after first iteration
274
# END for each direction to search
276
# ALL INVALID FILES OUTPUT
277
###########################
278
if not queried_files and return_invalid:
279
invalidFiles = graph.invalidFiles()
280
sys.stdout.writelines( ( iv + "\n" for iv in invalidFiles ) )
287
sys.stdout.write("Saving dot file to %s\n" % dotOutputFile)
289
import networkx.drawing.nx_pydot as pydot
291
sys.stderr.write( "Required pydot module not installed" )
293
if queried_files and dotgraph is not None:
294
pydot.write_dot( dotgraph, dotOutputFile )
296
pydot.write_dot( graph, dotOutputFile )