watobo 0.9.8.677

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 (260) hide show
  1. data/bin/watobo_gui.rb +39 -0
  2. data/certificates/cert.pem +19 -0
  3. data/certificates/privkey.pem +15 -0
  4. data/certificates/watobo_dh.key +5 -0
  5. data/config/datastore.yml +5 -0
  6. data/config/forwarding_proxy.yml +12 -0
  7. data/config/general.yml +4 -0
  8. data/config/gui.yml +23 -0
  9. data/config/interceptor.yml +16 -0
  10. data/config/scan_policy.yml +13 -0
  11. data/config/scanner.yml +34 -0
  12. data/icons/Add.ico +0 -0
  13. data/icons/Add_24x24.ico +0 -0
  14. data/icons/Bandwidth.ico +0 -0
  15. data/icons/Bandwidth_24x24.ico +0 -0
  16. data/icons/Mr. Bomb.ico +0 -0
  17. data/icons/Mr. Bomb_16x16.ico +0 -0
  18. data/icons/Mr. Bomb_24x24.ico +0 -0
  19. data/icons/Orange Ball.ico +0 -0
  20. data/icons/Orange Ball_16x16.ico +0 -0
  21. data/icons/Orange Ball_24x24.ico +0 -0
  22. data/icons/Pause.ico +0 -0
  23. data/icons/Play.ico +0 -0
  24. data/icons/Play_24x24.ico +0 -0
  25. data/icons/Red Ball.ico +0 -0
  26. data/icons/Red Ball_16x16.ico +0 -0
  27. data/icons/Red Ball_24x24.ico +0 -0
  28. data/icons/Run.ico +0 -0
  29. data/icons/Stop.ico +0 -0
  30. data/icons/Stop_24x24.ico +0 -0
  31. data/icons/Terminal.ico +0 -0
  32. data/icons/Transfer.ico +0 -0
  33. data/icons/Transfer_16x16.ico +0 -0
  34. data/icons/User.ico +0 -0
  35. data/icons/User_16x16.ico +0 -0
  36. data/icons/User_24x24.ico +0 -0
  37. data/icons/Web Browser.ico +0 -0
  38. data/icons/Web Browser_16x16.ico +0 -0
  39. data/icons/Write Document.ico +0 -0
  40. data/icons/Yellow Ball.ico +0 -0
  41. data/icons/Yellow Ball_16x16.ico +0 -0
  42. data/icons/Yellow Ball_24x24.ico +0 -0
  43. data/icons/advanced.ico +0 -0
  44. data/icons/advanced_16x16.ico +0 -0
  45. data/icons/advanced_24x24.ico +0 -0
  46. data/icons/applications.ico +0 -0
  47. data/icons/applications_16x16.ico +0 -0
  48. data/icons/applications_24x24.ico +0 -0
  49. data/icons/browser_16x16.ico +0 -0
  50. data/icons/browser_24x24.ico +0 -0
  51. data/icons/burn.ico +0 -0
  52. data/icons/burn_16x16.ico +0 -0
  53. data/icons/burn_24x24.ico +0 -0
  54. data/icons/calculator.ico +0 -0
  55. data/icons/calculator_24x24.ico +0 -0
  56. data/icons/cb_checked.ico +0 -0
  57. data/icons/cb_checked_orange.ico +0 -0
  58. data/icons/cb_unchecked.ico +0 -0
  59. data/icons/coin_24x24.ico +0 -0
  60. data/icons/conversation.ico +0 -0
  61. data/icons/conversation_24x24.ico +0 -0
  62. data/icons/engine.ico +0 -0
  63. data/icons/filter.ico +0 -0
  64. data/icons/folder.ico +0 -0
  65. data/icons/folder_16x16.ico +0 -0
  66. data/icons/fuzzer_16x16.ico +0 -0
  67. data/icons/fuzzer_24x24.ico +0 -0
  68. data/icons/go-down_16x16.png +0 -0
  69. data/icons/go-up_16x16.png +0 -0
  70. data/icons/help.ico +0 -0
  71. data/icons/help_16x16.ico +0 -0
  72. data/icons/help_24x24.ico +0 -0
  73. data/icons/iChat.ico +0 -0
  74. data/icons/iChat_16x16.ico +0 -0
  75. data/icons/iChat_24x24.ico +0 -0
  76. data/icons/icons.txt +3 -0
  77. data/icons/info.ico +0 -0
  78. data/icons/info_16x16.ico +0 -0
  79. data/icons/info_24x24.ico +0 -0
  80. data/icons/interceptor_24x24.ico +0 -0
  81. data/icons/lock.ico +0 -0
  82. data/icons/lock_12x12.ico +0 -0
  83. data/icons/lock_16x16.ico +0 -0
  84. data/icons/monitor.ico +0 -0
  85. data/icons/plugin.ico +0 -0
  86. data/icons/plugin_24x24.ico +0 -0
  87. data/icons/scan_16x16.png +0 -0
  88. data/icons/send.ico +0 -0
  89. data/icons/server.ico +0 -0
  90. data/icons/server_16x16.ico +0 -0
  91. data/icons/siberas_logo_x24.gif +0 -0
  92. data/icons/tag_blue_32x32.ico +0 -0
  93. data/icons/watobo-48x48.png +0 -0
  94. data/icons/watobo-logo.png +0 -0
  95. data/icons/watobo.ico +0 -0
  96. data/lib/watobo/adapters/data_store.rb +38 -0
  97. data/lib/watobo/adapters/file/file_store.rb +211 -0
  98. data/lib/watobo/adapters/session_store.rb +27 -0
  99. data/lib/watobo/adapters.rb +26 -0
  100. data/lib/watobo/config.rb +143 -0
  101. data/lib/watobo/constants.rb +92 -0
  102. data/lib/watobo/core/active_check.rb +404 -0
  103. data/lib/watobo/core/cookie.rb +69 -0
  104. data/lib/watobo/core/fuzz_gen.rb +160 -0
  105. data/lib/watobo/core/http_socket.rb +142 -0
  106. data/lib/watobo/core/interceptor.rb +729 -0
  107. data/lib/watobo/core/passive_check.rb +141 -0
  108. data/lib/watobo/core/project.rb +1058 -0
  109. data/lib/watobo/core/scanner.rb +396 -0
  110. data/lib/watobo/core/session.rb +1320 -0
  111. data/lib/watobo/core/simple_ca.rb +393 -0
  112. data/lib/watobo/core.rb +34 -0
  113. data/lib/watobo/defaults.rb +40 -0
  114. data/lib/watobo/external/diff/lcs/array.rb +42 -0
  115. data/lib/watobo/external/diff/lcs/block.rb +72 -0
  116. data/lib/watobo/external/diff/lcs/callbacks.rb +343 -0
  117. data/lib/watobo/external/diff/lcs/change.rb +190 -0
  118. data/lib/watobo/external/diff/lcs/hunk.rb +279 -0
  119. data/lib/watobo/external/diff/lcs/ldiff.rb +247 -0
  120. data/lib/watobo/external/diff/lcs/string.rb +40 -0
  121. data/lib/watobo/external/diff/lcs.rb +1124 -0
  122. data/lib/watobo/external/ntlm/ntlm.rb +797 -0
  123. data/lib/watobo/externals.rb +28 -0
  124. data/lib/watobo/framework/create_project.rb +55 -0
  125. data/lib/watobo/framework/init.rb +149 -0
  126. data/lib/watobo/framework/init_modules.rb +116 -0
  127. data/lib/watobo/framework/license_text.rb +50 -0
  128. data/lib/watobo/framework.rb +26 -0
  129. data/lib/watobo/gui/about_watobo.rb +68 -0
  130. data/lib/watobo/gui/browser_preview.rb +460 -0
  131. data/lib/watobo/gui/certificate_dialog.rb +132 -0
  132. data/lib/watobo/gui/chat_diff.rb +415 -0
  133. data/lib/watobo/gui/chatviewer_frame.rb +427 -0
  134. data/lib/watobo/gui/checkboxtree.rb +221 -0
  135. data/lib/watobo/gui/checks_policy_frame.rb +123 -0
  136. data/lib/watobo/gui/client_cert_dialog.rb +227 -0
  137. data/lib/watobo/gui/confirm_scan_dialog.rb +67 -0
  138. data/lib/watobo/gui/conversation_table.rb +386 -0
  139. data/lib/watobo/gui/conversation_table_ctrl.rb +175 -0
  140. data/lib/watobo/gui/csrf_token_dialog.rb +446 -0
  141. data/lib/watobo/gui/dashboard.rb +341 -0
  142. data/lib/watobo/gui/define_scope_frame.rb +380 -0
  143. data/lib/watobo/gui/edit_comment.rb +70 -0
  144. data/lib/watobo/gui/edit_scope_dialog.rb +69 -0
  145. data/lib/watobo/gui/finding_info.rb +212 -0
  146. data/lib/watobo/gui/findings_tree.rb +459 -0
  147. data/lib/watobo/gui/full_scan_dialog.rb +269 -0
  148. data/lib/watobo/gui/fuzzer_gui.rb +1522 -0
  149. data/lib/watobo/gui/hex_viewer.rb +106 -0
  150. data/lib/watobo/gui/interceptor_gui.rb +994 -0
  151. data/lib/watobo/gui/interceptor_settings_dialog.rb +201 -0
  152. data/lib/watobo/gui/log_viewer.rb +97 -0
  153. data/lib/watobo/gui/login_wizzard.rb +301 -0
  154. data/lib/watobo/gui/main_window.rb +1815 -0
  155. data/lib/watobo/gui/manual_request_editor.rb +1105 -0
  156. data/lib/watobo/gui/master_pw_dialog.rb +142 -0
  157. data/lib/watobo/gui/password_policy_dialog.rb +98 -0
  158. data/lib/watobo/gui/plugin/base.rb +82 -0
  159. data/lib/watobo/gui/plugin_board.rb +95 -0
  160. data/lib/watobo/gui/preferences_dialog.rb +116 -0
  161. data/lib/watobo/gui/progress_window.rb +102 -0
  162. data/lib/watobo/gui/project_wizzard.rb +369 -0
  163. data/lib/watobo/gui/proxy_dialog.rb +550 -0
  164. data/lib/watobo/gui/quick_scan_dialog.rb +242 -0
  165. data/lib/watobo/gui/request_editor.rb +480 -0
  166. data/lib/watobo/gui/save_chat_dialog.rb +158 -0
  167. data/lib/watobo/gui/scanner_settings_dialog.rb +360 -0
  168. data/lib/watobo/gui/select_chat_dialog.rb +169 -0
  169. data/lib/watobo/gui/session_management_dialog.rb +688 -0
  170. data/lib/watobo/gui/sites_tree.rb +347 -0
  171. data/lib/watobo/gui/status_bar.rb +88 -0
  172. data/lib/watobo/gui/table_editor.rb +445 -0
  173. data/lib/watobo/gui/tagless_viewer.rb +62 -0
  174. data/lib/watobo/gui/templates/plugin.rb +80 -0
  175. data/lib/watobo/gui/templates/plugin2.rb +103 -0
  176. data/lib/watobo/gui/text_viewer.rb +247 -0
  177. data/lib/watobo/gui/transcoder_window.rb +215 -0
  178. data/lib/watobo/gui/utils/gui_utils.rb +129 -0
  179. data/lib/watobo/gui/utils/init_icons.rb +106 -0
  180. data/lib/watobo/gui/utils/load_icons.rb +54 -0
  181. data/lib/watobo/gui/utils/load_plugins.rb +94 -0
  182. data/lib/watobo/gui/utils/master_password.rb +90 -0
  183. data/lib/watobo/gui/utils/save_default_settings.rb +99 -0
  184. data/lib/watobo/gui/utils/save_project_settings.rb +21 -0
  185. data/lib/watobo/gui/utils/save_proxy_settings.rb +45 -0
  186. data/lib/watobo/gui/utils/save_session_settings.rb +21 -0
  187. data/lib/watobo/gui/utils/session_history.rb +134 -0
  188. data/lib/watobo/gui/workspace_dialog.rb +89 -0
  189. data/lib/watobo/gui/www_auth_dialog.rb +348 -0
  190. data/lib/watobo/gui/xml_viewer_frame.rb +114 -0
  191. data/lib/watobo/gui.rb +139 -0
  192. data/lib/watobo/mixins/httpparser.rb +664 -0
  193. data/lib/watobo/mixins/request_parser.rb +210 -0
  194. data/lib/watobo/mixins/shapers.rb +345 -0
  195. data/lib/watobo/mixins/transcoders.rb +88 -0
  196. data/lib/watobo/mixins.rb +32 -0
  197. data/lib/watobo/utils/check_regex.rb +36 -0
  198. data/lib/watobo/utils/copy_object.rb +29 -0
  199. data/lib/watobo/utils/crypto.rb +74 -0
  200. data/lib/watobo/utils/expand_range.rb +45 -0
  201. data/lib/watobo/utils/file_management.rb +73 -0
  202. data/lib/watobo/utils/load_chat.rb +219 -0
  203. data/lib/watobo/utils/load_icon.rb +47 -0
  204. data/lib/watobo/utils/print_debug.rb +34 -0
  205. data/lib/watobo/utils/response_hash.rb +143 -0
  206. data/lib/watobo/utils/secure_eval.rb +57 -0
  207. data/lib/watobo/utils/text2request.rb +96 -0
  208. data/lib/watobo/utils.rb +32 -0
  209. data/lib/watobo.rb +76 -0
  210. data/modules/active/Apache/mod_status.rb +123 -0
  211. data/modules/active/Flash/crossdomain.rb +102 -0
  212. data/modules/active/directories/dirwalker.rb +89 -0
  213. data/modules/active/discovery/fileextensions.rb +151 -0
  214. data/modules/active/discovery/http_methods.rb +135 -0
  215. data/modules/active/domino/domino_db.lst +164 -0
  216. data/modules/active/domino/domino_db.rb +128 -0
  217. data/modules/active/fileinclusion/lfi_simple.rb +134 -0
  218. data/modules/active/jboss/jboss_basic.rb +119 -0
  219. data/modules/active/sap/business_objects.rb +73 -0
  220. data/modules/active/sap/its_commands.rb +101 -0
  221. data/modules/active/sap/its_service_parameter.rb +105 -0
  222. data/modules/active/sap/its_services.rb +103 -0
  223. data/modules/active/sap/its_xss.rb +98 -0
  224. data/modules/active/sqlinjection/sql_boolean.rb +262 -0
  225. data/modules/active/sqlinjection/sqli_simple.rb +205 -0
  226. data/modules/active/xss/xss_simple.rb +179 -0
  227. data/modules/passive/cookie_options.rb +97 -0
  228. data/modules/passive/cookie_xss.rb +85 -0
  229. data/modules/passive/detect_code.rb +89 -0
  230. data/modules/passive/detect_fileupload.rb +80 -0
  231. data/modules/passive/detect_infrastructure.rb +98 -0
  232. data/modules/passive/detect_one_time_tokens.rb +86 -0
  233. data/modules/passive/dirindexing.rb +81 -0
  234. data/modules/passive/disclosure_emails.rb +82 -0
  235. data/modules/passive/disclosure_ipaddr.rb +87 -0
  236. data/modules/passive/filename_as_parameter.rb +85 -0
  237. data/modules/passive/form_spotter.rb +75 -0
  238. data/modules/passive/hotspots.rb +86 -0
  239. data/modules/passive/in_script_parameter.rb +92 -0
  240. data/modules/passive/multiple_server_headers.rb +98 -0
  241. data/modules/passive/possible_login.rb +134 -0
  242. data/modules/passive/redirect_url.rb +88 -0
  243. data/modules/passive/redirectionz.rb +96 -0
  244. data/modules/passive/xss_dom.rb +91 -0
  245. data/plugins/catalog/catalog.ico +0 -0
  246. data/plugins/catalog/catalog.rb +726 -0
  247. data/plugins/catalog/db_tests +6483 -0
  248. data/plugins/catalog/db_variables +29 -0
  249. data/plugins/filefinder/dbs/hbci.db +12 -0
  250. data/plugins/filefinder/filefinder.rb +602 -0
  251. data/plugins/sslchecker/cli/sslchecker_cli.rb +21 -0
  252. data/plugins/sslchecker/gui/cipher_table.rb +260 -0
  253. data/plugins/sslchecker/gui/gui.rb +245 -0
  254. data/plugins/sslchecker/gui/sslchecker.rb +26 -0
  255. data/plugins/sslchecker/icons/green_16x16.ico +0 -0
  256. data/plugins/sslchecker/icons/grey_16x16.ico +0 -0
  257. data/plugins/sslchecker/icons/red_16x16.ico +0 -0
  258. data/plugins/sslchecker/icons/sslchecker.ico +0 -0
  259. data/plugins/sslchecker/lib/check.rb +117 -0
  260. metadata +317 -0
@@ -0,0 +1,1124 @@
1
+ # .
2
+ # lcs.rb
3
+ #
4
+ # Copyright 2012 by siberas, http://www.siberas.de
5
+ #
6
+ # This file is part of WATOBO (Web Application Tool Box)
7
+ # http://watobo.sourceforge.com
8
+ #
9
+ # WATOBO is free software; you can redistribute it and/or modify
10
+ # it under the terms of the GNU General Public License as published by
11
+ # the Free Software Foundation version 2 of the License.
12
+ #
13
+ # WATOBO is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU General Public License
19
+ # along with WATOBO; if not, write to the Free Software
20
+ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21
+ # .
22
+ #! /usr/env/bin ruby
23
+ #--
24
+ # Copyright 2004 Austin Ziegler <diff-lcs@halostatue.ca>
25
+ # adapted from:
26
+ # Algorithm::Diff (Perl) by Ned Konz <perl@bike-nomad.com>
27
+ # Smalltalk by Mario I. Wolczko <mario@wolczko.com>
28
+ # implements McIlroy-Hunt diff algorithm
29
+ #
30
+ # This program is free software. It may be redistributed and/or modified
31
+ # under the terms of the GPL version 2 (or later), the Perl Artistic
32
+ # licence, or the Ruby licence.
33
+ #
34
+ # $Id: lcs.rb,v 1.9 2004/10/17 20:31:10 austin Exp $
35
+ #++
36
+
37
+ module Diff
38
+ # = Diff::LCS 1.1.2
39
+ # Computes "intelligent" differences between two sequenced Enumerables.
40
+ # This is an implementation of the McIlroy-Hunt "diff" algorithm for
41
+ # Enumerable objects that include Diffable.
42
+ #
43
+ # Based on Mario I. Wolczko's <mario@wolczko.com> Smalltalk version
44
+ # (1.2, 1993) and Ned Konz's <perl@bike-nomad.com> Perl version
45
+ # (Algorithm::Diff).
46
+ #
47
+ # == Synopsis
48
+ # require 'diff/lcs'
49
+ #
50
+ # seq1 = %w(a b c e h j l m n p)
51
+ # seq2 = %w(b c d e f j k l m r s t)
52
+ #
53
+ # lcs = Diff::LCS.LCS(seq1, seq2)
54
+ # diffs = Diff::LCS.diff(seq1, seq2)
55
+ # sdiff = Diff::LCS.sdiff(seq1, seq2)
56
+ # seq = Diff::LCS.traverse_sequences(seq1, seq2, callback_obj)
57
+ # bal = Diff::LCS.traverse_balanced(seq1, seq2, callback_obj)
58
+ # seq2 == Diff::LCS.patch(seq1, diffs)
59
+ # seq2 == Diff::LCS.patch!(seq1, diffs)
60
+ # seq1 == Diff::LCS.unpatch(seq2, diffs)
61
+ # seq1 == Diff::LCS.unpatch!(seq2, diffs)
62
+ # seq2 == Diff::LCS.patch(seq1, sdiff)
63
+ # seq2 == Diff::LCS.patch!(seq1, sdiff)
64
+ # seq1 == Diff::LCS.unpatch(seq2, sdiff)
65
+ # seq1 == Diff::LCS.unpatch!(seq2, sdiff)
66
+ #
67
+ # Alternatively, objects can be extended with Diff::LCS:
68
+ #
69
+ # seq1.extend(Diff::LCS)
70
+ # lcs = seq1.lcs(seq2)
71
+ # diffs = seq1.diff(seq2)
72
+ # sdiff = seq1.sdiff(seq2)
73
+ # seq = seq1.traverse_sequences(seq2, callback_obj)
74
+ # bal = seq1.traverse_balanced(seq2, callback_obj)
75
+ # seq2 == seq1.patch(diffs)
76
+ # seq2 == seq1.patch!(diffs)
77
+ # seq1 == seq2.unpatch(diffs)
78
+ # seq1 == seq2.unpatch!(diffs)
79
+ # seq2 == seq1.patch(sdiff)
80
+ # seq2 == seq1.patch!(sdiff)
81
+ # seq1 == seq2.unpatch(sdiff)
82
+ # seq1 == seq2.unpatch!(sdiff)
83
+ #
84
+ # Default extensions are provided for Array and String objects through
85
+ # the use of 'diff/lcs/array' and 'diff/lcs/string'.
86
+ #
87
+ # == Introduction (by Mark-Jason Dominus)
88
+ #
89
+ # <em>The following text is from the Perl documentation. The only
90
+ # changes have been to make the text appear better in Rdoc</em>.
91
+ #
92
+ # I once read an article written by the authors of +diff+; they said
93
+ # that they hard worked very hard on the algorithm until they found the
94
+ # right one.
95
+ #
96
+ # I think what they ended up using (and I hope someone will correct me,
97
+ # because I am not very confident about this) was the `longest common
98
+ # subsequence' method. In the LCS problem, you have two sequences of
99
+ # items:
100
+ #
101
+ # a b c d f g h j q z
102
+ # a b c d e f g i j k r x y z
103
+ #
104
+ # and you want to find the longest sequence of items that is present in
105
+ # both original sequences in the same order. That is, you want to find a
106
+ # new sequence *S* which can be obtained from the first sequence by
107
+ # deleting some items, and from the second sequence by deleting other
108
+ # items. You also want *S* to be as long as possible. In this case *S*
109
+ # is:
110
+ #
111
+ # a b c d f g j z
112
+ #
113
+ # From there it's only a small step to get diff-like output:
114
+ #
115
+ # e h i k q r x y
116
+ # + - + + - + + +
117
+ #
118
+ # This module solves the LCS problem. It also includes a canned function
119
+ # to generate +diff+-like output.
120
+ #
121
+ # It might seem from the example above that the LCS of two sequences is
122
+ # always pretty obvious, but that's not always the case, especially when
123
+ # the two sequences have many repeated elements. For example, consider
124
+ #
125
+ # a x b y c z p d q
126
+ # a b c a x b y c z
127
+ #
128
+ # A naive approach might start by matching up the +a+ and +b+ that
129
+ # appear at the beginning of each sequence, like this:
130
+ #
131
+ # a x b y c z p d q
132
+ # a b c a b y c z
133
+ #
134
+ # This finds the common subsequence +a b c z+. But actually, the LCS is
135
+ # +a x b y c z+:
136
+ #
137
+ # a x b y c z p d q
138
+ # a b c a x b y c z
139
+ #
140
+ # == Author
141
+ # This version is by Austin Ziegler <diff-lcs@halostatue.ca>.
142
+ #
143
+ # It is based on the Perl Algorithm::Diff by Ned Konz
144
+ # <perl@bike-nomad.com>, copyright &copy; 2000 - 2002 and the Smalltalk
145
+ # diff version by Mario I. Wolczko <mario@wolczko.com>, copyright &copy;
146
+ # 1993. Documentation includes work by Mark-Jason Dominus.
147
+ #
148
+ # == Licence
149
+ # Copyright &copy; 2004 Austin Ziegler
150
+ # This program is free software; you can redistribute it and/or modify it
151
+ # under the same terms as Ruby, or alternatively under the Perl Artistic
152
+ # licence.
153
+ #
154
+ # == Credits
155
+ # Much of the documentation is taken directly from the Perl
156
+ # Algorithm::Diff implementation and was written originally by Mark-Jason
157
+ # Dominus <mjd-perl-diff@plover.com> and later by Ned Konz. The basic Ruby
158
+ # implementation was re-ported from the Smalltalk implementation, available
159
+ # at ftp://st.cs.uiuc.edu/pub/Smalltalk/MANCHESTER/manchester/4.0/diff.st
160
+ #
161
+ # #sdiff and #traverse_balanced were written for the Perl version by Mike
162
+ # Schilli <m@perlmeister.com>.
163
+ #
164
+ # "The algorithm is described in <em>A Fast Algorithm for Computing Longest
165
+ # Common Subsequences</em>, CACM, vol.20, no.5, pp.350-353, May 1977, with
166
+ # a few minor improvements to improve the speed."
167
+ module LCS
168
+ VERSION = '1.1.2'
169
+ end
170
+ end
171
+
172
+ module Diff::LCS
173
+ # Returns an Array containing the longest common subsequence(s) between
174
+ # +self+ and +other+. See Diff::LCS#LCS.
175
+ #
176
+ # lcs = seq1.lcs(seq2)
177
+ def lcs(other, &block) #:yields self[ii] if there are matched subsequences:
178
+ Diff::LCS.LCS(self, other, &block)
179
+ end
180
+
181
+ # Returns the difference set between +self+ and +other+. See
182
+ # Diff::LCS#diff.
183
+ def diff(other, callbacks = nil, &block)
184
+ Diff::LCS::diff(self, other, callbacks, &block)
185
+ end
186
+
187
+ # Returns the balanced ("side-by-side") difference set between +self+ and
188
+ # +other+. See Diff::LCS#sdiff.
189
+ def sdiff(other, callbacks = nil, &block)
190
+ Diff::LCS::sdiff(self, other, callbacks, &block)
191
+ end
192
+
193
+ # Traverses the discovered longest common subsequences between +self+ and
194
+ # +other+. See Diff::LCS#traverse_sequences.
195
+ def traverse_sequences(other, callbacks = nil, &block)
196
+ traverse_sequences(self, other, callbacks || Diff::LCS::YieldingCallbacks,
197
+ &block)
198
+ end
199
+
200
+ # Traverses the discovered longest common subsequences between +self+ and
201
+ # +other+ using the alternate, balanced algorithm. See
202
+ # Diff::LCS#traverse_balanced.
203
+ def traverse_balanced(other, callbacks = nil, &block)
204
+ traverse_balanced(self, other, callbacks || Diff::LCS::YieldingCallbacks,
205
+ &block)
206
+ end
207
+
208
+ # Attempts to patch a copy of +self+ with the provided +patchset+. See
209
+ # Diff::LCS#patch.
210
+ def patch(patchset)
211
+ Diff::LCS::patch(self.dup, patchset)
212
+ end
213
+
214
+ # Attempts to unpatch a copy of +self+ with the provided +patchset+.
215
+ # See Diff::LCS#patch.
216
+ def unpatch(patchset)
217
+ Diff::LCS::unpatch(self.dup, patchset)
218
+ end
219
+
220
+ # Attempts to patch +self+ with the provided +patchset+. See
221
+ # Diff::LCS#patch!. Does no autodiscovery.
222
+ def patch!(patchset)
223
+ Diff::LCS::patch!(self, patchset)
224
+ end
225
+
226
+ # Attempts to unpatch +self+ with the provided +patchset+. See
227
+ # Diff::LCS#unpatch. Does no autodiscovery.
228
+ def unpatch!(patchset)
229
+ Diff::LCS::unpatch!(self, patchset)
230
+ end
231
+ end
232
+
233
+ module Diff::LCS
234
+ class << self
235
+ # Given two sequenced Enumerables, LCS returns an Array containing their
236
+ # longest common subsequences.
237
+ #
238
+ # lcs = Diff::LCS.LCS(seq1, seq2)
239
+ #
240
+ # This array whose contents is such that:
241
+ #
242
+ # lcs.each_with_index do |ee, ii|
243
+ # assert(ee.nil? || (seq1[ii] == seq2[ee]))
244
+ # end
245
+ #
246
+ # If a block is provided, the matching subsequences will be yielded from
247
+ # +seq1+ in turn and may be modified before they are placed into the
248
+ # returned Array of subsequences.
249
+ def LCS(seq1, seq2, &block) #:yields seq1[ii] for each matched:
250
+ matches = Diff::LCS.__lcs(seq1, seq2)
251
+ ret = []
252
+ matches.each_with_index do |ee, ii|
253
+ unless matches[ii].nil?
254
+ if block_given?
255
+ ret << (yield seq1[ii])
256
+ else
257
+ ret << seq1[ii]
258
+ end
259
+ end
260
+ end
261
+ ret
262
+ end
263
+
264
+ # Diff::LCS.diff computes the smallest set of additions and deletions
265
+ # necessary to turn the first sequence into the second, and returns a
266
+ # description of these changes.
267
+ #
268
+ # See Diff::LCS::DiffCallbacks for the default behaviour. An alternate
269
+ # behaviour may be implemented with Diff::LCS::ContextDiffCallbacks.
270
+ # If a Class argument is provided for +callbacks+, #diff will attempt
271
+ # to initialise it. If the +callbacks+ object (possibly initialised)
272
+ # responds to #finish, it will be called.
273
+ def diff(seq1, seq2, callbacks = nil, &block) # :yields diff changes:
274
+ callbacks ||= Diff::LCS::DiffCallbacks
275
+ if callbacks.kind_of?(Class)
276
+ cb = callbacks.new rescue callbacks
277
+ callbacks = cb
278
+ end
279
+ traverse_sequences(seq1, seq2, callbacks)
280
+ callbacks.finish if callbacks.respond_to?(:finish)
281
+
282
+ if block_given?
283
+ res = callbacks.diffs.map do |hunk|
284
+ if hunk.kind_of?(Array)
285
+ hunk = hunk.map { |block| yield block }
286
+ else
287
+ yield hunk
288
+ end
289
+ end
290
+ res
291
+ else
292
+ callbacks.diffs
293
+ end
294
+ end
295
+
296
+ # Diff::LCS.sdiff computes all necessary components to show two sequences
297
+ # and their minimized differences side by side, just like the Unix
298
+ # utility <em>sdiff</em> does:
299
+ #
300
+ # old < -
301
+ # same same
302
+ # before | after
303
+ # - > new
304
+ #
305
+ # See Diff::LCS::SDiffCallbacks for the default behaviour. An alternate
306
+ # behaviour may be implemented with Diff::LCS::ContextDiffCallbacks. If
307
+ # a Class argument is provided for +callbacks+, #diff will attempt to
308
+ # initialise it. If the +callbacks+ object (possibly initialised)
309
+ # responds to #finish, it will be called.
310
+ def sdiff(seq1, seq2, callbacks = nil, &block) #:yields diff changes:
311
+ callbacks ||= Diff::LCS::SDiffCallbacks
312
+ if callbacks.kind_of?(Class)
313
+ cb = callbacks.new rescue callbacks
314
+ callbacks = cb
315
+ end
316
+ traverse_balanced(seq1, seq2, callbacks)
317
+ callbacks.finish if callbacks.respond_to?(:finish)
318
+
319
+ if block_given?
320
+ res = callbacks.diffs.map do |hunk|
321
+ if hunk.kind_of?(Array)
322
+ hunk = hunk.map { |block| yield block }
323
+ else
324
+ yield hunk
325
+ end
326
+ end
327
+ res
328
+ else
329
+ callbacks.diffs
330
+ end
331
+ end
332
+
333
+ # Diff::LCS.traverse_sequences is the most general facility provided by this
334
+ # module; +diff+ and +LCS+ are implemented as calls to it.
335
+ #
336
+ # The arguments to #traverse_sequences are the two sequences to
337
+ # traverse, and a callback object, like this:
338
+ #
339
+ # traverse_sequences(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new)
340
+ #
341
+ # #diff is implemented with #traverse_sequences.
342
+ #
343
+ # == Callback Methods
344
+ # Optional callback methods are <em>emphasized</em>.
345
+ #
346
+ # callbacks#match:: Called when +a+ and +b+ are pointing
347
+ # to common elements in +A+ and +B+.
348
+ # callbacks#discard_a:: Called when +a+ is pointing to an
349
+ # element not in +B+.
350
+ # callbacks#discard_b:: Called when +b+ is pointing to an
351
+ # element not in +A+.
352
+ # <em>callbacks#finished_a</em>:: Called when +a+ has reached the end of
353
+ # sequence +A+.
354
+ # <em>callbacks#finished_b</em>:: Called when +b+ has reached the end of
355
+ # sequence +B+.
356
+ #
357
+ # == Algorithm
358
+ # a---+
359
+ # v
360
+ # A = a b c e h j l m n p
361
+ # B = b c d e f j k l m r s t
362
+ # ^
363
+ # b---+
364
+ #
365
+ # If there are two arrows (+a+ and +b+) pointing to elements of
366
+ # sequences +A+ and +B+, the arrows will initially point to the first
367
+ # elements of their respective sequences. #traverse_sequences will
368
+ # advance the arrows through the sequences one element at a time,
369
+ # calling a method on the user-specified callback object before each
370
+ # advance. It will advance the arrows in such a way that if there are
371
+ # elements <tt>A[ii]</tt> and <tt>B[jj]</tt> which are both equal and
372
+ # part of the longest common subsequence, there will be some moment
373
+ # during the execution of #traverse_sequences when arrow +a+ is pointing
374
+ # to <tt>A[ii]</tt> and arrow +b+ is pointing to <tt>B[jj]</tt>. When
375
+ # this happens, #traverse_sequences will call <tt>callbacks#match</tt>
376
+ # and then it will advance both arrows.
377
+ #
378
+ # Otherwise, one of the arrows is pointing to an element of its sequence
379
+ # that is not part of the longest common subsequence.
380
+ # #traverse_sequences will advance that arrow and will call
381
+ # <tt>callbacks#discard_a</tt> or <tt>callbacks#discard_b</tt>, depending
382
+ # on which arrow it advanced. If both arrows point to elements that are
383
+ # not part of the longest common subsequence, then #traverse_sequences
384
+ # will advance one of them and call the appropriate callback, but it is
385
+ # not specified which it will call.
386
+ #
387
+ # The methods for <tt>callbacks#match</tt>, <tt>callbacks#discard_a</tt>,
388
+ # and <tt>callbacks#discard_b</tt> are invoked with an event comprising
389
+ # the action ("=", "+", or "-", respectively), the indicies +ii+ and
390
+ # +jj+, and the elements <tt>A[ii]</tt> and <tt>B[jj]</tt>. Return
391
+ # values are discarded by #traverse_sequences.
392
+ #
393
+ # === End of Sequences
394
+ # If arrow +a+ reaches the end of its sequence before arrow +b+ does,
395
+ # #traverse_sequence try to call <tt>callbacks#finished_a</tt> with the
396
+ # last index and element of +A+ (<tt>A[-1]</tt>) and the current index
397
+ # and element of +B+ (<tt>B[jj]</tt>). If <tt>callbacks#finished_a</tt>
398
+ # does not exist, then <tt>callbacks#discard_b</tt> will be called on
399
+ # each element of +B+ until the end of the sequence is reached (the call
400
+ # will be done with <tt>A[-1]</tt> and <tt>B[jj]</tt> for each element).
401
+ #
402
+ # If +b+ reaches the end of +B+ before +a+ reaches the end of +A+,
403
+ # <tt>callbacks#finished_b</tt> will be called with the current index
404
+ # and element of +A+ (<tt>A[ii]</tt>) and the last index and element of
405
+ # +B+ (<tt>A[-1]</tt>). Again, if <tt>callbacks#finished_b</tt> does not
406
+ # exist on the callback object, then <tt>callbacks#discard_a</tt> will
407
+ # be called on each element of +A+ until the end of the sequence is
408
+ # reached (<tt>A[ii]</tt> and <tt>B[-1]</tt>).
409
+ #
410
+ # There is a chance that one additional <tt>callbacks#discard_a</tt> or
411
+ # <tt>callbacks#discard_b</tt> will be called after the end of the
412
+ # sequence is reached, if +a+ has not yet reached the end of +A+ or +b+
413
+ # has not yet reached the end of +B+.
414
+ def traverse_sequences(seq1, seq2, callbacks = Diff::LCS::SequenceCallbacks, &block) #:yields change events:
415
+ matches = Diff::LCS.__lcs(seq1, seq2)
416
+
417
+ run_finished_a = run_finished_b = false
418
+ string = seq1.kind_of?(String)
419
+
420
+ a_size = seq1.size
421
+ b_size = seq2.size
422
+ ai = bj = 0
423
+
424
+ (0 .. matches.size).each do |ii|
425
+ b_line = matches[ii]
426
+
427
+ ax = string ? seq1[ii, 1] : seq1[ii]
428
+ bx = string ? seq2[bj, 1] : seq2[bj]
429
+
430
+ if b_line.nil?
431
+ unless ax.nil?
432
+ event = Diff::LCS::ContextChange.new('-', ii, ax, bj, bx)
433
+ event = yield event if block_given?
434
+ callbacks.discard_a(event)
435
+ end
436
+ else
437
+ loop do
438
+ break unless bj < b_line
439
+ bx = string ? seq2[bj, 1] : seq2[bj]
440
+ event = Diff::LCS::ContextChange.new('+', ii, ax, bj, bx)
441
+ event = yield event if block_given?
442
+ callbacks.discard_b(event)
443
+ bj += 1
444
+ end
445
+ bx = string ? seq2[bj, 1] : seq2[bj]
446
+ event = Diff::LCS::ContextChange.new('=', ii, ax, bj, bx)
447
+ event = yield event if block_given?
448
+ callbacks.match(event)
449
+ bj += 1
450
+ end
451
+ ai = ii
452
+ end
453
+ ai += 1
454
+
455
+ # The last entry (if any) processed was a match. +ai+ and +bj+ point
456
+ # just past the last matching lines in their sequences.
457
+ while (ai < a_size) or (bj < b_size)
458
+ # last A?
459
+ if ai == a_size and bj < b_size
460
+ if callbacks.respond_to?(:finished_a) and not run_finished_a
461
+ ax = string ? seq1[-1, 1] : seq1[-1]
462
+ bx = string ? seq2[bj, 1] : seq2[bj]
463
+ event = Diff::LCS::ContextChange.new('>', (a_size - 1), ax, bj, bx)
464
+ event = yield event if block_given?
465
+ callbacks.finished_a(event)
466
+ run_finished_a = true
467
+ else
468
+ ax = string ? seq1[ai, 1] : seq1[ai]
469
+ loop do
470
+ bx = string ? seq2[bj, 1] : seq2[bj]
471
+ event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
472
+ event = yield event if block_given?
473
+ callbacks.discard_b(event)
474
+ bj += 1
475
+ break unless bj < b_size
476
+ end
477
+ end
478
+ end
479
+
480
+ # last B?
481
+ if bj == b_size and ai < a_size
482
+ if callbacks.respond_to?(:finished_b) and not run_finished_b
483
+ ax = string ? seq1[ai, 1] : seq1[ai]
484
+ bx = string ? seq2[-1, 1] : seq2[-1]
485
+ event = Diff::LCS::ContextChange.new('<', ai, ax, (b_size - 1), bx)
486
+ event = yield event if block_given?
487
+ callbacks.finished_b(event)
488
+ run_finished_b = true
489
+ else
490
+ bx = string ? seq2[bj, 1] : seq2[bj]
491
+ loop do
492
+ ax = string ? seq1[ai, 1] : seq1[ai]
493
+ event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
494
+ event = yield event if block_given?
495
+ callbacks.discard_a(event)
496
+ ai += 1
497
+ break unless bj < b_size
498
+ end
499
+ end
500
+ end
501
+
502
+ if ai < a_size
503
+ ax = string ? seq1[ai, 1] : seq1[ai]
504
+ bx = string ? seq2[bj, 1] : seq2[bj]
505
+ event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
506
+ event = yield event if block_given?
507
+ callbacks.discard_a(event)
508
+ ai += 1
509
+ end
510
+
511
+ if bj < b_size
512
+ ax = string ? seq1[ai, 1] : seq1[ai]
513
+ bx = string ? seq2[bj, 1] : seq2[bj]
514
+ event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
515
+ event = yield event if block_given?
516
+ callbacks.discard_b(event)
517
+ bj += 1
518
+ end
519
+ end
520
+ end
521
+
522
+ # #traverse_balanced is an alternative to #traverse_sequences. It
523
+ # uses a different algorithm to iterate through the entries in the
524
+ # computed longest common subsequence. Instead of viewing the changes as
525
+ # insertions or deletions from one of the sequences, #traverse_balanced
526
+ # will report <em>changes</em> between the sequences. To represent a
527
+ #
528
+ # The arguments to #traverse_balanced are the two sequences to traverse
529
+ # and a callback object, like this:
530
+ #
531
+ # traverse_balanced(seq1, seq2, Diff::LCS::ContextDiffCallbacks.new)
532
+ #
533
+ # #sdiff is implemented with #traverse_balanced.
534
+ #
535
+ # == Callback Methods
536
+ # Optional callback methods are <em>emphasized</em>.
537
+ #
538
+ # callbacks#match:: Called when +a+ and +b+ are pointing
539
+ # to common elements in +A+ and +B+.
540
+ # callbacks#discard_a:: Called when +a+ is pointing to an
541
+ # element not in +B+.
542
+ # callbacks#discard_b:: Called when +b+ is pointing to an
543
+ # element not in +A+.
544
+ # <em>callbacks#change</em>:: Called when +a+ and +b+ are pointing
545
+ # to the same relative position, but
546
+ # <tt>A[a]</tt> and <tt>B[b]</tt> are
547
+ # not the same; a <em>change</em> has
548
+ # occurred.
549
+ #
550
+ # #traverse_balanced might be a bit slower than #traverse_sequences,
551
+ # noticable only while processing huge amounts of data.
552
+ #
553
+ # The +sdiff+ function of this module is implemented as call to
554
+ # #traverse_balanced.
555
+ #
556
+ # == Algorithm
557
+ # a---+
558
+ # v
559
+ # A = a b c e h j l m n p
560
+ # B = b c d e f j k l m r s t
561
+ # ^
562
+ # b---+
563
+ #
564
+ # === Matches
565
+ # If there are two arrows (+a+ and +b+) pointing to elements of
566
+ # sequences +A+ and +B+, the arrows will initially point to the first
567
+ # elements of their respective sequences. #traverse_sequences will
568
+ # advance the arrows through the sequences one element at a time,
569
+ # calling a method on the user-specified callback object before each
570
+ # advance. It will advance the arrows in such a way that if there are
571
+ # elements <tt>A[ii]</tt> and <tt>B[jj]</tt> which are both equal and
572
+ # part of the longest common subsequence, there will be some moment
573
+ # during the execution of #traverse_sequences when arrow +a+ is pointing
574
+ # to <tt>A[ii]</tt> and arrow +b+ is pointing to <tt>B[jj]</tt>. When
575
+ # this happens, #traverse_sequences will call <tt>callbacks#match</tt>
576
+ # and then it will advance both arrows.
577
+ #
578
+ # === Discards
579
+ # Otherwise, one of the arrows is pointing to an element of its sequence
580
+ # that is not part of the longest common subsequence.
581
+ # #traverse_sequences will advance that arrow and will call
582
+ # <tt>callbacks#discard_a</tt> or <tt>callbacks#discard_b</tt>,
583
+ # depending on which arrow it advanced.
584
+ #
585
+ # === Changes
586
+ # If both +a+ and +b+ point to elements that are not part of the longest
587
+ # common subsequence, then #traverse_sequences will try to call
588
+ # <tt>callbacks#change</tt> and advance both arrows. If
589
+ # <tt>callbacks#change</tt> is not implemented, then
590
+ # <tt>callbacks#discard_a</tt> and <tt>callbacks#discard_b</tt> will be
591
+ # called in turn.
592
+ #
593
+ # The methods for <tt>callbacks#match</tt>, <tt>callbacks#discard_a</tt>,
594
+ # <tt>callbacks#discard_b</tt>, and <tt>callbacks#change</tt> are
595
+ # invoked with an event comprising the action ("=", "+", "-", or "!",
596
+ # respectively), the indicies +ii+ and +jj+, and the elements
597
+ # <tt>A[ii]</tt> and <tt>B[jj]</tt>. Return values are discarded by
598
+ # #traverse_balanced.
599
+ #
600
+ # === Context
601
+ # Note that +ii+ and +jj+ may not be the same index position, even if
602
+ # +a+ and +b+ are considered to be pointing to matching or changed
603
+ # elements.
604
+ def traverse_balanced(seq1, seq2, callbacks = Diff::LCS::BalancedCallbacks)
605
+ matches = Diff::LCS.__lcs(seq1, seq2)
606
+ a_size = seq1.size
607
+ b_size = seq2.size
608
+ ai = bj = mb = 0
609
+ ma = -1
610
+ string = seq1.kind_of?(String)
611
+
612
+ # Process all the lines in the match vector.
613
+ loop do
614
+ # Find next match indices +ma+ and +mb+
615
+ loop do
616
+ ma += 1
617
+ break unless ma < matches.size and matches[ma].nil?
618
+ end
619
+
620
+ break if ma >= matches.size # end of matches?
621
+ mb = matches[ma]
622
+
623
+ # Change(seq2)
624
+ while (ai < ma) or (bj < mb)
625
+ ax = string ? seq1[ai, 1] : seq1[ai]
626
+ bx = string ? seq2[bj, 1] : seq2[bj]
627
+
628
+ case [(ai < ma), (bj < mb)]
629
+ when [true, true]
630
+ if callbacks.respond_to?(:change)
631
+ event = Diff::LCS::ContextChange.new('!', ai, ax, bj, bx)
632
+ event = yield event if block_given?
633
+ callbacks.change(event)
634
+ ai += 1
635
+ bj += 1
636
+ else
637
+ event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
638
+ event = yield event if block_given?
639
+ callbacks.discard_a(event)
640
+ ai += 1
641
+ ax = string ? seq1[ai, 1] : seq1[ai]
642
+ event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
643
+ event = yield event if block_given?
644
+ callbacks.discard_b(event)
645
+ bj += 1
646
+ end
647
+ when [true, false]
648
+ event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
649
+ event = yield event if block_given?
650
+ callbacks.discard_a(event)
651
+ ai += 1
652
+ when [false, true]
653
+ event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
654
+ event = yield event if block_given?
655
+ callbacks.discard_b(event)
656
+ bj += 1
657
+ end
658
+ end
659
+
660
+ # Match
661
+ ax = string ? seq1[ai, 1] : seq1[ai]
662
+ bx = string ? seq2[bj, 1] : seq2[bj]
663
+ event = Diff::LCS::ContextChange.new('=', ai, ax, bj, bx)
664
+ event = yield event if block_given?
665
+ callbacks.match(event)
666
+ ai += 1
667
+ bj += 1
668
+ end
669
+
670
+ while (ai < a_size) or (bj < b_size)
671
+ ax = string ? seq1[ai, 1] : seq1[ai]
672
+ bx = string ? seq2[bj, 1] : seq2[bj]
673
+
674
+ case [(ai < a_size), (bj < b_size)]
675
+ when [true, true]
676
+ if callbacks.respond_to?(:change)
677
+ event = Diff::LCS::ContextChange.new('!', ai, ax, bj, bx)
678
+ event = yield event if block_given?
679
+ callbacks.change(event)
680
+ ai += 1
681
+ bj += 1
682
+ else
683
+ event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
684
+ event = yield event if block_given?
685
+ callbacks.discard_a(event)
686
+ ai += 1
687
+ ax = string ? seq1[ai, 1] : seq1[ai]
688
+ event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
689
+ event = yield event if block_given?
690
+ callbacks.discard_b(event)
691
+ bj += 1
692
+ end
693
+ when [true, false]
694
+ event = Diff::LCS::ContextChange.new('-', ai, ax, bj, bx)
695
+ event = yield event if block_given?
696
+ callbacks.discard_a(event)
697
+ ai += 1
698
+ when [false, true]
699
+ event = Diff::LCS::ContextChange.new('+', ai, ax, bj, bx)
700
+ event = yield event if block_given?
701
+ callbacks.discard_b(event)
702
+ bj += 1
703
+ end
704
+ end
705
+ end
706
+
707
+ PATCH_MAP = { #:nodoc:
708
+ :patch => { '+' => '+', '-' => '-', '!' => '!', '=' => '=' },
709
+ :unpatch => { '+' => '-', '-' => '+', '!' => '!', '=' => '=' }
710
+ }
711
+
712
+ # Given a patchset, convert the current version to the new
713
+ # version. If +direction+ is not specified (must be
714
+ # <tt>:patch</tt> or <tt>:unpatch</tt>), then discovery of the
715
+ # direction of the patch will be attempted.
716
+ def patch(src, patchset, direction = nil)
717
+ string = src.kind_of?(String)
718
+ # Start with a new empty type of the source's class
719
+ res = src.class.new
720
+
721
+ # Normalize the patchset.
722
+ patchset = __normalize_patchset(patchset)
723
+
724
+ direction ||= Diff::LCS.__diff_direction(src, patchset)
725
+ direction ||= :patch
726
+
727
+ ai = bj = 0
728
+
729
+ patchset.each do |change|
730
+ # Both Change and ContextChange support #action
731
+ action = PATCH_MAP[direction][change.action]
732
+
733
+ case change
734
+ when Diff::LCS::ContextChange
735
+ case direction
736
+ when :patch
737
+ el = change.new_element
738
+ op = change.old_position
739
+ np = change.new_position
740
+ when :unpatch
741
+ el = change.old_element
742
+ op = change.new_position
743
+ np = change.old_position
744
+ end
745
+
746
+ case action
747
+ when '-' # Remove details from the old string
748
+ while ai < op
749
+ res << (string ? src[ai, 1] : src[ai])
750
+ ai += 1
751
+ bj += 1
752
+ end
753
+ ai += 1
754
+ when '+'
755
+ while bj < np
756
+ res << (string ? src[ai, 1] : src[ai])
757
+ ai += 1
758
+ bj += 1
759
+ end
760
+
761
+ res << el
762
+ bj += 1
763
+ when '='
764
+ # This only appears in sdiff output with the SDiff callback.
765
+ # Therefore, we only need to worry about dealing with a single
766
+ # element.
767
+ res << el
768
+
769
+ ai += 1
770
+ bj += 1
771
+ when '!'
772
+ while ai < op
773
+ res << (string ? src[ai, 1] : src[ai])
774
+ ai += 1
775
+ bj += 1
776
+ end
777
+
778
+ bj += 1
779
+ ai += 1
780
+
781
+ res << el
782
+ end
783
+ when Diff::LCS::Change
784
+ case action
785
+ when '-'
786
+ while ai < change.position
787
+ res << (string ? src[ai, 1] : src[ai])
788
+ ai += 1
789
+ bj += 1
790
+ end
791
+ ai += 1
792
+ when '+'
793
+ while bj < change.position
794
+ res << (string ? src[ai, 1] : src[ai])
795
+ ai += 1
796
+ bj += 1
797
+ end
798
+
799
+ bj += 1
800
+
801
+ res << change.element
802
+ end
803
+ end
804
+ end
805
+
806
+ while ai < src.size
807
+ res << (string ? src[ai, 1] : src[ai])
808
+ ai += 1
809
+ bj += 1
810
+ end
811
+
812
+ res
813
+ end
814
+
815
+ # Given a set of patchset, convert the current version to the prior
816
+ # version. Does no auto-discovery.
817
+ def unpatch!(src, patchset)
818
+ Diff::LCS.patch(src, patchset, :unpatch)
819
+ end
820
+
821
+ # Given a set of patchset, convert the current version to the next
822
+ # version. Does no auto-discovery.
823
+ def patch!(src, patchset)
824
+ Diff::LCS.patch(src, patchset, :patch)
825
+ end
826
+
827
+ # private
828
+ # Compute the longest common subsequence between the sequenced Enumerables
829
+ # +a+ and +b+. The result is an array whose contents is such that
830
+ #
831
+ # result = Diff::LCS.__lcs(a, b)
832
+ # result.each_with_index do |e, ii|
833
+ # assert_equal(a[ii], b[e]) unless e.nil?
834
+ # end
835
+ def __lcs(a, b)
836
+ a_start = b_start = 0
837
+ a_finish = a.size - 1
838
+ b_finish = b.size - 1
839
+ vector = []
840
+
841
+ # Prune off any common elements at the beginning...
842
+ while (a_start <= a_finish) and
843
+ (b_start <= b_finish) and
844
+ (a[a_start] == b[b_start])
845
+ vector[a_start] = b_start
846
+ a_start += 1
847
+ b_start += 1
848
+ end
849
+
850
+ # Now the end...
851
+ while (a_start <= a_finish) and
852
+ (b_start <= b_finish) and
853
+ (a[a_finish] == b[b_finish])
854
+ vector[a_finish] = b_finish
855
+ a_finish -= 1
856
+ b_finish -= 1
857
+ end
858
+
859
+ # Now, compute the equivalence classes of positions of elements.
860
+ b_matches = Diff::LCS.__position_hash(b, b_start .. b_finish)
861
+
862
+ thresh = []
863
+ links = []
864
+
865
+ (a_start .. a_finish).each do |ii|
866
+ ai = a.kind_of?(String) ? a[ii, 1] : a[ii]
867
+ bm = b_matches[ai]
868
+ kk = nil
869
+ bm.reverse_each do |jj|
870
+ if kk and (thresh[kk] > jj) and (thresh[kk - 1] < jj)
871
+ thresh[kk] = jj
872
+ else
873
+ kk = Diff::LCS.__replace_next_larger(thresh, jj, kk)
874
+ end
875
+ links[kk] = [ (kk > 0) ? links[kk - 1] : nil, ii, jj ] unless kk.nil?
876
+ end
877
+ end
878
+
879
+ unless thresh.empty?
880
+ link = links[thresh.size - 1]
881
+ while not link.nil?
882
+ vector[link[1]] = link[2]
883
+ link = link[0]
884
+ end
885
+ end
886
+
887
+ vector
888
+ end
889
+
890
+ # Find the place at which +value+ would normally be inserted into the
891
+ # Enumerable. If that place is already occupied by +value+, do nothing
892
+ # and return +nil+. If the place does not exist (i.e., it is off the end
893
+ # of the Enumerable), add it to the end. Otherwise, replace the element
894
+ # at that point with +value+. It is assumed that the Enumerable's values
895
+ # are numeric.
896
+ #
897
+ # This operation preserves the sort order.
898
+ def __replace_next_larger(enum, value, last_index = nil)
899
+ # Off the end?
900
+ if enum.empty? or (value > enum[-1])
901
+ enum << value
902
+ return enum.size - 1
903
+ end
904
+
905
+ # Binary search for the insertion point
906
+ last_index ||= enum.size
907
+ first_index = 0
908
+ while (first_index <= last_index)
909
+ ii = (first_index + last_index) >> 1
910
+
911
+ found = enum[ii]
912
+
913
+ if value == found
914
+ return nil
915
+ elsif value > found
916
+ first_index = ii + 1
917
+ else
918
+ last_index = ii - 1
919
+ end
920
+ end
921
+
922
+ # The insertion point is in first_index; overwrite the next larger
923
+ # value.
924
+ enum[first_index] = value
925
+ return first_index
926
+ end
927
+
928
+ # If +vector+ maps the matching elements of another collection onto this
929
+ # Enumerable, compute the inverse +vector+ that maps this Enumerable
930
+ # onto the collection. (Currently unused.)
931
+ def __inverse_vector(a, vector)
932
+ inverse = a.dup
933
+ (0 ... vector.size).each do |ii|
934
+ inverse[vector[ii]] = ii unless vector[ii].nil?
935
+ end
936
+ inverse
937
+ end
938
+
939
+ # Returns a hash mapping each element of an Enumerable to the set of
940
+ # positions it occupies in the Enumerable, optionally restricted to the
941
+ # elements specified in the range of indexes specified by +interval+.
942
+ def __position_hash(enum, interval = 0 .. -1)
943
+ hash = Hash.new { |hh, kk| hh[kk] = [] }
944
+ interval.each do |ii|
945
+ kk = enum.kind_of?(String) ? enum[ii, 1] : enum[ii]
946
+ hash[kk] << ii
947
+ end
948
+ hash
949
+ end
950
+
951
+ # Examine the patchset and the source to see in which direction the
952
+ # patch should be applied.
953
+ #
954
+ # WARNING: By default, this examines the whole patch, so this could take
955
+ # some time. This also works better with Diff::LCS::ContextChange or
956
+ # Diff::LCS::Change as its source, as an array will cause the creation
957
+ # of one of the above.
958
+ def __diff_direction(src, patchset, limit = nil)
959
+ count = left = left_miss = right = right_miss = 0
960
+ string = src.kind_of?(String)
961
+
962
+ patchset.each do |change|
963
+ count += 1
964
+
965
+ case change
966
+ when Diff::LCS::Change
967
+ # With a simplistic change, we can't tell the difference between
968
+ # the left and right on '!' actions, so we ignore those. On '='
969
+ # actions, if there's a miss, we miss both left and right.
970
+ element = string ? src[change.position, 1] : src[change.position]
971
+
972
+ case change.action
973
+ when '-'
974
+ if element == change.element
975
+ left += 1
976
+ else
977
+ left_miss += 1
978
+ end
979
+ when '+'
980
+ if element == change.element
981
+ right += 1
982
+ else
983
+ right_miss += 1
984
+ end
985
+ when '='
986
+ if element != change.element
987
+ left_miss += 1
988
+ right_miss += 1
989
+ end
990
+ end
991
+ when Diff::LCS::ContextChange
992
+ case change.action
993
+ when '-' # Remove details from the old string
994
+ element = string ? src[change.old_position, 1] : src[change.old_position]
995
+ if element == change.old_element
996
+ left += 1
997
+ else
998
+ left_miss += 1
999
+ end
1000
+ when '+'
1001
+ element = string ? src[change.new_position, 1] : src[change.new_position]
1002
+ if element == change.new_element
1003
+ right += 1
1004
+ else
1005
+ right_miss += 1
1006
+ end
1007
+ when '='
1008
+ le = string ? src[change.old_position, 1] : src[change.old_position]
1009
+ re = string ? src[change.new_position, 1] : src[change.new_position]
1010
+
1011
+ left_miss += 1 if le != change.old_element
1012
+ right_miss += 1 if re != change.new_element
1013
+ when '!'
1014
+ element = string ? src[change.old_position, 1] : src[change.old_position]
1015
+ if element == change.old_element
1016
+ left += 1
1017
+ else
1018
+ element = string ? src[change.new_position, 1] : src[change.new_position]
1019
+ if element == change.new_element
1020
+ right += 1
1021
+ else
1022
+ left_miss += 1
1023
+ right_miss += 1
1024
+ end
1025
+ end
1026
+ end
1027
+ end
1028
+
1029
+ break if not limit.nil? and count > limit
1030
+ end
1031
+
1032
+ no_left = (left == 0) and (left_miss >= 0)
1033
+ no_right = (right == 0) and (right_miss >= 0)
1034
+
1035
+ case [no_left, no_right]
1036
+ when [false, true]
1037
+ return :patch
1038
+ when [true, false]
1039
+ return :unpatch
1040
+ else
1041
+ raise "The provided patchset does not appear to apply to the provided value as either source or destination value."
1042
+ end
1043
+ end
1044
+
1045
+ # Normalize the patchset. A patchset is always a sequence of changes, but
1046
+ # how those changes are represented may vary, depending on how they were
1047
+ # generated. In all cases we support, we also support the array
1048
+ # representation of the changes. The formats are:
1049
+ #
1050
+ # [ # patchset <- Diff::LCS.diff(a, b)
1051
+ # [ # one or more hunks
1052
+ # Diff::LCS::Change # one or more changes
1053
+ # ] ]
1054
+ #
1055
+ # [ # patchset, equivalent to the above
1056
+ # [ # one or more hunks
1057
+ # [ action, line, value ] # one or more changes
1058
+ # ] ]
1059
+ #
1060
+ # [ # patchset <- Diff::LCS.diff(a, b, Diff::LCS::ContextDiffCallbacks)
1061
+ # # OR <- Diff::LCS.sdiff(a, b, Diff::LCS::ContextDiffCallbacks)
1062
+ # [ # one or more hunks
1063
+ # Diff::LCS::ContextChange # one or more changes
1064
+ # ] ]
1065
+ #
1066
+ # [ # patchset, equivalent to the above
1067
+ # [ # one or more hunks
1068
+ # [ action, [ old line, old value ], [ new line, new value ] ]
1069
+ # # one or more changes
1070
+ # ] ]
1071
+ #
1072
+ # [ # patchset <- Diff::LCS.sdiff(a, b)
1073
+ # # OR <- Diff::LCS.diff(a, b, Diff::LCS::SDiffCallbacks)
1074
+ # Diff::LCS::ContextChange # one or more changes
1075
+ # ]
1076
+ #
1077
+ # [ # patchset, equivalent to the above
1078
+ # [ action, [ old line, old value ], [ new line, new value ] ]
1079
+ # # one or more changes
1080
+ # ]
1081
+ #
1082
+ # The result of this will be either of the following.
1083
+ #
1084
+ # [ # patchset
1085
+ # Diff::LCS::ContextChange # one or more changes
1086
+ # ]
1087
+ #
1088
+ # [ # patchset
1089
+ # Diff::LCS::Change # one or more changes
1090
+ # ]
1091
+ #
1092
+ # If either of the above is provided, it will be returned as such.
1093
+ #
1094
+ def __normalize_patchset(patchset)
1095
+ patchset.map do |hunk|
1096
+ case hunk
1097
+ when Diff::LCS::ContextChange, Diff::LCS::Change
1098
+ hunk
1099
+ when Array
1100
+ if (not hunk[0].kind_of?(Array)) and hunk[1].kind_of?(Array) and hunk[2].kind_of?(Array)
1101
+ Diff::LCS::ContextChange.from_a(hunk)
1102
+ else
1103
+ hunk.map do |change|
1104
+ case change
1105
+ when Diff::LCS::ContextChange, Diff::LCS::Change
1106
+ change
1107
+ when Array
1108
+ # change[1] will ONLY be an array in a ContextChange#to_a call.
1109
+ # In Change#to_a, it represents the line (singular).
1110
+ if change[1].kind_of?(Array)
1111
+ Diff::LCS::ContextChange.from_a(change)
1112
+ else
1113
+ Diff::LCS::Change.from_a(change)
1114
+ end
1115
+ end
1116
+ end
1117
+ end
1118
+ else
1119
+ raise ArgumentError, "Cannot normalise a hunk of class #{hunk.class}."
1120
+ end
1121
+ end.flatten
1122
+ end
1123
+ end
1124
+ end