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-server.rb ADDED
@@ -0,0 +1,403 @@
1
+ #!/usr/bin/env ruby
2
+ #coding:utf-8
3
+
4
+ ############################################################################################################
5
+ # text-server —— S 端:textmind 解析服务(命令行启动) #
6
+ # #
7
+ # 移植改造自 imdoc/TextUtils/config.ru: #
8
+ # - 原 `rackup config.ru` 部署方式 -> `ruby text-server.rb`(或安装后 require)命令行直接启动 #
9
+ # - 原依赖 gem:rack-cors -> 手工 CORS 响应头(放行规则与原配置一致,少一个依赖) #
10
+ # - 修正原 config.ru 三处失效缺省: #
11
+ # r.params['radix'].to_i || 10 (nil.to_i==0 恒真,缺省 10 从未生效 -> 缺省 10) #
12
+ # r.params['format'].to_sym (缺省会 nil.to_sym 崩溃 -> 缺省 :raw) #
13
+ # r.params['flag'].to_s.downcase.to_sym || :both(:"" 恒真 -> 缺省 :both;mode 同理缺省 :one) #
14
+ # - 路由路径与参数名与原服务完全一致,原 Postman 集合(imdoc/TextUtils/doc/)无需改动即可继续使用 #
15
+ # #
16
+ # 接口(JSON 请求体,POST): #
17
+ # /type/{int,float,precent,text,bool} #
18
+ # /text/{head,tail,include,empty,length,strip,join,+, -, *,split,cut,match,exchange,draw/lines, #
19
+ # match/paragraph, match/cascade, match/xml} #
20
+ # /list/{pop,push,shift,unshift,+, -,catch,sub,match,map,reduce} #
21
+ # /tree/{keys,values,get,set,+} #
22
+ # 注意:list 的 match/map/reduce 接收 ruby 代码串并 eval(内部信任工具,请勿暴露到不可信网络) #
23
+ # #
24
+ # 启动: #
25
+ # ruby text-server.rb # 127.0.0.1:9292 #
26
+ # ruby text-server.rb --port 9293 # 指定端口 #
27
+ # ruby text-server.rb --host 0.0.0.0 # 监听所有网卡(原 CORS 允许的 192.168.0.x 前端即此场景) #
28
+ # #
29
+ # 配套:C 端 text-client.rb(本地解析或 --remote 调用本服务) #
30
+ ############################################################################################################
31
+
32
+ require_relative 'text-tools'
33
+ require 'roda'
34
+
35
+ module TextServer
36
+ # 原 rack-cors 放行来源(与 config.ru 一致):本地 9200 前端与 192.168.0.x 内网前端
37
+ ALLOWED_ORIGINS = ['localhost:9200', '127.0.0.1:9200']
38
+ ALLOWED_ORIGIN_PATTERN = /\Ahttp:\/\/192\.168\.0\.\d{1,3}(:\d+)?\z/
39
+
40
+ module_function
41
+
42
+ # 判断 Origin 是否在 CORS 放行名单内。
43
+ #
44
+ # @param origin [String, nil] 请求 Origin 头
45
+ # @return [Boolean]
46
+ def allowed_origin? origin
47
+ return false unless origin
48
+ ALLOWED_ORIGINS.include?(origin) || ALLOWED_ORIGIN_PATTERN.match?(origin)
49
+ end
50
+
51
+ # S 端启动入口:解析 --host/--port 并拉起 Rack 服务。
52
+ #
53
+ # @param argv [Array<String>] 命令行参数
54
+ # @return [void]
55
+ def run! argv
56
+ host, port = '127.0.0.1', 9292
57
+ argv.each_slice(2) do|key, value|
58
+ case key
59
+ when '--host' then host = value || host
60
+ when '--port' then port = (value || port).to_i
61
+ end
62
+ end
63
+ require 'rackup'
64
+ puts "textmind S 端启动: http://#{host}:#{port} (POST /type|/text|/list|/tree/*)"
65
+ Rackup::Server.start(app: App.freeze.app, Host: host, Port: port)
66
+ end
67
+ end
68
+
69
+ # textmind 解析服务(Roda 应用)。
70
+ class App < Roda
71
+ plugin :json_parser # 解析 JSON 请求体到 r.params
72
+ plugin :request_headers # 读取请求头(CORS Origin)
73
+ plugin :all_verbs # 全动词路由(GET/POST/PUT/…/OPTIONS)
74
+
75
+ route do |r|
76
+ # ---- 手工 CORS:按原 rack-cors 规则放行并回写响应头 ----
77
+ if origin = r.headers['Origin'] and TextServer.allowed_origin?(origin)
78
+ response['Access-Control-Allow-Origin'] = origin
79
+ response['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD'
80
+ response['Access-Control-Allow-Headers'] = 'x-domain-token, Content-Type'
81
+ response['Access-Control-Expose-Headers'] = 'Some-Custom-Response-Header'
82
+ response['Access-Control-Max-Age'] = '600'
83
+ end
84
+
85
+ # CORS 预检(OPTIONS)直接放行
86
+ r.options do
87
+ ''
88
+ end
89
+
90
+ # ---- 类型操作:字符串 -> 目标类型 ----
91
+ r.on 'type' do
92
+ # POST /type/int radix=2, text="100" => 4
93
+ r.post 'int' do
94
+ text = r.params['text']
95
+ radix = r.params['radix'] ? r.params['radix'].to_i : 10
96
+ text.int(radix).to_json
97
+ end
98
+
99
+ # POST /type/float tail=2, text="3.1415926" => 3.14
100
+ r.post 'float' do
101
+ text = r.params['text']
102
+ tail = r.params['tail'] ? r.params['tail'].to_i : 2
103
+ text.float(tail).to_json
104
+ end
105
+
106
+ # POST /type/precent tail=2, text="0.8675309" => "86.75%"
107
+ r.post 'precent' do
108
+ text = r.params['text']
109
+ tail = r.params['tail'] ? r.params['tail'].to_i : 2
110
+ text.precent(tail).to_json
111
+ end
112
+
113
+ # POST /type/text format=meta, text="hello world" => JSON 编码
114
+ r.post 'text' do
115
+ text = r.params['text']
116
+ format = (r.params['format'] || 'raw').to_sym
117
+ text.text(format).to_json
118
+ end
119
+
120
+ # POST /type/bool text="true" => true
121
+ r.post 'bool' do
122
+ text = r.params['text']
123
+ text.bool.to_json
124
+ end
125
+ end
126
+
127
+ # ---- 文本操作 ----
128
+ r.on 'text' do
129
+ # POST /text/head prefix="aa", strip=false, text="aabbcc"
130
+ r.post 'head' do
131
+ text = r.params['text']
132
+ prefix = r.params['prefix']
133
+ strip = r.params['strip'] || false
134
+ text.head?(prefix, strip).to_json
135
+ end
136
+
137
+ # POST /text/tail postfix="cc", strip=false, text="aabbcc"
138
+ r.post 'tail' do
139
+ text = r.params['text']
140
+ postfix = r.params['postfix']
141
+ strip = r.params['strip'] || false
142
+ text.tail?(postfix, strip).to_json
143
+ end
144
+
145
+ # POST /text/include pattern="bb", text="aabbcc"
146
+ r.post 'include' do
147
+ text = r.params['text']
148
+ pattern = r.params['pattern']
149
+ text.include?(pattern).to_json
150
+ end
151
+
152
+ # POST /text/empty text=""
153
+ r.post 'empty' do
154
+ text = r.params['text']
155
+ text.empty?.to_json
156
+ end
157
+
158
+ # POST /text/length text="aabbcc"
159
+ r.post 'length' do
160
+ text = r.params['text']
161
+ text.length.to_json
162
+ end
163
+
164
+ # POST /text/strip flag=both, text=" aa "
165
+ r.post 'strip' do
166
+ text = r.params['text']
167
+ flag = (r.params['flag'] || 'both').to_s.downcase.to_sym
168
+ result = flag==:both ? text.strip : flag==:left ? text.lstrip : flag==:right ? text.rstrip : text
169
+ result.to_json
170
+ end
171
+
172
+ # POST /text/join attaches=["22345678","32345678"], text="aa"
173
+ r.post 'join' do
174
+ text = r.params['text']
175
+ attaches = r.params['attaches'] || []
176
+ text.join(attaches).to_json
177
+ end
178
+
179
+ # POST /text/+ oprand="bb", text="aa" => "aabb"
180
+ r.post '+' do
181
+ text = r.params['text']
182
+ oprand = r.params['oprand'] || ''
183
+ (text + oprand).to_json
184
+ end
185
+
186
+ # POST /text/- oprand="bc", text="aabbcc" => "aa"(从首次出现处截断)
187
+ r.post '-' do
188
+ text = r.params['text']
189
+ oprand = r.params['oprand'] || ''
190
+ (text - oprand).to_json
191
+ end
192
+
193
+ # POST /text/* oprand=3, text="ab" => "ababab"
194
+ r.post '*' do
195
+ text = r.params['text']
196
+ oprand = (r.params['oprand'] || 1).to_s.to_i
197
+ (text * oprand).to_json
198
+ end
199
+
200
+ # POST /text/split oprand=" ", text="a b" (缺省 ' ',原文标注 DIFF: '' <=> ' ')
201
+ r.post 'split' do
202
+ text = r.params['text']
203
+ oprand = r.params['oprand'] || ' ' # DIFF: '' <=> ' '
204
+ (text / oprand).to_json
205
+ end
206
+
207
+ # POST /text/cut lidx=1, ridx=0, text="abcdef"(1 起数闭区间,0 取到末尾)
208
+ r.post 'cut' do
209
+ text = r.params['text']
210
+ lidx = r.params['lidx'] || 1
211
+ ridx = r.params['ridx'] || 0
212
+ text.cut(lidx, ridx).to_json
213
+ end
214
+
215
+ # POST /text/match patterns=["a\\d","c\\d"], mode=one|all|union, text="a1b2c3"
216
+ r.post 'match' do
217
+ text = r.params['text']
218
+ patterns = r.params['patterns'] || []
219
+ mode = (r.params['mode'] || 'one').to_s.downcase.to_sym
220
+ ([:one, :all].include?(mode) ? (!patterns.first.to_s.empty? ? text.matches(patterns.first.to_s,mode) : text) :
221
+ text.matches(patterns,mode)).to_json
222
+ end
223
+
224
+ # POST /text/exchange oldtext="e", newtext="z", repeat=all, text="abedef"
225
+ r.post 'exchange' do
226
+ text = r.params['text']
227
+ oldtext = r.params['oldtext']
228
+ newtext = r.params['newtext']
229
+ repeat = (r.params['repeat'] || 'all').to_s
230
+ repeat = repeat.include?('all') ? repeat.to_sym : repeat.to_i
231
+ text.exchange(oldtext, newtext, repeat).to_json
232
+ end
233
+
234
+ # POST /text/draw/lines preflag="a", postflag="b", text="1111\na\n2222\nb\n3333"
235
+ r.post 'draw/lines' do
236
+ text = r.params['text']
237
+ preflag = Regexp.new r.params['preflag'].to_s
238
+ postflag = Regexp.new r.params['postflag'].to_s
239
+ rests, content = text.draw_lines(preflag, postflag)
240
+ [content, rests].to_json
241
+ end
242
+
243
+ # POST /text/match/paragraph start="a", finish="b", text="a1a2b3a4b"
244
+ r.post 'match/paragraph' do
245
+ text = r.params['text']
246
+ start = r.params['start'].to_s
247
+ finish = r.params['finish'].to_s
248
+ text.match_paragraph(start, finish).to_json
249
+ end
250
+
251
+ # POST /text/match/cascade start="a", finish="b", text="a1{a2}b3"
252
+ r.post 'match/cascade' do
253
+ text = r.params['text']
254
+ start = r.params['start'].to_s
255
+ finish = r.params['finish'].to_s
256
+ text.match_cascade(start, finish).to_json
257
+ end
258
+
259
+ # POST /text/match/xml tag="a", text="<a>1<a>2</a>3</a>"
260
+ r.post 'match/xml' do
261
+ text = r.params['text']
262
+ tag = r.params['tag'].to_s
263
+ text.match_xml(tag).to_json
264
+ end
265
+ end
266
+
267
+ # ---- 列表操作 ----
268
+ r.on 'list' do
269
+ # POST /list/pop list=[1,2,3]
270
+ r.post 'pop' do
271
+ list = r.params['list']
272
+ list.pop.to_json
273
+ end
274
+
275
+ # POST /list/push item="x", list=[1,2,3]
276
+ r.post 'push' do
277
+ list = r.params['list']
278
+ item = r.params['item']
279
+ list.push(item).to_json
280
+ end
281
+
282
+ # POST /list/shift list=[1,2,3]
283
+ r.post 'shift' do
284
+ list = r.params['list']
285
+ list.shift.to_json
286
+ end
287
+
288
+ # POST /list/unshift item="x", list=[1,2,3]
289
+ r.post 'unshift' do
290
+ list = r.params['list']
291
+ item = r.params['item']
292
+ list.unshift(item).to_json
293
+ end
294
+
295
+ # POST /list/+ oprand=[4,5], list=[1,2,3]
296
+ r.post '+' do
297
+ list = r.params['list']
298
+ alt = r.params['oprand'] || []
299
+ (list + alt).to_json
300
+ end
301
+
302
+ # POST /list/- oprand=[2], list=[1,2,3]
303
+ r.post '-' do
304
+ list = r.params['list']
305
+ alt = r.params['oprand'] || []
306
+ (list - alt).to_json
307
+ end
308
+
309
+ # POST /list/catch oprands=[["a","b"],["c"]], list=[1,2,3]
310
+ r.post 'catch' do
311
+ list = r.params['list']
312
+ alts = r.params['oprands'] || []
313
+ list.catch(alts).to_json
314
+ end
315
+
316
+ # POST /list/sub lidx=1, ridx=0, list=[1,2,3]
317
+ r.post 'sub' do
318
+ list = r.params['list']
319
+ lidx = r.params['lidx'] || 1
320
+ ridx = r.params['ridx'] || 0
321
+ list.sub(lidx, ridx).to_json
322
+ end
323
+
324
+ # POST /list/match func="proc{|c| c >= 'b'}", repeat=1, list=["a","b","c"]
325
+ # repeat: 0 只判存在 / 1 取首个 / "all" 取全部 / n 取前 n 个
326
+ r.post 'match' do
327
+ list = r.params['list']
328
+ repeat = r.params['repeat'] || 1
329
+ repeat = :all if repeat.to_s == 'all'
330
+ func = eval(r.params['func'])
331
+ list.match(repeat, &func).to_json
332
+ end
333
+
334
+ # POST /list/map func="proc{|c| c * 2}", list=[1,2,3]
335
+ r.post 'map' do
336
+ list = r.params['list']
337
+ func = eval(r.params['func'])
338
+ list.map(&func).to_json
339
+ end
340
+
341
+ # POST /list/reduce func="proc{|a,b| a+b}", init="0:integer", list=[1,2,3]
342
+ # init 形如 "初值:类型",类型支持 text/integer/float/list/tree
343
+ r.post 'reduce' do
344
+ list = r.params['list']
345
+ func = eval(r.params['func'])
346
+ sectors = r.params['init'].to_s.split(":")
347
+ type = sectors.last.strip
348
+ init = sectors[0..-2].join(':')
349
+ iv = case type
350
+ when 'text'; init.strip.to_s
351
+ when 'integer'; init.strip.to_i
352
+ when 'float'; init.strip.to_f
353
+ when 'list'; eval(init)
354
+ when 'tree'; eval(init)
355
+ end
356
+ list.reduce(iv,&func).to_json
357
+ end
358
+ end
359
+
360
+ # ---- 树操作 ----
361
+ r.on 'tree' do
362
+ # POST /tree/keys tree={"a":1}
363
+ r.post 'keys' do
364
+ tree = r.params['tree']
365
+ tree.keys.to_json
366
+ end
367
+
368
+ # POST /tree/values tree={"a":1}
369
+ r.post 'values' do
370
+ tree = r.params['tree']
371
+ tree.values.to_json
372
+ end
373
+
374
+ # POST /tree/get key="a", tree={"a":1}
375
+ r.post 'get' do
376
+ tree = r.params['tree']
377
+ get_key = r.params['key'].to_s
378
+ tree[get_key].to_json
379
+ end
380
+
381
+ # POST /tree/set key="a", val=2, tree={"a":1}
382
+ r.post 'set' do
383
+ tree = r.params['tree']
384
+ set_key = r.params['key']
385
+ set_val = r.params['val']
386
+ tree[set_key]=set_val
387
+ tree.to_json
388
+ end
389
+
390
+ # POST /tree/+ oprand={"b":2}|[1,3,5]|"x", tree={"a":1}
391
+ r.post '+' do
392
+ tree = r.params['tree']
393
+ alt = r.params['oprand'] # DIFF: "num" <=> num
394
+ (tree + alt).to_json
395
+ end
396
+ end
397
+
398
+ end
399
+ end
400
+
401
+ if $PROGRAM_NAME == __FILE__
402
+ TextServer.run! ARGV
403
+ end