Diff
Modified: trunk/Tools/ChangeLog (280863 => 280864)
--- trunk/Tools/ChangeLog 2021-08-10 21:28:35 UTC (rev 280863)
+++ trunk/Tools/ChangeLog 2021-08-10 21:35:31 UTC (rev 280864)
@@ -1,3 +1,38 @@
+2021-08-10 Jonathan Bedard <jbed...@apple.com>
+
+ [git-webkit] Color `log` output
+ https://bugs.webkit.org/show_bug.cgi?id=228662
+ <rdar://problem/81344181>
+
+ Reviewed by Dewei Zhu.
+
+ * Scripts/libraries/webkitcorepy/setup.py: Bump version.
+ * Scripts/libraries/webkitcorepy/webkitcorepy/__init__.py: Bump version, export Terminal.
+ * Scripts/libraries/webkitcorepy/webkitcorepy/terminal.py: Added.
+ (Terminal):
+ (Terminal.assert_writeable_stream): Assert that the provided object is a writable stream.
+ (Terminal.supports_color): Check if the provided file supports colored output.
+ (Terminal.isatty): Check if a stream is an atty, both according to the stream itself and our override.
+ (Terminal.override_atty): Allow callers to override and declare explicitly if a stream is an atty.
+ (Terminal.Text): Group text colors and styles.
+ (Terminal.Style): Group functions modifying style of a stream.
+ (Terminal.Style.enabled): Check if styled printing is enabled on a stream.
+ (Terminal.Style.disable): Disable styled printing on a stream.
+ (Terminal.Style.enable): Enable styled printing on a stream.
+ (Terminal.Style.is_styled): Check if a stream is currently styled.
+ (Terminal.Style.__init__): Construct a text style.
+ (Terminal.Style.__repr__): Output the terminal characters styling a stream.
+ (Terminal.Style.set): Apply style to a stream.
+ (Terminal.Style.apply): Apply style to a stream, unapply when exiting context.
+ * Scripts/libraries/webkitscmpy/setup.py:
+ * Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py:
+ * Scripts/libraries/webkitscmpy/webkitscmpy/program/blame.py: Pass isatty to child process.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/program/command.py:
+ (FilteredCommand.pager): Pass isatty to child process, ask 'more' to display colors.
+ (FilteredCommand.main): Color header and error.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/program/log.py: Pass isatty to child process.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/test/log_unittest.py: Override atty behavior.
+
2021-08-10 Ben Nham <n...@apple.com>
Fix nested resource load tracepoints
Modified: trunk/Tools/Scripts/libraries/webkitcorepy/setup.py (280863 => 280864)
--- trunk/Tools/Scripts/libraries/webkitcorepy/setup.py 2021-08-10 21:28:35 UTC (rev 280863)
+++ trunk/Tools/Scripts/libraries/webkitcorepy/setup.py 2021-08-10 21:35:31 UTC (rev 280864)
@@ -30,7 +30,7 @@
setup(
name='webkitcorepy',
- version='0.8.1',
+ version='0.8.2',
description='Library containing various Python support classes and functions.',
long_description=readme(),
classifiers=[
Modified: trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/__init__.py (280863 => 280864)
--- trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/__init__.py 2021-08-10 21:28:35 UTC (rev 280863)
+++ trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/__init__.py 2021-08-10 21:35:31 UTC (rev 280864)
@@ -40,7 +40,7 @@
from webkitcorepy.measure_time import MeasureTime
from webkitcorepy.nested_fuzzy_dict import NestedFuzzyDict
-version = Version(0, 8, 1)
+version = Version(0, 8, 2)
from webkitcorepy.autoinstall import Package, AutoInstall
if sys.version_info > (3, 0):
Modified: trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/terminal.py (280863 => 280864)
--- trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/terminal.py 2021-08-10 21:28:35 UTC (rev 280863)
+++ trunk/Tools/Scripts/libraries/webkitcorepy/webkitcorepy/terminal.py 2021-08-10 21:35:31 UTC (rev 280864)
@@ -20,10 +20,20 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+import contextlib
+import io
import sys
+from webkitcorepy import StringIO
+if sys.version_info > (3, 0):
+ file = io.IOBase
+
+
class Terminal(object):
+ _atty_overrides = {}
+ colors = True
+
@classmethod
def input(cls, *args, **kwargs):
return (input if sys.version_info > (3, 0) else raw_input)(*args, **kwargs)
@@ -57,3 +67,167 @@
response = default
return response
+
+ @classmethod
+ def assert_writeable_stream(cls, target):
+ if not isinstance(target, (io.IOBase, file, StringIO)):
+ raise ValueError('{} is not an IO object'.format(target))
+ if not isinstance(target, StringIO) and not (getattr(target, 'writable', None) and target.writable()) and 'w' not in getattr(target, 'mode', 'r'):
+ raise ValueError('{} is an IO object, but is not writable {}'.format(target, ))
+
+ @classmethod
+ def supports_color(cls, file):
+ if not cls.colors:
+ return False
+ return cls.isatty(file)
+
+ @classmethod
+ def isatty(cls, file):
+ try:
+ return cls._atty_overrides.get(file.fileno(), file.isatty())
+ except (io.UnsupportedOperation, AttributeError):
+ return cls._atty_overrides.get(str(file), file.isatty())
+
+ @classmethod
+ @contextlib.contextmanager
+ def override_atty(cls, target, isatty=True):
+ if not isinstance(target, (io.IOBase, file, StringIO)):
+ raise ValueError('{} is not an IO object'.format(target))
+
+ try:
+ key = target.fileno()
+ except (io.UnsupportedOperation, AttributeError):
+ key = str(target)
+
+ previous = cls._atty_overrides.get(key)
+ cls._atty_overrides[key] = isatty
+
+ try:
+ yield
+ finally:
+ if previous is None:
+ del cls._atty_overrides[key]
+ else:
+ cls._atty_overrides[key] = previous
+
+ class Text(object):
+ value = lambda value: '\033[{}m'.format(value)
+
+ reset = value(0)
+
+ styles = [value(1), value(4), value(5), value(8)]
+ bold, underline, blink, concealed = styles
+
+ colors = [value(30), value(31), value(32), value(33), value(34), value(35), value(36), value(37)]
+ black, red, green, yellow, blue, magenta, cyan, white = colors
+
+ backgroundColors = [value(40), value(41), value(42), value(43), value(44), value(45), value(46), value(47)]
+ blackBackground, redBackground, greenBackground, yellowBackground, blueBackground, magentaBackground, cyanBackground, whiteBackground = colors
+
+ class Style(object):
+ top = {}
+ _disabled = set()
+ _is_styled = set()
+
+ @classmethod
+ def enabled(cls, file):
+ Terminal.assert_writeable_stream(file)
+
+ try:
+ if not Terminal.supports_color(file) or not file.fileno():
+ return False
+ except (io.UnsupportedOperation, AttributeError):
+ return False
+ return file.fileno() not in cls._disabled
+
+ @classmethod
+ def disable(cls, file):
+ Terminal.assert_writeable_stream(file)
+ if not cls.enabled(file):
+ return
+ cls._disabled.add(file.fileno())
+ Terminal.Style().set(file)
+
+ @classmethod
+ def enable(cls, file):
+ Terminal.assert_writeable_stream(file)
+ if cls.enabled(file):
+ return
+
+ cls._disabled.discard(file.fileno())
+ if cls.top.get(file.fileno()):
+ cls.top.get(file.fileno()).set(file)
+
+ @classmethod
+ def is_styled(cls, file):
+ try:
+ return file.fileno() in cls._is_styled
+ except (io.UnsupportedOperation, AttributeError):
+ return False
+
+ def __init__(self, style=None, color=None, backgroundColor=None):
+ if style and style not in Terminal.Text.styles:
+ raise ValueError('{} is not a recognized terminal text style'.format(style))
+ self.style = style
+
+ if color and color not in Terminal.Text.colors:
+ raise ValueError('{} is not a recognized terminal color'.format(color))
+ self.color = color
+
+ if backgroundColor and backgroundColor not in Terminal.Text.backgroundColors:
+ raise ValueError('{} is not a recognized terminal background color'.format(backgroundColor))
+ self.backgroundColor = backgroundColor
+
+ def __repr__(self):
+ result = Terminal.Text.reset
+ if self.style:
+ result += self.style
+ if self.color:
+ result += self.color
+ if self.backgroundColor:
+ result += self.backgroundColor
+ return result
+
+ def set(self, file):
+ Terminal.assert_writeable_stream(file)
+ will_style = True
+ if not self.style and not self.color and not self.backgroundColor:
+ will_style = False
+ elif not self.enabled(file):
+ will_style = False
+
+ if will_style:
+ self._is_styled.add(file.fileno())
+ file.write(str(self))
+ return
+
+ if self.is_styled(file):
+ file.write(Terminal.Text.reset)
+ try:
+ self._is_styled.discard(file.fileno())
+ except (io.UnsupportedOperation, AttributeError):
+ pass
+
+ @contextlib.contextmanager
+ def apply(self, target):
+ Terminal.assert_writeable_stream(target)
+ try:
+ previous = self.top.get(target.fileno())
+ except (io.UnsupportedOperation, AttributeError):
+ try:
+ yield
+ finally:
+ return
+
+ self.top[target.fileno()] = self
+ self.set(target)
+
+ try:
+ yield
+ finally:
+ if previous:
+ previous.set(target)
+ self.top[target.fileno()] = previous
+ else:
+ Terminal.Style().set(target)
+ del self.top[target.fileno()]
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/setup.py (280863 => 280864)
--- trunk/Tools/Scripts/libraries/webkitscmpy/setup.py 2021-08-10 21:28:35 UTC (rev 280863)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/setup.py 2021-08-10 21:35:31 UTC (rev 280864)
@@ -29,7 +29,7 @@
setup(
name='webkitscmpy',
- version='1.1.0',
+ version='1.1.1',
description='Library designed to interact with git and svn repositories.',
long_description=readme(),
classifiers=[
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py (280863 => 280864)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py 2021-08-10 21:28:35 UTC (rev 280863)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py 2021-08-10 21:35:31 UTC (rev 280864)
@@ -46,7 +46,7 @@
"Please install webkitcorepy with `pip install webkitcorepy --extra-index-url <package index URL>`"
)
-version = Version(1, 1, 0)
+version = Version(1, 1, 1)
AutoInstall.register(Package('fasteners', Version(0, 15, 0)))
AutoInstall.register(Package('monotonic', Version(1, 5)))
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/blame.py (280863 => 280864)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/blame.py 2021-08-10 21:28:35 UTC (rev 280863)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/blame.py 2021-08-10 21:35:31 UTC (rev 280864)
@@ -39,7 +39,8 @@
if __name__ == '__main__':
sys.exit(Blame.main(
- sys.argv[3:],
+ sys.argv[4:],
local.Scm.from_path(path=sys.argv[1], cached=True),
representation=sys.argv[2],
+ isatty=sys.argv[3] == 'isatty',
))
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/command.py (280863 => 280864)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/command.py 2021-08-10 21:28:35 UTC (rev 280863)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/command.py 2021-08-10 21:35:31 UTC (rev 280864)
@@ -27,6 +27,7 @@
import sys
import time
+from webkitcorepy import Terminal
from webkitscmpy.commit import Commit
from whichcraft import which
@@ -110,32 +111,35 @@
repository.cache = repository.Cache(repository)
# If we're a terminal, rely on 'more' to display output
- if sys.stdin.isatty() and not isinstance(args, list) and file:
+ if Terminal.isatty(sys.stdin) and not isinstance(args, list) and file:
environ = os.environ
environ['PYTHONPATH'] = ':'.join(sys.path)
child = subprocess.Popen(
- [sys.executable, file, repository.root_path, args.representation] + args.args,
+ [sys.executable, file, repository.root_path, args.representation, 'isatty' if Terminal.isatty(sys.stdout) else 'notatty'] + args.args,
env=environ,
cwd=os.getcwd(),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
- more = subprocess.Popen([which('more')] + ['-F'] if platform.system() == 'Darwin' else [], stdin=child.stdout)
+ more = subprocess.Popen([which('more')] + ['-F', '-R'] if platform.system() == 'Darwin' else [], stdin=child.stdout)
try:
while more.poll() is None and not child.poll():
time.sleep(0.25)
finally:
- child.kill()
- more.kill()
+ if child.returncode is None:
+ child.kill()
+ if more.returncode is None:
+ more.kill()
child_error = child.stderr.read()
if child_error:
- sys.stderr.buffer.write(b'\n' + child_error)
+ (sys.stderr.buffer if sys.version_info > (3, 0) else sys.stderr).write(b'\n' + child_error)
return child.returncode
- return FilteredCommand.main(args, repository, command=cls.name, **kwargs)
+ with Terminal.override_atty(sys.stdout, isatty=kwargs.get('isatty')), Terminal.override_atty(sys.stderr, isatty=kwargs.get('isatty')):
+ return FilteredCommand.main(args, repository, command=cls.name, **kwargs)
@classmethod
def main(cls, args, repository, command=None, representation=None, **kwargs):
@@ -234,7 +238,13 @@
line,
)
if header != line:
- sys.stdout.write(header)
+ with Terminal.Style(color=Terminal.Text.yellow, style=Terminal.Text.bold).apply(sys.stdout):
+ sys.stdout.write(' '.join(header.split(' ')[:2]))
+
+ sys.stdout.write(' ')
+ with Terminal.Style(color=Terminal.Text.red).apply(sys.stdout):
+ sys.stdout.write(' '.join(header.split(' ')[2:]))
+
line = log_output.stdout.readline()
continue
@@ -257,6 +267,8 @@
finally:
if not log_output.returncode:
- sys.stderr.write(log_output.stderr.read())
- log_output.kill()
+ with Terminal.Style(color=Terminal.Text.red).apply(sys.stderr):
+ sys.stderr.write(log_output.stderr.read())
+ if log_output.returncode is None:
+ log_output.kill()
return log_output.returncode
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/log.py (280863 => 280864)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/log.py 2021-08-10 21:28:35 UTC (rev 280863)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/log.py 2021-08-10 21:35:31 UTC (rev 280864)
@@ -24,6 +24,7 @@
import sys
+from webkitcorepy import Terminal
from webkitscmpy import local
from webkitscmpy.program.command import FilteredCommand
@@ -34,12 +35,15 @@
@classmethod
def main(cls, args, repository, **kwargs):
+ config = getattr(repository, 'config', lambda: {})()
+ Terminal.colors = config.get('color.diff', config.get('color.ui', 'auto')) != 'false'
return cls.pager(args, repository, file=__file__, **kwargs)
if __name__ == '__main__':
sys.exit(Log.main(
- sys.argv[3:],
+ sys.argv[4:],
local.Scm.from_path(path=sys.argv[1], cached=True),
representation=sys.argv[2],
+ isatty=sys.argv[3] == 'isatty',
))
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/log_unittest.py (280863 => 280864)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/log_unittest.py 2021-08-10 21:28:35 UTC (rev 280863)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/log_unittest.py 2021-08-10 21:35:31 UTC (rev 280864)
@@ -20,13 +20,10 @@
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-import json
import os
-import shutil
-import tempfile
+import sys
-from datetime import datetime
-from webkitcorepy import OutputCapture, testing
+from webkitcorepy import OutputCapture, Terminal, testing
from webkitcorepy.mocks import Time as MockTime
from webkitscmpy import program, mocks
@@ -40,7 +37,7 @@
os.mkdir(os.path.join(self.path, '.svn'))
def test_git(self):
- with OutputCapture() as captured, mocks.local.Git(self.path), mocks.local.Svn(), MockTime:
+ with OutputCapture() as captured, mocks.local.Git(self.path), mocks.local.Svn(), MockTime, Terminal.override_atty(sys.stdin, isatty=False):
self.assertEqual(-1, program.main(
args=('log', 'main'),
path=self.path,
@@ -81,7 +78,7 @@
)
def test_git_svn(self):
- with OutputCapture() as captured, mocks.local.Git(self.path, git_svn=True), mocks.local.Svn(), MockTime:
+ with OutputCapture() as captured, mocks.local.Git(self.path, git_svn=True), mocks.local.Svn(), MockTime, Terminal.override_atty(sys.stdin, isatty=False):
self.assertEqual(-1, program.main(
args=('log', 'main'),
path=self.path,
@@ -127,7 +124,7 @@
)
def test_git_svn_revision(self):
- with OutputCapture() as captured, mocks.local.Git(self.path, git_svn=True), mocks.local.Svn(), MockTime:
+ with OutputCapture() as captured, mocks.local.Git(self.path, git_svn=True), mocks.local.Svn(), MockTime, Terminal.override_atty(sys.stdin, isatty=False):
self.assertEqual(-1, program.main(
args=('log', 'main', '--revision'),
path=self.path,
@@ -174,7 +171,7 @@
)
def test_svn(self):
- with OutputCapture() as captured, mocks.local.Git(), mocks.local.Svn(self.path), MockTime:
+ with OutputCapture() as captured, mocks.local.Git(), mocks.local.Svn(self.path), MockTime, Terminal.override_atty(sys.stdin, isatty=False):
self.assertEqual(-1, program.main(
args=('log',),
path=self.path,