├── .coveragerc ├── .git-blame-ignore-revs ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── check-release.yml │ ├── enforce-label.yml │ ├── prep-release.yml │ ├── publish-release.yml │ ├── tests.yml │ └── update-integration-tests.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .readthedocs.yaml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── RELEASE.md ├── codecov.yml ├── conftest.py ├── docs ├── Makefile ├── doc-requirements.txt ├── make.bat └── source │ ├── changelog.md │ ├── cli.rst │ ├── conf.py │ ├── config.rst │ ├── diffing.rst │ ├── extensions.rst │ ├── glossary.rst │ ├── images │ ├── diff-bad-shortened.png │ ├── diff-bad.png │ ├── nbdiff-terminal.png │ ├── nbdiff-web.png │ ├── nbext-preview.png │ ├── nbmerge-terminal.png │ ├── nbmerge-web.png │ └── nbshow.png │ ├── index.rst │ ├── installing.rst │ ├── merging.rst │ ├── nodevenv.rst │ ├── restapi.rst │ ├── testing.rst │ ├── usecases.rst │ └── vcs.rst ├── examples ├── example4 │ ├── center.ipynb │ ├── left.ipynb │ └── right.ipynb ├── example6 │ ├── center.ipynb │ ├── left.ipynb │ └── right.ipynb ├── example7 │ ├── center.ipynb │ ├── left.ipynb │ └── right.ipynb ├── example8 │ ├── center.ipynb │ ├── left.ipynb │ └── right.ipynb └── example9 │ ├── center.ipynb │ ├── left.ipynb │ └── right.ipynb ├── jupyter-config ├── jupyter_notebook_config.d │ └── nbdime.json ├── jupyter_server_config.d │ └── nbdime.json └── nbconfig │ └── notebook.d │ └── nbdime.json ├── lerna.json ├── nbdime ├── __init__.py ├── __main__.py ├── _version.py ├── args.py ├── config.py ├── diff_format.py ├── diff_format.schema.json ├── diff_utils.py ├── diffing │ ├── __init__.py │ ├── config.py │ ├── directorydiff.py │ ├── generic.py │ ├── lcs.py │ ├── notebooks.py │ ├── seq_bruteforce.py │ ├── seq_difflib.py │ ├── seq_myers.py │ ├── sequences.py │ └── snakes.py ├── gitfiles.py ├── ignorables.py ├── log.py ├── merge_format.schema.json ├── merging │ ├── __init__.py │ ├── autoresolve.py │ ├── chunks.py │ ├── decisions.py │ ├── generic.py │ ├── notebooks.py │ └── strategies.py ├── nbdiffapp.py ├── nbmergeapp.py ├── nbpatchapp.py ├── nbshowapp.py ├── notebook_ext │ ├── index.js │ └── nbdime.yaml ├── patching.py ├── prettyprint.py ├── profiling.py ├── tests │ ├── __init__.py │ ├── conftest.py │ ├── files │ │ ├── apap--1.ipynb │ │ ├── apap--2.ipynb │ │ ├── apap--3.ipynb │ │ ├── apap.ipynb │ │ ├── attachment--change_attachment.ipynb │ │ ├── attachment--empty_attachments_list.ipynb │ │ ├── attachment--remove_attachment.ipynb │ │ ├── attachment.ipynb │ │ ├── cellids--base.ipynb │ │ ├── cellids--local.ipynb │ │ ├── cellids--remote.ipynb │ │ ├── empty.ipynb │ │ ├── error--1.ipynb │ │ ├── error--2.ipynb │ │ ├── foo--1.ipynb │ │ ├── foo--2.ipynb │ │ ├── inline-conflict--1.ipynb │ │ ├── inline-conflict--2.ipynb │ │ ├── inline-conflict--3.ipynb │ │ ├── inline-conflict--decisions.json │ │ ├── markdown-only--1.ipynb │ │ ├── markdown-only--2.ipynb │ │ ├── mixed-conflicts--1.ipynb │ │ ├── mixed-conflicts--2.ipynb │ │ ├── mixed-conflicts--3.ipynb │ │ ├── multi_cell_nb--cellchange.ipynb │ │ ├── multi_cell_nb--local.ipynb │ │ ├── multi_cell_nb--remote.ipynb │ │ ├── multi_cell_nb.ipynb │ │ ├── multilevel-test-base-local-diff.json │ │ ├── multilevel-test-base.ipynb │ │ ├── multilevel-test-local.ipynb │ │ ├── multilevel-test-merged.ipynb │ │ ├── multilevel-test-remote.ipynb │ │ ├── output-conflict--1.ipynb │ │ ├── output-conflict--2.ipynb │ │ ├── output-conflict--3.ipynb │ │ ├── single_cell_nb--changed_ec.ipynb │ │ ├── single_cell_nb--changed_metadata.ipynb │ │ ├── single_cell_nb--changed_output.ipynb │ │ ├── single_cell_nb--changed_source.ipynb │ │ ├── single_cell_nb--changed_source_output.ipynb │ │ ├── single_cell_nb--changed_source_output_ec.ipynb │ │ ├── single_cell_nb--json_output.ipynb │ │ ├── single_cell_nb--json_output_changed.ipynb │ │ ├── single_cell_nb.ipynb │ │ ├── source-conflict--1.ipynb │ │ ├── source-conflict--2.ipynb │ │ ├── source-conflict--3.ipynb │ │ ├── src-and-output--1.ipynb │ │ ├── src-and-output--2.ipynb │ │ ├── test-data-singlecell--1.ipynb │ │ ├── test-data-singlecell--2.ipynb │ │ ├── unicode--1.ipynb │ │ └── unicode--2.ipynb │ ├── filters │ │ ├── add_helper.py │ │ ├── noop.py │ │ └── strip_outputs.py │ ├── test_apply_merge.py │ ├── test_args.py │ ├── test_autoresolve.py │ ├── test_cli_apps.py │ ├── test_decision_tools.py │ ├── test_diff.py │ ├── test_diff_format.py │ ├── test_diff_gitrefs.py │ ├── test_diff_json_conversion.py │ ├── test_diff_performance.py │ ├── test_diff_sequence.py │ ├── test_diff_sequence_bruteforce.py │ ├── test_diff_sequence_difflib.py │ ├── test_directory_differ.py │ ├── test_git_diffdriver.py │ ├── test_git_filter_integration.py │ ├── test_hg_differ.py │ ├── test_js_artifacts_installed.py │ ├── test_merge.py │ ├── test_merge_format.py │ ├── test_merge_notebooks.py │ ├── test_merge_notebooks_inline.py │ ├── test_myers.py │ ├── test_notebook_diff.py │ ├── test_package.py │ ├── test_patch.py │ ├── test_prettyprint.py │ ├── test_server_extension.py │ ├── test_utils.py │ ├── test_web.py │ └── utils.py ├── utils.py ├── vcs │ ├── __init__.py │ ├── git │ │ ├── __init__.py │ │ ├── diffdriver.py │ │ ├── difftool.py │ │ ├── filter_integration.py │ │ ├── mergedriver.py │ │ └── mergetool.py │ └── hg │ │ ├── __init__.py │ │ ├── diff.py │ │ ├── diffweb.py │ │ ├── merge.py │ │ └── mergeweb.py └── webapp │ ├── __init__.py │ ├── nb_server_extension.py │ ├── nbdifftool.py │ ├── nbdiffweb.py │ ├── nbdimeserver.py │ ├── nbmergetool.py │ ├── nbmergeweb.py │ ├── static │ └── favicon.ico │ ├── templates │ ├── compare.html │ ├── diff.html │ ├── difftool.html │ ├── merge.html │ ├── mergetool.html │ └── nbdimepage.html │ ├── testnotebooks │ ├── base.ipynb │ ├── cellchange.ipynb │ ├── local.ipynb │ ├── remote.ipynb │ ├── scrollA.ipynb │ └── scrollB.ipynb │ └── webutil.py ├── package-lock.json ├── package.json ├── packages ├── labextension │ ├── package.json │ ├── schema │ │ └── plugin.json │ ├── src │ │ ├── actions.ts │ │ ├── index.ts │ │ ├── plugin.ts │ │ ├── utils.ts │ │ └── widget.ts │ ├── style │ │ ├── index.css │ │ └── index.js │ └── tsconfig.json ├── nbdime │ ├── .npmignore │ ├── babel.config.js │ ├── jest.config.js │ ├── package.json │ ├── scripts │ │ └── copy-files.js │ ├── src │ │ ├── chunking │ │ │ ├── decisionchunking.ts │ │ │ ├── diffchunking.ts │ │ │ └── index.ts │ │ ├── common │ │ │ ├── basepanel.ts │ │ │ ├── collapsible.css │ │ │ ├── collapsiblepanel.ts │ │ │ ├── dragpanel.css │ │ │ ├── dragpanel.ts │ │ │ ├── editor.ts │ │ │ ├── exceptions.ts │ │ │ ├── interfaces.ts │ │ │ ├── mergeview.ts │ │ │ └── util.ts │ │ ├── diff │ │ │ ├── diffentries.ts │ │ │ ├── model │ │ │ │ ├── cell.ts │ │ │ │ ├── common.ts │ │ │ │ ├── immutable.ts │ │ │ │ ├── index.ts │ │ │ │ ├── notebook.ts │ │ │ │ ├── output.ts │ │ │ │ ├── renderable.ts │ │ │ │ └── string.ts │ │ │ ├── range.ts │ │ │ ├── util.ts │ │ │ └── widget │ │ │ │ ├── cell.ts │ │ │ │ ├── common.ts │ │ │ │ ├── index.ts │ │ │ │ ├── metadata.ts │ │ │ │ ├── notebook.ts │ │ │ │ ├── output.ts │ │ │ │ └── renderable.ts │ │ ├── merge │ │ │ ├── decisions.ts │ │ │ ├── model │ │ │ │ ├── cell.ts │ │ │ │ ├── common.ts │ │ │ │ ├── index.ts │ │ │ │ ├── metadata.ts │ │ │ │ └── notebook.ts │ │ │ └── widget │ │ │ │ ├── cell.ts │ │ │ │ ├── common.ts │ │ │ │ ├── dragdrop.ts │ │ │ │ ├── index.ts │ │ │ │ ├── metadata.ts │ │ │ │ ├── notebook.ts │ │ │ │ └── output.ts │ │ ├── patch │ │ │ ├── common.ts │ │ │ ├── generic.ts │ │ │ ├── index.ts │ │ │ └── stringified.ts │ │ ├── request │ │ │ └── index.ts │ │ ├── styles │ │ │ ├── common.css │ │ │ ├── diff.css │ │ │ ├── merge.css │ │ │ └── variables.css │ │ └── upstreaming │ │ │ ├── flexlayout.ts │ │ │ ├── flexpanel.css │ │ │ └── flexpanel.ts │ ├── test │ │ ├── files │ │ │ ├── base.ipynb.json │ │ │ └── decisionsA.json │ │ ├── jest-file-mock.js │ │ ├── jest-setup-files.js │ │ └── src │ │ │ ├── common │ │ │ ├── collapsiblepanel.spec.ts │ │ │ ├── dragpanel.spec.ts │ │ │ ├── flexpanel.spec.ts │ │ │ ├── mergeview.spec.ts │ │ │ └── util.spec.ts │ │ │ ├── diff │ │ │ ├── chunking.spec.ts │ │ │ ├── model.spec.ts │ │ │ ├── util.spec.ts │ │ │ └── widget │ │ │ │ ├── metadata.spec.ts │ │ │ │ └── output.spec.ts │ │ │ ├── merge │ │ │ ├── decisions.spec.ts │ │ │ ├── model.spec.ts │ │ │ └── widget.spec.ts │ │ │ └── patch │ │ │ └── patching.spec.ts │ ├── tsconfig.json │ └── tsconfig.test.json └── webapp │ ├── package.json │ ├── src │ ├── app │ │ ├── common.css │ │ ├── common.ts │ │ ├── compare.ts │ │ ├── diff.css │ │ ├── diff.ts │ │ ├── merge.css │ │ ├── merge.ts │ │ ├── rendermime.ts │ │ ├── res.ts │ │ ├── save.ts │ │ └── staticdiff.ts │ └── index.ts │ ├── tsconfig.json │ └── webpack.config.js ├── pyproject.toml ├── scripts └── bump_version.py ├── setup.py ├── tsconfig.json ├── tsconfig_base.json └── ui-tests ├── README.md ├── data ├── diff_test1 │ ├── center.ipynb │ └── left.ipynb ├── diff_test2 │ ├── center.ipynb │ └── left.ipynb ├── diff_test3 │ ├── center.ipynb │ └── left.ipynb ├── merge_test1 │ ├── center.ipynb │ ├── left.ipynb │ └── right.ipynb ├── merge_test2 │ ├── center.ipynb │ ├── left.ipynb │ └── right.ipynb ├── merge_test3 │ ├── center.ipynb │ ├── left.ipynb │ └── right.ipynb ├── merge_test4 │ ├── center.ipynb │ ├── left.ipynb │ └── right.ipynb ├── merge_test5 │ ├── center.ipynb │ ├── left.ipynb │ └── right.ipynb └── merge_test6 │ ├── center.ipynb │ ├── left.ipynb │ └── right.ipynb ├── package-lock.json ├── package.json ├── playwright.config.js └── tests ├── nbdime-diff-test1.spec.ts ├── nbdime-diff-test1.spec.ts-snapshots ├── diff-test1-take-a-snapshot-at-opening-1-linux.png └── diff-test1-take-a-snapshot-at-opening-1-win32.png ├── nbdime-diff-test2.spec.ts ├── nbdime-diff-test2.spec.ts-snapshots ├── diff-test2-take-a-snapshot-at-opening-1-linux.png └── diff-test2-take-a-snapshot-at-opening-1-win32.png ├── nbdime-diff-test3.spec.ts ├── nbdime-diff-test3.spec.ts-snapshots ├── diff-test3-take-a-snapshot-at-opening-1-linux.png └── diff-test3-take-a-snapshot-at-opening-1-win32.png ├── nbdime-merge-test1.spec.ts ├── nbdime-merge-test1.spec.ts-snapshots ├── 3-panels-view-1-linux.png ├── 3-panels-view-1-win32.png ├── merge-test1-choose-central-version-for-conflict-1-linux.png ├── merge-test1-choose-central-version-for-conflict-1-win32.png ├── merge-test1-choose-left-version-for-conflict-1-linux.png ├── merge-test1-choose-left-version-for-conflict-1-win32.png ├── merge-test1-choose-right-version-for-conflict-1-linux.png ├── merge-test1-choose-right-version-for-conflict-1-win32.png ├── merge-test1-should-not-collapse-source-for-unchanged-metadata-1-linux.png ├── merge-test1-should-not-collapse-source-for-unchanged-metadata-1-win32.png ├── merge-test1-take-a-snapshot-at-opening-1-linux.png └── merge-test1-take-a-snapshot-at-opening-1-win32.png ├── nbdime-merge-test2.spec.ts ├── nbdime-merge-test2.spec.ts-snapshots ├── merge-test2-choose-central-version-1-linux.png ├── merge-test2-choose-central-version-1-win32.png ├── merge-test2-choose-left-version-1-linux.png ├── merge-test2-choose-left-version-1-win32.png ├── merge-test2-choose-right-version-1-linux.png ├── merge-test2-choose-right-version-1-win32.png ├── merge-test2-take-a-snapshot-at-opening-1-linux.png └── merge-test2-take-a-snapshot-at-opening-1-win32.png ├── nbdime-merge-test3.spec.ts ├── nbdime-merge-test4.spec.ts ├── nbdime-merge-test4.spec.ts-snapshots ├── merge-test4-should-synchronize-the-collapse-status-between-editor-1-linux.png └── merge-test4-should-synchronize-the-collapse-status-between-editor-1-win32.png ├── nbdime-merge-test5.spec.ts ├── nbdime-merge-test5.spec.ts-snapshots ├── merge-test5-take-a-snapshot-at-opening-1-linux.png └── merge-test5-take-a-snapshot-at-opening-1-win32.png ├── nbdime-merge-test6.spec.ts └── nbdime-merge-test6.spec.ts-snapshots ├── merge-test6-take-a-snapshot-at-opening-1-linux.png └── merge-test6-take-a-snapshot-at-opening-1-win32.png /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = nbdime/tests/* 3 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Reformatting all JS / JSON / MD / CSS with prettier 2 | 9eeb4671dd2ce61c0a54f4be196ff2b986573aba 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ipynb diff=jupyternotebook 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Basic set up for three package managers 2 | 3 | version: 2 4 | updates: 5 | 6 | # Maintain dependencies for GitHub Actions 7 | - package-ecosystem: "github-actions" 8 | directory: "/" 9 | schedule: 10 | interval: "monthly" 11 | 12 | # Maintain dependencies for npm 13 | - package-ecosystem: "npm" 14 | directory: "/" 15 | schedule: 16 | interval: "weekly" 17 | allow: 18 | dependency-type: production 19 | -------------------------------------------------------------------------------- /.github/workflows/check-release.yml: -------------------------------------------------------------------------------- 1 | name: Check Release 2 | on: 3 | push: 4 | branches: ["master", "*"] 5 | pull_request: 6 | branches: ["*"] 7 | 8 | jobs: 9 | check_release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | - name: Base Setup 15 | uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 16 | - name: Check Release 17 | uses: fcollonval/jupyter_releaser/.github/actions/check-release@1e5300b94b842e61d4f10bed0db8e855c8fe9108 18 | env: 19 | RH_TAG_FORMAT: "{version}" 20 | with: 21 | token: ${{ secrets.GITHUB_TOKEN }} 22 | - name: Upload Distributions 23 | uses: actions/upload-artifact@v3 24 | with: 25 | name: nbdime-releaser-dist-${{ '{{ github.run_number }}' }} 26 | path: .jupyter_releaser_checkout/dist 27 | -------------------------------------------------------------------------------- /.github/workflows/enforce-label.yml: -------------------------------------------------------------------------------- 1 | name: Enforce PR label 2 | 3 | on: 4 | pull_request: 5 | types: [labeled, unlabeled, opened, edited, synchronize] 6 | jobs: 7 | enforce-label: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | pull-requests: write 11 | steps: 12 | - name: enforce-triage-label 13 | uses: jupyterlab/maintainer-tools/.github/actions/enforce-label@v1 14 | -------------------------------------------------------------------------------- /.github/workflows/prep-release.yml: -------------------------------------------------------------------------------- 1 | name: "Step 1: Prep Release" 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | version_spec: 6 | description: "New Version Specifier" 7 | default: "next" 8 | required: false 9 | branch: 10 | description: "The branch to target" 11 | required: false 12 | post_version_spec: 13 | description: "Post Version Specifier" 14 | required: false 15 | silent: 16 | description: "Set a placeholder in the changelog and don't publish the release." 17 | required: false 18 | type: boolean 19 | since: 20 | description: "Use PRs with activity since this date or git reference" 21 | required: false 22 | since_last_stable: 23 | description: "Use PRs with activity since the last stable git tag" 24 | required: false 25 | type: boolean 26 | jobs: 27 | prep_release: 28 | runs-on: ubuntu-latest 29 | permissions: 30 | contents: write 31 | steps: 32 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 33 | 34 | - name: Prep Release 35 | id: prep-release 36 | uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2 37 | with: 38 | token: ${{ secrets.GITHUB_TOKEN }} 39 | version_spec: ${{ github.event.inputs.version_spec }} 40 | silent: ${{ github.event.inputs.silent }} 41 | post_version_spec: ${{ github.event.inputs.post_version_spec }} 42 | target: ${{ github.event.inputs.target }} 43 | branch: ${{ github.event.inputs.branch }} 44 | since: ${{ github.event.inputs.since }} 45 | since_last_stable: ${{ github.event.inputs.since_last_stable }} 46 | 47 | - name: "** Next Step **" 48 | run: | 49 | echo "Optional): Review Draft Release: ${{ steps.prep-release.outputs.release_url }}" 50 | -------------------------------------------------------------------------------- /.github/workflows/publish-release.yml: -------------------------------------------------------------------------------- 1 | name: "Step 2: Publish Release" 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | branch: 6 | description: "The target branch" 7 | required: false 8 | release_url: 9 | description: "The URL of the draft GitHub release" 10 | required: false 11 | steps_to_skip: 12 | description: "Comma separated list of steps to skip" 13 | required: false 14 | 15 | jobs: 16 | publish_release: 17 | runs-on: ubuntu-latest 18 | environment: release 19 | permissions: 20 | id-token: write 21 | steps: 22 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 23 | 24 | - uses: actions/create-github-app-token@v1 25 | id: app-token 26 | with: 27 | app-id: ${{ vars.APP_ID }} 28 | private-key: ${{ secrets.APP_PRIVATE_KEY }} 29 | 30 | - name: Populate Release 31 | id: populate-release 32 | uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2 33 | with: 34 | token: ${{ steps.app-token.outputs.token }} 35 | branch: ${{ github.event.inputs.branch }} 36 | release_url: ${{ github.event.inputs.release_url }} 37 | steps_to_skip: ${{ github.event.inputs.steps_to_skip }} 38 | 39 | - name: Finalize Release 40 | id: finalize-release 41 | env: 42 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 43 | uses: jupyter-server/jupyter_releaser/.github/actions/finalize-release@v2 44 | with: 45 | token: ${{ steps.app-token.outputs.token }} 46 | release_url: ${{ steps.populate-release.outputs.release_url }} 47 | 48 | - name: "** Next Step **" 49 | if: ${{ success() }} 50 | run: | 51 | echo "Verify the final release" 52 | echo ${{ steps.finalize-release.outputs.release_url }} 53 | 54 | - name: "** Failure Message **" 55 | if: ${{ failure() }} 56 | run: | 57 | echo "Failed to Publish the Draft Release Url:" 58 | echo ${{ steps.populate-release.outputs.release_url }} 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | *.egg-info 4 | __pycache__ 5 | .ipynb_checkpoints 6 | docs/build 7 | htmlcov 8 | dist 9 | build 10 | MANIFEST 11 | .coverage 12 | .cache 13 | .pytest_cache 14 | .idea 15 | 16 | 17 | .spyderproject 18 | **/.vscode/ 19 | **/node_modules/ 20 | nbdime/webapp/test/build/ 21 | nbdime/webapp/build/ 22 | packages/**/lib/ 23 | packages/**/coverage/ 24 | packages/**/test/build/ 25 | 26 | nbdime/webapp/static/** 27 | !nbdime/webapp/static/favicon.ico 28 | *.tsbuildinfo 29 | packages/webapp/webpack-stats.json 30 | nbdime/labextension/ 31 | ui-tests/playwright-report/ 32 | ui-tests/test-results/ 33 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/build 2 | **/node_modules 3 | **/lib 4 | **/dist 5 | **/static 6 | **/.ipynb_checkpoints 7 | **/coverage 8 | CHANGELOG.md 9 | package-lock.json 10 | nbdime/tests/files/inline-conflict--decisions.json 11 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "arrowParens": "avoid" 5 | } 6 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.11" 7 | nodejs: "20" 8 | 9 | sphinx: 10 | configuration: docs/source/conf.py 11 | 12 | python: 13 | install: 14 | - method: pip 15 | path: . 16 | extra_requirements: 17 | - docs 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We follow the [IPython Contributing Guide](https://github.com/ipython/ipython/blob/master/CONTRIBUTING.md). 4 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: off 2 | # show coverage in CI status, but never consider it a failure 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: 0% 8 | patch: 9 | default: 10 | target: 0% 11 | ignore: 12 | - "nbdime/tests" 13 | -------------------------------------------------------------------------------- /conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | def pytest_addoption(parser): 4 | parser.addoption("--quick", action="store_true", 5 | default=False, help="skip slow tests") 6 | parser.addoption("--slow", action="store_true", 7 | default=False, help="only run slow tests") 8 | 9 | 10 | def pytest_collection_modifyitems(config, items): 11 | if not config.getoption("--slow"): 12 | # --runslow given in cli: do not skip slow tests 13 | return 14 | skip_quick = pytest.mark.skip(reason="skipping all tests that are not slow") 15 | for item in items: 16 | if 'slow' not in item.fixturenames: 17 | item.add_marker(skip_quick) 18 | -------------------------------------------------------------------------------- /docs/doc-requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx 2 | sphinx_rtd_theme 3 | recommonmark 4 | -------------------------------------------------------------------------------- /docs/source/glossary.rst: -------------------------------------------------------------------------------- 1 | Glossary 2 | ======== 3 | 4 | .. glossary:: 5 | 6 | diff object 7 | A diff object represents the difference ``B-A`` between two objects, ``A`` and 8 | ``B``, as a list of operations (ops) to apply to ``A`` to obtain ``B``. 9 | 10 | merge decision 11 | An object describing a part of the merge operation between two objects 12 | with a common base. Contains both the information about local and remote 13 | changes, and the decision taken to resolve the merge. 14 | 15 | JSONPatch 16 | JSON Patch defines a JSON document structure for expressing a 17 | sequence of operations to apply to a JavaScript Object Notation 18 | (JSON) document; it is suitable for use with the ``HTTP PATCH`` method. See 19 | `RFC 6902 JavaScript Object Notation (JSON) Patch `_. -------------------------------------------------------------------------------- /docs/source/images/diff-bad-shortened.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/docs/source/images/diff-bad-shortened.png -------------------------------------------------------------------------------- /docs/source/images/diff-bad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/docs/source/images/diff-bad.png -------------------------------------------------------------------------------- /docs/source/images/nbdiff-terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/docs/source/images/nbdiff-terminal.png -------------------------------------------------------------------------------- /docs/source/images/nbdiff-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/docs/source/images/nbdiff-web.png -------------------------------------------------------------------------------- /docs/source/images/nbext-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/docs/source/images/nbext-preview.png -------------------------------------------------------------------------------- /docs/source/images/nbmerge-terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/docs/source/images/nbmerge-terminal.png -------------------------------------------------------------------------------- /docs/source/images/nbmerge-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/docs/source/images/nbmerge-web.png -------------------------------------------------------------------------------- /docs/source/images/nbshow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/docs/source/images/nbshow.png -------------------------------------------------------------------------------- /docs/source/nodevenv.rst: -------------------------------------------------------------------------------- 1 | Setting up a virtualenv with Node.js 2 | ------------------------------------ 3 | 4 | The following steps will: create a virtualenv, named ``myenv``, in the current 5 | directory; activate the virtualenv; and install npm inside the virtualenv 6 | using :command:`nodeenv`:: 7 | 8 | python -m venv myenv 9 | source myenv/bin/activate 10 | pip install nodeenv 11 | nodeenv -p 12 | 13 | With this environment active, you can now install nbdime and its 14 | dependencies using :command:`pip`. 15 | 16 | For example with Python 3.5, the steps with output are:: 17 | 18 | $ python -m venv myenv 19 | $ source myenv/bin/activate 20 | (myenv) $ pip install nodeenv 21 | Collecting nodeenv 22 | Downloading nodeenv-1.0.0.tar.gz 23 | Installing collected packages: nodeenv 24 | Running setup.py install for nodeenv ... done 25 | Successfully installed nodeenv-1.0.0 26 | (myenv) $ nodeenv -p 27 | * Install prebuilt node (7.2.0) ..... done. 28 | * Appending data to /Users/username/myenv/bin/activate 29 | (myenv) $ 30 | -------------------------------------------------------------------------------- /docs/source/restapi.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | REST API 3 | ======== 4 | 5 | The following is a preliminary REST API for nbdime. It is not yet frozen 6 | but is guided on preliminary work and likely close to the final 7 | result. 8 | 9 | The Python package, commandline, and web API should cover the same 10 | functionality using the same names but different methods of passing 11 | input/output data. Thus consider the request to be the input 12 | arguments and response to be the output arguments for all APIs. 13 | 14 | 15 | 16 | Definitions 17 | ------------ 18 | 19 | `json_*` : always a JSON object 20 | 21 | `json_notebook` : a full Jupyter notebook 22 | 23 | `json_diff_object` : diff result in nbdime diff format as specified in :doc:`diffing` 24 | 25 | `json_merge_decisions` : merge decisions as specified in :doc:`merging` 26 | 27 | 28 | 29 | /api/diff 30 | --------- 31 | 32 | Compute diff of two notebooks provided as filenames local 33 | to the server working directory, and/or as URLs. 34 | 35 | Request:: 36 | 37 | { 38 | "base": "filename.ipynb" | "http://your-domain/url/path", 39 | "remote": "filename.ipynb" | "http://your-domain/url/path" 40 | } 41 | 42 | Response:: 43 | 44 | { 45 | "base": json_notebook, 46 | "diff": json_diff_object 47 | } 48 | 49 | 50 | /api/merge 51 | ---------- 52 | 53 | Compute merge of three notebooks provided as filenames local 54 | to the server working directory, and/or as URLs. 55 | 56 | Request:: 57 | 58 | { 59 | "base": "filename.ipynb" | "http://your-domain/url/path", 60 | "local": "filename.ipynb" | "http://your-domain/url/path", 61 | "remote": "filename.ipynb" | "http://your-domain/url/path" 62 | } 63 | 64 | Response:: 65 | 66 | { 67 | "base": json_notebook, 68 | "merge_decisions": json_merge_decisions 69 | } 70 | -------------------------------------------------------------------------------- /docs/source/testing.rst: -------------------------------------------------------------------------------- 1 | Testing 2 | ======= 3 | 4 | See the latest automated build, test, and coverage status at: 5 | 6 | - `Build and test on Travis `_ 7 | - `Coverage on codecov `_ 8 | 9 | Dependencies 10 | ------------ 11 | 12 | Install the test dependencies:: 13 | 14 | pip install .[test] 15 | 16 | Running tests locally 17 | --------------------- 18 | 19 | To run python tests, locally, enter:: 20 | 21 | pytest 22 | 23 | from the project root. If you have Python 2 and Python 3 installed, 24 | you may need to enter:: 25 | 26 | python3 -m pytest 27 | 28 | to run the tests with Python 3. See the `pytest documentation`_ for more 29 | options. 30 | 31 | To run javascript/typescript tests, enter:: 32 | 33 | npm test 34 | 35 | Submitting test cases 36 | --------------------- 37 | 38 | If you have notebooks with interesting merge challenges, 39 | please consider `contributing them `_ 40 | to nbdime as test cases! 41 | 42 | .. _pytest documentation: http://pytest.org/latest/ 43 | -------------------------------------------------------------------------------- /docs/source/usecases.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Use cases 3 | ========= 4 | 5 | Use cases for nbdime are envisioned to be mainly in the categories 6 | of a merge command for version control integration and 7 | diff command for inspecting changes and automated regression 8 | testing. At the core of nbdime is the diff algorithms, which 9 | must handle not only text in source cells but also a number of 10 | data formats based on mime types in output cells. 11 | 12 | 13 | Basic diffing use cases 14 | ----------------------- 15 | 16 | While developing basic correct diffing is fairly 17 | straightforward, there are still 18 | some issues to discuss. 19 | 20 | Other tasks (issues will be created for these): 21 | 22 | - Plugin framework for mime type specific diffing. 23 | 24 | - Diffing of common output types (png, svg, etc.) 25 | 26 | - Improve fundamental sequence diff algorithm. 27 | Current algorithm is based on a brute force 28 | O(N^2) longest common subsequence (LCS) algorithm. This 29 | will be rewritten in terms of a faster algorithm such 30 | as Myers O(ND) LCS based diff algorithm, optionally 31 | using Python's difflib for some use cases where it makes sense. 32 | 33 | 34 | Version control use cases 35 | ------------------------- 36 | 37 | Most commonly, cell source is the primary content, 38 | and output can presumably be regenerated. Indeed, it 39 | is not possible to guarantee that merged sources and 40 | merged output is consistent or makes any kind of sense. 41 | 42 | Some tasks: 43 | 44 | - Merge of output cell content is not planned. 45 | 46 | - Is it important to track source lines moving between cells? 47 | 48 | 49 | Regression testing use cases 50 | ---------------------------- 51 | 52 | .. TODO:: Add text and description -------------------------------------------------------------------------------- /examples/example4/center.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "eabfe4e0-d646-4d09-95a4-09ee3d030db6", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "x = np.arange(0, 2, 0.1)\n", 11 | "y = np.exp(x)" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "id": "626d6a3c-69d5-43c6-ba1c-9c799ddc4884", 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "four" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "id": "4c301133-ede0-44e0-9937-099501d64b94", 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "five" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": null, 37 | "id": "9b1af30e-ea9d-4375-a6be-02c3d85de344", 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "six" 42 | ] 43 | } 44 | ], 45 | "metadata": { 46 | "kernelspec": { 47 | "display_name": "Python 3 (ipykernel)", 48 | "language": "python", 49 | "name": "python3" 50 | }, 51 | "language_info": { 52 | "codemirror_mode": { 53 | "name": "ipython", 54 | "version": 3 55 | }, 56 | "file_extension": ".py", 57 | "mimetype": "text/x-python", 58 | "name": "python", 59 | "nbconvert_exporter": "python", 60 | "pygments_lexer": "ipython3", 61 | "version": "3.11.3" 62 | } 63 | }, 64 | "nbformat": 4, 65 | "nbformat_minor": 5 66 | } 67 | -------------------------------------------------------------------------------- /examples/example4/left.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "2d47bfe2-d91d-4f72-9426-1885c4edb5a9", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "x = np.arange(0, 2, 0.1)\n", 11 | "y = np.exp(x + 2)" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "id": "b905f16a-3c78-4b85-bebe-bb731ddad9bf", 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "one" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "id": "25f31bfe-fb2e-411f-b7e8-9805bdc34ba2", 28 | "metadata": {}, 29 | "outputs": [], 30 | "source": [ 31 | "two" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": null, 37 | "id": "e6bb329d", 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [ 41 | "three" 42 | ] 43 | } 44 | ], 45 | "metadata": { 46 | "kernelspec": { 47 | "display_name": "Python 3 (ipykernel)", 48 | "language": "python", 49 | "name": "python3" 50 | }, 51 | "language_info": { 52 | "codemirror_mode": { 53 | "name": "ipython", 54 | "version": 3 55 | }, 56 | "file_extension": ".py", 57 | "mimetype": "text/x-python", 58 | "name": "python", 59 | "nbconvert_exporter": "python", 60 | "pygments_lexer": "ipython3", 61 | "version": "3.11.3" 62 | } 63 | }, 64 | "nbformat": 4, 65 | "nbformat_minor": 5 66 | } 67 | -------------------------------------------------------------------------------- /examples/example4/right.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "ff160690-22c8-48f3-9b89-d78a8997baec", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "x = np.arange(0, 2, 0.1)\n", 11 | "z = np.exp(x)" 12 | ] 13 | } 14 | ], 15 | "metadata": { 16 | "kernelspec": { 17 | "display_name": "Python 3 (ipykernel)", 18 | "language": "python", 19 | "name": "python3" 20 | }, 21 | "language_info": { 22 | "codemirror_mode": { 23 | "name": "ipython", 24 | "version": 3 25 | }, 26 | "file_extension": ".py", 27 | "mimetype": "text/x-python", 28 | "name": "python", 29 | "nbconvert_exporter": "python", 30 | "pygments_lexer": "ipython3", 31 | "version": "3.11.3" 32 | } 33 | }, 34 | "nbformat": 4, 35 | "nbformat_minor": 5 36 | } 37 | -------------------------------------------------------------------------------- /examples/example6/center.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "eabfe4e0-d646-4d09-95a4-09ee3d030db6", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "from matplotlib import pyplot as plt\n", 11 | "import numpy as np\n", 12 | "x = np.arange(0, 2, 0.1)\n", 13 | "y1 = np.exp(x)\n", 14 | "plt.plot(x, y1, 'b')" 15 | ] 16 | } 17 | ], 18 | "metadata": { 19 | "kernelspec": { 20 | "display_name": "Python 3 (ipykernel)", 21 | "language": "python", 22 | "name": "python3" 23 | }, 24 | "language_info": { 25 | "codemirror_mode": { 26 | "name": "ipython", 27 | "version": 3 28 | }, 29 | "file_extension": ".py", 30 | "mimetype": "text/x-python", 31 | "name": "python", 32 | "nbconvert_exporter": "python", 33 | "pygments_lexer": "ipython3", 34 | "version": "3.11.3" 35 | } 36 | }, 37 | "nbformat": 4, 38 | "nbformat_minor": 5 39 | } 40 | -------------------------------------------------------------------------------- /examples/example6/left.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "2d47bfe2-d91d-4f72-9426-1885c4edb5a9", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "from matplotlib import pyplot as plt\n", 11 | "import numpy as np\n", 12 | "x = np.linspace(0, 2, 10)\n", 13 | "z = np.exp(x + 2)\n", 14 | "plt.plot(x, z, 'c')\n", 15 | "# this is an example" 16 | ] 17 | } 18 | ], 19 | "metadata": { 20 | "kernelspec": { 21 | "display_name": "Python 3 (ipykernel)", 22 | "language": "python", 23 | "name": "python3" 24 | }, 25 | "language_info": { 26 | "codemirror_mode": { 27 | "name": "ipython", 28 | "version": 3 29 | }, 30 | "file_extension": ".py", 31 | "mimetype": "text/x-python", 32 | "name": "python", 33 | "nbconvert_exporter": "python", 34 | "pygments_lexer": "ipython3", 35 | "version": "3.11.3" 36 | } 37 | }, 38 | "nbformat": 4, 39 | "nbformat_minor": 5 40 | } 41 | -------------------------------------------------------------------------------- /examples/example6/right.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "ff160690-22c8-48f3-9b89-d78a8997baec", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "from matplotlib import pyplot as plt\n", 11 | "import numpy as np\n", 12 | "import math\n", 13 | "import pandas\n", 14 | "x = np.arange(0, 2, 0.1)\n", 15 | "y2 = np.exp(x)\n", 16 | "plt.plot(x, y2, 'b')\n", 17 | "y1 = y2\n", 18 | "plt.plot(x, y1, 'g')\n", 19 | "# this is easy" 20 | ] 21 | } 22 | ], 23 | "metadata": { 24 | "kernelspec": { 25 | "display_name": "Python 3 (ipykernel)", 26 | "language": "python", 27 | "name": "python3" 28 | }, 29 | "language_info": { 30 | "codemirror_mode": { 31 | "name": "ipython", 32 | "version": 3 33 | }, 34 | "file_extension": ".py", 35 | "mimetype": "text/x-python", 36 | "name": "python", 37 | "nbconvert_exporter": "python", 38 | "pygments_lexer": "ipython3", 39 | "version": "3.11.3" 40 | } 41 | }, 42 | "nbformat": 4, 43 | "nbformat_minor": 5 44 | } 45 | -------------------------------------------------------------------------------- /examples/example7/center.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "eabfe4e0-d646-4d09-95a4-09ee3d030db6", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "\n", 13 | "def gaussian(x, a, b, c):\n", 14 | " return a * np.exp(-b * (x-c)**2)\n", 15 | "\n", 16 | "def noisy_gaussian():\n", 17 | " nx = 100\n", 18 | " x = np.linspace(-5.0, 5.0, nx)\n", 19 | " y = gaussian(x, a=2.0, b=0.5, c=1.5)\n", 20 | " noise = np.random.normal(0.0, 0.2, nx)\n", 21 | " return x, y\n", 22 | "\n", 23 | "def plot(x, y):\n", 24 | " plt.plot(x, y, \"r\", label=\"Data\")\n", 25 | " plt.legend()\n", 26 | " plt.ylim(-0.5, 2.5)\n", 27 | " plt.show()\n", 28 | "\n", 29 | "x, y = noisy_gaussian()\n", 30 | "plot(x, y)" 31 | ] 32 | } 33 | ], 34 | "metadata": { 35 | "kernelspec": { 36 | "display_name": "Python 3 (ipykernel)", 37 | "language": "python", 38 | "name": "python3" 39 | }, 40 | "language_info": { 41 | "codemirror_mode": { 42 | "name": "ipython", 43 | "version": 3 44 | }, 45 | "file_extension": ".py", 46 | "mimetype": "text/x-python", 47 | "name": "python", 48 | "nbconvert_exporter": "python", 49 | "pygments_lexer": "ipython3", 50 | "version": "3.11.3" 51 | } 52 | }, 53 | "nbformat": 4, 54 | "nbformat_minor": 5 55 | } 56 | -------------------------------------------------------------------------------- /examples/example7/left.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "2d47bfe2-d91d-4f72-9426-1885c4edb5a9", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "%matplotlib inline\n", 13 | "\n", 14 | "def gaussian(x, a, b, c):\n", 15 | " return a * np.exp(-b * (x-c)**2)\n", 16 | "\n", 17 | "def noisy_gaussian():\n", 18 | " # gaussian array y in interval -5 <= x <= 5\n", 19 | " nx = 100\n", 20 | " x = np.linspace(-5.0, 5.0, nx)\n", 21 | " y = gaussian(x, a=2.0, b=0.5, c=1.5)\n", 22 | " noise = np.random.normal(0.0, 0.2, nx)\n", 23 | " y += noise\n", 24 | " return x, y\n", 25 | "\n", 26 | "def fit(x, y, n):\n", 27 | " pfit = np.polyfit(x, y, n)\n", 28 | " yfit = np.polyval(pfit, x)\n", 29 | " return yfit\n", 30 | "\n", 31 | "def plot(x, y, yfit):\n", 32 | " # this method is for plotting with fitting\n", 33 | " plt.plot(x, y, \"r\", label=\"Data\")\n", 34 | " plt.plot(x, yfit, \"b\", label=\"Fit\")\n", 35 | " plt.legend()\n", 36 | " plt.ylim(-0.5, 2.5)\n", 37 | " plt.show()\n", 38 | "\n", 39 | "x, y = noisy_gaussian()\n", 40 | "yfit = fit(x, y, n=5) # fit a 5th order polynomial to it\n", 41 | "plot(x, y, yfit)" 42 | ] 43 | } 44 | ], 45 | "metadata": { 46 | "kernelspec": { 47 | "display_name": "Python 3 (ipykernel)", 48 | "language": "python", 49 | "name": "python3" 50 | }, 51 | "language_info": { 52 | "codemirror_mode": { 53 | "name": "ipython", 54 | "version": 3 55 | }, 56 | "file_extension": ".py", 57 | "mimetype": "text/x-python", 58 | "name": "python", 59 | "nbconvert_exporter": "python", 60 | "pygments_lexer": "ipython3", 61 | "version": "3.11.3" 62 | } 63 | }, 64 | "nbformat": 4, 65 | "nbformat_minor": 5 66 | } 67 | -------------------------------------------------------------------------------- /examples/example7/right.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "ff160690-22c8-48f3-9b89-d78a8997baec", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "\n", 13 | "def gaussian(x, a, b, c):\n", 14 | " return a * np.exp(-b * (x-c)**2)\n", 15 | "\n", 16 | "def noisy_gaussian():\n", 17 | " # gaussian array y in interval -5 <= x <= 5\n", 18 | " nx = 100\n", 19 | " x = np.linspace(-5.0, 5.0, nx)\n", 20 | " y = gaussian(x, a=2.0, b=0.5, c=1.5)\n", 21 | " noise = np.random.normal(0.0, 0.2, nx)\n", 22 | " y += noise\n", 23 | " return x, y\n", 24 | "\n", 25 | "def plot(x, y, yfit):\n", 26 | " plt.plot(x, y, \"c\", label=\"Data\")\n", 27 | " plt.plot(x, yfit, \"g\", label=\"Fit\")\n", 28 | " plt.legend()\n", 29 | " plt.ylim(-0.5, 2.5)\n", 30 | " plt.show()\n", 31 | "\n", 32 | "x, y = noisy_gaussian()\n", 33 | "yfit = fit(x, y, n=5) # fit a 5th order polynomial to it\n", 34 | "plot(x, y, yfit)" 35 | ] 36 | } 37 | ], 38 | "metadata": { 39 | "kernelspec": { 40 | "display_name": "Python 3 (ipykernel)", 41 | "language": "python", 42 | "name": "python3" 43 | }, 44 | "language_info": { 45 | "codemirror_mode": { 46 | "name": "ipython", 47 | "version": 3 48 | }, 49 | "file_extension": ".py", 50 | "mimetype": "text/x-python", 51 | "name": "python", 52 | "nbconvert_exporter": "python", 53 | "pygments_lexer": "ipython3", 54 | "version": "3.11.3" 55 | } 56 | }, 57 | "nbformat": 4, 58 | "nbformat_minor": 5 59 | } 60 | -------------------------------------------------------------------------------- /examples/example8/center.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "eabfe4e0-d646-4d09-95a4-09ee3d030db6", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "\n", 13 | "def gaussian(x, a, b, c):\n", 14 | " #calculate a gaussian\n", 15 | " return a * np.exp(-b * (x-c)**2)\n", 16 | "\n", 17 | "def sinus ():\n", 18 | " # Here you can see a sinus function\n", 19 | " # Let's keep on adding lines\n", 20 | " # And lines\n", 21 | " nx = 100\n", 22 | " x = np.linspace(-5.0, 5.0, nx)\n", 23 | " y = np.sin(x)\n", 24 | " return x, y\n", 25 | "\n", 26 | "def noisy_gaussian():\n", 27 | " # gaussian array y in interval -5 <= x <= 5\n", 28 | " nx = 100\n", 29 | " x = np.linspace(-5.0, 5.0, nx)\n", 30 | " y = gaussian(x, a=2.0, b=0.5, c=1.5)\n", 31 | " noise = np.random.normal(0.0, 0.2, nx)\n", 32 | " y += noise\n", 33 | " return x, y" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "id": "cfdfd11a", 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": null, 47 | "id": "8819fa79", 48 | "metadata": {}, 49 | "outputs": [], 50 | "source": [] 51 | } 52 | ], 53 | "metadata": { 54 | "kernelspec": { 55 | "display_name": "Python 3 (ipykernel)", 56 | "language": "python", 57 | "name": "python3" 58 | }, 59 | "language_info": { 60 | "codemirror_mode": { 61 | "name": "ipython", 62 | "version": 3 63 | }, 64 | "file_extension": ".py", 65 | "mimetype": "text/x-python", 66 | "name": "python", 67 | "nbconvert_exporter": "python", 68 | "pygments_lexer": "ipython3", 69 | "version": "3.11.3" 70 | } 71 | }, 72 | "nbformat": 4, 73 | "nbformat_minor": 5 74 | } 75 | -------------------------------------------------------------------------------- /examples/example8/left.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "2d47bfe2-d91d-4f72-9426-1885c4edb5a9", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "%matplotlib inline\n", 13 | "%this is a comment\n", 14 | "\n", 15 | "def gaussian(x, a, b, c):\n", 16 | " return a * np.exp(-b * (x-c)**2)\n", 17 | "\n", 18 | "def noisy_gaussian():\n", 19 | " # gaussian array y in interval -5 <= x <= 5\n", 20 | " nx = 100\n", 21 | " x = np.linspace(-5.0, 5.0, nx)\n", 22 | " y = gaussian(x, a=2.0, b=0.5, c=1.5)\n", 23 | " noise = np.random.normal(0.0, 0.2, nx)\n", 24 | " y += noise\n", 25 | " return x, y" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "id": "4dd69787", 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "# one" 36 | ] 37 | } 38 | ], 39 | "metadata": { 40 | "kernelspec": { 41 | "display_name": "Python 3 (ipykernel)", 42 | "language": "python", 43 | "name": "python3" 44 | }, 45 | "language_info": { 46 | "codemirror_mode": { 47 | "name": "ipython", 48 | "version": 3 49 | }, 50 | "file_extension": ".py", 51 | "mimetype": "text/x-python", 52 | "name": "python", 53 | "nbconvert_exporter": "python", 54 | "pygments_lexer": "ipython3", 55 | "version": "3.11.3" 56 | } 57 | }, 58 | "nbformat": 4, 59 | "nbformat_minor": 5 60 | } 61 | -------------------------------------------------------------------------------- /examples/example8/right.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "ff160690-22c8-48f3-9b89-d78a8997baec", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "\n", 13 | "def gaussian(x, a, b, c):\n", 14 | " return a * np.exp(-b * (x-c)**2)\n", 15 | "\n", 16 | "def noisy_gaussian():\n", 17 | " # gaussian array y in interval -5 <= x <= 5\n", 18 | " nx = 100\n", 19 | " x = np.linspace(-5.0, 5.0, nx)\n", 20 | " y = gaussian(x, a=2.0, b=0.5, c=1.5)\n", 21 | " noise = np.random.normal(0.0, 0.2, nx)\n", 22 | " y += noise\n", 23 | " return x, y" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "id": "d2ac7887", 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": null, 37 | "id": "9b467904", 38 | "metadata": {}, 39 | "outputs": [], 40 | "source": [] 41 | } 42 | ], 43 | "metadata": { 44 | "kernelspec": { 45 | "display_name": "Python 3 (ipykernel)", 46 | "language": "python", 47 | "name": "python3" 48 | }, 49 | "language_info": { 50 | "codemirror_mode": { 51 | "name": "ipython", 52 | "version": 3 53 | }, 54 | "file_extension": ".py", 55 | "mimetype": "text/x-python", 56 | "name": "python", 57 | "nbconvert_exporter": "python", 58 | "pygments_lexer": "ipython3", 59 | "version": "3.11.3" 60 | } 61 | }, 62 | "nbformat": 4, 63 | "nbformat_minor": 5 64 | } 65 | -------------------------------------------------------------------------------- /examples/example9/center.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "eabfe4e0-d646-4d09-95a4-09ee3d030db6", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "\n", 13 | "def gaussian(x, a, b, c):\n", 14 | " #calculate a gaussian\n", 15 | " return a * np.exp(-b * (x-c)**2)\n", 16 | "\n", 17 | "def sinus ():\n", 18 | " # Here you can see a sinus function\n", 19 | " x = np.linspace(-5.0, 5.0, 100)\n", 20 | " y = np.sin(x)\n", 21 | " return x, y\n", 22 | "\n", 23 | "def noisy_gaussian():\n", 24 | " # gaussian array y in interval -5 <= x <= 5\n", 25 | " nx = 100\n", 26 | " x = np.linspace(-5.0, 5.0, nx)\n", 27 | " y = gaussian(x, a=2.0, b=0.5, c=1.5)\n", 28 | " noise = np.random.normal(0.0, 0.2, nx)\n", 29 | " y += noise\n", 30 | " return x, y" 31 | ] 32 | } 33 | ], 34 | "metadata": { 35 | "kernelspec": { 36 | "display_name": "Python 3 (ipykernel)", 37 | "language": "python", 38 | "name": "python3" 39 | }, 40 | "language_info": { 41 | "codemirror_mode": { 42 | "name": "ipython", 43 | "version": 3 44 | }, 45 | "file_extension": ".py", 46 | "mimetype": "text/x-python", 47 | "name": "python", 48 | "nbconvert_exporter": "python", 49 | "pygments_lexer": "ipython3", 50 | "version": "3.11.3" 51 | } 52 | }, 53 | "nbformat": 4, 54 | "nbformat_minor": 5 55 | } 56 | -------------------------------------------------------------------------------- /examples/example9/left.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "2d47bfe2-d91d-4f72-9426-1885c4edb5a9", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "%matplotlib inline\n", 13 | "%this is a comment\n", 14 | "\n", 15 | "def gaussian(x, a, b, c):\n", 16 | " return a * np.exp(-b * (x-c)**2)\n", 17 | "\n", 18 | "def noisy_gaussian():\n", 19 | " # gaussian array y in interval -5 <= x <= 5\n", 20 | " nx = 100\n", 21 | " x = np.linspace(-5.0, 5.0, nx)\n", 22 | " y = gaussian(x, a=2.0, b=0.5, c=1.5)\n", 23 | " noise = np.random.normal(0.0, 0.2, nx)\n", 24 | " y += noise\n", 25 | " # add a line\n", 26 | " # add a second line\n", 27 | " return x, y" 28 | ] 29 | } 30 | ], 31 | "metadata": { 32 | "kernelspec": { 33 | "display_name": "Python 3 (ipykernel)", 34 | "language": "python", 35 | "name": "python3" 36 | }, 37 | "language_info": { 38 | "codemirror_mode": { 39 | "name": "ipython", 40 | "version": 3 41 | }, 42 | "file_extension": ".py", 43 | "mimetype": "text/x-python", 44 | "name": "python", 45 | "nbconvert_exporter": "python", 46 | "pygments_lexer": "ipython3", 47 | "version": "3.11.3" 48 | } 49 | }, 50 | "nbformat": 4, 51 | "nbformat_minor": 5 52 | } 53 | -------------------------------------------------------------------------------- /examples/example9/right.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "ff160690-22c8-48f3-9b89-d78a8997baec", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "\n", 13 | "def gaussian(x, a, b, c):\n", 14 | " return a * np.exp(-b * (x-c)**2)\n", 15 | "\n", 16 | "def noisy_gaussian():\n", 17 | " # gaussian array y in interval -5 <= x <= 5\n", 18 | " nx = 100\n", 19 | " x = np.linspace(-5.0, 5.0, nx)\n", 20 | " y = gaussian(x, a=2.0, b=0.5, c=1.5)\n", 21 | " noise = np.random.normal(0.0, 0.2, nx)\n", 22 | " y += noise\n", 23 | " # add a line\n", 24 | " # add a second line\n", 25 | " return x, y" 26 | ] 27 | } 28 | ], 29 | "metadata": { 30 | "kernelspec": { 31 | "display_name": "Python 3 (ipykernel)", 32 | "language": "python", 33 | "name": "python3" 34 | }, 35 | "language_info": { 36 | "codemirror_mode": { 37 | "name": "ipython", 38 | "version": 3 39 | }, 40 | "file_extension": ".py", 41 | "mimetype": "text/x-python", 42 | "name": "python", 43 | "nbconvert_exporter": "python", 44 | "pygments_lexer": "ipython3", 45 | "version": "3.11.3" 46 | } 47 | }, 48 | "nbformat": 4, 49 | "nbformat_minor": 5 50 | } 51 | -------------------------------------------------------------------------------- /jupyter-config/jupyter_notebook_config.d/nbdime.json: -------------------------------------------------------------------------------- 1 | { 2 | "NotebookApp": { 3 | "nbserver_extensions": { 4 | "nbdime": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jupyter-config/jupyter_server_config.d/nbdime.json: -------------------------------------------------------------------------------- 1 | { 2 | "ServerApp": { 3 | "jpserver_extensions": { 4 | "nbdime": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jupyter-config/nbconfig/notebook.d/nbdime.json: -------------------------------------------------------------------------------- 1 | { 2 | "load_extensions": { 3 | "nbdime/index": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "independent" 3 | } 4 | -------------------------------------------------------------------------------- /nbdime/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | from functools import partial 7 | from ._version import __version__ 8 | 9 | from .diffing import diff, diff_notebooks 10 | from .patching import patch, patch_notebook 11 | from .merging import merge_notebooks, decide_merge, apply_decisions 12 | 13 | 14 | def _load_jupyter_server_extension(nb_server_app, nb6_entrypoint=False): 15 | # Wrap this here to avoid pulling in webapp in a normal run 16 | from .webapp.nb_server_extension import _load_jupyter_server_extension 17 | _load_jupyter_server_extension(nb_server_app, nb6_entrypoint=nb6_entrypoint) 18 | 19 | 20 | load_jupyter_server_extension = partial(_load_jupyter_server_extension, nb6_entrypoint=True) 21 | 22 | 23 | def _jupyter_server_extension_paths(): 24 | return [{ 25 | "module": "nbdime" 26 | }] 27 | 28 | 29 | _jupyter_server_extension_points = _jupyter_server_extension_paths 30 | 31 | 32 | def _jupyter_labextension_paths(): 33 | return [{ 34 | "src": "labextension", 35 | "dest": "nbdime-jupyterlab" 36 | }] 37 | 38 | 39 | def _jupyter_nbextension_paths(): 40 | return [dict( 41 | section="notebook", 42 | # the path is relative to the `nbdime` directory 43 | src="notebook_ext", 44 | # directory in the `nbextension/` namespace 45 | dest="nbdime", 46 | # _also_ in the `nbextension/` namespace 47 | require="nbdime/index")] 48 | 49 | 50 | __all__ = [ 51 | "__version__", 52 | "diff", "diff_notebooks", 53 | "patch", "patch_notebook", 54 | "decide_merge", "merge_notebooks", "apply_decisions", 55 | "load_jupyter_server_extension", 56 | "_load_jupyter_server_extension", 57 | "_jupyter_server_extension_points", 58 | "_jupyter_server_extension_paths", 59 | ] 60 | -------------------------------------------------------------------------------- /nbdime/_version.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Jupyter Development Team. 2 | # Distributed under the terms of the Modified BSD License. 3 | 4 | import re 5 | from collections import namedtuple 6 | 7 | VersionInfo = namedtuple("VersionInfo", ["major", "minor", "micro", "releaselevel", "serial"]) 8 | 9 | _specifier_ = {"a": "alpha", "b": "beta", "rc": "candidate", "": "final"} 10 | 11 | __version__ = "4.0.2" 12 | 13 | parser = re.compile( 14 | r"^(?P\d+)\.(?P\d+)\.(?P\d+)((?P[A-z]+)(?P\d+))?$" 15 | ) 16 | 17 | parsed_version = parser.match(__version__) 18 | groups = parsed_version.groupdict() 19 | 20 | release_level = groups.get("releaselevel", "") or "" 21 | 22 | version_info = VersionInfo( 23 | int(groups["major"]), 24 | int(groups["minor"]), 25 | int(groups["micro"]), 26 | # This will be whatever word is set to ensure `final` 27 | # is only set when releaselevel pattern is not found 28 | _specifier_.get(release_level, release_level), 29 | groups.get("serial", ""), 30 | ) 31 | -------------------------------------------------------------------------------- /nbdime/diffing/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | from .generic import diff 7 | from .notebooks import diff_notebooks 8 | 9 | __all__ = ["diff", "diff_notebooks"] 10 | -------------------------------------------------------------------------------- /nbdime/diffing/config.py: -------------------------------------------------------------------------------- 1 | 2 | class DiffConfig: 3 | """Set of predicates/differs/other configs to pass around""" 4 | 5 | def __init__(self, *, predicates=None, differs=None, atomic_paths=None): 6 | if predicates is None: 7 | from .generic import default_predicates 8 | predicates = default_predicates() 9 | 10 | if differs is None: 11 | from .generic import default_differs 12 | differs = default_differs() 13 | 14 | self.predicates = predicates 15 | self.differs = differs 16 | self._atomic_paths = atomic_paths or {} 17 | 18 | def diff_item_at_path(self, a, b, path): 19 | """Calculate the diff for path.""" 20 | self.differs[path](a, b, path=path, config=self) 21 | 22 | def is_atomic(self, x, path=None): 23 | "Return True for values that diff should treat as a single atomic value." 24 | try: 25 | return self._atomic_paths[path] 26 | except KeyError: 27 | return not isinstance(x, (str, list, dict)) 28 | 29 | def __copy__(self): 30 | return DiffConfig( 31 | predicates=self.predicates.copy(), 32 | differs=self.differs.copy(), 33 | atomic_paths=self._atomic_paths.copy(), 34 | ) 35 | -------------------------------------------------------------------------------- /nbdime/diffing/lcs.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | from ..diff_format import SequenceDiffBuilder 7 | 8 | 9 | def diff_from_lcs(A, B, A_indices, B_indices): 10 | """Compute the diff of A and B, given indices of their lcs.""" 11 | di = SequenceDiffBuilder() 12 | N, M = len(A), len(B) 13 | llcs = len(A_indices) 14 | assert llcs == len(B_indices) 15 | # x,y = how many symbols we have consumed from A and B 16 | x = 0 17 | y = 0 18 | for r in range(llcs): 19 | i = A_indices[r] 20 | j = B_indices[r] 21 | if i > x: 22 | di.removerange(x, i-x) 23 | if j > y: 24 | di.addrange(x, B[y:j]) 25 | x = i + 1 26 | y = j + 1 27 | if x < N: 28 | di.removerange(x, N-x) 29 | if y < M: 30 | di.addrange(x, B[y:M]) 31 | return di.validated() 32 | -------------------------------------------------------------------------------- /nbdime/diffing/seq_difflib.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | from difflib import SequenceMatcher 7 | from ..diff_format import SequenceDiffBuilder 8 | 9 | 10 | __all__ = ["diff_sequence_difflib"] 11 | 12 | 13 | def opcodes_to_diff(a, b, opcodes): 14 | "Convert difflib opcodes to nbdime diff format." 15 | di = SequenceDiffBuilder() 16 | for opcode in opcodes: 17 | action, abegin, aend, bbegin, bend = opcode 18 | asize = aend - abegin 19 | #bsize = bend - bbegin 20 | if action == "equal": 21 | # Unlike difflib we don't represent equal stretches explicitly 22 | pass 23 | elif action == "replace": 24 | di.removerange(abegin, asize) 25 | di.addrange(abegin, b[bbegin:bend]) 26 | elif action == "insert": 27 | di.addrange(abegin, b[bbegin:bend]) 28 | elif action == "delete": 29 | di.removerange(abegin, asize) 30 | else: 31 | raise RuntimeError("Unknown action {}".format(action)) 32 | return di.validated() 33 | 34 | 35 | def diff_sequence_difflib(a, b): 36 | """Compute the diff of two sequences. 37 | 38 | This implementation uses SequenceMatcher from the builtin Python difflib. 39 | 40 | By the difflib documentation this should work for sequences of 41 | hashable objects, i.e. the elements of the sequences are only 42 | compared for full equality. Therefore this function does not take 43 | a custom compare function like the other diff_sequence_* variants. 44 | """ 45 | assert not any(isinstance(x, (list, dict)) for x in a), 'element in sequence not hashable' 46 | assert not any(isinstance(x, (list, dict)) for x in b), 'element in sequence not hashable' 47 | s = SequenceMatcher(None, a, b, autojunk=False) 48 | return opcodes_to_diff(a, b, s.get_opcodes()) 49 | -------------------------------------------------------------------------------- /nbdime/diffing/seq_myers.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | import operator 7 | 8 | __all__ = ["diff_sequence_myers"] 9 | 10 | 11 | def diff_sequence_myers(A, B, compare=operator.__eq__): 12 | """Compute the diff of A and B using Myers' O(ND) algorithm.""" 13 | raise NotImplementedError("Work in progress...") 14 | -------------------------------------------------------------------------------- /nbdime/ignorables.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (c) IPython Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | diff_ignorables = ('sources', 'outputs', 'attachments', 'metadata', 'id', 'details') 7 | -------------------------------------------------------------------------------- /nbdime/log.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | import logging 7 | 8 | 9 | class NBDiffFormatError(ValueError): 10 | pass 11 | 12 | 13 | def init_logging(level=logging.INFO): 14 | """Sets up logging for nbdime entry points. 15 | 16 | Call this in all entry points (if __name__ == "__main__"). 17 | Sets the log level for all nbdime loggers to `level`, 18 | unless `level` is given as `None`. 19 | """ 20 | format = '[%(levelname)1.1s %(module)s:%(lineno)d] %(message)s' 21 | logging.basicConfig(format=format, level=level) 22 | logging.captureWarnings(True) 23 | 24 | 25 | def set_nbdime_log_level(level, set_main=True): 26 | """Set a log level for nbdime loggers""" 27 | logger.setLevel(level) 28 | if set_main: 29 | _baseLogger = logging.getLogger() 30 | _baseLogger.setLevel(level) 31 | 32 | 33 | logger = logging.getLogger('nbdime') 34 | 35 | debug = logger.debug 36 | info = logger.info 37 | warning = logger.warning 38 | error = logger.error 39 | exception = logger.exception 40 | critical = logger.critical 41 | -------------------------------------------------------------------------------- /nbdime/merging/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | from .generic import decide_merge 7 | from .decisions import apply_decisions 8 | from .notebooks import merge_notebooks 9 | 10 | __all__ = ["decide_merge", "merge_notebooks", "apply_decisions"] 11 | -------------------------------------------------------------------------------- /nbdime/notebook_ext/nbdime.yaml: -------------------------------------------------------------------------------- 1 | Type: Jupyter Notebook Extension 2 | Name: nbdime 3 | Description: Tools for diffing and merging of Jupyter notebooks. 4 | Main: index.js 5 | Compatibility: Jupyter 4.x, 5.x 6 | Parameters: 7 | 8 | - name: nbdime.add_checkpoints_toolbar_button 9 | description: Add a toolbar button to diff against checkpointed notebook 10 | input_type: checkbox 11 | default: true 12 | 13 | - name: nbdime.add_git_toolbar_button 14 | description: Add a toolbar button to diff against most recently git committed notebook 15 | input_type: checkbox 16 | default: true 17 | -------------------------------------------------------------------------------- /nbdime/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | 7 | -------------------------------------------------------------------------------- /nbdime/tests/files/apap--1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Ipsum lorem:" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 4, 13 | "metadata": { 14 | "collapsed": false 15 | }, 16 | "outputs": [ 17 | { 18 | "data": { 19 | "image/png" : "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAUSURBVBhXY/j//39+fj6I4uTkBAA+xgdjMUPuuQAAAABJRU5ErkJggg==", 20 | "text/plain": [ 21 | "" 22 | ] 23 | }, 24 | "metadata": { 25 | "image/png": { 26 | "height": 403, 27 | "width": 596 28 | } 29 | }, 30 | "output_type": "display_data" 31 | } 32 | ], 33 | "source": [ 34 | "Some lines\n", 35 | "To help with\n", 36 | "Alignment\n", 37 | "Another one bites the dust\n" 38 | ] 39 | } 40 | ], 41 | "metadata": { 42 | "kernelspec": { 43 | "display_name": "Python 3", 44 | "language": "python", 45 | "name": "python3" 46 | }, 47 | "language_info": { 48 | "codemirror_mode": { 49 | "name": "ipython", 50 | "version": 3 51 | }, 52 | "file_extension": ".py", 53 | "mimetype": "text/x-python", 54 | "name": "python", 55 | "nbconvert_exporter": "python", 56 | "pygments_lexer": "ipython3", 57 | "version": "3.5.2" 58 | } 59 | }, 60 | "nbformat": 4, 61 | "nbformat_minor": 0 62 | } 63 | -------------------------------------------------------------------------------- /nbdime/tests/files/apap--2.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Foo bar, foo bar, the quick brown fox:" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 4, 13 | "metadata": { 14 | "collapsed": false 15 | }, 16 | "outputs": [ 17 | { 18 | "data": { 19 | "image/png" : "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAUSURBVBhXY/j//39+fj6I4uTkBAA+xgdjMUPuuQAAAABJRU5ErkJggg==", 20 | "text/plain": [ 21 | "" 22 | ] 23 | }, 24 | "metadata": { 25 | "image/png": { 26 | "height": 403, 27 | "width": 596 28 | } 29 | }, 30 | "output_type": "display_data" 31 | } 32 | ], 33 | "source": [ 34 | "Some lines\n", 35 | "To help with\n", 36 | "Alignment\n", 37 | "Another one bites the dust\n" 38 | ] 39 | } 40 | ], 41 | "metadata": { 42 | "kernelspec": { 43 | "display_name": "Python 3", 44 | "language": "python", 45 | "name": "python3" 46 | }, 47 | "language_info": { 48 | "codemirror_mode": { 49 | "name": "ipython", 50 | "version": 3 51 | }, 52 | "file_extension": ".py", 53 | "mimetype": "text/x-python", 54 | "name": "python", 55 | "nbconvert_exporter": "python", 56 | "pygments_lexer": "ipython3", 57 | "version": "3.5.2" 58 | } 59 | }, 60 | "nbformat": 4, 61 | "nbformat_minor": 0 62 | } 63 | -------------------------------------------------------------------------------- /nbdime/tests/files/apap--3.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "Foo bar, foo bar, What is love, baby don't hurt me:" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 4, 13 | "metadata": { 14 | "collapsed": false 15 | }, 16 | "outputs": [ 17 | { 18 | "data": { 19 | "image/png" : "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAAZSURBVBhXY/4PBB8+fGhgfG9p///LoycMAHyNDHEXLXDWAAAAAElFTkSuQmCC", 20 | "text/plain": [ 21 | "" 22 | ] 23 | }, 24 | "metadata": { 25 | "image/png": { 26 | "height": 403, 27 | "width": 596 28 | } 29 | }, 30 | "output_type": "display_data" 31 | } 32 | ], 33 | "source": [ 34 | "Some lines\n", 35 | "To help with\n", 36 | "Alignment\n", 37 | "War! What is it good for?\n" 38 | ] 39 | } 40 | ], 41 | "metadata": { 42 | "kernelspec": { 43 | "display_name": "Python 3", 44 | "language": "python", 45 | "name": "python3" 46 | }, 47 | "language_info": { 48 | "codemirror_mode": { 49 | "name": "ipython", 50 | "version": 3 51 | }, 52 | "file_extension": ".py", 53 | "mimetype": "text/x-python", 54 | "name": "python", 55 | "nbconvert_exporter": "python", 56 | "pygments_lexer": "ipython3", 57 | "version": "3.5.2" 58 | } 59 | }, 60 | "nbformat": 4, 61 | "nbformat_minor": 0 62 | } 63 | -------------------------------------------------------------------------------- /nbdime/tests/files/apap.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 4, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "data": { 12 | "image/png" : "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAUSURBVBhXY1ixYkVraysDCDMwAAAtgAUX7HVO4wAAAABJRU5ErkJggg==", 13 | "text/plain": [ 14 | "" 15 | ] 16 | }, 17 | "metadata": { 18 | "image/png": { 19 | "height": 403, 20 | "width": 596 21 | } 22 | }, 23 | "output_type": "display_data" 24 | } 25 | ], 26 | "source": [ 27 | "Some lines\n", 28 | "To help with\n", 29 | "Alignment\n", 30 | "Yeah\n" 31 | ] 32 | } 33 | ], 34 | "metadata": { 35 | "kernelspec": { 36 | "display_name": "Python 3", 37 | "language": "python", 38 | "name": "python3" 39 | }, 40 | "language_info": { 41 | "codemirror_mode": { 42 | "name": "ipython", 43 | "version": 3 44 | }, 45 | "file_extension": ".py", 46 | "mimetype": "text/x-python", 47 | "name": "python", 48 | "nbconvert_exporter": "python", 49 | "pygments_lexer": "ipython3", 50 | "version": "3.5.2" 51 | } 52 | }, 53 | "nbformat": 4, 54 | "nbformat_minor": 0 55 | } 56 | -------------------------------------------------------------------------------- /nbdime/tests/files/attachment--change_attachment.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "This is a test notebook with only markdown cells\n", 8 | " ![inline image](attachment:test.png)\n", 9 | "======" 10 | ], 11 | "attachments" : { 12 | "test.png": { 13 | "image/png" : "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAUSURBVBhXY/j//39+fj6I4uTkBAA+xgdjMUPuuQAAAABJRU5ErkJggg==" 14 | } 15 | } 16 | } 17 | ], 18 | "metadata": { 19 | "celltoolbar": "Raw Cell Format", 20 | "kernelspec": { 21 | "display_name": "Python 3", 22 | "language": "python", 23 | "name": "python3" 24 | }, 25 | "language_info": { 26 | "codemirror_mode": { 27 | "name": "ipython", 28 | "version": 3 29 | }, 30 | "file_extension": ".py", 31 | "mimetype": "text/x-python", 32 | "name": "python", 33 | "nbconvert_exporter": "python", 34 | "pygments_lexer": "ipython3", 35 | "version": "3.5.2" 36 | } 37 | }, 38 | "nbformat": 4, 39 | "nbformat_minor": 0 40 | } 41 | -------------------------------------------------------------------------------- /nbdime/tests/files/attachment--empty_attachments_list.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "This is a test notebook with only markdown cells\n", 8 | " ![inline image](attachment:test.png)\n", 9 | "======" 10 | ], 11 | "attachments": {} 12 | } 13 | ], 14 | "metadata": { 15 | "celltoolbar": "Raw Cell Format", 16 | "kernelspec": { 17 | "display_name": "Python 3", 18 | "language": "python", 19 | "name": "python3" 20 | }, 21 | "language_info": { 22 | "codemirror_mode": { 23 | "name": "ipython", 24 | "version": 3 25 | }, 26 | "file_extension": ".py", 27 | "mimetype": "text/x-python", 28 | "name": "python", 29 | "nbconvert_exporter": "python", 30 | "pygments_lexer": "ipython3", 31 | "version": "3.5.2" 32 | } 33 | }, 34 | "nbformat": 4, 35 | "nbformat_minor": 0 36 | } 37 | -------------------------------------------------------------------------------- /nbdime/tests/files/attachment--remove_attachment.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "This is a test notebook with only markdown cells\n", 8 | " ![inline image](attachment:test.png)\n", 9 | "======" 10 | ] 11 | } 12 | ], 13 | "metadata": { 14 | "celltoolbar": "Raw Cell Format", 15 | "kernelspec": { 16 | "display_name": "Python 3", 17 | "language": "python", 18 | "name": "python3" 19 | }, 20 | "language_info": { 21 | "codemirror_mode": { 22 | "name": "ipython", 23 | "version": 3 24 | }, 25 | "file_extension": ".py", 26 | "mimetype": "text/x-python", 27 | "name": "python", 28 | "nbconvert_exporter": "python", 29 | "pygments_lexer": "ipython3", 30 | "version": "3.5.2" 31 | } 32 | }, 33 | "nbformat": 4, 34 | "nbformat_minor": 0 35 | } 36 | -------------------------------------------------------------------------------- /nbdime/tests/files/attachment.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "This is a test notebook with only markdown cells\n", 8 | " ![inline image](attachment:test.png)\n", 9 | "======" 10 | ], 11 | "attachments" : { 12 | "test.png": { 13 | "image/png" : "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAUSURBVBhXY1ixYkVraysDCDMwAAAtgAUX7HVO4wAAAABJRU5ErkJggg==" 14 | } 15 | } 16 | } 17 | ], 18 | "metadata": { 19 | "celltoolbar": "Raw Cell Format", 20 | "kernelspec": { 21 | "display_name": "Python 3", 22 | "language": "python", 23 | "name": "python3" 24 | }, 25 | "language_info": { 26 | "codemirror_mode": { 27 | "name": "ipython", 28 | "version": 3 29 | }, 30 | "file_extension": ".py", 31 | "mimetype": "text/x-python", 32 | "name": "python", 33 | "nbconvert_exporter": "python", 34 | "pygments_lexer": "ipython3", 35 | "version": "3.5.2" 36 | } 37 | }, 38 | "nbformat": 4, 39 | "nbformat_minor": 0 40 | } 41 | -------------------------------------------------------------------------------- /nbdime/tests/files/empty.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": {}, 5 | "cells": [] 6 | } 7 | -------------------------------------------------------------------------------- /nbdime/tests/files/foo--1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 5, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "data": { 12 | "text/plain": [ 13 | "6" 14 | ] 15 | }, 16 | "execution_count": 5, 17 | "metadata": {}, 18 | "output_type": "execute_result" 19 | } 20 | ], 21 | "source": [ 22 | "def foe(x, y):\n", 23 | " return x + y\n", 24 | "foe(3, 2)" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 6, 30 | "metadata": { 31 | "collapsed": false 32 | }, 33 | "outputs": [ 34 | { 35 | "data": { 36 | "text/plain": [ 37 | "2" 38 | ] 39 | }, 40 | "execution_count": 6, 41 | "metadata": {}, 42 | "output_type": "execute_result" 43 | } 44 | ], 45 | "source": [ 46 | "def foo(x, y):\n", 47 | " return x * y\n", 48 | "foo(1, 2)" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "metadata": { 55 | "collapsed": true 56 | }, 57 | "outputs": [], 58 | "source": [] 59 | } 60 | ], 61 | "metadata": { 62 | "kernelspec": { 63 | "display_name": "Python 2", 64 | "language": "python", 65 | "name": "python2" 66 | }, 67 | "language_info": { 68 | "codemirror_mode": { 69 | "name": "ipython", 70 | "version": 2 71 | }, 72 | "file_extension": ".py", 73 | "mimetype": "text/x-python", 74 | "name": "python", 75 | "nbconvert_exporter": "python", 76 | "pygments_lexer": "ipython2", 77 | "version": "2.7.8" 78 | } 79 | }, 80 | "nbformat": 4, 81 | "nbformat_minor": 0 82 | } 83 | -------------------------------------------------------------------------------- /nbdime/tests/files/foo--2.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 5, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "data": { 12 | "text/plain": [ 13 | "3" 14 | ] 15 | }, 16 | "execution_count": 5, 17 | "metadata": {}, 18 | "output_type": "execute_result" 19 | } 20 | ], 21 | "source": [ 22 | "def foo(x, y):\n", 23 | " return x + y\n", 24 | "foo(1, 2)" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 6, 30 | "metadata": { 31 | "collapsed": false 32 | }, 33 | "outputs": [ 34 | { 35 | "data": { 36 | "text/plain": [ 37 | "2" 38 | ] 39 | }, 40 | "execution_count": 6, 41 | "metadata": {}, 42 | "output_type": "execute_result" 43 | } 44 | ], 45 | "source": [ 46 | "def foe(x, y):\n", 47 | " return x * y\n", 48 | "foe(1, 2)" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": null, 54 | "metadata": { 55 | "collapsed": true 56 | }, 57 | "outputs": [], 58 | "source": [] 59 | } 60 | ], 61 | "metadata": { 62 | "kernelspec": { 63 | "display_name": "Python 2", 64 | "language": "python", 65 | "name": "python2" 66 | }, 67 | "language_info": { 68 | "codemirror_mode": { 69 | "name": "ipython", 70 | "version": 2 71 | }, 72 | "file_extension": ".py", 73 | "mimetype": "text/x-python", 74 | "name": "python", 75 | "nbconvert_exporter": "python", 76 | "pygments_lexer": "ipython2", 77 | "version": "2.7.8" 78 | } 79 | }, 80 | "nbformat": 4, 81 | "nbformat_minor": 0 82 | } 83 | -------------------------------------------------------------------------------- /nbdime/tests/files/inline-conflict--1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stdout", 12 | "output_type": "stream", 13 | "text": [ 14 | "3\n" 15 | ] 16 | } 17 | ], 18 | "source": [ 19 | "x = 1\n", 20 | "y = 3\n", 21 | "print(x * y)" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": { 28 | "collapsed": true 29 | }, 30 | "outputs": [], 31 | "source": [] 32 | } 33 | ], 34 | "metadata": { 35 | "kernelspec": { 36 | "display_name": "Python 2", 37 | "language": "python", 38 | "name": "python2" 39 | }, 40 | "language_info": { 41 | "codemirror_mode": { 42 | "name": "ipython", 43 | "version": 2 44 | }, 45 | "file_extension": ".py", 46 | "mimetype": "text/x-python", 47 | "name": "python", 48 | "nbconvert_exporter": "python", 49 | "pygments_lexer": "ipython2", 50 | "version": "2.7.12" 51 | } 52 | }, 53 | "nbformat": 4, 54 | "nbformat_minor": 1 55 | } 56 | -------------------------------------------------------------------------------- /nbdime/tests/files/inline-conflict--2.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stdout", 12 | "output_type": "stream", 13 | "text": [ 14 | "0\n" 15 | ] 16 | } 17 | ], 18 | "source": [ 19 | "x = 1\n", 20 | "y = 3\n", 21 | "z = 4\n", 22 | "print(x * y / z)" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": null, 28 | "metadata": { 29 | "collapsed": true 30 | }, 31 | "outputs": [], 32 | "source": [] 33 | } 34 | ], 35 | "metadata": { 36 | "kernelspec": { 37 | "display_name": "Python 2", 38 | "language": "python", 39 | "name": "python2" 40 | }, 41 | "language_info": { 42 | "codemirror_mode": { 43 | "name": "ipython", 44 | "version": 2 45 | }, 46 | "file_extension": ".py", 47 | "mimetype": "text/x-python", 48 | "name": "python", 49 | "nbconvert_exporter": "python", 50 | "pygments_lexer": "ipython2", 51 | "version": "2.7.12" 52 | } 53 | }, 54 | "nbformat": 4, 55 | "nbformat_minor": 1 56 | } 57 | -------------------------------------------------------------------------------- /nbdime/tests/files/inline-conflict--3.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stdout", 12 | "output_type": "stream", 13 | "text": [ 14 | "3\n" 15 | ] 16 | } 17 | ], 18 | "source": [ 19 | "x = 1\n", 20 | "q = 3.1\n", 21 | "print(x + q)" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": { 28 | "collapsed": true 29 | }, 30 | "outputs": [], 31 | "source": [] 32 | } 33 | ], 34 | "metadata": { 35 | "kernelspec": { 36 | "display_name": "Python 2", 37 | "language": "python", 38 | "name": "python2" 39 | }, 40 | "language_info": { 41 | "codemirror_mode": { 42 | "name": "ipython", 43 | "version": 2 44 | }, 45 | "file_extension": ".py", 46 | "mimetype": "text/x-python", 47 | "name": "python", 48 | "nbconvert_exporter": "python", 49 | "pygments_lexer": "ipython2", 50 | "version": "2.7.12" 51 | } 52 | }, 53 | "nbformat": 4, 54 | "nbformat_minor": 1 55 | } 56 | -------------------------------------------------------------------------------- /nbdime/tests/files/markdown-only--1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "This is a test notebook with only markdown cells\n", 8 | "======" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": {}, 21 | "source": [ 22 | "In other news\n", 23 | "------------\n", 24 | "The quick brown fox jumps over the lazy dog." 25 | ] 26 | } 27 | ], 28 | "metadata": { 29 | "kernelspec": { 30 | "display_name": "Python 2", 31 | "language": "python", 32 | "name": "python2" 33 | }, 34 | "language_info": { 35 | "codemirror_mode": { 36 | "name": "ipython", 37 | "version": 2 38 | }, 39 | "file_extension": ".py", 40 | "mimetype": "text/x-python", 41 | "name": "python", 42 | "nbconvert_exporter": "python", 43 | "pygments_lexer": "ipython2", 44 | "version": "2.7.8" 45 | } 46 | }, 47 | "nbformat": 4, 48 | "nbformat_minor": 0 49 | } 50 | -------------------------------------------------------------------------------- /nbdime/tests/files/markdown-only--2.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "This is a test notebook with markdown cells only\n", 8 | "=======" 9 | ] 10 | }, 11 | { 12 | "cell_type": "markdown", 13 | "metadata": {}, 14 | "source": [ 15 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": {}, 21 | "source": [ 22 | "In the news\n", 23 | "-----------\n", 24 | "The quick brown fox jumps over the lazy dog!" 25 | ] 26 | } 27 | ], 28 | "metadata": { 29 | "kernelspec": { 30 | "display_name": "Python 2", 31 | "language": "python", 32 | "name": "python2" 33 | }, 34 | "language_info": { 35 | "codemirror_mode": { 36 | "name": "ipython", 37 | "version": 2 38 | }, 39 | "file_extension": ".py", 40 | "mimetype": "text/x-python", 41 | "name": "python", 42 | "nbconvert_exporter": "python", 43 | "pygments_lexer": "ipython2", 44 | "version": "2.7.8" 45 | } 46 | }, 47 | "nbformat": 4, 48 | "nbformat_minor": 0 49 | } 50 | -------------------------------------------------------------------------------- /nbdime/tests/files/multi_cell_nb--cellchange.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "from math import sqrt\n", 12 | "\n", 13 | "def f(x, y):\n", 14 | " r2 = x**2 + y**2\n", 15 | " return sqrt(r2)" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": 2, 21 | "metadata": { 22 | "collapsed": false 23 | }, 24 | "outputs": [ 25 | { 26 | "name": "stdout", 27 | "output_type": "stream", 28 | "text": [ 29 | "5.0\n" 30 | ] 31 | } 32 | ], 33 | "source": [ 34 | "l = f(3, 4)\n", 35 | "print(l)" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "## from Inserted Markdown cell!" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 3, 48 | "metadata": { 49 | "collapsed": false 50 | }, 51 | "outputs": [ 52 | { 53 | "data": { 54 | "text/plain": [ 55 | "6.324555320336759" 56 | ] 57 | }, 58 | "execution_count": 3, 59 | "metadata": {}, 60 | "output_type": "execute_result" 61 | } 62 | ], 63 | "source": [ 64 | "f(6, -2)" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": null, 70 | "metadata": { 71 | "collapsed": true 72 | }, 73 | "outputs": [], 74 | "source": [] 75 | } 76 | ], 77 | "metadata": { 78 | "kernelspec": { 79 | "display_name": "Python 3", 80 | "language": "python", 81 | "name": "python3" 82 | }, 83 | "language_info": { 84 | "codemirror_mode": { 85 | "name": "ipython", 86 | "version": 3 87 | }, 88 | "file_extension": ".py", 89 | "mimetype": "text/x-python", 90 | "name": "python", 91 | "nbconvert_exporter": "python", 92 | "pygments_lexer": "ipython3", 93 | "version": "3.5.1" 94 | } 95 | }, 96 | "nbformat": 4, 97 | "nbformat_minor": 0 98 | } 99 | -------------------------------------------------------------------------------- /nbdime/tests/files/single_cell_nb--changed_ec.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stdout", 12 | "output_type": "stream", 13 | "text": [ 14 | "6\n" 15 | ] 16 | } 17 | ], 18 | "source": [ 19 | "def printit(x):\n", 20 | " print(x + 3)\n", 21 | "printit(3)" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": { 28 | "collapsed": true 29 | }, 30 | "outputs": [], 31 | "source": [] 32 | } 33 | ], 34 | "metadata": { 35 | "kernelspec": { 36 | "display_name": "Python 3", 37 | "language": "python", 38 | "name": "python3" 39 | }, 40 | "language_info": { 41 | "codemirror_mode": { 42 | "name": "ipython", 43 | "version": 3 44 | }, 45 | "file_extension": ".py", 46 | "mimetype": "text/x-python", 47 | "name": "python", 48 | "nbconvert_exporter": "python", 49 | "pygments_lexer": "ipython3", 50 | "version": "3.4.3+" 51 | } 52 | }, 53 | "nbformat": 4, 54 | "nbformat_minor": 0 55 | } 56 | -------------------------------------------------------------------------------- /nbdime/tests/files/single_cell_nb--changed_metadata.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false, 8 | "nbdime-dummy-field": 42 9 | }, 10 | "outputs": [ 11 | { 12 | "name": "stdout", 13 | "output_type": "stream", 14 | "text": [ 15 | "6\n" 16 | ] 17 | } 18 | ], 19 | "source": [ 20 | "def printit(x):\n", 21 | " print(x + 3)\n", 22 | "printit(3)" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": null, 28 | "metadata": { 29 | "collapsed": true 30 | }, 31 | "outputs": [], 32 | "source": [] 33 | } 34 | ], 35 | "metadata": { 36 | "kernelspec": { 37 | "display_name": "Python 3", 38 | "language": "python", 39 | "name": "python3" 40 | }, 41 | "language_info": { 42 | "codemirror_mode": { 43 | "name": "ipython", 44 | "version": 3 45 | }, 46 | "file_extension": ".py", 47 | "mimetype": "text/x-python", 48 | "name": "python", 49 | "nbconvert_exporter": "python", 50 | "pygments_lexer": "ipython3", 51 | "version": "3.4.2+" 52 | } 53 | }, 54 | "nbformat": 4, 55 | "nbformat_minor": 0 56 | } 57 | -------------------------------------------------------------------------------- /nbdime/tests/files/single_cell_nb--changed_output.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stdout", 12 | "output_type": "stream", 13 | "text": [ 14 | "7\n" 15 | ] 16 | } 17 | ], 18 | "source": [ 19 | "def printit(x):\n", 20 | " print(x + 3)\n", 21 | "printit(3)" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": { 28 | "collapsed": true 29 | }, 30 | "outputs": [], 31 | "source": [] 32 | } 33 | ], 34 | "metadata": { 35 | "kernelspec": { 36 | "display_name": "Python 3", 37 | "language": "python", 38 | "name": "python3" 39 | }, 40 | "language_info": { 41 | "codemirror_mode": { 42 | "name": "ipython", 43 | "version": 3 44 | }, 45 | "file_extension": ".py", 46 | "mimetype": "text/x-python", 47 | "name": "python", 48 | "nbconvert_exporter": "python", 49 | "pygments_lexer": "ipython3", 50 | "version": "3.4.3+" 51 | } 52 | }, 53 | "nbformat": 4, 54 | "nbformat_minor": 0 55 | } 56 | -------------------------------------------------------------------------------- /nbdime/tests/files/single_cell_nb--changed_source.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stdout", 12 | "output_type": "stream", 13 | "text": [ 14 | "6\n" 15 | ] 16 | } 17 | ], 18 | "source": [ 19 | "def printit(x):\n", 20 | " print(x + 3)\n", 21 | "printit(4)" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": { 28 | "collapsed": true 29 | }, 30 | "outputs": [], 31 | "source": [] 32 | } 33 | ], 34 | "metadata": { 35 | "kernelspec": { 36 | "display_name": "Python 3", 37 | "language": "python", 38 | "name": "python3" 39 | }, 40 | "language_info": { 41 | "codemirror_mode": { 42 | "name": "ipython", 43 | "version": 3 44 | }, 45 | "file_extension": ".py", 46 | "mimetype": "text/x-python", 47 | "name": "python", 48 | "nbconvert_exporter": "python", 49 | "pygments_lexer": "ipython3", 50 | "version": "3.4.3+" 51 | } 52 | }, 53 | "nbformat": 4, 54 | "nbformat_minor": 0 55 | } 56 | -------------------------------------------------------------------------------- /nbdime/tests/files/single_cell_nb--changed_source_output.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stdout", 12 | "output_type": "stream", 13 | "text": [ 14 | "7\n" 15 | ] 16 | } 17 | ], 18 | "source": [ 19 | "def printit(x):\n", 20 | " print(x + 3)\n", 21 | "printit(4)" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": { 28 | "collapsed": true 29 | }, 30 | "outputs": [], 31 | "source": [] 32 | } 33 | ], 34 | "metadata": { 35 | "kernelspec": { 36 | "display_name": "Python 3", 37 | "language": "python", 38 | "name": "python3" 39 | }, 40 | "language_info": { 41 | "codemirror_mode": { 42 | "name": "ipython", 43 | "version": 3 44 | }, 45 | "file_extension": ".py", 46 | "mimetype": "text/x-python", 47 | "name": "python", 48 | "nbconvert_exporter": "python", 49 | "pygments_lexer": "ipython3", 50 | "version": "3.4.3+" 51 | } 52 | }, 53 | "nbformat": 4, 54 | "nbformat_minor": 0 55 | } 56 | -------------------------------------------------------------------------------- /nbdime/tests/files/single_cell_nb--changed_source_output_ec.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stdout", 12 | "output_type": "stream", 13 | "text": [ 14 | "7\n" 15 | ] 16 | } 17 | ], 18 | "source": [ 19 | "def printit(x):\n", 20 | " print(x + 3)\n", 21 | "printit(4)" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": { 28 | "collapsed": true 29 | }, 30 | "outputs": [], 31 | "source": [] 32 | } 33 | ], 34 | "metadata": { 35 | "kernelspec": { 36 | "display_name": "Python 3", 37 | "language": "python", 38 | "name": "python3" 39 | }, 40 | "language_info": { 41 | "codemirror_mode": { 42 | "name": "ipython", 43 | "version": 3 44 | }, 45 | "file_extension": ".py", 46 | "mimetype": "text/x-python", 47 | "name": "python", 48 | "nbconvert_exporter": "python", 49 | "pygments_lexer": "ipython3", 50 | "version": "3.4.3+" 51 | } 52 | }, 53 | "nbformat": 4, 54 | "nbformat_minor": 0 55 | } 56 | -------------------------------------------------------------------------------- /nbdime/tests/files/single_cell_nb--json_output.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "output_type": "display_data", 12 | "metadata": { 13 | "collapsed": false 14 | }, 15 | "data": { 16 | "application/json": { 17 | "json": { 18 | "a": 54, 19 | "c": [ 20 | "abc", 21 | "def" 22 | ], 23 | "b": null 24 | } 25 | } 26 | } 27 | } 28 | ], 29 | "source": [ 30 | "def printit(x):\n", 31 | " print(x + 3)\n", 32 | "printit(3)" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": { 39 | "collapsed": true 40 | }, 41 | "outputs": [], 42 | "source": [] 43 | } 44 | ], 45 | "metadata": { 46 | "kernelspec": { 47 | "display_name": "Python 3", 48 | "language": "python", 49 | "name": "python3" 50 | }, 51 | "language_info": { 52 | "codemirror_mode": { 53 | "name": "ipython", 54 | "version": 3 55 | }, 56 | "file_extension": ".py", 57 | "mimetype": "text/x-python", 58 | "name": "python", 59 | "nbconvert_exporter": "python", 60 | "pygments_lexer": "ipython3", 61 | "version": "3.4.3+" 62 | } 63 | }, 64 | "nbformat": 4, 65 | "nbformat_minor": 0 66 | } 67 | -------------------------------------------------------------------------------- /nbdime/tests/files/single_cell_nb--json_output_changed.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "output_type": "display_data", 12 | "metadata": { 13 | "collapsed": false 14 | }, 15 | "data": { 16 | "application/json": { 17 | "json": { 18 | "c": [ 19 | "def", 20 | "abc" 21 | ], 22 | "a": 54, 23 | "b": null 24 | } 25 | } 26 | } 27 | } 28 | ], 29 | "source": [ 30 | "def printit(x):\n", 31 | " print(x + 3)\n", 32 | "printit(3)" 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": { 39 | "collapsed": true 40 | }, 41 | "outputs": [], 42 | "source": [] 43 | } 44 | ], 45 | "metadata": { 46 | "kernelspec": { 47 | "display_name": "Python 3", 48 | "language": "python", 49 | "name": "python3" 50 | }, 51 | "language_info": { 52 | "codemirror_mode": { 53 | "name": "ipython", 54 | "version": 3 55 | }, 56 | "file_extension": ".py", 57 | "mimetype": "text/x-python", 58 | "name": "python", 59 | "nbconvert_exporter": "python", 60 | "pygments_lexer": "ipython3", 61 | "version": "3.4.3+" 62 | } 63 | }, 64 | "nbformat": 4, 65 | "nbformat_minor": 0 66 | } 67 | -------------------------------------------------------------------------------- /nbdime/tests/files/single_cell_nb.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": false 8 | }, 9 | "outputs": [ 10 | { 11 | "name": "stdout", 12 | "output_type": "stream", 13 | "text": [ 14 | "6\n" 15 | ] 16 | } 17 | ], 18 | "source": [ 19 | "def printit(x):\n", 20 | " print(x + 3)\n", 21 | "printit(3)" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": { 28 | "collapsed": true 29 | }, 30 | "outputs": [], 31 | "source": [] 32 | } 33 | ], 34 | "metadata": { 35 | "kernelspec": { 36 | "display_name": "Python 3", 37 | "language": "python", 38 | "name": "python3" 39 | }, 40 | "language_info": { 41 | "codemirror_mode": { 42 | "name": "ipython", 43 | "version": 3 44 | }, 45 | "file_extension": ".py", 46 | "mimetype": "text/x-python", 47 | "name": "python", 48 | "nbconvert_exporter": "python", 49 | "pygments_lexer": "ipython3", 50 | "version": "3.4.3+" 51 | } 52 | }, 53 | "nbformat": 4, 54 | "nbformat_minor": 0 55 | } 56 | -------------------------------------------------------------------------------- /nbdime/tests/files/source-conflict--1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "test\n", 12 | "hello\n", 13 | "more\n", 14 | "text\n", 15 | "world" 16 | ] 17 | } 18 | ], 19 | "metadata": { 20 | "kernelspec": { 21 | "display_name": "Python 2", 22 | "language": "python", 23 | "name": "python2" 24 | }, 25 | "language_info": { 26 | "codemirror_mode": { 27 | "name": "ipython", 28 | "version": 2 29 | }, 30 | "file_extension": ".py", 31 | "mimetype": "text/x-python", 32 | "name": "python", 33 | "nbconvert_exporter": "python", 34 | "pygments_lexer": "ipython2", 35 | "version": "2.7.12" 36 | } 37 | }, 38 | "nbformat": 4, 39 | "nbformat_minor": 1 40 | } 41 | -------------------------------------------------------------------------------- /nbdime/tests/files/source-conflict--2.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "test 2\n", 12 | "hello 2\n", 13 | "more\n", 14 | "text 2\n", 15 | "world 2" 16 | ] 17 | } 18 | ], 19 | "metadata": { 20 | "kernelspec": { 21 | "display_name": "Python 2", 22 | "language": "python", 23 | "name": "python2" 24 | }, 25 | "language_info": { 26 | "codemirror_mode": { 27 | "name": "ipython", 28 | "version": 2 29 | }, 30 | "file_extension": ".py", 31 | "mimetype": "text/x-python", 32 | "name": "python", 33 | "nbconvert_exporter": "python", 34 | "pygments_lexer": "ipython2", 35 | "version": "2.7.12" 36 | } 37 | }, 38 | "nbformat": 4, 39 | "nbformat_minor": 1 40 | } 41 | -------------------------------------------------------------------------------- /nbdime/tests/files/source-conflict--3.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "test 3\n", 12 | "hello 3\n", 13 | "more\n", 14 | "text 3\n", 15 | "world 3" 16 | ] 17 | } 18 | ], 19 | "metadata": { 20 | "kernelspec": { 21 | "display_name": "Python 2", 22 | "language": "python", 23 | "name": "python2" 24 | }, 25 | "language_info": { 26 | "codemirror_mode": { 27 | "name": "ipython", 28 | "version": 2 29 | }, 30 | "file_extension": ".py", 31 | "mimetype": "text/x-python", 32 | "name": "python", 33 | "nbconvert_exporter": "python", 34 | "pygments_lexer": "ipython2", 35 | "version": "2.7.12" 36 | } 37 | }, 38 | "nbformat": 4, 39 | "nbformat_minor": 1 40 | } 41 | -------------------------------------------------------------------------------- /nbdime/tests/files/test-data-singlecell--1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "# This version has a bug\n", 12 | "def mult(a, b):\n", 13 | " return a / b" 14 | ] 15 | } 16 | ], 17 | "metadata": { 18 | "kernelspec": { 19 | "display_name": "Python 2", 20 | "language": "python", 21 | "name": "python2" 22 | }, 23 | "language_info": { 24 | "codemirror_mode": { 25 | "name": "ipython", 26 | "version": 2 27 | }, 28 | "file_extension": ".py", 29 | "mimetype": "text/x-python", 30 | "name": "python", 31 | "nbconvert_exporter": "python", 32 | "pygments_lexer": "ipython2", 33 | "version": "2.7.8" 34 | } 35 | }, 36 | "nbformat": 4, 37 | "nbformat_minor": 0 38 | } 39 | -------------------------------------------------------------------------------- /nbdime/tests/files/test-data-singlecell--2.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "def mult(a, b):\n", 12 | " \"This version is debugged.\"\n", 13 | " return a * b" 14 | ] 15 | } 16 | ], 17 | "metadata": { 18 | "kernelspec": { 19 | "display_name": "Python 2", 20 | "language": "python", 21 | "name": "python2" 22 | }, 23 | "language_info": { 24 | "codemirror_mode": { 25 | "name": "ipython", 26 | "version": 2 27 | }, 28 | "file_extension": ".py", 29 | "mimetype": "text/x-python", 30 | "name": "python", 31 | "nbconvert_exporter": "python", 32 | "pygments_lexer": "ipython2", 33 | "version": "2.7.8" 34 | } 35 | }, 36 | "nbformat": 4, 37 | "nbformat_minor": 0 38 | } 39 | -------------------------------------------------------------------------------- /nbdime/tests/files/unicode--1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "The Norwegian alphabet contains the letters æøå which are outside the ASCII charset.\n", 8 | "\n", 9 | "Many languages such as French contain accented letters such as è, é, ê.\n", 10 | "\n", 11 | "Some libraries print mathematical symbols such as\n", 12 | "\n", 13 | "Einstein is famous for the formula e=mc².\n", 14 | "\n", 15 | "There's 360° in a circle, and 1 µm is 10⁻⁶ m." 16 | ] 17 | } 18 | ], 19 | "metadata": { 20 | "kernelspec": { 21 | "display_name": "Python 2", 22 | "language": "python", 23 | "name": "python2" 24 | }, 25 | "language_info": { 26 | "codemirror_mode": { 27 | "name": "ipython", 28 | "version": 2 29 | }, 30 | "file_extension": ".py", 31 | "mimetype": "text/x-python", 32 | "name": "python", 33 | "nbconvert_exporter": "python", 34 | "pygments_lexer": "ipython2", 35 | "version": "2.7.12" 36 | } 37 | }, 38 | "nbformat": 4, 39 | "nbformat_minor": 1 40 | } 41 | -------------------------------------------------------------------------------- /nbdime/tests/files/unicode--2.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "The Norwegian alphabet contains the letters æøå which are outside the ASCII charset.\n", 8 | "\n", 9 | "Many languages such as French contain accented letters such as è, é, ê.\n", 10 | "\n", 11 | "Some libraries print mathematical symbols such as\n", 12 | "\n", 13 | "Greek letters such as δ, Σ, and θ are popular in mathematics.\n", 14 | "\n", 15 | "Einstein is famous for the formula e=mc²." 16 | ] 17 | } 18 | ], 19 | "metadata": { 20 | "kernelspec": { 21 | "display_name": "Python 2", 22 | "language": "python", 23 | "name": "python2" 24 | }, 25 | "language_info": { 26 | "codemirror_mode": { 27 | "name": "ipython", 28 | "version": 2 29 | }, 30 | "file_extension": ".py", 31 | "mimetype": "text/x-python", 32 | "name": "python", 33 | "nbconvert_exporter": "python", 34 | "pygments_lexer": "ipython2", 35 | "version": "2.7.12" 36 | } 37 | }, 38 | "nbformat": 4, 39 | "nbformat_minor": 1 40 | } 41 | -------------------------------------------------------------------------------- /nbdime/tests/filters/noop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # Copyright (c) Jupyter Development Team. 5 | # Distributed under the terms of the Modified BSD License. 6 | 7 | """A no-op filter. 8 | """ 9 | 10 | import argparse 11 | import sys 12 | 13 | import nbformat 14 | 15 | from nbdime.utils import setup_std_streams 16 | 17 | 18 | def _build_arg_parser(): 19 | parser = argparse.ArgumentParser() 20 | parser.add_argument( 21 | 'mode', default=None, 22 | help='The mode to run the filter in. Either "clean" or "smudge".') 23 | return parser 24 | 25 | 26 | def clean(notebook): 27 | pass 28 | 29 | 30 | def smudge(notebook): 31 | pass 32 | 33 | 34 | def do_filter(notebook, args): 35 | if args.mode == 'clean': 36 | return clean(notebook) 37 | elif args.mode == 'smudge': 38 | return smudge(notebook) 39 | 40 | raise argparse.ArgumentError( 41 | args.mode, 42 | 'mode can only be "clean" or "smudge", got %r.' % args.mode 43 | ) 44 | 45 | 46 | def main(args=None): 47 | if args is None: 48 | args = sys.argv[1:] 49 | setup_std_streams() 50 | nb = nbformat.read(sys.stdin, as_version=4) 51 | opts = _build_arg_parser().parse_args(args) 52 | do_filter(nb, opts) 53 | nbformat.write(nb, sys.stdout) 54 | 55 | 56 | if __name__ == "__main__": 57 | sys.exit(main()) 58 | -------------------------------------------------------------------------------- /nbdime/tests/filters/strip_outputs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # Copyright (c) Jupyter Development Team. 5 | # Distributed under the terms of the Modified BSD License. 6 | 7 | """A test filter that removes outputs on clean, no-op on smudge. 8 | """ 9 | 10 | import argparse 11 | import sys 12 | 13 | import nbformat 14 | 15 | from nbdime.utils import setup_std_streams 16 | 17 | 18 | def _build_arg_parser(): 19 | parser = argparse.ArgumentParser() 20 | parser.add_argument( 21 | 'mode', default=None, 22 | help='The mode to run the filter in. Either "clean" or "smudge".') 23 | return parser 24 | 25 | 26 | def clean(notebook): 27 | for cell in notebook['cells']: 28 | if 'outputs' in cell: 29 | cell['outputs'] = [] 30 | 31 | 32 | def smudge(notebook): 33 | pass 34 | 35 | 36 | def do_filter(notebook, args): 37 | if args.mode == 'clean': 38 | return clean(notebook) 39 | elif args.mode == 'smudge': 40 | return smudge(notebook) 41 | 42 | raise argparse.ArgumentError( 43 | args.mode, 44 | 'mode can only be "clean" or "smudge", got %r.' % args.mode 45 | ) 46 | 47 | 48 | def main(args=None): 49 | if args is None: 50 | args = sys.argv[1:] 51 | setup_std_streams() 52 | nb = nbformat.read(sys.stdin, as_version=4) 53 | opts = _build_arg_parser().parse_args(args) 54 | do_filter(nb, opts) 55 | nbformat.write(nb, sys.stdout) 56 | 57 | 58 | if __name__ == "__main__": 59 | sys.exit(main()) 60 | -------------------------------------------------------------------------------- /nbdime/tests/test_diff_format.py: -------------------------------------------------------------------------------- 1 | from jsonschema import Draft4Validator as Validator 2 | from nbdime import diff, diff_notebooks 3 | 4 | 5 | def test_check_schema(json_schema_diff): 6 | Validator.check_schema(json_schema_diff) 7 | 8 | 9 | def test_validate_obj_diff(diff_validator): 10 | a = {"foo": [1, 2, 3], "bar": {"ting": 7, "tang": 123}} 11 | b = {"foo": [1, 3, 4], "bar": {"tang": 126, "hello": "world"}} 12 | d = diff(a, b) 13 | 14 | diff_validator.validate(d) 15 | 16 | 17 | def test_validate_array_diff(diff_validator): 18 | a = [2, 3, 4] 19 | b = [1, 2, 4, 6] 20 | d = diff(a, b) 21 | 22 | diff_validator.validate(d) 23 | 24 | 25 | def test_validate_matching_notebook_diff(matching_nb_pairs, diff_validator): 26 | a, b = matching_nb_pairs 27 | d = diff_notebooks(a, b) 28 | 29 | diff_validator.validate(d) 30 | -------------------------------------------------------------------------------- /nbdime/tests/test_diff_json_conversion.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | import json 4 | from nbdime import diff 5 | from nbdime.diff_utils import to_clean_dicts, to_json_patch 6 | 7 | def test_diff_to_json(): 8 | a = { "foo": [1,2,3], "bar": {"ting": 7, "tang": 123 } } 9 | b = { "foo": [1,3,4], "bar": {"tang": 126, "hello": "world" } } 10 | d1 = diff(a, b) 11 | 12 | d2 = to_clean_dicts(d1) 13 | assert len(d2) == len(d1) 14 | assert all(len(e2) == len(e1) for e1, e2 in zip(d1, d2)) 15 | 16 | j = json.dumps(d1) 17 | d3 = json.loads(j) 18 | assert len(d3) == len(d1) 19 | assert all(len(e3) == len(e1) for e1, e3 in zip(d1, d3)) 20 | assert d2 == d3 21 | 22 | 23 | def test_diff_to_json_patch(): 24 | a = [2, 3, 4] 25 | b = [1, 2, 4, 6] 26 | d = diff(a, b) 27 | 28 | assert to_json_patch(d) == [ 29 | {'op': 'add', 'path': '/0', 'value': 1}, 30 | {'op': 'remove', 'path': '/2'}, 31 | {'op': 'add', 'path': '/3', 'value': 6} 32 | ] 33 | 34 | try: 35 | import jsonpatch 36 | except ImportError: 37 | jsonpatch = None 38 | pytest.xfail("Not comparing to jsonpatch") 39 | 40 | if jsonpatch: 41 | assert to_json_patch(d) == jsonpatch.make_patch(a, b).patch 42 | -------------------------------------------------------------------------------- /nbdime/tests/test_diff_performance.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | 4 | import random 5 | import string 6 | 7 | from .utils import outputs_to_notebook 8 | 9 | from nbdime.diffing import diff_notebooks 10 | 11 | 12 | def random_string(N): 13 | return ''.join(random.choice( 14 | string.ascii_uppercase + string.digits) for _ in range(N)) 15 | 16 | 17 | @pytest.mark.timeout(timeout=20) 18 | def test_text_mimedata_performance(): 19 | # Create some random text (seeded) for a few 20 | # outputs on each side 21 | random.seed(0) 22 | base = outputs_to_notebook([ 23 | [random_string(50000)], 24 | [random_string(80000)], 25 | [random_string(30000)], 26 | ]) 27 | remote = outputs_to_notebook([ 28 | [random_string(50000)], 29 | [random_string(30000)], 30 | [random_string(30000)], 31 | [random_string(80000)], 32 | ]) 33 | 34 | # Since the contents is random, ignore the actual diff 35 | # Only interested in performance not blowing up 36 | diff_notebooks(base, remote) 37 | 38 | -------------------------------------------------------------------------------- /nbdime/tests/test_diff_sequence.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | 7 | 8 | import pytest 9 | 10 | from nbdime import patch 11 | from nbdime.diff_format import is_valid_diff 12 | 13 | import nbdime.diffing.sequences 14 | from nbdime.diffing.sequences import diff_sequence 15 | 16 | 17 | def check_diff_sequence_and_patch(a, b): 18 | d = diff_sequence(a, b) 19 | assert is_valid_diff(d) 20 | assert patch(a, d) == b 21 | d = diff_sequence(b, a) 22 | assert is_valid_diff(d) 23 | assert patch(b, d) == a 24 | 25 | #algorithms = ["difflib", "bruteforce", "myers"] 26 | algorithms = ["difflib", "bruteforce"] 27 | 28 | 29 | @pytest.fixture(params=algorithms) 30 | def algorithm(request): 31 | alg = nbdime.diffing.sequences.diff_sequence_algorithm 32 | nbdime.diffing.sequences.diff_sequence_algorithm = request.param 33 | yield request.param 34 | nbdime.diffing.sequences.diff_sequence_algorithm = alg 35 | 36 | 37 | def test_diff_sequence(algorithm): 38 | "FIXME: Add wide range of test cases here." 39 | 40 | a = """\ 41 | def f(a, b): 42 | c = a * b 43 | return c 44 | 45 | def g(x): 46 | y = x**2 47 | return y 48 | """.splitlines() 49 | 50 | b = [] 51 | check_diff_sequence_and_patch(a, b) 52 | 53 | for i in range(len(a)+1): 54 | for j in range(len(a)+1): 55 | for k in range(len(a)+1): 56 | for l in range(len(a)+1): 57 | b = a[i:j] + a[k:l] 58 | check_diff_sequence_and_patch(a, b) 59 | -------------------------------------------------------------------------------- /nbdime/tests/test_diff_sequence_difflib.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | 7 | 8 | from nbdime import patch 9 | from nbdime.diffing.seq_difflib import diff_sequence_difflib 10 | from nbdime.diff_format import is_valid_diff 11 | 12 | 13 | def check_diff_sequence_and_patch(a, b): 14 | d = diff_sequence_difflib(a, b) 15 | assert is_valid_diff(d) 16 | assert patch(a, d) == b 17 | d = diff_sequence_difflib(b, a) 18 | assert is_valid_diff(d) 19 | assert patch(b, d) == a 20 | 21 | 22 | def test_diff_sequence_difflib(): 23 | a = """\ 24 | def f(a, b): 25 | c = a * b 26 | return c 27 | 28 | def g(x): 29 | y = x**2 30 | return y 31 | """.splitlines() 32 | 33 | b = [] 34 | check_diff_sequence_and_patch(a, b) 35 | 36 | for i in range(len(a)+1): 37 | for j in range(len(a)+1): 38 | for k in range(len(a)+1): 39 | for l in range(len(a)+1): 40 | b = a[i:j] + a[k:l] 41 | check_diff_sequence_and_patch(a, b) 42 | -------------------------------------------------------------------------------- /nbdime/tests/test_js_artifacts_installed.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | 4 | 5 | def test_nbdime_js_artifacts_present(): 6 | from nbdime.webapp import __file__ as webapp_init 7 | static_folder = os.path.join(os.path.dirname(webapp_init), 'static') 8 | targets = [os.path.join(static_folder, 'nbdime.js')] 9 | missing = [t for t in targets if not os.path.exists(t)] 10 | if missing: 11 | raise ValueError(('missing files: %s' % missing)) 12 | -------------------------------------------------------------------------------- /nbdime/tests/test_merge_format.py: -------------------------------------------------------------------------------- 1 | 2 | from jsonschema import Draft4Validator as Validator 3 | from nbdime import decide_merge 4 | from nbdime.merging.notebooks import decide_notebook_merge 5 | 6 | 7 | def test_check_schema(json_schema_merge): 8 | Validator.check_schema(json_schema_merge) 9 | 10 | 11 | def test_validate_obj_merge(merge_validator): 12 | b = {"p": {"b": 1}} 13 | l = {"p": {"b": 1}, "n": {"s": 7, "l": 2}} 14 | r = {"p": {"b": 1}, "n": {"s": 7, "r": 3}} 15 | decisions = decide_merge(b, l, r) 16 | 17 | merge_validator.validate(decisions) 18 | 19 | 20 | def test_validate_array_merge(merge_validator): 21 | b = [1, 9] 22 | l = [1, 2, 7, 9] 23 | r = [1, 3, 7, 9] 24 | decisions = decide_merge(b, l, r) 25 | 26 | merge_validator.validate(decisions) 27 | 28 | 29 | def test_validate_matching_notebook_merge(matching_nb_triplets, merge_validator, reset_log): 30 | base, local, remote = matching_nb_triplets 31 | decisions = decide_notebook_merge(base, local, remote) 32 | 33 | merge_validator.validate(decisions) 34 | -------------------------------------------------------------------------------- /nbdime/tests/test_package.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | 7 | def test_pkg_import_no_webapp(): 8 | # Test that importing nbdime does not import webapp 9 | # as this is an expensive import 10 | import nbdime 11 | import sys 12 | assert 'ndime.webapp' not in sys.modules 13 | -------------------------------------------------------------------------------- /nbdime/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import shutil 4 | import tempfile 5 | 6 | from nbdime.utils import ( 7 | strings_to_lists, revert_strings_to_lists, is_in_repo, 8 | locate_gitattributes 9 | ) 10 | 11 | 12 | def test_string_to_lists(): 13 | obj = {"c": [{"s": "ting\ntang", "o": [{"ot": "stream"}]}]} 14 | obj2 = strings_to_lists(obj) 15 | obj3 = revert_strings_to_lists(obj2) 16 | assert obj3 == obj 17 | 18 | 19 | def test_is_repo(): 20 | try: 21 | tmpdir = tempfile.mkdtemp(prefix='nbdime-test') 22 | subdir = tempfile.mkdtemp(dir=tmpdir) 23 | subfile = tempfile.NamedTemporaryFile(dir=tmpdir) 24 | subsubfile = tempfile.NamedTemporaryFile(dir=subdir) 25 | with subfile, subsubfile: 26 | assert is_in_repo(tmpdir) is False 27 | assert is_in_repo(subdir) is False 28 | assert is_in_repo(subfile.name) is False 29 | assert is_in_repo(subsubfile.name) is False 30 | os.makedirs(os.path.join(subdir, '.git')) 31 | assert is_in_repo(tmpdir) is False 32 | assert is_in_repo(subdir) is True 33 | assert is_in_repo(subfile.name) is False 34 | assert is_in_repo(subsubfile.name) is True 35 | os.makedirs(os.path.join(tmpdir, '.git')) 36 | assert is_in_repo(tmpdir) is True 37 | assert is_in_repo(subdir) is True 38 | assert is_in_repo(subfile.name) is True 39 | assert is_in_repo(subsubfile.name) is True 40 | 41 | finally: 42 | shutil.rmtree(tmpdir) 43 | 44 | 45 | def test_locate_gitattributes_local(git_repo): 46 | gitattr = locate_gitattributes(scope=None) 47 | assert gitattr is not None 48 | 49 | 50 | def test_locate_gitattributes_global(needs_git): 51 | gitattr = locate_gitattributes(scope='global') 52 | assert gitattr is not None 53 | 54 | 55 | def test_locate_gitattributes_system(needs_git): 56 | gitattr = locate_gitattributes(scope='system') 57 | assert gitattr is not None 58 | -------------------------------------------------------------------------------- /nbdime/vcs/__init__.py: -------------------------------------------------------------------------------- 1 | """Contains entry points for VCS integration.""" 2 | -------------------------------------------------------------------------------- /nbdime/vcs/git/__init__.py: -------------------------------------------------------------------------------- 1 | """Contains entry points for git integration.""" 2 | -------------------------------------------------------------------------------- /nbdime/vcs/hg/__init__.py: -------------------------------------------------------------------------------- 1 | """Contains entry points for mercurial integration.""" 2 | -------------------------------------------------------------------------------- /nbdime/vcs/hg/diff.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A mercurial external differ for notebooks. 3 | 4 | Uses nbdime to create diffs for notebooks instead of plain text diffs of JSON. 5 | See the documentation for how to correctly configure mercurial to use this. 6 | 7 | Use with: 8 | 9 | hg extdiff -p hg-nbdiff [ []] 10 | """ 11 | 12 | 13 | 14 | import os 15 | import sys 16 | 17 | from nbdime import nbdiffapp 18 | from nbdime.args import ( 19 | add_diff_args, add_filename_args, add_diff_cli_args, add_prettyprint_args, 20 | ConfigBackedParser, 21 | ) 22 | from nbdime.diffing.directorydiff import diff_directories 23 | 24 | 25 | def main(args=None): 26 | if args is None: 27 | args = sys.argv[1:] 28 | import argparse 29 | parser = ConfigBackedParser('hg-nbdiff', description=__doc__, 30 | formatter_class=argparse.RawDescriptionHelpFormatter, 31 | ) 32 | 33 | add_diff_args(parser) 34 | add_diff_cli_args(parser) 35 | add_prettyprint_args(parser) 36 | add_filename_args(parser, ('base', 'remote')) 37 | 38 | opts = parser.parse_args(args) 39 | 40 | # TODO: Filter base/remote: If directories, find all modified notebooks 41 | # If files that are not notebooks, ensure a decent error is printed. 42 | if not os.path.isfile(opts.base) or not os.path.isfile(opts.remote): 43 | base, remote = opts.base, opts.remote 44 | for a, b in diff_directories(base, remote): 45 | opts.base, opts.remote = a, b 46 | ret = nbdiffapp.main_diff(opts) 47 | if ret != 0: 48 | return ret 49 | return ret 50 | else: 51 | return nbdiffapp.main_diff(opts) 52 | 53 | 54 | 55 | if __name__ == "__main__": 56 | sys.exit(main()) 57 | -------------------------------------------------------------------------------- /nbdime/vcs/hg/diffweb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A mercurial external differ for notebooks. 3 | 4 | Uses nbdime to create diffs for notebooks instead of plain text diffs of JSON. 5 | See the documentation for how to correctly configure mercurial to use this. 6 | 7 | Use with: 8 | 9 | hg extdiff -p hg-nbdiff [ []] 10 | """ 11 | 12 | 13 | 14 | import os 15 | import sys 16 | 17 | from nbdime.args import ConfigBackedParser 18 | from nbdime.webapp import nbdifftool 19 | from nbdime.diffing.directorydiff import diff_directories 20 | 21 | 22 | def main(args=None): 23 | if args is None: 24 | args = sys.argv[1:] 25 | import argparse 26 | parser = ConfigBackedParser('hg-nbdiffweb', description=__doc__, 27 | formatter_class=argparse.RawDescriptionHelpFormatter, 28 | ) 29 | 30 | nbdifftool.build_arg_parser(parser) 31 | opts = parser.parse_args(args) 32 | 33 | # TODO: If a/b are files that are not notebooks, ensure a decent error is printed. 34 | if not os.path.isfile(opts.local) or not os.path.isfile(opts.remote): 35 | local, remote = opts.local, opts.remote 36 | for a, b in diff_directories(local, remote): 37 | opts.local, opts.remote = a, b 38 | ret = nbdifftool.main_parsed(opts) 39 | if ret != 0: 40 | return ret 41 | return ret 42 | else: 43 | return nbdifftool.main_parsed(opts) 44 | 45 | 46 | 47 | if __name__ == "__main__": 48 | sys.exit(main()) 49 | -------------------------------------------------------------------------------- /nbdime/vcs/hg/merge.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A mercurial CLI merge tool for notebooks. 3 | 4 | Uses nbdime to merge notebooks instead of plain text merges of JSON. 5 | See the documentation for how to correctly configure mercurial to use this. 6 | 7 | Use with: 8 | 9 | hg merge [ []] 10 | """ 11 | 12 | 13 | 14 | import argparse 15 | import sys 16 | 17 | from nbdime import nbmergeapp 18 | from nbdime.args import ( 19 | add_generic_args, add_diff_args, add_merge_args, add_filename_args, 20 | ConfigBackedParser, 21 | ) 22 | 23 | 24 | 25 | def main(args=None): 26 | if args is None: 27 | args = sys.argv[1:] 28 | parser = ConfigBackedParser('hg-nbmerge', description=__doc__, 29 | formatter_class=argparse.RawDescriptionHelpFormatter, 30 | ) 31 | 32 | add_generic_args(parser) 33 | add_diff_args(parser) 34 | add_merge_args(parser) 35 | 36 | # Argument list, we are given base, local, remote 37 | add_filename_args(parser, ["base", "local", "remote", "merged"]) 38 | 39 | opts = parser.parse_args(args) 40 | # mergeapp expects an additional decisions arg: 41 | opts.decisions = False 42 | opts.out = opts.merged 43 | del opts.merged 44 | return nbmergeapp.main_merge(opts) 45 | 46 | 47 | if __name__ == "__main__": 48 | sys.exit(main()) 49 | -------------------------------------------------------------------------------- /nbdime/vcs/hg/mergeweb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """A mercurial web-based merge tool for notebooks. 3 | 4 | Uses nbdime to merge notebooks instead of plain text merges of JSON. 5 | See the documentation for how to correctly configure mercurial to use this. 6 | 7 | Use with: 8 | 9 | hg merge [ []] 10 | """ 11 | 12 | 13 | 14 | import argparse 15 | import sys 16 | 17 | from nbdime.args import ConfigBackedParser 18 | from nbdime.webapp import nbmergetool 19 | 20 | 21 | def main(args=None): 22 | if args is None: 23 | args = sys.argv[1:] 24 | parser = ConfigBackedParser('hg-nbmergeweb', description=__doc__, 25 | formatter_class=argparse.RawDescriptionHelpFormatter, 26 | ) 27 | 28 | nbmergetool.build_arg_parser(parser) 29 | 30 | opts = parser.parse_args(args) 31 | return nbmergetool.main_parsed(opts) 32 | 33 | 34 | if __name__ == "__main__": 35 | sys.exit(main()) 36 | -------------------------------------------------------------------------------- /nbdime/webapp/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | -------------------------------------------------------------------------------- /nbdime/webapp/nbdifftool.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | 4 | 5 | 6 | 7 | import sys 8 | 9 | from ..args import ( 10 | ConfigBackedParser, 11 | add_generic_args, add_diff_args, add_web_args, add_filename_args, 12 | args_for_server, args_for_browse, process_diff_flags) 13 | from .nbdimeserver import main_server as run_server 14 | from .webutil import browse 15 | 16 | 17 | # TODO: Tool server is passed a (mandatory?) single-use access token, which is 18 | # used to authenticate the browser session. 19 | 20 | def build_arg_parser(parser=None): 21 | """ 22 | Creates an argument parser for the diff tool, that also lets the 23 | user specify a port and displays a help message. 24 | """ 25 | description = 'difftool for nbdime.' 26 | if parser is None: 27 | parser = ConfigBackedParser( 28 | description=description, 29 | add_help=True 30 | ) 31 | add_generic_args(parser) 32 | add_web_args(parser, 0) 33 | add_diff_args(parser) 34 | add_filename_args(parser, ["local", "remote"]) 35 | return parser 36 | 37 | 38 | def main_parsed(opts): 39 | """Main function called after parsing CLI options 40 | 41 | Called by both main here and gitdifftool 42 | """ 43 | process_diff_flags(opts) 44 | base = opts.local 45 | remote = opts.remote 46 | return run_server( 47 | difftool_args=dict(base=base, remote=remote), 48 | on_port=lambda port: browse( 49 | port=port, 50 | rel_url='difftool', 51 | **args_for_browse(opts)), 52 | **args_for_server(opts)) 53 | 54 | 55 | def main(args=None): 56 | if args is None: 57 | args = sys.argv[1:] 58 | opts = build_arg_parser().parse_args(args) 59 | return main_parsed(opts) 60 | 61 | 62 | if __name__ == "__main__": 63 | sys.exit(main()) 64 | -------------------------------------------------------------------------------- /nbdime/webapp/nbmergetool.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | 4 | 5 | 6 | 7 | import sys 8 | 9 | from ..args import ( 10 | ConfigBackedParser, 11 | add_generic_args, add_filename_args, add_diff_args, add_merge_args, 12 | add_web_args, args_for_server, args_for_browse, process_diff_flags) 13 | from .nbdimeserver import main_server as run_server 14 | from .webutil import browse 15 | 16 | 17 | # TODO: Tool server is passed a (mandatory?) single-use access token, which is 18 | # used to authenticate the browser session. 19 | 20 | def build_arg_parser(parser=None): 21 | """ 22 | Creates an argument parser for the merge tool, that also lets the 23 | user specify a port and displays a help message. 24 | """ 25 | description = 'mergetool for Nbdime.' 26 | if parser is None: 27 | parser = ConfigBackedParser( 28 | description=description, 29 | add_help=True 30 | ) 31 | add_generic_args(parser) 32 | add_diff_args(parser) 33 | add_merge_args(parser) 34 | add_web_args(parser, 0) 35 | add_filename_args(parser, ["base", "local", "remote", "merged"]) 36 | return parser 37 | 38 | 39 | def main_parsed(opts): 40 | """Main function called after parsing CLI options 41 | 42 | Called by both main here and gitmergetool 43 | """ 44 | process_diff_flags(opts) 45 | base = opts.base 46 | local = opts.local 47 | remote = opts.remote 48 | merged = opts.merged 49 | return run_server( 50 | mergetool_args=dict(base=base, local=local, remote=remote), 51 | outputfilename=merged, 52 | on_port=lambda port: browse( 53 | port=port, 54 | rel_url='mergetool', 55 | **args_for_browse(opts)), 56 | **args_for_server(opts)) 57 | 58 | 59 | def main(args=None): 60 | if args is None: 61 | args = sys.argv[1:] 62 | opts = build_arg_parser().parse_args(args) 63 | return main_parsed(opts) 64 | 65 | 66 | if __name__ == "__main__": 67 | sys.exit(main()) 68 | -------------------------------------------------------------------------------- /nbdime/webapp/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/nbdime/webapp/static/favicon.ico -------------------------------------------------------------------------------- /nbdime/webapp/templates/compare.html: -------------------------------------------------------------------------------- 1 | {% extends "nbdimepage.html" %} 2 | 3 | {% block nbdimeheader %} 4 | 5 |
6 |

Notebook Diff/Merge

7 | Enter two or more notebook filenames/URLs in the form below to get started. 8 | 9 |
10 |
11 | Please input filenames of notebooks to compare: 12 | 14 | 16 | 18 | 19 | 20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 |
29 |
30 | Local 31 | Base 32 | Remote 33 |
34 |
35 | 36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /nbdime/webapp/templates/diff.html: -------------------------------------------------------------------------------- 1 | {% extends "nbdimepage.html" %} 2 | 3 | {% block nbdimeheader %} 4 | 5 |
6 |

Notebook Diff

7 | Enter notebook filenames or URLs in the form below to get started. 8 | 9 |
10 |
11 | Please input filenames/URLs of notebooks to diff: 12 | 14 | 16 | 17 |
18 |
19 |
20 | 21 | 22 | 23 | 24 |
25 |
26 | Base 27 | Remote 28 |
29 |
30 | 31 | {% endblock %} 32 | -------------------------------------------------------------------------------- /nbdime/webapp/templates/difftool.html: -------------------------------------------------------------------------------- 1 | {% extends "nbdimepage.html" %} 2 | 3 | {% block nbdimeheader %} 4 | 5 |
6 |

Notebook Diff

7 |
8 | 9 | 10 | 11 | 12 |
13 |
14 | Base 15 | Remote 16 |
17 |
18 | 19 | {% endblock %} -------------------------------------------------------------------------------- /nbdime/webapp/templates/merge.html: -------------------------------------------------------------------------------- 1 | {% extends "nbdimepage.html" %} 2 | 3 | {% block nbdimeheader %} 4 | 5 |
6 |

Notebook Merge

7 | Enter notebook filenames or URLs in the form below to get started. 8 | 9 |
10 |
11 | Please input filenames/URLs of notebooks to merge: 12 | 14 | 16 | 18 | 19 | 20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 |
28 |
29 | Local 30 | Base 31 | Remote 32 |
33 |
34 | 35 | {% endblock %} 36 | -------------------------------------------------------------------------------- /nbdime/webapp/templates/mergetool.html: -------------------------------------------------------------------------------- 1 | {% extends "nbdimepage.html" %} 2 | 3 | {% block nbdimeheader %} 4 | 5 |
6 |

Notebook Merge

7 |
8 | 9 | 10 | 11 | 12 |
13 |
14 | Local 15 | Base 16 | Remote 17 |
18 |
19 | 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /nbdime/webapp/templates/nbdimepage.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | nbdime - diff and merge your Jupyter notebooks 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% block nbdimeheader %} 18 | {% endblock %} 19 | 20 |
21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /nbdime/webapp/testnotebooks/cellchange.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "from math import sqrt\n", 12 | "\n", 13 | "def f(x, y):\n", 14 | " r2 = x**2 + y**2\n", 15 | " return sqrt(r2)" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": 2, 21 | "metadata": { 22 | "collapsed": false 23 | }, 24 | "outputs": [ 25 | { 26 | "name": "stdout", 27 | "output_type": "stream", 28 | "text": [ 29 | "5.0\n" 30 | ] 31 | } 32 | ], 33 | "source": [ 34 | "l = f(3, 4)\n", 35 | "print(l)" 36 | ] 37 | }, 38 | { 39 | "cell_type": "markdown", 40 | "metadata": {}, 41 | "source": [ 42 | "## from Inserted Markdown cell!" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 3, 48 | "metadata": { 49 | "collapsed": false 50 | }, 51 | "outputs": [ 52 | { 53 | "data": { 54 | "text/plain": [ 55 | "6.324555320336759" 56 | ] 57 | }, 58 | "execution_count": 3, 59 | "metadata": {}, 60 | "output_type": "execute_result" 61 | } 62 | ], 63 | "source": [ 64 | "f(6, -2)" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": null, 70 | "metadata": { 71 | "collapsed": true 72 | }, 73 | "outputs": [], 74 | "source": [] 75 | } 76 | ], 77 | "metadata": { 78 | "kernelspec": { 79 | "display_name": "Python 3", 80 | "language": "python", 81 | "name": "python3" 82 | }, 83 | "language_info": { 84 | "codemirror_mode": { 85 | "name": "ipython", 86 | "version": 3 87 | }, 88 | "file_extension": ".py", 89 | "mimetype": "text/x-python", 90 | "name": "python", 91 | "nbconvert_exporter": "python", 92 | "pygments_lexer": "ipython3", 93 | "version": "3.5.2" 94 | } 95 | }, 96 | "nbformat": 4, 97 | "nbformat_minor": 0 98 | } 99 | -------------------------------------------------------------------------------- /nbdime/webapp/testnotebooks/scrollA.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "from math import sqrt\n", 12 | "\n", 13 | "def f(x, y):\n", 14 | " r2 = x**2+y**2\n", 15 | " return sqrt(r2)\n", 16 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaa" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 2, 22 | "metadata": { 23 | "collapsed": false 24 | }, 25 | "outputs": [ 26 | { 27 | "name": "stdout", 28 | "output_type": "stream", 29 | "text": [ 30 | "5.0\n" 31 | ] 32 | } 33 | ], 34 | "source": [ 35 | "l = f(3, 4)\n", 36 | "print(l)" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 3, 42 | "metadata": { 43 | "collapsed": false 44 | }, 45 | "outputs": [ 46 | { 47 | "data": { 48 | "text/plain": [ 49 | "52.0" 50 | ] 51 | }, 52 | "execution_count": 3, 53 | "metadata": {}, 54 | "output_type": "execute_result" 55 | } 56 | ], 57 | "source": [ 58 | "def g(z):\n", 59 | " return z**2 + 3.0\n", 60 | "g(7)" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": null, 66 | "metadata": { 67 | "collapsed": true 68 | }, 69 | "outputs": [], 70 | "source": [] 71 | } 72 | ], 73 | "metadata": { 74 | "kernelspec": { 75 | "display_name": "Python 3", 76 | "language": "python", 77 | "name": "python3" 78 | }, 79 | "language_info": { 80 | "codemirror_mode": { 81 | "name": "ipython", 82 | "version": 3 83 | }, 84 | "file_extension": ".py", 85 | "mimetype": "text/x-python", 86 | "name": "python", 87 | "nbconvert_exporter": "python", 88 | "pygments_lexer": "ipython3", 89 | "version": "3.5.2" 90 | } 91 | }, 92 | "nbformat": 4, 93 | "nbformat_minor": 0 94 | } 95 | -------------------------------------------------------------------------------- /nbdime/webapp/testnotebooks/scrollB.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "from math import sqrt\n", 12 | "\n", 13 | "def f(x, y):\n", 14 | " r2 = x**2+y**2\n", 15 | " return sqrt(r2)\n", 16 | "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaaaaaaaaa aaaaaaa" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": 2, 22 | "metadata": { 23 | "collapsed": false 24 | }, 25 | "outputs": [ 26 | { 27 | "name": "stdout", 28 | "output_type": "stream", 29 | "text": [ 30 | "5.0\n" 31 | ] 32 | } 33 | ], 34 | "source": [ 35 | "l = f(3, 4)\n", 36 | "print(l)" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 3, 42 | "metadata": { 43 | "collapsed": false 44 | }, 45 | "outputs": [ 46 | { 47 | "data": { 48 | "text/plain": [ 49 | "52.0" 50 | ] 51 | }, 52 | "execution_count": 3, 53 | "metadata": {}, 54 | "output_type": "execute_result" 55 | } 56 | ], 57 | "source": [ 58 | "def g(z):\n", 59 | " return z**2 + 3.0\n", 60 | "g(7)" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": null, 66 | "metadata": { 67 | "collapsed": true 68 | }, 69 | "outputs": [], 70 | "source": [] 71 | } 72 | ], 73 | "metadata": { 74 | "kernelspec": { 75 | "display_name": "Python 2", 76 | "language": "python", 77 | "name": "python2" 78 | }, 79 | "language_info": { 80 | "codemirror_mode": { 81 | "name": "ipython", 82 | "version": 2 83 | }, 84 | "file_extension": ".py", 85 | "mimetype": "text/x-python", 86 | "name": "python", 87 | "nbconvert_exporter": "python", 88 | "pygments_lexer": "ipython2", 89 | "version": "2.7.11" 90 | } 91 | }, 92 | "nbformat": 4, 93 | "nbformat_minor": 0 94 | } 95 | -------------------------------------------------------------------------------- /nbdime/webapp/webutil.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | 4 | 5 | 6 | import logging 7 | import threading 8 | import webbrowser 9 | from tornado.httputil import url_concat 10 | 11 | _logger = logging.getLogger(__name__) 12 | 13 | 14 | def browse(port, browsername=None, base_url='/', rel_url='', ip='127.0.0.1', **url_args): 15 | try: 16 | browser = webbrowser.get(browsername) 17 | except webbrowser.Error as e: 18 | _logger.warning('No web browser found: %s.', e) 19 | browser = None 20 | 21 | if ip == '0.0.0.0': 22 | ip = '127.0.0.1' 23 | elif ip in ('::', '0:0:0:0:0:0:0:0'): 24 | ip = '::1' 25 | 26 | base_url = base_url.rstrip('/') 27 | 28 | url = url_concat("http://%s:%s%s/%s" % (ip, port, base_url, rel_url), url_args) 29 | _logger.info("URL: %s", url) 30 | if browser: 31 | def launch_browser(): 32 | browser.open(url, new=2) 33 | threading.Thread(target=launch_browser).start() 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nbdime-top-repo", 3 | "version": "4.0.0-rc.0", 4 | "private": true, 5 | "workspaces": [ 6 | "packages/*" 7 | ], 8 | "scripts": { 9 | "build": "lerna run build", 10 | "build:tsc": "tsc --build", 11 | "build:dev": "lerna run build:dev", 12 | "clean": "lerna run clean", 13 | "prettier": "prettier --list-different --write \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", 14 | "prettier:check": "prettier --check \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", 15 | "publish": "npm run clean && npm run build && lerna publish --no-private -m \"Publish npm packages\"", 16 | "test": "lerna run test", 17 | "update:all": "update-dependency --lerna --minimal --regex .*", 18 | "update:lab": "update-dependency --lerna --minimal --regex ^@jupyterlab/", 19 | "update:lab:next": "update-dependency --lerna --minimal --regex ^@jupyterlab/ ^next", 20 | "updated": "lerna updated", 21 | "watch:webapp": "run-p watch:lib watch:app", 22 | "watch:app": "lerna exec --stream --scope \"nbdime-webapp\" npm run watch", 23 | "watch:lib": "lerna exec --stream --parallel --scope \"nbdime\" --scope \"nbdime-jupyterlab\" npm run watch" 24 | }, 25 | "devDependencies": { 26 | "@jupyterlab/buildutils": "^4.0.0", 27 | "lerna": "^7.3.1", 28 | "npm-run-all": "^4.1.5", 29 | "npm-which": "^3.0.1", 30 | "prettier": "^3.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/labextension/schema/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "jupyter.lab.setting-icon-class": "jp-Icon-16 nbdime-icon fa fa-clock-o", 3 | "jupyter.lab.setting-icon-label": "Nbdime", 4 | "title": "Nbdime", 5 | "description": "Settings for the nbdime extension.", 6 | "properties": { 7 | "hideUnchanged": { 8 | "title": "Hide Unchanged Cells", 9 | "description": "Whether unchanged cells should be hidden by default.", 10 | "type": "boolean", 11 | "default": true 12 | } 13 | }, 14 | "additionalProperties": false, 15 | "type": "object" 16 | } 17 | -------------------------------------------------------------------------------- /packages/labextension/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import NBDiffProvider from './plugin'; 5 | 6 | export default NBDiffProvider; 7 | -------------------------------------------------------------------------------- /packages/labextension/src/utils.ts: -------------------------------------------------------------------------------- 1 | export function urlRStrip(target: string): string { 2 | if (target.slice(-1) === '/') { 3 | return target.slice(0, -1); 4 | } 5 | return target; 6 | } 7 | -------------------------------------------------------------------------------- /packages/labextension/style/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Jupyter Development Team. 3 | * Distributed under the terms of the Modified BSD License. 4 | */ 5 | import './index.css'; 6 | -------------------------------------------------------------------------------- /packages/labextension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig_base", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "lib" 6 | }, 7 | "references": [{ "path": "../nbdime" }] 8 | } 9 | -------------------------------------------------------------------------------- /packages/nbdime/.npmignore: -------------------------------------------------------------------------------- 1 | scripts/ 2 | src/ 3 | test/ 4 | coverage/ 5 | -------------------------------------------------------------------------------- /packages/nbdime/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | sourceMap: 'inline', 3 | presets: [ 4 | [ 5 | '@babel/preset-env', 6 | { 7 | targets: { 8 | node: 'current', 9 | }, 10 | }, 11 | ], 12 | ], 13 | }; 14 | -------------------------------------------------------------------------------- /packages/nbdime/jest.config.js: -------------------------------------------------------------------------------- 1 | const esModules = [ 2 | '@jupyterlab', 3 | '@codemirror', 4 | '@jupyter/ydoc', 5 | 'lib0', 6 | 'nanoid', 7 | 'vscode-ws-jsonrpc', 8 | 'y-protocols', 9 | 'y-websocket', 10 | 'yjs', 11 | ].join('|'); 12 | 13 | module.exports = { 14 | testEnvironment: 'jsdom', 15 | automock: false, 16 | moduleNameMapper: { 17 | '\\.(css|less|sass|scss)$': 'identity-obj-proxy', 18 | '\\.(svg)$': '/test/jest-file-mock.js', 19 | }, 20 | transform: { 21 | // Extracted from https://github.com/kulshekhar/ts-jest/blob/v29.0.3/presets/index.js 22 | '^.+\\.tsx?$': [ 23 | 'ts-jest/legacy', 24 | { 25 | tsconfig: `./tsconfig.test.json`, 26 | }, 27 | ], 28 | '^.+\\.jsx?$': 'babel-jest', 29 | }, 30 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 31 | setupFiles: ['/test/jest-setup-files.js'], 32 | testPathIgnorePatterns: ['/lib/', '/node_modules/'], 33 | testRegex: '/test/src/.*.spec.ts$', 34 | transformIgnorePatterns: [`/node_modules/(?!${esModules}).+`], 35 | reporters: ['default', 'github-actions'], 36 | }; 37 | -------------------------------------------------------------------------------- /packages/nbdime/scripts/copy-files.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | var fs = require('fs-extra'); 5 | fs.copySync('src/', 'lib/', { 6 | filter: pth => fs.lstatSync(pth).isDirectory() || /\.css$/.test(pth), 7 | }); 8 | fs.copySync('src/', 'lib/', { 9 | filter: pth => fs.lstatSync(pth).isDirectory() || /\.svg$/.test(pth), 10 | }); 11 | fs.copySync('src/', 'lib/', { 12 | filter: pth => fs.lstatSync(pth).isDirectory() || /\.png$/.test(pth), 13 | }); 14 | -------------------------------------------------------------------------------- /packages/nbdime/src/chunking/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | export * from './diffchunking'; 5 | export * from './decisionchunking'; 6 | -------------------------------------------------------------------------------- /packages/nbdime/src/common/basepanel.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import type { CodeEditor } from '@jupyterlab/codeeditor'; 5 | 6 | import { type ITranslator, nullTranslator } from '@jupyterlab/translation'; 7 | 8 | import { Panel } from '@lumino/widgets'; 9 | 10 | import type { 11 | IDiffViewOptions, 12 | IDiffWidgetOptions, 13 | IMergeViewOptions, 14 | } from './interfaces'; 15 | 16 | /** 17 | * Common panel for diff views 18 | */ 19 | export class DiffPanel< 20 | T, 21 | U extends IDiffViewOptions = IDiffViewOptions, 22 | > extends Panel { 23 | constructor({ 24 | model, 25 | editorFactory, 26 | translator, 27 | ...viewOptions 28 | }: IDiffWidgetOptions & U) { 29 | super(); 30 | this._editorFactory = editorFactory; 31 | this._model = model; 32 | this._translator = translator ?? nullTranslator; 33 | this._viewOptions = viewOptions as U; 34 | } 35 | 36 | protected _editorFactory: CodeEditor.Factory | undefined; 37 | protected _model: T; 38 | protected _translator: ITranslator; 39 | protected _viewOptions: U; 40 | } 41 | 42 | /** 43 | * Common panel for merge views 44 | */ 45 | export class MergePanel extends DiffPanel {} 46 | -------------------------------------------------------------------------------- /packages/nbdime/src/common/dragpanel.css: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | | Copyright (c) 2014-2016, Jupyter Development Team. 3 | | 4 | | Distributed under the terms of the Modified BSD License. 5 | |----------------------------------------------------------------------------*/ 6 | 7 | .jp-DragPanel-dragHandle { 8 | background: 9 | radial-gradient(rgba(255, 255, 255, 0.6) 30%, transparent 31%) 0 0, 10 | radial-gradient(transparent 19%, rgba(0, 0, 0, 0.4) 20%, transparent 50%) 0 11 | 1px; 12 | background-size: 6px 6px; 13 | cursor: grab; 14 | } 15 | 16 | .lm-Panel.jp-DragPanel .lm-Widget.jp-mod-dropTarget { 17 | border-top: 1px dashed black; 18 | } 19 | 20 | .lm-mod-drag-image { 21 | left: 0px; 22 | top: 0px; 23 | } 24 | -------------------------------------------------------------------------------- /packages/nbdime/src/common/exceptions.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | 'use strict'; 5 | 6 | /** 7 | * An error that should be displayed to the user 8 | */ 9 | export class NotifyUserError extends Error { 10 | constructor(message: string, severity: NotifyUserError.Severity = 'error') { 11 | super(message); 12 | // Set the prototype explicitly. 13 | (Object as any).setPrototypeOf(this, NotifyUserError.prototype); 14 | this.message = message; 15 | this.stack = new Error().stack; 16 | this.severity = severity; 17 | } 18 | 19 | severity: NotifyUserError.Severity; 20 | } 21 | 22 | export namespace NotifyUserError { 23 | /** 24 | * Severity of an error. 25 | * 26 | * Anything less severe that warning shouldn't 27 | * use an exception. 28 | */ 29 | export type Severity = 'error' | 'warning'; 30 | } 31 | -------------------------------------------------------------------------------- /packages/nbdime/src/common/interfaces.ts: -------------------------------------------------------------------------------- 1 | import type { CodeEditor } from '@jupyterlab/codeeditor'; 2 | import type { IRenderMimeRegistry } from '@jupyterlab/rendermime'; 3 | import type { ITranslator } from '@jupyterlab/translation'; 4 | 5 | /** 6 | * Diff view options 7 | */ 8 | export interface IDiffViewOptions { 9 | /** 10 | * When true stretches of unchanged text will be collapsed in the text editors. 11 | * When a number is given, this indicates the amount of lines to leave visible 12 | * around such stretches (which defaults to 2). Defaults to true. 13 | */ 14 | collapseIdentical?: boolean | number; 15 | /** 16 | * The translation manager. 17 | */ 18 | translator?: ITranslator; 19 | } 20 | 21 | /** 22 | * Common merge view options 23 | */ 24 | export interface IMergeViewOptions extends IDiffViewOptions { 25 | /** 26 | * Whether to show the base version (4-panels) or not (3-panels). 27 | */ 28 | showBase?: boolean; 29 | } 30 | 31 | /** 32 | * Main widget constructor options 33 | */ 34 | // TODO `T` should be scoped down but more API rework will be needed on the model to achieve that 35 | // there is definitely room to rationalize the code with more abstract or mixin classes. 36 | export interface IDiffWidgetOptions extends IDiffViewOptions { 37 | /** 38 | * Diff model 39 | */ 40 | model: T; 41 | /** 42 | * Text editor factory 43 | */ 44 | editorFactory?: CodeEditor.Factory; 45 | } 46 | 47 | export interface IMimeDiffWidgetOptions extends IDiffWidgetOptions { 48 | /** 49 | * Rendermime registry 50 | */ 51 | rendermime: IRenderMimeRegistry; 52 | } 53 | 54 | export interface ICellDiffWidgetOptions extends IMimeDiffWidgetOptions { 55 | /** 56 | * Cell mime type 57 | */ 58 | // TODO this seems redundant as mimetype is part of the model 59 | mimetype: string; 60 | } 61 | -------------------------------------------------------------------------------- /packages/nbdime/src/diff/model/common.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 'use strict'; 4 | 5 | /** 6 | * Describes a model whose view can be collapsible. 7 | * 8 | * Intended as hints for a view of the model, and not a requirement. 9 | */ 10 | export interface ICollapsibleModel { 11 | /** 12 | * Whether a view of the model should be collapsible (hint) 13 | */ 14 | collapsible: boolean; 15 | 16 | /** 17 | * String to show in header of collapser element 18 | */ 19 | collapsibleHeader: string; 20 | 21 | /** 22 | * The initial state of a collapsible view 23 | */ 24 | startCollapsed: boolean; 25 | } 26 | 27 | /** 28 | * Base interface for diff models. 29 | */ 30 | export interface IDiffModel extends ICollapsibleModel { 31 | /** 32 | * Is diff no-op? 33 | */ 34 | unchanged: boolean; 35 | 36 | /** 37 | * Whether diff represents a simple addition 38 | */ 39 | added: boolean; 40 | 41 | /** 42 | * Whether diff represents a simple deletion 43 | */ 44 | deleted: boolean; 45 | } 46 | -------------------------------------------------------------------------------- /packages/nbdime/src/diff/model/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | export * from './cell'; 5 | export * from './common'; 6 | export * from './immutable'; 7 | export * from './notebook'; 8 | export * from './output'; 9 | export * from './renderable'; 10 | export * from './string'; 11 | -------------------------------------------------------------------------------- /packages/nbdime/src/diff/widget/common.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 'use strict'; 4 | 5 | export const TWOWAY_DIFF_CLASS = 'jp-Diff-twoway'; 6 | export const ADDED_DIFF_CLASS = 'jp-Diff-added'; 7 | export const DELETED_DIFF_CLASS = 'jp-Diff-deleted'; 8 | export const UNCHANGED_DIFF_CLASS = 'jp-Diff-unchanged'; 9 | 10 | export const DIFF_CLASSES = ['jp-Diff-base', 'jp-Diff-remote']; 11 | 12 | export const CHUNK_PANEL_CLASS = 'jp-Diff-addremchunk'; 13 | export const ADDED_CHUNK_PANEL_CLASS = 'jp-Diff-addedchunk'; 14 | export const REMOVED_CHUNK_PANEL_CLASS = 'jp-Diff-removedchunk'; 15 | export const ADD_DEL_LABEL_CLASS = 'jp-Diff-label'; 16 | -------------------------------------------------------------------------------- /packages/nbdime/src/diff/widget/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | export * from './cell'; 5 | export * from './notebook'; 6 | export * from './metadata'; 7 | -------------------------------------------------------------------------------- /packages/nbdime/src/diff/widget/metadata.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 'use strict'; 4 | 5 | import type { Widget } from '@lumino/widgets'; 6 | 7 | import { DiffPanel } from '../../common/basepanel'; 8 | 9 | import { createNbdimeMergeView } from '../../common/mergeview'; 10 | 11 | import { CollapsiblePanel } from '../../common/collapsiblepanel'; 12 | 13 | import type { IDiffWidgetOptions } from '../../common/interfaces'; 14 | 15 | import type { IStringDiffModel } from '../model'; 16 | 17 | import { TWOWAY_DIFF_CLASS } from './common'; 18 | 19 | const ROOT_METADATA_CLASS = 'jp-Metadata-diff'; 20 | 21 | /** 22 | * MetadataWidget for changes to Notebook-level metadata 23 | */ 24 | export class MetadataDiffWidget extends DiffPanel { 25 | constructor(options: IDiffWidgetOptions) { 26 | super(options); 27 | console.assert(!this._model.added && !this._model.deleted); 28 | this.addClass(ROOT_METADATA_CLASS); 29 | this.init(); 30 | } 31 | 32 | init() { 33 | let model = this._model; 34 | if (!model.unchanged) { 35 | this.addClass(TWOWAY_DIFF_CLASS); 36 | let view: Widget = createNbdimeMergeView({ 37 | remote: model, 38 | factory: this._editorFactory, 39 | translator: this._translator, 40 | ...this._viewOptions, 41 | }); 42 | if (model.collapsible) { 43 | view = new CollapsiblePanel( 44 | view, 45 | model.collapsibleHeader, 46 | model.startCollapsed, 47 | ); 48 | } 49 | this.addWidget(view); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/nbdime/src/diff/widget/renderable.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 'use strict'; 4 | 5 | import type { JSONValue, PartialJSONValue } from '@lumino/coreutils'; 6 | 7 | import { PanelLayout, Widget } from '@lumino/widgets'; 8 | 9 | import type { IRenderMimeRegistry } from '@jupyterlab/rendermime'; 10 | 11 | import type { RenderableDiffModel } from '../model'; 12 | 13 | /** 14 | * Widget for outputs with renderable MIME data. 15 | */ 16 | export abstract class RenderableDiffView< 17 | T extends JSONValue | PartialJSONValue, 18 | > extends Widget { 19 | constructor( 20 | model: RenderableDiffModel, 21 | editorClass: string[], 22 | rendermime: IRenderMimeRegistry, 23 | mimetype: string, 24 | ) { 25 | super(); 26 | this.rendermime = rendermime; 27 | this.model = model; 28 | this.mimetype = mimetype; 29 | let bdata = model.base; 30 | let rdata = model.remote; 31 | this.layout = new PanelLayout(); 32 | 33 | let ci = 0; 34 | if (bdata) { 35 | let widget = this.createSubView(bdata, model.trusted); 36 | this.layout.addWidget(widget); 37 | widget.addClass(editorClass[ci++]); 38 | } 39 | if (rdata && rdata !== bdata) { 40 | let widget = this.createSubView(rdata, model.trusted); 41 | this.layout.addWidget(widget); 42 | widget.addClass(editorClass[ci++]); 43 | } 44 | } 45 | 46 | get layout(): PanelLayout | null { 47 | return super.layout as PanelLayout | null; 48 | } 49 | set layout(value: PanelLayout | null) { 50 | super.layout = value; 51 | } 52 | 53 | mimetype: string; 54 | 55 | /** 56 | * Create a widget which renders the given cell output 57 | */ 58 | protected abstract createSubView(data: T, trusted: boolean): Widget; 59 | 60 | protected rendermime: IRenderMimeRegistry; 61 | 62 | protected model: RenderableDiffModel; 63 | } 64 | -------------------------------------------------------------------------------- /packages/nbdime/src/merge/model/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | export * from './cell'; 5 | export * from './common'; 6 | export * from './metadata'; 7 | export * from './notebook'; 8 | -------------------------------------------------------------------------------- /packages/nbdime/src/merge/model/metadata.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 'use strict'; 4 | 5 | import type * as nbformat from '@jupyterlab/nbformat'; 6 | 7 | import type { IDiffEntry } from '../../diff/diffentries'; 8 | 9 | import { 10 | IStringDiffModel, 11 | createPatchStringDiffModel, 12 | createDirectStringDiffModel, 13 | } from '../../diff/model'; 14 | 15 | import type { MergeDecision } from '../../merge/decisions'; 16 | 17 | import { ObjectMergeModel, DecisionStringDiffModel } from './common'; 18 | import { JSONObject, JSONExt } from '@lumino/coreutils'; 19 | 20 | /** 21 | * Model of a merge of metadata with decisions 22 | */ 23 | export class MetadataMergeModel extends ObjectMergeModel< 24 | nbformat.INotebookMetadata, 25 | IStringDiffModel 26 | > { 27 | constructor(base: nbformat.INotebookMetadata, decisions: MergeDecision[]) { 28 | super(base, decisions, 'application/json'); 29 | } 30 | 31 | serialize(): nbformat.INotebookMetadata { 32 | if (!this.merged || this.merged.remote === null) { 33 | throw new Error('Missing notebook metadata merge data.'); 34 | } 35 | // This will check whether metadata is valid JSON. 36 | // Validation of compatibility vs notebook format 37 | // will happen on server side. 38 | return JSON.parse(this.merged.remote); 39 | } 40 | 41 | protected createDiffModel(diff: IDiffEntry[]): IStringDiffModel { 42 | if (diff && diff.length > 0) { 43 | return createPatchStringDiffModel(this.base, diff); 44 | } else { 45 | const baseCopy = JSONExt.deepCopy(this.base) as JSONObject; 46 | return createDirectStringDiffModel(baseCopy, baseCopy); 47 | } 48 | } 49 | 50 | protected createMergedDiffModel(): IStringDiffModel { 51 | return new DecisionStringDiffModel(this.base, this.decisions, [ 52 | this.local, 53 | this.remote, 54 | ]); 55 | } 56 | 57 | declare base: nbformat.INotebookMetadata; 58 | } 59 | -------------------------------------------------------------------------------- /packages/nbdime/src/merge/widget/common.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 'use strict'; 4 | 5 | import { Widget } from '@lumino/widgets'; 6 | 7 | // Merge classes: 8 | export const UNCHANGED_MERGE_CLASS = 'jp-Merge-unchanged'; 9 | export const ONEWAY_LOCAL_CLASS = 'jp-Merge-oneway-local'; 10 | export const ONEWAY_REMOTE_CLASS = 'jp-Merge-oneway-remote'; 11 | export const TWOWAY_ADDITION_CLASS = 'jp-Merge-twoway-addition'; 12 | export const TWOWAY_DELETION_CLASS = 'jp-Merge-twoway-deletion'; 13 | 14 | const BASE_MERGE_CLASS = 'jp-Merge-base'; 15 | const LOCAL_MERGE_CLASS = 'jp-Merge-local'; 16 | const REMOTE_MERGE_CLASS = 'jp-Merge-remote'; 17 | const MERGED_MERGE_CLASS = 'jp-Merge-merged'; 18 | 19 | export const MERGE_CLASSES = [ 20 | BASE_MERGE_CLASS, 21 | LOCAL_MERGE_CLASS, 22 | REMOTE_MERGE_CLASS, 23 | MERGED_MERGE_CLASS, 24 | ]; 25 | 26 | /** 27 | * Create a widget containing a checkbox with a label. 28 | * 29 | * @export 30 | * @param {boolean} value - The initial check state (true = checked) 31 | * @param {string} text - The text of the label 32 | * @returns {{checkbox: HTMLInputElement, widget: Widget }} 33 | */ 34 | export function createCheckbox( 35 | value: boolean, 36 | text: string, 37 | indeterminate = false, 38 | ): { checkbox: HTMLInputElement; widget: Widget } { 39 | let checkbox = document.createElement('input'); 40 | checkbox.setAttribute('type', 'checkbox'); 41 | checkbox.checked = value; 42 | checkbox.indeterminate = indeterminate; 43 | // Create label for checkbox: 44 | let widget = new Widget(); 45 | let label = document.createElement('label'); 46 | label.textContent = text; 47 | // Combine checkbox and label: 48 | label.insertBefore(checkbox, label.childNodes[0]); 49 | // Add checkbox to header: 50 | widget.node.appendChild(label); 51 | return { checkbox, widget }; 52 | } 53 | -------------------------------------------------------------------------------- /packages/nbdime/src/merge/widget/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | export * from './cell'; 5 | export * from './notebook'; 6 | export * from './metadata'; 7 | -------------------------------------------------------------------------------- /packages/nbdime/src/patch/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 'use strict'; 4 | 5 | export * from './common'; 6 | export * from './generic'; 7 | export * from './stringified'; 8 | -------------------------------------------------------------------------------- /packages/nbdime/src/styles/variables.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --codemirror-border: var(--jp-border-width) solid 3 | var(--jp-cell-editor-border-color); 4 | 5 | --jp-nbdime-output-color1: rgba(0, 141, 255, 0.7); 6 | --jp-nbdime-output-color2: rgba(0, 141, 255, 0.5); 7 | --jp-nbdime-output-color3: rgba(0, 141, 255, 0.3); 8 | 9 | --jp-diff-added-color0: rgba(0, 200, 20, 0.6); 10 | --jp-diff-added-color1: rgba(0, 200, 20, 0.3); 11 | --jp-diff-added-color2: rgba(0, 200, 20, 0.3); 12 | 13 | --jp-diff-deleted-color0: rgba(240, 20, 0, 0.55); 14 | --jp-diff-deleted-color1: rgba(255, 20, 0, 0.3); 15 | --jp-diff-deleted-color2: rgba(255, 20, 0, 0.3); 16 | 17 | --jp-merge-local-color1: #cbcbf8; 18 | --jp-merge-local-color2: #ececff; 19 | --jp-merge-local-color3: #e6e6ff; 20 | 21 | --jp-merge-remote-color1: #beb; 22 | --jp-merge-remote-color2: #dfd; 23 | --jp-merge-remote-color3: #d8f9d8; 24 | 25 | --jp-merge-both-color1: #f88888; 26 | --jp-merge-both-color2: #ffdddd; 27 | 28 | --jp-merge-either-color1: #aee; 29 | --jp-merge-either-color2: #cff4f4; 30 | } 31 | -------------------------------------------------------------------------------- /packages/nbdime/src/upstreaming/flexpanel.css: -------------------------------------------------------------------------------- 1 | .lm-FlexPanel { 2 | display: flex; 3 | } 4 | 5 | .lm-FlexPanel.lm-mod-left-to-right { 6 | flex-direction: row; 7 | } 8 | 9 | .lm-FlexPanel.lm-mod-right-to-left { 10 | flex-direction: row-reverse; 11 | } 12 | 13 | .lm-FlexPanel.lm-mod-top-to-bottom { 14 | flex-direction: column; 15 | } 16 | 17 | .lm-FlexPanel.lm-mod-bottom-to-top { 18 | flex-direction: column-reverse; 19 | } 20 | -------------------------------------------------------------------------------- /packages/nbdime/test/files/base.ipynb.json: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": "from math import sqrt\n\ndef f(x, y):\n r2 = x**2+y**2\n return sqrt(r2)" 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 2, 15 | "metadata": { 16 | "collapsed": false 17 | }, 18 | "outputs": [ 19 | { 20 | "name": "stdout", 21 | "output_type": "stream", 22 | "text": "5.0\n" 23 | } 24 | ], 25 | "source": "l = f(3, 4)\nprint(l)" 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 3, 30 | "metadata": { 31 | "collapsed": false 32 | }, 33 | "outputs": [ 34 | { 35 | "data": { 36 | "text/plain": "52.0" 37 | }, 38 | "execution_count": 3, 39 | "metadata": {}, 40 | "output_type": "execute_result" 41 | } 42 | ], 43 | "source": "def g(z):\n return z**2 + 3.0\ng(7)" 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": null, 48 | "metadata": { 49 | "collapsed": true 50 | }, 51 | "outputs": [], 52 | "source": [] 53 | }, 54 | { 55 | "cell_type": "markdown", 56 | "metadata": {}, 57 | "source": "# Header\nLatex text: $ a = b $" 58 | } 59 | ], 60 | "metadata": { 61 | "kernelspec": { 62 | "display_name": "Python 2", 63 | "language": "python", 64 | "name": "python2" 65 | }, 66 | "language_info": { 67 | "codemirror_mode": { 68 | "name": "ipython", 69 | "version": 2 70 | }, 71 | "file_extension": ".py", 72 | "mimetype": "text/x-python", 73 | "name": "python", 74 | "nbconvert_exporter": "python", 75 | "pygments_lexer": "ipython2", 76 | "version": "2.7.11" 77 | } 78 | }, 79 | "nbformat": 4, 80 | "nbformat_minor": 0 81 | } 82 | -------------------------------------------------------------------------------- /packages/nbdime/test/jest-file-mock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /packages/nbdime/test/jest-setup-files.js: -------------------------------------------------------------------------------- 1 | global.fetch = require('jest-fetch-mock'); 2 | //global.crypto = require('crypto'); 3 | 4 | global.DragEvent = class DragEvent {}; 5 | -------------------------------------------------------------------------------- /packages/nbdime/test/src/common/collapsiblepanel.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import { Widget } from '@lumino/widgets'; 5 | 6 | import { CollapsiblePanel } from '../../../src/common/collapsiblepanel'; 7 | 8 | describe('common', () => { 9 | describe('CollapsiblePanel', () => { 10 | it('should be initialized with an inner widget', () => { 11 | let inner = new Widget(); 12 | let p = new CollapsiblePanel(inner); 13 | expect(p).not.toBe(null); 14 | }); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/nbdime/test/src/common/dragpanel.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import { DragDropPanel } from '../../../src/common/dragpanel'; 5 | 6 | describe('common', () => { 7 | describe('DragPanel', () => { 8 | it('should be initialized with no options', () => { 9 | let p = new DragDropPanel(); 10 | expect(p).not.toBe(null); 11 | }); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/nbdime/test/src/common/mergeview.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import { MergeView } from '../../../src/common/mergeview'; 5 | import { createDirectStringDiffModel } from '../../../src/diff/model/string'; 6 | 7 | describe('common', () => { 8 | describe('MergeView', () => { 9 | it('should be initialized for unchanged diff', () => { 10 | let orig = 'Value'; 11 | let remote = createDirectStringDiffModel(orig, orig); 12 | let p = new MergeView({ 13 | remote, 14 | }); 15 | expect(p).not.toBe(null); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/nbdime/test/src/diff/widget/metadata.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 4 | import { createDirectStringDiffModel } from '../../../../src/diff/model/string'; 5 | 6 | import { MetadataDiffWidget } from '../../../../src/diff/widget'; 7 | 8 | describe('diff', () => { 9 | describe('widget', () => { 10 | describe('MetadataDiffWidget', () => { 11 | it('should create a widget for an unchanged model', () => { 12 | let model = createDirectStringDiffModel('{}', '{}'); 13 | let widget = new MetadataDiffWidget({ model }); 14 | expect(widget).not.toBe(null); 15 | }); 16 | }); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/nbdime/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig_base", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "lib", 6 | "resolveJsonModule": true 7 | }, 8 | "include": ["src/**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/nbdime/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "compilerOptions": { 4 | "rootDir": null, 5 | "inlineSourceMap": true, 6 | "types": ["jest"] 7 | }, 8 | "include": ["src/**/*", "test/**/*"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/webapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nbdime-webapp", 3 | "version": "4.0.2", 4 | "private": true, 5 | "license": "BSD-3-Clause", 6 | "main": "static/nbdime.js", 7 | "scripts": { 8 | "build": "webpack --mode=production", 9 | "build:dev": "webpack --mode=development", 10 | "clean": "rimraf build && rimraf -g \"../../nbdime/webapp/static/!(favicon.ico)\"", 11 | "profile": "webpack --profile --json > webpack-stats.json", 12 | "watch": "webpack --mode=development --watch" 13 | }, 14 | "dependencies": { 15 | "@fortawesome/fontawesome-free": "^5.12.0", 16 | "@jupyterlab/application": "^4.0.0", 17 | "@jupyterlab/apputils": "^4.0.0", 18 | "@jupyterlab/cells": "^4.0.0", 19 | "@jupyterlab/codemirror": "^4.0.0", 20 | "@jupyterlab/coreutils": "^6.0.0", 21 | "@jupyterlab/mathjax-extension": "^4.0.0", 22 | "@jupyterlab/nbformat": "^4.0.0", 23 | "@jupyterlab/notebook": "^4.0.0", 24 | "@jupyterlab/rendermime": "^4.0.0", 25 | "@jupyterlab/theme-light-extension": "^4.0.0", 26 | "@lumino/dragdrop": "^2.0.0", 27 | "@lumino/widgets": "^2.0.0", 28 | "alertify.js": "^1.0.12", 29 | "file-saver": "^2.0.1", 30 | "nbdime": "^7.0.2" 31 | }, 32 | "devDependencies": { 33 | "@types/file-saver": "^2.0.0", 34 | "@types/json-stable-stringify": "^1.0.32", 35 | "@types/node": "^18.15.0", 36 | "@types/sanitizer": "^0.0.28", 37 | "css-loader": "^6.7.3", 38 | "file-loader": "^6.2.0", 39 | "rimraf": "^5.0.0", 40 | "source-map-loader": "^4.0.1", 41 | "style-loader": "^3.3.2", 42 | "ts-loader": "^9.4.2", 43 | "typescript": "^4.9.0", 44 | "webpack": "^5.78.0", 45 | "webpack-cli": "^5.0.1" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/webapp/src/app/common.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow: scroll !important; 3 | background-color: var(--jp-layout-color1); 4 | color: var(--jp-content-font-color1); 5 | 6 | font-family: sans-serif; 7 | } 8 | 9 | .CodeMirror-scroll { 10 | max-height: calc(100vh - 4em); 11 | } 12 | 13 | #nbdime-header { 14 | margin: auto; 15 | width: 90%; 16 | margin-bottom: 20px; 17 | } 18 | 19 | #nbdime-header-banner { 20 | display: grid; 21 | grid-auto-rows: minmax(18px, auto); 22 | margin-top: 10px; 23 | } 24 | 25 | .nbdime-header-button { 26 | display: inline-block; 27 | margin-right: 10px; 28 | } 29 | 30 | #nbdime-header-buttonrow .nbdime-spinner { 31 | width: 14px; 32 | height: 14px; 33 | display: inline-flex; 34 | border-width: 3px; 35 | } 36 | 37 | .nbdime-spinner { 38 | border: 16px solid #eee; 39 | border-top-color: #999; 40 | border-bottom-color: #999; 41 | border-radius: 50%; 42 | animation: spin 1.3s linear infinite; 43 | } 44 | 45 | @keyframes spin { 46 | 0% { 47 | transform: rotate(0deg); 48 | } 49 | 100% { 50 | transform: rotate(360deg); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/webapp/src/app/save.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Jupyter Development Team. 2 | // Distributed under the terms of the Modified BSD License. 3 | 'use strict'; 4 | 5 | import * as alertify from 'alertify.js'; 6 | 7 | import type * as nbformat from '@jupyterlab/nbformat'; 8 | 9 | import type { NotebookMergeWidget } from 'nbdime/lib/merge/widget'; 10 | 11 | /** 12 | * Extract the merged notebook from the model, as well as any remaining 13 | * conflicts, and send them to the server for storage / further processing. 14 | */ 15 | export function extractMergedNotebook( 16 | widget: NotebookMergeWidget, 17 | ): nbformat.INotebookContent { 18 | let nb = widget.model.serialize(); 19 | let validated = widget.validateMerged(nb); 20 | if (JSON.stringify(nb) !== JSON.stringify(validated)) { 21 | alertify.error( 22 | 'Value in internal model did not correspond to value from editors. ' + 23 | 'The values in the editors were used, but you should double check the output.', 24 | ); 25 | } 26 | return nb; 27 | } 28 | -------------------------------------------------------------------------------- /packages/webapp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig_base", 3 | "compilerOptions": { 4 | "noImplicitAny": false, 5 | "rootDir": "src", 6 | "outDir": "lib", 7 | "ignoreDeprecations": "5.0" 8 | }, 9 | "references": [{ "path": "../nbdime" }], 10 | "include": ["src/**/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/webapp/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/index.ts', 5 | output: { 6 | path: path.resolve(__dirname, '..', '..', 'nbdime', 'webapp', 'static'), 7 | filename: 'nbdime.js', 8 | publicPath: './static/', 9 | }, 10 | bail: true, 11 | devtool: 'source-map', 12 | module: { 13 | rules: [ 14 | { test: /\.css$/, use: ['style-loader', 'css-loader'] }, 15 | { test: /\.ipynb$/, type: 'json' }, 16 | { test: /\.ts$/, loader: 'ts-loader' }, 17 | { test: /\.js$/, loader: 'source-map-loader' }, 18 | { test: /\.html$/, loader: 'file-loader' }, 19 | // jquery-ui loads some images 20 | { test: /\.(jpg|png|gif)$/, type: 'asset/resource' }, 21 | // required to load font-awesome 22 | { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, type: 'asset' }, 23 | { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, type: 'asset' }, 24 | { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, type: 'asset' }, 25 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, type: 'asset/resource' }, 26 | { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, type: 'asset' }, 27 | ], 28 | }, 29 | resolve: { 30 | // Add '.ts' as resolvable extension. 31 | extensions: ['.webpack.js', '.web.js', '.ts', '.js'], 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding: utf-8 3 | 4 | # Copyright (c) Jupyter Development Team. 5 | # Distributed under the terms of the Modified BSD License. 6 | __import__("setuptools").setup() 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "include": [], 4 | "references": [ 5 | { "path": "./packages/nbdime" }, 6 | { "path": "./packages/webapp" }, 7 | { "path": "./packages/labextension" } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig_base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "declaration": true, 5 | "declarationMap": true, 6 | "lib": ["dom", "es6"], 7 | "module": "esnext", 8 | "moduleResolution": "node", 9 | "noEmitOnError": true, 10 | "pretty": true, 11 | "skipLibCheck": true, 12 | "strict": true, 13 | "sourceMap": true, 14 | "target": "es2020", 15 | "noUnusedLocals": true, 16 | "esModuleInterop": true, 17 | "noImplicitReturns": true, 18 | "resolveJsonModule": true, 19 | "strictPropertyInitialization": false, 20 | "importsNotUsedAsValues": "error" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ui-tests/data/diff_test1/center.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "eabfe4e0-d646-4d09-95a4-09ee3d030db6", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "x = np.arange(0, 2, 0.1)\n", 13 | "y = np.exp(x)\n", 14 | "plt.plot(x,y)" 15 | ] 16 | } 17 | ], 18 | "metadata": { 19 | "kernelspec": { 20 | "display_name": "Python 3 (ipykernel)", 21 | "language": "python", 22 | "name": "python3" 23 | }, 24 | "language_info": { 25 | "codemirror_mode": { 26 | "name": "ipython", 27 | "version": 3 28 | }, 29 | "file_extension": ".py", 30 | "mimetype": "text/x-python", 31 | "name": "python", 32 | "nbconvert_exporter": "python", 33 | "pygments_lexer": "ipython3", 34 | "version": "3.11.3" 35 | } 36 | }, 37 | "nbformat": 4, 38 | "nbformat_minor": 5 39 | } 40 | -------------------------------------------------------------------------------- /ui-tests/data/diff_test1/left.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "2d47bfe2-d91d-4f72-9426-1885c4edb5a9", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "x = np.arange(0, 2, 0.1)\n", 13 | "y = np.exp(x)\n", 14 | "plt.plot(x,y)" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "id": "b5b285e4", 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "# one comment" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "id": "e3ab7a01", 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [] 34 | } 35 | ], 36 | "metadata": { 37 | "kernelspec": { 38 | "display_name": "Python 3 (ipykernel)", 39 | "language": "python", 40 | "name": "python3" 41 | }, 42 | "language_info": { 43 | "codemirror_mode": { 44 | "name": "ipython", 45 | "version": 3 46 | }, 47 | "file_extension": ".py", 48 | "mimetype": "text/x-python", 49 | "name": "python", 50 | "nbconvert_exporter": "python", 51 | "pygments_lexer": "ipython3", 52 | "version": "3.11.3" 53 | } 54 | }, 55 | "nbformat": 4, 56 | "nbformat_minor": 5 57 | } 58 | -------------------------------------------------------------------------------- /ui-tests/data/diff_test2/center.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "2d47bfe2-d91d-4f72-9426-1885c4edb5a9", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "x = np.arange(0, 2, 0.1)\n", 13 | "y = np.exp(x)\n", 14 | "plt.plot(x,y)" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "id": "b5b285e4", 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "# comment" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "id": "e3ab7a01", 31 | "metadata": {}, 32 | "outputs": [], 33 | "source": [] 34 | } 35 | ], 36 | "metadata": { 37 | "kernelspec": { 38 | "display_name": "Python 3 (ipykernel)", 39 | "language": "python", 40 | "name": "python3" 41 | }, 42 | "language_info": { 43 | "codemirror_mode": { 44 | "name": "ipython", 45 | "version": 3 46 | }, 47 | "file_extension": ".py", 48 | "mimetype": "text/x-python", 49 | "name": "python", 50 | "nbconvert_exporter": "python", 51 | "pygments_lexer": "ipython3", 52 | "version": "3.11.3" 53 | } 54 | }, 55 | "nbformat": 4, 56 | "nbformat_minor": 5 57 | } 58 | -------------------------------------------------------------------------------- /ui-tests/data/diff_test2/left.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "eabfe4e0-d646-4d09-95a4-09ee3d030db6", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "x = np.arange(0, 2, 0.1)\n", 13 | "y = np.exp(x)\n", 14 | "plt.plot(x,y)" 15 | ] 16 | } 17 | ], 18 | "metadata": { 19 | "kernelspec": { 20 | "display_name": "Python 3 (ipykernel)", 21 | "language": "python", 22 | "name": "python3" 23 | }, 24 | "language_info": { 25 | "codemirror_mode": { 26 | "name": "ipython", 27 | "version": 3 28 | }, 29 | "file_extension": ".py", 30 | "mimetype": "text/x-python", 31 | "name": "python", 32 | "nbconvert_exporter": "python", 33 | "pygments_lexer": "ipython3", 34 | "version": "3.11.3" 35 | } 36 | }, 37 | "nbformat": 4, 38 | "nbformat_minor": 5 39 | } 40 | -------------------------------------------------------------------------------- /ui-tests/data/diff_test3/center.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "eabfe4e0-d646-4d09-95a4-09ee3d030db6", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "\n", 13 | "def gaussian(x, a, b, c):\n", 14 | " #calculate a gaussian\n", 15 | " return a * np.exp(-b * (x-c)**2)\n", 16 | "\n", 17 | "def sinus ():\n", 18 | " # Here you can see a sinus function\n", 19 | " # Let's keep on adding lines\n", 20 | " # And lines\n", 21 | " nx = 100\n", 22 | " x = np.linspace(-5.0, 5.0, nx)\n", 23 | " y = np.sin(x)\n", 24 | " return x, y\n", 25 | "\n", 26 | "def noisy_gaussian():\n", 27 | " # gaussian array y in interval -5 <= x <= 5\n", 28 | " nx = 100\n", 29 | " x = np.linspace(-5.0, 5.0, nx)\n", 30 | " y = gaussian(x, a=2.0, b=0.5, c=1.5)\n", 31 | " noise = np.random.normal(0.0, 0.2, nx)\n", 32 | " y += noise\n", 33 | " return x, y" 34 | ] 35 | } 36 | ], 37 | "metadata": { 38 | "kernelspec": { 39 | "display_name": "Python 3 (ipykernel)", 40 | "language": "python", 41 | "name": "python3" 42 | }, 43 | "language_info": { 44 | "codemirror_mode": { 45 | "name": "ipython", 46 | "version": 3 47 | }, 48 | "file_extension": ".py", 49 | "mimetype": "text/x-python", 50 | "name": "python", 51 | "nbconvert_exporter": "python", 52 | "pygments_lexer": "ipython3", 53 | "version": "3.11.3" 54 | } 55 | }, 56 | "nbformat": 4, 57 | "nbformat_minor": 5 58 | } 59 | -------------------------------------------------------------------------------- /ui-tests/data/diff_test3/left.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "2d47bfe2-d91d-4f72-9426-1885c4edb5a9", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "%matplotlib inline\n", 13 | "%this is a comment\n", 14 | "\n", 15 | "def gaussian(x, a, b, c):\n", 16 | " return a * np.exp(-b * (x-c)**2)\n", 17 | "\n", 18 | "def noisy_gaussian():\n", 19 | " # gaussian array y in interval -5 <= x <= 5\n", 20 | " nx = 100\n", 21 | " x = np.linspace(-5.0, 5.0, nx)\n", 22 | " y = gaussian(x, a=2.0, b=0.5, c=1.5)\n", 23 | " noise = np.random.normal(0.0, 0.2, nx)\n", 24 | " y += noise\n", 25 | " return x, y" 26 | ] 27 | } 28 | ], 29 | "metadata": { 30 | "kernelspec": { 31 | "display_name": "Python 3 (ipykernel)", 32 | "language": "python", 33 | "name": "python3" 34 | }, 35 | "language_info": { 36 | "codemirror_mode": { 37 | "name": "ipython", 38 | "version": 3 39 | }, 40 | "file_extension": ".py", 41 | "mimetype": "text/x-python", 42 | "name": "python", 43 | "nbconvert_exporter": "python", 44 | "pygments_lexer": "ipython3", 45 | "version": "3.11.3" 46 | } 47 | }, 48 | "nbformat": 4, 49 | "nbformat_minor": 5 50 | } 51 | -------------------------------------------------------------------------------- /ui-tests/data/merge_test1/center.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "eabfe4e0-d646-4d09-95a4-09ee3d030db6", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "x = np.arange(0, 2, 0.1)\n", 13 | "y = np.exp(x)\n", 14 | "plt.plot(x,y)" 15 | ] 16 | } 17 | ], 18 | "metadata": { 19 | "kernelspec": { 20 | "display_name": "Python 3 (ipykernel)", 21 | "language": "python", 22 | "name": "python3" 23 | }, 24 | "language_info": { 25 | "codemirror_mode": { 26 | "name": "ipython", 27 | "version": 3 28 | }, 29 | "file_extension": ".py", 30 | "mimetype": "text/x-python", 31 | "name": "python", 32 | "nbconvert_exporter": "python", 33 | "pygments_lexer": "ipython3", 34 | "version": "3.11.3" 35 | } 36 | }, 37 | "nbformat": 4, 38 | "nbformat_minor": 5 39 | } 40 | -------------------------------------------------------------------------------- /ui-tests/data/merge_test1/left.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "eabfe4e0-d646-4d09-95a4-09ee3d030db6", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "x = np.arange(0, 2, 0.1)\n", 13 | "y = np.exp(x + 2)\n", 14 | "plt.plot(x,y)" 15 | ] 16 | } 17 | ], 18 | "metadata": { 19 | "kernelspec": { 20 | "display_name": "Python 3 (ipykernel)", 21 | "language": "python", 22 | "name": "python3" 23 | }, 24 | "language_info": { 25 | "codemirror_mode": { 26 | "name": "ipython", 27 | "version": 3 28 | }, 29 | "file_extension": ".py", 30 | "mimetype": "text/x-python", 31 | "name": "python", 32 | "nbconvert_exporter": "python", 33 | "pygments_lexer": "ipython3", 34 | "version": "3.11.3" 35 | } 36 | }, 37 | "nbformat": 4, 38 | "nbformat_minor": 5 39 | } 40 | -------------------------------------------------------------------------------- /ui-tests/data/merge_test1/right.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "eabfe4e0-d646-4d09-95a4-09ee3d030db6", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "x = np.arange(0, 2, 0.1)\n", 13 | "y = np.exp(2 * x)\n", 14 | "plt.plot(x,y)" 15 | ] 16 | } 17 | ], 18 | "metadata": { 19 | "kernelspec": { 20 | "display_name": "Python 3 (ipykernel)", 21 | "language": "python", 22 | "name": "python3" 23 | }, 24 | "language_info": { 25 | "codemirror_mode": { 26 | "name": "ipython", 27 | "version": 3 28 | }, 29 | "file_extension": ".py", 30 | "mimetype": "text/x-python", 31 | "name": "python", 32 | "nbconvert_exporter": "python", 33 | "pygments_lexer": "ipython3", 34 | "version": "3.11.3" 35 | } 36 | }, 37 | "nbformat": 4, 38 | "nbformat_minor": 5 39 | } 40 | -------------------------------------------------------------------------------- /ui-tests/data/merge_test2/center.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "eabfe4e0-d646-4d09-95a4-09ee3d030db6", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "x = np.arange(0, 2, 0.1)\n", 13 | "y = np.exp(x)\n", 14 | "plt.plot(x,y)" 15 | ] 16 | } 17 | ], 18 | "metadata": { 19 | "kernelspec": { 20 | "display_name": "Python 3 (ipykernel)", 21 | "language": "python", 22 | "name": "python3" 23 | }, 24 | "language_info": { 25 | "codemirror_mode": { 26 | "name": "ipython", 27 | "version": 3 28 | }, 29 | "file_extension": ".py", 30 | "mimetype": "text/x-python", 31 | "name": "python", 32 | "nbconvert_exporter": "python", 33 | "pygments_lexer": "ipython3", 34 | "version": "3.11.3" 35 | } 36 | }, 37 | "nbformat": 4, 38 | "nbformat_minor": 5 39 | } 40 | -------------------------------------------------------------------------------- /ui-tests/data/merge_test2/left.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "2d47bfe2-d91d-4f72-9426-1885c4edb5a9", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "x = np.arange(0, 2, 0.1)\n", 13 | "y = np.exp(x + 2)\n", 14 | "plt.plot(x,y)" 15 | ] 16 | } 17 | ], 18 | "metadata": { 19 | "kernelspec": { 20 | "display_name": "Python 3 (ipykernel)", 21 | "language": "python", 22 | "name": "python3" 23 | }, 24 | "language_info": { 25 | "codemirror_mode": { 26 | "name": "ipython", 27 | "version": 3 28 | }, 29 | "file_extension": ".py", 30 | "mimetype": "text/x-python", 31 | "name": "python", 32 | "nbconvert_exporter": "python", 33 | "pygments_lexer": "ipython3", 34 | "version": "3.11.3" 35 | } 36 | }, 37 | "nbformat": 4, 38 | "nbformat_minor": 5 39 | } 40 | -------------------------------------------------------------------------------- /ui-tests/data/merge_test2/right.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "ff160690-22c8-48f3-9b89-d78a8997baec", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "x = np.arange(0, 2, 0.1)\n", 13 | "y = np.exp(x + 2)\n", 14 | "plt.plot(x,y)" 15 | ] 16 | } 17 | ], 18 | "metadata": { 19 | "kernelspec": { 20 | "display_name": "Python 3 (ipykernel)", 21 | "language": "python", 22 | "name": "python3" 23 | }, 24 | "language_info": { 25 | "codemirror_mode": { 26 | "name": "ipython", 27 | "version": 3 28 | }, 29 | "file_extension": ".py", 30 | "mimetype": "text/x-python", 31 | "name": "python", 32 | "nbconvert_exporter": "python", 33 | "pygments_lexer": "ipython3", 34 | "version": "3.11.3" 35 | } 36 | }, 37 | "nbformat": 4, 38 | "nbformat_minor": 5 39 | } 40 | -------------------------------------------------------------------------------- /ui-tests/data/merge_test4/center.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "eabfe4e0-d646-4d09-95a4-09ee3d030db6", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "\n", 13 | "def gaussian(x, a, b, c):\n", 14 | " return a * np.exp(-b * (x-c)**2)\n", 15 | "\n", 16 | "def sinus ():\n", 17 | " # Here you can see a sinus function\n", 18 | " x = np.linspace(-5.0, 5.0, 100)\n", 19 | " y = np.sin(x)\n", 20 | " return x, y\n", 21 | "\n", 22 | "def noisy_gaussian():\n", 23 | " # gaussian array y in interval -5 <= x <= 5\n", 24 | " nx = 100\n", 25 | " x = np.linspace(-5.0, 5.0, nx)\n", 26 | " y = gaussian(x, a=2.0, b=0.5, c=1.5)\n", 27 | " noise = np.random.normal(0.0, 0.2, nx)\n", 28 | " y += noise\n", 29 | " # add a line\n", 30 | " # add a second line\n", 31 | " return x, y" 32 | ] 33 | } 34 | ], 35 | "metadata": { 36 | "kernelspec": { 37 | "display_name": "Python 3 (ipykernel)", 38 | "language": "python", 39 | "name": "python3" 40 | }, 41 | "language_info": { 42 | "codemirror_mode": { 43 | "name": "ipython", 44 | "version": 3 45 | }, 46 | "file_extension": ".py", 47 | "mimetype": "text/x-python", 48 | "name": "python", 49 | "nbconvert_exporter": "python", 50 | "pygments_lexer": "ipython3", 51 | "version": "3.11.3" 52 | } 53 | }, 54 | "nbformat": 4, 55 | "nbformat_minor": 5 56 | } 57 | -------------------------------------------------------------------------------- /ui-tests/data/merge_test4/left.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "2d47bfe2-d91d-4f72-9426-1885c4edb5a9", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "\n", 13 | "def gaussian(x, a, b, c):\n", 14 | " return a * np.exp(-b * (x-c)**2)\n", 15 | "\n", 16 | "def sinus ():\n", 17 | " # Here you can see a sinus function\n", 18 | " x = np.linspace(-5.0, 5.0, 100)\n", 19 | " y = np.sin(x)\n", 20 | " return x, y\n", 21 | "\n", 22 | "def noisy_gaussian():\n", 23 | " # gaussian array y in interval -5 <= x <= 5\n", 24 | " nx = 200\n", 25 | " x = np.linspace(-5.0, 5.0, nx)\n", 26 | " y = gaussian(x, a=2.0, b=0.5, c=1.5)\n", 27 | " noise = np.random.normal(0.0, 0.2, nx)\n", 28 | " y += noise\n", 29 | " # add a line\n", 30 | " # add a second line\n", 31 | " return x, y" 32 | ] 33 | } 34 | ], 35 | "metadata": { 36 | "kernelspec": { 37 | "display_name": "Python 3 (ipykernel)", 38 | "language": "python", 39 | "name": "python3" 40 | }, 41 | "language_info": { 42 | "codemirror_mode": { 43 | "name": "ipython", 44 | "version": 3 45 | }, 46 | "file_extension": ".py", 47 | "mimetype": "text/x-python", 48 | "name": "python", 49 | "nbconvert_exporter": "python", 50 | "pygments_lexer": "ipython3", 51 | "version": "3.11.3" 52 | } 53 | }, 54 | "nbformat": 4, 55 | "nbformat_minor": 5 56 | } 57 | -------------------------------------------------------------------------------- /ui-tests/data/merge_test4/right.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "ff160690-22c8-48f3-9b89-d78a8997baec", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "\n", 13 | "def gaussian(x, a, b, c):\n", 14 | " return a * np.exp(-b * (x-c)**2)\n", 15 | "\n", 16 | "def sinus ():\n", 17 | " # Here you can see a sinus function\n", 18 | " x = np.linspace(-5.0, 5.0, 100)\n", 19 | " y = np.sin(x)\n", 20 | " return x, y\n", 21 | "\n", 22 | "def noisy_gaussian():\n", 23 | " # gaussian array y in interval -5 <= x <= 5\n", 24 | " nx = 100\n", 25 | " x = np.linspace(-5.0, 5.0, nx)\n", 26 | " y = gaussian(x, a=2.0, b=0.5, c=1.5)\n", 27 | " noise = np.random.normal(0.0, 0.2, nx)\n", 28 | " y += noise\n", 29 | " # add a line\n", 30 | " # add a second line\n", 31 | " return x, y" 32 | ] 33 | } 34 | ], 35 | "metadata": { 36 | "kernelspec": { 37 | "display_name": "Python 3 (ipykernel)", 38 | "language": "python", 39 | "name": "python3" 40 | }, 41 | "language_info": { 42 | "codemirror_mode": { 43 | "name": "ipython", 44 | "version": 3 45 | }, 46 | "file_extension": ".py", 47 | "mimetype": "text/x-python", 48 | "name": "python", 49 | "nbconvert_exporter": "python", 50 | "pygments_lexer": "ipython3", 51 | "version": "3.11.3" 52 | } 53 | }, 54 | "nbformat": 4, 55 | "nbformat_minor": 5 56 | } 57 | -------------------------------------------------------------------------------- /ui-tests/data/merge_test5/center.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "08e85e68-1722-4847-a13f-97c921829073", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "# Download the data from the EPA website\n", 11 | "data_file_urls = [\n", 12 | " 'https://aqs.epa.gov/aqsweb/airdata/daily_88101_2020.zip',\n", 13 | " 'https://aqs.epa.gov/aqsweb/airdata/daily_88101_2019.zip',\n", 14 | "]\n", 15 | "# copied this example from https://stackoverflow.com/questions/16694907/download-large-file-in-python-with-requests\n", 16 | "for url in data_file_urls:\n", 17 | " local_filename = \"data/{}\".format(url.split('/')[-1])\n", 18 | " with requests.get(url, stream=True) as r:\n", 19 | " r.raise_for_status()\n", 20 | " with open(local_filename, 'wb') as f:\n", 21 | " for chunk in r.iter_content(chunk_size=8192): \n", 22 | " f.write(chunk)\n", 23 | "# and unzip the files\n", 24 | "files_to_unzip = [\"data/{}\".format(url.split('/')[-1]) for url in data_file_urls]\n", 25 | "for f in files_to_unzip:\n", 26 | " with zipfile.ZipFile(f,\"r\") as zip_ref:\n", 27 | " zip_ref.extractall(\"data\")" 28 | ] 29 | } 30 | ], 31 | "metadata": { 32 | "kernelspec": { 33 | "display_name": "Python 3 (ipykernel)", 34 | "language": "python", 35 | "name": "python3" 36 | }, 37 | "language_info": { 38 | "codemirror_mode": { 39 | "name": "ipython", 40 | "version": 3 41 | }, 42 | "file_extension": ".py", 43 | "mimetype": "text/x-python", 44 | "name": "python", 45 | "nbconvert_exporter": "python", 46 | "pygments_lexer": "ipython3", 47 | "version": "3.11.4" 48 | } 49 | }, 50 | "nbformat": 4, 51 | "nbformat_minor": 5 52 | } 53 | -------------------------------------------------------------------------------- /ui-tests/data/merge_test5/left.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "08e85e68-1722-4847-a13f-97c921829073", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "# Download the data from the EPA website\n", 11 | "data_file_urls = [\n", 12 | " 'https://aqs.epa.gov/aqsweb/airdata/daily_88101_2020.zip',\n", 13 | " 'https://aqs.epa.gov/aqsweb/airdata/daily_88101_2019.zip',\n", 14 | " 'https://aqs.epa.gov/aqsweb/airdata/daily_88101_2021.zip',\n", 15 | " 'https://aqs.epa.gov/aqsweb/airdata/daily_88101_2022.zip',\n", 16 | "]\n", 17 | "# copied this example from https://stackoverflow.com/questions/16694907/download-large-file-in-python-with-requests\n", 18 | "for url in data_file_urls:\n", 19 | " local_filename = \"data/{}\".format(url.split('/')[-1])\n", 20 | " with requests.get(url, stream=True) as r:\n", 21 | " r.raise_for_status()\n", 22 | " with open(local_filename, 'wb') as f:\n", 23 | " for chunk in r.iter_content(chunk_size=8192): \n", 24 | " f.write(chunk)\n", 25 | "# and unzip the files\n", 26 | "files_to_unzip = [\"data/{}\".format(url.split('/')[-1]) for url in data_file_urls]\n", 27 | "for f in files_to_unzip:\n", 28 | " with zipfile.ZipFile(f,\"r\") as zip_ref:\n", 29 | " zip_ref.extractall(\"data\")" 30 | ] 31 | } 32 | ], 33 | "metadata": { 34 | "kernelspec": { 35 | "display_name": "Python 3 (ipykernel)", 36 | "language": "python", 37 | "name": "python3" 38 | }, 39 | "language_info": { 40 | "codemirror_mode": { 41 | "name": "ipython", 42 | "version": 3 43 | }, 44 | "file_extension": ".py", 45 | "mimetype": "text/x-python", 46 | "name": "python", 47 | "nbconvert_exporter": "python", 48 | "pygments_lexer": "ipython3", 49 | "version": "3.11.4" 50 | } 51 | }, 52 | "nbformat": 4, 53 | "nbformat_minor": 5 54 | } 55 | -------------------------------------------------------------------------------- /ui-tests/data/merge_test5/right.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "id": "08e85e68-1722-4847-a13f-97c921829073", 7 | "metadata": {}, 8 | "outputs": [], 9 | "source": [ 10 | "# Download the data from the EPA website\n", 11 | "data_file_urls = [\n", 12 | " 'https://aqs.epa.gov/aqsweb/airdata/daily_88101_2017.zip',\n", 13 | " 'https://aqs.epa.gov/aqsweb/airdata/daily_88101_2018.zip',\n", 14 | "]\n", 15 | "# copied this example from https://stackoverflow.com/questions/16694907/download-large-file-in-python-with-requests\n", 16 | "for url in data_file_urls:\n", 17 | " local_filename = \"data/{}\".format(url.split('/')[-1])\n", 18 | " with requests.get(url, stream=True) as t:\n", 19 | " t.raise_for_status()\n", 20 | " with open(local_filename, 'wb') as f:\n", 21 | " for chunk in t.iter_content(chunk_size=8192): \n", 22 | " f.write(chunk)" 23 | ] 24 | } 25 | ], 26 | "metadata": { 27 | "kernelspec": { 28 | "display_name": "Python 3 (ipykernel)", 29 | "language": "python", 30 | "name": "python3" 31 | }, 32 | "language_info": { 33 | "codemirror_mode": { 34 | "name": "ipython", 35 | "version": 3 36 | }, 37 | "file_extension": ".py", 38 | "mimetype": "text/x-python", 39 | "name": "python", 40 | "nbconvert_exporter": "python", 41 | "pygments_lexer": "ipython3", 42 | "version": "3.11.4" 43 | } 44 | }, 45 | "nbformat": 4, 46 | "nbformat_minor": 5 47 | } 48 | -------------------------------------------------------------------------------- /ui-tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nbdime-ui-tests", 3 | "version": "1.0.0", 4 | "description": "Nbdime Integration Tests", 5 | "private": true, 6 | "scripts": { 7 | "start": "nbmerge-web -p 41000 data/merge_test1/center.ipynb data/merge_test1/left.ipynb data/merge_test1/right.ipynb ", 8 | "test": "npx playwright test", 9 | "test:update": "npx playwright test --update-snapshots", 10 | "playwright": "npx playwright" 11 | }, 12 | "devDependencies": { 13 | "@playwright/test": "^1.36.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ui-tests/playwright.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Configuration for Playwright 3 | */ 4 | 5 | module.exports = { 6 | reporter: [ 7 | [process.env.CI ? 'github' : 'list'], 8 | ['html', { open: process.env.CI ? 'never' : 'on-failure' }], 9 | ], 10 | reportSlowTests: null, 11 | retries: process.env.CI ? 1 : 0, 12 | timeout: 60000, 13 | use: { 14 | // Browser options 15 | // headless: false, 16 | // slowMo: 500, 17 | 18 | // Context options 19 | viewport: { width: 1024, height: 768 }, 20 | 21 | // Artifacts 22 | // trace: 'on-first-retry', 23 | video: 'retain-on-failure', 24 | }, 25 | webServer: { 26 | command: 'npm start', 27 | url: 'http://localhost:41000/merge', 28 | timeout: 120 * 1000, 29 | reuseExistingServer: !process.env.CI, 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-diff-test1.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | test.beforeEach(async ({ page }) => { 4 | await page.goto('http://localhost:41000/diff'); 5 | await page.locator('#diff-remote').fill('data/diff_test1/left.ipynb'); 6 | await page.locator('#diff-base').fill('data/diff_test1/center.ipynb'); 7 | await page.getByRole('button', { name: 'Diff files' }).click(); 8 | }); 9 | 10 | /* added cells between left and right editors */ 11 | test.describe('diff test1', () => { 12 | test('take a snapshot at opening', async ({ page }) => { 13 | expect(await page.locator('#main').screenshot()).toMatchSnapshot(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-diff-test1.spec.ts-snapshots/diff-test1-take-a-snapshot-at-opening-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-diff-test1.spec.ts-snapshots/diff-test1-take-a-snapshot-at-opening-1-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-diff-test1.spec.ts-snapshots/diff-test1-take-a-snapshot-at-opening-1-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-diff-test1.spec.ts-snapshots/diff-test1-take-a-snapshot-at-opening-1-win32.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-diff-test2.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | test.beforeEach(async ({ page }) => { 4 | await page.goto('http://localhost:41000/diff'); 5 | await page.locator('#diff-remote').fill('data/diff_test2/left.ipynb'); 6 | await page.locator('#diff-base').fill('data/diff_test2/center.ipynb'); 7 | await page.getByRole('button', { name: 'Diff files' }).click(); 8 | }); 9 | 10 | /* deleted cells between left and right editors */ 11 | test.describe('diff test2', () => { 12 | test('take a snapshot at opening', async ({ page }) => { 13 | expect(await page.locator('#main').screenshot()).toMatchSnapshot(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-diff-test2.spec.ts-snapshots/diff-test2-take-a-snapshot-at-opening-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-diff-test2.spec.ts-snapshots/diff-test2-take-a-snapshot-at-opening-1-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-diff-test2.spec.ts-snapshots/diff-test2-take-a-snapshot-at-opening-1-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-diff-test2.spec.ts-snapshots/diff-test2-take-a-snapshot-at-opening-1-win32.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-diff-test3.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | test.beforeEach(async ({ page }) => { 4 | await page.goto('http://localhost:41000/diff'); 5 | await page.locator('#diff-remote').fill('data/diff_test3/left.ipynb'); 6 | await page.locator('#diff-base').fill('data/diff_test3/center.ipynb'); 7 | await page.getByRole('button', { name: 'Diff files' }).click(); 8 | }); 9 | 10 | /* notebooks with spacers */ 11 | test.describe('diff test3', () => { 12 | test('take a snapshot at opening', async ({ page }) => { 13 | expect(await page.locator('#main').screenshot()).toMatchSnapshot(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-diff-test3.spec.ts-snapshots/diff-test3-take-a-snapshot-at-opening-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-diff-test3.spec.ts-snapshots/diff-test3-take-a-snapshot-at-opening-1-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-diff-test3.spec.ts-snapshots/diff-test3-take-a-snapshot-at-opening-1-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-diff-test3.spec.ts-snapshots/diff-test3-take-a-snapshot-at-opening-1-win32.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/3-panels-view-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/3-panels-view-1-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/3-panels-view-1-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/3-panels-view-1-win32.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-choose-central-version-for-conflict-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-choose-central-version-for-conflict-1-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-choose-central-version-for-conflict-1-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-choose-central-version-for-conflict-1-win32.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-choose-left-version-for-conflict-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-choose-left-version-for-conflict-1-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-choose-left-version-for-conflict-1-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-choose-left-version-for-conflict-1-win32.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-choose-right-version-for-conflict-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-choose-right-version-for-conflict-1-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-choose-right-version-for-conflict-1-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-choose-right-version-for-conflict-1-win32.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-should-not-collapse-source-for-unchanged-metadata-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-should-not-collapse-source-for-unchanged-metadata-1-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-should-not-collapse-source-for-unchanged-metadata-1-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-should-not-collapse-source-for-unchanged-metadata-1-win32.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-take-a-snapshot-at-opening-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-take-a-snapshot-at-opening-1-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-take-a-snapshot-at-opening-1-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test1.spec.ts-snapshots/merge-test1-take-a-snapshot-at-opening-1-win32.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test2.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | test.beforeEach(async ({ page }) => { 4 | await page.goto('http://localhost:41000/merge'); 5 | await page.locator('#merge-local').fill('data/merge_test2/left.ipynb'); 6 | await page.locator('#merge-base').fill('data/merge_test2/center.ipynb'); 7 | await page.locator('#merge-remote').fill('data/merge_test2/right.ipynb'); 8 | await page.getByRole('button', { name: 'Merge files' }).click(); 9 | }); 10 | 11 | /* notebooks of same length with 0 conflict*/ 12 | test.describe('merge test2 ', () => { 13 | test('take a snapshot at opening', async ({ page }) => { 14 | await expect.soft(page.getByText('➭')).toHaveCount(11); 15 | expect(await page.locator('#main').screenshot()).toMatchSnapshot(); 16 | }); 17 | 18 | test('choose left version', async ({ page }) => { 19 | await page 20 | .locator('.cm-merge-left-editor') 21 | .nth(1) // This select the cell; 0 being the notebook metadata 22 | .locator('.jp-Merge-gutter-picker') 23 | .last() 24 | .click(); 25 | expect(await page.locator('#main').screenshot()).toMatchSnapshot(); 26 | }); 27 | 28 | test('choose central version', async ({ page }) => { 29 | await page 30 | .locator('.cm-central-editor') 31 | .nth(1) // This select the cell; 0 being the notebook metadata 32 | .locator('.jp-Merge-gutter-picker') 33 | .nth(1) 34 | .click(); 35 | expect(await page.locator('#main').screenshot()).toMatchSnapshot(); 36 | }); 37 | 38 | test('choose right version', async ({ page }) => { 39 | await page 40 | .locator('.cm-merge-right-editor') 41 | .nth(1) // This select the cell; 0 being the notebook metadata 42 | .locator('.jp-Merge-gutter-picker') 43 | .last() 44 | .click(); 45 | expect(await page.locator('#main').screenshot()).toMatchSnapshot(); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test2.spec.ts-snapshots/merge-test2-choose-central-version-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test2.spec.ts-snapshots/merge-test2-choose-central-version-1-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test2.spec.ts-snapshots/merge-test2-choose-central-version-1-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test2.spec.ts-snapshots/merge-test2-choose-central-version-1-win32.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test2.spec.ts-snapshots/merge-test2-choose-left-version-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test2.spec.ts-snapshots/merge-test2-choose-left-version-1-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test2.spec.ts-snapshots/merge-test2-choose-left-version-1-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test2.spec.ts-snapshots/merge-test2-choose-left-version-1-win32.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test2.spec.ts-snapshots/merge-test2-choose-right-version-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test2.spec.ts-snapshots/merge-test2-choose-right-version-1-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test2.spec.ts-snapshots/merge-test2-choose-right-version-1-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test2.spec.ts-snapshots/merge-test2-choose-right-version-1-win32.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test2.spec.ts-snapshots/merge-test2-take-a-snapshot-at-opening-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test2.spec.ts-snapshots/merge-test2-take-a-snapshot-at-opening-1-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test2.spec.ts-snapshots/merge-test2-take-a-snapshot-at-opening-1-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test2.spec.ts-snapshots/merge-test2-take-a-snapshot-at-opening-1-win32.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test4.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | test.beforeEach(async ({ page }) => { 4 | await page.goto('http://localhost:41000/merge'); 5 | await page.locator('#merge-local').fill('data/merge_test4/left.ipynb'); 6 | await page.locator('#merge-base').fill('data/merge_test4/center.ipynb'); 7 | await page.locator('#merge-remote').fill('data/merge_test4/right.ipynb'); 8 | await page.getByRole('button', { name: 'Merge files' }).click(); 9 | }); 10 | 11 | /* notebooks of same length and 1 conflict*/ 12 | test.describe('merge test4', () => { 13 | test('should synchronize the collapse status between editor', async ({ 14 | page, 15 | }) => { 16 | expect.soft(await page.locator('#main').screenshot()).toMatchSnapshot(); 17 | 18 | // Should display 8 collapsers 19 | const collapsers1 = page.getByText('12 unchanged lines'); 20 | await expect.soft(collapsers1).toHaveCount(4); 21 | const collapsers2 = page.getByText('5 unchanged lines'); 22 | await expect.soft(collapsers2).toHaveCount(4); 23 | await expect.soft(page.getByText('import numpy')).toHaveCount(0); 24 | await expect 25 | .soft(page.getByText('noise = np.random.normal(0.0, 0.2, nx)')) 26 | .toHaveCount(0); 27 | 28 | // Click on the base editor collapsers 29 | await page.getByText('12 unchanged lines').nth(1).click(); 30 | await expect.soft(collapsers1).toHaveCount(0); 31 | await page.getByText('5 unchanged lines').nth(1).click(); 32 | await expect.soft(collapsers2).toHaveCount(0); 33 | 34 | // Should not display any collapser 35 | 36 | await expect(page.getByText('import numpy')).toHaveCount(4); 37 | await expect( 38 | page.getByText('noise = np.random.normal(0.0, 0.2, nx)'), 39 | ).toHaveCount(4); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test4.spec.ts-snapshots/merge-test4-should-synchronize-the-collapse-status-between-editor-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test4.spec.ts-snapshots/merge-test4-should-synchronize-the-collapse-status-between-editor-1-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test4.spec.ts-snapshots/merge-test4-should-synchronize-the-collapse-status-between-editor-1-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test4.spec.ts-snapshots/merge-test4-should-synchronize-the-collapse-status-between-editor-1-win32.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test5.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | test.describe('merge test5', () => { 4 | test.beforeEach(async ({ page }) => { 5 | await page.goto('http://localhost:41000/merge'); 6 | await page.locator('#merge-local').fill('data/merge_test5/left.ipynb'); 7 | await page.locator('#merge-base').fill('data/merge_test5/center.ipynb'); 8 | await page.locator('#merge-remote').fill('data/merge_test5/right.ipynb'); 9 | await page.getByRole('button', { name: 'Merge files' }).click(); 10 | }); 11 | 12 | test('take a snapshot at opening', async ({ page }) => { 13 | await expect.soft(page.getByText('➭')).toHaveCount(16); 14 | expect.soft(await page.locator('#main').screenshot()).toMatchSnapshot(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test5.spec.ts-snapshots/merge-test5-take-a-snapshot-at-opening-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test5.spec.ts-snapshots/merge-test5-take-a-snapshot-at-opening-1-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test5.spec.ts-snapshots/merge-test5-take-a-snapshot-at-opening-1-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test5.spec.ts-snapshots/merge-test5-take-a-snapshot-at-opening-1-win32.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test6.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | test.describe('merge test6', () => { 4 | test.beforeEach(async ({ page }) => { 5 | await page.goto('http://localhost:41000/merge'); 6 | await page.locator('#merge-local').fill('data/merge_test6/left.ipynb'); 7 | await page.locator('#merge-base').fill('data/merge_test6/center.ipynb'); 8 | await page.locator('#merge-remote').fill('data/merge_test6/right.ipynb'); 9 | await page.getByRole('button', { name: 'Merge files' }).click(); 10 | }); 11 | 12 | test('take a snapshot at opening', async ({ page }) => { 13 | await page.getByText('Hide unchanged cells').click(); 14 | await page.getByText('a = "hello the world"').waitFor(); 15 | // Check that single editor are not collapsed; added cell and unchanged cell 16 | expect.soft(await page.locator('#main').screenshot()).toMatchSnapshot(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test6.spec.ts-snapshots/merge-test6-take-a-snapshot-at-opening-1-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test6.spec.ts-snapshots/merge-test6-take-a-snapshot-at-opening-1-linux.png -------------------------------------------------------------------------------- /ui-tests/tests/nbdime-merge-test6.spec.ts-snapshots/merge-test6-take-a-snapshot-at-opening-1-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/nbdime/458beebb56b9de160abdbb84ae2d19f2d61fbd4e/ui-tests/tests/nbdime-merge-test6.spec.ts-snapshots/merge-test6-take-a-snapshot-at-opening-1-win32.png --------------------------------------------------------------------------------