Skip to content

Commit

Permalink
Add nbdiff-web-export to export a diff inside a rendered HTML
Browse files Browse the repository at this point in the history
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
  • Loading branch information
trams committed Nov 24, 2020
1 parent f6beb60 commit a1f12b6
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 29 deletions.
2 changes: 1 addition & 1 deletion nbdime/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ def add_filter_args(diff_parser):


def add_git_diff_driver_args(diff_parser):
"""Adds a set of 7 stanard git diff driver arguments:
"""Adds a set of 7 standard git diff driver arguments:
path old-file old-hex old-mode new-file new-hex new-mode [ rename-to rename-metadata ]
Note: Only path, base and remote are added to parsed namespace
Expand Down
59 changes: 34 additions & 25 deletions nbdime/nbdiffapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,38 +33,27 @@ def main_diff(args):
process_diff_flags(args)
base, remote, paths = resolve_diff_args(args)

# Check if base/remote are gitrefs:
# 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):
# We are asked to do a diff of git revisions:
status = 0
for fbase, fremote in changed_notebooks(base, remote, paths):
status = _handle_diff(fbase, fremote, output, args)
if status != 0:
# Short-circuit on error in diff handling
return status
return status
yield fbase, fremote
else: # Not gitrefs:
return _handle_diff(base, remote, output, args)
yield base, remote


def _handle_diff(base, remote, output, args):
"""Handles 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):
print("Missing file {}".format(fn))
return 1
# 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:
a = read_notebook(base, on_null='empty')
b = read_notebook(remote, on_null='empty')

d = diff_notebooks(a, b)
a, b, d = _build_diff(base, remote, on_null="empty")

# Output as JSON to file, or print to stdout:
if output:
Expand All @@ -90,6 +79,26 @@ def write(self, text):
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):
print("Missing file {}".format(fn))
return 1
# 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(
Expand Down
102 changes: 102 additions & 0 deletions nbdime/webapp/nbdiffwebexport.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import sys
import os
import json

from jinja2 import FileSystemLoader, Environment

from ..args import (
Path,
ConfigBackedParser,
add_generic_args)

from ..nbdiffapp import (
list_changed_file_pairs,
resolve_diff_args,
_build_diff)


here = os.path.abspath(os.path.dirname(__file__))
static_path = os.path.join(here, 'static')
template_path = os.path.join(here, 'templates')


def build_arg_parser():
"""
Creates an argument parser for the diff tool, that also lets the
user specify a port and displays a help message.
"""
description = 'Difftool for Nbdime.'
parser = ConfigBackedParser(
description=description,
add_help=True
)
add_generic_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(
"--nbdime_url",
type=Path,
default="",
help="URL to nbdime.js"
)
parser.add_argument(
"--output-dir",
type=Path,
default="output/",
help="a path to an output dir"
)
return parser


def main_export(opts):
env = Environment(loader=FileSystemLoader([template_path]), autoescape=False)
outputdir = opts.output_dir
nbdime_url = opts.nbdime_url
if not nbdime_url:
nbdime_url = "nbdime.js"
import shutil
shutil.copy(os.path.join(static_path, "nbdime.js"), os.path.join(outputdir, nbdime_url))

base, remote, paths = resolve_diff_args(opts)
index = 1
for fbase, fremote in list_changed_file_pairs(base, remote, paths):
# on_null="minimal" is crucial cause web renderer expects
# base_notebook to be a valid notebook even if it is missing
base_notebook, remote_notebook, diff = _build_diff(fbase, fremote, on_null="minimal")
data = json.dumps(dict(
base=base_notebook,
diff=diff
))

template = env.get_template("diffembedded.html")
rendered = template.render(
data=data,
nbdime_url=nbdime_url)
outputfilename = os.path.join(outputdir, "diff" + str(index) + ".html")
with open(outputfilename, "w") as f:
f.write(rendered)
index += 1


def main(args=None):
if args is None:
args = sys.argv[1:]
opts = build_arg_parser().parse_args(args)
return main_export(opts)


if __name__ == "__main__":
sys.exit(main())
1 change: 1 addition & 0 deletions nbdime/webapp/nbdimeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ def make_app(**params):
app.exit_code = 0
return app


def init_app(on_port=None, closable=False, **params):
asyncio_patch()
_logger.debug('Using params: %s', params)
Expand Down
38 changes: 38 additions & 0 deletions nbdime/webapp/templates/diffembedded.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<title>nbdime - diff and merge your Jupyter notebooks</title>

</head>


<!-- TODO: make nbdime.init() setup the forms/input user interface? -->

<body>

<div id="nbdime-header" class="nbdime-Diff">
<h3>Notebook Diff</h3>
<script id='diff-and-base' type="application/json">{{ data|tojson|safe }}</script>
<div id="nbdime-header-buttonrow">
<input id="nbdime-hide-unchanged" type="checkbox"><label for="cbox1">Hide unchanged cells</label></input>
<button id="nbdime-trust" style="display: none">Trust outputs</button>
<button id="nbdime-close" type="checkbox" style="display: none">Close tool</button>
<button id="nbdime-export" type="checkbox" style="display: none">Export diff</button>
</div>
<div id=nbdime-header-banner>
<span id="nbdime-header-base">Base</span>
<span id="nbdime-header-remote">Remote</span>
</div>
</div> <!-- ndime-header -->

<div id="nbdime-root" class="nbdime-root">
</div>

<script src="{{ nbdime_url }}"></script>
<noscript>Nbdime relies on javascript for diff/merge views!</noscript>
</body>
</html>
5 changes: 5 additions & 0 deletions packages/webapp/src/app/diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ function getDiff(base: string, remote: string | undefined) {
requestDiff(base, remote, baseUrl, onDiffRequestCompleted, onDiffRequestFailed);
}

export
function renderDiff(diff: any) {
onDiffRequestCompleted(JSON.parse(diff));
}

/**
* Callback for a successfull diff request
*/
Expand Down
13 changes: 10 additions & 3 deletions packages/webapp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
'use strict';

import {
initializeDiff
initializeDiff, renderDiff
} from './app/diff';

import {
Expand Down Expand Up @@ -44,8 +44,15 @@ import './app/merge.css';
/** */
function initialize() {
let closable = getConfigOption('closable');
let type: 'diff' | 'merge' | 'compare';
if (document.getElementById('compare-local')) {
let type: 'diff' | 'merge' | 'compare' | 'diff-and-base';
if (document.getElementById('diff-and-base')) {
type = 'diff-and-base'
closable = false
let el = document.getElementById('diff-and-base');
if (el && el.textContent) {
renderDiff(JSON.parse(el.textContent))
}
} else if (document.getElementById('compare-local')) {
initializeCompare();
type = 'compare';
} else if (getConfigOption('local') || document.getElementById('merge-local')) {
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
'nbshow = nbdime.nbshowapp:main',
'nbdiff = nbdime.nbdiffapp:main',
'nbdiff-web = nbdime.webapp.nbdiffweb:main',
'nbdiff-web-export = nbdime.webapp.nbdiffwebexport:main',
'nbmerge = nbdime.nbmergeapp:main',
'nbmerge-web = nbdime.webapp.nbmergeweb:main',
'git-nbdiffdriver = nbdime.vcs.git.diffdriver:main',
Expand Down

0 comments on commit a1f12b6

Please sign in to comment.