├── Makefile ├── README.md ├── fastdiff.wasm ├── python ├── .editorconfig ├── .github │ └── ISSUE_TEMPLATE.md ├── .gitignore ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs │ ├── Makefile │ ├── conf.py │ ├── contributing.rst │ ├── history.rst │ ├── index.rst │ ├── installation.rst │ ├── make.bat │ ├── readme.rst │ └── usage.rst ├── fastdiff │ ├── __init__.py │ ├── _base.py │ ├── _native.py │ ├── compare.py │ └── fastdiff.wasm ├── requirements_dev.txt ├── setup.cfg ├── setup.py ├── tests │ └── test_fastdiff.py └── tox.ini └── wasm ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src └── lib.rs /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | cargo build --manifest-path wasm/Cargo.toml --release --target=wasm32-unknown-unknown 3 | cp wasm/target/wasm32-unknown-unknown/release/fastdiff.wasm fastdiff.wasm 4 | 5 | optimize: 6 | wasm-strip fastdiff.wasm 7 | wasm-opt -O3 fastdiff.wasm -o fastdiff.wasm 8 | 9 | test_python: python 10 | cd python && make install && pytest -v -s --benchmark-skip 11 | 12 | test: test_python 13 | 14 | bench_python: python 15 | cd python && pytest --benchmark-only 16 | 17 | bench: bench_python 18 | 19 | all: build optimize test_python 20 | 21 | python: build optimize 22 | cp fastdiff.wasm python/fastdiff/ 23 | 24 | publish: 25 | cd python && python setup.py sdist bdist_wheel 26 | cd python && python setup.py sdist upload 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fastdiff 2 | 3 | `fastdiff` is a re-implementation of difflib in pure Rust compiled to WebAssembly to speedup different language integrations: 4 | 5 | * [Python](https://github.com/syrusakbary/fastdiff/tree/master/python): via [Wasmer](https://github.com/wasmerio/python-ext-wasm), falling back to `difflib` [compare](https://docs.python.org/3/library/difflib.html#difflib.Differ.compare). 6 | 7 | 8 | ## Install 9 | 10 | ### Python 11 | To install `fastdiff` in Python, you just need to do: 12 | 13 | ```shell 14 | pip install fastdiff 15 | ``` 16 | 17 | And then, use it in Python like this: 18 | ```python 19 | from fastdiff import compare 20 | 21 | str1 = 'hello\nwasm\n' 22 | str2 = 'hello\npython\n' 23 | 24 | print(compare(str1, str2)) 25 | ``` 26 | 27 | ## Benchmarks 28 | 29 | ### Python 30 | 31 | When comparing strings with 300 lines, the WebAssembly based approach is about **100 times faster** than the pure Python approach. 32 | 33 | ``` 34 | ---------------------------------------------------------------------------------------------- benchmark: 3 tests --------------------------------------------------------------------------------------------- 35 | Name (time in ms) Min Max Mean StdDev Median IQR Outliers OPS Rounds Iterations 36 | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 37 | test_benchmark_content_native 47.0735 (1.0) 50.7082 (1.0) 47.4401 (1.0) 0.7944 (1.0) 47.1680 (1.0) 0.2133 (1.0) 2;2 21.0792 (1.0) 21 1 38 | test_benchmark_compile_native 1,187.2395 (25.22) 1,305.0581 (25.74) 1,222.2197 (25.76) 48.3778 (60.90) 1,198.9380 (25.42) 51.4950 (241.44) 1;0 0.8182 (0.04) 5 1 39 | test_benchmark_content_base 5,480.3568 (116.42) 5,623.0508 (110.89) 5,516.5008 (116.28) 60.7623 (76.49) 5,487.1852 (116.33) 57.0271 (267.38) 1;0 0.1813 (0.01) 5 1 40 | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 41 | ``` 42 | 43 | ## Building 44 | 45 | For building the WebAssembly file, you need Rust and the `wasm32` target. 46 | 47 | ``` 48 | # Install Rustup 49 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 50 | 51 | # Add wasm32 target 52 | rustup target add wasm32-unknown-unknown 53 | ``` 54 | 55 | And then, run: 56 | 57 | ``` 58 | make build 59 | ``` 60 | 61 | ## Testing 62 | 63 | For testing in Python you can do: 64 | 65 | ``` 66 | make test_python 67 | ``` 68 | -------------------------------------------------------------------------------- /fastdiff.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syrusakbary/fastdiff/5bf48f8b8ae1cb99f45286d86f084b391a97bd19/fastdiff.wasm -------------------------------------------------------------------------------- /python/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /python/.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * fastdiff version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | .benchmarks/ 4 | .eggs/ 5 | 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # dotenv 87 | .env 88 | 89 | # virtualenv 90 | .venv 91 | venv/ 92 | ENV/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | -------------------------------------------------------------------------------- /python/CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every little bit 8 | helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/syrusakbary/fastdiff/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 30 | wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | fastdiff could always use more documentation, whether as part of the 42 | official fastdiff docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | Submit Feedback 46 | ~~~~~~~~~~~~~~~ 47 | 48 | The best way to send feedback is to file an issue at https://github.com/syrusakbary/fastdiff/issues. 49 | 50 | If you are proposing a feature: 51 | 52 | * Explain in detail how it would work. 53 | * Keep the scope as narrow as possible, to make it easier to implement. 54 | * Remember that this is a volunteer-driven project, and that contributions 55 | are welcome :) 56 | 57 | Get Started! 58 | ------------ 59 | 60 | Ready to contribute? Here's how to set up `fastdiff` for local development. 61 | 62 | 1. Fork the `fastdiff` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/fastdiff.git 66 | 67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 68 | 69 | $ mkvirtualenv fastdiff 70 | $ cd fastdiff/ 71 | $ python setup.py develop 72 | 73 | 4. Create a branch for local development:: 74 | 75 | $ git checkout -b name-of-your-bugfix-or-feature 76 | 77 | Now you can make your changes locally. 78 | 79 | 5. When you're done making changes, check that your changes pass flake8 and the 80 | tests, including testing other Python versions with tox:: 81 | 82 | $ flake8 fastdiff tests 83 | $ python setup.py test or py.test 84 | $ tox 85 | 86 | To get flake8 and tox, just pip install them into your virtualenv. 87 | 88 | 6. Commit your changes and push your branch to GitHub:: 89 | 90 | $ git add . 91 | $ git commit -m "Your detailed description of your changes." 92 | $ git push origin name-of-your-bugfix-or-feature 93 | 94 | 7. Submit a pull request through the GitHub website. 95 | 96 | Pull Request Guidelines 97 | ----------------------- 98 | 99 | Before you submit a pull request, check that it meets these guidelines: 100 | 101 | 1. The pull request should include tests. 102 | 2. If the pull request adds functionality, the docs should be updated. Put 103 | your new functionality into a function with a docstring, and add the 104 | feature to the list in README.rst. 105 | 3. The pull request should work for Python 2.7, 3.4, 3.5 and 3.6, and for PyPy. Check 106 | https://travis-ci.org/syrusakbary/fastdiff/pull_requests 107 | and make sure that the tests pass for all supported Python versions. 108 | 109 | Tips 110 | ---- 111 | 112 | To run a subset of tests:: 113 | 114 | $ py.test tests.test_fastdiff 115 | 116 | 117 | Deploying 118 | --------- 119 | 120 | A reminder for the maintainers on how to deploy. 121 | Make sure all your changes are committed (including an entry in HISTORY.rst). 122 | Then run:: 123 | 124 | $ bumpversion patch # possible: major / minor / patch 125 | $ git push 126 | $ git push --tags 127 | 128 | Travis will then deploy to PyPI if tests pass. 129 | -------------------------------------------------------------------------------- /python/HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 0.1.0 (2019-07-04) 6 | ------------------ 7 | 8 | * First release on PyPI. 9 | -------------------------------------------------------------------------------- /python/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019, Syrus Akbary 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /python/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CONTRIBUTING.rst 2 | include HISTORY.rst 3 | include LICENSE 4 | include README.rst 5 | include fastdiff/fastdiff.wasm 6 | 7 | recursive-include tests * 8 | recursive-exclude * __pycache__ 9 | recursive-exclude * *.py[co] 10 | 11 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 12 | -------------------------------------------------------------------------------- /python/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | 7 | try: 8 | from urllib import pathname2url 9 | except: 10 | from urllib.request import pathname2url 11 | 12 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 13 | endef 14 | export BROWSER_PYSCRIPT 15 | 16 | define PRINT_HELP_PYSCRIPT 17 | import re, sys 18 | 19 | for line in sys.stdin: 20 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 21 | if match: 22 | target, help = match.groups() 23 | print("%-20s %s" % (target, help)) 24 | endef 25 | export PRINT_HELP_PYSCRIPT 26 | 27 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 28 | 29 | help: 30 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 31 | 32 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 33 | 34 | clean-build: ## remove build artifacts 35 | rm -fr build/ 36 | rm -fr dist/ 37 | rm -fr .eggs/ 38 | find . -name '*.egg-info' -exec rm -fr {} + 39 | find . -name '*.egg' -exec rm -f {} + 40 | 41 | clean-pyc: ## remove Python file artifacts 42 | find . -name '*.pyc' -exec rm -f {} + 43 | find . -name '*.pyo' -exec rm -f {} + 44 | find . -name '*~' -exec rm -f {} + 45 | find . -name '__pycache__' -exec rm -fr {} + 46 | 47 | clean-test: ## remove test and coverage artifacts 48 | rm -fr .tox/ 49 | rm -f .coverage 50 | rm -fr htmlcov/ 51 | rm -fr .pytest_cache 52 | 53 | lint: ## check style with flake8 54 | flake8 fastdiff tests 55 | 56 | test: ## run tests quickly with the default Python 57 | py.test -s --benchmark-skip 58 | 59 | test-all: ## run tests on every Python version with tox 60 | tox 61 | 62 | coverage: ## check code coverage quickly with the default Python 63 | coverage run --source fastdiff -m pytest 64 | coverage report -m 65 | coverage html 66 | $(BROWSER) htmlcov/index.html 67 | 68 | docs: ## generate Sphinx HTML documentation, including API docs 69 | rm -f docs/fastdiff.rst 70 | rm -f docs/modules.rst 71 | sphinx-apidoc -o docs/ fastdiff 72 | $(MAKE) -C docs clean 73 | $(MAKE) -C docs html 74 | $(BROWSER) docs/_build/html/index.html 75 | 76 | servedocs: docs ## compile the docs watching for changes 77 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 78 | 79 | release: dist ## package and upload a release 80 | twine upload dist/* 81 | 82 | dist: clean ## builds source and wheel package 83 | python setup.py sdist 84 | python setup.py bdist_wheel 85 | ls -l dist 86 | 87 | install: clean ## install the package to the active Python's site-packages 88 | python setup.py install 89 | 90 | upload: dist ## Uploads the package using twine 91 | twine upload dist/* 92 | -------------------------------------------------------------------------------- /python/README.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | fastdiff 3 | ======== 4 | 5 | 6 | .. image:: https://img.shields.io/pypi/v/fastdiff.svg 7 | :target: https://pypi.python.org/pypi/fastdiff 8 | 9 | .. image:: https://img.shields.io/travis/syrusakbary/fastdiff.svg 10 | :target: https://travis-ci.org/syrusakbary/fastdiff 11 | 12 | .. image:: https://readthedocs.org/projects/fastdiff/badge/?version=latest 13 | :target: https://fastdiff.readthedocs.io/en/latest/?badge=latest 14 | :alt: Documentation Status 15 | 16 | 17 | .. image:: https://pyup.io/repos/github/syrusakbary/fastdiff/shield.svg 18 | :target: https://pyup.io/repos/github/syrusakbary/fastdiff/ 19 | :alt: Updates 20 | 21 | 22 | 23 | A fast/native implementation of diff algorithms using WebAssembly and Wasmer_. 24 | 25 | * Free software: MIT license 26 | 27 | 28 | Features 29 | -------- 30 | 31 | * Uses WebAssembly to achieve 75x speedup in the compare algorithm 32 | * Compatible with any platform 33 | 34 | 35 | Credits 36 | ------- 37 | 38 | This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. 39 | 40 | .. _Wasmer: https://pypi.org/project/wasmer/ 41 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 42 | .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage 43 | -------------------------------------------------------------------------------- /python/docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = fastdiff 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /python/docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # fastdiff documentation build configuration file, created by 5 | # sphinx-quickstart on Fri Jun 9 13:47:02 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another 17 | # directory, add these directories to sys.path here. If the directory is 18 | # relative to the documentation root, use os.path.abspath to make it 19 | # absolute, like shown here. 20 | # 21 | import os 22 | import sys 23 | sys.path.insert(0, os.path.abspath('..')) 24 | 25 | import fastdiff 26 | 27 | # -- General configuration --------------------------------------------- 28 | 29 | # If your documentation needs a minimal Sphinx version, state it here. 30 | # 31 | # needs_sphinx = '1.0' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 35 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # The suffix(es) of source filenames. 41 | # You can specify multiple suffix as a list of string: 42 | # 43 | # source_suffix = ['.rst', '.md'] 44 | source_suffix = '.rst' 45 | 46 | # The master toctree document. 47 | master_doc = 'index' 48 | 49 | # General information about the project. 50 | project = u'fastdiff' 51 | copyright = u"2019, Syrus Akbary" 52 | author = u"Syrus Akbary" 53 | 54 | # The version info for the project you're documenting, acts as replacement 55 | # for |version| and |release|, also used in various other places throughout 56 | # the built documents. 57 | # 58 | # The short X.Y version. 59 | version = fastdiff.__version__ 60 | # The full version, including alpha/beta/rc tags. 61 | release = fastdiff.__version__ 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | # 66 | # This is also used if you do content translation via gettext catalogs. 67 | # Usually you set "language" from the command line for these cases. 68 | language = None 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | # This patterns also effect to html_static_path and html_extra_path 73 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 74 | 75 | # The name of the Pygments (syntax highlighting) style to use. 76 | pygments_style = 'sphinx' 77 | 78 | # If true, `todo` and `todoList` produce output, else they produce nothing. 79 | todo_include_todos = False 80 | 81 | 82 | # -- Options for HTML output ------------------------------------------- 83 | 84 | # The theme to use for HTML and HTML Help pages. See the documentation for 85 | # a list of builtin themes. 86 | # 87 | html_theme = 'alabaster' 88 | 89 | # Theme options are theme-specific and customize the look and feel of a 90 | # theme further. For a list of options available for each theme, see the 91 | # documentation. 92 | # 93 | # html_theme_options = {} 94 | 95 | # Add any paths that contain custom static files (such as style sheets) here, 96 | # relative to this directory. They are copied after the builtin static files, 97 | # so a file named "default.css" will overwrite the builtin "default.css". 98 | html_static_path = ['_static'] 99 | 100 | 101 | # -- Options for HTMLHelp output --------------------------------------- 102 | 103 | # Output file base name for HTML help builder. 104 | htmlhelp_basename = 'fastdiffdoc' 105 | 106 | 107 | # -- Options for LaTeX output ------------------------------------------ 108 | 109 | latex_elements = { 110 | # The paper size ('letterpaper' or 'a4paper'). 111 | # 112 | # 'papersize': 'letterpaper', 113 | 114 | # The font size ('10pt', '11pt' or '12pt'). 115 | # 116 | # 'pointsize': '10pt', 117 | 118 | # Additional stuff for the LaTeX preamble. 119 | # 120 | # 'preamble': '', 121 | 122 | # Latex figure (float) alignment 123 | # 124 | # 'figure_align': 'htbp', 125 | } 126 | 127 | # Grouping the document tree into LaTeX files. List of tuples 128 | # (source start file, target name, title, author, documentclass 129 | # [howto, manual, or own class]). 130 | latex_documents = [ 131 | (master_doc, 'fastdiff.tex', 132 | u'fastdiff Documentation', 133 | u'Syrus Akbary', 'manual'), 134 | ] 135 | 136 | 137 | # -- Options for manual page output ------------------------------------ 138 | 139 | # One entry per manual page. List of tuples 140 | # (source start file, name, description, authors, manual section). 141 | man_pages = [ 142 | (master_doc, 'fastdiff', 143 | u'fastdiff Documentation', 144 | [author], 1) 145 | ] 146 | 147 | 148 | # -- Options for Texinfo output ---------------------------------------- 149 | 150 | # Grouping the document tree into Texinfo files. List of tuples 151 | # (source start file, target name, title, author, 152 | # dir menu entry, description, category) 153 | texinfo_documents = [ 154 | (master_doc, 'fastdiff', 155 | u'fastdiff Documentation', 156 | author, 157 | 'fastdiff', 158 | 'One line description of project.', 159 | 'Miscellaneous'), 160 | ] 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /python/docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /python/docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst 2 | -------------------------------------------------------------------------------- /python/docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to fastdiff's documentation! 2 | ====================================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | readme 9 | installation 10 | usage 11 | modules 12 | contributing 13 | history 14 | 15 | Indices and tables 16 | ================== 17 | * :ref:`genindex` 18 | * :ref:`modindex` 19 | * :ref:`search` 20 | -------------------------------------------------------------------------------- /python/docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | 8 | Stable release 9 | -------------- 10 | 11 | To install fastdiff, run this command in your terminal: 12 | 13 | .. code-block:: console 14 | 15 | $ pip install fastdiff 16 | 17 | This is the preferred method to install fastdiff, as it will always install the most recent stable release. 18 | 19 | If you don't have `pip`_ installed, this `Python installation guide`_ can guide 20 | you through the process. 21 | 22 | .. _pip: https://pip.pypa.io 23 | .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ 24 | 25 | 26 | From sources 27 | ------------ 28 | 29 | The sources for fastdiff can be downloaded from the `Github repo`_. 30 | 31 | You can either clone the public repository: 32 | 33 | .. code-block:: console 34 | 35 | $ git clone git://github.com/syrusakbary/fastdiff 36 | 37 | Or download the `tarball`_: 38 | 39 | .. code-block:: console 40 | 41 | $ curl -OL https://github.com/syrusakbary/fastdiff/tarball/master 42 | 43 | Once you have a copy of the source, you can install it with: 44 | 45 | .. code-block:: console 46 | 47 | $ python setup.py install 48 | 49 | 50 | .. _Github repo: https://github.com/syrusakbary/fastdiff 51 | .. _tarball: https://github.com/syrusakbary/fastdiff/tarball/master 52 | -------------------------------------------------------------------------------- /python/docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=fastdiff 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /python/docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /python/docs/usage.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Usage 3 | ===== 4 | 5 | To use fastdiff in a project:: 6 | 7 | import fastdiff 8 | -------------------------------------------------------------------------------- /python/fastdiff/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Top-level package for fastdiff.""" 4 | 5 | from .compare import compare 6 | 7 | __author__ = """Syrus Akbary""" 8 | __email__ = 'me@syrusakbary.com' 9 | __version__ = '0.1.0' 10 | 11 | __all__ = ["compare"] 12 | -------------------------------------------------------------------------------- /python/fastdiff/_base.py: -------------------------------------------------------------------------------- 1 | from difflib import Differ 2 | 3 | def compare(first, second): 4 | differ = Differ() 5 | return list(differ.compare(first.splitlines(), second.splitlines())) 6 | -------------------------------------------------------------------------------- /python/fastdiff/_native.py: -------------------------------------------------------------------------------- 1 | from wasmer import Instance, Store, Module 2 | import os 3 | 4 | __dir__ = os.path.dirname(os.path.realpath(__file__)) 5 | wasm_file_location = os.path.join(__dir__, "fastdiff.wasm") 6 | 7 | 8 | # Instantiates the module. 9 | def initiate_instance(): 10 | 11 | with open(wasm_file_location, 'rb') as fp: 12 | wasm_bytes = fp.read() 13 | 14 | store = Store() 15 | module = Module(store, wasm_bytes) 16 | instance = Instance(module) 17 | return instance 18 | 19 | _instance = None 20 | 21 | 22 | def get_instance(): 23 | global _instance 24 | if _instance is None: 25 | _instance = initiate_instance() 26 | return _instance 27 | 28 | 29 | def allocate_cstr(string, instance): 30 | subject = bytes(string, 'utf-8') 31 | length = len(subject) 32 | pointer = instance.exports.allocate(length) 33 | 34 | # Write the subject into the memory. 35 | memory = instance.exports.memory.uint8_view(pointer) 36 | 37 | for nth in range(0, length): 38 | memory[nth] = subject[nth] 39 | 40 | # C-string terminates by NULL. 41 | memory[length] = 0 42 | 43 | return pointer, length 44 | 45 | 46 | def get_cstr(pointer, instance): 47 | memory = instance.exports.memory.uint8_view(pointer) 48 | memory_length = len(memory) 49 | nth = 0 50 | 51 | while nth < memory_length: 52 | byte = memory[nth] 53 | 54 | if byte == 0: 55 | break 56 | 57 | yield byte 58 | nth += 1 59 | 60 | 61 | def compare(first, second): 62 | instance = get_instance() 63 | 64 | # Allocate memory for the subject, and get a pointer to it. 65 | first_pointer, first_length = allocate_cstr(first, instance) 66 | second_pointer, second_length = allocate_cstr(second, instance) 67 | 68 | output_pointer = instance.exports.compare(first_pointer, second_pointer) 69 | output_bytes = bytes(get_cstr(output_pointer, instance)) 70 | 71 | # Deallocate 72 | deallocate = instance.exports.deallocate 73 | deallocate(first_pointer, first_length) 74 | deallocate(second_pointer, second_length) 75 | deallocate(output_pointer, len(output_bytes)) 76 | 77 | return output_bytes.decode().splitlines() 78 | 79 | 80 | _instance = get_instance() 81 | -------------------------------------------------------------------------------- /python/fastdiff/compare.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Main module.""" 4 | 5 | try: 6 | from ._native import compare 7 | except: 8 | # If any errors happen, fall back to the base approach 9 | from ._base import compare 10 | 11 | __all__ = ["compare"] 12 | -------------------------------------------------------------------------------- /python/fastdiff/fastdiff.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/syrusakbary/fastdiff/5bf48f8b8ae1cb99f45286d86f084b391a97bd19/python/fastdiff/fastdiff.wasm -------------------------------------------------------------------------------- /python/requirements_dev.txt: -------------------------------------------------------------------------------- 1 | pip==18.1 2 | bumpversion==0.5.3 3 | wheel==0.32.1 4 | watchdog==0.9.0 5 | flake8==3.5.0 6 | tox==3.5.2 7 | coverage==4.5.1 8 | Sphinx==1.8.1 9 | twine==1.12.1 10 | 11 | pytest==3.8.2 12 | pytest-runner==4.2 13 | pytest-benchmark==3.2.2 14 | -------------------------------------------------------------------------------- /python/setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.0 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:fastdiff/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [bdist_wheel] 15 | universal = 1 16 | 17 | [flake8] 18 | exclude = docs 19 | 20 | [aliases] 21 | # Define setup.py command aliases here 22 | test = pytest 23 | 24 | [tool:pytest] 25 | collect_ignore = ['setup.py'] 26 | 27 | -------------------------------------------------------------------------------- /python/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """The setup script.""" 5 | 6 | from setuptools import setup, find_packages 7 | 8 | with open('README.rst') as readme_file: 9 | readme = readme_file.read() 10 | 11 | with open('HISTORY.rst') as history_file: 12 | history = history_file.read() 13 | 14 | requirements = [ 15 | 'wasmer>=1.0.0', 16 | 'wasmer-compiler-cranelift>=1.0.0' 17 | ] 18 | 19 | setup_requirements = ['pytest-runner', ] 20 | 21 | test_requirements = ['pytest', ] 22 | 23 | setup( 24 | author="Syrus Akbary", 25 | author_email='me@syrusakbary.com', 26 | classifiers=[ 27 | 'Development Status :: 2 - Pre-Alpha', 28 | 'Intended Audience :: Developers', 29 | 'License :: OSI Approved :: MIT License', 30 | 'Natural Language :: English', 31 | "Programming Language :: Python :: 2", 32 | 'Programming Language :: Python :: 2.7', 33 | 'Programming Language :: Python :: 3', 34 | 'Programming Language :: Python :: 3.4', 35 | 'Programming Language :: Python :: 3.5', 36 | 'Programming Language :: Python :: 3.6', 37 | 'Programming Language :: Python :: 3.7', 38 | ], 39 | description="A fast native implementation of diff algorithm with a pure python fallback", 40 | install_requires=requirements, 41 | license="MIT license", 42 | long_description=readme + '\n\n' + history, 43 | include_package_data=True, 44 | keywords='fastdiff', 45 | name='fastdiff', 46 | packages=find_packages(include=['fastdiff']), 47 | setup_requires=setup_requirements, 48 | test_suite='tests', 49 | tests_require=test_requirements, 50 | url='https://github.com/syrusakbary/fastdiff', 51 | version='0.3.0', 52 | zip_safe=False, 53 | ) 54 | -------------------------------------------------------------------------------- /python/tests/test_fastdiff.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Tests for `fastdiff` package.""" 5 | 6 | import pytest 7 | 8 | 9 | from fastdiff import compare 10 | from fastdiff._base import compare as compare_base 11 | from fastdiff._native import compare as compare_native, initiate_instance 12 | 13 | 14 | def test_content(): 15 | """Sample pytest test function with the pytest fixture as an argument.""" 16 | compared = compare("one\ntwo\nthree", "one\ntwo\nfour") 17 | assert compared == [' one', ' two', '- three', '+ four'] 18 | 19 | def test_base(): 20 | compared = compare_base("one\ntwo\nthree", "one\ntwo\nfour") 21 | assert compared == [' one', ' two', '- three', '+ four'] 22 | 23 | def test_native(): 24 | compared = compare_native("one\ntwo\nthree", "one\ntwo\nfour") 25 | assert compared == [' one', ' two', '- three', '+ four'] 26 | 27 | CONTENTS1 = "one\ntwo\nthree\n"*100 28 | CONTENTS2 = "one\ntwo\nfour\n"*100 29 | 30 | 31 | def test_benchmark_content_base(benchmark): 32 | """Sample pytest test function with the pytest fixture as an argument.""" 33 | def f(): 34 | compare_base(CONTENTS1, CONTENTS2) 35 | benchmark(f) 36 | 37 | 38 | def test_benchmark_content_native(benchmark): 39 | """Sample pytest test function with the pytest fixture as an argument.""" 40 | def f(): 41 | compare_native(CONTENTS1, CONTENTS2) 42 | 43 | benchmark(f) 44 | 45 | 46 | def test_benchmark_compile_native(benchmark): 47 | """Sample pytest test function with the pytest fixture as an argument.""" 48 | def f(): 49 | initiate_instance() 50 | 51 | benchmark(f) 52 | -------------------------------------------------------------------------------- /python/tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py27, py34, py35, py36, flake8 3 | 4 | [travis] 5 | python = 6 | 3.6: py36 7 | 3.5: py35 8 | 3.4: py34 9 | 2.7: py27 10 | 11 | [testenv:flake8] 12 | basepython = python 13 | deps = flake8 14 | commands = flake8 fastdiff 15 | 16 | [testenv] 17 | setenv = 18 | PYTHONPATH = {toxinidir} 19 | deps = 20 | -r{toxinidir}/requirements_dev.txt 21 | ; If you want to make tox run the tests with the same versions, create a 22 | ; requirements.txt with the pinned versions and uncomment the following line: 23 | ; -r{toxinidir}/requirements.txt 24 | commands = 25 | pip install -U pip 26 | py.test --basetemp={envtmpdir} 27 | 28 | 29 | -------------------------------------------------------------------------------- /wasm/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /wasm/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "difflib" 5 | version = "0.4.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "fastdiff" 10 | version = "0.1.2" 11 | dependencies = [ 12 | "difflib 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 13 | ] 14 | 15 | [metadata] 16 | "checksum difflib 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 17 | -------------------------------------------------------------------------------- /wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fastdiff" 3 | version = "0.1.2" 4 | authors = ["Syrus "] 5 | edition = "2018" 6 | 7 | [lib] 8 | crate-type = ["cdylib"] 9 | 10 | [profile.release] 11 | lto = true 12 | opt-level = 's' 13 | 14 | [dependencies] 15 | difflib = "0.4.0" -------------------------------------------------------------------------------- /wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | use difflib::differ::Differ; 2 | use std::{ 3 | ffi::{CStr, CString}, 4 | mem, 5 | os::raw::{c_char, c_void}, 6 | }; 7 | 8 | #[no_mangle] 9 | pub extern "C" fn allocate(size: usize) -> *mut c_void { 10 | let mut buffer = Vec::with_capacity(size); 11 | let pointer = buffer.as_mut_ptr(); 12 | 13 | mem::forget(buffer); 14 | 15 | pointer as *mut c_void 16 | } 17 | 18 | #[no_mangle] 19 | pub extern "C" fn deallocate(ptr: *mut u8, capacity: usize) { 20 | unsafe { 21 | let _ = Vec::from_raw_parts(ptr, 0, capacity); 22 | } 23 | } 24 | 25 | #[no_mangle] 26 | pub extern "C" fn compare(first: *const c_char, second: *const c_char) -> *const c_char { 27 | assert!(!first.is_null()); 28 | assert!(!second.is_null()); 29 | 30 | let first_str = unsafe { CStr::from_ptr(first).to_str().unwrap() }; 31 | let second_str = unsafe { CStr::from_ptr(second).to_str().unwrap() }; 32 | 33 | let first_lines: Vec<&str> = first_str.split("\n").collect::>(); 34 | let second_lines: Vec<&str> = second_str.split("\n").collect::>(); 35 | 36 | // // Differ examples 37 | let differ = Differ::new(); 38 | let diff = differ.compare(&first_lines, &second_lines); 39 | 40 | CString::new(diff.join("\n")).unwrap().into_raw() 41 | } 42 | --------------------------------------------------------------------------------