cc 1.2.1 → 1.4.0

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 (9) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -3
  3. data/cc.rb +1 -0
  4. data/monkey-patch.rb +375 -1
  5. data/text-client.rb +297 -0
  6. data/text-server.rb +403 -0
  7. data/text-tools.rb +418 -0
  8. data/tree.rb +184 -184
  9. metadata +5 -2
data/text-tools.rb ADDED
@@ -0,0 +1,418 @@
1
+ #coding:utf-8
2
+ require 'json'
3
+
4
+ ############################################################################################################
5
+ # HOW TO USE #
6
+ ############################################################################################################
7
+
8
+ =begin
9
+ require 'cc'
10
+ CC.use 'text-tools' # immediately when loading this file
11
+
12
+ # 类型操作
13
+ p "100".int(2), "3.14159".float(3), "0.86".precent, "true".bool
14
+
15
+ # 文本操作
16
+ p "aaabbb".head? "aa"
17
+ p "cccddd".tail? "dd"
18
+ p "abcdef" - "c"
19
+ p "abcdef" / "c"
20
+ p "abedef".exchange "e", 'z'
21
+ p "a11111a2222b3333b4444a5555b".match_paragraph('a','b')
22
+ p "<a>1<a><a>2</a>3</a>".match_xml('a')
23
+
24
+ # 列表操作
25
+ p ['a','b','c'].match(1){|c| c >= 'b'}
26
+ p ['a','b','c'].catch('d')
27
+
28
+ # 树操作
29
+ p ({a: 1, b: 2} + [1, 3, 5]).keys
30
+ p ({a: 1}).get :a
31
+ =end
32
+
33
+ ############################################################################################################
34
+ # text-tools —— 文本/列表/树的人类直觉操作扩展(移植自 imdoc/TextUtils/text_mind + text_abstract) #
35
+ # #
36
+ # 组成(单一文件自带全部依赖,零第三方依赖): #
37
+ # TextAbstract(模块函数):draw_fragments / match_paragraph / match_cascade / match_html_tag #
38
+ # —— 复杂文本的特征字段摘取与采集(段落/级联/标签结构匹配) #
39
+ # TextAbstract::Type -> String 类型转换(int / float / precent / text / bool) #
40
+ # TextAbstract::TextOperator -> String 文本操作(head? / tail? / matches / join / cut / - / / …) #
41
+ # TextAbstract::ListOperator -> Array 列表操作(catch / match / sub) #
42
+ # TextAbstract::TreeOperator -> Hash 树操作(get / set / +) #
43
+ # #
44
+ # 配套工具: #
45
+ # text-server.rb —— S 端:命令行启动 HTTP 解析服务(本文件的 HTTP 包装) #
46
+ # text-client.rb —— C 端:命令行传入解析(本地执行本文件,或 --remote 走 S 端服务) #
47
+ ############################################################################################################
48
+
49
+ module TextAbstract
50
+ module_function
51
+
52
+ # 按前置/后置标记抽取文本片段(特定环境遗留方法,不推荐常用)。
53
+ #
54
+ # 遇到 pre_flag 匹配行时开始收集,遇到 post_flag 匹配行时结束;
55
+ # 标记行本身包含在片段内,未闭合的片段追加到上下文。
56
+ #
57
+ # @param text [String] 原始文本
58
+ # @param pre_flag [Regexp] 开始标记(行级正则)
59
+ # @param post_flag [Regexp] 结束标记(行级正则)
60
+ # @return [Array<String>] 二元组 [上下文文本, 片段数组]
61
+ def draw_fragments text,pre_flag,post_flag
62
+ context,fragments = [],[]
63
+ temp,flag = "",false
64
+ text.each_line do|line|
65
+ if pre_flag.match(line)
66
+ temp = String.new if flag == false
67
+ flag = true
68
+ end
69
+ if flag == true
70
+ temp << line # base on line!
71
+ else
72
+ context << line
73
+ end
74
+ if post_flag.match(line)
75
+ flag = false
76
+ fragments << temp
77
+ temp = String.new
78
+ end
79
+ end
80
+ context << temp if flag == true # exist pre_flag but no post_flag
81
+ fragments.delete("")
82
+ return context.join(),fragments
83
+ end
84
+
85
+ # 严格顺序匹配开始/结束标记之间的段落。
86
+ #
87
+ # start 和 finish 有严格先后顺序:一段未结束不开启另一段。
88
+ # 返回匹配到的段落正文数组(不含首尾标记本身)。
89
+ #
90
+ # @param text [String] 完整文档文本
91
+ # @param start [String] 开始标记(严格字符串包含匹配)
92
+ # @param finish [String] 结束标记(严格字符串包含匹配)
93
+ # @return [Array<String>] 段落正文列表
94
+ def match_paragraph text,start,finish
95
+ paragraphs,current_text = [],text.clone
96
+ until current_text==""
97
+ return paragraphs unless current_text.include?(start) # 严格匹配开始
98
+ start_split = current_text.split(start)
99
+ current_text = start_split[1..-1].join(start)
100
+ return paragraphs unless current_text.include?(finish) # 严格匹配结束
101
+ finish_split = current_text.split(finish)
102
+ context = finish_split[0]
103
+ current_text = finish_split[1..-1].join(finish)+(text[-1*finish.size..-1]==finish ? finish : "")
104
+ paragraphs << context
105
+ end
106
+ return paragraphs
107
+ end
108
+
109
+ # 可嵌套匹配开始/结束标记,返回树状段落结构。
110
+ #
111
+ # start 和 finish 可以嵌套(一个 start/finish 包含在另一个内部),
112
+ # 但全局上仍保持顺序。返回嵌套数组:字符串为内容,数组为下一层级。
113
+ #
114
+ # @param text [String] 完整文档文本
115
+ # @param start [String] 开始标记
116
+ # @param finish [String] 结束标记
117
+ # @return [Array] 树状段落结构
118
+ def match_cascade text,start,finish
119
+ paragraphs,current_text = [],text
120
+ return paragraphs unless text.include?(start) or text.include?(finish)
121
+ current_pos,parent_pos = paragraphs,[paragraphs]
122
+ until current_text==""
123
+ st_pos = (current_text.include?(start) ? current_text.split(start)[0] : current_text).size
124
+ fn_pos = (current_text.include?(finish) ? current_text.split(finish)[0] : current_text).size
125
+ if fn_pos==st_pos # 无标志或标志互包含
126
+ current_pos << current_text
127
+ current_text = ""
128
+ end
129
+ if fn_pos < st_pos # 结束在前
130
+ csp = current_text.split(finish)
131
+ current_pos << csp[0]
132
+ current_pos << finish # 本级完成
133
+ cur_parent = parent_pos.pop # 取回上一级
134
+ current_pos = cur_parent # 返回上一级
135
+ current_text = current_text.sub(csp[0],"").sub(finish,"")
136
+ end
137
+ if st_pos < fn_pos # 开始在前
138
+ csp = current_text.split(start)
139
+ current_pos << csp[0] unless csp[0]=="" # 本级内容加入
140
+ current_pos << [start] # 下一级内容加入
141
+ parent_pos << current_pos # 本级加入上下文
142
+ current_pos = current_pos[-1]
143
+ current_text = current_text.sub(csp[0],"").sub(start,"")
144
+ end
145
+ end
146
+ return paragraphs
147
+ end
148
+
149
+ # 匹配 HTML 标签的嵌套结构(<tag ...> ... </tag>)。
150
+ #
151
+ # @param html [String] HTML 文本
152
+ # @param tag [String] 标签名(如 "table"、"div")
153
+ # @return [Array] 树状段落结构
154
+ def match_html_tag html,tag
155
+ paragraphs = self.match_cascade(html,"<#{tag}","</#{tag}>")
156
+ end
157
+
158
+ end
159
+
160
+ module TextAbstract
161
+
162
+ # 类型操作模块:把字符串按目标类型转换/格式化。
163
+ module Type
164
+ # 按进制字符串转整数。
165
+ #
166
+ # @param radix [Integer] 进制,默认 10
167
+ # @return [Integer]
168
+ def int radix=10
169
+ self.to_i(radix)
170
+ end
171
+
172
+ # 转浮点并保留 tail 位小数。
173
+ #
174
+ # @param tail [Integer] 保留位数,默认 2
175
+ # @return [Float]
176
+ def float tail=2
177
+ self.to_f.round(tail)
178
+ end
179
+
180
+ # 转百分比字符串(乘 100 保留 tail 位 + "%")。
181
+ #
182
+ # @param tail [Integer] 保留位数,默认 2
183
+ # @return [String]
184
+ def precent tail=2
185
+ (self.to_f*100).round(tail).to_s+"%"
186
+ end
187
+
188
+ # 原样或 JSON 化输出。
189
+ #
190
+ # @param format [Symbol] :raw 原样 / :meta JSON 编码
191
+ # @return [String]
192
+ def text format=:raw
193
+ return self if format==:raw
194
+ return self.to_json if format==:meta
195
+ end
196
+
197
+ # 转布尔:'true'/'false' 按字面,其余按是否为空。
198
+ #
199
+ # @return [Boolean]
200
+ def bool
201
+ return true if self.downcase=='true'
202
+ return false if self.downcase=='false'
203
+ return self.empty?
204
+ end
205
+ end
206
+
207
+ # 文本操作模块:常见文本加工的直觉操作符。
208
+ module TextOperator
209
+ # 判断是否以 prefix 开头。
210
+ #
211
+ # @param prefix [String] 前缀
212
+ # @param strip [Boolean] 是否先去除两端空白
213
+ # @return [Boolean]
214
+ def head? prefix, strip=false #=> bool
215
+ left = strip ? self.strip : self
216
+ left[0..(prefix.length-1)]==prefix
217
+ end
218
+
219
+ # 判断是否以 postfix 结尾。
220
+ #
221
+ # @param postfix [String] 后缀
222
+ # @param strip [Boolean] 是否先去除两端空白
223
+ # @return [Boolean]
224
+ def tail? postfix, strip=false #=> bool
225
+ left = strip ? self.strip : self
226
+ left[(-1*postfix.length)..-1]==postfix
227
+ end
228
+
229
+ # include? / empty? 由 Ruby 核心提供,不再重复定义。
230
+
231
+ # 按模式匹配文本。
232
+ #
233
+ # @param pattern [String, Array<String>] 模式:单模式传字符串,多模式传数组
234
+ # @param mode [Symbol] :one 取首个匹配 / :all 全部匹配 / :union 多模式并集
235
+ # @return [MatchData, Array]
236
+ def matches pattern, mode=:one #=> match
237
+ return self.match(Regexp.new pattern) if mode==:one
238
+ return self.scan(Regexp.new pattern) if mode==:all
239
+ return self.scan(Regexp.union *pattern.map{|pat|Regexp.new(pat)}) if mode==:union
240
+ end
241
+
242
+ # 按前置/后置标记抽行片段(draw_fragments 的字符串包装)。
243
+ #
244
+ # @param preflag [Regexp] 开始标记
245
+ # @param postflag [Regexp] 结束标记
246
+ # @return [Array<String>] 二元组 [上下文文本, 片段数组]
247
+ def draw_lines preflag, postflag #=> text, list
248
+ restext, contexts = TextAbstract.draw_fragments self, preflag, postflag
249
+ end
250
+
251
+ # 严格顺序匹配段落(match_paragraph 的字符串包装)。
252
+ #
253
+ # @param start [String] 开始标记
254
+ # @param finish [String] 结束标记
255
+ # @return [Array<String>] 段落正文列表
256
+ def match_paragraph start, finish #=> list
257
+ paragraphs = TextAbstract.match_paragraph self, start, finish
258
+ end
259
+
260
+ # 可嵌套匹配(match_cascade 的字符串包装)。
261
+ #
262
+ # @param start [String] 开始标记
263
+ # @param finish [String] 结束标记
264
+ # @return [Array] 树状段落结构
265
+ def match_cascade start, finish #=> list
266
+ paragraphs = TextAbstract.match_cascade self, start, finish
267
+ end
268
+
269
+ # 匹配 HTML 标签嵌套结构(match_html_tag 的字符串包装)。
270
+ #
271
+ # @param tag [String] 标签名
272
+ # @return [Array] 树状段落结构
273
+ def match_xml tag #=> tree
274
+ document = TextAbstract.match_html_tag self,tag
275
+ end
276
+
277
+ # length / size / strip / lstrip / rstrip 由 Ruby 核心提供。
278
+
279
+ # 把后续文本依次拼接在自身后面。
280
+ #
281
+ # @param texts [String...] 待拼接文本
282
+ # @return [String]
283
+ def join *texts #=> text
284
+ self+texts.map{|t|t}.join
285
+ end
286
+
287
+ # 按人类直觉截取(1 起数的闭区间,ridx=0 表示取到末尾)。
288
+ #
289
+ # @param index1 [Integer] 起始序号(1 起)
290
+ # @param index2 [Integer] 结束序号,0 表示末尾
291
+ # @return [String]
292
+ def cut index1, index2=0 #=> text
293
+ self[(index1-1)..(index2-1)]
294
+ end
295
+
296
+ # +(拼接)与 *(重复)由 Ruby 核心提供。
297
+
298
+ # 从首次出现 text 的位置截断(含 text),未出现则原样返回。
299
+ #
300
+ # @param text [String] 截断标记
301
+ # @return [String]
302
+ def - text #=> text
303
+ self.include?(text) ? self.split(text).first : self
304
+ end
305
+
306
+ # 按分隔符切分为列表(即 split 的直觉别名)。
307
+ #
308
+ # @param text [String] 分隔符
309
+ # @return [Array<String>]
310
+ def / text #=> list
311
+ self.split(text)
312
+ end
313
+
314
+ # 按模式替换:oldtext 匹配处替换为 newtext,最多 num 次。
315
+ #
316
+ # @param text [String] 匹配模式(正则源文本)
317
+ # @param newtext [String] 替换文本
318
+ # @param num [Integer, Symbol] 次数上限,:all 表示全部
319
+ # @return [String]
320
+ def exchange text, newtext, num=:all #=> text
321
+ num = 65535 if num==:all
322
+ count = 0
323
+ self.gsub(Regexp.new(text)) do |match|
324
+ count += 1
325
+ count <= num ? newtext : text
326
+ end
327
+ end
328
+ end
329
+
330
+ # 列表操作模块:列表加工的直觉操作符。
331
+ #
332
+ # pop/push/shift/unshift/+/-/find/select/filter/map/collect/reduce/inject
333
+ # 均由 Ruby 核心提供,不再重复定义。
334
+ module ListOperator
335
+ # 把自身挂到列表头部,返回新列表。
336
+ #
337
+ # @param list [Array] 待挂接的元素序列
338
+ # @return [Array] 新列表
339
+ def catch *list #=> list(list)
340
+ list.unshift(self)
341
+ end
342
+
343
+ # 按条件块检索。
344
+ #
345
+ # @param num [Integer, Symbol] 0 只判存在 / 1 取首个 / :all 取全部 / n>1 取前 n 个
346
+ # @yield [item] 匹配条件
347
+ # @return [Boolean, Object, Array]
348
+ def match num=1, &block #=> bool | item | list
349
+ return self.find(&block).nil? if num ==0
350
+ return self.find(&block) if num ==1
351
+ return self.select(&block) if num ==:all
352
+ return self.select(&block)[0..(num-1)] if num > 1
353
+ end
354
+
355
+ # 按人类直觉截取(1 起数的闭区间,index2=0 表示取到末尾)。
356
+ #
357
+ # @param index1 [Integer] 起始序号(1 起)
358
+ # @param index2 [Integer] 结束序号,0 表示末尾
359
+ # @return [Array]
360
+ def sub index1, index2=0 #=> list
361
+ self[(index1-1)..(index2-1)]
362
+ end
363
+ end
364
+
365
+ # 树操作模块:散列表的直觉操作符。
366
+ #
367
+ # keys/values/[]/[]= 由 Ruby 核心提供,不再重复定义。
368
+ module TreeOperator
369
+ # 取键值([] 的直觉别名)。
370
+ #
371
+ # @param key [Object] 键
372
+ # @return [Object]
373
+ def get key #=> item
374
+ self[key]
375
+ end
376
+
377
+ # 写键值([]= 的直觉别名)。
378
+ #
379
+ # @param key [Object] 键
380
+ # @param value [Object] 值
381
+ # @return [Object] 写入的值
382
+ def set key, value #=> item
383
+ self[key] = value
384
+ end
385
+
386
+ # 合并树/列表/单值为新树。
387
+ #
388
+ # Hash 深合并;Array 按现有最大整数键续排;单值追加为下一个整数键。
389
+ #
390
+ # @param oprand [Hash, Array, Object] 待合并对象
391
+ # @return [Hash] 合并结果新树
392
+ def + oprand #=> tree
393
+ index = self.keys.filter{|k|k.instance_of?(Integer)}.max
394
+ index = index ? index+1 : 0
395
+ if oprand.instance_of?(Hash)
396
+ self.merge(oprand)
397
+ elsif oprand.instance_of?(Array)
398
+ self.merge oprand.reduce({}){|tr,op|index+=1;tr.merge({(index-1)=>op})}
399
+ else
400
+ self.merge({index=>oprand})
401
+ end
402
+ end
403
+ end
404
+
405
+ end
406
+
407
+ class String
408
+ include TextAbstract::Type
409
+ include TextAbstract::TextOperator
410
+ end
411
+
412
+ class Array
413
+ include TextAbstract::ListOperator
414
+ end
415
+
416
+ class Hash
417
+ include TextAbstract::TreeOperator
418
+ end