webdiff 1.2.0__tar.gz → 1.3.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.
Files changed (288) hide show
  1. {webdiff-1.2.0 → webdiff-1.3.0}/PKG-INFO +17 -7
  2. {webdiff-1.2.0 → webdiff-1.3.0}/README.md +13 -4
  3. {webdiff-1.2.0 → webdiff-1.3.0}/pyproject.toml +5 -4
  4. webdiff-1.3.0/webdiff/app.py +366 -0
  5. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/argparser.py +7 -3
  6. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/diff.py +20 -3
  7. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/dirdiff.py +5 -5
  8. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/github_fetcher.py +2 -2
  9. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/gitwebdiff.py +1 -0
  10. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/style.css +13 -7
  11. webdiff-1.3.0/webdiff/static/js/file_diff.js +61 -0
  12. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/templates/file_diff.html +0 -8
  13. webdiff-1.3.0/webdiff/toy.py +26 -0
  14. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/util.py +19 -0
  15. webdiff-1.2.0/webdiff/app.py +0 -347
  16. webdiff-1.2.0/webdiff/static/js/file_diff.js +0 -47
  17. webdiff-1.2.0/webdiff/static/js/jquery-2.1.1.min.js +0 -4
  18. {webdiff-1.2.0 → webdiff-1.3.0}/LICENSE +0 -0
  19. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/__init__.py +0 -0
  20. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/githubdiff.py +0 -0
  21. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/gitwebshow.py +0 -0
  22. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/localfilediff.py +0 -0
  23. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/options.py +0 -0
  24. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/__init__.py +0 -0
  25. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/codediff.js/codediff.css +0 -0
  26. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/codediff.js/googlecode.css +0 -0
  27. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/Inconsolata/Inconsolata-Bold.ttf +0 -0
  28. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/Inconsolata/Inconsolata-Regular.ttf +0 -0
  29. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/Inconsolata/OFL.txt +0 -0
  30. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/googlecode.css +0 -0
  31. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/inconsolata.css +0 -0
  32. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/a11y-dark.css +0 -0
  33. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/a11y-light.css +0 -0
  34. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/agate.css +0 -0
  35. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/an-old-hope.css +0 -0
  36. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/androidstudio.css +0 -0
  37. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/arduino-light.css +0 -0
  38. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/arta.css +0 -0
  39. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/ascetic.css +0 -0
  40. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/atom-one-dark-reasonable.css +0 -0
  41. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/atom-one-dark.css +0 -0
  42. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/atom-one-light.css +0 -0
  43. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/3024.css +0 -0
  44. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/apathy.css +0 -0
  45. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/apprentice.css +0 -0
  46. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/ashes.css +0 -0
  47. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-cave-light.css +0 -0
  48. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-cave.css +0 -0
  49. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-dune-light.css +0 -0
  50. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-dune.css +0 -0
  51. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-estuary-light.css +0 -0
  52. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-estuary.css +0 -0
  53. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-forest-light.css +0 -0
  54. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-forest.css +0 -0
  55. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-heath-light.css +0 -0
  56. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-heath.css +0 -0
  57. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-lakeside-light.css +0 -0
  58. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-lakeside.css +0 -0
  59. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-plateau-light.css +0 -0
  60. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-plateau.css +0 -0
  61. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-savanna-light.css +0 -0
  62. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-savanna.css +0 -0
  63. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-seaside-light.css +0 -0
  64. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-seaside.css +0 -0
  65. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-sulphurpool-light.css +0 -0
  66. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atelier-sulphurpool.css +0 -0
  67. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/atlas.css +0 -0
  68. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/bespin.css +0 -0
  69. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-bathory.css +0 -0
  70. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-burzum.css +0 -0
  71. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-dark-funeral.css +0 -0
  72. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-gorgoroth.css +0 -0
  73. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-immortal.css +0 -0
  74. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-khold.css +0 -0
  75. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-marduk.css +0 -0
  76. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-mayhem.css +0 -0
  77. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-nile.css +0 -0
  78. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal-venom.css +0 -0
  79. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/black-metal.css +0 -0
  80. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/brewer.css +0 -0
  81. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/bright.css +0 -0
  82. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/brogrammer.css +0 -0
  83. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/brush-trees-dark.css +0 -0
  84. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/brush-trees.css +0 -0
  85. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/chalk.css +0 -0
  86. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/circus.css +0 -0
  87. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/classic-dark.css +0 -0
  88. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/classic-light.css +0 -0
  89. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/codeschool.css +0 -0
  90. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/colors.css +0 -0
  91. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/cupcake.css +0 -0
  92. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/cupertino.css +0 -0
  93. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/danqing.css +0 -0
  94. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/darcula.css +0 -0
  95. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/dark-violet.css +0 -0
  96. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/darkmoss.css +0 -0
  97. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/darktooth.css +0 -0
  98. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/decaf.css +0 -0
  99. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/default-dark.css +0 -0
  100. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/default-light.css +0 -0
  101. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/dirtysea.css +0 -0
  102. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/dracula.css +0 -0
  103. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/edge-dark.css +0 -0
  104. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/edge-light.css +0 -0
  105. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/eighties.css +0 -0
  106. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/embers.css +0 -0
  107. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/equilibrium-dark.css +0 -0
  108. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/equilibrium-gray-dark.css +0 -0
  109. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/equilibrium-gray-light.css +0 -0
  110. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/equilibrium-light.css +0 -0
  111. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/espresso.css +0 -0
  112. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/eva-dim.css +0 -0
  113. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/eva.css +0 -0
  114. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/flat.css +0 -0
  115. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/framer.css +0 -0
  116. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/fruit-soda.css +0 -0
  117. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/gigavolt.css +0 -0
  118. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/github.css +0 -0
  119. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/google-dark.css +0 -0
  120. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/google-light.css +0 -0
  121. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/grayscale-dark.css +0 -0
  122. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/grayscale-light.css +0 -0
  123. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/green-screen.css +0 -0
  124. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/gruvbox-dark-hard.css +0 -0
  125. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/gruvbox-dark-medium.css +0 -0
  126. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/gruvbox-dark-pale.css +0 -0
  127. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/gruvbox-dark-soft.css +0 -0
  128. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/gruvbox-light-hard.css +0 -0
  129. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/gruvbox-light-medium.css +0 -0
  130. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/gruvbox-light-soft.css +0 -0
  131. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/hardcore.css +0 -0
  132. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/harmonic16-dark.css +0 -0
  133. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/harmonic16-light.css +0 -0
  134. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/heetch-dark.css +0 -0
  135. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/heetch-light.css +0 -0
  136. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/helios.css +0 -0
  137. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/hopscotch.css +0 -0
  138. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/horizon-dark.css +0 -0
  139. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/horizon-light.css +0 -0
  140. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/humanoid-dark.css +0 -0
  141. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/humanoid-light.css +0 -0
  142. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/ia-dark.css +0 -0
  143. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/ia-light.css +0 -0
  144. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/icy-dark.css +0 -0
  145. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/ir-black.css +0 -0
  146. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/isotope.css +0 -0
  147. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/kimber.css +0 -0
  148. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/london-tube.css +0 -0
  149. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/macintosh.css +0 -0
  150. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/marrakesh.css +0 -0
  151. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/materia.css +0 -0
  152. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/material-darker.css +0 -0
  153. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/material-lighter.css +0 -0
  154. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/material-palenight.css +0 -0
  155. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/material-vivid.css +0 -0
  156. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/material.css +0 -0
  157. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/mellow-purple.css +0 -0
  158. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/mexico-light.css +0 -0
  159. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/mocha.css +0 -0
  160. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/monokai.css +0 -0
  161. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/nebula.css +0 -0
  162. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/nord.css +0 -0
  163. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/nova.css +0 -0
  164. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/ocean.css +0 -0
  165. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/oceanicnext.css +0 -0
  166. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/one-light.css +0 -0
  167. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/onedark.css +0 -0
  168. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/outrun-dark.css +0 -0
  169. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/papercolor-dark.css +0 -0
  170. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/papercolor-light.css +0 -0
  171. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/paraiso.css +0 -0
  172. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/pasque.css +0 -0
  173. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/phd.css +0 -0
  174. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/pico.css +0 -0
  175. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/pop.css +0 -0
  176. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/porple.css +0 -0
  177. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/qualia.css +0 -0
  178. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/railscasts.css +0 -0
  179. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/rebecca.css +0 -0
  180. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/ros-pine-dawn.css +0 -0
  181. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/ros-pine-moon.css +0 -0
  182. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/ros-pine.css +0 -0
  183. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/sagelight.css +0 -0
  184. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/sandcastle.css +0 -0
  185. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/seti-ui.css +0 -0
  186. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/shapeshifter.css +0 -0
  187. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/silk-dark.css +0 -0
  188. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/silk-light.css +0 -0
  189. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/snazzy.css +0 -0
  190. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/solar-flare-light.css +0 -0
  191. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/solar-flare.css +0 -0
  192. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/solarized-dark.css +0 -0
  193. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/solarized-light.css +0 -0
  194. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/spacemacs.css +0 -0
  195. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/summercamp.css +0 -0
  196. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/summerfruit-dark.css +0 -0
  197. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/summerfruit-light.css +0 -0
  198. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/synth-midnight-terminal-dark.css +0 -0
  199. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/synth-midnight-terminal-light.css +0 -0
  200. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/tango.css +0 -0
  201. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/tender.css +0 -0
  202. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/tomorrow-night.css +0 -0
  203. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/tomorrow.css +0 -0
  204. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/twilight.css +0 -0
  205. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/unikitty-dark.css +0 -0
  206. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/unikitty-light.css +0 -0
  207. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/vulcan.css +0 -0
  208. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/windows-10-light.css +0 -0
  209. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/windows-10.css +0 -0
  210. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/windows-95-light.css +0 -0
  211. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/windows-95.css +0 -0
  212. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/windows-high-contrast-light.css +0 -0
  213. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/windows-high-contrast.css +0 -0
  214. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/windows-nt-light.css +0 -0
  215. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/windows-nt.css +0 -0
  216. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/woodland.css +0 -0
  217. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/xcode-dusk.css +0 -0
  218. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/base16/zenburn.css +0 -0
  219. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/brown-paper.css +0 -0
  220. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/brown-papersq.png +0 -0
  221. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/codepen-embed.css +0 -0
  222. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/color-brewer.css +0 -0
  223. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/dark.css +0 -0
  224. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/default.css +0 -0
  225. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/devibeans.css +0 -0
  226. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/docco.css +0 -0
  227. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/far.css +0 -0
  228. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/felipec.css +0 -0
  229. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/foundation.css +0 -0
  230. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/github-dark-dimmed.css +0 -0
  231. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/github-dark.css +0 -0
  232. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/github.css +0 -0
  233. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/gml.css +0 -0
  234. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/googlecode.css +0 -0
  235. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/gradient-dark.css +0 -0
  236. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/gradient-light.css +0 -0
  237. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/grayscale.css +0 -0
  238. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/hybrid.css +0 -0
  239. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/idea.css +0 -0
  240. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/intellij-light.css +0 -0
  241. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/ir-black.css +0 -0
  242. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/isbl-editor-dark.css +0 -0
  243. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/isbl-editor-light.css +0 -0
  244. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/kimbie-dark.css +0 -0
  245. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/kimbie-light.css +0 -0
  246. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/lightfair.css +0 -0
  247. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/lioshi.css +0 -0
  248. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/magula.css +0 -0
  249. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/mono-blue.css +0 -0
  250. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/monokai-sublime.css +0 -0
  251. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/monokai.css +0 -0
  252. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/night-owl.css +0 -0
  253. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/nnfx-dark.css +0 -0
  254. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/nnfx-light.css +0 -0
  255. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/nord.css +0 -0
  256. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/obsidian.css +0 -0
  257. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/panda-syntax-dark.css +0 -0
  258. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/panda-syntax-light.css +0 -0
  259. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/paraiso-dark.css +0 -0
  260. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/paraiso-light.css +0 -0
  261. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/pojoaque.css +0 -0
  262. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/pojoaque.jpg +0 -0
  263. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/purebasic.css +0 -0
  264. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/qtcreator-dark.css +0 -0
  265. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/qtcreator-light.css +0 -0
  266. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/rainbow.css +0 -0
  267. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/routeros.css +0 -0
  268. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/school-book.css +0 -0
  269. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/shades-of-purple.css +0 -0
  270. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/srcery.css +0 -0
  271. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/stackoverflow-dark.css +0 -0
  272. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/stackoverflow-light.css +0 -0
  273. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/sunburst.css +0 -0
  274. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/tokyo-night-dark.css +0 -0
  275. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/tokyo-night-light.css +0 -0
  276. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/tomorrow-night-blue.css +0 -0
  277. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/tomorrow-night-bright.css +0 -0
  278. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/vs.css +0 -0
  279. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/vs2015.css +0 -0
  280. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/xcode.css +0 -0
  281. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/css/themes/xt256.css +0 -0
  282. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/img/favicon.ico +0 -0
  283. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/img/sprites.png +0 -0
  284. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/img/trans_bg.gif +0 -0
  285. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/js/highlight.min.js +0 -0
  286. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/static/js/highlight.pack.min.js +0 -0
  287. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/templates/__init__.py +0 -0
  288. {webdiff-1.2.0 → webdiff-1.3.0}/webdiff/unified_diff.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: webdiff
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: Two-column web-based git difftool
5
5
  Home-page: https://github.com/danvk/webdiff/
6
6
  License: Apache-2.0
@@ -18,10 +18,11 @@ Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Topic :: Software Development :: Version Control
21
- Requires-Dist: PyGithub (==2.3.0)
21
+ Requires-Dist: PyGithub (>=2.3.0,<3.0.0)
22
+ Requires-Dist: aiohttp (>=3.9.5,<4.0.0)
22
23
  Requires-Dist: binaryornot
23
24
  Requires-Dist: pillow
24
- Requires-Dist: unidiff (==0.7.4)
25
+ Requires-Dist: unidiff (>=0.7.4,<0.8.0)
25
26
  Project-URL: Repository, https://github.com/danvk/webdiff/
26
27
  Description-Content-Type: text/markdown
27
28
 
@@ -55,6 +56,10 @@ Features include:
55
56
 
56
57
  pip install webdiff
57
58
 
59
+ or using [pipx]:
60
+
61
+ pipx install webdiff
62
+
58
63
  or, if you prefer [Homebrew]:
59
64
 
60
65
  brew install danvk/webdiff/webdiff
@@ -135,8 +140,7 @@ Options are:
135
140
  poetry install
136
141
  cd ts
137
142
  yarn
138
- # see https://github.com/webpack/webpack/issues/14532
139
- NODE_OPTIONS=--openssl-legacy-provider webpack
143
+ yarn build
140
144
 
141
145
  Then from the root directory:
142
146
 
@@ -168,7 +172,7 @@ To iterate on the PyPI package, run:
168
172
 
169
173
  pip3 uninstall webdiff
170
174
  poetry build
171
- pip3 install dist/webdiff-?.?.?.tar.gz
175
+ pip3 install dist/webdiff-(latest).tar.gz
172
176
 
173
177
  To publish to pypitest:
174
178
 
@@ -179,6 +183,8 @@ And to the real pypi:
179
183
 
180
184
  poetry publish
181
185
 
186
+ You can publish pre-release versions to pypi by adding "bN" to the version number.
187
+
182
188
  See [pypirc][] and [poetry][] docs for details on setting up tokens for pypi.
183
189
 
184
190
  Publication checklist. Do these from _outside_ the webdiff directory:
@@ -208,7 +214,10 @@ When you run `git webdiff (args)`, it runs:
208
214
 
209
215
  This tells `git` to set up two directories and invoke `webdiff leftdir rightdir`.
210
216
 
211
- There's one complication involving symlinks. `git difftool -d` may fill one of the sides (typically the right) with symlinks. This is faster than copying files, but unfortunately `git diff --no-index` does not resolve these symlinks. To make this work, if a directory contains symlinks, webdiff makes a copy of it before diffing. For file diffs, it resolves the symlink before passing it to `git diff --no-index`. The upshot is that you can run `git webdiff`, edit a file, reload the browser window and see the changes.
217
+ There are two wrinkles here:
218
+
219
+ - `git difftool -d` may fill one of the sides (typically the right) with symlinks. This is faster than copying files, but unfortunately `git diff --no-index` does not resolve these symlinks. To make this work, if a directory contains symlinks, webdiff makes a copy of it before diffing. For file diffs, it resolves the symlink before passing it to `git diff --no-index`. The upshot is that you can run `git webdiff`, edit a file, reload the browser window and see the changes.
220
+ - `git difftool` cleans up its temporary directories when the main webdiff process terminates. Since webdiff detaches to give you back your terminal, it has to make another copy of the directories (this time without resolving symlinks) to make sure they're still there for the child process.
212
221
 
213
222
  [pypirc]: https://packaging.python.org/specifications/pypirc/
214
223
  [Homebrew]: https://brew.sh/
@@ -216,4 +225,5 @@ There's one complication involving symlinks. `git difftool -d` may fill one of t
216
225
  [git config]: https://git-scm.com/docs/git-config
217
226
  [themes]: http://example.com
218
227
  [poetry]: https://python-poetry.org/docs/repositories/#publishable-repositories
228
+ [pipx]: https://pipx.pypa.io/stable/
219
229
 
@@ -28,6 +28,10 @@ Features include:
28
28
 
29
29
  pip install webdiff
30
30
 
31
+ or using [pipx]:
32
+
33
+ pipx install webdiff
34
+
31
35
  or, if you prefer [Homebrew]:
32
36
 
33
37
  brew install danvk/webdiff/webdiff
@@ -108,8 +112,7 @@ Options are:
108
112
  poetry install
109
113
  cd ts
110
114
  yarn
111
- # see https://github.com/webpack/webpack/issues/14532
112
- NODE_OPTIONS=--openssl-legacy-provider webpack
115
+ yarn build
113
116
 
114
117
  Then from the root directory:
115
118
 
@@ -141,7 +144,7 @@ To iterate on the PyPI package, run:
141
144
 
142
145
  pip3 uninstall webdiff
143
146
  poetry build
144
- pip3 install dist/webdiff-?.?.?.tar.gz
147
+ pip3 install dist/webdiff-(latest).tar.gz
145
148
 
146
149
  To publish to pypitest:
147
150
 
@@ -152,6 +155,8 @@ And to the real pypi:
152
155
 
153
156
  poetry publish
154
157
 
158
+ You can publish pre-release versions to pypi by adding "bN" to the version number.
159
+
155
160
  See [pypirc][] and [poetry][] docs for details on setting up tokens for pypi.
156
161
 
157
162
  Publication checklist. Do these from _outside_ the webdiff directory:
@@ -181,7 +186,10 @@ When you run `git webdiff (args)`, it runs:
181
186
 
182
187
  This tells `git` to set up two directories and invoke `webdiff leftdir rightdir`.
183
188
 
184
- There's one complication involving symlinks. `git difftool -d` may fill one of the sides (typically the right) with symlinks. This is faster than copying files, but unfortunately `git diff --no-index` does not resolve these symlinks. To make this work, if a directory contains symlinks, webdiff makes a copy of it before diffing. For file diffs, it resolves the symlink before passing it to `git diff --no-index`. The upshot is that you can run `git webdiff`, edit a file, reload the browser window and see the changes.
189
+ There are two wrinkles here:
190
+
191
+ - `git difftool -d` may fill one of the sides (typically the right) with symlinks. This is faster than copying files, but unfortunately `git diff --no-index` does not resolve these symlinks. To make this work, if a directory contains symlinks, webdiff makes a copy of it before diffing. For file diffs, it resolves the symlink before passing it to `git diff --no-index`. The upshot is that you can run `git webdiff`, edit a file, reload the browser window and see the changes.
192
+ - `git difftool` cleans up its temporary directories when the main webdiff process terminates. Since webdiff detaches to give you back your terminal, it has to make another copy of the directories (this time without resolving symlinks) to make sure they're still there for the child process.
185
193
 
186
194
  [pypirc]: https://packaging.python.org/specifications/pypirc/
187
195
  [Homebrew]: https://brew.sh/
@@ -189,3 +197,4 @@ There's one complication involving symlinks. `git difftool -d` may fill one of t
189
197
  [git config]: https://git-scm.com/docs/git-config
190
198
  [themes]: http://example.com
191
199
  [poetry]: https://python-poetry.org/docs/repositories/#publishable-repositories
200
+ [pipx]: https://pipx.pypa.io/stable/
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "webdiff"
7
- version = "1.2.0"
7
+ version = "1.3.0"
8
8
  description = "Two-column web-based git difftool"
9
9
  license = "Apache-2.0"
10
10
  readme = "README.md"
@@ -28,12 +28,13 @@ include = ["webdiff/static/js/file_diff.js"]
28
28
  python = "^3.10"
29
29
  binaryornot = "*"
30
30
  pillow = "*"
31
- PyGithub = "2.3.0"
32
- unidiff = "==0.7.4"
31
+ PyGithub = "^2.3.0"
32
+ unidiff = "^0.7.4"
33
+ aiohttp = "^3.9.5"
33
34
 
34
35
  [tool.poetry.group.dev.dependencies]
35
36
  pytest = "^7.1.3"
36
- ruff = "^0.4.6"
37
+ ruff = "0.7"
37
38
 
38
39
  [tool.poetry.scripts]
39
40
  webdiff = "webdiff.app:run"
@@ -0,0 +1,366 @@
1
+ #!/usr/bin/env python
2
+ """Web-based file differ.
3
+
4
+ For usage, see README.md.
5
+ """
6
+
7
+ import dataclasses
8
+ import importlib.metadata
9
+ import json
10
+ import logging
11
+ import mimetypes
12
+ import os
13
+ import platform
14
+ import signal
15
+ import socket
16
+ import subprocess
17
+ import sys
18
+ import threading
19
+ import time
20
+ import webbrowser
21
+
22
+ import aiohttp
23
+ import aiohttp.web_request
24
+ from aiohttp import web
25
+ from binaryornot.check import is_binary
26
+
27
+ from webdiff import argparser, diff, options, util
28
+ from webdiff.dirdiff import make_resolved_dir
29
+
30
+ VERSION = importlib.metadata.version('webdiff')
31
+
32
+
33
+ def determine_path():
34
+ """Borrowed from wxglade.py"""
35
+ try:
36
+ root = __file__
37
+ if os.path.islink(root):
38
+ root = os.path.realpath(root)
39
+ return os.path.dirname(os.path.abspath(root))
40
+ except Exception as e:
41
+ print(f"I'm sorry, but something is wrong. Error: {e}")
42
+ print('There is no __file__ variable. Please contact the author.')
43
+ sys.exit()
44
+
45
+
46
+ GIT_CONFIG = {}
47
+ DIFF = None
48
+ PORT = None
49
+ HOSTNAME = 'localhost'
50
+ DEBUG = os.environ.get('DEBUG')
51
+ DEBUG_DETACH = os.environ.get('DEBUG_DETACH')
52
+ WEBDIFF_DIR = determine_path()
53
+
54
+ if DEBUG:
55
+ handler = logging.StreamHandler()
56
+ handler.setLevel(logging.DEBUG)
57
+ formatter = logging.Formatter(
58
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
59
+ )
60
+ handler.setFormatter(formatter)
61
+
62
+ for logname in ['']:
63
+ log = logging.getLogger(logname)
64
+ log.setLevel(logging.DEBUG)
65
+ log.addHandler(handler)
66
+ logging.getLogger('github').setLevel(logging.ERROR)
67
+ logging.getLogger('binaryornot').setLevel(logging.ERROR)
68
+
69
+
70
+ async def handle_index(request: aiohttp.web_request.Request):
71
+ idx = int(request.match_info.get('idx', '0'))
72
+ pairs = diff.get_thin_list(DIFF)
73
+ with open(os.path.join(WEBDIFF_DIR, 'templates/file_diff.html'), 'r') as file:
74
+ html = file.read()
75
+ html = html.replace(
76
+ '{{data}}',
77
+ json.dumps(
78
+ {
79
+ 'idx': idx,
80
+ 'has_magick': util.is_imagemagick_available(),
81
+ 'pairs': pairs,
82
+ 'git_config': GIT_CONFIG,
83
+ },
84
+ indent=2,
85
+ ),
86
+ )
87
+ return web.Response(body=html, content_type='text/html', charset='utf-8')
88
+
89
+
90
+ async def handle_thick(request: aiohttp.web_request.Request):
91
+ idx = int(request.match_info.get('idx'))
92
+ return web.json_response(diff.get_thick_dict(DIFF[idx]))
93
+
94
+
95
+ async def handle_get_contents(request: aiohttp.web_request.Request):
96
+ side = request.match_info['side']
97
+ form_data = await request.post()
98
+ path = form_data.get('path', '')
99
+ if not path:
100
+ return web.json_response({'error': 'incomplete'}, status=400)
101
+ should_normalize = form_data.get('normalize_json')
102
+
103
+ idx = diff.find_diff_index(DIFF, side, path)
104
+ if idx is None:
105
+ return web.json_response({'error': 'not found'}, status=400)
106
+
107
+ d = DIFF[idx]
108
+ abs_path = d.a_path if side == 'a' else d.b_path
109
+
110
+ try:
111
+ if is_binary(abs_path):
112
+ size = os.path.getsize(abs_path)
113
+ return web.Response(text=f'Binary file ({size} bytes)')
114
+ else:
115
+ if should_normalize:
116
+ abs_path = util.normalize_json(abs_path)
117
+ return web.FileResponse(abs_path, headers={'Content-Type': 'text/plain'})
118
+ except Exception as e:
119
+ return web.json_response({'error': str(e)}, status=500)
120
+
121
+
122
+ async def handle_diff_ops(request: aiohttp.web_request.Request):
123
+ idx = int(request.match_info.get('idx'))
124
+ payload = await request.json()
125
+ options = payload.get('options') or []
126
+ should_normalize = payload.get('normalize_json')
127
+ logging.debug([*payload.keys()])
128
+ logging.debug({**payload})
129
+ extra_args = GIT_CONFIG['webdiff']['extraFileDiffArgs']
130
+ if extra_args:
131
+ options += extra_args.split(' ')
132
+ diff_ops = [
133
+ dataclasses.asdict(op)
134
+ for op in diff.get_diff_ops(DIFF[idx], options, normalize_json=should_normalize)
135
+ ]
136
+ return web.json_response(diff_ops, status=200)
137
+
138
+
139
+ async def handle_theme(request: aiohttp.web_request.Request):
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(
144
+ WEBDIFF_DIR, 'static/css/themes', theme_dir, theme_file + '.css'
145
+ )
146
+ return web.FileResponse(theme_path)
147
+
148
+
149
+ async def handle_favicon(request):
150
+ return web.FileResponse(os.path.join(WEBDIFF_DIR, 'static/img/favicon.ico'))
151
+
152
+
153
+ async def handle_get_image(request: aiohttp.web_request.Request):
154
+ side = request.match_info.get('side')
155
+ path = request.match_info.get('path')
156
+ mime_type, _ = mimetypes.guess_type(path)
157
+ if not mime_type or not mime_type.startswith('image/'):
158
+ return web.json_response({'error': 'wrong type'}, status=400)
159
+
160
+ idx = diff.find_diff_index(DIFF, side, path)
161
+ if idx is None:
162
+ return web.json_response({'error': 'not found'}, status=400)
163
+
164
+ d = DIFF[idx]
165
+ abs_path = d.a_path if side == 'a' else d.b_path
166
+ return web.FileResponse(abs_path, headers={'Content-Type': mime_type})
167
+
168
+
169
+ async def handle_pdiff(request: aiohttp.web_request.Request):
170
+ idx = int(request.match_info.get('idx'))
171
+ d = DIFF[idx]
172
+ try:
173
+ _, pdiff_image = util.generate_pdiff_image(d.a_path, d.b_path)
174
+ dilated_image_path = util.generate_dilated_pdiff_image(pdiff_image)
175
+ return web.FileResponse(dilated_image_path)
176
+ except util.ImageMagickNotAvailableError:
177
+ return web.Response(status=501, text='ImageMagick is not available')
178
+ except util.ImageMagickError as e:
179
+ return web.Response(status=501, text=f'ImageMagick error {e}')
180
+
181
+
182
+ async def handle_pdiff_bbox(request: aiohttp.web_request.Request):
183
+ idx = int(request.match_info.get('idx'))
184
+ d = DIFF[idx]
185
+ try:
186
+ _, pdiff_image = util.generate_pdiff_image(d.a_path, d.b_path)
187
+ bbox = util.get_pdiff_bbox(pdiff_image)
188
+ return web.json_response(bbox, status=200)
189
+ except util.ImageMagickNotAvailableError:
190
+ return web.json_response('ImageMagick is not available', status=501)
191
+ except util.ImageMagickError as e:
192
+ return web.json_response(f'ImageMagick error {e}', status=501)
193
+
194
+
195
+ async def websocket_handler(request: aiohttp.web_request.Request):
196
+ ws = web.WebSocketResponse()
197
+ await ws.prepare(request)
198
+
199
+ async for msg in ws:
200
+ if msg.type == aiohttp.WSMsgType.TEXT:
201
+ await ws.send_str(msg.data + '/answer')
202
+ elif msg.type == aiohttp.WSMsgType.ERROR:
203
+ print('ws connection closed with exception %s' % ws.exception())
204
+
205
+ maybe_shutdown()
206
+ return ws
207
+
208
+
209
+ @web.middleware
210
+ async def request_time_middleware(request, handler):
211
+ note_request_time()
212
+ response = await handler(request)
213
+ return response
214
+
215
+
216
+ app = web.Application(middlewares=[request_time_middleware])
217
+ app.add_routes(
218
+ [
219
+ web.get('/', handle_index),
220
+ web.get(r'/{idx:\d+}', handle_index),
221
+ web.get('/favicon.ico', handle_favicon),
222
+ web.get('/theme.css', handle_theme),
223
+ web.static('/static', os.path.join(WEBDIFF_DIR, 'static')),
224
+ web.get(r'/thick/{idx:\d+}', handle_thick),
225
+ web.post(r'/{side:a|b}/get_contents', handle_get_contents),
226
+ web.post(r'/diff/{idx:\d+}', handle_diff_ops),
227
+ # Image diffs
228
+ web.get(r'/{side:a|b}/image/{path:.*}', handle_get_image),
229
+ web.get(r'/pdiff/{idx:\d+}', handle_pdiff),
230
+ web.get(r'/pdiffbbox/{idx:\d+}', handle_pdiff_bbox),
231
+ # Websocket for detecting when the tab is closed
232
+ web.get('/ws', websocket_handler),
233
+ ]
234
+ )
235
+
236
+
237
+ def note_request_time():
238
+ global LAST_REQUEST_MS
239
+ LAST_REQUEST_MS = time.time() * 1000
240
+
241
+
242
+ def open_browser():
243
+ global PORT
244
+ global HOSTNAME
245
+ global GIT_CONFIG
246
+ if not os.environ.get('WEBDIFF_NO_OPEN') and GIT_CONFIG['webdiff']['openBrowser']:
247
+ webbrowser.open_new_tab('http://%s:%s' % (HOSTNAME, PORT))
248
+
249
+
250
+ def usage_and_die():
251
+ sys.stderr.write(argparser.USAGE)
252
+ sys.exit(1)
253
+
254
+
255
+ def random_port():
256
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
257
+ sock.bind(('localhost', 0))
258
+ port = sock.getsockname()[1]
259
+ sock.close()
260
+ return port
261
+
262
+
263
+ def pick_a_port(args, webdiff_config):
264
+ if 'port' in args:
265
+ return args['port']
266
+
267
+ env_port = os.environ.get('WEBDIFF_PORT')
268
+ if env_port:
269
+ return int(env_port)
270
+
271
+ # gitconfig
272
+ if webdiff_config['port'] != -1:
273
+ return webdiff_config['port']
274
+
275
+ return random_port()
276
+
277
+
278
+ def run_http():
279
+ threading.Timer(0.1, open_browser).start()
280
+ web.run_app(app, host=HOSTNAME, port=PORT, print=print if DEBUG else None)
281
+ logging.debug('http server shut down')
282
+
283
+
284
+ def maybe_shutdown():
285
+ """Wait a second for new requests, then shut down the server."""
286
+ last_ms = LAST_REQUEST_MS
287
+
288
+ def shutdown():
289
+ if LAST_REQUEST_MS <= last_ms: # subsequent requests abort shutdown
290
+ logging.debug('Shutting down...')
291
+ signal.raise_signal(signal.SIGINT)
292
+ else:
293
+ logging.debug('Received subsequent request; shutdown aborted.')
294
+
295
+ logging.debug(
296
+ 'Received request to shut down; waiting 500ms for subsequent requests...'
297
+ )
298
+ threading.Timer(0.5, shutdown).start()
299
+
300
+
301
+ def run():
302
+ global DIFF, PORT, HOSTNAME, GIT_CONFIG
303
+ try:
304
+ parsed_args = argparser.parse(sys.argv[1:], VERSION)
305
+ except argparser.UsageError as e:
306
+ sys.stderr.write('Error: %s\n\n' % e)
307
+ usage_and_die()
308
+
309
+ GIT_CONFIG = options.get_config()
310
+ WEBDIFF_CONFIG = GIT_CONFIG['webdiff']
311
+ DIFF = argparser.diff_for_args(parsed_args, WEBDIFF_CONFIG)
312
+
313
+ if DEBUG:
314
+ sys.stderr.write('Invoked as: %s\n' % sys.argv)
315
+ sys.stderr.write('Args: %s\n' % parsed_args)
316
+ sys.stderr.write('Diff: %s\n' % DIFF)
317
+ sys.stderr.write('GitConfig: %s\n' % GIT_CONFIG)
318
+
319
+ PORT = pick_a_port(parsed_args, WEBDIFF_CONFIG)
320
+ HOSTNAME = (
321
+ parsed_args.get('host')
322
+ or os.environ.get('WEBDIFF_HOST')
323
+ or WEBDIFF_CONFIG['host']
324
+ )
325
+ if HOSTNAME == '<hostname>':
326
+ _hostname = platform.node()
327
+ # platform.node will return empty string if it can't find the hostname
328
+ if not _hostname:
329
+ sys.stderr.write('Warning: hostname could not be determined\n')
330
+ else:
331
+ HOSTNAME = _hostname
332
+
333
+ run_in_process = os.environ.get('WEBDIFF_RUN_IN_PROCESS') or (
334
+ DEBUG and not DEBUG_DETACH
335
+ )
336
+
337
+ if not os.environ.get('WEBDIFF_LOGGED_MESSAGE'):
338
+ # Printing this in the main process gives you your prompt back more cleanly.
339
+ print(
340
+ """Serving diffs on http://%s:%s
341
+ Close the browser tab when you're done to terminate the process."""
342
+ % (HOSTNAME, PORT)
343
+ )
344
+ os.environ['WEBDIFF_LOGGED_MESSAGE'] = '1'
345
+
346
+ if run_in_process:
347
+ run_http()
348
+ else:
349
+ os.environ['WEBDIFF_RUN_IN_PROCESS'] = '1'
350
+ os.environ['WEBDIFF_PORT'] = str(PORT)
351
+ if os.environ.get('WEBDIFF_FROM_GIT_DIFFTOOL'):
352
+ # git difftool will clean up these directories when we detach.
353
+ # To make them accessible to the child process, we make a (shallow) copy.
354
+ assert 'dirs' in parsed_args
355
+ dir_a, dir_b = parsed_args['dirs']
356
+ copied_dir_a = make_resolved_dir(dir_a)
357
+ copied_dir_b = make_resolved_dir(dir_b)
358
+ os.environ['WEBDIFF_DIR_A'] = copied_dir_a
359
+ os.environ['WEBDIFF_DIR_B'] = copied_dir_b
360
+ logging.debug(f'Copied {dir_a} -> {copied_dir_a} before detaching')
361
+ logging.debug(f'Copied {dir_b} -> {copied_dir_b} before detaching')
362
+ subprocess.Popen((sys.executable, *sys.argv))
363
+
364
+
365
+ if __name__ == '__main__':
366
+ run()
@@ -4,9 +4,7 @@ import argparse
4
4
  import os
5
5
  import re
6
6
 
7
- from webdiff import dirdiff
8
- from webdiff import githubdiff
9
- from webdiff import github_fetcher
7
+ from webdiff import dirdiff, github_fetcher, githubdiff
10
8
  from webdiff.localfilediff import LocalFileDiff
11
9
 
12
10
 
@@ -81,6 +79,12 @@ def parse(args, version=None):
81
79
 
82
80
  else:
83
81
  a, b = args.dirs
82
+ if os.environ.get('WEBDIFF_DIR_A') and os.environ.get('WEBDIFF_DIR_B'):
83
+ # This happens when you run "git webdiff" and we have to make a copy of
84
+ # the temp directories before we detach and git difftool cleans them up.
85
+ a = os.environ.get('WEBDIFF_DIR_A')
86
+ b = os.environ.get('WEBDIFF_DIR_B')
87
+
84
88
  for x in (a, b):
85
89
  if not os.path.exists(x):
86
90
  raise UsageError('"%s" does not exist' % x)
@@ -29,17 +29,30 @@ def get_thin_dict(diff):
29
29
  - change type (add, delete, move, change)
30
30
  - (in the future) diffstats
31
31
  """
32
- return {'a': diff.a, 'b': diff.b, 'type': diff.type, 'num_add': diff.num_add, 'num_delete': diff.num_delete}
32
+ return {
33
+ 'a': diff.a,
34
+ 'b': diff.b,
35
+ 'type': diff.type,
36
+ 'num_add': diff.num_add,
37
+ 'num_delete': diff.num_delete,
38
+ }
33
39
 
34
40
 
35
41
  def fast_num_lines(path: str) -> int:
36
42
  # See https://stackoverflow.com/q/9629179/388951 for the idea to use a Unix command.
37
43
  # Unfortunately `wc -l` ignores the last line if there is no trailing newline. So
38
44
  # instead, see https://stackoverflow.com/a/38870057/388951
39
- return int(subprocess.check_output(['grep', '-c', '', path]))
45
+ try:
46
+ return int(subprocess.check_output(['grep', '-c', '', path]))
47
+ except subprocess.CalledProcessError as e:
48
+ if e.returncode == 1:
49
+ return 0 # grep -c returns an error code if there are no matches
50
+ raise
40
51
 
41
52
 
42
- def get_diff_ops(diff: LocalFileDiff, git_diff_args=None) -> List[Code]:
53
+ def get_diff_ops(
54
+ diff: LocalFileDiff, git_diff_args=None, normalize_json=False
55
+ ) -> List[Code]:
43
56
  """Run git diff on the file pair and convert the results to a sequence of codes.
44
57
 
45
58
  git_diff_args is passed directly to git diff. It can be something like ['-w'] or
@@ -48,6 +61,10 @@ def get_diff_ops(diff: LocalFileDiff, git_diff_args=None) -> List[Code]:
48
61
  # git diff --no-index doesn't follow symlinks. So we help it a bit.
49
62
  a_path = os.path.realpath(diff.a_path) if diff.a_path else ''
50
63
  b_path = os.path.realpath(diff.b_path) if diff.b_path else ''
64
+ if normalize_json:
65
+ a_path = a_path and util.normalize_json(a_path)
66
+ b_path = b_path and util.normalize_json(b_path)
67
+
51
68
  if a_path and b_path:
52
69
  num_lines = fast_num_lines(b_path)
53
70
  args = (
@@ -1,7 +1,7 @@
1
1
  """Compute the diff between two directories on local disk."""
2
2
 
3
- import os
4
3
  import logging
4
+ import os
5
5
  import shutil
6
6
  import subprocess
7
7
  import tempfile
@@ -27,7 +27,7 @@ def contains_symlinks(dir: str):
27
27
  return False
28
28
 
29
29
 
30
- def make_resolved_dir(dir: str) -> str:
30
+ def make_resolved_dir(dir: str, follow_symlinks=False) -> str:
31
31
  # TODO: clean up this directory
32
32
  temp_dir = tempfile.mkdtemp(prefix='webdiff')
33
33
  for root, dirs, files in os.walk(dir):
@@ -38,7 +38,7 @@ def make_resolved_dir(dir: str) -> str:
38
38
  src_file = os.path.join(root, file_name)
39
39
  rel = os.path.relpath(src_file, dir)
40
40
  dst_file = os.path.join(temp_dir, rel)
41
- shutil.copy(src_file, dst_file, follow_symlinks=True)
41
+ shutil.copy(src_file, dst_file, follow_symlinks=follow_symlinks)
42
42
  return temp_dir
43
43
 
44
44
 
@@ -49,11 +49,11 @@ def gitdiff(a_dir: str, b_dir: str, webdiff_config):
49
49
  cmd += ' ' + extra_args
50
50
  a_dir_nosym = a_dir
51
51
  if contains_symlinks(a_dir):
52
- a_dir_nosym = make_resolved_dir(a_dir)
52
+ a_dir_nosym = make_resolved_dir(a_dir, follow_symlinks=True)
53
53
  logging.debug(f'Inlined symlinks in left directory {a_dir} -> {a_dir_nosym}')
54
54
  b_dir_nosym = b_dir
55
55
  if contains_symlinks(b_dir):
56
- b_dir_nosym = make_resolved_dir(b_dir)
56
+ b_dir_nosym = make_resolved_dir(b_dir, follow_symlinks=True)
57
57
  logging.debug(f'Inlined symlinks in right directory {b_dir} -> {b_dir_nosym}')
58
58
  args = cmd.split(' ') + [a_dir_nosym, b_dir_nosym]
59
59
  logging.debug('Running git command: %s', args)
@@ -6,11 +6,11 @@ This will create symlinks or clone git repos as needed.
6
6
  # Use this PR for testing to see all four types of change at once:
7
7
  # https://github.com/danvk/test-repo/pull/2/
8
8
 
9
- from collections import OrderedDict
10
9
  import os
11
10
  import re
12
11
  import subprocess
13
12
  import sys
13
+ from collections import OrderedDict
14
14
 
15
15
  from github import Github, UnknownObjectException
16
16
 
@@ -138,7 +138,7 @@ def _get_github_remotes():
138
138
 
139
139
  # e.g. 'origin git@github.com:danvk/expandable-image-grid.git (push)'
140
140
  ssh_push_re = re.compile(
141
- '(?P<name>[^\s]+)\s+((?P<user>[^@]+)@)?(?P<host>[^:]+)(?::(?P<path>[^\s]+))?\s\(push\)'
141
+ r'(?P<name>[^\s]+)\s+((?P<user>[^@]+)@)?(?P<host>[^:]+)(?::(?P<path>[^\s]+))?\s\(push\)'
142
142
  )
143
143
 
144
144
  # e.g. 'origin https://github.com/danvk/git-helpers.git (push)'
@@ -21,6 +21,7 @@ def run(argv=sys.argv):
21
21
  if not os.environ.get('DEBUG')
22
22
  else os.path.join(os.path.curdir, 'test.sh')
23
23
  )
24
+ os.environ['WEBDIFF_FROM_GIT_DIFFTOOL'] = '1'
24
25
  subprocess.call(f'git difftool -d -x {cmd}'.split(' ') + argv[1:])
25
26
  except KeyboardInterrupt:
26
27
  # Don't raise an exception to the user when sigint is received