cr.rb 4.0.3 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/bin/cr +418 -646
  3. data/lib/cr.rb +234 -14
  4. metadata +4 -4
data/bin/cr CHANGED
@@ -3,813 +3,585 @@
3
3
  # File : cr.rb
4
4
  # Authors : ccmywish <ccmywish@qq.com>
5
5
  # Created on : <2021-07-08>
6
- # Last modified : <2023-02-05>
6
+ # Last modified : <2023-02-12>
7
7
  #
8
8
  # cr:
9
9
  #
10
10
  # This file is used to explain a CRyptic command
11
11
  # or an acronym's real meaning in computer world or
12
12
  # other fields.
13
- #
14
13
  # ------------------------------------------------------
15
14
 
16
15
  require 'cr'
17
- require 'tomlrb'
18
- require 'fileutils'
19
16
 
20
- CR_DEFAULT_LIBRARY = File.expand_path("~/.cryptic-resolver")
17
+ class CrypticResolver::CliHandler
21
18
 
22
- $CR_DEFAULT_DICTS = [
23
- "https://github.com/cryptic-resolver/cryptic_common.git",
24
- "https://github.com/cryptic-resolver/cryptic_computer.git",
25
- "https://github.com/cryptic-resolver/cryptic_windows.git",
26
- "https://github.com/cryptic-resolver/cryptic_linux.git",
27
- "https://github.com/cryptic-resolver/cryptic_technology"
28
- ]
19
+ def initialize(args)
20
+ option = args.shift
29
21
 
30
- # The config file will override the default dicts, but not default library!
31
- if ENV['CRYPTIC_RESOLVER_CONFIG']
32
- file = ENV['CRYPTIC_RESOLVER_CONFIG']
33
- if test('f', file)
34
- config = Tomlrb.load_file file
22
+ case option when nil, '-h','--help' then help or exit end
35
23
 
36
- CR_EXTRA_LIBRARY ||= config['EXTRA_LIBRARY']
24
+ reslvr = CrypticResolver::Resolver.new
37
25
 
38
- if config['DEFAULT_DICTS']
39
- CR_DEFAULT_DICTS = config['DEFAULT_DICTS']
40
- else
41
- CR_DEFAULT_DICTS = $CR_DEFAULT_DICTS
42
- end
26
+ reslvr.add_default_dicts_if_none_exists
43
27
 
44
- else
45
- puts "FATAL: Your CRYPTIC_RESOLVER_CONFIG is NOT a file!"
46
- exit 1
28
+ case option
29
+ when '-l' then reslvr.list_dicts
30
+ when '-c' then reslvr.word_count
31
+ when '-u' then reslvr.update_dicts
32
+ when '-s' then reslvr.search_word args.shift
33
+ when '-a' then reslvr.add_dict args.shift
34
+ when '-d' then reslvr.del_dict args.shift
35
+ else reslvr.resolve_word option end
47
36
  end
48
37
 
49
- else
50
- # if user doesn't specify, we use the hard-coded defaults
51
- CR_EXTRA_LIBRARY = nil
52
- CR_DEFAULT_DICTS = $CR_DEFAULT_DICTS
53
- end
54
38
 
39
+ def help
40
+ puts <<~HELP
41
+ cr: Cryptic Resolver v#{CrypticResolver::GEM_VERSION}
55
42
 
56
- # Prevent runtime error
57
- if not CR_EXTRA_LIBRARY.nil?
58
- if ! test('d', CR_EXTRA_LIBRARY)
59
- puts "FATAL: Your CRYPTIC_RESOLVER_CONFIG's option 'EXTRA_LIBRARY' is NOT a legal directory!"
60
- exit 1
61
- end
62
- end
43
+ Usage:
44
+ cr <keyword> Search the keyword
45
+ cr emacs => Edit macros: A feature-rich editor
63
46
 
47
+ Options:
48
+ -c Print word count
49
+ -s <pattern> Search words matched with pattern
50
+ -h Print this help
64
51
 
65
- # This is used to display what you are pulling when adding dicts
66
- CR_DEFAULT_DICTS_USER_AND_NAMES = CR_DEFAULT_DICTS.map do |e|
67
- user, repo = e.split('/').last(2)
68
- repo = repo.split('.').first
69
- user + '/' + repo
70
- end
52
+ Dictionaries:
53
+ -l List local dicts and official dicts
54
+ -u Update all dicts in Default library
55
+ -a <https://repo[.git]> Add a new dict
56
+ -a <user/repo> Add a new dict from Github
57
+ -a <dict_name> Add an official dict, use -l to see
58
+ -d <dict_name> Delete a dict
71
59
 
72
- # Same with the pulled repo dirs' names in CR_DEFAULT_LIBRARY
73
- CR_DEFAULT_DICTS_NAMES = CR_DEFAULT_DICTS.map do |e|
74
- e.split('/').last.split('.').first
75
- end
76
-
77
-
78
- ####################
79
- # helper: for color
80
- ####################
81
-
82
- def bold(str) "\e[1m#{str}\e[0m" end
83
- def underline(str) "\e[4m#{str}\e[0m" end
84
- def red(str) "\e[31m#{str}\e[0m" end
85
- def green(str) "\e[32m#{str}\e[0m" end
86
- def yellow(str) "\e[33m#{str}\e[0m" end
87
- def blue(str) "\e[34m#{str}\e[0m" end
88
- def purple(str) "\e[35m#{str}\e[0m" end
89
- def cyan(str) "\e[36m#{str}\e[0m" end
60
+ HELP
61
+ end
90
62
 
63
+ end
91
64
 
92
- ####################
93
- # core: logic
94
- ####################
95
65
 
96
- def is_there_any_dict?
97
- unless Dir.exist? CR_DEFAULT_LIBRARY
98
- Dir.mkdir CR_DEFAULT_LIBRARY
99
- end
66
+ class CrypticResolver::Resolver
100
67
 
101
- !Dir.empty? CR_DEFAULT_LIBRARY
102
- end
68
+ # Notice that we only update the Default library, not Extra library
69
+ def update_dicts
103
70
 
71
+ @counter.count_def_lib(display: false)
72
+ old_wc = @counter.word_count_of_def_lib
104
73
 
105
- def add_default_dicts_if_none_exists
106
- unless is_there_any_dict?
107
- puts "cr: Adding default dicts..."
74
+ puts "cr: Updating all dicts in Default library..."
108
75
 
109
76
  begin
110
- if RUBY_PLATFORM.include? "mingw"
111
- # Windows doesn't have fork
112
- CR_DEFAULT_DICTS_USER_AND_NAMES.each_with_index do |name, i|
113
- puts "cr: Pulling #{name}..."
114
- `git -C #{CR_DEFAULT_LIBRARY} clone #{CR_DEFAULT_DICTS[i]} -q`
115
- end
116
- else
117
- # *nix-like
118
- CR_DEFAULT_DICTS_USER_AND_NAMES.each_with_index do |name, i|
119
- fork do
120
- puts "cr: Pulling #{name}..."
121
- `git -C #{CR_DEFAULT_LIBRARY} clone #{CR_DEFAULT_DICTS[i]} -q`
77
+ Dir.chdir DEFAULT_LIB_PATH do
78
+ if Gem.win_platform?
79
+ # Windows doesn't have fork
80
+ Dir.children(DEFAULT_LIB_PATH).each do |dict|
81
+ next if File.file? dict
82
+ puts "cr: Wait to update #{dict}..."
83
+ `git -C ./#{dict} pull -q`
122
84
  end
123
- end
124
- Process.waitall
85
+ else
86
+ # *nix
87
+ Dir.children(DEFAULT_LIB_PATH).each do |dict|
88
+ next if File.file? dict
89
+ fork do
90
+ puts "cr: Wait to update #{dict}..."
91
+ `git -C ./#{dict} pull -q`
92
+ end
93
+ end
94
+ Process.waitall
95
+ end # end if/else
125
96
  end
126
97
 
127
98
  rescue Interrupt
128
- puts "cr: Cancel add default dicts"
129
- exit 1
99
+ abort "cr: Cancel update"
130
100
  end
131
101
 
132
- puts "cr: Add done"
133
- word_count(p: false)
134
- puts
135
- puts "#{$DefaultLibWordCount} words added"
136
-
137
- # Really added
138
- return true
139
- end
140
- # Not added
141
- return false
142
- end
143
-
102
+ puts "cr: Update done"
144
103
 
145
- # Notice that we only update the Default library, not Extra library
146
- def update_dicts()
147
- return if add_default_dicts_if_none_exists
104
+ # clear
105
+ @counter.word_count_of_def_lib = 0
106
+ # recount
107
+ @counter.count_def_lib(display: false)
148
108
 
149
- word_count(p: false)
150
- old_wc = $DefaultLibWordCount
151
-
152
- puts "cr: Updating all dicts in Default library..."
153
-
154
- begin
155
- Dir.chdir CR_DEFAULT_LIBRARY do
156
-
157
- if RUBY_PLATFORM.include? "mingw"
158
- # Windows doesn't have fork
159
- Dir.children(CR_DEFAULT_LIBRARY).each do |dict|
160
- puts "cr: Wait to update #{dict}..."
161
- `git -C ./#{dict} pull -q`
162
- end
163
- else
164
- # *nix
165
- Dir.children(CR_DEFAULT_LIBRARY).each do |dict|
166
- fork do
167
- puts "cr: Wait to update #{dict}..."
168
- `git -C ./#{dict} pull -q`
169
- end
170
- end
171
- Process.waitall
172
-
173
- end # end if/else
174
- end
175
-
176
- rescue Interrupt
177
- puts "cr: Cancel update"
178
- exit 1
109
+ new_wc = @counter.word_count_of_def_lib
110
+ puts ; puts "#{new_wc - old_wc} words added in Default library"
179
111
  end
180
112
 
181
- puts "cr: Update done"
182
-
183
- # clear
184
- $DefaultLibWordCount = 0
185
- # recount
186
- word_count(p: false)
187
113
 
188
- new_wc = $DefaultLibWordCount
189
-
190
- puts
191
- puts "#{new_wc - old_wc} words added in Default library"
192
-
193
- end
194
-
195
-
196
- def add_dict(repo)
197
- if repo.nil?
198
- puts bold(red("cr: Need an argument!"))
199
- exit -1
200
- end
201
-
202
- # Ensure the cr home dir exists
203
- FileUtils.mkdir_p(CR_DEFAULT_LIBRARY)
114
+ def add_dict(repo)
115
+ if repo.nil?
116
+ abort bold(red("cr: Need an argument!"))
117
+ end
204
118
 
205
- # Simplify adding dictionary
206
- if !repo.start_with?("https://") and !repo.start_with?("git@")
207
- if repo.include?('/')
208
- repo = "https://github.com/#{repo}.git"
209
- else
210
- repo = "https://github.com/cryptic-resolver/cryptic_#{repo}.git"
119
+ # Simplify adding dictionary
120
+ if !repo.start_with?("https://") and !repo.start_with?("git@")
121
+ if repo.include?('/')
122
+ repo = "https://github.com/#{repo}.git"
123
+ else
124
+ repo = "https://github.com/cryptic-resolver/cryptic_#{repo}.git"
125
+ end
211
126
  end
212
- end
213
127
 
214
- begin
128
+ begin
215
129
  puts "cr: Adding new dictionary..."
216
- `git -C #{CR_DEFAULT_LIBRARY} clone #{repo} -q`
217
- rescue Interrupt
218
- puts "cr: Cancel add dict"
219
- exit 1
220
- end
221
-
222
- puts "cr: Add new dictionary done"
130
+ `git -C #{DEFAULT_LIB_PATH} clone #{repo} -q`
131
+ rescue Interrupt
132
+ abort "cr: Cancel add dict"
133
+ end
223
134
 
224
- # github/com/ccmywish/ruby_knowledge(.git)
225
- dict = repo.split('/')[-1].delete_suffix('.git')
226
- wc = count_dict_words(CR_DEFAULT_LIBRARY ,dict)
227
- puts
228
- puts "#{wc} words added"
135
+ puts "cr: Add new dictionary done"
229
136
 
230
- end
137
+ # github/com/ccmywish/ruby_knowledge(.git)
138
+ dict = repo.split('/')[-1].delete_suffix('.git')
139
+ wc = @counter.count_dict_words(DEFAULT_LIB_PATH ,dict)
140
+ puts ; puts "#{wc} words added"
141
+ end
231
142
 
232
143
 
233
- def del_dict(repo)
234
- if repo.nil?
235
- puts bold(red("cr: Need an argument!"))
236
- exit -1
237
- end
238
- Dir.chdir CR_DEFAULT_LIBRARY do
239
- begin
144
+ def del_dict(repo)
145
+ if repo.nil?
146
+ abort bold(red("cr: Need an argument!"))
147
+ end
148
+ Dir.chdir DEFAULT_LIB_PATH do
149
+ begin
240
150
  # Dir.rmdir repo # Can't rm a filled dir
241
151
  # FileUtils.rmdir repo # Can't rm a filled dir
242
152
  FileUtils.rm_rf repo
243
153
  puts "cr: Delete dictionary #{bold(green(repo))} done"
244
- rescue Exception => e
154
+ rescue Exception => e
245
155
  puts bold(red("cr: #{e}"))
246
156
  list_dicts
157
+ end
247
158
  end
248
159
  end
249
- end
250
-
251
160
 
252
- def load_sheet(library, dict, sheet_name)
253
- file = library + "/#{dict}/#{sheet_name}.toml"
254
161
 
255
- if File.exist? file
256
- return Tomlrb.load_file file # gem 'tomlrb'
257
- # return TOML.load_file file # gem 'toml'
258
- else
259
- nil
162
+ def load_sheet(library, dict, sheet_name)
163
+ file = library + "/#{dict}/#{sheet_name}.toml"
164
+ if File.exist? file
165
+ return Tomlrb.load_file file # gem 'tomlrb'
166
+ # return TOML.load_file file # gem 'toml'
167
+ else nil end
260
168
  end
261
- end
262
169
 
263
170
 
264
- #
265
- # Pretty print the info of the given word
266
- #
267
- # A info looks like this
268
- # emacs = {
269
- # name = "Emacs"
270
- # desc = "edit macros"
271
- # more = "a feature-rich editor"
272
- # see = ["Vim"]
273
- # }
274
- #
275
- # @param info [Hash] the information of the given word (mapped to a keyword in TOML)
276
- #
277
- def pp_info(info)
278
- name = info['name'] || red("No name!") # keyword `or` is invalid here in Ruby
279
-
280
- desc = info['desc']
281
- more = info['more']
282
-
283
- if desc
284
- puts "\n #{name}: #{desc}"
285
- print "\n ",more,"\n" if more
286
- else
287
- puts "\n #{name}"
288
- print "\n ",more,"\n" if more
289
- end
171
+ =begin
172
+ Pretty print the info of the given word
290
173
 
291
- if see_also = info['see']
292
- print "\n", purple("SEE ALSO ")
293
- if see_also.is_a?(Array)
294
- last_ndx = see_also.size - 1
295
- see_also.each_with_index do |x,i|
296
- if last_ndx == i
297
- print underline(x) # Last word doesn't show space
298
- else
299
- print underline(x),' '
174
+ A info looks like this
175
+ emacs = {
176
+ name = "Emacs"
177
+ desc = "edit macros"
178
+ more = "a feature-rich editor"
179
+ see = ["Vim"]
180
+ }
181
+
182
+ @param info [Hash] the information of the given word (mapped to a keyword in TOML)
183
+ =end
184
+ def pp_info(info)
185
+ name = info['name'] || red("No name!") # keyword `or` is invalid here in Ruby
186
+
187
+ desc = info['desc']
188
+ more = info['more']
189
+
190
+ if desc
191
+ puts "\n #{name}: #{desc}"
192
+ print "\n ",more,"\n" if more
193
+ else
194
+ puts "\n #{name}"
195
+ print "\n ",more,"\n" if more
196
+ end
197
+
198
+ if see_also = info['see']
199
+ print "\n", purple("SEE ALSO ")
200
+ if see_also.is_a?(Array)
201
+ last_ndx = see_also.size - 1
202
+ see_also.each_with_index do |x,i|
203
+ if last_ndx == i
204
+ print underline(x) # Last word doesn't show space
205
+ else
206
+ print underline(x),' '
207
+ end
300
208
  end
209
+ else
210
+ print underline(see_also)
301
211
  end
302
- else
303
- print underline(see_also)
212
+ puts
304
213
  end
305
214
  puts
306
215
  end
307
- puts
308
- end
309
-
310
- # Print default cryptic_ dicts
311
- def pp_dict(dict)
312
- puts green("From: #{dict}")
313
- end
314
-
315
216
 
316
- #
317
- # Used for synonym jump
318
- # Because we absolutely jump to a must-have word
319
- # So we can directly lookup to it
320
- #
321
- # Notice that, we must jump to a specific word definition
322
- # So in the toml file, you must specify the precise word.
323
- # If it has multiple meanings, for example
324
- #
325
- # [blah]
326
- # same = "xdg" # this is wrong, because xdg has multiple
327
- # # definitions, and all of them specify a
328
- # # category
329
- #
330
- # [blah]
331
- # same = "XDG downloader =>xdg.Download" # this is correct
332
- #
333
- # [blah]
334
- # name = "BlaH" # You may want to display a better name first
335
- # same = "XDG downloader =>xdg.Download" # this is correct
336
- #
337
- #
338
- def pp_same_info(library, dict, word, cache_content, same_key, own_name)
339
-
340
- # If it's a synonym for anther word,
341
- # we should lookup into this dict again, but maybe with a different file
342
217
 
343
- # file name
344
- x = word.chr.downcase
345
-
346
- #
347
- # dictionary maintainer must obey the rule for xxx.yyy word:
348
- # xxx should be lower case
349
- # yyy can be any case
350
- #
351
- # Because yyy should clearly explain the category info, IBM is better than ibm
352
- # Implementation should not be too simple if we want to stress the function we
353
- # expect.
354
- #
355
- # 'jump to' will output to user, so this is important not only inside our sheet.
356
- #
357
- # same = "XDM downloader=>xdm.Download"
358
- #
359
- # We split 'same' key into two parts via spaceship symbol `=>`, first part will
360
- # output to user, the second part is for internal jump.
361
- #
362
-
363
- jump_to, same = same_key.split("=>")
364
- same = jump_to if same.nil?
365
-
366
- unless own_name
367
- own_name = word
218
+ # Print default cryptic_ dicts
219
+ def pp_dict(dict)
220
+ puts green("From: #{dict}")
368
221
  end
369
- puts blue(bold(own_name)) + ' redirects to ' + blue(bold(jump_to))
370
222
 
371
- #
372
- # As '.' is used to delimit a word and a category, what if
373
- # we jump to a dotted word?
374
- #
375
- # [eg]
376
- # same = "e.g." # this must lead to a wrong resolution to
377
- # # word 'e', category 'g'
378
- #
379
- # All you need is to be like this:
380
- #
381
- # [eg]
382
- # same = "'e.g.'" # cr will notice the single quote
383
- #
384
223
 
385
- if same =~ /^'(.*)'$/
386
- same, category = $1, nil
387
- else
388
- same, category = same.split('.')
389
- end
224
+ =begin
225
+ Used for synonym jump
226
+ Because we absolutely jump to a must-have word
227
+ So we can directly lookup to it
390
228
 
391
- if same.chr == x
392
- # No need to load another dictionary if match
393
- sheet_content = cache_content
394
- else
395
- sheet_content = load_sheet(library, dict, same.chr.downcase)
396
- end
229
+ Notice that, we must jump to a specific word definition
230
+ So in the toml file, you must specify the precise word.
231
+ If it has multiple meanings, for example
397
232
 
398
- if category.nil?
399
- info = sheet_content[same]
400
- else
401
- info = sheet_content[same][category]
402
- end
233
+ [blah]
234
+ same = "xdg" # this is wrong, because xdg has multiple
235
+ # definitions, and all of them specify a
236
+ # category
403
237
 
404
- if info.nil?
405
- puts red("WARN: Synonym jumps to the wrong place `#{same}`,
406
- Please consider fixing this in `#{x}.toml` of the dictionary `#{dict}`")
407
- # exit
408
- return false
409
- # double or more jumps
410
- elsif same_key = info['same']
411
- own_name = info['name']
412
- return pp_same_info(library, dict, same, cache_content, same_key, own_name)
413
- else
414
- pp_info(info)
415
- return true
416
- end
417
- end
238
+ [blah]
239
+ same = "XDG downloader =>xdg.Download" # this is correct
418
240
 
241
+ [blah]
242
+ name = "BlaH" # You may want to display a better name first
243
+ same = "XDG downloader =>xdg.Download" # this is correct
244
+ =end
245
+ def pp_same_info(library, dict, word, cache_content, same_key, own_name)
419
246
 
247
+ # If it's a synonym for anther word,
248
+ # we should lookup into this dict again, but maybe with a different file
420
249
 
421
- #
422
- # Lookup the given word in a sheet (a toml file) and also print.
423
- # The core idea is that:
424
- #
425
- # 1. if the word is `same` with another synonym, it will directly jump to
426
- # a word in this dictionary, but maybe a different sheet.
427
- #
428
- # 2. load the toml file and check the given word
429
- # 2.1 with no category specifier
430
- # [abcd]
431
- # 2.2 with category specifier
432
- # [abcd.tYPe]
433
- #
434
- def lookup(library, dict, file, word)
435
- sheet_content = load_sheet(library, dict, file)
436
- return false if sheet_content.nil?
250
+ # file name
251
+ x = word.chr.downcase
437
252
 
438
- info = sheet_content[word]
439
- return false if info.nil?
253
+ =begin
254
+ dictionary maintainer must obey the rule for xxx.yyy word:
255
+ xxx should be lower case
256
+ yyy can be any case
440
257
 
441
- # Warn if the info is empty. For example:
442
- # emacs = { }
443
- if info.size == 0
444
- puts red("WARN: Lack of everything of the given word
445
- Please consider fixing this in the dict `#{dict}`")
446
- exit
447
- end
258
+ Because yyy should clearly explain the category info, IBM is better than ibm
259
+ Implementation should not be too simple if we want to stress the function we
260
+ expect.
448
261
 
262
+ 'jump to' will output to user, so this is important not only inside our sheet.
449
263
 
450
- # Word with no category specifier
451
- # We call this meaning as type 1
452
- type_1_exist_flag = false
264
+ same = "XDM downloader=>xdm.Download"
453
265
 
454
- # if already jump, don't check the word itself
455
- is_jump = false
266
+ We split 'same' key into two parts via spaceship symbol `=>`, first part will
267
+ output to user, the second part is for internal jump.
268
+ =end
269
+ jump_to, same = same_key.split("=>")
270
+ same = jump_to if same.nil?
456
271
 
457
- # synonym info print
458
- if same_key = info['same']
459
- own_name = info['name']
460
- pp_dict(dict)
461
- pp_same_info(library, dict, word, sheet_content, same_key, own_name)
462
- # It's also a type 1
463
- type_1_exist_flag = true
464
- is_jump = true
465
- end
272
+ unless own_name
273
+ own_name = word
274
+ end
275
+ puts blue(bold(own_name)) + ' redirects to ' + blue(bold(jump_to))
466
276
 
467
- # normal info print
468
- # To developer:
469
- # The word should at least has one of `desc` and `more`
470
- # But when none exists, this may not be considered wrong,
471
- # Because the type2 make the case too.
472
- #
473
- # So, just ignore it, even if it's really a mistake(insignificant)
474
- # by dictionary maintainers.
475
- #
476
- if !is_jump && (info.has_key?('desc') || info.has_key?('more'))
477
- pp_dict(dict)
478
- pp_info(info)
479
- type_1_exist_flag = true
480
- end
277
+ =begin
278
+ As '.' is used to delimit a word and a category, what if
279
+ we jump to a dotted word?
481
280
 
482
- # Meanings with category specifier
483
- # We call this meaning as type 2
484
- categories = info.keys - ["name", "desc", "more", "same", "see"]
281
+ [eg]
282
+ same = "e.g." # this must lead to a wrong resolution to
283
+ # word 'e', category 'g'
485
284
 
486
- if !categories.empty?
285
+ All you need is to be like this:
487
286
 
488
- if type_1_exist_flag
489
- print blue(bold("OR")),"\n"
287
+ [eg]
288
+ same = "'e.g.'" # cr will notice the single quote
289
+ =end
290
+ if same =~ /^'(.*)'$/
291
+ same, category = $1, nil
490
292
  else
491
- pp_dict(dict)
293
+ same, category = same.split('.')
492
294
  end
493
295
 
494
- categories.each do |meaning|
495
- info0 = sheet_content[word][meaning]
496
- if same_key = info0['same']
497
- own_name = info0['name']
498
- pp_same_info(library, dict, word, sheet_content, same_key, own_name)
499
- else
500
- pp_info(info0)
501
- end
502
-
503
- # last meaning doesn't show this separate line
504
- print blue(bold("OR")),"\n" unless categories.last == meaning
296
+ if same.chr == x
297
+ # No need to load another dictionary if match
298
+ sheet_content = cache_content
299
+ else
300
+ sheet_content = load_sheet(library, dict, same.chr.downcase)
505
301
  end
506
- return true
507
- elsif type_1_exist_flag
508
- return true
509
- else
510
- return false
511
- end
512
- end
513
-
514
-
515
- #
516
- # The main procedure of `cr`
517
- #
518
- # 1. Search the default library first
519
- # 2. Search the extra library if it does exist
520
- #
521
- # The `search` is done via the `lookup` function. It will print
522
- # the info while finding. If `lookup` always return false then
523
- # means lacking of this word in our dicts. So a welcomed
524
- # contribution is printed on the screen.
525
- #
526
- def resolve_word(word)
527
302
 
528
- add_default_dicts_if_none_exists
529
-
530
- word = word.downcase # downcase! would lead to frozen error in Ruby 2.7.2
531
- # The index is the toml file we'll look into
532
- index = word.chr
533
- case index
534
- when '0'..'9'
535
- index = '0-9'
536
- end
537
-
538
- # cache lookup's results
539
- results = []
540
-
541
- # First consider the default library
542
- default = Dir.children(CR_DEFAULT_LIBRARY)
543
- default.each do |dict|
544
- results << lookup(CR_DEFAULT_LIBRARY,dict,index,word)
545
- end
303
+ if category.nil?
304
+ info = sheet_content[same]
305
+ else
306
+ info = sheet_content[same][category]
307
+ end
546
308
 
547
- # Then is the extra library
548
- if CR_EXTRA_LIBRARY
549
- extra = Dir.children(CR_EXTRA_LIBRARY)
550
- extra.each do |dict|
551
- results << lookup(CR_EXTRA_LIBRARY,dict,index,word)
309
+ if info.nil?
310
+ puts red("WARN: Synonym jumps to the wrong place `#{same}`,
311
+ Please consider fixing this in `#{x}.toml` of the dictionary `#{dict}`")
312
+ # exit
313
+ return false
314
+ # double or more jumps
315
+ elsif same_key = info['same']
316
+ own_name = info['name']
317
+ return pp_same_info(library, dict, same, cache_content, same_key, own_name)
318
+ else
319
+ pp_info(info)
320
+ return true
552
321
  end
553
322
  end
554
323
 
555
- unless results.include? true
556
- puts <<-NotFound
557
- cr: Not found anything.
558
-
559
- You could
560
-
561
- #{blue("case 1: Update all dicts")}
562
-
563
- #{yellow("$cr -u")}
564
-
565
- #{blue("case 2: List available official and feature dicts")}
566
324
 
567
- #{yellow("$cr -l")}
568
325
 
569
- #{yellow("$cr -a repo")} (Add a specific dict to default lib)
570
-
571
- #{blue("case 3: Contribute to theses dicts")}
572
-
573
- Visit: https://github.com/cryptic-resolver
326
+ #
327
+ # Lookup the given word in a sheet (a toml file) and also print.
328
+ # The core idea is that:
329
+ #
330
+ # 1. if the word is `same` with another synonym, it will directly jump to
331
+ # a word in this dictionary, but maybe a different sheet.
332
+ #
333
+ # 2. load the toml file and check the given word
334
+ # 2.1 with no category specifier
335
+ # [abcd]
336
+ # 2.2 with category specifier
337
+ # [abcd.tYPe]
338
+ #
339
+ def lookup(library, dict, file, word)
340
+ sheet_content = load_sheet(library, dict, file)
341
+ return false if sheet_content.nil?
574
342
 
575
- NotFound
343
+ info = sheet_content[word]
344
+ return false if info.nil?
576
345
 
577
- else
578
- return
579
- end
346
+ # Warn if the info is empty. For example:
347
+ # emacs = { }
348
+ if info.size == 0
349
+ abort red("WARN: Lack of everything of the given word
350
+ Please consider fixing this in the dict `#{dict}`")
351
+ end
580
352
 
581
- end
353
+ # Word with no category specifier
354
+ # We call this meaning as type 1
355
+ type_1_exist_flag = false
582
356
 
357
+ # if already jump, don't check the word itself
358
+ is_jump = false
583
359
 
584
- #
585
- # Delegate to `search_word_internal`
586
- #
587
- def search_word(pattern)
588
- found_or_not1 = false
589
- found_or_not2 = false
360
+ # synonym info print
361
+ if same_key = info['same']
362
+ own_name = info['name']
363
+ pp_dict(dict)
364
+ pp_same_info(library, dict, word, sheet_content, same_key, own_name)
365
+ # It's also a type 1
366
+ type_1_exist_flag = true
367
+ is_jump = true
368
+ end
590
369
 
591
- found_or_not1 = search_word_internal(pattern, CR_DEFAULT_LIBRARY)
592
- if CR_EXTRA_LIBRARY
593
- found_or_not2 = search_word_internal(pattern, CR_EXTRA_LIBRARY)
594
- end
370
+ # normal info print
371
+ # To developer:
372
+ # The word should at least has one of `desc` and `more`
373
+ # But when none exists, this may not be considered wrong,
374
+ # Because the type2 make the case too.
375
+ #
376
+ # So, just ignore it, even if it's really a mistake(insignificant)
377
+ # by dictionary maintainers.
378
+ #
379
+ if !is_jump && (info.has_key?('desc') || info.has_key?('more'))
380
+ pp_dict(dict)
381
+ pp_info(info)
382
+ type_1_exist_flag = true
383
+ end
595
384
 
596
- if (found_or_not1 == false) && (found_or_not2 == false)
597
- puts red("cr: No words match with #{pattern.inspect}")
598
- puts
599
- end
600
- end
385
+ # Meanings with category specifier
386
+ # We call this meaning as type 2
387
+ categories = info.keys - ["name", "desc", "more", "same", "see"]
601
388
 
602
- #
603
- # This `search_word_internal` routine is quite like `resolve_word`
604
- # Notice:
605
- # We handle two cases
606
- #
607
- # 1. the 'pattern' is the regexp itself
608
- # 2. the 'pattern' is like '/blah/'
609
- #
610
- # The second is what Ruby and Perl users like to do, handle it!
611
- #
612
- def search_word_internal(pattern, library)
389
+ if !categories.empty?
613
390
 
614
- if pattern.nil?
615
- puts bold(red("cr: Need an argument!"))
616
- exit -1
617
- end
391
+ if type_1_exist_flag
392
+ print blue(bold("OR")),"\n"
393
+ else
394
+ pp_dict(dict)
395
+ end
618
396
 
619
- add_default_dicts_if_none_exists
397
+ categories.each do |meaning|
398
+ info0 = sheet_content[word][meaning]
399
+ if same_key = info0['same']
400
+ own_name = info0['name']
401
+ pp_same_info(library, dict, word, sheet_content, same_key, own_name)
402
+ else
403
+ pp_info(info0)
404
+ end
620
405
 
621
- if pattern =~ /^\/(.*)\/$/
622
- regexp = %r[#$1]
623
- else
624
- regexp = %r[#{pattern}]
406
+ # last meaning doesn't show this separate line
407
+ print blue(bold("OR")),"\n" unless categories.last == meaning
408
+ end
409
+ return true
410
+ elsif type_1_exist_flag then return true
411
+ else return false end
625
412
  end
626
413
 
627
- found_or_not = false
628
414
 
629
415
  #
630
- # Try to match every word in all dicts
416
+ # The main procedure of `cr`
417
+ #
418
+ # 1. Search the default library first
419
+ # 2. Search the extra library if it does exist
631
420
  #
632
- Dir.children(library).each do |dict|
633
- sheets = Dir.children(File.join(library, dict)).select do
634
- _1.end_with?('.toml')
421
+ # The `search` is done via the `lookup` function. It will print
422
+ # the info while finding. If `lookup` always return false then
423
+ # means lacking of this word in our dicts. So a welcomed
424
+ # contribution is printed on the screen.
425
+ #
426
+ def resolve_word(word)
427
+
428
+ word = word.downcase # downcase! would lead to frozen error in Ruby 2.7.2
429
+ # The index is the toml file we'll look into
430
+ index = word.chr
431
+ case index
432
+ when '0'..'9'
433
+ index = '0-9'
635
434
  end
636
435
 
637
- similar_words_in_a_dict = []
638
-
639
- sheets.each do |sheet|
640
- sheet_content = load_sheet(library, dict, File.basename(sheet,'.toml'))
436
+ # cache lookup's results
437
+ results = []
641
438
 
642
- sheet_content.keys.each do
643
- if _1 =~ regexp
644
- found_or_not = true
645
- similar_words_in_a_dict << _1
646
- end
647
- end
648
- # end of each sheet in a dict
439
+ # First consider the default library
440
+ default = Dir.children(DEFAULT_LIB_PATH)
441
+ default.each do |dict|
442
+ results << lookup(DEFAULT_LIB_PATH,dict,index,word)
649
443
  end
650
444
 
651
- unless similar_words_in_a_dict.empty?
652
- pp_dict(dict)
653
- require 'ls_table'
654
- LsTable.ls(similar_words_in_a_dict) do |e|
655
- puts blue(e)
445
+ # Then is the extra library
446
+ if @extra_lib_path
447
+ extra = Dir.children(@extra_lib_path)
448
+ extra.each do |dict|
449
+ results << lookup(@extra_lib_path,dict,index,word)
656
450
  end
657
- puts
658
451
  end
659
- end
660
- return found_or_not
661
- end
662
452
 
453
+ unless results.include? true
454
+ puts <<~NotFound
455
+ cr: Not found anything.
663
456
 
664
- def help
665
- word_count(p: false)
666
- puts <<-HELP
667
- cr: Cryptic Resolver v#{CR_GEM_VERSION} (#{$TwoLibWordCount} words: Default lib/#{$DefaultLibWordCount} && Extra lib/#{$ExtraLibWordCount})
457
+ You could
668
458
 
669
- Usage:
459
+ #{blue("case 1: Update all dicts")}
670
460
 
671
- cr emacs => Edit macros: A feature-rich editor
672
- cr -c => Print word count
673
- cr -l => List local dicts and official dicts
674
- cr -u => Update all dicts in Default library
461
+ #{yellow("$cr -u")}
675
462
 
676
- cr -a repo.git => Add a new dict
677
- cr -a user/repo => Add a new dict from Github
678
- cr -a repo => Add an official dict from Github
463
+ #{blue("case 2: List available official and feature dicts")}
679
464
 
680
- cr -d cryptic_xx => Delete a dict
681
- cr -s pattern => Search words matched with pattern
682
- cr -v => Print version
683
- cr -h => Print this help
465
+ #{yellow("$cr -l")}
684
466
 
685
- HELP
467
+ #{yellow("$cr -a repo")} (Add a specific dict to default lib)
686
468
 
687
- add_default_dicts_if_none_exists
469
+ #{blue("case 3: Contribute to theses dicts")}
688
470
 
689
- end
471
+ Visit: https://github.com/cryptic-resolver
690
472
 
473
+ NotFound
474
+ else return end
475
+ end
691
476
 
692
- def print_version
693
- puts "cr: Cryptic Resolver version #{CR_GEM_VERSION} in Ruby "
694
- end
695
477
 
478
+ #
479
+ # Delegate to `search_word_internal`
480
+ #
481
+ def search_word(pattern)
482
+ found_or_not1 = false
483
+ found_or_not2 = false
696
484
 
697
- #
698
- # 1. List Default library's dicts
699
- # 2. List Extra library's dicts
700
- # 3. List official dicts
701
- #
702
- def list_dicts
703
- Dir.chdir CR_DEFAULT_LIBRARY do
704
- puts blue("=> Default library: #{CR_DEFAULT_LIBRARY}")
705
- Dir.children(CR_DEFAULT_LIBRARY).each_with_index do |dict,i|
706
- puts "#{blue(i+1)}. #{bold(green(dict))}"
485
+ found_or_not1 = search_word_internal(pattern, DEFAULT_LIB_PATH)
486
+ if @extra_lib_path
487
+ found_or_not2 = search_word_internal(pattern, @extra_lib_path)
707
488
  end
708
- end
709
489
 
710
- if CR_EXTRA_LIBRARY
711
- puts
712
- Dir.chdir CR_EXTRA_LIBRARY do
713
- puts blue("=> Extra library: #{CR_EXTRA_LIBRARY}")
714
- Dir.children(CR_EXTRA_LIBRARY).each_with_index do |dict,i|
715
- puts "#{blue(i+1)}. #{bold(green(dict))}"
716
- end
490
+ if (found_or_not1 == false) && (found_or_not2 == false)
491
+ puts red("cr: No words match with #{pattern.inspect}") ; puts
717
492
  end
718
493
  end
719
494
 
720
- puts
721
- puts blue("=> Official dicts: (Add it by 'cr -a xxx')")
722
- puts CR_OFFICIAL_DICTS
723
-
724
- end
725
-
726
-
727
- # Two libraries word count (Default library + Extra library)
728
- $TwoLibWordCount = 0
729
- # Default library word count
730
- $DefaultLibWordCount = 0
731
- # Extra library word count
732
- $ExtraLibWordCount = 0
733
-
734
- def count_dict_words(library, dict)
495
+ #
496
+ # This `search_word_internal` routine is quite like `resolve_word`
497
+ # Notice:
498
+ # We handle two cases
499
+ #
500
+ # 1. the 'pattern' is the regexp itself
501
+ # 2. the 'pattern' is like '/blah/'
502
+ #
503
+ # The second is what Ruby and Perl users like to do, handle it!
504
+ #
505
+ def search_word_internal(pattern, library)
735
506
 
736
- dict_dir = library + "/#{dict}"
507
+ if pattern.nil?
508
+ abort bold(red("cr: Need an argument!"))
509
+ end
737
510
 
738
- wc = 0
511
+ if pattern =~ /^\/(.*)\/$/
512
+ regexp = %r[#$1]
513
+ else
514
+ regexp = %r[#{pattern}]
515
+ end
739
516
 
740
- Dir.children(dict_dir).each do |entry|
741
- next unless entry.end_with?('.toml')
742
- sheet_content = load_sheet(library, dict, entry.delete_suffix('.toml'))
743
- count = sheet_content.keys.count
517
+ found_or_not = false
744
518
 
745
- wc = wc + count
746
- end
747
- return wc
748
- end
519
+ # Try to match every word in all dicts
520
+ Dir.children(library).each do |dict|
521
+ sheets = Dir.children(File.join(library, dict)).select do
522
+ _1.end_with?('.toml')
523
+ end
749
524
 
525
+ similar_words_in_a_dict = []
750
526
 
751
- def word_count(p:)
752
- # Always check before Dir.children (this method creates dir if not exists)
753
- is_there_any_dict?
527
+ sheets.each do |sheet|
528
+ sheet_content = load_sheet(library, dict, File.basename(sheet,'.toml'))
754
529
 
755
- default_lib = Dir.children(CR_DEFAULT_LIBRARY)
530
+ sheet_content.keys.each do
531
+ if _1 =~ regexp
532
+ found_or_not = true
533
+ similar_words_in_a_dict << _1
534
+ end
535
+ end
536
+ # end of each sheet in a dict
537
+ end
756
538
 
757
- # Count default library
758
- unless default_lib.empty?
759
- puts(bold(green("Default library: "))) if p
760
- default_lib.each do |s|
761
- wc = count_dict_words(CR_DEFAULT_LIBRARY,s)
762
- $DefaultLibWordCount += wc
763
- # With color, ljust not works, so we disable color
764
- puts(" #{s.ljust(17)}: #{wc}") if p
539
+ unless similar_words_in_a_dict.empty?
540
+ pp_dict(dict)
541
+ require 'ls_table'
542
+ LsTable.ls(similar_words_in_a_dict) do |e|
543
+ puts blue(e)
544
+ end
545
+ puts
546
+ end
765
547
  end
548
+ return found_or_not
766
549
  end
767
550
 
768
551
 
769
- if CR_EXTRA_LIBRARY
770
- extra_lib = Dir.children(CR_EXTRA_LIBRARY)
771
- extra_lib.reject! do |f|
772
- File.file? f
552
+ # 1. List Default library's dicts
553
+ # 2. List Extra library's dicts
554
+ # 3. List official dicts
555
+ def list_dicts
556
+ Dir.chdir DEFAULT_LIB_PATH do
557
+ puts blue("=> Default library: #{DEFAULT_LIB_PATH}")
558
+ Dir.children(DEFAULT_LIB_PATH).each_with_index do |dict,i|
559
+ puts "#{blue(i+1)}. #{bold(green(dict))}"
560
+ end
773
561
  end
774
- # Count extra library
775
- unless extra_lib.empty?
776
- wc = 0
777
- puts(bold(blue("\nExtra library:"))) if p
778
- extra_lib.each do |s|
779
- wc = count_dict_words(CR_EXTRA_LIBRARY,s)
780
- $ExtraLibWordCount += wc
781
- puts(" #{s.ljust(17)}: #{wc}") if p
562
+
563
+ if @extra_lib_path
564
+ puts
565
+ Dir.chdir @extra_lib_path do
566
+ puts blue("=> Extra library: #{@extra_lib_path}")
567
+ Dir.children(@extra_lib_path).each_with_index do |dict,i|
568
+ puts "#{blue(i+1)}. #{bold(green(dict))}"
569
+ end
782
570
  end
783
571
  end
572
+
573
+ puts ; puts blue("=> Official dicts: (Add it by 'cr -a xxx')")
574
+ puts RECOMMENDED_DICTS
784
575
  end
785
- $TwoLibWordCount = $DefaultLibWordCount + $ExtraLibWordCount
786
576
 
787
- if p
788
- puts
789
- puts "#{$DefaultLibWordCount.to_s.rjust(4)} words in Default library"
790
- puts "#{$ExtraLibWordCount.to_s.rjust(4) } words in Extra library"
791
- puts "#{$TwoLibWordCount.to_s.rjust(4) } words altogether"
577
+
578
+ def word_count
579
+ @counter.count!(display: true)
792
580
  end
793
- end
794
581
 
582
+ end
795
583
 
796
584
  ####################
797
585
  # main: CLI Handling
798
586
  ####################
799
- arg = ARGV.shift
800
-
801
- case arg
802
- when nil then help
803
- when '-v' then print_version
804
- when '-h' then help
805
- when '-l' then list_dicts
806
- when '-c' then word_count(p: true)
807
- when '-u' then update_dicts
808
- when '-s' then search_word ARGV.shift
809
- when '-a' then add_dict ARGV.shift
810
- when '-d' then del_dict ARGV.shift
811
- when '--help'
812
- help
813
- else
814
- resolve_word arg
815
- end
587
+ cli = CrypticResolver::CliHandler.new ARGV