##// END OF EJS Templates
util: add a file object proxy that can notify observers...
Gregory Szorc -
r36541:bfe38f78 default
parent child Browse files
Show More
@@ -488,6 +488,228 b' def popen4(cmd, env=None, newlines=False'
488 env=env)
488 env=env)
489 return p.stdin, p.stdout, p.stderr, p
489 return p.stdin, p.stdout, p.stderr, p
490
490
491 class fileobjectproxy(object):
492 """A proxy around file objects that tells a watcher when events occur.
493
494 This type is intended to only be used for testing purposes. Think hard
495 before using it in important code.
496 """
497 __slots__ = (
498 r'_orig',
499 r'_observer',
500 )
501
502 def __init__(self, fh, observer):
503 object.__setattr__(self, '_orig', fh)
504 object.__setattr__(self, '_observer', observer)
505
506 def __getattribute__(self, name):
507 ours = {
508 # IOBase
509 r'close',
510 # closed if a property
511 r'fileno',
512 r'flush',
513 r'isatty',
514 r'readable',
515 r'readline',
516 r'readlines',
517 r'seek',
518 r'seekable',
519 r'tell',
520 r'truncate',
521 r'writable',
522 r'writelines',
523 # RawIOBase
524 r'read',
525 r'readall',
526 r'readinto',
527 r'write',
528 # BufferedIOBase
529 # raw is a property
530 r'detach',
531 # read defined above
532 r'read1',
533 # readinto defined above
534 # write defined above
535 }
536
537 # We only observe some methods.
538 if name in ours:
539 return object.__getattribute__(self, name)
540
541 return getattr(object.__getattribute__(self, r'_orig'), name)
542
543 def __delattr__(self, name):
544 return delattr(object.__getattribute__(self, r'_orig'), name)
545
546 def __setattr__(self, name, value):
547 return setattr(object.__getattribute__(self, r'_orig'), name, value)
548
549 def __iter__(self):
550 return object.__getattribute__(self, r'_orig').__iter__()
551
552 def _observedcall(self, name, *args, **kwargs):
553 # Call the original object.
554 orig = object.__getattribute__(self, r'_orig')
555 res = getattr(orig, name)(*args, **kwargs)
556
557 # Call a method on the observer of the same name with arguments
558 # so it can react, log, etc.
559 observer = object.__getattribute__(self, r'_observer')
560 fn = getattr(observer, name, None)
561 if fn:
562 fn(res, *args, **kwargs)
563
564 return res
565
566 def close(self, *args, **kwargs):
567 return object.__getattribute__(self, r'_observedcall')(
568 r'close', *args, **kwargs)
569
570 def fileno(self, *args, **kwargs):
571 return object.__getattribute__(self, r'_observedcall')(
572 r'fileno', *args, **kwargs)
573
574 def flush(self, *args, **kwargs):
575 return object.__getattribute__(self, r'_observedcall')(
576 r'flush', *args, **kwargs)
577
578 def isatty(self, *args, **kwargs):
579 return object.__getattribute__(self, r'_observedcall')(
580 r'isatty', *args, **kwargs)
581
582 def readable(self, *args, **kwargs):
583 return object.__getattribute__(self, r'_observedcall')(
584 r'readable', *args, **kwargs)
585
586 def readline(self, *args, **kwargs):
587 return object.__getattribute__(self, r'_observedcall')(
588 r'readline', *args, **kwargs)
589
590 def readlines(self, *args, **kwargs):
591 return object.__getattribute__(self, r'_observedcall')(
592 r'readlines', *args, **kwargs)
593
594 def seek(self, *args, **kwargs):
595 return object.__getattribute__(self, r'_observedcall')(
596 r'seek', *args, **kwargs)
597
598 def seekable(self, *args, **kwargs):
599 return object.__getattribute__(self, r'_observedcall')(
600 r'seekable', *args, **kwargs)
601
602 def tell(self, *args, **kwargs):
603 return object.__getattribute__(self, r'_observedcall')(
604 r'tell', *args, **kwargs)
605
606 def truncate(self, *args, **kwargs):
607 return object.__getattribute__(self, r'_observedcall')(
608 r'truncate', *args, **kwargs)
609
610 def writable(self, *args, **kwargs):
611 return object.__getattribute__(self, r'_observedcall')(
612 r'writable', *args, **kwargs)
613
614 def writelines(self, *args, **kwargs):
615 return object.__getattribute__(self, r'_observedcall')(
616 r'writelines', *args, **kwargs)
617
618 def read(self, *args, **kwargs):
619 return object.__getattribute__(self, r'_observedcall')(
620 r'read', *args, **kwargs)
621
622 def readall(self, *args, **kwargs):
623 return object.__getattribute__(self, r'_observedcall')(
624 r'readall', *args, **kwargs)
625
626 def readinto(self, *args, **kwargs):
627 return object.__getattribute__(self, r'_observedcall')(
628 r'readinto', *args, **kwargs)
629
630 def write(self, *args, **kwargs):
631 return object.__getattribute__(self, r'_observedcall')(
632 r'write', *args, **kwargs)
633
634 def detach(self, *args, **kwargs):
635 return object.__getattribute__(self, r'_observedcall')(
636 r'detach', *args, **kwargs)
637
638 def read1(self, *args, **kwargs):
639 return object.__getattribute__(self, r'_observedcall')(
640 r'read1', *args, **kwargs)
641
642 DATA_ESCAPE_MAP = {pycompat.bytechr(i): br'\x%02x' % i for i in range(256)}
643 DATA_ESCAPE_MAP.update({
644 b'\\': b'\\\\',
645 b'\r': br'\r',
646 b'\n': br'\n',
647 })
648 DATA_ESCAPE_RE = remod.compile(br'[\x00-\x08\x0a-\x1f\\\x7f-\xff]')
649
650 def escapedata(s):
651 return DATA_ESCAPE_RE.sub(lambda m: DATA_ESCAPE_MAP[m.group(0)], s)
652
653 class fileobjectobserver(object):
654 """Logs file object activity."""
655 def __init__(self, fh, name, reads=True, writes=True, logdata=False):
656 self.fh = fh
657 self.name = name
658 self.logdata = logdata
659 self.reads = reads
660 self.writes = writes
661
662 def _writedata(self, data):
663 if not self.logdata:
664 self.fh.write('\n')
665 return
666
667 # Simple case writes all data on a single line.
668 if b'\n' not in data:
669 self.fh.write(': %s\n' % escapedata(data))
670 return
671
672 # Data with newlines is written to multiple lines.
673 self.fh.write(':\n')
674 lines = data.splitlines(True)
675 for line in lines:
676 self.fh.write('%s> %s\n' % (self.name, escapedata(line)))
677
678 def read(self, res, size=-1):
679 if not self.reads:
680 return
681
682 self.fh.write('%s> read(%d) -> %d' % (self.name, size, len(res)))
683 self._writedata(res)
684
685 def readline(self, res, limit=-1):
686 if not self.reads:
687 return
688
689 self.fh.write('%s> readline() -> %d' % (self.name, len(res)))
690 self._writedata(res)
691
692 def write(self, res, data):
693 if not self.writes:
694 return
695
696 self.fh.write('%s> write(%d) -> %r' % (self.name, len(data), res))
697 self._writedata(data)
698
699 def flush(self, res):
700 if not self.writes:
701 return
702
703 self.fh.write('%s> flush() -> %r\n' % (self.name, res))
704
705 def makeloggingfileobject(logh, fh, name, reads=True, writes=True,
706 logdata=False):
707 """Turn a file object into a logging file object."""
708
709 observer = fileobjectobserver(logh, name, reads=reads, writes=writes,
710 logdata=logdata)
711 return fileobjectproxy(fh, observer)
712
491 def version():
713 def version():
492 """Return version information if available."""
714 """Return version information if available."""
493 try:
715 try:
General Comments 0
You need to be logged in to leave comments. Login now