card 1.18.0 → 1.18.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/card.gemspec +20 -16
  4. data/db/migrate_core_cards/20150202143810_import_bootstrap_layout.rb +1 -1
  5. data/db/schema.rb +110 -92
  6. data/lib/card.rb +1 -0
  7. data/lib/card/content.rb +4 -73
  8. data/lib/card/content/chunk.rb +119 -0
  9. data/lib/card/content/parser.rb +75 -0
  10. data/lib/card/diff.rb +25 -398
  11. data/lib/card/diff/lcs.rb +247 -0
  12. data/lib/card/diff/result.rb +131 -0
  13. data/lib/card/director_register.rb +5 -0
  14. data/lib/card/query/attributes.rb +19 -13
  15. data/lib/card/set/event.rb +2 -1
  16. data/lib/card/set_pattern.rb +4 -2
  17. data/lib/card/spec_helper.rb +7 -1
  18. data/lib/card/stage_director.rb +33 -5
  19. data/lib/card/subcards.rb +11 -3
  20. data/lib/card/subdirector_array.rb +14 -1
  21. data/lib/cardio.rb +8 -5
  22. data/mod/01_core/chunk/include.rb +2 -2
  23. data/mod/01_core/chunk/link.rb +3 -3
  24. data/mod/01_core/chunk/literal.rb +20 -14
  25. data/mod/01_core/chunk/query_reference.rb +2 -2
  26. data/mod/01_core/chunk/reference.rb +47 -38
  27. data/mod/01_core/chunk/uri.rb +17 -13
  28. data/mod/01_core/format/html_format.rb +0 -2
  29. data/mod/01_core/set/all/actify.rb +12 -1
  30. data/mod/01_core/set/all/collection.rb +4 -4
  31. data/mod/01_core/set/all/fetch.rb +0 -27
  32. data/mod/01_core/set/all/name.rb +33 -12
  33. data/mod/01_core/set/all/pattern.rb +2 -6
  34. data/mod/01_core/set/all/phases.rb +0 -1
  35. data/mod/01_core/set/all/references.rb +2 -2
  36. data/mod/01_core/set/all/rules.rb +10 -3
  37. data/mod/01_core/set/all/tracked_attributes.rb +0 -1
  38. data/mod/01_core/set/all/type.rb +0 -14
  39. data/mod/01_core/spec/chunk/literal_spec.rb +1 -1
  40. data/mod/01_core/spec/chunk/uri_spec.rb +204 -201
  41. data/mod/01_core/spec/set/all/type_spec.rb +3 -1
  42. data/mod/01_history/lib/card/action.rb +7 -9
  43. data/mod/01_history/set/all/history.rb +6 -1
  44. data/mod/02_basic_types/set/all/all_csv.rb +1 -1
  45. data/mod/02_basic_types/set/type/pointer.rb +20 -9
  46. data/mod/03_machines/lib/javascript/wagn.js.coffee +1 -1
  47. data/mod/04_settings/set/right/structure.rb +7 -1
  48. data/mod/05_email/set/right/follow.rb +22 -22
  49. data/mod/05_email/set/type_plus_right/user/follow.rb +25 -26
  50. data/mod/05_standard/set/all/rich_html/wrapper.rb +12 -6
  51. data/mod/05_standard/set/rstar/rules_editor.rb +6 -4
  52. data/mod/05_standard/set/self/all.rb +0 -10
  53. data/mod/05_standard/set/self/stats.rb +6 -15
  54. data/mod/05_standard/set/type/set.rb +0 -6
  55. data/mod/05_standard/spec/chunk/include_spec.rb +2 -2
  56. data/mod/05_standard/spec/chunk/link_spec.rb +1 -1
  57. data/mod/05_standard/spec/chunk/query_reference_spec.rb +5 -4
  58. data/spec/lib/card/chunk_spec.rb +7 -5
  59. data/spec/lib/card/content_spec.rb +11 -11
  60. data/spec/lib/card/diff_spec.rb +4 -4
  61. data/spec/lib/card/stage_director_spec.rb +56 -0
  62. data/spec/lib/card/subcards_spec.rb +0 -1
  63. data/spec/models/card/type_transition_spec.rb +5 -42
  64. metadata +12 -23
  65. data/lib/card/chunk.rb +0 -122
@@ -0,0 +1,75 @@
1
+ require_dependency 'card/content/chunk'
2
+
3
+ class Card
4
+ class Content
5
+ class Parser
6
+ def initialize chunk_list
7
+ @chunk_list = chunk_list
8
+ end
9
+ def parse content, content_object
10
+ chunks = []
11
+ return chunks unless content.is_a? String
12
+
13
+ position = last_position = 0
14
+ prefix_regexp = Chunk.get_prefix_regexp @chunk_list
15
+ interval_string = ''
16
+
17
+ while (prefix_match = content[position..-1].match(prefix_regexp))
18
+ prefix = prefix_match[0]
19
+ # prefix of matched chunk
20
+ chunk_start = prefix_match.begin(0) + position
21
+ # content index of beginning of chunk
22
+ if prefix_match.begin(0) > 0
23
+ # if matched chunk is not beginning of test string
24
+ interval_string += content[position..chunk_start - 1]
25
+ # hold onto the non-chunk part of the string
26
+ end
27
+
28
+ chunk_class = Chunk.find_class_by_prefix prefix
29
+ # get the chunk class from the prefix
30
+ match, offset =
31
+ chunk_class.full_match content[chunk_start..-1], prefix
32
+ # see whether the full chunk actually matches
33
+ # (as opposed to bogus prefix)
34
+ context_ok = chunk_class.context_ok? content, chunk_start
35
+ # make sure there aren't contextual reasons for ignoring this chunk
36
+ position = chunk_start
37
+ # move scanning position up to beginning of chunk
38
+
39
+ if match
40
+ # we have a chunk match
41
+ position += (match.end(0) - offset.to_i)
42
+ # move scanning position up to end of chunk
43
+ if context_ok
44
+ chunks << interval_string unless interval_string.empty?
45
+ # add the nonchunk string to the chunk list
46
+ chunks << chunk_class.new(match, content_object)
47
+ # add the chunk to the chunk list
48
+ interval_string = ''
49
+ # reset interval string for next go-round
50
+ last_position = position
51
+ # note that the end of the chunk was the last place where a
52
+ # chunk was found (so far)
53
+ end
54
+ else
55
+ position += 1
56
+ # no match. look at the next character
57
+ end
58
+
59
+ next unless !match || !context_ok
60
+ interval_string += content[chunk_start..position - 1]
61
+ # moving beyond the alleged chunk.
62
+ # append failed string to "nonchunk" string
63
+ end
64
+
65
+ if chunks.any? && last_position < content.size
66
+ remainder = content[last_position..-1]
67
+ # handle any leftover nonchunk string at the end of content
68
+ chunks << remainder
69
+ end
70
+
71
+ chunks
72
+ end
73
+ end
74
+ end
75
+ end
data/lib/card/diff.rb CHANGED
@@ -1,34 +1,27 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
- module Card::Diff
4
- def self.complete a, b, opts={}
5
- DiffBuilder.new(a, b, opts).complete
6
- end
7
-
8
- def self.summary a, b, opts={}
9
- DiffBuilder.new(a, b, opts).summary
10
- end
3
+ class Card
4
+ class Diff
5
+ class << self
6
+ def complete a, b, opts={}
7
+ Card::Diff.new(a, b, opts).complete
8
+ end
11
9
 
12
- def self.render_added_chunk text
13
- "<ins class='diffins diff-green'>#{text}</ins>"
14
- end
10
+ def summary a, b, opts={}
11
+ Card::Diff.new(a, b, opts).summary
12
+ end
15
13
 
16
- def self.render_deleted_chunk text, _count=true
17
- "<del class='diffdel diff-red'>#{text}</del>"
18
- end
14
+ def render_added_chunk text
15
+ "<ins class='diffins diff-green'>#{text}</ins>"
16
+ end
19
17
 
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
18
+ def render_deleted_chunk text, _count=true
19
+ "<del class='diffdel diff-red'>#{text}</del>"
20
+ end
27
21
  end
28
- end
29
22
 
30
- class DiffBuilder
31
- attr_reader :summary, :complete
23
+ attr_reader :result
24
+ delegate :summary, :complete, to: :result
32
25
 
33
26
  # diff options
34
27
  # :format => :html|:text|:pointer|:raw
@@ -40,29 +33,23 @@ module Card::Diff
40
33
  # summary: {length: <number> , joint: <string> }
41
34
 
42
35
  def initialize old_version, new_version, opts={}
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 !@new_version
51
- @complete = ''
52
- @summary = ''
53
- else
54
- lcs_diff
36
+ @result = Result.new opts[:summary]
37
+ if new_version
38
+ lcs_opts = lcs_opts_for_format opts[:format]
39
+ LCS.new(lcs_opts).run(old_version, new_version, @result)
55
40
  end
56
41
  end
57
42
 
58
43
  def red?
59
- @dels_cnt > 0
44
+ @result.dels_cnt > 0
60
45
  end
61
46
 
62
47
  def green?
63
- @adds_cnt > 0
48
+ @result.adds_cnt > 0
64
49
  end
65
50
 
51
+ private
52
+
66
53
  def lcs_opts_for_format format
67
54
  opts = {}
68
55
  case format
@@ -78,365 +65,5 @@ module Card::Diff
78
65
  end
79
66
  opts
80
67
  end
81
-
82
- def lcs_diff
83
- @lcs = LCS.new(@old_version, @new_version, @lcs_opts)
84
- @summary = @lcs.summary
85
- @complete = @lcs.complete
86
- @dels_cnt = @lcs.dels_cnt
87
- @adds_cnt = @lcs.adds_cnt
88
- end
89
-
90
- class LCS
91
- attr_reader :adds_cnt, :dels_cnt
92
- def initialize old_text, new_text, opts, _summary=nil
93
- # regex; remove match completely from diff
94
- @reject_pattern = opts[:reject]
95
-
96
- # regex; put back to the result after diff
97
- @exclude_pattern = opts[:exclude]
98
-
99
- @preprocess = opts[:preprocess] # block; called with every word
100
- @postprocess = opts[:postprocess] # block; called with complete diff
101
-
102
- @adds_cnt = 0
103
- @dels_cnt = 0
104
-
105
- @splitters = %w( <[^>]+> \[\[[^\]]+\]\] \{\{[^}]+\}\} \s+ )
106
- @disjunction_pattern = /^\s/
107
- @summary ||= Summary.new opts[:summary]
108
- if !old_text
109
- list = split_and_preprocess(new_text)
110
- if @exclude_pattern
111
- list = list.reject { |word| word.match @exclude_pattern }
112
- end
113
- text = postprocess list.join
114
- @result = added_chunk text
115
- @summary.add text
116
- else
117
- init_diff old_text, new_text
118
- run_diff
119
- end
120
- end
121
-
122
- def summary
123
- @summary.result
124
- end
125
-
126
- def complete
127
- @result
128
- end
129
-
130
- private
131
-
132
- def init_diff old_text, new_text
133
- @adds = []
134
- @dels = []
135
- @result = ''
136
- old_words, old_ex = separate_comparables_from_excludees old_text
137
- new_words, new_ex = separate_comparables_from_excludees new_text
138
-
139
- @words = {
140
- old: old_words,
141
- new: new_words
142
- }
143
- @excludees = {
144
- old: ExcludeeIterator.new(old_ex),
145
- new: ExcludeeIterator.new(new_ex)
146
- }
147
- end
148
-
149
- def run_diff
150
- prev_action = nil
151
- ::Diff::LCS.traverse_balanced(@words[:old], @words[:new]) do |word|
152
- if prev_action
153
- if prev_action != word.action &&
154
- !(prev_action == '-' && word.action == '!') &&
155
- !(prev_action == '!' && word.action == '+')
156
-
157
- # delete and/or add section stops here; write changes to result
158
- write_dels
159
- write_adds
160
-
161
- # new neutral section starts
162
- # we can just write excludees to result
163
- write_excludees
164
-
165
- else # current word belongs to edit of previous word
166
- case word.action
167
- when '-'
168
- del_old_excludees
169
- when '+'
170
- add_new_excludees
171
- when '!'
172
- del_old_excludees
173
- add_new_excludees
174
- else
175
- write_excludees
176
- end
177
- end
178
- else
179
- write_excludees
180
- end
181
-
182
- process_word word
183
- prev_action = word.action
184
- end
185
- write_dels
186
- write_adds
187
- write_excludees
188
-
189
- @result = postprocess @result
190
- end
191
-
192
- def added_chunk text, count=true
193
- @adds_cnt += 1 if count
194
- Card::Diff.render_added_chunk text
195
- end
196
-
197
- def deleted_chunk text, count=true
198
- @dels_cnt += 1 if count
199
- Card::Diff.render_deleted_chunk text
200
- end
201
-
202
- def write_unchanged text
203
- @result << text
204
- @summary.omit
205
- end
206
-
207
- def write_dels
208
- unless @dels.empty?
209
- @result << deleted_chunk(@dels.join)
210
- @summary.delete @dels.join
211
- @dels = []
212
- end
213
- end
214
-
215
- def write_adds
216
- unless @adds.empty?
217
- @result << added_chunk(@adds.join)
218
- @summary.add @adds.join
219
- @adds = []
220
- end
221
- end
222
-
223
- def write_excludees
224
- while (ex = @excludees[:new].next)
225
- @result << ex[:element]
226
- end
227
- end
228
-
229
- def del_old_excludees
230
- while (ex = @excludees[:old].next)
231
- if ex[:type] == :disjunction
232
- @dels << ex[:element]
233
- else
234
- write_dels
235
- @result << ex[:element]
236
- end
237
- end
238
- end
239
-
240
- def add_new_excludees
241
- while (ex = @excludees[:new].next)
242
- if ex[:type] == :disjunction
243
- @adds << ex[:element]
244
- else
245
- write_adds
246
- @result << ex[:element]
247
- end
248
- end
249
- end
250
-
251
- def process_word word
252
- process_element word.old_element, word.new_element, word.action
253
- end
254
-
255
- def process_element old_element, new_element, action
256
- case action
257
- when '-'
258
- minus old_element
259
- when '+'
260
- plus new_element
261
- when '!'
262
- minus old_element
263
- plus new_element
264
- else
265
- write_unchanged new_element
266
- @excludees[:new].word_step
267
- end
268
- end
269
-
270
- def plus new_element
271
- @adds << new_element
272
- @excludees[:new].word_step
273
- end
274
-
275
- def minus old_element
276
- @dels << old_element
277
- @excludees[:old].word_step
278
- end
279
-
280
- def separate_comparables_from_excludees text
281
- # return two arrays, one with all words, one with pairs
282
- # (index in word list, html_tag)
283
- list = split_and_preprocess text
284
- if @exclude_pattern
285
- check_exclude_and_disjunction_pattern list
286
- else
287
- [list, []]
288
- end
289
- end
290
-
291
- def check_exclude_and_disjunction_pattern list
292
- list.each_with_index.each_with_object([[], []]) do |pair, res|
293
- element, index = pair
294
- if element.match @disjunction_pattern
295
- res[1] << { chunk_index: index, element: element,
296
- type: :disjunction }
297
- elsif element.match @exclude_pattern
298
- res[1] << { chunk_index: index, element: element, type:
299
- :excludee }
300
- else
301
- res[0] << element
302
- end
303
- end
304
- end
305
-
306
- def split_and_preprocess text
307
- splitted = split_to_list_of_words(text).select do |s|
308
- !s.empty? && (!@reject_pattern || !s.match(@reject_pattern))
309
- end
310
- @preprocess ? splitted.map { |s| @preprocess.call(s) } : splitted
311
- end
312
-
313
- def split_to_list_of_words text
314
- split_regex = /(#{@splitters.join '|'})/
315
- text.split(split_regex)
316
- end
317
-
318
- def preprocess text
319
- if @preprocess
320
- @preprocess.call(text)
321
- else
322
- text
323
- end
324
- end
325
-
326
- def postprocess text
327
- if @postprocess
328
- @postprocess.call(text)
329
- else
330
- text
331
- end
332
- end
333
-
334
- class Summary
335
- def initialize opts
336
- opts ||= {}
337
- @remaining_chars = opts[:length] || 50
338
- @joint = opts[:joint] || '...'
339
-
340
- @summary = nil
341
- @chunks = []
342
- end
343
-
344
- def result
345
- @summary ||= render_chunks
346
- end
347
-
348
- def add text
349
- add_chunk text, :added
350
- end
351
-
352
- def delete text
353
- add_chunk text, :deleted
354
- end
355
-
356
- def omit
357
- if @chunks.empty? || @chunks.last[:action] != :ellipsis
358
- add_chunk @joint, :ellipsis
359
- end
360
- end
361
-
362
- private
363
-
364
- def add_chunk text, action
365
- if @remaining_chars > 0
366
- @chunks << { action: action, text: text }
367
- @remaining_chars -= text.size
368
- end
369
- end
370
-
371
- def render_chunks
372
- truncate_overlap
373
- @chunks.map do |chunk|
374
- Card::Diff.render_chunk chunk[:action], chunk[:text]
375
- end.join
376
- end
377
-
378
- def truncate_overlap
379
- if @remaining_chars < 0
380
- if @chunks.last[:action] == :ellipsis
381
- @chunks.pop
382
- @remaining_chars += @joint.size
383
- end
384
-
385
- index = @chunks.size - 1
386
- while @remaining_chars < @joint.size && index >= 0
387
- if @remaining_chars + @chunks[index][:text].size == @joint.size
388
- replace_with_joint index
389
- break
390
- elsif @remaining_chars + @chunks[index][:text].size > @joint.size
391
- cut_with_joint index
392
- break
393
- else
394
- @remaining_chars += @chunks[index][:text].size
395
- @chunks.delete_at(index)
396
- end
397
- index -= 1
398
- end
399
- end
400
- end
401
-
402
- def cut_with_joint index
403
- @chunks[index][:text] =
404
- @chunks[index][:text][0..(@remaining_chars - @joint.size - 1)]
405
- @chunks[index][:text] += @joint
406
- end
407
-
408
- def replace_with_joint index
409
- @chunks.pop
410
- if index - 1 >= 0
411
- if @chunks[index - 1][:action] == :added
412
- @chunks << { action: :ellipsis, text: @joint }
413
- elsif @chunks[index - 1][:action] == :deleted
414
- @chunks << { action: :added, text: @joint }
415
- end
416
- end
417
- end
418
- end
419
-
420
- class ExcludeeIterator
421
- def initialize list
422
- @list = list
423
- @index = 0
424
- @chunk_index = 0
425
- end
426
-
427
- def word_step
428
- @chunk_index += 1
429
- end
430
-
431
- def next
432
- if @index < @list.size && @list[@index][:chunk_index] == @chunk_index
433
- res = @list[@index]
434
- @index += 1
435
- @chunk_index += 1
436
- res
437
- end
438
- end
439
- end
440
- end
441
68
  end
442
69
  end