cr.rb 4.1.4 → 4.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. checksums.yaml +4 -4
  2. data/exe/cr +67 -0
  3. data/lib/cr/counter.rb +106 -0
  4. data/lib/cr/version.rb +3 -3
  5. data/lib/cr.rb +530 -117
  6. metadata +20 -5
  7. data/bin/cr +0 -597
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cr.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.4
4
+ version: 4.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aoran Zeng
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2023-03-22 00:00:00.000000000 Z
11
+ date: 2023-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tomlrb
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0.1'
41
+ - !ruby/object:Gem::Dependency
42
+ name: colorator
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.1'
41
55
  description: |
42
56
  This command line tool `cr` is used to record and explain cryptic commands, acronyms(initialism), abbreviations and so forth in daily life.
43
57
  Not only can it be used in computer filed via our default sheet cryptic_computer, but also you can use this to manage your own knowledge base easily.
@@ -47,8 +61,9 @@ executables:
47
61
  extensions: []
48
62
  extra_rdoc_files: []
49
63
  files:
50
- - bin/cr
64
+ - exe/cr
51
65
  - lib/cr.rb
66
+ - lib/cr/counter.rb
52
67
  - lib/cr/version.rb
53
68
  homepage: https://github.com/cryptic-resolver/cr.rb
54
69
  licenses:
@@ -64,7 +79,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
64
79
  requirements:
65
80
  - - ">="
66
81
  - !ruby/object:Gem::Version
67
- version: '0'
82
+ version: 3.0.0
68
83
  required_rubygems_version: !ruby/object:Gem::Requirement
69
84
  requirements:
70
85
  - - ">="
data/bin/cr DELETED
@@ -1,597 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # ------------------------------------------------------
3
- # File : cr.rb
4
- # Authors : ccmywish <ccmywish@qq.com>
5
- # Created on : <2021-07-08>
6
- # Last modified : <2023-03-22>
7
- #
8
- # cr:
9
- #
10
- # This file is used to explain a CRyptic command
11
- # or an acronym's real meaning in computer world or
12
- # other fields.
13
- # ------------------------------------------------------
14
-
15
- require 'cr'
16
-
17
- class CrypticResolver::CliHandler
18
-
19
- def initialize(args)
20
- option = args.shift
21
-
22
- case option when nil, '-h','--help' then help or exit end
23
-
24
- reslvr = CrypticResolver::Resolver.new
25
-
26
- reslvr.add_default_dicts_if_none_exists
27
-
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
36
- end
37
-
38
-
39
- def help
40
- puts <<~HELP
41
- cr: Cryptic Resolver v#{CrypticResolver::GEM_VERSION}
42
-
43
- Usage:
44
- cr <keyword> Search the keyword
45
- cr emacs => Edit macros: A feature-rich editor
46
-
47
- Options:
48
- -c Print word count
49
- -s <pattern> Search words matched with pattern
50
- -h Print this help
51
-
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
59
-
60
- HELP
61
- end
62
-
63
- end
64
-
65
-
66
- class CrypticResolver::Resolver
67
-
68
- # Notice that we only update the Default library, not Extra library
69
- def update_dicts
70
-
71
- @counter.count_def_lib(display: false)
72
- old_wc = @counter.word_count_of_def_lib
73
-
74
- puts "cr: Updating all dicts in Default library..."
75
-
76
- begin
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`
84
- end
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
96
- end
97
-
98
- rescue Interrupt
99
- abort "cr: Cancel update"
100
- end
101
-
102
- puts "cr: Update done"
103
-
104
- # clear
105
- @counter.word_count_of_def_lib = 0
106
- # recount
107
- @counter.count_def_lib(display: false)
108
-
109
- new_wc = @counter.word_count_of_def_lib
110
- puts ; puts "#{new_wc - old_wc} words added in Default library"
111
- end
112
-
113
-
114
- def add_dict(repo)
115
- if repo.nil?
116
- abort bold(red("cr: Need an argument!"))
117
- end
118
-
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
126
- end
127
-
128
- begin
129
- puts "cr: Adding new dictionary..."
130
- `git -C #{DEFAULT_LIB_PATH} clone #{repo} -q`
131
- rescue Interrupt
132
- abort "cr: Cancel add dict"
133
- end
134
-
135
- puts "cr: Add new dictionary done"
136
-
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
142
-
143
-
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
150
- # Dir.rmdir repo # Can't rm a filled dir
151
- # FileUtils.rmdir repo # Can't rm a filled dir
152
- FileUtils.rm_rf repo
153
- puts "cr: Delete dictionary #{bold(green(repo))} done"
154
- rescue Exception => e
155
- puts bold(red("cr: #{e}"))
156
- list_dicts
157
- end
158
- end
159
- end
160
-
161
-
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
168
- end
169
-
170
-
171
- =begin
172
- Pretty print the info of the given word
173
-
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
208
- end
209
- else
210
- print underline(see_also)
211
- end
212
- puts
213
- end
214
- puts
215
- end
216
-
217
-
218
- # Print default cryptic_ dicts
219
- def pp_dict(dict)
220
- puts green("From: #{dict}")
221
- end
222
-
223
-
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
228
-
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
232
-
233
- [blah]
234
- same = "xdg" # this is wrong, because xdg has multiple
235
- # definitions, and all of them specify a
236
- # category
237
-
238
- [blah]
239
- same = "XDG downloader =>xdg.Download" # this is correct
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)
246
-
247
- # If it's a synonym for anther word,
248
- # we should lookup into this dict again, but maybe with a different file
249
-
250
- # file name
251
- x = word.chr.downcase
252
-
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
257
-
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.
261
-
262
- 'jump to' will output to user, so this is important not only inside our sheet.
263
-
264
- same = "XDM downloader=>xdm.Download"
265
-
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?
271
-
272
- unless own_name
273
- own_name = word
274
- end
275
- puts blue(bold(own_name)) + ' redirects to ' + blue(bold(jump_to))
276
-
277
- =begin
278
- As '.' is used to delimit a word and a category, what if
279
- we jump to a dotted word?
280
-
281
- [eg]
282
- same = "e.g." # this must lead to a wrong resolution to
283
- # word 'e', category 'g'
284
-
285
- All you need is to be like this:
286
-
287
- [eg]
288
- same = "'e.g.'" # cr will notice the single quote
289
- =end
290
- if same =~ /^'(.*)'$/
291
- same, category = $1, nil
292
- else
293
- same, category = same.split('.')
294
- end
295
-
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)
301
- end
302
-
303
- if category.nil?
304
- info = sheet_content[same]
305
- else
306
- info = sheet_content[same][category]
307
- end
308
-
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
321
- end
322
- end
323
-
324
-
325
-
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?
342
-
343
- info = sheet_content[word]
344
- return false if info.nil?
345
-
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
352
-
353
- # Word with no category specifier
354
- # We call this meaning as type 1
355
- type_1_exist_flag = false
356
-
357
- # if already jump, don't check the word itself
358
- is_jump = false
359
-
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
369
-
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
384
-
385
- # Meanings with category specifier
386
- # We call this meaning as type 2
387
- categories = info.keys - ["name", "desc", "more", "same", "see"]
388
-
389
- if !categories.empty?
390
-
391
- if type_1_exist_flag
392
- print blue(bold("OR")),"\n"
393
- else
394
- pp_dict(dict)
395
- end
396
-
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
405
-
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
412
- end
413
-
414
-
415
- #
416
- # The main procedure of `cr`
417
- #
418
- # 1. Search the default library first
419
- # 2. Search the extra library if it does exist
420
- #
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'
434
- end
435
-
436
- # cache lookup's results
437
- results = []
438
-
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)
443
- end
444
-
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)
450
- end
451
- end
452
-
453
- unless results.include? true
454
- puts <<~NotFound
455
- cr: Not found anything.
456
-
457
- You could
458
-
459
- #{blue("case 1: Update all dicts")}
460
-
461
- #{yellow("$cr -u")}
462
-
463
- #{blue("case 2: List available official and feature dicts")}
464
-
465
- #{yellow("$cr -l")}
466
-
467
- #{yellow("$cr -a repo")} (Add a specific dict to default lib)
468
-
469
- #{blue("case 3: Contribute to theses dicts")}
470
-
471
- Visit: https://github.com/cryptic-resolver
472
-
473
- NotFound
474
- else return end
475
- end
476
-
477
-
478
- #
479
- # Delegate to `search_word_internal`
480
- #
481
- def search_word(pattern)
482
- found_or_not1 = false
483
- found_or_not2 = false
484
-
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)
488
- end
489
-
490
- if (found_or_not1 == false) && (found_or_not2 == false)
491
- puts red("cr: No words match with #{pattern.inspect}") ; puts
492
- end
493
- end
494
-
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)
506
-
507
- if pattern.nil?
508
- abort bold(red("cr: Need an argument!"))
509
- end
510
-
511
- if pattern =~ /^\/(.*)\/$/
512
- regexp = %r[#$1]
513
- else
514
- regexp = %r[#{pattern}]
515
- end
516
-
517
- found_or_not = false
518
-
519
- # Try to match every word in all dicts
520
- Dir.children(library).each do |dict|
521
-
522
- path = File.join(library, dict)
523
- next if File.file? path
524
- sheets = Dir.children(path).select do
525
- _1.end_with?('.toml')
526
- end
527
-
528
- similar_words_in_a_dict = []
529
-
530
- sheets.each do |sheet|
531
- sheet_content = load_sheet(library, dict, File.basename(sheet,'.toml'))
532
-
533
- sheet_content.keys.each do
534
- if _1 =~ regexp
535
- found_or_not = true
536
- similar_words_in_a_dict << _1
537
- end
538
- end
539
- # end of each sheet in a dict
540
- end
541
-
542
- unless similar_words_in_a_dict.empty?
543
- pp_dict(dict)
544
- require 'ls_table'
545
- LsTable.ls(similar_words_in_a_dict) do |e|
546
- puts blue(e)
547
- end
548
- puts
549
- end
550
- end
551
- return found_or_not
552
- end
553
-
554
-
555
- # 1. List Default library's dicts
556
- # 2. List Extra library's dicts
557
- # 3. List official dicts
558
- def list_dicts
559
-
560
- def _do_the_same_thing
561
- count = 0
562
- Dir.children(".").each do |dict|
563
- next if File.file? dict
564
- count += 1
565
- prefix = (count.to_s + '.').ljust 4
566
- puts " #{prefix}#{dict}"
567
- end
568
- end
569
-
570
- Dir.chdir DEFAULT_LIB_PATH do
571
- puts bold(green("Default library: #{DEFAULT_LIB_PATH}"))
572
- _do_the_same_thing
573
- end
574
-
575
- if @extra_lib_path
576
- puts
577
- Dir.chdir @extra_lib_path do
578
- puts bold(green("Extra library: #{@extra_lib_path}"))
579
- _do_the_same_thing
580
- end
581
- end
582
-
583
- puts ; puts bold(green("Official dictionaries: (Add it by 'cr -a xxx')"))
584
- puts RECOMMENDED_DICTS
585
- end
586
-
587
-
588
- def word_count
589
- @counter.count!(display: true)
590
- end
591
-
592
- end
593
-
594
- ####################
595
- # main: CLI Handling
596
- ####################
597
- cli = CrypticResolver::CliHandler.new ARGV