| 1 | #!/usr/bin/env python |
|---|
| 2 | VERSION = "1.4.2" |
|---|
| 3 | |
|---|
| 4 | import nxs |
|---|
| 5 | import numpy |
|---|
| 6 | import math |
|---|
| 7 | import os |
|---|
| 8 | import sys |
|---|
| 9 | |
|---|
| 10 | NAN = float("nan") |
|---|
| 11 | |
|---|
| 12 | class NXSdata: |
|---|
| 13 | def __init__(self, nxs, path, **kwargs): |
|---|
| 14 | nxs.openpath(path) |
|---|
| 15 | (self.dims, self.type) = nxs.getinfo() |
|---|
| 16 | if len(self.dims) == 1: |
|---|
| 17 | self.dims=self.dims[0] |
|---|
| 18 | self.attrs = nxs.getattrs() |
|---|
| 19 | self.data = nxs.getdata() |
|---|
| 20 | |
|---|
| 21 | def getDiffSym(diffType, format): |
|---|
| 22 | sumMap = {Diff.SAME:Diff.SAME, Diff.NEWLEFT:'-', Diff.NEWRIGHT:'+', |
|---|
| 23 | Diff.DIFF:Diff.DIFF, Diff.UNKNOWN:Diff.UNKNOWN} |
|---|
| 24 | if format == "unified": |
|---|
| 25 | return sumMap[diffType] |
|---|
| 26 | else: |
|---|
| 27 | return diffType |
|---|
| 28 | |
|---|
| 29 | def getPercentDiff(left, right, nandiff=float("nan")): |
|---|
| 30 | # find the difference (absolute) |
|---|
| 31 | diffs = left-right |
|---|
| 32 | combine = numpy.fabs(left) + numpy.fabs(right) |
|---|
| 33 | |
|---|
| 34 | # do the percent part |
|---|
| 35 | diffs = 100. * numpy.fabs(diffs/left) |
|---|
| 36 | try: |
|---|
| 37 | diffs[0] |
|---|
| 38 | return diffs |
|---|
| 39 | except IndexError: |
|---|
| 40 | return [diffs] |
|---|
| 41 | |
|---|
| 42 | class Diff: |
|---|
| 43 | SAME = " " |
|---|
| 44 | NEWLEFT = "<" |
|---|
| 45 | NEWRIGHT = ">" |
|---|
| 46 | DIFF = "|" |
|---|
| 47 | UNKNOWN = "?" |
|---|
| 48 | def __init__(self, path, **kwargs): |
|---|
| 49 | self.path = path |
|---|
| 50 | try: |
|---|
| 51 | left = kwargs["left"] |
|---|
| 52 | except KeyError: |
|---|
| 53 | left = None |
|---|
| 54 | try: |
|---|
| 55 | right = kwargs["right"] |
|---|
| 56 | except KeyError: |
|---|
| 57 | right = None |
|---|
| 58 | temp = self.path.split(" ") |
|---|
| 59 | if len(temp) == 2: |
|---|
| 60 | (self.summary, self.path) = temp |
|---|
| 61 | try: |
|---|
| 62 | self.summary = kwargs["diff"] |
|---|
| 63 | if self.summary == "<" or self.summary == "-": |
|---|
| 64 | self.summary = Diff.NEWLEFT |
|---|
| 65 | elif self.summary == ">" or self.summary == "+": |
|---|
| 66 | self.summary = Diff.NEWRIGHT |
|---|
| 67 | else: |
|---|
| 68 | raise "Do not understand diff \"%s\"" % self.summary |
|---|
| 69 | except KeyError: |
|---|
| 70 | self.summary = Diff.UNKNOWN |
|---|
| 71 | try: |
|---|
| 72 | self.setFormat(kwargs["format"]) |
|---|
| 73 | except KeyError: |
|---|
| 74 | self.setFormat("standard") |
|---|
| 75 | self.details = [] |
|---|
| 76 | if self.summary != Diff.UNKNOWN: |
|---|
| 77 | return |
|---|
| 78 | self.__cmpData(left, right) |
|---|
| 79 | if len(self.details) > 0: |
|---|
| 80 | self.summary = Diff.DIFF |
|---|
| 81 | else: |
|---|
| 82 | self.summary = Diff.SAME |
|---|
| 83 | |
|---|
| 84 | def __shortPath(self): |
|---|
| 85 | mypath= self.path.split("/") |
|---|
| 86 | mypath = [item.split(":")[0] for item in mypath] |
|---|
| 87 | return '/'.join(mypath) |
|---|
| 88 | |
|---|
| 89 | def __str__(self): |
|---|
| 90 | result = "%s %s" % (getDiffSym(self.summary, self.__format), \ |
|---|
| 91 | self.__shortPath()) |
|---|
| 92 | if self.summary == Diff.DIFF: |
|---|
| 93 | result += " " + " ".join(self.details) |
|---|
| 94 | return result |
|---|
| 95 | |
|---|
| 96 | def __cmpData(self, left, right): |
|---|
| 97 | left = left.getData(self.path) |
|---|
| 98 | right = right.getData(self.path) |
|---|
| 99 | if left.type != right.type: |
|---|
| 100 | self.details.append("TYPE MISMATCH: %s != %s" \ |
|---|
| 101 | % (left.type, right.type)) |
|---|
| 102 | if left.attrs != right.attrs: |
|---|
| 103 | self.details.append("ATTRIBUTES MISMATCH: %s != %s" % \ |
|---|
| 104 | (left.attrs, right.attrs)) |
|---|
| 105 | if left.type == "CHAR" or left.type == "char" \ |
|---|
| 106 | or right.type =="CHAR" or right.type == "char": |
|---|
| 107 | if left.data != right.data: |
|---|
| 108 | self.details.append("DATA MISMATCH: %s != %s" % \ |
|---|
| 109 | (left.data, right.data)) |
|---|
| 110 | return |
|---|
| 111 | if not numpy.alltrue(left.dims == right.dims): |
|---|
| 112 | self.details.append("DIMENSION MISMATCH: %s != %s" \ |
|---|
| 113 | % (left.dims, right.dims)) |
|---|
| 114 | return |
|---|
| 115 | else: |
|---|
| 116 | try: |
|---|
| 117 | if left.data.dtype != right.data.dtype: |
|---|
| 118 | self.details.append("DATA MISMATCH: %s != %s" \ |
|---|
| 119 | % (left.data.dtype.name, \ |
|---|
| 120 | right.data.dtype.name)) |
|---|
| 121 | return |
|---|
| 122 | except TypeError: |
|---|
| 123 | pass |
|---|
| 124 | except AttributeError: |
|---|
| 125 | pass |
|---|
| 126 | |
|---|
| 127 | # special case when the array is only nans |
|---|
| 128 | if numpy.alltrue(numpy.isnan(left.data)) and \ |
|---|
| 129 | numpy.alltrue(numpy.isnan(right.data)): |
|---|
| 130 | return |
|---|
| 131 | |
|---|
| 132 | if numpy.alltrue(left.data == right.data): |
|---|
| 133 | return |
|---|
| 134 | else: |
|---|
| 135 | if left.data.size == 1 and right.data.size == 1: |
|---|
| 136 | self.details.append("DATA MISMATCH %s != %s" \ |
|---|
| 137 | % (str(left.data), str(right.data))) |
|---|
| 138 | return |
|---|
| 139 | diffs = getPercentDiff(left.data, right.data) |
|---|
| 140 | if numpy.nanmax(diffs) <= 0.: |
|---|
| 141 | return |
|---|
| 142 | stats = getStats(diffs) |
|---|
| 143 | self.details.append("MISMATCH [min%s,max%s,med%s,avg%s,dev%s]" \ |
|---|
| 144 | % (stats[0], stats[1], stats[2], stats[3], |
|---|
| 145 | stats[4])) |
|---|
| 146 | def setFormat(self, format): |
|---|
| 147 | if format == "unified": |
|---|
| 148 | self.__format = "unified" |
|---|
| 149 | else: |
|---|
| 150 | self.__format = "standard" |
|---|
| 151 | |
|---|
| 152 | class NXSfile: |
|---|
| 153 | def __init__(self, name, **kwargs): |
|---|
| 154 | try: |
|---|
| 155 | self.__ignorelinks = kwargs["ignorelinks"] |
|---|
| 156 | except KeyError: |
|---|
| 157 | self.__ignorelinks = False |
|---|
| 158 | try: |
|---|
| 159 | self.__ignorenotes = kwargs["ignorenotes"] |
|---|
| 160 | except KeyError: |
|---|
| 161 | self.__ignorenotes = False |
|---|
| 162 | try: |
|---|
| 163 | self.__ignorets = kwargs["ignorets"] |
|---|
| 164 | except KeyError: |
|---|
| 165 | self.__ignorets = False |
|---|
| 166 | self.name = os.path.abspath(name) |
|---|
| 167 | self.__initPaths() |
|---|
| 168 | |
|---|
| 169 | def __initPaths(self, **kwargs): |
|---|
| 170 | self.__paths = [] |
|---|
| 171 | self.__nxs = nxs.open(self.name) |
|---|
| 172 | self.__paths = self.__getPaths() |
|---|
| 173 | self.__paths.sort() |
|---|
| 174 | while '/' in self.__paths: |
|---|
| 175 | del self.__paths[self.__paths.index('/')] |
|---|
| 176 | |
|---|
| 177 | def __getPaths(self, **kwargs): |
|---|
| 178 | result = [] |
|---|
| 179 | #result.append(self.__nxs.longpath) # add the groups to the tree |
|---|
| 180 | parent = self.__nxs.longpath.split('/')[-1] |
|---|
| 181 | listing = self.__nxs.getentries() |
|---|
| 182 | for name in listing.keys(): |
|---|
| 183 | nxclass = listing[name] |
|---|
| 184 | if nxclass == "SDS": |
|---|
| 185 | if self.__ignorenotes and parent.endswith("NXnote"): |
|---|
| 186 | if name == "author" or name == "date": |
|---|
| 187 | continue |
|---|
| 188 | elif self.__ignorets and name == "SNStranslation_service": |
|---|
| 189 | continue |
|---|
| 190 | self.__nxs.opendata(name) |
|---|
| 191 | attrs = self.__nxs.getattrs() |
|---|
| 192 | longpath = self.__nxs.longpath |
|---|
| 193 | shortpath = self.__nxs.path |
|---|
| 194 | self.__nxs.closedata() |
|---|
| 195 | if self.__ignorelinks and attrs.has_key("target"): |
|---|
| 196 | target = attrs["target"] |
|---|
| 197 | if target != shortpath: |
|---|
| 198 | continue |
|---|
| 199 | result.append(longpath) |
|---|
| 200 | else: |
|---|
| 201 | self.__nxs.opengroup(name,nxclass) |
|---|
| 202 | result.extend(self.__getPaths()) |
|---|
| 203 | self.__nxs.closegroup() |
|---|
| 204 | return result |
|---|
| 205 | |
|---|
| 206 | def __str__(self): |
|---|
| 207 | return self.name |
|---|
| 208 | |
|---|
| 209 | def __eq__(self, other): |
|---|
| 210 | """This provides only the simplest of cases for comparing equality""" |
|---|
| 211 | return self.name == other.name |
|---|
| 212 | |
|---|
| 213 | def __getMissing(self, left, right): |
|---|
| 214 | result = filter(lambda a, right=right:not a in right, left) |
|---|
| 215 | return result |
|---|
| 216 | |
|---|
| 217 | def __removeDuplicate(self, item, array): |
|---|
| 218 | if item not in array: |
|---|
| 219 | return |
|---|
| 220 | index = array.index(item) |
|---|
| 221 | try: |
|---|
| 222 | while True: |
|---|
| 223 | index = array.index(item, index+1) |
|---|
| 224 | del array[index] |
|---|
| 225 | except ValueError: |
|---|
| 226 | pass |
|---|
| 227 | |
|---|
| 228 | def cmpPaths(self, other, **kwargs): |
|---|
| 229 | if self.__paths == other.__paths: |
|---|
| 230 | diffPaths = self.__paths[:] |
|---|
| 231 | else: |
|---|
| 232 | # find what one has that the other is missing |
|---|
| 233 | missingOther = self.__getMissing(self.__paths, other.__paths) |
|---|
| 234 | missingSelf = self.__getMissing(other.__paths, self.__paths) |
|---|
| 235 | |
|---|
| 236 | # merge the results |
|---|
| 237 | diffPaths = self.__paths[:] |
|---|
| 238 | diffPaths.extend(other.__paths) |
|---|
| 239 | diffPaths.sort() |
|---|
| 240 | for path in diffPaths: |
|---|
| 241 | self.__removeDuplicate(path, diffPaths) |
|---|
| 242 | for path in missingSelf: |
|---|
| 243 | index = diffPaths.index(path) |
|---|
| 244 | diffPaths[index] = "> %s" % diffPaths[index] |
|---|
| 245 | for path in missingOther: |
|---|
| 246 | index = diffPaths.index(path) |
|---|
| 247 | diffPaths[index] = "< %s" % diffPaths[index] |
|---|
| 248 | |
|---|
| 249 | result = [] |
|---|
| 250 | for path in diffPaths: |
|---|
| 251 | if path.startswith("<") or path.startswith(">"): |
|---|
| 252 | result.append(Diff(path, diff=path[0])) |
|---|
| 253 | else: |
|---|
| 254 | result.append(Diff(path, left=self, right=other)) |
|---|
| 255 | |
|---|
| 256 | return result |
|---|
| 257 | |
|---|
| 258 | def getData(self, path, **kwargs): |
|---|
| 259 | return NXSdata(self.__nxs, path) |
|---|
| 260 | |
|---|
| 261 | def isNaN(x): |
|---|
| 262 | if x * 1.0 < x: |
|---|
| 263 | return True |
|---|
| 264 | return (x == 1.0) and (x == 2.0) |
|---|
| 265 | |
|---|
| 266 | def removeNaN(array): |
|---|
| 267 | hasnans = numpy.isnan(array) |
|---|
| 268 | indices = numpy.where(hasnans == True)[0] |
|---|
| 269 | if len(indices) <= 0: |
|---|
| 270 | return array |
|---|
| 271 | return numpy.delete(array, indices.tolist()) |
|---|
| 272 | try: |
|---|
| 273 | len(myarray) |
|---|
| 274 | return myarray |
|---|
| 275 | except TypeError: |
|---|
| 276 | return [myarray] |
|---|
| 277 | |
|---|
| 278 | def getStats(array, **kwargs): |
|---|
| 279 | myarray = numpy.copy(array) |
|---|
| 280 | myarray = myarray.ravel() |
|---|
| 281 | origLength = myarray.size |
|---|
| 282 | myarray = removeNaN(myarray) |
|---|
| 283 | myarray.sort() |
|---|
| 284 | length = myarray.size |
|---|
| 285 | if length ==0 and length < origLength: |
|---|
| 286 | avg = NAN |
|---|
| 287 | minimum = NAN |
|---|
| 288 | maximum = NAN |
|---|
| 289 | median = NAN |
|---|
| 290 | stddev = NAN |
|---|
| 291 | else: |
|---|
| 292 | avg = numpy.average(myarray) |
|---|
| 293 | minimum = numpy.nanmin(myarray) |
|---|
| 294 | maximum = numpy.nanmax(myarray) |
|---|
| 295 | median = myarray[(length/2)-1] |
|---|
| 296 | stddev = myarray.std() |
|---|
| 297 | result = map(lambda x: "%.2f%%" % x, |
|---|
| 298 | (minimum, maximum, median, avg, stddev)) |
|---|
| 299 | result.append(origLength) # add the number of elements |
|---|
| 300 | result.append(origLength - length) # number of NaNs found |
|---|
| 301 | return result |
|---|
| 302 | |
|---|
| 303 | def cmpData(left, right, path, **kwargs): |
|---|
| 304 | left = left.getData(path) |
|---|
| 305 | right = right.getData(path) |
|---|
| 306 | if left.type != right.type: |
|---|
| 307 | return "TYPE MISMATCH: %s != %s" % (left.type, right.type) |
|---|
| 308 | if left.dims != right.dims: |
|---|
| 309 | return "DIMENSION MISMATCH: %s != %s" % (left.dims, right.dims) |
|---|
| 310 | if left.attrs != right.attrs: |
|---|
| 311 | return "ATTRIBUTES MISMATCH: %s != %s" % (left.attrs, right.attrs) |
|---|
| 312 | if left.type == "CHAR": |
|---|
| 313 | if left.data == right.data: |
|---|
| 314 | return "" |
|---|
| 315 | else: |
|---|
| 316 | return "DATA MISMATCH: %s != %s" % (left.data, right.data) |
|---|
| 317 | if utils.vector_is_equals(left.data, right.data): |
|---|
| 318 | return "" |
|---|
| 319 | else: |
|---|
| 320 | diffs = [] |
|---|
| 321 | import math |
|---|
| 322 | for i in range(len(left.data)): |
|---|
| 323 | diffs.append(math.fabs((left.data[i]-right.data[i])/left.data[i])) |
|---|
| 324 | stats = getStats(diffs) |
|---|
| 325 | return "MISMATCH [min%s,max%s,med%s,avg%s,dev%s]" \ |
|---|
| 326 | % (stats[0], stats[1], stats[2], stats[3], stats[4]) |
|---|
| 327 | |
|---|
| 328 | def delinearIndex(linear, dims): |
|---|
| 329 | length = len(dims) |
|---|
| 330 | if length == 1: |
|---|
| 331 | return linear |
|---|
| 332 | elif length == 2: |
|---|
| 333 | index = [0,0] |
|---|
| 334 | index[1] = linear % dims[1] |
|---|
| 335 | index[0] = (linear - index[1])/dims[1] |
|---|
| 336 | return tuple(index) |
|---|
| 337 | else: |
|---|
| 338 | raise "Do not know how to deal with dimension " + length |
|---|
| 339 | |
|---|
| 340 | def printDataDiff(left, right, diff, **kwargs): |
|---|
| 341 | # determine the symbols for right and left |
|---|
| 342 | try: |
|---|
| 343 | format = kwargs["format"] |
|---|
| 344 | except KeyError: |
|---|
| 345 | format = "standard" |
|---|
| 346 | leftSym = getDiffSym(Diff.NEWLEFT, format) |
|---|
| 347 | rightSym = getDiffSym(Diff.NEWRIGHT, format) |
|---|
| 348 | |
|---|
| 349 | # get the data |
|---|
| 350 | left = left.getData(diff.path) |
|---|
| 351 | right = right.getData(diff.path) |
|---|
| 352 | |
|---|
| 353 | # the dimensions of the data |
|---|
| 354 | leftLength = 1 |
|---|
| 355 | try: |
|---|
| 356 | for dim in left.dims: |
|---|
| 357 | leftLength *= dim |
|---|
| 358 | except TypeError: |
|---|
| 359 | leftLength *= left.dims |
|---|
| 360 | rightLength = 1 |
|---|
| 361 | try: |
|---|
| 362 | for dim in right.dims: |
|---|
| 363 | rightLength *= dim |
|---|
| 364 | except TypeError: |
|---|
| 365 | rightLength *= right.dims |
|---|
| 366 | |
|---|
| 367 | # the threshold for printing values |
|---|
| 368 | try: |
|---|
| 369 | threshold = kwargs["threshold"] |
|---|
| 370 | except KeyError: |
|---|
| 371 | threshold = None |
|---|
| 372 | |
|---|
| 373 | try: |
|---|
| 374 | shownandiffs = kwargs["shownandiffs"] |
|---|
| 375 | except KeyError: |
|---|
| 376 | shownandiffs = False |
|---|
| 377 | |
|---|
| 378 | # how many items to show |
|---|
| 379 | if not threshold: |
|---|
| 380 | try: |
|---|
| 381 | numItems = kwargs["numitems"] |
|---|
| 382 | except KeyError: |
|---|
| 383 | numItems = 0 |
|---|
| 384 | if numItems < 0: |
|---|
| 385 | pass |
|---|
| 386 | elif numItems == 0: |
|---|
| 387 | leftLength = 10 |
|---|
| 388 | rightLength = 10 |
|---|
| 389 | else: |
|---|
| 390 | leftLength = numItems |
|---|
| 391 | rightLength = numItems |
|---|
| 392 | |
|---|
| 393 | |
|---|
| 394 | # print out type and dims |
|---|
| 395 | if (left.type == right.type) and (left.dims == right.dims): |
|---|
| 396 | print left.type, left.dims |
|---|
| 397 | else: |
|---|
| 398 | print leftSym, left.type, left.dims |
|---|
| 399 | print rightSym, right.type, right.dims |
|---|
| 400 | |
|---|
| 401 | if left.attrs == right.attrs: |
|---|
| 402 | print left.attrs |
|---|
| 403 | else: |
|---|
| 404 | print leftSym, left.attrs |
|---|
| 405 | print rightSym, right.attrs |
|---|
| 406 | |
|---|
| 407 | if threshold is None: |
|---|
| 408 | try: |
|---|
| 409 | print leftSym, left.data.__str__(last=leftLength) |
|---|
| 410 | except TypeError: |
|---|
| 411 | print str(left.data) |
|---|
| 412 | try: |
|---|
| 413 | print rightSym, right.data.__str__(last=rightLength) |
|---|
| 414 | except TypeError: |
|---|
| 415 | print str(right.data) |
|---|
| 416 | else: |
|---|
| 417 | nanIndices = [] |
|---|
| 418 | changeInfo = [] |
|---|
| 419 | length = min(leftLength, rightLength) |
|---|
| 420 | myDiff = getPercentDiff(left.data, right.data) |
|---|
| 421 | myDiff.ravel() |
|---|
| 422 | for i in xrange(myDiff.size): |
|---|
| 423 | index = delinearIndex(i, left.dims) |
|---|
| 424 | if isNaN(myDiff): |
|---|
| 425 | nanIndices.append(index) |
|---|
| 426 | elif myDiff > threshold: |
|---|
| 427 | changeInfo.append((index, myDiff, left.data[i], right.data[i])) |
|---|
| 428 | print "%d values changed between number and NaN" % len(nanIndices) |
|---|
| 429 | print "%d values changed more than %.2f%%" % (len(changeInfo), |
|---|
| 430 | threshold) |
|---|
| 431 | if shownandiffs and len(nanIndices) > 0: |
|---|
| 432 | print "Indices changed between number and NaN:", nanIndices |
|---|
| 433 | if len(changeInfo) > 0: |
|---|
| 434 | print "%10s %5s %10s %10s" % ("index", "%diff", "left", "right") |
|---|
| 435 | for item in changeInfo: |
|---|
| 436 | print "%10s %5.2f %10f %10f" % item |
|---|
| 437 | |
|---|
| 438 | if __name__ == "__main__": |
|---|
| 439 | import optparse |
|---|
| 440 | |
|---|
| 441 | info = [] |
|---|
| 442 | info.append("This utility compares two files that are readable by the ") |
|---|
| 443 | info.append("NeXus API.") |
|---|
| 444 | |
|---|
| 445 | info.append("In the difference the '<' or '-' symbol means that the ") |
|---|
| 446 | info.append("field exists in the left file but not the right.") |
|---|
| 447 | info.append("'>' or '+' symbol means that the field exists in the right ") |
|---|
| 448 | info.append("file but not the left.") |
|---|
| 449 | info.append("'|' symbol means that the field has changed between the ") |
|---|
| 450 | info.append("two files.") |
|---|
| 451 | |
|---|
| 452 | parser = optparse.OptionParser("usage %prog [options] <left> <right>", |
|---|
| 453 | None, optparse.Option, VERSION, 'error', |
|---|
| 454 | " ".join(info)) |
|---|
| 455 | parser.add_option("-v", "--verbose", action="count", dest="verbose", |
|---|
| 456 | help="Enable verbose print statements", default=0) |
|---|
| 457 | parser.add_option("-q", "--brief", action="store_true", dest="quiet", |
|---|
| 458 | help="Disable verbose print statements") |
|---|
| 459 | parser.add_option("-u", "", action="store_true", dest="unifieddiff", |
|---|
| 460 | help="Use the unified output format.") |
|---|
| 461 | parser.add_option("-s", "--report-identical-files", action="store_true", |
|---|
| 462 | dest="reportidenticalfiles", |
|---|
| 463 | help="Report when two files are the same.") |
|---|
| 464 | parser.add_option("-S", "--suppress-common-lines", action="store_true", |
|---|
| 465 | dest="suppresscommon", help="Do not output common lines") |
|---|
| 466 | parser.add_option("", "--show-values", action="store_true", |
|---|
| 467 | dest="showvalues", |
|---|
| 468 | help="Show the values of arrays that do not match") |
|---|
| 469 | parser.add_option("", "--num-values", dest="numvalues", |
|---|
| 470 | help="Set the number of values to show in " |
|---|
| 471 | + "\"--show-values\" mode. If not specified the default " |
|---|
| 472 | + "is ten (10). To show all values specify minus one " |
|---|
| 473 | + "(-1).") |
|---|
| 474 | parser.add_option("", "--show-percent", dest="threshold", |
|---|
| 475 | help="Set a threshold for the minimum percentage " |
|---|
| 476 | + "difference shown in values. This turns on " |
|---|
| 477 | + "\"--show-values\" mode and overrides " |
|---|
| 478 | + "\"--num-values\".") |
|---|
| 479 | parser.add_option("", "--show-nan-diffs", dest="shownandiffs", |
|---|
| 480 | action="store_true", |
|---|
| 481 | help="Whether or not to show the indices that changed " |
|---|
| 482 | + "to nan when using \"--show-percent\" mode.") |
|---|
| 483 | parser.add_option("-L", "--ignore-links", dest="ignorelinks", |
|---|
| 484 | action="store_true", |
|---|
| 485 | help="Only compare the original copy of links") |
|---|
| 486 | parser.add_option("", "--ignore-note-meta", dest="ignorenotes", |
|---|
| 487 | action="store_true", |
|---|
| 488 | help="Ignore the author and date fields in notes") |
|---|
| 489 | parser.add_option("", "--ignore-ts", dest="ignorets", action="store_true", |
|---|
| 490 | help="Ignore differences in the version of ts used") |
|---|
| 491 | |
|---|
| 492 | parser.set_defaults(verbose=False) |
|---|
| 493 | parser.set_defaults(ignorelinks=False) |
|---|
| 494 | parser.set_defaults(numvalues=None) |
|---|
| 495 | parser.set_defaults(threshold=None) |
|---|
| 496 | parser.set_defaults(shownandiffs=False) |
|---|
| 497 | parser.set_defaults(ignorenotes=False) |
|---|
| 498 | parser.set_defaults(ignorets=False) |
|---|
| 499 | |
|---|
| 500 | # parse and fix values |
|---|
| 501 | (options, args) = parser.parse_args() |
|---|
| 502 | if options.quiet: |
|---|
| 503 | options.verbose = -1 |
|---|
| 504 | if options.unifieddiff: |
|---|
| 505 | format = "unified" |
|---|
| 506 | else: |
|---|
| 507 | format = "standard" |
|---|
| 508 | if options.numvalues is not None: |
|---|
| 509 | options.showvalues = True |
|---|
| 510 | options.numvalues = int(options.numvalues) |
|---|
| 511 | else: |
|---|
| 512 | options.numvalues = 0 |
|---|
| 513 | if options.threshold is not None: |
|---|
| 514 | options.showvalues = True |
|---|
| 515 | options.threshold = float(options.threshold) |
|---|
| 516 | if len(args) != 2: |
|---|
| 517 | parser.error("Must compare two files") |
|---|
| 518 | else: |
|---|
| 519 | left = NXSfile(args[0], ignorelinks=options.ignorelinks, |
|---|
| 520 | ignorenotes=options.ignorenotes, |
|---|
| 521 | ignorets=options.ignorets) |
|---|
| 522 | right = NXSfile(args[1], ignorelinks=options.ignorelinks, |
|---|
| 523 | ignorenotes=options.ignorenotes, |
|---|
| 524 | ignorets=options.ignorets) |
|---|
| 525 | |
|---|
| 526 | # direct comparison of the two files |
|---|
| 527 | if left == right: |
|---|
| 528 | if options.reportidenticalfiles: |
|---|
| 529 | print "Files %s and %s are identical" % (left, right) |
|---|
| 530 | sys.exit() |
|---|
| 531 | |
|---|
| 532 | # create the full diff |
|---|
| 533 | diffs = left.cmpPaths(right) |
|---|
| 534 | |
|---|
| 535 | # determine if there were differences |
|---|
| 536 | different = False |
|---|
| 537 | for diff in diffs: |
|---|
| 538 | if len(diff.summary) > 0: |
|---|
| 539 | different = True |
|---|
| 540 | |
|---|
| 541 | # take the easy way out if quiet or identical |
|---|
| 542 | if not different: |
|---|
| 543 | if options.reportidenticalfiles: |
|---|
| 544 | print "Files %s and %s are identical" % (left, right) |
|---|
| 545 | sys.exit() |
|---|
| 546 | elif options.verbose == -1: |
|---|
| 547 | print "Files %s and %s differ" % (left, right) |
|---|
| 548 | sys.exit() |
|---|
| 549 | |
|---|
| 550 | # remove common lines if requestsed |
|---|
| 551 | if options.suppresscommon: |
|---|
| 552 | indices = range(len(diffs)) |
|---|
| 553 | indices.reverse() |
|---|
| 554 | for i in indices: |
|---|
| 555 | if diffs[i].summary == Diff.SAME: |
|---|
| 556 | del diffs[i] |
|---|
| 557 | |
|---|
| 558 | # reformat the diff |
|---|
| 559 | for diff in diffs: |
|---|
| 560 | diff.setFormat(format) |
|---|
| 561 | |
|---|
| 562 | # print out the result |
|---|
| 563 | for diff in diffs: |
|---|
| 564 | print diff |
|---|
| 565 | if options.showvalues and diff.summary == Diff.DIFF: |
|---|
| 566 | printDataDiff(left, right, diff, format=format, |
|---|
| 567 | numitems=options.numvalues, |
|---|
| 568 | threshold=options.threshold, |
|---|
| 569 | shownandiffs=options.shownandiffs) |
|---|