[PATCH] pager: migrate heavily-used extension into core

Bryan O'Sullivan bos at serpentine.com
Fri Feb 3 22:28:37 UTC 2017


# HG changeset patch
# User Bryan O'Sullivan <bryano at fb.com>
# Date 1486160890 28800
#      Fri Feb 03 14:28:10 2017 -0800
# Node ID a4d9e5c20bd72caefcc8efe29a46b6a31a816606
# Parent  abf029200e198878a4576a87e095bd8d77d9cea9
pager: migrate heavily-used extension into core

No default behaviours were harmed during the making of this change.

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -107,6 +107,8 @@ globalopts = [
     ('', 'version', None, _('output version information and exit')),
     ('h', 'help', None, _('display help and exit')),
     ('', 'hidden', False, _('consider hidden changesets')),
+    ('', 'pager', 'auto',
+     _("when to paginate (boolean, always, auto, or never)"), _('TYPE')),
 ]
 
 dryrunopts = [('n', 'dry-run', None,
diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py
--- a/mercurial/dispatch.py
+++ b/mercurial/dispatch.py
@@ -816,6 +816,39 @@ def _dispatch(req):
 def _runcommand(ui, options, cmd, cmdfunc):
     """Run a command function, possibly with profiling enabled."""
     try:
+        p = ui.config("pager", "pager", encoding.environ.get("PAGER"))
+        usepager = False
+        always = util.parsebool(options['pager'])
+        auto = options['pager'] == 'auto'
+
+        if not p or '--debugger' in sys.argv or not ui.formatted():
+            pass
+        elif always:
+            usepager = True
+        elif not auto:
+            usepager = False
+        else:
+            attend = ui.configlist('pager', 'attend', ui.attended)
+            ignore = ui.configlist('pager', 'ignore')
+            cmds, _ = cmdutil.findcmd(cmd, commands.table)
+
+            for cmd in cmds:
+                var = 'attend-%s' % cmd
+                if ui.config('pager', var):
+                    usepager = ui.configbool('pager', var)
+                    break
+                if cmd in attend or (cmd not in ignore and not attend):
+                    usepager = True
+                    break
+
+        ui.pageractive = usepager
+
+        if usepager:
+            ui.setconfig('ui', 'formatted', ui.formatted(), 'pager')
+            ui.setconfig('ui', 'interactive', False, 'pager')
+            if util.safehasattr(signal, "SIGPIPE"):
+                signal.signal(signal.SIGPIPE, signal.SIG_DFL)
+            ui._runpager(p)
         return cmdfunc()
     except error.SignatureError:
         raise error.CommandError(cmd, _('invalid arguments'))
diff --git a/mercurial/extensions.py b/mercurial/extensions.py
--- a/mercurial/extensions.py
+++ b/mercurial/extensions.py
@@ -27,7 +27,7 @@ from . import (
 _aftercallbacks = {}
 _order = []
 _builtin = set(['hbisect', 'bookmarks', 'parentrevspec', 'progress', 'interhg',
-                'inotify', 'hgcia'])
+                'inotify', 'hgcia', 'pager'])
 
 def extensions(ui=None):
     if ui:
diff --git a/mercurial/help.py b/mercurial/help.py
--- a/mercurial/help.py
+++ b/mercurial/help.py
@@ -230,6 +230,7 @@ helptable = sorted([
      loaddoc('scripting')),
     (['internals'], _("Technical implementation topics"),
      internalshelp),
+    (["pager"], _("Using an External Pager"), loaddoc('pager')),
 ])
 
 # Maps topics with sub-topics to a list of their sub-topics.
diff --git a/hgext/pager.py b/mercurial/help/pager.txt
rename from hgext/pager.py
rename to mercurial/help/pager.txt
--- a/hgext/pager.py
+++ b/mercurial/help/pager.txt
@@ -1,19 +1,3 @@
-# pager.py - display output using a pager
-#
-# Copyright 2008 David Soria Parra <dsp at php.net>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-#
-# To load the extension, add it to your configuration file:
-#
-#   [extension]
-#   pager =
-#
-# Run 'hg help pager' to get info on configuration.
-
-'''browse command output with an external pager
-
 To set the pager that should be used, set the application variable::
 
   [pager]
@@ -56,119 +40,3 @@ you can use --pager=<value>::
   - require the pager: `yes` or `on`.
   - suppress the pager: `no` or `off` (any unrecognized value
   will also work).
-
-'''
-from __future__ import absolute_import
-
-import atexit
-import os
-import signal
-import subprocess
-import sys
-
-from mercurial.i18n import _
-from mercurial import (
-    cmdutil,
-    commands,
-    dispatch,
-    encoding,
-    extensions,
-    util,
-    )
-
-# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
-# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
-# be specifying the version(s) of Mercurial they are tested with, or
-# leave the attribute unspecified.
-testedwith = 'ships-with-hg-core'
-
-def _runpager(ui, p):
-    pager = subprocess.Popen(p, shell=True, bufsize=-1,
-                             close_fds=util.closefds, stdin=subprocess.PIPE,
-                             stdout=util.stdout, stderr=util.stderr)
-
-    # back up original file objects and descriptors
-    olduifout = ui.fout
-    oldstdout = util.stdout
-    stdoutfd = os.dup(util.stdout.fileno())
-    stderrfd = os.dup(util.stderr.fileno())
-
-    # create new line-buffered stdout so that output can show up immediately
-    ui.fout = util.stdout = newstdout = os.fdopen(util.stdout.fileno(), 'wb', 1)
-    os.dup2(pager.stdin.fileno(), util.stdout.fileno())
-    if ui._isatty(util.stderr):
-        os.dup2(pager.stdin.fileno(), util.stderr.fileno())
-
-    @atexit.register
-    def killpager():
-        if util.safehasattr(signal, "SIGINT"):
-            signal.signal(signal.SIGINT, signal.SIG_IGN)
-        pager.stdin.close()
-        ui.fout = olduifout
-        util.stdout = oldstdout
-        # close new stdout while it's associated with pager; otherwise stdout
-        # fd would be closed when newstdout is deleted
-        newstdout.close()
-        # restore original fds: stdout is open again
-        os.dup2(stdoutfd, util.stdout.fileno())
-        os.dup2(stderrfd, util.stderr.fileno())
-        pager.wait()
-
-def uisetup(ui):
-    class pagerui(ui.__class__):
-        def _runpager(self, pagercmd):
-            _runpager(self, pagercmd)
-
-    ui.__class__ = pagerui
-
-    def pagecmd(orig, ui, options, cmd, cmdfunc):
-        p = ui.config("pager", "pager", encoding.environ.get("PAGER"))
-        usepager = False
-        always = util.parsebool(options['pager'])
-        auto = options['pager'] == 'auto'
-
-        if not p or '--debugger' in sys.argv or not ui.formatted():
-            pass
-        elif always:
-            usepager = True
-        elif not auto:
-            usepager = False
-        else:
-            attend = ui.configlist('pager', 'attend', attended)
-            ignore = ui.configlist('pager', 'ignore')
-            cmds, _ = cmdutil.findcmd(cmd, commands.table)
-
-            for cmd in cmds:
-                var = 'attend-%s' % cmd
-                if ui.config('pager', var):
-                    usepager = ui.configbool('pager', var)
-                    break
-                if (cmd in attend or
-                     (cmd not in ignore and not attend)):
-                    usepager = True
-                    break
-
-        setattr(ui, 'pageractive', usepager)
-
-        if usepager:
-            ui.setconfig('ui', 'formatted', ui.formatted(), 'pager')
-            ui.setconfig('ui', 'interactive', False, 'pager')
-            if util.safehasattr(signal, "SIGPIPE"):
-                signal.signal(signal.SIGPIPE, signal.SIG_DFL)
-            ui._runpager(p)
-        return orig(ui, options, cmd, cmdfunc)
-
-    # Wrap dispatch._runcommand after color is loaded so color can see
-    # ui.pageractive. Otherwise, if we loaded first, color's wrapped
-    # dispatch._runcommand would run without having access to ui.pageractive.
-    def afterloaded(loaded):
-        extensions.wrapfunction(dispatch, '_runcommand', pagecmd)
-    extensions.afterloaded('color', afterloaded)
-
-def extsetup(ui):
-    commands.globalopts.append(
-        ('', 'pager', 'auto',
-         _("when to paginate (boolean, always, auto, or never)"),
-         _('TYPE')))
-
-attended = ['annotate', 'cat', 'diff', 'export', 'glog', 'log', 'qdiff']
diff --git a/mercurial/statprof.py b/mercurial/statprof.py
--- a/mercurial/statprof.py
+++ b/mercurial/statprof.py
@@ -130,7 +130,7 @@ skips = set(["util.py:check", "extension
              "color.py:colorcmd", "dispatch.py:checkargs",
              "dispatch.py:<lambda>", "dispatch.py:_runcatch",
              "dispatch.py:_dispatch", "dispatch.py:_runcommand",
-             "pager.py:pagecmd", "dispatch.py:run",
+             "dispatch.py:run",
              "dispatch.py:dispatch", "dispatch.py:runcommand",
              "hg.py:<module>", "evolve.py:warnobserrors",
          ])
diff --git a/mercurial/ui.py b/mercurial/ui.py
--- a/mercurial/ui.py
+++ b/mercurial/ui.py
@@ -7,13 +7,16 @@
 
 from __future__ import absolute_import
 
+import atexit
 import contextlib
 import errno
 import getpass
 import inspect
 import os
 import re
+import signal
 import socket
+import subprocess
 import sys
 import tempfile
 import traceback
@@ -94,6 +97,10 @@ default = %s
 # pager =""",
 }
 
+# commands whose output may be run through the pager
+# (this is a top-level global in case extensions need to add to it)
+pagedcommands = ['annotate', 'cat', 'diff', 'export', 'glog', 'log', 'qdiff']
+
 class ui(object):
     def __init__(self, src=None):
         """Create a fresh new ui object if no src given
@@ -102,6 +109,7 @@ class ui(object):
         In most cases, you should use ui.copy() to create a copy of an existing
         ui object.
         """
+        self.pageractive = False
         # _buffers: used for temporary capture of output
         self._buffers = []
         # 3-tuple describing how each buffer in the stack behaves.
@@ -1251,6 +1259,39 @@ class ui(object):
             if ('ui', 'quiet') in overrides:
                 self.fixconfig(section='ui')
 
+    def _runpager(self, p):
+        pager = subprocess.Popen(p, shell=True, bufsize=-1,
+                                 close_fds=util.closefds, stdin=subprocess.PIPE,
+                                 stdout=util.stdout, stderr=util.stderr)
+
+        # back up original file objects and descriptors
+        olduifout = self.fout
+        oldstdout = util.stdout
+        stdoutfd = os.dup(util.stdout.fileno())
+        stderrfd = os.dup(util.stderr.fileno())
+
+        # create new line-buffered stdout so that output can show up immediately
+        self.fout = util.stdout = newstdout = os.fdopen(util.stdout.fileno(),
+                                                        'wb', 1)
+        os.dup2(pager.stdin.fileno(), util.stdout.fileno())
+        if self._isatty(util.stderr):
+            os.dup2(pager.stdin.fileno(), util.stderr.fileno())
+
+        @atexit.register
+        def killpager():
+            if util.safehasattr(signal, "SIGINT"):
+                signal.signal(signal.SIGINT, signal.SIG_IGN)
+            pager.stdin.close()
+            self.fout = olduifout
+            util.stdout = oldstdout
+            # close new stdout while it's associated with pager; otherwise
+            # stdout fd would be closed when newstdout is deleted
+            newstdout.close()
+            # restore original fds: stdout is open again
+            os.dup2(stdoutfd, util.stdout.fileno())
+            os.dup2(stderrfd, util.stderr.fileno())
+            pager.wait()
+
 class paths(dict):
     """Represents a collection of paths and their configs.
 
diff --git a/tests/test-chg.t b/tests/test-chg.t
--- a/tests/test-chg.t
+++ b/tests/test-chg.t
@@ -41,13 +41,11 @@ pager
   >     sys.stdout.write('paged! %r\n' % line)
   > EOF
 
-enable pager extension globally, but spawns the master server with no tty:
+enable pager globally, but spawn the master server with no tty:
 
   $ chg init pager
   $ cd pager
   $ cat >> $HGRCPATH <<EOF
-  > [extensions]
-  > pager =
   > [pager]
   > pager = python $TESTTMP/fakepager.py
   > EOF
diff --git a/tests/test-completion.t b/tests/test-completion.t
--- a/tests/test-completion.t
+++ b/tests/test-completion.t
@@ -138,6 +138,7 @@ Show the global options
   --help
   --hidden
   --noninteractive
+  --pager
   --profile
   --quiet
   --repository
@@ -171,6 +172,7 @@ Show the options for the "serve" command
   --ipv6
   --name
   --noninteractive
+  --pager
   --pid-file
   --port
   --prefix
diff --git a/tests/test-extension.t b/tests/test-extension.t
--- a/tests/test-extension.t
+++ b/tests/test-extension.t
@@ -543,6 +543,8 @@ hide outer repo
       --version           output version information and exit
    -h --help              display help and exit
       --hidden            consider hidden changesets
+      --pager TYPE        when to paginate (boolean, always, auto, or never)
+                          (default: auto)
 
 
 
@@ -578,6 +580,8 @@ hide outer repo
       --version           output version information and exit
    -h --help              display help and exit
       --hidden            consider hidden changesets
+      --pager TYPE        when to paginate (boolean, always, auto, or never)
+                          (default: auto)
 
 
 
@@ -856,6 +860,8 @@ extension help itself
       --version           output version information and exit
    -h --help              display help and exit
       --hidden            consider hidden changesets
+      --pager TYPE        when to paginate (boolean, always, auto, or never)
+                          (default: auto)
 
 Make sure that single '-v' option shows help and built-ins only for 'dodo' command
   $ hg help -v dodo
@@ -889,6 +895,8 @@ Make sure that single '-v' option shows 
       --version           output version information and exit
    -h --help              display help and exit
       --hidden            consider hidden changesets
+      --pager TYPE        when to paginate (boolean, always, auto, or never)
+                          (default: auto)
 
 In case when extension name doesn't match any of its commands,
 help message should ask for '-v' to get list of built-in aliases
@@ -960,6 +968,8 @@ help options '-v' and '-v -e' should be 
       --version           output version information and exit
    -h --help              display help and exit
       --hidden            consider hidden changesets
+      --pager TYPE        when to paginate (boolean, always, auto, or never)
+                          (default: auto)
 
   $ hg help -v -e dudu
   dudu extension -
@@ -992,6 +1002,8 @@ help options '-v' and '-v -e' should be 
       --version           output version information and exit
    -h --help              display help and exit
       --hidden            consider hidden changesets
+      --pager TYPE        when to paginate (boolean, always, auto, or never)
+                          (default: auto)
 
 Disabled extension commands:
 
diff --git a/tests/test-globalopts.t b/tests/test-globalopts.t
--- a/tests/test-globalopts.t
+++ b/tests/test-globalopts.t
@@ -351,6 +351,7 @@ Testing -h/--help:
    hgweb         Configuring hgweb
    internals     Technical implementation topics
    merge-tools   Merge Tools
+   pager         Using an External Pager
    patterns      File Name Patterns
    phases        Working with Phases
    revisions     Specifying Revisions
@@ -432,6 +433,7 @@ Testing -h/--help:
    hgweb         Configuring hgweb
    internals     Technical implementation topics
    merge-tools   Merge Tools
+   pager         Using an External Pager
    patterns      File Name Patterns
    phases        Working with Phases
    revisions     Specifying Revisions
diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -113,6 +113,7 @@ Short help:
    hgweb         Configuring hgweb
    internals     Technical implementation topics
    merge-tools   Merge Tools
+   pager         Using an External Pager
    patterns      File Name Patterns
    phases        Working with Phases
    revisions     Specifying Revisions
@@ -188,6 +189,7 @@ Short help:
    hgweb         Configuring hgweb
    internals     Technical implementation topics
    merge-tools   Merge Tools
+   pager         Using an External Pager
    patterns      File Name Patterns
    phases        Working with Phases
    revisions     Specifying Revisions
@@ -262,7 +264,6 @@ Test extension help:
        largefiles    track large binary files
        mq            manage a stack of patches
        notify        hooks for sending email push notifications
-       pager         browse command output with an external pager
        patchbomb     command to send changesets as (a series of) patch emails
        purge         command to delete untracked files from the working
                      directory
@@ -326,6 +327,8 @@ Test short command list with verbose opt
       --version           output version information and exit
    -h --help              display help and exit
       --hidden            consider hidden changesets
+      --pager TYPE        when to paginate (boolean, always, auto, or never)
+                          (default: auto)
   
   (use 'hg help' for the full list of commands)
 
@@ -422,6 +425,8 @@ Verbose help for add
       --version           output version information and exit
    -h --help              display help and exit
       --hidden            consider hidden changesets
+      --pager TYPE        when to paginate (boolean, always, auto, or never)
+                          (default: auto)
 
 Test the textwidth config option
 
@@ -827,6 +832,7 @@ Test that default list of commands omits
    hgweb         Configuring hgweb
    internals     Technical implementation topics
    merge-tools   Merge Tools
+   pager         Using an External Pager
    patterns      File Name Patterns
    phases        Working with Phases
    revisions     Specifying Revisions
@@ -1914,6 +1920,13 @@ Dish up an empty repo; serve it cold.
   Merge Tools
   </td></tr>
   <tr><td>
+  <a href="/help/pager">
+  pager
+  </a>
+  </td><td>
+  Using an External Pager
+  </td></tr>
+  <tr><td>
   <a href="/help/patterns">
   patterns
   </a>
@@ -2523,6 +2536,9 @@ Dish up an empty repo; serve it cold.
   <tr><td></td>
   <td>--hidden</td>
   <td>consider hidden changesets</td></tr>
+  <tr><td></td>
+  <td>--pager TYPE</td>
+  <td>when to paginate (boolean, always, auto, or never) (default: auto)</td></tr>
   </table>
   
   </div>
@@ -2718,6 +2734,9 @@ Dish up an empty repo; serve it cold.
   <tr><td></td>
   <td>--hidden</td>
   <td>consider hidden changesets</td></tr>
+  <tr><td></td>
+  <td>--pager TYPE</td>
+  <td>when to paginate (boolean, always, auto, or never) (default: auto)</td></tr>
   </table>
   
   </div>
diff --git a/tests/test-hgweb-json.t b/tests/test-hgweb-json.t
--- a/tests/test-hgweb-json.t
+++ b/tests/test-hgweb-json.t
@@ -1593,6 +1593,10 @@ help/ shows help topics
         "topic": "merge-tools"
       },
       {
+        "summary": "Using an External Pager",
+        "topic": "pager"
+      },
+      {
         "summary": "File Name Patterns",
         "topic": "patterns"
       },
diff --git a/tests/test-install.t b/tests/test-install.t
--- a/tests/test-install.t
+++ b/tests/test-install.t
@@ -166,6 +166,7 @@ path variables are expanded (~ is the sa
     help/hg.1.txt
     help/hgignore.5.txt
     help/hgrc.5.txt
+    help/pager.txt
   Not tracked:
 
   $ python wixxml.py templates
diff --git a/tests/test-pager.t b/tests/test-pager.t
--- a/tests/test-pager.t
+++ b/tests/test-pager.t
@@ -10,8 +10,6 @@ pager was running.
   $ cat >> $HGRCPATH <<EOF
   > [ui]
   > formatted = yes
-  > [extensions]
-  > pager=
   > [pager]
   > pager = python $TESTTMP/fakepager.py
   > EOF


More information about the Mercurial-devel mailing list