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