lingo 1.8.2 → 1.8.3
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.
- data/ChangeLog +33 -0
- data/README +6 -5
- data/Rakefile +6 -4
- data/{lib/lingo/cachable.rb → bin/lingosrv} +30 -58
- data/bin/lingoweb +30 -0
- data/de.lang +2 -13
- data/en/lingo-irr.txt +266 -0
- data/en/lingo-wdn.txt +37319 -0
- data/en.lang +2 -15
- data/lib/lingo/app.rb +82 -0
- data/lib/lingo/attendee/abbreviator.rb +22 -26
- data/lib/lingo/attendee/debugger.rb +8 -4
- data/lib/lingo/attendee/decomposer.rb +0 -1
- data/lib/lingo/attendee/dehyphenizer.rb +2 -2
- data/lib/lingo/attendee/multi_worder.rb +20 -13
- data/lib/lingo/attendee/noneword_filter.rb +2 -7
- data/lib/lingo/attendee/sequencer.rb +43 -19
- data/lib/lingo/attendee/stemmer/porter.rb +2 -2
- data/lib/lingo/attendee/stemmer.rb +1 -1
- data/lib/lingo/attendee/synonymer.rb +1 -9
- data/lib/lingo/attendee/text_reader.rb +42 -29
- data/lib/lingo/attendee/text_writer.rb +3 -6
- data/lib/lingo/attendee/tokenizer.rb +87 -69
- data/lib/lingo/attendee/variator.rb +7 -5
- data/lib/lingo/attendee/vector_filter.rb +11 -11
- data/lib/lingo/attendee/word_searcher.rb +1 -9
- data/lib/lingo/attendee.rb +24 -105
- data/lib/lingo/buffered_attendee.rb +2 -9
- data/lib/lingo/call.rb +18 -13
- data/lib/lingo/cli.rb +5 -10
- data/lib/lingo/config.rb +40 -7
- data/lib/lingo/ctl.rb +69 -57
- data/lib/lingo/database/hash_store.rb +9 -4
- data/lib/lingo/database/sdbm_store.rb +4 -7
- data/lib/lingo/database/source/multi_key.rb +1 -1
- data/lib/lingo/database/source/multi_value.rb +1 -1
- data/lib/lingo/database/source.rb +2 -20
- data/lib/lingo/database.rb +30 -19
- data/lib/lingo/debug.rb +79 -0
- data/lib/lingo/{core_ext.rb → language/char.rb} +43 -42
- data/lib/lingo/language/dictionary.rb +38 -46
- data/lib/lingo/language/grammar.rb +40 -57
- data/lib/lingo/language/lexical.rb +4 -7
- data/lib/lingo/language/lexical_hash.rb +17 -35
- data/lib/lingo/language/token.rb +4 -0
- data/lib/lingo/language/word.rb +7 -8
- data/lib/lingo/language/word_form.rb +4 -4
- data/lib/lingo/language.rb +2 -1
- data/lib/lingo/srv/config.ru +4 -0
- data/lib/lingo/srv/lingosrv.cfg +14 -0
- data/lib/lingo/{reportable.rb → srv.rb} +59 -61
- data/lib/lingo/version.rb +1 -1
- data/lib/lingo/web/config.ru +4 -0
- data/lib/lingo/web/lingoweb.cfg +14 -0
- data/lib/lingo/web/public/lingo.png +0 -0
- data/lib/lingo/web/public/lingoweb.css +74 -0
- data/lib/lingo/web/views/index.erb +92 -0
- data/lib/lingo/web.rb +94 -0
- data/lib/lingo.rb +27 -29
- data/lingo.cfg +1 -1
- data/lir.cfg +24 -0
- data/ru/lingo-dic.txt +22342 -0
- data/ru/lingo-mul.txt +5151 -0
- data/ru/lingo-syn.txt +0 -0
- data/ru.lang +99 -0
- data/test/attendee/ts_sequencer.rb +2 -2
- data/test/attendee/ts_text_reader.rb +36 -2
- data/test/attendee/ts_text_writer.rb +6 -6
- data/test/lir.vec +3 -3
- data/test/test_helper.rb +104 -102
- data/test/ts_database.rb +1 -1
- data/test/ts_language.rb +55 -96
- data/txt/artikel-ru.txt +45 -0
- data/txt/lir.txt +1 -3
- metadata +143 -83
- data/TODO +0 -23
data/lib/lingo/config.rb
CHANGED
@@ -27,11 +27,15 @@
|
|
27
27
|
require 'yaml'
|
28
28
|
require_relative 'cli'
|
29
29
|
|
30
|
+
YAML::ENGINE.yamler = 'psych'
|
31
|
+
|
30
32
|
class Lingo
|
31
33
|
|
32
34
|
class Config
|
33
35
|
|
34
36
|
def initialize(*args)
|
37
|
+
@deprecated = Hash.new { |h, k| h[k] = true; false }
|
38
|
+
|
35
39
|
@cli, @opts = CLI.new, {}
|
36
40
|
|
37
41
|
@cli.execute(*args)
|
@@ -40,9 +44,10 @@ class Lingo
|
|
40
44
|
load_config('language', :lang)
|
41
45
|
load_config('config')
|
42
46
|
|
43
|
-
Array(self['meeting/attendees']).
|
44
|
-
r = a['text_reader'] || a['textreader'] or next # DEPRECATE textreader
|
47
|
+
deprecate(:textreader, :text_reader) if Array(self['meeting/attendees']).map(&:keys).flatten.include?('textreader')
|
45
48
|
|
49
|
+
if r = get('meeting/attendees', 'text_reader') ||
|
50
|
+
get('meeting/attendees', 'textreader') # DEPRECATE textreader
|
46
51
|
f = @cli.files
|
47
52
|
|
48
53
|
if i = r['files']
|
@@ -50,9 +55,7 @@ class Lingo
|
|
50
55
|
elsif !f.empty?
|
51
56
|
r['files'] = f
|
52
57
|
end
|
53
|
-
|
54
|
-
break
|
55
|
-
}
|
58
|
+
end
|
56
59
|
end
|
57
60
|
|
58
61
|
def [](key)
|
@@ -64,6 +67,23 @@ class Lingo
|
|
64
67
|
(self[nodes_to_key(nodes)] ||= {})[node] = val
|
65
68
|
end
|
66
69
|
|
70
|
+
def get(key, *names)
|
71
|
+
val = self[key]
|
72
|
+
|
73
|
+
while name = names.shift
|
74
|
+
case val
|
75
|
+
when Hash then val = val[name]
|
76
|
+
when Array then val = val.find { |h|
|
77
|
+
k, v = h.dup.shift
|
78
|
+
break v if k == name
|
79
|
+
}
|
80
|
+
else break
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
val
|
85
|
+
end
|
86
|
+
|
67
87
|
def stdin
|
68
88
|
@cli.stdin
|
69
89
|
end
|
@@ -76,10 +96,23 @@ class Lingo
|
|
76
96
|
@cli.stderr
|
77
97
|
end
|
78
98
|
|
99
|
+
def warn(*msg)
|
100
|
+
stderr.puts(*msg)
|
101
|
+
end
|
102
|
+
|
79
103
|
def quit(*args)
|
80
104
|
@cli.send(:quit, *args)
|
81
105
|
end
|
82
106
|
|
107
|
+
def deprecate(old, new, obj = self)
|
108
|
+
unless @deprecated[[source = obj.class.name.sub(/\ALingo::/, ''), old]]
|
109
|
+
warn(
|
110
|
+
"DEPRECATION WARNING: #{source} option `#{old}' is deprecated " <<
|
111
|
+
"and will be removed in Lingo 1.9. Please use `#{new}' instead."
|
112
|
+
)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
83
116
|
private
|
84
117
|
|
85
118
|
def key_to_nodes(key)
|
@@ -91,8 +124,8 @@ class Lingo
|
|
91
124
|
end
|
92
125
|
|
93
126
|
def load_config(key, type = key.to_sym)
|
94
|
-
file = Lingo.find(type, @opts[key]
|
95
|
-
@opts.update(File.open(file, encoding: ENC
|
127
|
+
file = Lingo.find(type, @opts[key]) { quit }
|
128
|
+
@opts.update(File.open(file, encoding: ENC) { |f| YAML.load(f) })
|
96
129
|
end
|
97
130
|
|
98
131
|
end
|
data/lib/lingo/ctl.rb
CHANGED
@@ -39,59 +39,52 @@ class Lingo
|
|
39
39
|
h[k] = COMMANDS.has_key?(k) ? k : 'usage'
|
40
40
|
}
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
{ config: %w[configuration],
|
45
|
-
lang: %w[language],
|
46
|
-
dict: %w[dictionary dictionaries],
|
47
|
-
store: %w[store],
|
48
|
-
sample: %w[sample\ text\ file] }.each { |what, (sing, plur)|
|
49
|
-
COMMANDS["list#{what}"] = [
|
50
|
-
"List available #{plur || "#{sing}s"}", 'Arguments: [name...]'
|
51
|
-
] if what != :store
|
52
|
-
COMMANDS["find#{what}"] = [
|
53
|
-
"Find #{sing} in Lingo search path", 'Arguments: name'
|
54
|
-
]
|
55
|
-
COMMANDS["copy#{what}"] = [
|
56
|
-
"Copy #{sing} to local Lingo directory", 'Arguments: name'
|
57
|
-
] if what != :store
|
58
|
-
|
59
|
-
%w[list find copy].each { |method|
|
60
|
-
next unless COMMANDS.has_key?(name = "#{method}#{what}")
|
61
|
-
class_eval %Q{def do_#{name}; #{method}(:#{what}); end}
|
62
|
-
|
63
|
-
[0, -1].repeated_permutation(2) { |i, j|
|
64
|
-
key = "#{method[i]}#{what[j]}"
|
65
|
-
break ALIASES[key] = name unless ALIASES.has_key?(key)
|
66
|
-
}
|
67
|
-
}
|
68
|
-
|
69
|
-
if what == :store
|
70
|
-
COMMANDS['clearstore'] = [
|
71
|
-
'Remove store files to force rebuild', 'Arguments: name'
|
72
|
-
]
|
73
|
-
ALIASES['cs'] = 'clearstore'
|
74
|
-
end
|
75
|
-
}
|
76
|
-
|
77
|
-
{ demo: ['Initialize demo directory (Default: current directory)',
|
78
|
-
'Arguments: [path]'],
|
79
|
-
path: 'Print search path for dictionaries and configurations',
|
80
|
-
help: 'Print help for available commands',
|
81
|
-
version: 'Print Lingo version number' }.each { |what, description|
|
82
|
-
COMMANDS[name = what.to_s] = description; ALIASES[name[0]] = name
|
83
|
-
}
|
84
|
-
|
85
|
-
USAGE = <<EOT
|
42
|
+
USAGE = <<-EOT
|
86
43
|
Usage: #{PROG} <command> [arguments] [options]
|
87
44
|
#{PROG} [-h|--help] [--version]
|
88
|
-
EOT
|
45
|
+
EOT
|
89
46
|
|
90
47
|
def ctl
|
91
48
|
parse_options
|
92
49
|
send("do_#{ALIASES[ARGV.shift]}")
|
93
50
|
end
|
94
51
|
|
52
|
+
def self.cmd(name, short, desc, args = nil, default = nil)
|
53
|
+
if name.is_a?(Array)
|
54
|
+
m, f, k = name
|
55
|
+
name, short = "#{m}#{k}", "#{f}#{short}"
|
56
|
+
class_eval %Q{private; def do_#{name}; #{m}(:#{k}); end}
|
57
|
+
end
|
58
|
+
|
59
|
+
if args
|
60
|
+
desc = [desc, args = "Arguments: #{args}"]
|
61
|
+
args << " (Default: #{default})" if default
|
62
|
+
end
|
63
|
+
|
64
|
+
COMMANDS[name], ALIASES[short] = desc, name
|
65
|
+
end
|
66
|
+
|
67
|
+
{ config: %w[c configuration],
|
68
|
+
lang: %w[l language],
|
69
|
+
dict: %w[d dictionary dictionaries],
|
70
|
+
store: %w[s store],
|
71
|
+
sample: %w[e sample\ text\ file]
|
72
|
+
}.each { |n, (s, q, r)|
|
73
|
+
t = n == :store
|
74
|
+
|
75
|
+
cmd([:list, :l, n], s, "List available #{r || "#{q}s"}", '[name...]') if !t
|
76
|
+
cmd([:find, :f, n], s, "Find #{q} in Lingo search path", 'name')
|
77
|
+
cmd([:copy, :c, n], s, "Copy #{q} to local Lingo directory", 'name') if !t
|
78
|
+
cmd([:clear, :c, n], s, 'Remove store files to force rebuild', 'name') if t
|
79
|
+
}
|
80
|
+
|
81
|
+
{ demo: [:d, 'Initialize demo directory', '[path]', 'current directory'],
|
82
|
+
rackup: [:r, 'Print path to rackup file', 'name'],
|
83
|
+
path: [:p, 'Print search path for dictionaries and configurations'],
|
84
|
+
help: [:h, 'Print help for available commands'],
|
85
|
+
version: [:v, 'Print Lingo version number']
|
86
|
+
}.each { |n, (s, *a)| cmd(n.to_s, s.to_s, *a) }
|
87
|
+
|
95
88
|
private
|
96
89
|
|
97
90
|
def list(what, doit = true)
|
@@ -103,20 +96,20 @@ EOT
|
|
103
96
|
end
|
104
97
|
|
105
98
|
def find(what, doit = true)
|
106
|
-
name = ARGV.shift or
|
99
|
+
name = ARGV.shift or missing_arg(:name)
|
107
100
|
no_args
|
108
101
|
|
109
|
-
file = Lingo.find(what, name, path: path_for_scope
|
102
|
+
file = Lingo.find(what, name, path: path_for_scope) { usage }
|
110
103
|
doit ? puts(file) : file
|
111
104
|
end
|
112
105
|
|
113
106
|
def copy(what)
|
114
|
-
|
107
|
+
usage('Source and target are the same.') if OPTIONS[:scope] == :local
|
115
108
|
|
116
109
|
source = find(what, false)
|
117
110
|
target = File.join(path_for_scope(:local), Lingo.basepath(what, source))
|
118
111
|
|
119
|
-
|
112
|
+
usage('Source and target are the same.') if source == target
|
120
113
|
|
121
114
|
FileUtils.mkdir_p(File.dirname(target))
|
122
115
|
FileUtils.cp(source, target, verbose: true)
|
@@ -137,6 +130,19 @@ EOT
|
|
137
130
|
copy_list(:sample)
|
138
131
|
end
|
139
132
|
|
133
|
+
def do_rackup(doit = true)
|
134
|
+
name = ARGV.shift or missing_arg(:name)
|
135
|
+
no_args
|
136
|
+
|
137
|
+
require 'lingo/app'
|
138
|
+
|
139
|
+
if file = Lingo::App.rackup(name)
|
140
|
+
doit ? puts(file) : file
|
141
|
+
else
|
142
|
+
usage("Invalid app name `#{name.inspect}'.")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
140
146
|
def do_path
|
141
147
|
no_args
|
142
148
|
puts path_for_scope || PATH
|
@@ -172,10 +178,6 @@ EOT
|
|
172
178
|
doit ? puts(msg) : msg
|
173
179
|
end
|
174
180
|
|
175
|
-
def do_usage(msg = nil)
|
176
|
-
abort "#{"#{PROGNAME}: #{msg}\n\n" if msg}#{USAGE}"
|
177
|
-
end
|
178
|
-
|
179
181
|
def parse_options
|
180
182
|
OptionParser.new(USAGE, OPTWIDTH) { |opts|
|
181
183
|
opts.separator ''
|
@@ -185,7 +187,7 @@ EOT
|
|
185
187
|
OPTIONS[:scope] = :system
|
186
188
|
}
|
187
189
|
|
188
|
-
opts.on('--global',
|
190
|
+
opts.on('--global', "Restrict command to the user's personal Lingo directory") {
|
189
191
|
OPTIONS[:scope] = :global
|
190
192
|
}
|
191
193
|
|
@@ -212,17 +214,27 @@ EOT
|
|
212
214
|
when :global then [HOME]
|
213
215
|
when :local then [OPTIONS[:path] || CURR]
|
214
216
|
when nil
|
215
|
-
else
|
217
|
+
else usage("Invalid scope `#{scope.inspect}'.")
|
216
218
|
end
|
217
219
|
end
|
218
220
|
|
221
|
+
def usage(msg = nil)
|
222
|
+
abort "#{"#{PROGNAME}: #{msg}\n\n" if msg}#{USAGE}"
|
223
|
+
end
|
224
|
+
|
225
|
+
alias_method :do_usage, :usage
|
226
|
+
|
227
|
+
def missing_arg(arg)
|
228
|
+
usage("Required argument `#{arg}' missing.")
|
229
|
+
end
|
230
|
+
|
219
231
|
def no_args
|
220
|
-
|
232
|
+
usage('Too many arguments.') unless ARGV.empty?
|
221
233
|
end
|
222
234
|
|
223
235
|
def copy_list(what)
|
224
236
|
files = list(what, false)
|
225
|
-
files.select!
|
237
|
+
files.select! { |i| yield i } if block_given?
|
226
238
|
files.each { |file| ARGV.replace([file]); copy(what) }
|
227
239
|
end
|
228
240
|
|
@@ -30,20 +30,19 @@ class Lingo
|
|
30
30
|
|
31
31
|
module HashStore
|
32
32
|
|
33
|
-
|
34
|
-
@db.dup
|
35
|
-
end
|
36
|
-
|
33
|
+
# Never close, because we can't restore.
|
37
34
|
def close
|
38
35
|
self
|
39
36
|
end
|
40
37
|
|
41
38
|
private
|
42
39
|
|
40
|
+
# Never up-to-date.
|
43
41
|
def uptodate?
|
44
42
|
false
|
45
43
|
end
|
46
44
|
|
45
|
+
# Nothing to do.
|
47
46
|
def uptodate!
|
48
47
|
nil
|
49
48
|
end
|
@@ -56,10 +55,16 @@ class Lingo
|
|
56
55
|
{}
|
57
56
|
end
|
58
57
|
|
58
|
+
# Never closed.
|
59
59
|
def _closed?
|
60
60
|
false
|
61
61
|
end
|
62
62
|
|
63
|
+
# Dup key, because we're reusing everything.
|
64
|
+
def _each
|
65
|
+
@db.each { |key, val| yield key.dup, val }
|
66
|
+
end
|
67
|
+
|
63
68
|
end
|
64
69
|
|
65
70
|
end
|
@@ -34,6 +34,8 @@ class Lingo
|
|
34
34
|
|
35
35
|
Database.register(self, %w[dir pag], -1, false)
|
36
36
|
|
37
|
+
MAX_LENGTH = 950
|
38
|
+
|
37
39
|
private
|
38
40
|
|
39
41
|
def uptodate?
|
@@ -48,15 +50,10 @@ class Lingo
|
|
48
50
|
SDBM.open(@stofile)
|
49
51
|
end
|
50
52
|
|
51
|
-
def _get(key)
|
52
|
-
val = super
|
53
|
-
val && val.encode(ENC)
|
54
|
-
end
|
55
|
-
|
56
53
|
def _set(key, val)
|
57
|
-
if val.
|
54
|
+
if val.bytesize > MAX_LENGTH
|
58
55
|
warn "Warning: Entry `#{key}' (#{@srcfile}) too long for SDBM. Truncating..."
|
59
|
-
val = val
|
56
|
+
val = val.byteslice(0, MAX_LENGTH)
|
60
57
|
end
|
61
58
|
|
62
59
|
super
|
@@ -47,24 +47,6 @@ class Lingo
|
|
47
47
|
|
48
48
|
class Source
|
49
49
|
|
50
|
-
# Define printable characters for tokenizer for UTF-8 encoding
|
51
|
-
UTF8_DIGIT = '[0-9]'
|
52
|
-
# Define Basic Latin printable characters for UTF-8 encoding from U+0000 to U+007f
|
53
|
-
UTF8_BASLAT = '[A-Za-z]'
|
54
|
-
# Define Latin-1 Supplement printable characters for UTF-8 encoding from U+0080 to U+00ff
|
55
|
-
UTF8_LAT1SP = '[\xc3\x80-\xc3\x96\xc3\x98-\xc3\xb6\xc3\xb8-\xc3\xbf]'
|
56
|
-
# Define Latin Extended-A printable characters for UTF-8 encoding from U+0100 to U+017f
|
57
|
-
UTF8_LATEXA = '[\xc4\x80-\xc4\xbf\xc5\x80-\xc5\xbf]'
|
58
|
-
# Define Latin Extended-B printable characters for UTF-8 encoding from U+0180 to U+024f
|
59
|
-
UTF8_LATEXB = '[\xc6\x80-\xc6\xbf\xc7\x80-\xc7\xbf\xc8\x80-\xc8\xbf\xc9\x80-\xc9\x8f]'
|
60
|
-
# Define IPA Extension printable characters for UTF-8 encoding from U+024f to U+02af
|
61
|
-
UTF8_IPAEXT = '[\xc9\xa0-\xc9\xbf\xca\xa0-\xca\xaf]'
|
62
|
-
# Collect all UTF-8 printable characters in Unicode range U+0000 to U+02af
|
63
|
-
UTF8_CHAR = "#{UTF8_DIGIT}|#{UTF8_BASLAT}|#{UTF8_LAT1SP}|#{UTF8_LATEXA}|#{UTF8_LATEXB}|#{UTF8_IPAEXT}"
|
64
|
-
|
65
|
-
PRINTABLE_CHAR = "#{UTF8_CHAR}|[<>-]"
|
66
|
-
LEGAL_CHAR = '[ /&()\[\].,-]'
|
67
|
-
|
68
50
|
def self.get(name, *args)
|
69
51
|
Lingo.get_const(name, self).new(*args)
|
70
52
|
end
|
@@ -89,7 +71,7 @@ class Lingo
|
|
89
71
|
@def = @config.fetch('def-wc', Language::LA_UNKNOWN).downcase
|
90
72
|
@sep = @config['separator']
|
91
73
|
|
92
|
-
@wrd = "(?:#{
|
74
|
+
@wrd = "(?:#{Language::Char::ANY})+"
|
93
75
|
@pat = /^#{@wrd}$/
|
94
76
|
|
95
77
|
@pos = 0
|
@@ -108,7 +90,7 @@ class Lingo
|
|
108
90
|
next if line =~ /\A\s*#/ || line.strip.empty?
|
109
91
|
|
110
92
|
line.chomp!
|
111
|
-
line.downcase
|
93
|
+
line.replace(Unicode.downcase(line))
|
112
94
|
|
113
95
|
if length < 4096 && line =~ @pat
|
114
96
|
yield convert_line(line, $1, $2)
|
data/lib/lingo/database.rb
CHANGED
@@ -39,8 +39,6 @@ class Lingo
|
|
39
39
|
|
40
40
|
class Database
|
41
41
|
|
42
|
-
include Cachable
|
43
|
-
|
44
42
|
FLD_SEP = '|'
|
45
43
|
IDX_REF = '^'
|
46
44
|
KEY_REF = '*'
|
@@ -58,7 +56,7 @@ class Lingo
|
|
58
56
|
|
59
57
|
def register(klass, ext, prio = -1, meth = true)
|
60
58
|
BACKENDS.insert(prio, name = klass.name[/::(\w+)Store\z/, 1])
|
61
|
-
Array(ext).each { |i| BACKEND_BY_EXT[i.
|
59
|
+
Array(ext).each { |i| BACKEND_BY_EXT[i.insert(0, '.')] = name }
|
62
60
|
|
63
61
|
klass.const_set(:EXT, ext)
|
64
62
|
klass.class_eval('def store_ext; EXT; end', __FILE__, __LINE__) if meth
|
@@ -78,6 +76,8 @@ class Lingo
|
|
78
76
|
@srcfile = Lingo.find(:dict, @config['name'], relax: true)
|
79
77
|
@crypter = @config.has_key?('crypt') && Crypter.new
|
80
78
|
|
79
|
+
@val = Hash.new { |h, k| h[k] = [] }
|
80
|
+
|
81
81
|
begin
|
82
82
|
@stofile = Lingo.find(:store, @srcfile)
|
83
83
|
FileUtils.mkdir_p(File.dirname(@stofile))
|
@@ -89,13 +89,12 @@ class Lingo
|
|
89
89
|
end
|
90
90
|
|
91
91
|
use_backend(backend, skip_ext)
|
92
|
-
init_cachable
|
93
92
|
|
94
93
|
convert unless uptodate?
|
95
94
|
end
|
96
95
|
|
97
96
|
def closed?
|
98
|
-
|
97
|
+
!@db || _closed?
|
99
98
|
end
|
100
99
|
|
101
100
|
def open
|
@@ -108,16 +107,20 @@ class Lingo
|
|
108
107
|
end
|
109
108
|
|
110
109
|
def close
|
111
|
-
|
110
|
+
_close unless closed?
|
112
111
|
@db = nil
|
113
112
|
|
114
113
|
self
|
115
114
|
end
|
116
115
|
|
117
116
|
def to_h
|
118
|
-
|
119
|
-
|
120
|
-
|
117
|
+
hash = {}
|
118
|
+
each { |key, val| hash[key.freeze] = val }
|
119
|
+
hash
|
120
|
+
end
|
121
|
+
|
122
|
+
def each
|
123
|
+
_each { |key, val| yield _encode!(key), _encode!(val) } unless closed?
|
121
124
|
end
|
122
125
|
|
123
126
|
def [](key)
|
@@ -133,15 +136,15 @@ class Lingo
|
|
133
136
|
def []=(key, val)
|
134
137
|
return if closed?
|
135
138
|
|
136
|
-
val = val.
|
137
|
-
val.concat(retrieve(key)) if hit?(key)
|
138
|
-
|
139
|
-
val.sort!
|
139
|
+
val = @val[key].concat(val).sort!
|
140
140
|
val.uniq!
|
141
|
-
store(key, val)
|
142
141
|
|
143
|
-
|
144
|
-
|
142
|
+
val = val.join(FLD_SEP)
|
143
|
+
@crypter ? _set(*@crypter.encode(key, val)) : _set(key, val)
|
144
|
+
end
|
145
|
+
|
146
|
+
def warn(*msg)
|
147
|
+
@lingo.warn(*msg)
|
145
148
|
end
|
146
149
|
|
147
150
|
private
|
@@ -193,10 +196,18 @@ class Lingo
|
|
193
196
|
raise NotImplementedError
|
194
197
|
end
|
195
198
|
|
199
|
+
def _close
|
200
|
+
@db.close
|
201
|
+
end
|
202
|
+
|
196
203
|
def _closed?
|
197
204
|
@db.closed?
|
198
205
|
end
|
199
206
|
|
207
|
+
def _each
|
208
|
+
@db.each { |key, val| yield key, val }
|
209
|
+
end
|
210
|
+
|
200
211
|
def _set(key, val)
|
201
212
|
@db[key] = val
|
202
213
|
end
|
@@ -207,13 +218,13 @@ class Lingo
|
|
207
218
|
|
208
219
|
def _val(key)
|
209
220
|
if val = _get(@crypter ? @crypter.digest(key) : key)
|
210
|
-
val
|
221
|
+
_encode!(val)
|
211
222
|
@crypter ? @crypter.decode(key, val) : val
|
212
223
|
end
|
213
224
|
end
|
214
225
|
|
215
|
-
def
|
216
|
-
|
226
|
+
def _encode!(str)
|
227
|
+
str.force_encoding(ENC)
|
217
228
|
end
|
218
229
|
|
219
230
|
def convert(verbose = @lingo.config.stderr.tty?)
|
data/lib/lingo/debug.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
#--
|
4
|
+
###############################################################################
|
5
|
+
# #
|
6
|
+
# Lingo -- A full-featured automatic indexing system #
|
7
|
+
# #
|
8
|
+
# Copyright (C) 2005-2007 John Vorhauer #
|
9
|
+
# Copyright (C) 2007-2012 John Vorhauer, Jens Wille #
|
10
|
+
# #
|
11
|
+
# Lingo is free software; you can redistribute it and/or modify it under the #
|
12
|
+
# terms of the GNU Affero General Public License as published by the Free #
|
13
|
+
# Software Foundation; either version 3 of the License, or (at your option) #
|
14
|
+
# any later version. #
|
15
|
+
# #
|
16
|
+
# Lingo is distributed in the hope that it will be useful, but WITHOUT ANY #
|
17
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
|
18
|
+
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for #
|
19
|
+
# more details. #
|
20
|
+
# #
|
21
|
+
# You should have received a copy of the GNU Affero General Public License #
|
22
|
+
# along with Lingo. If not, see <http://www.gnu.org/licenses/>. #
|
23
|
+
# #
|
24
|
+
###############################################################################
|
25
|
+
#++
|
26
|
+
|
27
|
+
class Lingo
|
28
|
+
|
29
|
+
module Debug
|
30
|
+
|
31
|
+
extend self
|
32
|
+
|
33
|
+
PS_COMMAND = ENV['LINGO_PS_COMMAND'] || '/bin/ps'
|
34
|
+
PS_COLUMNS = ENV['LINGO_PS_COLUMNS'] || 'vsz,rss,sz,%mem,%cpu,time,etime,pid'
|
35
|
+
|
36
|
+
PS_RE = File.executable?(PS_COMMAND) ? %r{\A#{ENV['LINGO_DEBUG_PS']}\z} : nil
|
37
|
+
|
38
|
+
PS_NO_HEADING = Hash.new { |h, k| h[k] = true; false }
|
39
|
+
|
40
|
+
def ps(name)
|
41
|
+
system(PS_COMMAND,
|
42
|
+
'-o', PS_COLUMNS,
|
43
|
+
"--#{'no-' if PS_NO_HEADING[name]}heading",
|
44
|
+
Process.pid.to_s) if name =~ PS_RE
|
45
|
+
end
|
46
|
+
|
47
|
+
def profile(base)
|
48
|
+
return yield unless base
|
49
|
+
|
50
|
+
require 'ruby-prof'
|
51
|
+
|
52
|
+
result = RubyProf.profile { yield }
|
53
|
+
result.eliminate_methods! [/\b(?:Gem|HighLine)\b/,
|
54
|
+
/\A(?:Benchmark|FileUtils|Pathname|Util)\b/]
|
55
|
+
|
56
|
+
if base.is_a?(IO)
|
57
|
+
RubyProf::FlatPrinter.new(result).print(base)
|
58
|
+
else
|
59
|
+
FileUtils.mkdir_p(File.dirname(base))
|
60
|
+
|
61
|
+
mode = ENV['RUBY_PROF_MEASURE_MODE']
|
62
|
+
base += "-#{mode}" if mode && !mode.empty?
|
63
|
+
|
64
|
+
{
|
65
|
+
:txt => :FlatPrinter,
|
66
|
+
:lines => :FlatPrinterWithLineNumbers,
|
67
|
+
:html => :GraphHtmlPrinter,
|
68
|
+
:stack => :CallStackPrinter
|
69
|
+
}.each { |ext, name|
|
70
|
+
File.open("#{base}.#{ext}", 'a+', encoding: ENC) { |f|
|
71
|
+
RubyProf.const_get(name).new(result).print(f)
|
72
|
+
}
|
73
|
+
}
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|