cr.rb 4.1.4 → 4.1.6

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 +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