cr.rb 4.1.4 → 4.1.5

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