Skip to content

Commit a1f12b6

Browse files
committed
Add nbdiff-web-export to export a diff inside a rendered HTML
nbdiff-web-export --ouput-dir output ./nbdime/webapp/testnotebooks/remote.ipynb ./nbdime/webapp/testnotebooks/base.ipynb or nbdiff-web-export --output-dir output HEAD~1 HEAD These would generate inside output directory diff1.html diff2.html .... diffN.html depending how many files has been changed it would also drop nbdime.js in the output directory Alternatively one can use --nbdime_url optional parameter to specify where to get nbdime.js
1 parent f6beb60 commit a1f12b6

File tree

8 files changed

+192
-29
lines changed

8 files changed

+192
-29
lines changed

nbdime/args.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ def add_filter_args(diff_parser):
369369

370370

371371
def add_git_diff_driver_args(diff_parser):
372-
"""Adds a set of 7 stanard git diff driver arguments:
372+
"""Adds a set of 7 standard git diff driver arguments:
373373
path old-file old-hex old-mode new-file new-hex new-mode [ rename-to rename-metadata ]
374374
375375
Note: Only path, base and remote are added to parsed namespace

nbdime/nbdiffapp.py

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,38 +33,27 @@ def main_diff(args):
3333
process_diff_flags(args)
3434
base, remote, paths = resolve_diff_args(args)
3535

36-
# Check if base/remote are gitrefs:
36+
# We are asked to do a diff of git revisions:
37+
status = 0
38+
for fbase, fremote in list_changed_file_pairs(base, remote, paths):
39+
status = _handle_diff(fbase, fremote, output, args)
40+
if status != 0:
41+
# Short-circuit on error in diff handling
42+
return status
43+
return status
44+
45+
46+
def list_changed_file_pairs(base, remote, paths):
3747
if is_gitref(base) and is_gitref(remote):
38-
# We are asked to do a diff of git revisions:
39-
status = 0
4048
for fbase, fremote in changed_notebooks(base, remote, paths):
41-
status = _handle_diff(fbase, fremote, output, args)
42-
if status != 0:
43-
# Short-circuit on error in diff handling
44-
return status
45-
return status
49+
yield fbase, fremote
4650
else: # Not gitrefs:
47-
return _handle_diff(base, remote, output, args)
51+
yield base, remote
4852

4953

5054
def _handle_diff(base, remote, output, args):
5155
"""Handles diffs of files, either as filenames or file-like objects"""
52-
# Check that if args are filenames they either exist, or are
53-
# explicitly marked as missing (added/removed):
54-
for fn in (base, remote):
55-
if (isinstance(fn, string_types) and not os.path.exists(fn) and
56-
fn != EXPLICIT_MISSING_FILE):
57-
print("Missing file {}".format(fn))
58-
return 1
59-
# Both files cannot be missing
60-
assert not (base == EXPLICIT_MISSING_FILE and remote == EXPLICIT_MISSING_FILE), (
61-
'cannot diff %r against %r' % (base, remote))
62-
63-
# Perform actual work:
64-
a = read_notebook(base, on_null='empty')
65-
b = read_notebook(remote, on_null='empty')
66-
67-
d = diff_notebooks(a, b)
56+
a, b, d = _build_diff(base, remote, on_null="empty")
6857

6958
# Output as JSON to file, or print to stdout:
7059
if output:
@@ -90,6 +79,26 @@ def write(self, text):
9079
return 0
9180

9281

82+
def _build_diff(base, remote, on_null):
83+
"""Builds diffs of files, either as filenames or file-like objects"""
84+
# Check that if args are filenames they either exist, or are
85+
# explicitly marked as missing (added/removed):
86+
for fn in (base, remote):
87+
if (isinstance(fn, string_types) and not os.path.exists(fn) and
88+
fn != EXPLICIT_MISSING_FILE):
89+
print("Missing file {}".format(fn))
90+
return 1
91+
# Both files cannot be missing
92+
assert not (base == EXPLICIT_MISSING_FILE and remote == EXPLICIT_MISSING_FILE), (
93+
'cannot diff %r against %r' % (base, remote))
94+
95+
# Perform actual work:
96+
base_notebook = read_notebook(base, on_null=on_null)
97+
remote_notebook = read_notebook(remote, on_null=on_null)
98+
99+
d = diff_notebooks(base_notebook, remote_notebook)
100+
return base_notebook, remote_notebook, d
101+
93102
def _build_arg_parser(prog=None):
94103
"""Creates an argument parser for the nbdiff command."""
95104
parser = ConfigBackedParser(

nbdime/webapp/nbdiffwebexport.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import sys
2+
import os
3+
import json
4+
5+
from jinja2 import FileSystemLoader, Environment
6+
7+
from ..args import (
8+
Path,
9+
ConfigBackedParser,
10+
add_generic_args)
11+
12+
from ..nbdiffapp import (
13+
list_changed_file_pairs,
14+
resolve_diff_args,
15+
_build_diff)
16+
17+
18+
here = os.path.abspath(os.path.dirname(__file__))
19+
static_path = os.path.join(here, 'static')
20+
template_path = os.path.join(here, 'templates')
21+
22+
23+
def build_arg_parser():
24+
"""
25+
Creates an argument parser for the diff tool, that also lets the
26+
user specify a port and displays a help message.
27+
"""
28+
description = 'Difftool for Nbdime.'
29+
parser = ConfigBackedParser(
30+
description=description,
31+
add_help=True
32+
)
33+
add_generic_args(parser)
34+
parser.add_argument(
35+
"base", help="the base notebook filename OR base git-revision.",
36+
type=Path,
37+
nargs='?', default='HEAD',
38+
)
39+
parser.add_argument(
40+
"remote", help="the remote modified notebook filename OR remote git-revision.",
41+
type=Path,
42+
nargs='?', default=None,
43+
)
44+
parser.add_argument(
45+
"paths", help="filter diffs for git-revisions based on path",
46+
type=Path,
47+
nargs='*', default=None,
48+
)
49+
parser.add_argument(
50+
"--nbdime_url",
51+
type=Path,
52+
default="",
53+
help="URL to nbdime.js"
54+
)
55+
parser.add_argument(
56+
"--output-dir",
57+
type=Path,
58+
default="output/",
59+
help="a path to an output dir"
60+
)
61+
return parser
62+
63+
64+
def main_export(opts):
65+
env = Environment(loader=FileSystemLoader([template_path]), autoescape=False)
66+
outputdir = opts.output_dir
67+
nbdime_url = opts.nbdime_url
68+
if not nbdime_url:
69+
nbdime_url = "nbdime.js"
70+
import shutil
71+
shutil.copy(os.path.join(static_path, "nbdime.js"), os.path.join(outputdir, nbdime_url))
72+
73+
base, remote, paths = resolve_diff_args(opts)
74+
index = 1
75+
for fbase, fremote in list_changed_file_pairs(base, remote, paths):
76+
# on_null="minimal" is crucial cause web renderer expects
77+
# base_notebook to be a valid notebook even if it is missing
78+
base_notebook, remote_notebook, diff = _build_diff(fbase, fremote, on_null="minimal")
79+
data = json.dumps(dict(
80+
base=base_notebook,
81+
diff=diff
82+
))
83+
84+
template = env.get_template("diffembedded.html")
85+
rendered = template.render(
86+
data=data,
87+
nbdime_url=nbdime_url)
88+
outputfilename = os.path.join(outputdir, "diff" + str(index) + ".html")
89+
with open(outputfilename, "w") as f:
90+
f.write(rendered)
91+
index += 1
92+
93+
94+
def main(args=None):
95+
if args is None:
96+
args = sys.argv[1:]
97+
opts = build_arg_parser().parse_args(args)
98+
return main_export(opts)
99+
100+
101+
if __name__ == "__main__":
102+
sys.exit(main())

nbdime/webapp/nbdimeserver.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ def make_app(**params):
402402
app.exit_code = 0
403403
return app
404404

405+
405406
def init_app(on_port=None, closable=False, **params):
406407
asyncio_patch()
407408
_logger.debug('Using params: %s', params)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!DOCTYPE html>
2+
<html>
3+
4+
<head>
5+
<meta charset="utf-8"/>
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
8+
<title>nbdime - diff and merge your Jupyter notebooks</title>
9+
10+
</head>
11+
12+
13+
<!-- TODO: make nbdime.init() setup the forms/input user interface? -->
14+
15+
<body>
16+
17+
<div id="nbdime-header" class="nbdime-Diff">
18+
<h3>Notebook Diff</h3>
19+
<script id='diff-and-base' type="application/json">{{ data|tojson|safe }}</script>
20+
<div id="nbdime-header-buttonrow">
21+
<input id="nbdime-hide-unchanged" type="checkbox"><label for="cbox1">Hide unchanged cells</label></input>
22+
<button id="nbdime-trust" style="display: none">Trust outputs</button>
23+
<button id="nbdime-close" type="checkbox" style="display: none">Close tool</button>
24+
<button id="nbdime-export" type="checkbox" style="display: none">Export diff</button>
25+
</div>
26+
<div id=nbdime-header-banner>
27+
<span id="nbdime-header-base">Base</span>
28+
<span id="nbdime-header-remote">Remote</span>
29+
</div>
30+
</div> <!-- ndime-header -->
31+
32+
<div id="nbdime-root" class="nbdime-root">
33+
</div>
34+
35+
<script src="{{ nbdime_url }}"></script>
36+
<noscript>Nbdime relies on javascript for diff/merge views!</noscript>
37+
</body>
38+
</html>

packages/webapp/src/app/diff.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@ function getDiff(base: string, remote: string | undefined) {
161161
requestDiff(base, remote, baseUrl, onDiffRequestCompleted, onDiffRequestFailed);
162162
}
163163

164+
export
165+
function renderDiff(diff: any) {
166+
onDiffRequestCompleted(JSON.parse(diff));
167+
}
168+
164169
/**
165170
* Callback for a successfull diff request
166171
*/

packages/webapp/src/index.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
'use strict';
44

55
import {
6-
initializeDiff
6+
initializeDiff, renderDiff
77
} from './app/diff';
88

99
import {
@@ -44,8 +44,15 @@ import './app/merge.css';
4444
/** */
4545
function initialize() {
4646
let closable = getConfigOption('closable');
47-
let type: 'diff' | 'merge' | 'compare';
48-
if (document.getElementById('compare-local')) {
47+
let type: 'diff' | 'merge' | 'compare' | 'diff-and-base';
48+
if (document.getElementById('diff-and-base')) {
49+
type = 'diff-and-base'
50+
closable = false
51+
let el = document.getElementById('diff-and-base');
52+
if (el && el.textContent) {
53+
renderDiff(JSON.parse(el.textContent))
54+
}
55+
} else if (document.getElementById('compare-local')) {
4956
initializeCompare();
5057
type = 'compare';
5158
} else if (getConfigOption('local') || document.getElementById('merge-local')) {

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@
145145
'nbshow = nbdime.nbshowapp:main',
146146
'nbdiff = nbdime.nbdiffapp:main',
147147
'nbdiff-web = nbdime.webapp.nbdiffweb:main',
148+
'nbdiff-web-export = nbdime.webapp.nbdiffwebexport:main',
148149
'nbmerge = nbdime.nbmergeapp:main',
149150
'nbmerge-web = nbdime.webapp.nbmergeweb:main',
150151
'git-nbdiffdriver = nbdime.vcs.git.diffdriver:main',

0 commit comments

Comments
 (0)