cr.rb 4.1.4 → 4.1.6

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