wagn 1.14.1 → 1.14.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.tm_properties +2 -1
  3. data/VERSION +1 -1
  4. data/app/controllers/card_controller.rb +4 -3
  5. data/db/bootstrap/card_acts.yml +1 -1
  6. data/db/bootstrap/cards.yml +1028 -1028
  7. data/db/migrate_core_cards/20140629222005_add_email_cards.rb +2 -25
  8. data/db/migrate_core_cards/20141204061304_watchers_to_following.rb +38 -0
  9. data/db/version_core_cards.txt +1 -1
  10. data/features/history.feature +21 -0
  11. data/features/step_definitions/history_steps.rb +3 -0
  12. data/features/step_definitions/wagn_steps.rb +10 -1
  13. data/lib/card/act.rb +2 -2
  14. data/lib/card/action.rb +9 -9
  15. data/lib/card/diff.rb +370 -211
  16. data/lib/card/exceptions.rb +2 -0
  17. data/lib/card/format.rb +5 -3
  18. data/lib/card/query.rb +4 -4
  19. data/lib/card/query/card_spec.rb +69 -51
  20. data/lib/card/set.rb +3 -2
  21. data/lib/wagn.rb +15 -3
  22. data/lib/wagn/all.rb +1 -3
  23. data/lib/wagn/application.rb +10 -1
  24. data/lib/wagn/generators/wagn/templates/Gemfile +6 -1
  25. data/lib/wagn/log.rb +69 -0
  26. data/lib/wagn/tasks/wagn.rake +1 -1
  27. data/mod/01_core/set/all/collection.rb +8 -5
  28. data/mod/01_core/set/all/content.rb +1 -1
  29. data/mod/01_core/set/all/fetch.rb +34 -32
  30. data/mod/01_core/set/all/notify.rb +2 -2
  31. data/mod/01_core/set/all/phases.rb +5 -0
  32. data/mod/01_core/set/all/trash.rb +1 -1
  33. data/mod/02_basic_types/set/type/pointer.rb +4 -0
  34. data/mod/03_machines/set/type/coffee_script.rb +10 -0
  35. data/mod/03_machines/set/type/css.rb +9 -0
  36. data/mod/03_machines/set/type/java_script.rb +5 -0
  37. data/mod/03_machines/set/type/scss.rb +8 -2
  38. data/mod/05_standard/set/all/history.rb +10 -1
  39. data/mod/05_standard/set/type/html.rb +4 -0
  40. data/mod/05_standard/spec/set/all/base_spec.rb +2 -0
  41. data/mod/05_standard/spec/set/all/history_spec.rb +12 -0
  42. data/mod/06_email/set/all/email_html.rb +0 -4
  43. data/mod/06_email/set/type/email_template.rb +6 -2
  44. data/spec/lib/card/diff_spec.rb +207 -107
  45. data/spec/lib/card/query_spec.rb +14 -13
  46. data/test/fixtures/card_actions.yml +21 -0
  47. data/test/fixtures/card_acts.yml +103 -85
  48. data/test/fixtures/card_changes.yml +53 -23
  49. data/test/fixtures/cards.yml +1349 -1331
  50. data/test/seed.rb +5 -1
  51. metadata +8 -3
  52. data/lib/wagn/config/initializers/airbrake.rb +0 -9
@@ -125,36 +125,13 @@ class AddEmailCards < Wagn::CoreMigration
125
125
  Card.create! :name => '*following+*right+*default', :type_code=>:pointer
126
126
  Card.create! :name => '*following+*right+*update', :content=>'_left'
127
127
  Card.create! :name => '*following+*right+*create', :content=>'_left'
128
- Card::Codename.reset_cache
129
-
130
- # move old watch rules
131
- # +watchers
132
- follower_hash = Hash.new { |h, v| h[v] = [] }
133
128
 
134
- Card.search(:right_plus => {:codename=> "watchers"}).each do |card|
135
- if watched = card.left
136
- card.item_names.each do |user_name|
137
- follower_hash[user_name] << watched.name
138
- end
139
- end
140
- end
141
-
142
- follower_hash.each do |user, items|
143
- if card=Card.fetch(user) and card.account
144
- following = card.fetch :trait=>"following", :new=>{}
145
- following.items = items
146
- end
147
- end
148
-
149
- if watchers = Card[:watchers]
150
- watchers.update_attributes :codename=>nil
151
- watchers.delete!
152
- end
153
-
154
129
  if send = Card[:send]
155
130
  send.update_attributes :codename=>nil
156
131
  send.delete!
157
132
  end
133
+
134
+
158
135
  end
159
136
  end
160
137
 
@@ -0,0 +1,38 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ class WatchersToFollowing < Wagn::CoreMigration
4
+ def up
5
+
6
+ follower_hash = Hash.new { |h, v| h[v] = [] }
7
+
8
+ #NOTE: this migration must find cards in the trash, because the original (1.14.0) migration attempt
9
+ # did not successfully migration to the +*following card but did successfully delete +*watchers cards.
10
+ # Therefore cards migrated using 1.14.0 or 1.14.1 will not have the correct migrations
11
+
12
+ if watcher_card = Card.find_by_key('*watcher')
13
+ Card.find_by_sql("select * from cards where right_id = #{watcher_card.id}").each do |card|
14
+ card.include_set_modules
15
+
16
+ if watched = card.left
17
+ card.item_names.each do |user_name|
18
+ follower_hash[user_name] << watched.name
19
+ end
20
+ end
21
+ end
22
+
23
+ follower_hash.each do |user, items|
24
+ if card=Card.fetch(user) and card.account
25
+ following = card.fetch :trait=>"following", :new=>{}
26
+ items.each { |item| following.add_item item }
27
+ following.save!
28
+ end
29
+ end
30
+ end
31
+
32
+ if watchers = Card[:watchers]
33
+ watchers.update_attributes :codename=>nil
34
+ watchers.delete!
35
+ end
36
+
37
+ end
38
+ end
@@ -1 +1 @@
1
- 20141120120605
1
+ 20141204061304
@@ -0,0 +1,21 @@
1
+ @javascript
2
+ Feature: History
3
+ As an editor
4
+ I want to be able to browse through the history and save an old version as current.
5
+
6
+ Background:
7
+ Given I am signed in as Joe Admin
8
+ Then the card First should not contain "chicken"
9
+
10
+ Scenario: view history and rollback
11
+ When I go to url "/First?view=history"
12
+ Then In the main card content I should see a del with content "egg"
13
+
14
+ When I expand act 2
15
+ And In the main card content I click "Hide changes"
16
+ Then In the main card content I should not see a del with content "egg"
17
+
18
+ When In the main card content I click "Save as current"
19
+ Then the card First should contain "chicken"
20
+
21
+
@@ -0,0 +1,3 @@
1
+ When /^I expand act (\d+)$/ do |index|
2
+ all("a.arrow-right")[-index.to_i].click
3
+ end
@@ -150,7 +150,8 @@ end
150
150
 
151
151
  Then /debug/ do
152
152
  if RUBY_VERSION =~ /^2/
153
- byebug
153
+ require 'pry'
154
+ binding.pry
154
155
  else
155
156
  debugger
156
157
  end
@@ -251,6 +252,14 @@ Then /^In (.*) I should (not )?see a ([^\"]*) with class "([^\"]*)"$/ do |select
251
252
  end
252
253
  end
253
254
 
255
+ Then /^In (.*) I should (not )?see a ([^\"]*) with content "([^\"]*)"$/ do |selection, neg, element, content|
256
+ # checks for existence of a element with a class in a selection context
257
+ element = 'a' if element == 'link'
258
+ within scope_of(selection) do
259
+ page.send( ( neg ? :should_not : :should ), have_css( element, :text=>content ) )
260
+ end
261
+ end
262
+
254
263
  Then /^the "([^"]*)" field should contain "([^"]*)"$/ do |field, value|
255
264
  expect(field_labeled(field).value).to match(/#{value}/)
256
265
  end
@@ -18,9 +18,9 @@ class Card
18
18
 
19
19
  def self.find_all_with_actions_on card_ids, args={}
20
20
  sql = if args[:with_drafts]
21
- 'card_actions.card_id IN (:card_ids) AND ( (draft = 0 OR draft IS null) OR actor_id = :current_user_id)'
21
+ 'card_actions.card_id IN (:card_ids) AND ( (draft is false OR draft IS null) OR actor_id = :current_user_id)'
22
22
  else
23
- 'card_actions.card_id IN (:card_ids) AND ( (draft = 0 OR draft IS null) )'
23
+ 'card_actions.card_id IN (:card_ids) AND ( (draft is false OR draft IS null) )'
24
24
  end
25
25
  Card::Act.joins(:actions).where(sql,
26
26
  {:card_ids => card_ids, :current_user_id=>Card::Auth.current_id }).uniq.order(:id).reverse_order
@@ -160,31 +160,31 @@ class Card
160
160
  # end
161
161
 
162
162
 
163
- def name_diff
163
+ def name_diff opts={}
164
164
  if new_name?
165
- Card::Diff::DiffBuilder.new(old_values[:name],new_values[:name]).complete
165
+ Card::Diff.complete old_values[:name], new_values[:name], opts
166
166
  end
167
167
  end
168
168
 
169
- def cardtype_diff
169
+ def cardtype_diff opts={}
170
170
  if new_type?
171
- Card::Diff::DiffBuilder.new(old_values[:cardtype],new_values[:cardtype]).complete
171
+ Card::Diff.complete old_values[:cardtype], new_values[:cardtype], opts
172
172
  end
173
173
  end
174
174
 
175
- def content_diff diff_type=:expanded
175
+ def content_diff diff_type=:expanded, opts=nil
176
176
  if new_content?
177
177
  if diff_type == :summary
178
- content_diff_builder.summary
178
+ content_diff_builder(opts).summary
179
179
  else
180
- content_diff_builder.complete
180
+ content_diff_builder(opts).complete
181
181
  end
182
182
  end
183
183
  end
184
184
 
185
- def content_diff_builder
185
+ def content_diff_builder opts=nil
186
186
  @content_diff_builder ||= begin
187
- Card::Diff::DiffBuilder.new(old_values[:content], new_values[:content], :compare_html=>false)
187
+ Card::Diff::DiffBuilder.new(old_values[:content], new_values[:content], opts || card.diff_args)
188
188
  end
189
189
  end
190
190
 
@@ -1,12 +1,12 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  module Card::Diff
3
3
 
4
- def diff_complete(a, b)
5
- DiffBuilder.new(a, b).complete
4
+ def self.complete a, b, opts={}
5
+ DiffBuilder.new(a, b, opts).complete
6
6
  end
7
7
 
8
- def diff_summary(a, b)
9
- DiffBuilder.new(a, b).summary
8
+ def self.summary a, b, opts={}
9
+ DiffBuilder.new(a, b, opts).summary
10
10
  end
11
11
 
12
12
  def self.render_added_chunk text
@@ -16,255 +16,414 @@ module Card::Diff
16
16
  def self.render_deleted_chunk text, count=true
17
17
  "<del class='diffdel diff-red'>#{text}</del>"
18
18
  end
19
+
20
+ def self.render_chunk action, text
21
+ case action
22
+ when '+' then render_added_chunk text
23
+ when :added then render_added_chunk text
24
+ when '-' then render_deleted_chunk text
25
+ when :deleted then render_deleted_chunk text
26
+ else text
27
+ end
28
+ end
19
29
 
20
30
  class DiffBuilder
31
+ attr_reader :summary, :complete
32
+
33
+ # diff options
34
+ # :format => :html|:text|:pointer|:raw
35
+ # :html = maintain html structure, but compare only content
36
+ # :text = remove all html tags; compare plain text
37
+ # :pointer = remove all double square brackets
38
+ # :raw = escape html tags and compare everything
39
+ #
40
+ # :summary => {:length=><number> , :joint=><string> }
41
+
21
42
  def initialize(old_version, new_version, opts={})
22
- @old_version, @new_version = old_version, new_version
23
- @opts = opts
24
- @new_version ||= ''
25
- if !opts[:compare_html]
26
- @old_version.gsub! /<[^>]*>/,'' if @old_version
27
- @new_version.gsub! /<[^>]*>/,''
43
+ @new_version = new_version
44
+ @old_version = old_version
45
+ @lcs_opts = lcs_opts_for_format opts[:format]
46
+ @lcs_opts[:summary] = opts[:summary]
47
+ @dels_cnt = 0
48
+ @adds_cnt = 0
49
+
50
+ if not @new_version
51
+ @complete = ''
52
+ @summary = ''
28
53
  else
29
- @old_version = CGI::escapeHTML(@old_version) if @old_version
30
- @new_version = CGI::escapeHTML(@new_version)
54
+ lcs_diff
31
55
  end
32
- @summary = false
33
- @complete = false
34
- @adds = 0
35
- @dels = 0
36
56
  end
37
57
 
38
-
39
58
  def red?
40
- complete and @dels > 0
59
+ @dels_cnt > 0
41
60
  end
61
+
42
62
  def green?
43
- complete and @adds > 0
63
+ @adds_cnt > 0
44
64
  end
45
65
 
46
- def summary max_length = 50, joint = '...'
47
- @summary ||= begin
48
- if @old_version
49
- last_position = 0
50
- remaining_chars = max_length
51
- res = ''
52
- new_aggregated_lcs.each do |change|
53
- if change[:position] > last_position
54
- res += joint
55
- end
56
- res += render_chunk change[:action], change[:text][0..remaining_chars], false
57
- remaining_chars -= change[:text].size
58
- if remaining_chars < 0 # no more space left
59
- res += joint
60
- break
61
- end
62
- last_position = change[:position]
66
+ def lcs_opts_for_format format
67
+ opts = {}
68
+ case format
69
+ when :html
70
+ opts[:exclude] = /^</
71
+ when :text
72
+ opts[:reject] = /^</
73
+ opts[:postprocess] = Proc.new do |word|
74
+ word.gsub("\n",'<br>')
75
+ end
76
+ when :pointer
77
+ opts[:preprocess] = Proc.new do |word|
78
+ word.gsub('[[','').gsub(']]','')
79
+ end
80
+ else #:raw
81
+ opts[:preprocess] = Proc.new do |word|
82
+ CGI::escapeHTML(word)
83
+ end
84
+ end
85
+ opts
86
+ end
87
+
88
+ def lcs_diff
89
+ @lcs = LCS.new(@old_version, @new_version, @lcs_opts)
90
+ @summary = @lcs.summary
91
+ @complete = @lcs.complete
92
+ @dels_cnt = @lcs.dels_cnt
93
+ @adds_cnt = @lcs.adds_cnt
94
+ end
95
+
96
+
97
+ class LCS
98
+ attr_reader :adds_cnt, :dels_cnt
99
+ def initialize old_text, new_text, opts, summary=nil
100
+ @reject_pattern = opts[:reject] # regex; remove match completely from diff
101
+ @exclude_pattern = opts[:exclude] # regex; put back to the result after diff
102
+ @preprocess = opts[:preprocess] # block; called with every word
103
+ @postprocess = opts[:postprocess] # block; called with complete diff
104
+
105
+ @adds_cnt = 0
106
+ @dels_cnt = 0
107
+
108
+ @splitters = %w( <[^>]+> \[\[[^\]]+\]\] \{\{[^}]+\}\} \s+ )
109
+ @disjunction_pattern = /^\s/
110
+ @summary ||= Summary.new opts[:summary]
111
+ if not old_text
112
+ list = split_and_preprocess(new_text)
113
+ if @exclude_pattern
114
+ list = list.reject{ |word| word.match @exclude_pattern }
63
115
  end
64
- res
116
+ text = postprocess list.join
117
+ @result = added_chunk text
118
+ @summary.add text
65
119
  else
66
- res = @new_version[0..max_length]
67
- res += joint if @new_version.size > max_length
68
- added_chunk(res, false)
120
+ init_diff old_text, new_text
121
+ run_diff
69
122
  end
70
123
  end
71
- end
72
-
73
- def complete
74
- @complete ||= begin
75
- clear_stats
76
- if @old_version
77
- if @old_version.size < 1000
78
- better_complete_lcs_diff
124
+
125
+ def summary
126
+ @summary.result
127
+ end
128
+
129
+ def complete
130
+ @result
131
+ end
132
+
133
+ private
134
+
135
+ def init_diff old_text, new_text
136
+ @adds = []
137
+ @dels = []
138
+ @result = ''
139
+ old_words, old_ex = separate_comparables_from_excludees old_text
140
+ new_words, new_ex = separate_comparables_from_excludees new_text
141
+
142
+ @words = {
143
+ :old => old_words,
144
+ :new => new_words
145
+ }
146
+ @excludees = {
147
+ :old => ExcludeeIterator.new(old_ex),
148
+ :new => ExcludeeIterator.new(new_ex)
149
+ }
150
+ end
151
+
152
+ def run_diff
153
+ prev_action = nil
154
+ ::Diff::LCS.traverse_balanced(@words[:old], @words[:new]) do |word|
155
+
156
+ if prev_action
157
+ if prev_action != word.action and
158
+ !(prev_action == '-' and word.action == '!') and
159
+ !(prev_action == '!' and word.action == '+')
160
+
161
+ # delete and/or add section stops here; write changes to result
162
+ write_dels
163
+ write_adds
164
+
165
+ write_excludees # new neutral section starts, we can just write excludees to result
166
+
167
+ else # current word belongs to edit of previous word
168
+ case word.action
169
+ when '-'
170
+ del_old_excludees
171
+ when '+'
172
+ add_new_excludees
173
+ when '!'
174
+ del_old_excludees
175
+ add_new_excludees
176
+ else
177
+ write_excludees
178
+ end
179
+ end
79
180
  else
80
- fast_diff
181
+ write_excludees
81
182
  end
82
- else
83
- added_chunk(@new_version)
84
- end
183
+
184
+ process_word word
185
+ prev_action = word.action
186
+ end
187
+ write_dels
188
+ write_adds
189
+ write_excludees
190
+
191
+ @result = postprocess @result
192
+ end
193
+
194
+
195
+ def added_chunk text, count=true
196
+ @adds_cnt += 1 if count
197
+ Card::Diff.render_added_chunk text
85
198
  end
86
- end
87
-
88
- private
89
-
90
- def clear_stats
91
- @adds = 0
92
- @dels = 0
93
- end
94
-
95
- def added_chunk text, count=true
96
- @adds += 1 if count
97
- Card::Diff.render_added_chunk text
98
- end
99
-
100
- def deleted_chunk text, count=true
101
- @dels += 1 if count
102
- Card::Diff.render_deleted_chunk text
103
- end
104
-
105
199
 
106
- def render_chunk action, text, count=true
107
- case action
108
- when '+' then added_chunk(text,count)
109
- when :added then added_chunk(text,count)
110
- when '-' then deleted_chunk(text,count)
111
- when :deleted then deleted_chunk(text,count)
112
- else text
200
+ def deleted_chunk text, count=true
201
+ @dels_cnt += 1 if count
202
+ Card::Diff.render_deleted_chunk text
113
203
  end
114
- end
115
204
 
116
-
117
- def better_complete_lcs_diff old_v=@old_version, new_v=@new_version
118
- old_v = old_v.split(' ')
119
- new_v = new_v.split(' ')
120
- res = ''
121
- dels = []
122
- adds = []
123
- prev_action = nil
124
- ::Diff::LCS.traverse_balanced(old_v, new_v) do |chunk|
125
- if prev_action and prev_action != chunk.action and
126
- !(prev_action == '-' and chunk.action == '!') and
127
- !(prev_action == '!' and chunk.action == '+')
128
-
129
- if dels.present?
130
- res << deleted_chunk(dels.join(' '))
131
- dels = []
205
+
206
+ def write_unchanged text
207
+ @result << text
208
+ @summary.omit
209
+ end
210
+
211
+ def write_dels
212
+ if !@dels.empty?
213
+ @result << deleted_chunk(@dels.join)
214
+ @summary.delete @dels.join
215
+ @dels = []
216
+ end
217
+ end
218
+
219
+ def write_adds
220
+ if !@adds.empty?
221
+ @result << added_chunk(@adds.join)
222
+ @summary.add @adds.join
223
+ @adds = []
224
+ end
225
+ end
226
+
227
+ def write_excludees
228
+ while ex = @excludees[:new].next
229
+ @result << ex[:element]
230
+ end
231
+ end
232
+
233
+ def del_old_excludees
234
+ while ex = @excludees[:old].next
235
+ if ex[:type] == :disjunction
236
+ @dels << ex[:element]
237
+ else
238
+ write_dels
239
+ @result << ex[:element]
132
240
  end
133
- if !adds.empty?
134
- res << added_chunk(adds.join(' '))
135
- adds = []
241
+ end
242
+ end
243
+
244
+ def add_new_excludees
245
+ while ex = @excludees[:new].next
246
+ if ex[:type] == :disjunction
247
+ @adds << ex[:element]
248
+ else
249
+ write_adds
250
+ @result << ex[:element]
136
251
  end
137
252
  end
138
-
139
- case chunk.action
140
- when '-' then dels << chunk.old_element
141
- when '+' then adds << chunk.new_element
142
- when '!'
143
- dels << chunk.old_element
144
- adds << chunk.new_element
253
+ end
254
+
255
+ def process_word word
256
+ process_element word.old_element, word.new_element, word.action
257
+ end
258
+
259
+ def process_element old_element, new_element, action
260
+ case action
261
+ when '-'
262
+ @dels << old_element
263
+ @excludees[:old].word_step
264
+ when '+'
265
+ @adds << new_element
266
+ @excludees[:new].word_step
267
+ when '!'
268
+ @dels << old_element
269
+ @adds << new_element
270
+ @excludees[:old].word_step
271
+ @excludees[:new].word_step
145
272
  else
146
- res += ' ' + chunk.new_element
273
+ write_unchanged new_element
274
+ @excludees[:new].word_step
147
275
  end
148
- prev_action = chunk.action
149
276
  end
150
- res += deleted_chunk(dels.join(' ')) if dels.present?
151
- res += added_chunk(adds.join(' ')) if adds.present?
152
- res
153
- end
154
-
155
-
156
-
157
- def complete_lcs_diff old_v=@old_version, new_v=@new_version
158
- last_position = 0
159
- res = ''
160
- dels = ''
161
- adds = ''
162
- prev_action = nil
163
- ::Diff::LCS.traverse_balanced(old_v, new_v) do |chunk|
164
- if prev_action and prev_action != chunk.action and
165
- !(prev_action == '-' and chunk.action == '!') and
166
- !(prev_action == '!' and chunk.action == '+')
167
-
168
- if dels.present?
169
- res += deleted_chunk(dels)
170
- dels = ''
171
- end
172
- if !adds.empty?
173
- res += added_chunk(adds)
174
- adds = ''
277
+
278
+ def separate_comparables_from_excludees text
279
+ # return two arrays, one with all words, one with pairs (index in word list, html_tag)
280
+ list = split_and_preprocess text
281
+ if @exclude_pattern
282
+ list.each_with_index.inject([[],[]]) do |res, pair|
283
+ element, index = pair
284
+ if element.match @disjunction_pattern
285
+ res[1] << {:chunk_index=>index, :element=>element, :type=>:disjunction}
286
+ elsif element.match @exclude_pattern
287
+ res[1] << {:chunk_index=>index, :element=>element, :type=>:excludee}
288
+ else
289
+ res[0] << element
290
+ end
291
+ res
175
292
  end
176
- end
177
-
178
- case chunk.action
179
- when '-' then dels += chunk.old_element
180
- when '+' then adds += chunk.new_element
181
- when '!'
182
- dels += chunk.old_element
183
- adds += chunk.new_element
184
293
  else
185
- res += chunk.new_element
294
+ [list, []]
186
295
  end
187
- prev_action = chunk.action
188
296
  end
189
- res += deleted_chunk(dels) if dels.present?
190
- res += added_chunk(adds) if adds.present?
191
- res
192
- end
193
-
194
- def complete_diffy_diff
195
- new_diffy.to_s(:html)
196
- end
197
297
 
198
- # combines diffy and lcs:
199
- # find with diffy line changes
200
- # whenever added lines follow immediately after deleted lines compare them with lcs
201
- def fast_diff
202
- lines = { :deleted => [], :added=>[], :unchanged=>[], :eof=>[] }
203
- prev_action = nil
204
- res = ''
205
- inspect = false
206
- new_diffy.each_chunk do |line|
207
- action = case line
208
- when /^\+/ then :added
209
- when /^-/ then :deleted
210
- when /^ / then :unchanged
211
- else
212
- next
213
- end
214
- lines[action] << line.sub(/^./,'')
215
- if action == :added and prev_action == :deleted
216
- inspect = true
298
+ def split_and_preprocess text
299
+ splitted = split_to_list_of_words(text).select do |s|
300
+ s.size > 0 and (!@reject_pattern or !s.match @reject_pattern)
217
301
  end
302
+ @preprocess ? splitted.map {|s| @preprocess.call(s) } : splitted
303
+ end
218
304
 
219
- if inspect
220
- if action != :added
221
- res += better_complete_lcs_diff lines[:deleted].join, lines[:added].join
222
- inspect = false
223
- lines[:deleted].clear
224
- lines[:added].clear
225
- end
226
- elsif prev_action and action != prev_action
227
- text = lines[prev_action].join
228
- res += render_chunk prev_action, text
229
- lines[prev_action].clear
305
+ def split_to_list_of_words text
306
+ split_regex = /(#{@splitters.join '|'})/
307
+ text.split(split_regex)
308
+ end
309
+
310
+ def preprocess text
311
+ if @preprocess
312
+ @preprocess.call(text)
313
+ else
314
+ text
230
315
  end
231
- prev_action = action
232
316
  end
233
317
 
234
- res += if inspect
235
- better_complete_lcs_diff lines[:deleted].join, lines[:added].join
236
- elsif lines[prev_action].present?
237
- render_chunk prev_action, lines[prev_action].join
238
- else
239
- ''
318
+ def postprocess text
319
+ if @postprocess
320
+ @postprocess.call(text)
321
+ else
322
+ text
323
+ end
240
324
  end
241
- end
242
-
243
-
244
- def new_aggregated_lcs
245
- new_lcs.inject([]) do |res, change_block|
246
- last_action = nil
247
- change_block.each do |change|
248
- if change.action != last_action
249
- res << { :position => change.position,
250
- :action => change.action,
251
- :text => change.element
252
- }
253
- else
254
- res.last[:text] += change.element
325
+
326
+
327
+ class Summary
328
+ def initialize opts
329
+ opts ||= {}
330
+ @remaining_chars = opts[:length] || 50
331
+ @joint = opts[:joint] || '...'
332
+
333
+ @summary = nil
334
+ @chunks = []
335
+ end
336
+
337
+ def result
338
+ @summary ||= render_chunks
339
+ end
340
+
341
+ def add text
342
+ add_chunk text, :added
343
+ end
344
+
345
+ def delete text
346
+ add_chunk text, :deleted
347
+ end
348
+
349
+ def omit
350
+ if @chunks.empty? or @chunks.last[:action] != :ellipsis
351
+ add_chunk @joint, :ellipsis
352
+ end
353
+ end
354
+
355
+ private
356
+
357
+ def add_chunk text, action
358
+ if @remaining_chars > 0
359
+ @chunks << {:action => action, :text=>text}
360
+ @remaining_chars -= text.size
361
+ end
362
+ end
363
+
364
+ def render_chunks
365
+ truncate_overlap
366
+ @chunks.map do |chunk|
367
+ Card::Diff.render_chunk chunk[:action], chunk[:text]
368
+ end.join
369
+ end
370
+
371
+ def truncate_overlap
372
+ if @remaining_chars < 0
373
+ if @chunks.last[:action] == :ellipsis
374
+ @chunks.pop
375
+ @remaining_chars += @joint.size
376
+ end
377
+
378
+ index = @chunks.size - 1
379
+ while @remaining_chars < @joint.size and index >= 0
380
+ if @remaining_chars + @chunks[index][:text].size == @joint.size # d
381
+ @chunks.pop
382
+ if index-1 >= 0
383
+ if @chunks[index-1][:action] == :added
384
+ @chunks << {:action => :ellipsis, :text=>@joint}
385
+ elsif @chunks[index-1][:action] == :deleted
386
+ @chunks << {:action => :added, :text=>@joint}
387
+ end
388
+ end
389
+ break
390
+ elsif @remaining_chars + @chunks[index][:text].size > @joint.size
391
+ @chunks[index][:text] = @chunks[index][:text][0..(@remaining_chars-@joint.size-1)]
392
+ @chunks[index][:text] += @joint
393
+ break
394
+ else
395
+ @remaining_chars += @chunks[index][:text].size
396
+ @chunks.delete_at(index)
397
+ end
398
+ index -= 1
399
+ end
400
+ end
401
+ end
402
+
403
+ end
404
+
405
+ class ExcludeeIterator
406
+ def initialize list
407
+ @list = list
408
+ @index = 0
409
+ @chunk_index = 0
410
+ end
411
+
412
+ def word_step
413
+ @chunk_index += 1
414
+ end
415
+
416
+ def next
417
+ if @index < @list.size and @list[@index][:chunk_index] == @chunk_index
418
+ res = @list[@index]
419
+ @index += 1
420
+ @chunk_index +=1
421
+ res
255
422
  end
256
- last_action = change.action
257
423
  end
258
- res
259
424
  end
260
- end
261
-
262
- def new_diffy
263
- ::Diffy::Diff.new(@old_version, @new_version)
264
- end
265
-
266
- def new_lcs
267
- ::Diff::LCS.diff(@old_version,@new_version)
425
+
268
426
  end
269
427
  end
270
428
  end
429
+