webdiff 1.0.0__tar.gz → 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {webdiff-1.0.0/webdiff.egg-info → webdiff-1.1.0}/PKG-INFO +38 -35
- {webdiff-1.0.0 → webdiff-1.1.0}/README.md +24 -25
- webdiff-1.1.0/pyproject.toml +47 -0
- webdiff-1.1.0/webdiff/app.py +347 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/argparser.py +16 -9
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/diff.py +9 -7
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/dirdiff.py +6 -5
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/github_fetcher.py +21 -8
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/githubdiff.py +5 -5
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/gitwebdiff.py +6 -8
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/localfilediff.py +1 -1
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/options.py +25 -21
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/codediff.js/codediff.css +35 -10
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/codediff.js/codediff.js +86 -38
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/js/file_diff.js +1 -1
- webdiff-1.0.0/webdiff/templates/base.html → webdiff-1.1.0/webdiff/templates/file_diff.html +18 -3
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/unified_diff.py +20 -8
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/util.py +7 -7
- webdiff-1.0.0/MANIFEST.in +0 -3
- webdiff-1.0.0/PKG-INFO +0 -209
- webdiff-1.0.0/setup.cfg +0 -4
- webdiff-1.0.0/setup.py +0 -46
- webdiff-1.0.0/webdiff/app.py +0 -368
- webdiff-1.0.0/webdiff/templates/file_diff.html +0 -27
- webdiff-1.0.0/webdiff.egg-info/SOURCES.txt +0 -293
- webdiff-1.0.0/webdiff.egg-info/dependency_links.txt +0 -1
- webdiff-1.0.0/webdiff.egg-info/entry_points.txt +0 -3
- webdiff-1.0.0/webdiff.egg-info/requires.txt +0 -7
- webdiff-1.0.0/webdiff.egg-info/top_level.txt +0 -1
- {webdiff-1.0.0 → webdiff-1.1.0}/LICENSE +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/__init__.py +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/__init__.py +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/codediff.js/difflib.js +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/codediff.js/googlecode.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/Inconsolata/Inconsolata-Bold.ttf +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/Inconsolata/Inconsolata-Regular.ttf +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/Inconsolata/OFL.txt +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/googlecode.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/inconsolata.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/style.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/a11y-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/a11y-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/agate.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/an-old-hope.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/androidstudio.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/arduino-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/arta.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/ascetic.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/atom-one-dark-reasonable.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/atom-one-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/atom-one-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/3024.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/apathy.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/apprentice.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/ashes.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-cave-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-cave.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-dune-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-dune.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-estuary-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-estuary.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-forest-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-forest.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-heath-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-heath.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-lakeside-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-lakeside.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-plateau-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-plateau.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-savanna-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-savanna.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-seaside-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-seaside.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-sulphurpool-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atelier-sulphurpool.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/atlas.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/bespin.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/black-metal-bathory.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/black-metal-burzum.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/black-metal-dark-funeral.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/black-metal-gorgoroth.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/black-metal-immortal.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/black-metal-khold.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/black-metal-marduk.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/black-metal-mayhem.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/black-metal-nile.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/black-metal-venom.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/black-metal.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/brewer.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/bright.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/brogrammer.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/brush-trees-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/brush-trees.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/chalk.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/circus.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/classic-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/classic-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/codeschool.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/colors.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/cupcake.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/cupertino.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/danqing.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/darcula.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/dark-violet.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/darkmoss.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/darktooth.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/decaf.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/default-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/default-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/dirtysea.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/dracula.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/edge-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/edge-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/eighties.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/embers.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/equilibrium-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/equilibrium-gray-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/equilibrium-gray-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/equilibrium-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/espresso.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/eva-dim.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/eva.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/flat.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/framer.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/fruit-soda.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/gigavolt.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/github.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/google-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/google-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/grayscale-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/grayscale-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/green-screen.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/gruvbox-dark-hard.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/gruvbox-dark-medium.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/gruvbox-dark-pale.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/gruvbox-dark-soft.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/gruvbox-light-hard.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/gruvbox-light-medium.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/gruvbox-light-soft.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/hardcore.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/harmonic16-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/harmonic16-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/heetch-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/heetch-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/helios.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/hopscotch.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/horizon-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/horizon-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/humanoid-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/humanoid-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/ia-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/ia-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/icy-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/ir-black.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/isotope.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/kimber.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/london-tube.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/macintosh.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/marrakesh.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/materia.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/material-darker.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/material-lighter.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/material-palenight.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/material-vivid.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/material.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/mellow-purple.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/mexico-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/mocha.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/monokai.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/nebula.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/nord.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/nova.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/ocean.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/oceanicnext.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/one-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/onedark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/outrun-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/papercolor-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/papercolor-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/paraiso.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/pasque.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/phd.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/pico.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/pop.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/porple.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/qualia.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/railscasts.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/rebecca.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/ros-pine-dawn.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/ros-pine-moon.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/ros-pine.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/sagelight.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/sandcastle.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/seti-ui.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/shapeshifter.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/silk-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/silk-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/snazzy.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/solar-flare-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/solar-flare.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/solarized-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/solarized-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/spacemacs.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/summercamp.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/summerfruit-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/summerfruit-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/synth-midnight-terminal-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/synth-midnight-terminal-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/tango.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/tender.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/tomorrow-night.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/tomorrow.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/twilight.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/unikitty-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/unikitty-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/vulcan.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/windows-10-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/windows-10.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/windows-95-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/windows-95.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/windows-high-contrast-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/windows-high-contrast.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/windows-nt-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/windows-nt.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/woodland.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/xcode-dusk.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/base16/zenburn.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/brown-paper.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/brown-papersq.png +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/codepen-embed.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/color-brewer.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/default.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/devibeans.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/docco.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/far.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/felipec.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/foundation.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/github-dark-dimmed.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/github-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/github.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/gml.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/googlecode.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/gradient-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/gradient-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/grayscale.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/hybrid.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/idea.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/intellij-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/ir-black.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/isbl-editor-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/isbl-editor-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/kimbie-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/kimbie-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/lightfair.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/lioshi.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/magula.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/mono-blue.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/monokai-sublime.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/monokai.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/night-owl.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/nnfx-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/nnfx-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/nord.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/obsidian.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/panda-syntax-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/panda-syntax-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/paraiso-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/paraiso-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/pojoaque.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/pojoaque.jpg +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/purebasic.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/qtcreator-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/qtcreator-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/rainbow.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/routeros.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/school-book.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/shades-of-purple.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/srcery.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/stackoverflow-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/stackoverflow-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/sunburst.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/tokyo-night-dark.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/tokyo-night-light.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/tomorrow-night-blue.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/tomorrow-night-bright.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/vs.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/vs2015.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/xcode.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/css/themes/xt256.css +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/img/favicon.ico +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/img/sprites.png +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/img/trans_bg.gif +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/js/highlight.min.js +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/js/highlight.pack.min.js +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/static/js/jquery-2.1.1.min.js +0 -0
- {webdiff-1.0.0 → webdiff-1.1.0}/webdiff/templates/__init__.py +0 -0
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: webdiff
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: Two-column web-based git difftool
|
|
5
5
|
Home-page: https://github.com/danvk/webdiff/
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Keywords: git,difftool,web
|
|
6
8
|
Author: Dan Vanderkam
|
|
7
9
|
Author-email: danvdk@gmail.com
|
|
10
|
+
Requires-Python: >=3.10,<4.0
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
12
|
Classifier: Environment :: Console
|
|
9
13
|
Classifier: Environment :: Web Environment
|
|
10
|
-
Classifier: Framework :: Flask
|
|
11
|
-
Classifier: Development Status :: 4 - Beta
|
|
12
14
|
Classifier: Intended Audience :: Developers
|
|
13
15
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
20
|
Classifier: Topic :: Software Development :: Version Control
|
|
15
|
-
|
|
16
|
-
License-File: LICENSE
|
|
21
|
+
Requires-Dist: PyGithub (==2.3.0)
|
|
17
22
|
Requires-Dist: binaryornot
|
|
18
|
-
Requires-Dist: flask==2.2.2
|
|
19
23
|
Requires-Dist: pillow
|
|
20
|
-
Requires-Dist:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
Requires-Dist: Werkzeug==2.2.2
|
|
24
|
+
Requires-Dist: unidiff (==0.7.4)
|
|
25
|
+
Project-URL: Repository, https://github.com/danvk/webdiff/
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
24
27
|
|
|
25
28
|
# git webdiff
|
|
26
29
|
|
|
@@ -41,11 +44,12 @@ Features include:
|
|
|
41
44
|
* Step back and forth through multiple files in a single diff
|
|
42
45
|
* Rich support for image diffs
|
|
43
46
|
|
|
47
|
+
<!-- These are absolute URLs so that they display on pypi.org -->
|
|
44
48
|
<!-- This is `git webdiff 05157bba^..05157bba`, in this repo -->
|
|
45
|
-

|
|
49
|
+

|
|
46
50
|
|
|
47
51
|
<!-- This is `git webdiff c80f969^..c80f969` in the dygraphs-dpxdt repo -->
|
|
48
|
-

|
|
52
|
+

|
|
49
53
|
|
|
50
54
|
## Installation
|
|
51
55
|
|
|
@@ -78,7 +82,7 @@ You can pass all the same arguments that you would to `git diff`, e.g.
|
|
|
78
82
|
You can also use `webdiff` to view GitHub pull requests:
|
|
79
83
|
|
|
80
84
|
webdiff https://github.com/owner/repo/pull/123
|
|
81
|
-
webdiff '#
|
|
85
|
+
webdiff '#150' # if you're in a git repo with a github remote
|
|
82
86
|
|
|
83
87
|
This will download the files relevant to the Pull Request and run `webdiff`.
|
|
84
88
|
|
|
@@ -122,9 +126,7 @@ Options are:
|
|
|
122
126
|
|
|
123
127
|
## Development
|
|
124
128
|
|
|
125
|
-
|
|
126
|
-
source venv/bin/activate
|
|
127
|
-
pip install -r requirements.txt
|
|
129
|
+
poetry install
|
|
128
130
|
cd ts
|
|
129
131
|
yarn
|
|
130
132
|
# see https://github.com/webpack/webpack/issues/14532
|
|
@@ -132,7 +134,7 @@ Options are:
|
|
|
132
134
|
|
|
133
135
|
Then from the root directory:
|
|
134
136
|
|
|
135
|
-
|
|
137
|
+
poetry run webdiff/app.py testdata/dygraphsjs/{left,right}
|
|
136
138
|
|
|
137
139
|
or to launch in debug mode:
|
|
138
140
|
|
|
@@ -142,43 +144,42 @@ or to launch in debug mode:
|
|
|
142
144
|
|
|
143
145
|
To run the Python tests:
|
|
144
146
|
|
|
145
|
-
pytest
|
|
147
|
+
poetry run pytest
|
|
146
148
|
|
|
147
149
|
To format the code, run:
|
|
148
150
|
|
|
149
|
-
|
|
151
|
+
poetry run ruff format
|
|
150
152
|
cd ts
|
|
151
153
|
yarn prettier
|
|
152
154
|
|
|
153
155
|
To debug `git webdiff`, run:
|
|
154
156
|
|
|
155
|
-
|
|
157
|
+
./test-gitwebdiff.sh
|
|
158
|
+
|
|
159
|
+
## Publishing
|
|
156
160
|
|
|
157
161
|
To iterate on the PyPI package, run:
|
|
158
162
|
|
|
159
|
-
# from outside the webdiff virtualenv:
|
|
160
163
|
pip3 uninstall webdiff
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
python setup.py sdist
|
|
164
|
-
mkdir /tmp/webdiff-test
|
|
165
|
-
cp dist/webdiff-?.?.?.tar.gz /tmp/webdiff-test
|
|
166
|
-
|
|
167
|
-
deactivate
|
|
168
|
-
cd /tmp/webdiff-test
|
|
169
|
-
pip3 install webdiff-?.?.?.tar.gz
|
|
164
|
+
poetry build
|
|
165
|
+
pip3 install dist/webdiff-?.?.?.tar.gz
|
|
170
166
|
|
|
171
167
|
To publish to pypitest:
|
|
172
168
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
twine upload -r testpypi dist/*
|
|
169
|
+
poetry build
|
|
170
|
+
poetry publish -r testpypi
|
|
176
171
|
|
|
177
172
|
And to the real pypi:
|
|
178
173
|
|
|
179
|
-
|
|
174
|
+
poetry publish
|
|
175
|
+
|
|
176
|
+
See [pypirc][] and [poetry][] docs for details on setting up tokens for pypi.
|
|
180
177
|
|
|
181
|
-
|
|
178
|
+
Publication checklist. Do these from _outside_ the webdiff directory:
|
|
179
|
+
|
|
180
|
+
- Run `webdiff webdiff/testdata/.../{left,right}`
|
|
181
|
+
- Run `git webdiff 52aa15f^..52aa15f` in the codediff.js repo
|
|
182
|
+
- Run `webdiff https://github.com/danvk/webdiff/pull/160`
|
|
182
183
|
|
|
183
184
|
## Implementation notes
|
|
184
185
|
|
|
@@ -207,3 +208,5 @@ There's one complication involving symlinks. `git difftool -d` may fill one of t
|
|
|
207
208
|
[ImageMagick]: https://imagemagick.org/index.php
|
|
208
209
|
[git config]: https://git-scm.com/docs/git-config
|
|
209
210
|
[themes]: http://example.com
|
|
211
|
+
[poetry]: https://python-poetry.org/docs/repositories/#publishable-repositories
|
|
212
|
+
|
|
@@ -17,11 +17,12 @@ Features include:
|
|
|
17
17
|
* Step back and forth through multiple files in a single diff
|
|
18
18
|
* Rich support for image diffs
|
|
19
19
|
|
|
20
|
+
<!-- These are absolute URLs so that they display on pypi.org -->
|
|
20
21
|
<!-- This is `git webdiff 05157bba^..05157bba`, in this repo -->
|
|
21
|
-

|
|
22
|
+

|
|
22
23
|
|
|
23
24
|
<!-- This is `git webdiff c80f969^..c80f969` in the dygraphs-dpxdt repo -->
|
|
24
|
-

|
|
25
|
+

|
|
25
26
|
|
|
26
27
|
## Installation
|
|
27
28
|
|
|
@@ -54,7 +55,7 @@ You can pass all the same arguments that you would to `git diff`, e.g.
|
|
|
54
55
|
You can also use `webdiff` to view GitHub pull requests:
|
|
55
56
|
|
|
56
57
|
webdiff https://github.com/owner/repo/pull/123
|
|
57
|
-
webdiff '#
|
|
58
|
+
webdiff '#150' # if you're in a git repo with a github remote
|
|
58
59
|
|
|
59
60
|
This will download the files relevant to the Pull Request and run `webdiff`.
|
|
60
61
|
|
|
@@ -98,9 +99,7 @@ Options are:
|
|
|
98
99
|
|
|
99
100
|
## Development
|
|
100
101
|
|
|
101
|
-
|
|
102
|
-
source venv/bin/activate
|
|
103
|
-
pip install -r requirements.txt
|
|
102
|
+
poetry install
|
|
104
103
|
cd ts
|
|
105
104
|
yarn
|
|
106
105
|
# see https://github.com/webpack/webpack/issues/14532
|
|
@@ -108,7 +107,7 @@ Options are:
|
|
|
108
107
|
|
|
109
108
|
Then from the root directory:
|
|
110
109
|
|
|
111
|
-
|
|
110
|
+
poetry run webdiff/app.py testdata/dygraphsjs/{left,right}
|
|
112
111
|
|
|
113
112
|
or to launch in debug mode:
|
|
114
113
|
|
|
@@ -118,43 +117,42 @@ or to launch in debug mode:
|
|
|
118
117
|
|
|
119
118
|
To run the Python tests:
|
|
120
119
|
|
|
121
|
-
pytest
|
|
120
|
+
poetry run pytest
|
|
122
121
|
|
|
123
122
|
To format the code, run:
|
|
124
123
|
|
|
125
|
-
|
|
124
|
+
poetry run ruff format
|
|
126
125
|
cd ts
|
|
127
126
|
yarn prettier
|
|
128
127
|
|
|
129
128
|
To debug `git webdiff`, run:
|
|
130
129
|
|
|
131
|
-
|
|
130
|
+
./test-gitwebdiff.sh
|
|
131
|
+
|
|
132
|
+
## Publishing
|
|
132
133
|
|
|
133
134
|
To iterate on the PyPI package, run:
|
|
134
135
|
|
|
135
|
-
# from outside the webdiff virtualenv:
|
|
136
136
|
pip3 uninstall webdiff
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
python setup.py sdist
|
|
140
|
-
mkdir /tmp/webdiff-test
|
|
141
|
-
cp dist/webdiff-?.?.?.tar.gz /tmp/webdiff-test
|
|
142
|
-
|
|
143
|
-
deactivate
|
|
144
|
-
cd /tmp/webdiff-test
|
|
145
|
-
pip3 install webdiff-?.?.?.tar.gz
|
|
137
|
+
poetry build
|
|
138
|
+
pip3 install dist/webdiff-?.?.?.tar.gz
|
|
146
139
|
|
|
147
140
|
To publish to pypitest:
|
|
148
141
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
twine upload -r testpypi dist/*
|
|
142
|
+
poetry build
|
|
143
|
+
poetry publish -r testpypi
|
|
152
144
|
|
|
153
145
|
And to the real pypi:
|
|
154
146
|
|
|
155
|
-
|
|
147
|
+
poetry publish
|
|
148
|
+
|
|
149
|
+
See [pypirc][] and [poetry][] docs for details on setting up tokens for pypi.
|
|
150
|
+
|
|
151
|
+
Publication checklist. Do these from _outside_ the webdiff directory:
|
|
156
152
|
|
|
157
|
-
|
|
153
|
+
- Run `webdiff webdiff/testdata/.../{left,right}`
|
|
154
|
+
- Run `git webdiff 52aa15f^..52aa15f` in the codediff.js repo
|
|
155
|
+
- Run `webdiff https://github.com/danvk/webdiff/pull/160`
|
|
158
156
|
|
|
159
157
|
## Implementation notes
|
|
160
158
|
|
|
@@ -183,3 +181,4 @@ There's one complication involving symlinks. `git difftool -d` may fill one of t
|
|
|
183
181
|
[ImageMagick]: https://imagemagick.org/index.php
|
|
184
182
|
[git config]: https://git-scm.com/docs/git-config
|
|
185
183
|
[themes]: http://example.com
|
|
184
|
+
[poetry]: https://python-poetry.org/docs/repositories/#publishable-repositories
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["poetry-core"]
|
|
3
|
+
build-backend = "poetry.core.masonry.api"
|
|
4
|
+
|
|
5
|
+
[tool.poetry]
|
|
6
|
+
name = "webdiff"
|
|
7
|
+
version = "1.1.0"
|
|
8
|
+
description = "Two-column web-based git difftool"
|
|
9
|
+
license = "Apache-2.0"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
authors = [ "Dan Vanderkam <danvdk@gmail.com>" ]
|
|
12
|
+
homepage = "https://github.com/danvk/webdiff/"
|
|
13
|
+
repository = "https://github.com/danvk/webdiff/"
|
|
14
|
+
keywords = [ "git", "difftool", "web" ]
|
|
15
|
+
exclude = ["tests*"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Environment :: Web Environment",
|
|
19
|
+
"Development Status :: 4 - Beta",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"License :: OSI Approved :: Apache Software License",
|
|
22
|
+
"Topic :: Software Development :: Version Control"
|
|
23
|
+
]
|
|
24
|
+
# This is generated, so it's in .gitignore. But we need to package it.
|
|
25
|
+
include = ["webdiff/static/js/file_diff.js"]
|
|
26
|
+
|
|
27
|
+
[tool.poetry.dependencies]
|
|
28
|
+
python = "^3.10"
|
|
29
|
+
binaryornot = "*"
|
|
30
|
+
pillow = "*"
|
|
31
|
+
PyGithub = "2.3.0"
|
|
32
|
+
unidiff = "==0.7.4"
|
|
33
|
+
|
|
34
|
+
[tool.poetry.group.dev.dependencies]
|
|
35
|
+
pytest = "^7.1.3"
|
|
36
|
+
ruff = "^0.4.6"
|
|
37
|
+
|
|
38
|
+
[tool.poetry.scripts]
|
|
39
|
+
webdiff = "webdiff.app:run"
|
|
40
|
+
git-webdiff = "webdiff.gitwebdiff:run"
|
|
41
|
+
|
|
42
|
+
[tool.ruff]
|
|
43
|
+
line-length = 88
|
|
44
|
+
|
|
45
|
+
[tool.ruff.format]
|
|
46
|
+
quote-style = "single"
|
|
47
|
+
docstring-code-format = true
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""Web-based file differ.
|
|
3
|
+
|
|
4
|
+
For usage, see README.md.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import dataclasses
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import importlib.metadata
|
|
11
|
+
import mimetypes
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
import platform
|
|
15
|
+
import socket
|
|
16
|
+
import sys
|
|
17
|
+
import threading
|
|
18
|
+
import time
|
|
19
|
+
import webbrowser
|
|
20
|
+
from binaryornot.check import is_binary
|
|
21
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
22
|
+
from urllib.parse import urlparse, parse_qs
|
|
23
|
+
|
|
24
|
+
from webdiff import diff, util, argparser, options
|
|
25
|
+
|
|
26
|
+
VERSION = importlib.metadata.version('webdiff')
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def determine_path():
|
|
30
|
+
"""Borrowed from wxglade.py"""
|
|
31
|
+
try:
|
|
32
|
+
root = __file__
|
|
33
|
+
if os.path.islink(root):
|
|
34
|
+
root = os.path.realpath(root)
|
|
35
|
+
return os.path.dirname(os.path.abspath(root))
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print(f"I'm sorry, but something is wrong. Error: {e}")
|
|
38
|
+
print('There is no __file__ variable. Please contact the author.')
|
|
39
|
+
sys.exit()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
GIT_CONFIG = {}
|
|
43
|
+
DIFF = None
|
|
44
|
+
PORT = None
|
|
45
|
+
HOSTNAME = 'localhost'
|
|
46
|
+
DEBUG = os.environ.get('DEBUG')
|
|
47
|
+
WEBDIFF_DIR = determine_path()
|
|
48
|
+
|
|
49
|
+
if DEBUG:
|
|
50
|
+
handler = logging.StreamHandler()
|
|
51
|
+
handler.setLevel(logging.DEBUG)
|
|
52
|
+
formatter = logging.Formatter(
|
|
53
|
+
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
54
|
+
)
|
|
55
|
+
handler.setFormatter(formatter)
|
|
56
|
+
|
|
57
|
+
for logname in ['']:
|
|
58
|
+
log = logging.getLogger(logname)
|
|
59
|
+
log.setLevel(logging.DEBUG)
|
|
60
|
+
log.addHandler(handler)
|
|
61
|
+
logging.getLogger('github').setLevel(logging.ERROR)
|
|
62
|
+
|
|
63
|
+
# GPT's alternative:
|
|
64
|
+
# logging.basicConfig(level=logging.DEBUG if 'DEBUG' in os.environ else logging.INFO)
|
|
65
|
+
# logger = logging.getLogger(__name__)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class CustomHTTPRequestHandler(BaseHTTPRequestHandler):
|
|
69
|
+
def do_GET(self):
|
|
70
|
+
note_request_time()
|
|
71
|
+
path = urlparse(self.path).path.removesuffix('/') or '/'
|
|
72
|
+
|
|
73
|
+
if path == '/':
|
|
74
|
+
self.handle_index(0)
|
|
75
|
+
elif m := re.match(r'/(?P<idx>\d+)$', path):
|
|
76
|
+
self.handle_index(int(m['idx']))
|
|
77
|
+
elif path == '/favicon.ico':
|
|
78
|
+
self.handle_favicon()
|
|
79
|
+
elif path == '/theme.css':
|
|
80
|
+
self.handle_theme()
|
|
81
|
+
elif path.startswith('/static/'):
|
|
82
|
+
self.handle_static(path[1:])
|
|
83
|
+
elif m := re.match(r'/thick/(?P<idx>\d+)', path):
|
|
84
|
+
self.handle_thick(int(m['idx']))
|
|
85
|
+
elif m := re.match(r'/(?P<side>a|b)/image/(?P<path>.*)', path):
|
|
86
|
+
self.handle_image(m['side'], m['path'])
|
|
87
|
+
elif m := re.match(r'/pdiff/(?P<idx>\d+)', path):
|
|
88
|
+
self.handle_pdiff(int(m['idx']))
|
|
89
|
+
elif m := re.match(r'/pdiffbbox/(?P<idx>\d+)', path):
|
|
90
|
+
self.handle_pdiff_bbox(int(m['idx']))
|
|
91
|
+
else:
|
|
92
|
+
self.send_error(404, 'File not found')
|
|
93
|
+
|
|
94
|
+
def do_POST(self):
|
|
95
|
+
note_request_time()
|
|
96
|
+
path = urlparse(self.path).path.removesuffix('/') or '/'
|
|
97
|
+
|
|
98
|
+
content_length = int(self.headers['Content-Length'])
|
|
99
|
+
post_data = self.rfile.read(content_length)
|
|
100
|
+
|
|
101
|
+
if m := re.match(r'/(?P<side>a|b)/get_contents', path):
|
|
102
|
+
form_data = parse_qs(post_data.decode('utf-8'))
|
|
103
|
+
self.handle_get_contents(m['side'], form_data)
|
|
104
|
+
elif m := re.match(r'/diff/(?P<idx>\d+)', path):
|
|
105
|
+
payload = json.loads(post_data.decode('utf-8'))
|
|
106
|
+
self.handle_diff_ops(int(m['idx']), payload)
|
|
107
|
+
elif path == '/kill':
|
|
108
|
+
self.handle_kill()
|
|
109
|
+
else:
|
|
110
|
+
self.send_error(404, 'File not found')
|
|
111
|
+
|
|
112
|
+
def handle_index(self, idx: int):
|
|
113
|
+
pairs = diff.get_thin_list(DIFF)
|
|
114
|
+
self.send_response(200)
|
|
115
|
+
self.send_header('Content-Type', 'text/html; charset=utf-8')
|
|
116
|
+
self.end_headers()
|
|
117
|
+
with open(os.path.join(WEBDIFF_DIR, 'templates/file_diff.html'), 'r') as file:
|
|
118
|
+
html = file.read()
|
|
119
|
+
html = html.replace(
|
|
120
|
+
'{{data}}',
|
|
121
|
+
json.dumps(
|
|
122
|
+
{
|
|
123
|
+
'idx': idx,
|
|
124
|
+
'has_magick': util.is_imagemagick_available(),
|
|
125
|
+
'pairs': pairs,
|
|
126
|
+
'git_config': GIT_CONFIG,
|
|
127
|
+
},
|
|
128
|
+
indent=2,
|
|
129
|
+
),
|
|
130
|
+
)
|
|
131
|
+
self.wfile.write(html.encode('utf-8'))
|
|
132
|
+
|
|
133
|
+
def handle_thick(self, idx: int):
|
|
134
|
+
self.send_response_with_json(200, diff.get_thick_dict(DIFF[idx]))
|
|
135
|
+
|
|
136
|
+
def handle_favicon(self):
|
|
137
|
+
self.serve_static_file('static/img/favicon.ico', 'image/vnd.microsoft.icon')
|
|
138
|
+
|
|
139
|
+
def handle_theme(self):
|
|
140
|
+
theme = GIT_CONFIG['webdiff']['theme']
|
|
141
|
+
theme_dir = os.path.dirname(theme)
|
|
142
|
+
theme_file = os.path.basename(theme)
|
|
143
|
+
theme_path = os.path.join('static/css/themes', theme_dir, theme_file + '.css')
|
|
144
|
+
self.serve_static_file(theme_path, 'text/css')
|
|
145
|
+
|
|
146
|
+
def handle_static(self, path: str):
|
|
147
|
+
mime_type, _ = mimetypes.guess_type(path)
|
|
148
|
+
self.serve_static_file(path, mime_type)
|
|
149
|
+
|
|
150
|
+
def handle_image(self, side, path):
|
|
151
|
+
mime_type, _ = mimetypes.guess_type(path)
|
|
152
|
+
if not mime_type or not mime_type.startswith('image/'):
|
|
153
|
+
return self.send_response_with_json(400, {'error': 'wrong type'})
|
|
154
|
+
|
|
155
|
+
idx = diff.find_diff_index(DIFF, side, path)
|
|
156
|
+
if idx is None:
|
|
157
|
+
return self.send_response_with_json(400, {'error': 'not found'})
|
|
158
|
+
|
|
159
|
+
d = DIFF[idx]
|
|
160
|
+
abs_path = d.a_path if side == 'a' else d.b_path
|
|
161
|
+
self.serve_file(abs_path, mime_type)
|
|
162
|
+
|
|
163
|
+
def handle_pdiff(self, idx):
|
|
164
|
+
d = DIFF[idx]
|
|
165
|
+
try:
|
|
166
|
+
_, pdiff_image = util.generate_pdiff_image(d.a_path, d.b_path)
|
|
167
|
+
dilated_image_path = util.generate_dilated_pdiff_image(pdiff_image)
|
|
168
|
+
self.serve_static_file(dilated_image_path, 'image/png')
|
|
169
|
+
except util.ImageMagickNotAvailableError:
|
|
170
|
+
self.send_error(501, 'ImageMagick is not available')
|
|
171
|
+
except util.ImageMagickError as e:
|
|
172
|
+
self.send_error(501, f'ImageMagick error {e}')
|
|
173
|
+
|
|
174
|
+
def handle_pdiff_bbox(self, idx):
|
|
175
|
+
d = DIFF[idx]
|
|
176
|
+
try:
|
|
177
|
+
_, pdiff_image = util.generate_pdiff_image(d.a_path, d.b_path)
|
|
178
|
+
bbox = util.get_pdiff_bbox(pdiff_image)
|
|
179
|
+
self.send_response_with_json(200, bbox)
|
|
180
|
+
except util.ImageMagickNotAvailableError:
|
|
181
|
+
self.send_error(501, 'ImageMagick is not available')
|
|
182
|
+
except util.ImageMagickError as e:
|
|
183
|
+
self.send_error(501, f'ImageMagick error {e}')
|
|
184
|
+
|
|
185
|
+
def handle_get_contents(self, side: str, form_data: dict[str, list[str]]):
|
|
186
|
+
path = form_data.get('path', [''])[0]
|
|
187
|
+
if not path:
|
|
188
|
+
return self.send_response_with_json(400, {'error': 'incomplete'})
|
|
189
|
+
|
|
190
|
+
idx = diff.find_diff_index(DIFF, side, path)
|
|
191
|
+
if idx is None:
|
|
192
|
+
return self.send_response_with_json(400, {'error': 'not found'})
|
|
193
|
+
|
|
194
|
+
d = DIFF[idx]
|
|
195
|
+
abs_path = d.a_path if side == 'a' else d.b_path
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
if is_binary(abs_path):
|
|
199
|
+
size = os.path.getsize(abs_path)
|
|
200
|
+
contents = f'Binary file ({size} bytes)'
|
|
201
|
+
else:
|
|
202
|
+
with open(abs_path, 'r') as file:
|
|
203
|
+
contents = file.read()
|
|
204
|
+
self.send_response(200)
|
|
205
|
+
self.send_header('Content-Type', 'text/plain')
|
|
206
|
+
self.end_headers()
|
|
207
|
+
self.wfile.write(contents.encode('utf-8'))
|
|
208
|
+
except Exception as e:
|
|
209
|
+
self.send_response_with_json(500, {'error': str(e)})
|
|
210
|
+
|
|
211
|
+
def handle_diff_ops(self, idx: int, payload):
|
|
212
|
+
options = payload.get('options') or []
|
|
213
|
+
extra_args = GIT_CONFIG['webdiff']['extraFileDiffArgs']
|
|
214
|
+
if extra_args:
|
|
215
|
+
options += extra_args.split(' ')
|
|
216
|
+
diff_ops = [
|
|
217
|
+
dataclasses.asdict(op) for op in diff.get_diff_ops(DIFF[idx], options)
|
|
218
|
+
]
|
|
219
|
+
self.send_response_with_json(200, diff_ops)
|
|
220
|
+
|
|
221
|
+
def handle_kill(self):
|
|
222
|
+
last_ms = LAST_REQUEST_MS
|
|
223
|
+
|
|
224
|
+
def shutdown():
|
|
225
|
+
if LAST_REQUEST_MS <= last_ms: # subsequent requests abort shutdown
|
|
226
|
+
# See https://stackoverflow.com/a/19040484/388951
|
|
227
|
+
# and https://stackoverflow.com/q/4330111/388951
|
|
228
|
+
sys.stderr.write('Shutting down...\n')
|
|
229
|
+
threading.Thread(target=self.server.shutdown, daemon=True).start()
|
|
230
|
+
else:
|
|
231
|
+
logging.debug('Received subsequent request; canceling shutdown')
|
|
232
|
+
|
|
233
|
+
logging.debug('Received request to shut down; waiting 500ms for subsequent requests...')
|
|
234
|
+
threading.Timer(0.5, shutdown).start()
|
|
235
|
+
self.send_response(200)
|
|
236
|
+
self.send_header('Content-Type', 'text/plain')
|
|
237
|
+
self.end_headers()
|
|
238
|
+
self.wfile.write(b'Shutting down...')
|
|
239
|
+
|
|
240
|
+
def send_response_with_json(self, code, payload):
|
|
241
|
+
self.send_response(code)
|
|
242
|
+
self.send_header('Content-Type', 'application/json')
|
|
243
|
+
self.end_headers()
|
|
244
|
+
self.wfile.write(json.dumps(payload).encode('utf-8'))
|
|
245
|
+
|
|
246
|
+
def serve_static_file(self, file_path, mime_type):
|
|
247
|
+
self.serve_file(os.path.join(WEBDIFF_DIR, file_path), mime_type)
|
|
248
|
+
|
|
249
|
+
def serve_file(self, file_path, mime_type):
|
|
250
|
+
try:
|
|
251
|
+
with open(file_path, 'rb') as file:
|
|
252
|
+
contents = file.read()
|
|
253
|
+
self.send_response(200)
|
|
254
|
+
self.send_header('Content-Type', mime_type)
|
|
255
|
+
self.end_headers()
|
|
256
|
+
self.wfile.write(contents)
|
|
257
|
+
except Exception as e:
|
|
258
|
+
self.send_response_with_json(500, {'error': str(e)})
|
|
259
|
+
|
|
260
|
+
def log_request(self, *args):
|
|
261
|
+
if DEBUG:
|
|
262
|
+
super().log_request(*args)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def note_request_time():
|
|
266
|
+
global LAST_REQUEST_MS
|
|
267
|
+
LAST_REQUEST_MS = time.time() * 1000
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def open_browser():
|
|
271
|
+
global PORT
|
|
272
|
+
global HOSTNAME
|
|
273
|
+
global GIT_CONFIG
|
|
274
|
+
if not os.environ.get('WEBDIFF_NO_OPEN') and GIT_CONFIG['webdiff']['openBrowser']:
|
|
275
|
+
webbrowser.open_new_tab('http://%s:%s' % (HOSTNAME, PORT))
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def usage_and_die():
|
|
279
|
+
sys.stderr.write(argparser.USAGE)
|
|
280
|
+
sys.exit(1)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def pick_a_port(args, webdiff_config):
|
|
284
|
+
if 'port' in args:
|
|
285
|
+
return args['port']
|
|
286
|
+
|
|
287
|
+
env_port = os.environ.get('WEBDIFF_PORT')
|
|
288
|
+
if env_port:
|
|
289
|
+
return int(env_port)
|
|
290
|
+
|
|
291
|
+
# gitconfig
|
|
292
|
+
if webdiff_config['port'] != -1:
|
|
293
|
+
return webdiff_config['port']
|
|
294
|
+
|
|
295
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
296
|
+
sock.bind(('localhost', 0))
|
|
297
|
+
port = sock.getsockname()[1]
|
|
298
|
+
sock.close()
|
|
299
|
+
return port
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def run():
|
|
303
|
+
global DIFF, PORT, HOSTNAME, GIT_CONFIG
|
|
304
|
+
try:
|
|
305
|
+
parsed_args = argparser.parse(sys.argv[1:], VERSION)
|
|
306
|
+
except argparser.UsageError as e:
|
|
307
|
+
sys.stderr.write('Error: %s\n\n' % e)
|
|
308
|
+
usage_and_die()
|
|
309
|
+
|
|
310
|
+
GIT_CONFIG = options.get_config()
|
|
311
|
+
WEBDIFF_CONFIG = GIT_CONFIG['webdiff']
|
|
312
|
+
DIFF = argparser.diff_for_args(parsed_args, WEBDIFF_CONFIG)
|
|
313
|
+
|
|
314
|
+
if DEBUG:
|
|
315
|
+
sys.stderr.write('Invoked as: %s\n' % sys.argv)
|
|
316
|
+
sys.stderr.write('Args: %s\n' % parsed_args)
|
|
317
|
+
sys.stderr.write('Diff: %s\n' % DIFF)
|
|
318
|
+
sys.stderr.write('GitConfig: %s\n' % GIT_CONFIG)
|
|
319
|
+
|
|
320
|
+
PORT = pick_a_port(parsed_args, WEBDIFF_CONFIG)
|
|
321
|
+
HOSTNAME = (
|
|
322
|
+
parsed_args.get('host')
|
|
323
|
+
or os.environ.get('WEBDIFF_HOST')
|
|
324
|
+
or WEBDIFF_CONFIG['host']
|
|
325
|
+
)
|
|
326
|
+
if HOSTNAME == '<hostname>':
|
|
327
|
+
_hostname = platform.node()
|
|
328
|
+
# platform.node will return empty string if it can't find the hostname
|
|
329
|
+
if not _hostname:
|
|
330
|
+
sys.stderr.write('Warning: hostname could not be determined\n')
|
|
331
|
+
else:
|
|
332
|
+
HOSTNAME = _hostname
|
|
333
|
+
|
|
334
|
+
sys.stderr.write(
|
|
335
|
+
"""Serving diffs on http://%s:%s
|
|
336
|
+
Close the browser tab or hit Ctrl-C when you're done.
|
|
337
|
+
"""
|
|
338
|
+
% (HOSTNAME, PORT)
|
|
339
|
+
)
|
|
340
|
+
threading.Timer(0.1, open_browser).start()
|
|
341
|
+
|
|
342
|
+
server = HTTPServer((HOSTNAME, PORT), CustomHTTPRequestHandler)
|
|
343
|
+
server.serve_forever()
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
if __name__ == '__main__':
|
|
347
|
+
run()
|