cr.rb 4.1.4 → 4.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. checksums.yaml +4 -4
  2. data/exe/cr +67 -0
  3. data/lib/cr/counter.rb +106 -0
  4. data/lib/cr/version.rb +3 -3
  5. data/lib/cr.rb +530 -117
  6. metadata +20 -5
  7. data/bin/cr +0 -597
data/lib/cr.rb CHANGED
@@ -1,36 +1,546 @@
1
1
  # ------------------------------------------------------
2
2
  # File : cr.rb
3
- # Authors : ccmywish <ccmywish@qq.com>
3
+ # Authors : Aoran Zeng <ccmywish@qq.com>
4
4
  # Created on : <2022-04-15>
5
- # Last modified : <2023-03-22>
5
+ # Last modified : <2023-05-09>
6
6
  #
7
7
  # cr:
8
8
  #
9
9
  # This file is the lib of `cr.rb``
10
10
  # ------------------------------------------------------
11
11
 
12
- require 'cr/version'
13
- # require_relative "#{__dir__}/cr/version"
12
+ require 'colorator'
13
+ require_relative 'cr/version'
14
14
 
15
- module CrypticResolver
15
+ class String
16
+ def underline = "\e[4m#{self}\e[0m"
17
+ end
18
+
19
+ class CrypticResolver::Resolver
20
+
21
+ require_relative 'cr/counter'
22
+
23
+ # Notice that we only update the Default library, not Extra library
24
+ def update_dicts
25
+
26
+ @counter.count_def_lib(display: false)
27
+ old_wc = @counter.word_count_of_def_lib
28
+
29
+ puts "cr: Updating all dicts in Default library..."
30
+
31
+ begin
32
+ Dir.chdir DEFAULT_LIB_PATH do
33
+ if Gem.win_platform?
34
+ # Windows doesn't have fork
35
+ Dir.children(DEFAULT_LIB_PATH).each do |dict|
36
+ next if File.file? dict
37
+ puts "cr: Wait to update #{dict}..."
38
+ `git -C ./#{dict} pull -q`
39
+ end
40
+ else
41
+ # *nix
42
+ Dir.children(DEFAULT_LIB_PATH).each do |dict|
43
+ next if File.file? dict
44
+ fork do
45
+ puts "cr: Wait to update #{dict}..."
46
+ `git -C ./#{dict} pull -q`
47
+ end
48
+ end
49
+ Process.waitall
50
+ end # end if/else
51
+ end
52
+
53
+ rescue Interrupt
54
+ abort "cr: Cancel update"
55
+ end
56
+
57
+ puts "cr: Update done"
58
+
59
+ # clear
60
+ @counter.word_count_of_def_lib = 0
61
+ # recount
62
+ @counter.count_def_lib(display: false)
63
+
64
+ new_wc = @counter.word_count_of_def_lib
65
+ puts ; puts "#{new_wc - old_wc} words added in Default library"
66
+ end
67
+
68
+
69
+ def add_dict(repo)
70
+ if repo.nil?
71
+ abort "cr: Need an argument!".bold.red
72
+ end
73
+
74
+ # Simplify adding dictionary
75
+ if !repo.start_with?("https://") and !repo.start_with?("git@")
76
+ if repo.include?('/')
77
+ repo = "https://github.com/#{repo}.git"
78
+ else
79
+ repo = "https://github.com/cryptic-resolver/cryptic_#{repo}.git"
80
+ end
81
+ end
82
+
83
+ begin
84
+ puts "cr: Adding new dictionary..."
85
+ `git -C #{DEFAULT_LIB_PATH} clone #{repo} -q`
86
+ rescue Interrupt
87
+ abort "cr: Cancel add dict"
88
+ end
89
+
90
+ puts "cr: Add new dictionary done"
91
+
92
+ # github/com/ccmywish/ruby_knowledge(.git)
93
+ dict = repo.split('/')[-1].delete_suffix('.git')
94
+ wc = @counter.count_dict_words(DEFAULT_LIB_PATH ,dict)
95
+ puts ; puts "#{wc} words added"
96
+ end
97
+
98
+
99
+ def del_dict(repo)
100
+ if repo.nil?
101
+ abort "cr: Need an argument!".bold.red
102
+ end
103
+ Dir.chdir DEFAULT_LIB_PATH do
104
+ begin
105
+ # Dir.rmdir repo # Can't rm a filled dir
106
+ # FileUtils.rmdir repo # Can't rm a filled dir
107
+ FileUtils.rm_rf repo
108
+ puts "cr: Delete dictionary #{repo.bold.green} done"
109
+ rescue Exception => e
110
+ puts "cr: #{e}".bold.red
111
+ list_dicts
112
+ end
113
+ end
114
+ end
115
+
116
+
117
+ def load_sheet(library, dict, sheet_name)
118
+ file = library + "/#{dict}/#{sheet_name}.toml"
119
+ if File.exist? file
120
+ return Tomlrb.load_file file # gem 'tomlrb'
121
+ # return TOML.load_file file # gem 'toml'
122
+ else nil end
123
+ end
124
+
125
+
126
+ =begin
127
+ Pretty print the info of the given word
128
+
129
+ A info looks like this
130
+ emacs = {
131
+ name = "Emacs"
132
+ desc = "edit macros"
133
+ more = "a feature-rich editor"
134
+ see = ["Vim"]
135
+ }
136
+
137
+ @param info [Hash] the information of the given word (mapped to a keyword in TOML)
138
+ =end
139
+ def pp_info(info)
140
+ name = info['name'] || "No name!".red # keyword `or` is invalid here in Ruby
141
+
142
+ desc = info['desc']
143
+ more = info['more']
144
+
145
+ if desc
146
+ puts "\n #{name}: #{desc}"
147
+ print "\n ",more,"\n" if more
148
+ else
149
+ puts "\n #{name}"
150
+ print "\n ",more,"\n" if more
151
+ end
152
+
153
+ if see_also = info['see']
154
+ print "\n", "SEE ALSO ".magenta
155
+ if see_also.is_a?(Array)
156
+ last_ndx = see_also.size - 1
157
+ see_also.each_with_index do |x,i|
158
+ if last_ndx == i
159
+ print x.underline # Last word doesn't show space
160
+ else
161
+ print x.underline, ' '
162
+ end
163
+ end
164
+ else
165
+ print see_also.underline
166
+ end
167
+ puts
168
+ end
169
+ puts
170
+ end
171
+
172
+
173
+ # Print default cryptic_ dicts
174
+ def pp_dict(dict)
175
+ puts "From: #{dict}".green
176
+ end
177
+
178
+
179
+ =begin
180
+ Used for synonym jump
181
+ Because we absolutely jump to a must-have word
182
+ So we can directly lookup to it
183
+
184
+ Notice that, we must jump to a specific word definition
185
+ So in the toml file, you must specify the precise word.
186
+ If it has multiple meanings, for example
187
+
188
+ [blah]
189
+ same = "xdg" # this is wrong, because xdg has multiple
190
+ # definitions, and all of them specify a
191
+ # category
192
+
193
+ [blah]
194
+ same = "XDG downloader =>xdg.Download" # this is correct
195
+
196
+ [blah]
197
+ name = "BlaH" # You may want to display a better name first
198
+ same = "XDG downloader =>xdg.Download" # this is correct
199
+ =end
200
+ def pp_same_info(library, dict, word, cache_content, same_key, own_name)
201
+
202
+ # If it's a synonym for anther word,
203
+ # we should lookup into this dict again, but maybe with a different file
204
+
205
+ # file name
206
+ x = word.chr.downcase
207
+
208
+ =begin
209
+ dictionary maintainer must obey the rule for xxx.yyy word:
210
+ xxx should be lower case
211
+ yyy can be any case
212
+
213
+ Because yyy should clearly explain the category info, IBM is better than ibm
214
+ Implementation should not be too simple if we want to stress the function we
215
+ expect.
216
+
217
+ 'jump to' will output to user, so this is important not only inside our sheet.
218
+
219
+ same = "XDM downloader=>xdm.Download"
220
+
221
+ We split 'same' key into two parts via spaceship symbol `=>`, first part will
222
+ output to user, the second part is for internal jump.
223
+ =end
224
+ jump_to, same = same_key.split("=>")
225
+ same = jump_to if same.nil?
226
+
227
+ unless own_name
228
+ own_name = word
229
+ end
230
+ puts own_name.bold.blue + ' redirects to ' + jump_to.bold.blue
231
+
232
+ =begin
233
+ As '.' is used to delimit a word and a category, what if
234
+ we jump to a dotted word?
235
+
236
+ [eg]
237
+ same = "e.g." # this must lead to a wrong resolution to
238
+ # word 'e', category 'g'
239
+
240
+ All you need is to be like this:
241
+
242
+ [eg]
243
+ same = "'e.g.'" # cr will notice the single quote
244
+ =end
245
+ if same =~ /^'(.*)'$/
246
+ same, category = $1, nil
247
+ else
248
+ same, category = same.split('.')
249
+ end
250
+
251
+ if same.chr == x
252
+ # No need to load another dictionary if match
253
+ sheet_content = cache_content
254
+ else
255
+ sheet_content = load_sheet(library, dict, same.chr.downcase)
256
+ end
257
+
258
+ if category.nil?
259
+ info = sheet_content[same]
260
+ else
261
+ info = sheet_content[same][category]
262
+ end
263
+
264
+ if info.nil?
265
+ puts "WARN: Synonym jumps to the wrong place `#{same}`,
266
+ Please consider fixing this in `#{x}.toml` of the dictionary `#{dict}`".red
267
+ # exit
268
+ return false
269
+ # double or more jumps
270
+ elsif same_key = info['same']
271
+ own_name = info['name']
272
+ return pp_same_info(library, dict, same, cache_content, same_key, own_name)
273
+ else
274
+ pp_info(info)
275
+ return true
276
+ end
277
+ end
278
+
279
+
280
+
281
+ #
282
+ # Lookup the given word in a sheet (a toml file) and also print.
283
+ # The core idea is that:
284
+ #
285
+ # 1. if the word is `same` with another synonym, it will directly jump to
286
+ # a word in this dictionary, but maybe a different sheet.
287
+ #
288
+ # 2. load the toml file and check the given word
289
+ # 2.1 with no category specifier
290
+ # [abcd]
291
+ # 2.2 with category specifier
292
+ # [abcd.tYPe]
293
+ #
294
+ def lookup(library, dict, file, word)
295
+ sheet_content = load_sheet(library, dict, file)
296
+ return false if sheet_content.nil?
297
+
298
+ info = sheet_content[word]
299
+ return false if info.nil?
300
+
301
+ # Warn if the info is empty. For example:
302
+ # emacs = { }
303
+ if info.size == 0
304
+ abort "WARN: Lack of everything of the given word
305
+ Please consider fixing this in the dict `#{dict}`".red
306
+ end
307
+
308
+ # Word with no category specifier
309
+ # We call this meaning as type 1
310
+ type_1_exist_flag = false
311
+
312
+ # if already jump, don't check the word itself
313
+ is_jump = false
314
+
315
+ # synonym info print
316
+ if same_key = info['same']
317
+ own_name = info['name']
318
+ pp_dict(dict)
319
+ pp_same_info(library, dict, word, sheet_content, same_key, own_name)
320
+ # It's also a type 1
321
+ type_1_exist_flag = true
322
+ is_jump = true
323
+ end
16
324
 
17
- module Color
18
- def bold(str) "\e[1m#{str}\e[0m" end
19
- def underline(str) "\e[4m#{str}\e[0m" end
20
- def red(str) "\e[31m#{str}\e[0m" end
21
- def green(str) "\e[32m#{str}\e[0m" end
22
- def yellow(str) "\e[33m#{str}\e[0m" end
23
- def blue(str) "\e[34m#{str}\e[0m" end
24
- def purple(str) "\e[35m#{str}\e[0m" end
25
- def cyan(str) "\e[36m#{str}\e[0m" end
325
+ # normal info print
326
+ # To developer:
327
+ # The word should at least has one of `desc` and `more`
328
+ # But when none exists, this may not be considered wrong,
329
+ # Because the type2 make the case too.
330
+ #
331
+ # So, just ignore it, even if it's really a mistake(insignificant)
332
+ # by dictionary maintainers.
333
+ #
334
+ if !is_jump && (info.has_key?('desc') || info.has_key?('more'))
335
+ pp_dict(dict)
336
+ pp_info(info)
337
+ type_1_exist_flag = true
338
+ end
339
+
340
+ # Meanings with category specifier
341
+ # We call this meaning as type 2
342
+ categories = info.keys - ["name", "desc", "more", "same", "see"]
343
+
344
+ if !categories.empty?
345
+
346
+ if type_1_exist_flag
347
+ print "OR".bold.blue, "\n"
348
+ else
349
+ pp_dict(dict)
350
+ end
351
+
352
+ categories.each do |meaning|
353
+ info0 = sheet_content[word][meaning]
354
+ if same_key = info0['same']
355
+ own_name = info0['name']
356
+ pp_same_info(library, dict, word, sheet_content, same_key, own_name)
357
+ else
358
+ pp_info(info0)
359
+ end
360
+
361
+ # last meaning doesn't show this separate line
362
+ print "OR".bold.blue, "\n" unless categories.last == meaning
363
+ end
364
+ return true
365
+ elsif type_1_exist_flag then return true
366
+ else return false end
367
+ end
368
+
369
+
370
+ #
371
+ # The main procedure of `cr`
372
+ #
373
+ # 1. Search the default library first
374
+ # 2. Search the extra library if it does exist
375
+ #
376
+ # The `search` is done via the `lookup` function. It will print
377
+ # the info while finding. If `lookup` always return false then
378
+ # means lacking of this word in our dicts. So a welcomed
379
+ # contribution is printed on the screen.
380
+ #
381
+ def resolve_word(word)
382
+
383
+ word = word.downcase # downcase! would lead to frozen error in Ruby 2.7.2
384
+ # The index is the toml file we'll look into
385
+ index = word.chr
386
+ case index
387
+ when '0'..'9'
388
+ index = '0-9'
389
+ end
390
+
391
+ # cache lookup's results
392
+ results = []
393
+
394
+ # First consider the default library
395
+ default = Dir.children(DEFAULT_LIB_PATH)
396
+ default.each do |dict|
397
+ results << lookup(DEFAULT_LIB_PATH,dict,index,word)
398
+ end
399
+
400
+ # Then is the extra library
401
+ if @extra_lib_path
402
+ extra = Dir.children(@extra_lib_path)
403
+ extra.each do |dict|
404
+ results << lookup(@extra_lib_path,dict,index,word)
405
+ end
406
+ end
407
+
408
+ unless results.include? true
409
+ puts <<~NotFound
410
+ cr: Not found anything about '#{word}'. You could try
411
+
412
+ #{"case 1: Update all dictionaries".blue}
413
+ #{"$ cr -u".yellow}
414
+ #{"case 2: List available official and feature dictionaries".blue}
415
+ #{"$ cr -l".yellow}
416
+ #{"$ cr -a repo".yellow} (Add a specific dict to default lib)
417
+ #{"case 3: Contribute to theses dictionaries".blue}
418
+ Visit: https://github.com/cryptic-resolver
419
+
420
+ NotFound
421
+ else return end
422
+ end
423
+
424
+
425
+ #
426
+ # Delegate to `search_word_internal`
427
+ #
428
+ def search_word(pattern)
429
+ found_or_not1 = false
430
+ found_or_not2 = false
431
+
432
+ found_or_not1 = search_word_internal(pattern, DEFAULT_LIB_PATH)
433
+ if @extra_lib_path
434
+ found_or_not2 = search_word_internal(pattern, @extra_lib_path)
435
+ end
436
+
437
+ if (found_or_not1 == false) && (found_or_not2 == false)
438
+ puts "cr: No words match with #{pattern.inspect}".red ; puts
439
+ end
440
+ end
441
+
442
+ #
443
+ # This `search_word_internal` routine is quite like `resolve_word`
444
+ # Notice:
445
+ # We handle two cases
446
+ #
447
+ # 1. the 'pattern' is the regexp itself
448
+ # 2. the 'pattern' is like '/blah/'
449
+ #
450
+ # The second is what Ruby and Perl users like to do, handle it!
451
+ #
452
+ def search_word_internal(pattern, library)
453
+
454
+ if pattern.nil?
455
+ abort "cr: Need an argument!".bold.red
456
+ end
457
+
458
+ if pattern =~ /^\/(.*)\/$/
459
+ regexp = %r[#$1]
460
+ else
461
+ regexp = %r[#{pattern}]
462
+ end
463
+
464
+ found_or_not = false
465
+
466
+ # Try to match every word in all dicts
467
+ Dir.children(library).each do |dict|
468
+
469
+ path = File.join(library, dict)
470
+ next if File.file? path
471
+ sheets = Dir.children(path).select do
472
+ _1.end_with?('.toml')
473
+ end
474
+
475
+ similar_words_in_a_dict = []
476
+
477
+ sheets.each do |sheet|
478
+ sheet_content = load_sheet(library, dict, File.basename(sheet,'.toml'))
479
+
480
+ sheet_content.keys.each do
481
+ if _1 =~ regexp
482
+ found_or_not = true
483
+ similar_words_in_a_dict << _1
484
+ end
485
+ end
486
+ # end of each sheet in a dict
487
+ end
488
+
489
+ unless similar_words_in_a_dict.empty?
490
+ pp_dict(dict)
491
+ require 'ls_table'
492
+ LsTable.ls(similar_words_in_a_dict) do |e|
493
+ puts e.blue
494
+ end
495
+ puts
496
+ end
497
+ end
498
+ return found_or_not
499
+ end
500
+
501
+
502
+ # 1. List Default library's dicts
503
+ # 2. List Extra library's dicts
504
+ # 3. List official dicts
505
+ def list_dicts
506
+
507
+ def _do_the_same_thing
508
+ count = 0
509
+ Dir.children(".").each do |dict|
510
+ next if File.file? dict
511
+ count += 1
512
+ prefix = (count.to_s + '.').ljust 4
513
+ puts " #{prefix}#{dict}"
514
+ end
515
+ end
516
+
517
+ Dir.chdir DEFAULT_LIB_PATH do
518
+ puts "Default library: #{DEFAULT_LIB_PATH}".bold.green
519
+ _do_the_same_thing
520
+ end
521
+
522
+ if @extra_lib_path
523
+ puts
524
+ Dir.chdir @extra_lib_path do
525
+ puts "Extra library: #{@extra_lib_path}".bold.green
526
+ _do_the_same_thing
527
+ end
528
+ end
529
+
530
+ puts ; puts "Official dictionaries: (Add it by 'cr -a xxx')".bold.green
531
+ puts RECOMMENDED_DICTS
532
+ end
533
+
534
+
535
+ def word_count
536
+ @counter.count!(display: true)
26
537
  end
27
538
 
28
539
  end
29
540
 
30
541
 
31
- class CrypticResolver::Resolver
32
542
 
33
- include CrypticResolver::Color
543
+ class CrypticResolver::Resolver
34
544
 
35
545
  require 'tomlrb'
36
546
  require 'fileutils'
@@ -47,21 +557,19 @@ class CrypticResolver::Resolver
47
557
  "https://github.com/cryptic-resolver/cryptic_technology"
48
558
  ]
49
559
 
50
- extend CrypticResolver::Color
51
-
52
560
  RECOMMENDED_DICTS = <<~EOF
53
- #{yellow("Default:")}
561
+ #{"Default:".yellow}
54
562
  common computer windows
55
563
  linux technology
56
564
 
57
- #{yellow("Field:")}
565
+ #{"Field:".yellow}
58
566
  economy medicine electronics
59
567
  science math
60
568
 
61
- #{yellow("Specific:")}
569
+ #{"Specific:".yellow}
62
570
  x86 signal
63
571
 
64
- #{yellow("Feature:")}
572
+ #{"Feature:".yellow}
65
573
  ccmywish/CRuby-Source-Code-Dictionary
66
574
 
67
575
  EOF
@@ -164,99 +672,4 @@ class CrypticResolver::Resolver
164
672
  return false
165
673
  end
166
674
 
167
-
168
- end
169
-
170
-
171
- class CrypticResolver::Resolver::Counter
172
-
173
- include CrypticResolver::Color
174
-
175
- attr_accessor :word_count_of_two_libs, # def_lib + extra_lib
176
- :word_count_of_def_lib,
177
- :word_count_of_extra_lib,
178
-
179
- :resolver
180
-
181
- def initialize(resolver)
182
- @word_count_of_two_libs = 0
183
- @word_count_of_def_lib = 0
184
- @word_count_of_extra_lib = 0
185
- @resolver = resolver
186
- end
187
-
188
- # a.toml
189
- # b.toml
190
- # ...
191
- def count_dict_words(library, dict)
192
- dict_dir = library + "/#{dict}"
193
- wc = 0
194
- Dir.children(dict_dir).each do |entry|
195
- next unless entry.end_with?('.toml')
196
- next if File.directory? dict_dir + "/#{entry}"
197
- sheet_content = @resolver.load_sheet(library, dict, entry.delete_suffix('.toml'))
198
- count = sheet_content.keys.count
199
-
200
- wc = wc + count
201
- end
202
- return wc
203
- end
204
-
205
-
206
- # Count default library
207
- def count_def_lib(display: )
208
- path = CrypticResolver::Resolver::DEFAULT_LIB_PATH
209
- default_lib = Dir.children path
210
- unless default_lib.empty?
211
- puts bold(green("Default library: ")) if display
212
- default_lib.each do |s|
213
- next if File.file? path + "/#{s}"
214
- wc = count_dict_words(path,s)
215
- @word_count_of_def_lib += wc
216
- # With color, ljust not works, so we disable color
217
- puts("#{wc.to_s.rjust(5)} #{s}") if display
218
- end
219
- end
220
- return @word_count_of_def_lib
221
- end
222
-
223
-
224
- # Count extra library
225
- def count_extra_lib(display: )
226
- if path = @resolver.extra_lib_path
227
- extra_lib = Dir.children path
228
- unless extra_lib.empty?
229
- puts bold(green("\nExtra library:")) if display
230
- extra_lib.each do |s|
231
- next if File.file? path + "/#{s}"
232
- wc = count_dict_words(path,s)
233
- @word_count_of_extra_lib += wc
234
- puts("#{wc.to_s.rjust(5)} #{s}") if display
235
- end
236
- end
237
- end
238
- return @word_count_of_extra_lib
239
- end
240
-
241
-
242
- def count!(display: )
243
- count_def_lib(display: display)
244
- count_extra_lib(display: display)
245
- @word_count_of_two_libs = @word_count_of_def_lib + @word_count_of_extra_lib
246
-
247
- if display
248
- puts
249
- puts "#{@word_count_of_def_lib.to_s.rjust(4)} words in Default library"
250
- puts "#{@word_count_of_extra_lib.to_s.rjust(4) } words in Extra library"
251
- puts "#{@word_count_of_two_libs.to_s.rjust(4) } words altogether"
252
- end
253
- end
254
-
255
-
256
- def reset!
257
- @word_count_of_two_libs = 0
258
- @word_count_of_def_lib = 0
259
- @word_count_of_extra_lib = 0
260
- end
261
-
262
675
  end