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.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/card.gemspec +20 -16
- data/db/migrate_core_cards/20150202143810_import_bootstrap_layout.rb +1 -1
- data/db/schema.rb +110 -92
- data/lib/card.rb +1 -0
- data/lib/card/content.rb +4 -73
- data/lib/card/content/chunk.rb +119 -0
- data/lib/card/content/parser.rb +75 -0
- data/lib/card/diff.rb +25 -398
- data/lib/card/diff/lcs.rb +247 -0
- data/lib/card/diff/result.rb +131 -0
- data/lib/card/director_register.rb +5 -0
- data/lib/card/query/attributes.rb +19 -13
- data/lib/card/set/event.rb +2 -1
- data/lib/card/set_pattern.rb +4 -2
- data/lib/card/spec_helper.rb +7 -1
- data/lib/card/stage_director.rb +33 -5
- data/lib/card/subcards.rb +11 -3
- data/lib/card/subdirector_array.rb +14 -1
- data/lib/cardio.rb +8 -5
- data/mod/01_core/chunk/include.rb +2 -2
- data/mod/01_core/chunk/link.rb +3 -3
- data/mod/01_core/chunk/literal.rb +20 -14
- data/mod/01_core/chunk/query_reference.rb +2 -2
- data/mod/01_core/chunk/reference.rb +47 -38
- data/mod/01_core/chunk/uri.rb +17 -13
- data/mod/01_core/format/html_format.rb +0 -2
- data/mod/01_core/set/all/actify.rb +12 -1
- data/mod/01_core/set/all/collection.rb +4 -4
- data/mod/01_core/set/all/fetch.rb +0 -27
- data/mod/01_core/set/all/name.rb +33 -12
- data/mod/01_core/set/all/pattern.rb +2 -6
- data/mod/01_core/set/all/phases.rb +0 -1
- data/mod/01_core/set/all/references.rb +2 -2
- data/mod/01_core/set/all/rules.rb +10 -3
- data/mod/01_core/set/all/tracked_attributes.rb +0 -1
- data/mod/01_core/set/all/type.rb +0 -14
- data/mod/01_core/spec/chunk/literal_spec.rb +1 -1
- data/mod/01_core/spec/chunk/uri_spec.rb +204 -201
- data/mod/01_core/spec/set/all/type_spec.rb +3 -1
- data/mod/01_history/lib/card/action.rb +7 -9
- data/mod/01_history/set/all/history.rb +6 -1
- data/mod/02_basic_types/set/all/all_csv.rb +1 -1
- data/mod/02_basic_types/set/type/pointer.rb +20 -9
- data/mod/03_machines/lib/javascript/wagn.js.coffee +1 -1
- data/mod/04_settings/set/right/structure.rb +7 -1
- data/mod/05_email/set/right/follow.rb +22 -22
- data/mod/05_email/set/type_plus_right/user/follow.rb +25 -26
- data/mod/05_standard/set/all/rich_html/wrapper.rb +12 -6
- data/mod/05_standard/set/rstar/rules_editor.rb +6 -4
- data/mod/05_standard/set/self/all.rb +0 -10
- data/mod/05_standard/set/self/stats.rb +6 -15
- data/mod/05_standard/set/type/set.rb +0 -6
- data/mod/05_standard/spec/chunk/include_spec.rb +2 -2
- data/mod/05_standard/spec/chunk/link_spec.rb +1 -1
- data/mod/05_standard/spec/chunk/query_reference_spec.rb +5 -4
- data/spec/lib/card/chunk_spec.rb +7 -5
- data/spec/lib/card/content_spec.rb +11 -11
- data/spec/lib/card/diff_spec.rb +4 -4
- data/spec/lib/card/stage_director_spec.rb +56 -0
- data/spec/lib/card/subcards_spec.rb +0 -1
- data/spec/models/card/type_transition_spec.rb +5 -42
- metadata +12 -23
- 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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
10
|
+
def summary a, b, opts={}
|
11
|
+
Card::Diff.new(a, b, opts).summary
|
12
|
+
end
|
15
13
|
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
def render_added_chunk text
|
15
|
+
"<ins class='diffins diff-green'>#{text}</ins>"
|
16
|
+
end
|
19
17
|
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
31
|
-
|
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
|
-
@
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|