This is a diff of release 1.1 and master commit c3771b98e4379c85c2e9be67e19301ae42a17b9e. The changes to .travis.yml are removed and the perforce tests, a version control system, are excluded. --- diff --git a/README.rst b/README.rst index fe51150..90a8861 100644 --- a/README.rst +++ b/README.rst @@ -5,9 +5,10 @@ Ydiff :target: https://travis-ci.org/ymattw/ydiff :alt: Build status -Term based tool to view *colored*, *incremental* diff in a *Git/Mercurial/Svn* -workspace or from stdin, with *side by side* (similar to ``diff -y``) and *auto -pager* support. Requires python (>= 2.5.0) and ``less``. +Term based tool to view *colored*, *incremental* diff in a version controlled +workspace (supports Git, Mercurial, Perforce and Svn so far) or from stdin, +with *side by side* (similar to ``diff -y``) and *auto pager* support. Requires +python (>= 2.5.0) and ``less``. .. image:: https://raw.github.com/ymattw/ydiff/gh-pages/img/default.png :alt: default @@ -61,6 +62,25 @@ You can also install with Homebrew on Mac. (Thanks to `@josa42`_, brew install ydiff + +Install on Fedora +~~~~~~~~~~~~~~~~~ + +On Fedora, you can install ydiff with dnf. + +.. code-block:: bash + + dnf install ydiff + +Install on FreeBSD +~~~~~~~~~~~~~~~~~~ + +On FreeBSD, you can install ydiff with pkg. + +.. code-block:: bash + + pkg install ydiff + Download directly ~~~~~~~~~~~~~~~~~ @@ -96,6 +116,9 @@ Type ``ydiff -h`` to show usage:: -c M, --color=M colorize mode 'auto' (default), 'always', or 'never' -t N, --tab-width=N convert tab characters to this many spaces (default: 8) --wrap wrap long lines in side-by-side view + -p M, --pager=M pager application, suggested values are 'less' or 'cat' + -o M, --pager-options=M + options to supply to pager application Note: Option parser will stop on first unknown option and pass them down to @@ -148,6 +171,16 @@ as follows: ydiff_dir=$(dirname $(which ydiff)) ln -s "${ydiff_dir}/ydiff" "${ydiff_dir}/git-ydiff" +Utilize a specific pager application: + +.. code-block:: bash + + ydiff # default pager - less + LESS_OPTS='-FRSX --shift 1' + ydiff -p less -o "${LESS_OPTS}" # emulate default pager + ydiff -p /usr/bin/less # custom pager + ydiff -p cat # non-paging ANSI processor for colorizing + Pipe in a diff: .. code-block:: bash diff --git a/ydiff.py b/ydiff.py index 4d2605c..dfaffa7 100755 --- a/ydiff.py +++ b/ydiff.py @@ -59,7 +59,7 @@ COLORS = { 'lightcyan' : '\x1b[1;36m', } -# Keys for revision control probe, diff and log with diff +# Keys for revision control probe, diff and log (optional) with diff VCS_INFO = { 'Git': { 'probe': ['git', 'rev-parse'], @@ -71,6 +71,11 @@ VCS_INFO = { 'diff': ['hg', 'diff'], 'log': ['hg', 'log', '--patch'], }, + 'Perforce': { + 'probe': ['p4', 'dirs', '.'], + 'diff': ['p4', 'diff'], + 'log': None, + }, 'Svn': { 'probe': ['svn', 'info'], 'diff': ['svn', 'diff'], @@ -79,6 +84,26 @@ VCS_INFO = { } +def revision_control_probe(): + """Returns version control name (key in VCS_INFO) or None.""" + for vcs_name, ops in VCS_INFO.items(): + if check_command_status(ops.get('probe')): + return vcs_name + + +def revision_control_diff(vcs_name, args): + """Return diff from revision control system.""" + cmd = VCS_INFO[vcs_name]['diff'] + return subprocess.Popen(cmd + args, stdout=subprocess.PIPE).stdout + + +def revision_control_log(vcs_name, args): + """Return log from revision control system or None.""" + cmd = VCS_INFO[vcs_name].get('log') + if cmd is not None: + return subprocess.Popen(cmd + args, stdout=subprocess.PIPE).stdout + + def colorize(text, start_color, end_color='reset'): return COLORS[start_color] + text + COLORS[end_color] @@ -299,8 +324,11 @@ class PatchStream(object): def __iter__(self): for line in self._stream_header: yield line - for line in self._diff_hdl: - yield line + try: + for line in self._diff_hdl: + yield line + except RuntimeError: + return class PatchStreamForwarder(object): @@ -715,10 +743,20 @@ def markup_to_pager(stream, opts): See issue #30 (https://github.com/ymattw/ydiff/issues/30) for more information. """ - pager_cmd = ['less'] - if not os.getenv('LESS'): - # Args stolen from git source: github.com/git/git/blob/master/pager.c - pager_cmd.extend(['-FRSX', '--shift 1']) + pager_cmd = [opts.pager] + pager_opts = (opts.pager_options.split(' ') + if opts.pager_options is not None + else None) + + if opts.pager is None: + pager_cmd = ['less'] + if not os.getenv('LESS') and not opts.pager_options: + # Args stolen from git source: + # github.com/git/git/blob/master/pager.c + pager_opts = ['-FRSX', '--shift 1'] + + pager_opts = pager_opts if pager_opts is not None else [] + pager_cmd.extend(pager_opts) pager = subprocess.Popen( pager_cmd, stdin=subprocess.PIPE, stdout=sys.stdout) @@ -743,22 +781,6 @@ def check_command_status(arguments): return False -def revision_control_diff(args): - """Return diff from revision control system.""" - for _, ops in VCS_INFO.items(): - if check_command_status(ops['probe']): - return subprocess.Popen( - ops['diff'] + args, stdout=subprocess.PIPE).stdout - - -def revision_control_log(args): - """Return log from revision control system.""" - for _, ops in VCS_INFO.items(): - if check_command_status(ops['probe']): - return subprocess.Popen( - ops['log'] + args, stdout=subprocess.PIPE).stdout - - def decode(line): """Decode UTF-8 if necessary.""" if isinstance(line, unicode): @@ -815,8 +837,6 @@ def main(): rargs.insert(parsed_num, '--') OptionParser._process_args(self, largs, rargs, values) - supported_vcs = sorted(VCS_INFO.keys()) - usage = """%prog [options] [file|dir ...]""" parser = PassThroughOptionParser( usage=usage, description=META_INFO['description'], @@ -840,6 +860,13 @@ def main(): parser.add_option( '', '--wrap', action='store_true', help='wrap long lines in side-by-side view') + parser.add_option( + '-p', '--pager', metavar='M', + help="""pager application, suggested values are 'less' """ + """or 'cat'""") + parser.add_option( + '-o', '--pager-options', metavar='M', + help="""options to supply to pager application""") # Hack: use OptionGroup text for extra help message after option list option_group = OptionGroup( @@ -864,22 +891,26 @@ def main(): opts, args = parser.parse_args(ydiff_opts + sys.argv[1:]) - if opts.log: - diff_hdl = revision_control_log(args) - if not diff_hdl: - sys.stderr.write(('*** Not in a supported workspace, supported ' - 'are: %s\n') % ', '.join(supported_vcs)) - return 1 - elif sys.stdin.isatty(): - diff_hdl = revision_control_diff(args) - if not diff_hdl: - sys.stderr.write(('*** Not in a supported workspace, supported ' - 'are: %s\n\n') % ', '.join(supported_vcs)) - parser.print_help() - return 1 - else: + if not sys.stdin.isatty(): diff_hdl = (sys.stdin.buffer if hasattr(sys.stdin, 'buffer') else sys.stdin) + else: + vcs_name = revision_control_probe() + if vcs_name is None: + supported_vcs = ', '.join(sorted(VCS_INFO.keys())) + sys.stderr.write('*** Not in a supported workspace, supported are:' + ' %s\n' % supported_vcs) + return 1 + + if opts.log: + diff_hdl = revision_control_log(vcs_name, args) + if diff_hdl is None: + sys.stderr.write('*** %s does not support log command.\n' % + vcs_name) + return 1 + else: + # 'diff' is a must have feature. + diff_hdl = revision_control_diff(vcs_name, args) stream = PatchStream(diff_hdl)