/
nbdiffapp.py
150 lines (123 loc) · 4.69 KB
/
nbdiffapp.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# coding: utf-8
# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import unicode_literals
from __future__ import print_function
import json
import os
import sys
from six import string_types
from .args import (
add_generic_args, add_diff_args, process_diff_flags, resolve_diff_args,
add_diff_cli_args, add_prettyprint_args, ConfigBackedParser,
prettyprint_config_from_args,
Path,
)
from .diffing.notebooks import diff_notebooks
from .gitfiles import changed_notebooks, is_gitref
from .prettyprint import pretty_print_notebook_diff
from .utils import EXPLICIT_MISSING_FILE, read_notebook, setup_std_streams
_description = "Compute the difference between two Jupyter notebooks."
def main_diff(args):
"""Main handler of diff CLI"""
output = getattr(args, 'out', None)
process_diff_flags(args)
base, remote, paths = resolve_diff_args(args)
# We are asked to do a diff of git revisions:
status = 0
for fbase, fremote in list_changed_file_pairs(base, remote, paths):
status = _handle_diff(fbase, fremote, output, args)
if status != 0:
# Short-circuit on error in diff handling
return status
return status
def list_changed_file_pairs(base, remote, paths):
if is_gitref(base) and is_gitref(remote):
for fbase, fremote in changed_notebooks(base, remote, paths):
yield fbase, fremote
else: # Not gitrefs:
yield base, remote
def _handle_diff(base, remote, output, args):
"""Handles diffs of files, either as filenames or file-like objects"""
try:
a, b, d = _build_diff(base, remote, on_null="empty")
except ValueError as e:
print(e, file=sys.stderr)
return 1
# Output as JSON to file, or print to stdout:
if output:
with open(output, "w") as df:
# Compact version:
#json.dump(d, df)
# Verbose version:
json.dump(d, df, indent=2, separators=(",", ": "))
else:
# This printer is to keep the unit tests passing,
# some tests capture output with capsys which doesn't
# pick up on sys.stdout.write()
class Printer:
def write(self, text):
print(text, end="")
# This sets up what to ignore:
config = prettyprint_config_from_args(args, out=Printer())
# Separate out filenames:
base_name = base if isinstance(base, string_types) else base.name
remote_name = remote if isinstance(remote, string_types) else remote.name
pretty_print_notebook_diff(base_name, remote_name, a, d, config)
return 0
def _build_diff(base, remote, on_null):
"""Builds diffs of files, either as filenames or file-like objects"""
# Check that if args are filenames they either exist, or are
# explicitly marked as missing (added/removed):
for fn in (base, remote):
if (isinstance(fn, string_types) and not os.path.exists(fn) and
fn != EXPLICIT_MISSING_FILE):
raise ValueError("Missing file {}".format(fn))
# Both files cannot be missing
assert not (base == EXPLICIT_MISSING_FILE and remote == EXPLICIT_MISSING_FILE), (
'cannot diff %r against %r' % (base, remote))
# Perform actual work:
base_notebook = read_notebook(base, on_null=on_null)
remote_notebook = read_notebook(remote, on_null=on_null)
d = diff_notebooks(base_notebook, remote_notebook)
return base_notebook, remote_notebook, d
def _build_arg_parser(prog=None):
"""Creates an argument parser for the nbdiff command."""
parser = ConfigBackedParser(
description=_description,
prog=prog,
)
add_generic_args(parser)
add_diff_args(parser)
add_diff_cli_args(parser)
add_prettyprint_args(parser)
parser.add_argument(
"base", help="the base notebook filename OR base git-revision.",
type=Path,
nargs='?', default='HEAD',
)
parser.add_argument(
"remote", help="the remote modified notebook filename OR remote git-revision.",
type=Path,
nargs='?', default=None,
)
parser.add_argument(
"paths", help="filter diffs for git-revisions based on path",
type=Path,
nargs='*', default=None,
)
parser.add_argument(
'--out',
type=Path,
default=None,
help="if supplied, the diff is written to this file. "
"Otherwise it is printed to the terminal.")
return parser
def main(args=None):
if args is None:
args = sys.argv[1:]
setup_std_streams()
arguments = _build_arg_parser().parse_args(args)
return main_diff(arguments)
if __name__ == "__main__":
sys.exit(main())