-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathgdcl.rb
executable file
·318 lines (280 loc) · 10.8 KB
/
gdcl.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
#!/usr/bin/ruby -KuU
# encoding: utf-8
# invoke with:
# ruby gdcl.rb
# (for interactive search)
# OR
# ruby gdcl.rb [group] [keyword]
# (for non-interactive search)
########################
require 'yaml'
require 'fileutils'
require 'zlib'
require 'optparse'
require 'nokogiri'
require 'open-uri'
config_dir = Dir.home + "/.config/gdcl/"
xdg = "/etc/xdg/gdcl/"
script_dir = File.expand_path(File.dirname(__FILE__)) + "/"
# read config file from default directory or cwd, otherwise quit
if File.exist?(config_dir + "config.yml")
config = YAML::load(File.read(config_dir + "config.yml"))
elsif File.exist?(xdg + "config.yml")
config = YAML::load(File.read(xdg + "config.yml"))
FileUtils.mkdir_p config_dir
FileUtils.cp xdg + "config.yml", config_dir
elsif File.exist?(script_dir + "config.yml")
config = YAML::load(File.read(script_dir + "config.yml"))
FileUtils.mkdir_p config_dir
FileUtils.cp script_dir + "config.yml", config_dir
else
abort(" No configuration file found. Please make sure config.yml is located
either in the config folder under your home directory (i.e.,
~/.config/gdcl/config.yml), or in the same directory as the gdcl.rb
executable.")
end
# command-line options
options = {}
OptionParser.new do |opts|
opts.banner = " gdcl - Command-Line Dictionary Lookup Tool\n\n Usage: gdcl.rb [options] [dictionary group] [search term]"
opts.on("-c", "--names [GROUP]", "List all dictionaries in specified group by canonical name") { |v| v ? options[:dict_name] = v : options[:dict_name] = true }
opts.on("-C", "--case-off", "Enable case insensitive search") { options[:case_off] = true }
opts.on("-d", "--dict-directory DIRECTORY", "Directory in which to look for dictionaries") { |v| options[:dict_dir] = v }
opts.on("-f", "--forvo LANGUAGE", "Play back audio pronunciations from forvo.com") { |v| options[:forvo] = v }
opts.on("-g", "--groups", "Print a list of all available dictionary groups") { options[:groups] = true }
opts.on("-h", "--help", "Print this help message") { puts opts; exit }
opts.on("-H", "--history", "Record search term history in a log file") { options[:history] = true }
opts.on("-i", "--ignore FILENAMES", "List of dictionaries to ignore while searching") { |v| options[:ignore] = v }
opts.on("-l", "--list GROUP", "List all dictionaries in specified group") { |v| options[:list] = v }
opts.on("-L", "--logfile DIRECTORY", "Directory in which to store search log") { |v| options[:logfile] = v }
opts.on("-m", "--markup", "Don't strip DSL markup from output") { options[:markup] = true }
opts.on("-n", "--no-headers", "Remove headers and footers from results output") { options[:noheaders] = true }
opts.on("-p", "--pager-off", "Don't prompt to open results in pager") { options[:pager_off] = true }
opts.on("-r", "--restrict FILENAME", "Restrict search to the specified dictionary only") { |v| options[:restrict] = v }
end.parse!
# apply command-line options if any or else use defaults from config
group = config[:group]
default_group = config[:default_group]
kword = config[:kword]
interactive_search = config[:interactive_search]
if options[:noheaders] == true
header_footer = false
else
header_footer = config[:header_footer]
end
if options[:pager_off] == true
pager_off = true
else
pager_off = config[:pager_off]
end
if options[:dict_dir]
dict_dir = options[:dict_dir].gsub(/^~/, Dir.home)
else
dict_dir = config[:dict_dir].gsub(/^~/, Dir.home)
end
search_term = config[:search_term]
if options[:ignore]
del_dict = options[:ignore].split(",")
else
del_dict = config[:del_dict].split(",")
end
if options[:markup] == true
markup = ""
else
markup = /#{config[:markup]}/
end
markup_replace = config[:markup_replace]
if options[:history] || config[:history]
if options[:logfile]
log_location = File.join(options[:logfile], "history.txt")
elsif config[:logfile]
log_location = File.join(config[:logfile].gsub(/^~/, Dir.home), "history.txt")
else
log_location = File.join(config_dir, "history.txt")
end
end
# list available dictionaries in group
if options[:list]
dir = Dir[dict_dir + "#{options[:list]}/**/*.dsl.dz"]
dir.sort.each do |dict|
dict_file = dict.gsub(/.*\/(.*)\.dsl\.dz$/, "\\1")
if options[:dict_name]
dict_name = Zlib::GzipReader.open(dict, :external_encoding => "UTF-16LE").read.each_line.first.strip.sub("\xEF\xBB\xBF", "").gsub(/#NAME\s+"(.*)"/,"\\1")
puts dict_file + "\t" + dict_name
else
puts dict_file
end
end
exit
end
# list dictionaries by name
if options[:dict_name]
dir = Dir[dict_dir + "#{options[:dict_name]}/**/*.dsl.dz"]
dict_name = []
dir.sort.each do |dict|
dict_name.push(Zlib::GzipReader.open(dict, :external_encoding => "UTF-16LE").read.each_line.first.strip.sub("\xEF\xBB\xBF", "").gsub(/#NAME\s+"(.*)"/,"\\1"))
end
puts dict_name
exit
end
# list available groups
avail_group = Dir.glob(dict_dir + "*").select {|f| File.directory? f}
print_avail_group = ""
if options[:groups] == true
puts "**Available dictionary groups**"
avail_group.sort.each { |g| puts g.gsub(/.*\//, "")}
exit
end
# make a nice bracketed list of available groups
avail_group.sort.each do |dir|
strip_path = dir.gsub(/.*\//, "")
print_avail_group << "[#{strip_path}], "
end
# get forvo pronunciations
if options[:forvo]
key = config[:forvo_key]
if key == nil then abort(" Forvo key not set in user config.") end
lang = options[:forvo]
ARGV[0] ? lookup = ARGV[0] : abort("missing search term")
doc = Nokogiri::XML(open(URI.encode("http://apifree.forvo.com/key/#{key}/format/xml/action/word-pronunciations/word/#{lookup}/language/#{lang}")))
hits = doc.xpath("//items").first.attribute("total").content.to_i
path = doc.xpath("//pathogg")
path.each do |link|
puts "Playing result #{path.index(link) + 1} of #{hits.to_s} in #{lang} from forvo.com..."
`mplayer -really-quiet #{link.content}`
end
exit
end
# either run search automatically with provided args, or use interactive mode
if ARGV[0] then group = ARGV[0] end
if ARGV[1] then kword = ARGV[1] end
if ARGV[0] && ARGV[1] then interactive_search = false end
# option to select a group interactively instead of specifying one in config section above
if group == ""
puts " Please select a dictionary group to search from the following available groups:"
puts " " + print_avail_group.gsub(/, $/,"")
group = $stdin.gets.chomp
end
if group == ""
if default_group != ""
group = default_group
else
abort("No group specified")
end
end
# quit if user supplied a group that doesn't exist
if !avail_group.include?(dict_dir + group) then abort("Specified group does not exist") end
# working directory location
if options[:restrict]
rlist = options[:restrict].split(",")
dir = []
full_list = Dir[dict_dir + "#{group}/**/*.dsl.dz"]
full_list.each do |f|
rlist.each { |r| if f.gsub(/.*\/(.*)\.dsl\.dz$/, "\\1").match(r) then dir.push(f) end }
end
# full_list.each { |f| if f.gsub(/.*\/(.*)\.dsl\.dz$/, "\\1").match(rlist[0]) then dir.push(f) end }
# rlist.each { |f| dir.push( dict_dir + group + "/" + f + ".dsl.dz") }
else
dir = Dir[dict_dir + "#{group}/**/*.dsl.dz"]
end
# don't search any of the dictionaries specified for removal in config
del_dict.each {|x| dir.delete(dict_dir + group + "/" + x + ".dsl.dz")}
# get search term
if kword == ""
puts "Enter a search term (currently searching in group [#{group}]):"
kword = $stdin.gets.chomp
end
# prevent error if user enters empty search query
if kword == "" then abort("Invalid search term") end
# log terms
def log_term(kw, gr, loc)
logline = "#{Time.now}\t" + kw + "\t(" + gr + ")\n"
logfile = File.open(loc, "a") { |l| l << logline }
end
# keep running the search loop until this is true
quitapp = false
# main dictionary search loop
while quitapp != true
hit = 0
results = ""
total = 0
# if logging is enabled then log search info to file
if log_location then log_term(kword, group, log_location) end
# case-sensitive search off by default
if options[:case_off]
search_term = /^#{kword}/i
else
search_term = /^#{kword}/
end
dir.sort.each do |dict|
dict_name = Zlib::GzipReader.open(dict, :external_encoding => "UTF-16LE").read.each_line.first.strip.sub("\xEF\xBB\xBF", "").gsub(/#NAME\s+"(.*)"/,"\\1")
dict_header = "== " + dict_name + " ==\n"
if header_footer == false then dict_header = "" end
results << dict_header
print dict_header
counter = 0
Zlib::GzipReader.open(dict, :external_encoding => "UTF-16LE") do |file|
headword = ""
file.read.each_line do |line|
if line.match(/^\t/)
line = line.gsub(/\\\[(.*?)\\\]/,"%%%\\1%%%")
line_strip = line.gsub(markup,markup_replace)
line_strip = line_strip.gsub(/%%%(.*?)%%%/,"\[\\1\]")
if hit > 0
results << line_strip.gsub("~", headword)
puts line_strip.gsub("~", headword)
hit +=1
end
elsif line.match(search_term)
results << line
puts line
headword = line.chomp
hit = 1
counter +=1
total +=1
# print "\rSearching for #{kword}... A total of #{total} results found so far in [#{group}]"
elsif line.encode(universal_newline: true).match(/^$/)
hit = 0
end
end
end
if counter == 1
dict_footer = "\n#{counter} result found in [#{dict_name}]\n\n\n"
if header_footer == false then dict_footer = "" end
results << dict_footer
print dict_footer
else
dict_footer = "\n#{counter} results found in [#{dict_name}]\n\n\n"
if header_footer == false then dict_footer = "" end
results << dict_footer
print dict_footer
end
end
results_footer = "A total of #{total} result(s) found in [#{group}] for the term \"#{kword}\".\n"
if header_footer == false then results_footer = "" end
print results_footer
if interactive_search == true && total != 0 && pager_off != true
puts "Display results in pager? (y/n)"
$stdin.gets.chomp == "y" ? IO.popen("less", "w") { |f| f.puts results } : (puts "Search complete.")
# IO.popen("less", "w") { |f| f.puts results }
end
if interactive_search == true
puts "\nSearch again in [#{group}] or enter 'q' to quit, or 'g' to change group:"
kword = $stdin.gets.chomp
# quit if user enters "q"
kword == "q" || kword == "" ? quitapp = true : quitapp = false
# switch group if user enters "g"
if kword == "g"
puts "Please select a new group to search in (current group is [#{group}])"
puts " " + print_avail_group.gsub(/, $/,"")
group = $stdin.gets.chomp
dir = Dir[dict_dir + group + "/**/*.dsl.dz"] # change working directory location
del_dict.each {|x| dir.delete(dict_dir + group + "/" + x + ".dsl.dz")} # remove specified dictionaries
puts "Now searching in group [#{group}]"
puts "Please enter search term:"
kword = $stdin.gets.chomp
end
else
quitapp = true
end
end