Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resolves icdiff module usage #75

Closed
wants to merge 11 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# Created by .ignore support plugin (hsz.mobi)
### Python template
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# dotenv
.env

# virtualenv
.venv/
venv/
ENV/

# Spyder project settings
.spyderproject

# Rope project settings
.ropeproject

8 changes: 8 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
language: python
sudo: false

python:
- "2.7"

script:
- ./test.sh python2
14 changes: 14 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
1.9.1
Works as python API. For example
>>> from icdiff import diff
>>> diff(pre, post)
python 3.x bug fixes


1.9.0
Fix setup.py by symlinking icdiff to icdiff.py

1.8.2
Add short flags for --highlight (-H), --line-numbers (-N), and --whole-file (-W).
Fix use with bash process substitution and other special files

1.8.1
Updated remaining copy of unicode test file (b/1)

Expand Down
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@

Improved colored diff

![screenshot](http://www.jefftk.com/icdiff-css-demo.png)

## Installation

Download the [latest](releases/latest) `icdiff` binary and put it on your PATH.
Download the [latest](https://github.com/jeffkaufman/icdiff/releases) `icdiff` binary and put it on your PATH.

Alternatively, install with pip:
```
pip install git+https://github.com/jeffkaufman/icdiff.git
```

##Usage
## Usage

```sh
icdiff [options] left_file right_file
Expand All @@ -27,13 +29,13 @@ Show differences between files in a two column view.
only
--encoding=ENCODING specify the file encoding; defaults to utf8
--head=HEAD consider only the first N lines of each file
--highlight color by changing the background color instead of the
-H, --highlight color by changing the background color instead of the
foreground color. Very fast, ugly, displays all
changes
-L LABELS, --label=LABELS
override file labels with arbitrary tags. Use twice,
one for each file
--line-numbers generate output with line numbers
-N, --line-numbers generate output with line numbers
--no-bold use non-bold colors; recommended for with solarized
--no-headers don't label the left and right sides with their file
names
Expand All @@ -49,7 +51,7 @@ Show differences between files in a two column view.
-U NUM, --unified=NUM, --numlines=NUM
how many lines of context to print; can't be combined
with --whole-file
--whole-file show the whole file instead of just changed lines and
-W, --whole-file show the whole file instead of just changed lines and
context
```

Expand Down Expand Up @@ -78,6 +80,21 @@ To try it out, run:
svn diff --diff-cmd icdiff
```

## Using with Mercurial

Add the following to your `~/.hgrc`:

```sh
[extensions]
extdiff=

[extdiff]
cmd.icdiff=icdiff
opts.icdiff=--recursive --line-numbers
```

Or check more [in-depth setup instructions](http://ianobermiller.com/blog/2016/07/14/side-by-side-diffs-for-mercurial-hg-icdiff-revisited/).

## Running tests

```sh
Expand Down
93 changes: 66 additions & 27 deletions icdiff
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ import os
import sys
import errno
import difflib
from optparse import Option, OptionParser
from optparse import Option, OptionParser, BadOptionError
import re
import filecmp
import unicodedata
import codecs

__version__ = "1.8.1"
__version__ = "1.9.1"

color_codes = {
"red": '\033[0;31m',
Expand All @@ -40,6 +40,7 @@ color_codes = {
"cyan_bold": '\033[1;36m',
}


class ConsoleDiff(object):
"""Console colored side by side comparison with change highlights.

Expand Down Expand Up @@ -114,7 +115,7 @@ class ConsoleDiff(object):
line = line.replace(' ', '\0')
# expand tabs into spaces
line = line.expandtabs(self._tabsize)
# relace spaces from expanded tabs back into tab characters
# replace spaces from expanded tabs back into tab characters
# (we'll replace them with markup after we do differencing)
line = line.replace(' ', '\t')
return line.replace('\0', ' ').rstrip('\n')
Expand All @@ -123,7 +124,7 @@ class ConsoleDiff(object):
return fromlines, tolines

def _display_len(self, s):
# Handle wide characters like chinese.
# Handle wide characters like Chinese.
def width(c):
if ((isinstance(c, type(u"")) and
unicodedata.east_asian_width(c) == 'W')):
Expand Down Expand Up @@ -407,11 +408,13 @@ class ConsoleDiff(object):
def simple_colorize(s, chosen_color):
return "%s%s%s" % (color_codes[chosen_color], s, color_codes["none"])


def replace_all(replacements, string):
for search, replace in replacements.items():
string = string.replace(search, replace)
return string


class MultipleOption(Option):
ACTIONS = Option.ACTIONS + ("extend",)
STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",)
Expand All @@ -425,9 +428,24 @@ class MultipleOption(Option):
Option.take_action(self, action, dest, opt, value, values, parser)


def start():
class PassThroughOptionParser(OptionParser):

def _process_long_opt(self, rargs, values):
try:
OptionParser._process_long_opt(self, rargs, values)
except BadOptionError as err:
self.largs.append(err.opt_str)

def _process_short_opts(self, rargs, values):
try:
OptionParser._process_short_opts(self, rargs, values)
except BadOptionError as err:
self.largs.append(err.opt_str)


def get_options():
# If you change any of these, also update README.
parser = OptionParser(usage="usage: %prog [options] left_file right_file",
parser = PassThroughOptionParser(usage="usage: %prog [options] left_file right_file",
version="icdiff version %s" % __version__,
description="Show differences between files in a "
"two column view.",
Expand All @@ -439,7 +457,7 @@ def start():
help="specify the file encoding; defaults to utf8")
parser.add_option("--head", default=0,
help="consider only the first N lines of each file")
parser.add_option("--highlight", default=False,
parser.add_option("-H", "--highlight", default=False,
action="store_true",
help="color by changing the background color instead of "
"the foreground color. Very fast, ugly, displays all "
Expand All @@ -450,7 +468,7 @@ def start():
dest='labels',
help="override file labels with arbitrary tags. "
"Use twice, one for each file")
parser.add_option("--line-numbers", default=False,
parser.add_option("-N", "--line-numbers", default=False,
action="store_true",
help="generate output with line numbers")
parser.add_option("--no-bold", default=False,
Expand Down Expand Up @@ -480,19 +498,12 @@ def start():
metavar="NUM",
help="how many lines of context to print; "
"can't be combined with --whole-file")
parser.add_option("--whole-file", default=False,
parser.add_option("-W", "--whole-file", default=False,
action="store_true",
help="show the whole file instead of just changed "
"lines and context")

(options, args) = parser.parse_args()

if len(args) != 2:
parser.print_help()
sys.exit()

a, b = args

if not options.cols:
def ioctl_GWINSZ(fd):
try:
Expand All @@ -509,8 +520,21 @@ def start():
options.cols = cr[1]
else:
options.cols = 80
return options, args, parser


def validate_has_two_arguments(parser, args):
if len(args) != 2:
parser.print_help()
sys.exit()


def start():
options, args, parser = get_options()
validate_has_two_arguments(parser, args)
a, b = args
diff(a, b, options=options)

diff(options, a, b)

def codec_print(s, options):
s = "%s\n" % s
Expand All @@ -520,15 +544,29 @@ def codec_print(s, options):
sys.stdout.write(s.encode(options.output_encoding))


def diff(options, a, b):
def diff(a, b, options=None):
if options is None:
options = get_options()[0]

def print_meta(s):
codec_print(simple_colorize(s, "magenta"), options)

if os.path.isfile(a) and os.path.isfile(b):
if not filecmp.cmp(a, b, shallow=False):
diff_files(options, a, b)
# Don't use os.path.isfile; it returns False for file-like entities like
# bash's process substitution (/dev/fd/N).
is_a_file = not os.path.isdir(a)
is_b_file = not os.path.isdir(b)

if is_a_file and is_b_file:
try:
if not filecmp.cmp(a, b, shallow=False):
diff_files(options, a, b)
except OSError as ex:
if ex.errno == errno.ENOENT:
print_meta("error: file '%s' was not found" % ex.filename)
else:
raise ex

elif os.path.isdir(a) and os.path.isdir(b):
elif not is_a_file and not is_b_file:
a_contents = set(os.listdir(a))
b_contents = set(os.listdir(b))

Expand All @@ -538,15 +576,15 @@ def diff(options, a, b):
elif child not in a_contents:
print_meta("Only in %s: %s" % (b, child))
elif options.recursive:
diff(options,
os.path.join(a, child),
os.path.join(b, child))
elif os.path.isdir(a) and os.path.isfile(b):
diff(os.path.join(a, child),
os.path.join(b, child), options)
elif not is_a_file and is_b_file:
print_meta("File %s is a directory while %s is a file" % (a, b))

elif os.path.isfile(a) and os.path.isdir(b):
elif is_a_file and not is_b_file:
print_meta("File %s is a file while %s is a directory" % (a, b))


def read_file(fname, options):
try:
with codecs.open(fname, encoding=options.encoding, mode="rb") as inf:
Expand Down Expand Up @@ -609,3 +647,4 @@ if __name__ == "__main__":
pass
else:
raise

1 change: 1 addition & 0 deletions icdiff.py
Loading