card 1.18.0 → 1.18.1

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 (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