lingo 1.8.5 → 1.8.6

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.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog +25 -0
  3. data/README +7 -5
  4. data/Rakefile +58 -55
  5. data/{lingo-call.cfg → config/lingo-call.cfg} +1 -1
  6. data/{lingo.cfg → config/lingo.cfg} +10 -2
  7. data/{lir.cfg → config/lir.cfg} +10 -2
  8. data/{de → dict/de}/lingo-abk.txt +0 -0
  9. data/{de → dict/de}/lingo-dic.txt +0 -0
  10. data/{de → dict/de}/lingo-mul.txt +0 -0
  11. data/{de → dict/de}/lingo-syn.txt +0 -0
  12. data/{de → dict/de}/test_dic.txt +0 -0
  13. data/{de → dict/de}/test_gen.txt +0 -0
  14. data/{de → dict/de}/test_mu2.txt +0 -0
  15. data/{de → dict/de}/test_mul.txt +0 -0
  16. data/{de → dict/de}/test_sgw.txt +0 -0
  17. data/{de → dict/de}/test_syn.txt +0 -0
  18. data/{de → dict/de}/user-dic.txt +0 -0
  19. data/{en → dict/en}/lingo-dic.txt +0 -0
  20. data/{en → dict/en}/lingo-irr.txt +0 -0
  21. data/{en → dict/en}/lingo-mul.txt +0 -0
  22. data/{en → dict/en}/lingo-syn.txt +0 -0
  23. data/{en → dict/en}/lingo-wdn.txt +0 -0
  24. data/{en → dict/en}/user-dic.txt +0 -0
  25. data/{ru → dict/ru}/lingo-dic.txt +0 -0
  26. data/{ru → dict/ru}/lingo-mul.txt +0 -0
  27. data/{ru → dict/ru}/lingo-syn.txt +0 -0
  28. data/{ru → dict/ru}/user-dic.txt +0 -0
  29. data/{de.lang → lang/de.lang} +1 -1
  30. data/{en.lang → lang/en.lang} +0 -0
  31. data/{ru.lang → lang/ru.lang} +0 -0
  32. data/lib/lingo.rb +14 -15
  33. data/lib/lingo/app.rb +4 -2
  34. data/lib/lingo/attendee.rb +23 -43
  35. data/lib/lingo/attendee/abbreviator.rb +5 -5
  36. data/lib/lingo/attendee/debugger.rb +39 -12
  37. data/lib/lingo/attendee/decomposer.rb +3 -4
  38. data/lib/lingo/attendee/dehyphenizer.rb +4 -4
  39. data/lib/lingo/attendee/formatter.rb +1 -3
  40. data/lib/lingo/attendee/multi_worder.rb +3 -4
  41. data/lib/lingo/attendee/noneword_filter.rb +8 -12
  42. data/lib/lingo/attendee/object_filter.rb +6 -3
  43. data/lib/lingo/attendee/sequencer.rb +5 -5
  44. data/lib/lingo/attendee/stemmer.rb +3 -2
  45. data/lib/lingo/attendee/synonymer.rb +3 -4
  46. data/lib/lingo/attendee/text_reader.rb +39 -38
  47. data/lib/lingo/attendee/text_writer.rb +10 -10
  48. data/lib/lingo/attendee/tokenizer.rb +63 -33
  49. data/lib/lingo/attendee/variator.rb +3 -7
  50. data/lib/lingo/attendee/vector_filter.rb +132 -65
  51. data/lib/lingo/attendee/word_searcher.rb +5 -3
  52. data/lib/lingo/buffered_attendee.rb +1 -3
  53. data/lib/lingo/call.rb +4 -3
  54. data/lib/lingo/cli.rb +5 -1
  55. data/lib/lingo/config.rb +11 -5
  56. data/lib/lingo/ctl.rb +3 -3
  57. data/lib/lingo/database.rb +3 -1
  58. data/lib/lingo/database/crypter.rb +1 -3
  59. data/lib/lingo/database/source.rb +3 -1
  60. data/lib/lingo/database/source/key_value.rb +3 -1
  61. data/lib/lingo/database/source/multi_key.rb +3 -1
  62. data/lib/lingo/database/source/multi_value.rb +3 -1
  63. data/lib/lingo/database/source/single_word.rb +3 -1
  64. data/lib/lingo/database/source/word_class.rb +3 -1
  65. data/lib/lingo/debug.rb +5 -5
  66. data/lib/lingo/{agenda_item.rb → deferred_attendee.rb} +21 -12
  67. data/lib/lingo/error.rb +1 -1
  68. data/lib/lingo/language.rb +1 -9
  69. data/lib/lingo/language/dictionary.rb +2 -17
  70. data/lib/lingo/language/grammar.rb +10 -10
  71. data/lib/lingo/language/lexical.rb +2 -0
  72. data/lib/lingo/language/lexical_hash.rb +2 -0
  73. data/lib/lingo/language/token.rb +17 -3
  74. data/lib/lingo/language/word.rb +13 -5
  75. data/lib/lingo/language/word_form.rb +5 -3
  76. data/lib/lingo/progress.rb +2 -2
  77. data/lib/lingo/srv.rb +1 -1
  78. data/lib/lingo/srv/lingosrv.cfg +1 -1
  79. data/lib/lingo/version.rb +1 -1
  80. data/lib/lingo/web.rb +1 -1
  81. data/lib/lingo/web/lingoweb.cfg +1 -1
  82. data/test/attendee/ts_abbreviator.rb +4 -2
  83. data/test/attendee/ts_multi_worder.rb +81 -88
  84. data/test/attendee/ts_noneword_filter.rb +2 -2
  85. data/test/attendee/ts_object_filter.rb +2 -2
  86. data/test/attendee/ts_sequencer.rb +40 -20
  87. data/test/attendee/ts_stemmer.rb +52 -26
  88. data/test/attendee/ts_text_reader.rb +75 -56
  89. data/test/attendee/ts_text_writer.rb +6 -4
  90. data/test/attendee/ts_tokenizer.rb +304 -193
  91. data/test/attendee/ts_vector_filter.rb +242 -9
  92. data/test/ref/artikel.non +3 -0
  93. data/test/ref/artikel.vec +1 -4
  94. data/test/ref/artikel.vef +940 -0
  95. data/test/ref/artikel.ven +0 -3
  96. data/test/ref/artikel.ver +0 -3
  97. data/test/ref/artikel.vet +2580 -0
  98. data/test/ref/lir.non +34 -31
  99. data/test/ref/lir.seq +14 -15
  100. data/test/ref/lir.vec +37 -37
  101. data/test/ref/lir.vef +329 -0
  102. data/test/ref/lir.ven +329 -0
  103. data/test/ref/lir.ver +329 -0
  104. data/test/ref/lir.vet +329 -0
  105. data/test/test_helper.rb +29 -16
  106. data/test/ts_language.rb +6 -47
  107. metadata +74 -87
  108. data/lingo.rb +0 -29
  109. data/spec/spec_helper.rb +0 -5
@@ -28,6 +28,7 @@ class Lingo
28
28
 
29
29
  class Attendee
30
30
 
31
+ #--
31
32
  # Der Variator ermöglicht bei nicht erkannten Wörtern den listenbasierten
32
33
  # Austausch einzelner Wortteile einchließlich erneuter Wörterbuchsuche zur
33
34
  # Verbesserung der Worterkennungsquote.
@@ -69,11 +70,10 @@ class Lingo
69
70
  # out> :./PUNC:
70
71
  # out> *EOL('test.txt')
71
72
  # out> *EOF('test.txt')
73
+ #++
72
74
 
73
75
  class Variator < self
74
76
 
75
- protected
76
-
77
77
  def init
78
78
  @marker = get_key('marker', '*')
79
79
  @max = get_key('max-var', max = 10000).to_i
@@ -89,8 +89,7 @@ class Lingo
89
89
  set_gra
90
90
  end
91
91
 
92
- def control(cmd, param)
93
- # can control
92
+ def control(*)
94
93
  end
95
94
 
96
95
  def process(obj)
@@ -118,9 +117,6 @@ class Lingo
118
117
 
119
118
  private
120
119
 
121
- # Variiere die Bestandteile eines Arrays gemäß den Austauschvorgaben.
122
- #
123
- # variate( 'Tiieh', 'ieh', 'sch' ) => ['Tiieh', 'Tisch']
124
120
  def variate(variations, from, to)
125
121
  add, change, re = [], [from, to], Regexp.new(from)
126
122
 
@@ -24,10 +24,13 @@
24
24
  ###############################################################################
25
25
  #++
26
26
 
27
+ require 'csv'
28
+
27
29
  class Lingo
28
30
 
29
31
  class Attendee
30
32
 
33
+ #--
31
34
  # Die Hauptaufgabe des VectorFilter ist die Erstellung eines Dokumenten-Index-Vektor.
32
35
  # Dabei werden die durch die anderen Attendees ermittelten Grundformen eines Wortes
33
36
  # gespeichert und bei einem Datei- oder Record-Wechsel weitergeleitet. Der VectorFilter
@@ -76,101 +79,165 @@ class Lingo
76
79
  # out> *FILE('test.txt')
77
80
  # out> "0.28571 indexierung"
78
81
  # out> *EOF('test.txt')
82
+ #++
79
83
 
80
84
  class VectorFilter < self
81
85
 
82
- DEFAULT_SRC_SEP = '|'
86
+ DEFAULT_SRC_SEPARATOR = '|'
87
+ DEFAULT_POS_SEPARATOR = '@'
88
+
89
+ DEFAULT_DICT_SEPARATOR = Database::Source::WordClass::DEFAULT_SEPARATOR
90
+
91
+ DEFAULT_GENDER_SEPARATOR = Database::Source::WordClass::GENDER_SEPARATOR
83
92
 
84
- protected
93
+ TERMINALS = [:FILE, :RECORD, :EOF]
85
94
 
86
95
  def init
87
- if @debug = get_key('debug', false)
88
- @prompt = get_key('prompt', 'lex:) ')
89
- @preamble = get_key('preamble', true)
90
- else
91
- @lex = get_re('lexicals', '[sy]')
92
- @skip = get_array('skip', DEFAULT_SKIP, :upcase)
96
+ @lex = get_re('lexicals', '[sy]')
97
+ @skip = get_array('skip', DEFAULT_SKIP, :upcase)
98
+
99
+ @src = @pos = @sort_fmt = @sort_rel = @docnum = nil
93
100
 
94
- @dict = get_key('dict', false)
95
- @norm = get_key('norm', false) if @dict
96
- @dict = Database::Source::WordClass::DEFAULT_SEPARATOR if @dict == true
101
+ @tokens, @vectors, @word_count = [], Hash.nest(1) { [] }, Hash.new(0)
97
102
 
103
+ if @dict = get_key('dict', false)
104
+ @norm = get_key('norm', false)
105
+ @dict = DEFAULT_DICT_SEPARATOR if @dict == true
106
+ else
98
107
  @src = get_key('src', false)
99
- @src = DEFAULT_SRC_SEP if @src == true
108
+ @src = DEFAULT_SRC_SEPARATOR if @src == true
100
109
 
101
- if sort = get_key('sort', ENV['LINGO_NO_SORT'] ? false : 'normal')
102
- @sort_format, @sort_method = sort.downcase.split('_', 2)
103
- end
110
+ @pos = get_key('pos', false)
111
+ @pos = DEFAULT_POS_SEPARATOR if @pos == true
112
+
113
+ @tokens = get_array('tokens', '', :upcase)
114
+ @tokens.concat(Tokenizer.rules) if @tokens.delete('ALL')
104
115
  end
105
116
 
106
- @vectors, @word_count = [], 0
117
+ if sort = get_key('sort', ENV['LINGO_NO_SORT'] ? false : 'normal')
118
+ @sort_fmt, sort_method = sort.downcase.split('_', 2)
119
+
120
+ @sort_rel = rel = sort_method == 'rel'
121
+
122
+ unless @sort_fmt == 'normal'
123
+ if @tfidf = get_key('tfidf', false)
124
+ DeferredAttendee.enhance(self)
125
+ @docnum, rel = 0, true
126
+ end
127
+
128
+ _sort_fmt = @sort_fmt == 'sto' ? '%2$s {%1$X}' : '%X %s'
129
+ @sort_fmt = _sort_fmt.sub('X', rel ? '.5f' : 'd')
130
+ end
131
+ end
107
132
  end
108
133
 
109
- def control(cmd, param)
134
+ def control(cmd, *)
110
135
  case cmd
111
- when STR_CMD_EOL
112
- skip_command
113
- when STR_CMD_FILE, STR_CMD_RECORD, STR_CMD_EOF
114
- send_vectors unless @vectors.empty?
136
+ when :EOL then :skip_command
137
+ when *TERMINALS then send_vectors unless @docnum
115
138
  end
116
139
  end
117
140
 
141
+ def control_deferred(cmd, *)
142
+ @docnum += 1 if TERMINALS.include?(cmd)
143
+ end
144
+
118
145
  def process(obj)
119
- if @debug
120
- forward((@preamble = nil; @lingo.config.to_h.to_yaml)) if @preamble
121
- forward("#{@prompt} #{obj.inspect}") if eval(@debug)
122
- elsif obj.is_a?(Word) && !@skip.include?(obj.attr)
123
- @word_count += 1
124
-
125
- if @dict
126
- vec, sep = [], Database::Source::WordClass::GENDER_SEPARATOR
127
-
128
- obj.get_class(@lex).each { |lex|
129
- str = "#{lex.form} ##{lex.attr}"
130
- str << sep << lex.gender if lex.gender
131
- vec << str
132
- }
133
-
134
- unless vec.empty?
135
- wrd = @norm ? obj.lexicals.first.form : obj.form
136
- vec = Unicode.downcase("#{wrd}#{@dict}#{vec.join(' ')}")
137
- @sort_format ? @vectors << vec : forward(vec)
138
- end
139
- else
140
- obj.get_class(@lex).each { |lex|
141
- vec = Unicode.downcase(lex.form)
142
- vec << @src << lex.src if @src && lex.src
143
- @sort_format ? @vectors << vec : forward(vec)
144
- }
145
- end
146
+ if obj.is_a?(Token)
147
+ return unless @tokens.include?(obj.attr)
148
+ elsif obj.is_a?(Word)
149
+ return if @skip.include?(obj.attr)
150
+ else
151
+ return
152
+ end
153
+
154
+ @word_count[@docnum] += 1
155
+
156
+ @dict ? forward_dict(obj) : begin
157
+ pos = obj.position_and_offset if @pos
158
+
159
+ obj.is_a?(Token) ? forward_vector(obj, pos) :
160
+ obj.get_class(@lex).each { |lex| forward_vector(lex, pos, lex.src) }
146
161
  end
147
162
  end
148
163
 
149
164
  private
150
165
 
151
- def send_vectors
152
- if @sort_format == 'normal'
153
- @vectors.uniq!
154
- flush(@vectors.sort!)
155
- else
156
- cnt, fmt = Hash.new(0), '%d'
166
+ def vectors(docnum = nil)
167
+ @vectors[docnum || @docnum]
168
+ end
169
+
170
+ def word_count(docnum = nil)
171
+ @word_count[docnum || @docnum]
172
+ end
157
173
 
158
- @vectors.each { |v| cnt[v] += 1 }.clear
159
- vec = cnt.sort_by { |v, c| [-c, v] }
174
+ def forward_dict(obj, sep = DEFAULT_GENDER_SEPARATOR)
175
+ vectors = obj.get_class(@lex).map { |lex|
176
+ "#{lex.form} ##{lex.attr}".tap { |str|
177
+ str << sep << lex.gender if lex.gender
178
+ }
179
+ }
160
180
 
161
- if @sort_method == 'rel'
162
- fmt, wc = '%6.5f', @word_count.to_f
163
- vec.each { |v| v[1] /= wc }
164
- end
181
+ unless vectors.empty?
182
+ vec = @norm ? obj.lexicals.first.form : obj.form
183
+ forward_vector("#{vec}#{@dict}#{vectors.join(' ')}")
184
+ end
185
+ end
186
+
187
+ def forward_vector(vec, pos = nil, src = nil)
188
+ vec = vec.form if vec.is_a?(WordForm)
189
+
190
+ vec = Unicode.downcase(vec)
191
+ vec << @src << src if @src && src
192
+
193
+ @sort_fmt ? vectors[vec] << pos : forward(vec_pos(vec, [pos]))
194
+ end
195
+
196
+ def send_vectors
197
+ if @docnum
198
+ df, abs = Hash.new(0), @sort_rel ? nil : 1
199
+
200
+ @vectors.each_value { |w| w.each_key { |v| df[v] += 1 } }
165
201
 
166
- if @sort_format == 'sto'
167
- fmt, @word_count = "%s {#{fmt}}", 0
168
- else
169
- fmt.insert(1, '2$') << ' %1$s'
202
+ if @tfidf.is_a?(String)
203
+ CSV.open(@tfidf, 'wb') { |c| df.sort.each { |v| c << v } }
170
204
  end
171
205
 
172
- vec.each { |v| forward(fmt % v) }
206
+ yield lambda { |docnum|
207
+ wc = abs || word_count(docnum)
208
+ flush_vectors(wc, docnum) { |c, v, vp| [c / df[v], vp] }
209
+ }
210
+ elsif @sort_fmt == 'normal'
211
+ flush(map_vectors { |_, _, vp| vp }.sort!)
212
+ else
213
+ flush_vectors(@sort_rel ? word_count : 1) { |c, _, vp| [c, vp] }
173
214
  end
215
+
216
+ @word_count.clear
217
+ @vectors.clear
218
+ end
219
+
220
+ alias_method :flush_deferred, :send_vectors
221
+
222
+ def map_vectors(wc = 1, docnum = nil)
223
+ v = vectors(docnum)
224
+ v.map { |vec, pos| yield pos.size / wc.to_f, vec, vec_pos(vec, pos) }
225
+ ensure
226
+ v.clear if v
227
+ end
228
+
229
+ def flush_vectors(*args, &block)
230
+ map_vectors(*args, &block)
231
+ .sort_by { |w, v| [-w, v] }
232
+ .each { |vec| forward(@sort_fmt % vec) }
233
+ end
234
+
235
+ def vec_pos(vec, pos)
236
+ pos.clear unless @pos
237
+
238
+ pos.compact!
239
+ pos.uniq!
240
+ pos.empty? ? vec : "#{vec}#{@pos}#{pos.join(',')}"
174
241
  end
175
242
 
176
243
  end
@@ -28,6 +28,7 @@ class Lingo
28
28
 
29
29
  class Attendee
30
30
 
31
+ #--
31
32
  # Der WordSearcher ist das Herzstück von Lingo. Er macht die Hauptarbeit und versucht
32
33
  # alle Token die nach einem sinnvollen Wort aussehen, in den ihm angegebenen
33
34
  # Wörterbüchern zu finden und aufzulösen. Dabei werden die im Wörterbuch gefundenen
@@ -64,6 +65,7 @@ class Lingo
64
65
  # out> :./PUNC:
65
66
  # out> *EOL('test.txt')
66
67
  # out> *EOF('test.txt')
68
+ #++
67
69
 
68
70
  class WordSearcher < self
69
71
 
@@ -71,12 +73,12 @@ class Lingo
71
73
  set_dic
72
74
  end
73
75
 
74
- def control(cmd, param)
75
- # can control
76
+ def control(*)
76
77
  end
77
78
 
78
79
  def process(obj)
79
- forward(obj.is_a?(Token) && obj.word? ? @dic.find_word(obj.form) : obj)
80
+ forward(obj.is_a?(Token) && obj.word? ?
81
+ @dic.find_word(obj.form, obj) : obj)
80
82
  end
81
83
 
82
84
  end
@@ -33,8 +33,6 @@ class Lingo
33
33
  super
34
34
  end
35
35
 
36
- protected
37
-
38
36
  def process(obj)
39
37
  @buffer << obj
40
38
  process_buffer if process_buffer?
@@ -70,7 +68,7 @@ class Lingo
70
68
  end
71
69
 
72
70
  def control_multi(cmd)
73
- if [STR_CMD_RECORD, STR_CMD_EOF].include?(cmd)
71
+ if [:RECORD, :EOF].include?(cmd)
74
72
  @eof_handling = true
75
73
 
76
74
  while valid_tokens_in_buffer > 1
@@ -6,7 +6,7 @@
6
6
  # Lingo -- A full-featured automatic indexing system #
7
7
  # #
8
8
  # Copyright (C) 2005-2007 John Vorhauer #
9
- # Copyright (C) 2007-2012 John Vorhauer, Jens Wille #
9
+ # Copyright (C) 2007-2014 John Vorhauer, Jens Wille #
10
10
  # #
11
11
  # Lingo is free software; you can redistribute it and/or modify it under the #
12
12
  # terms of the GNU Affero General Public License as published by the Free #
@@ -48,8 +48,9 @@ class Lingo
48
48
  end
49
49
  end
50
50
 
51
- def talk(str, raw = false)
52
- config.stdin.reopen(str)
51
+ def talk(input, raw = false)
52
+ config.stdin.reopen(
53
+ input.respond_to?(:read) ? input.read : input)
53
54
 
54
55
  start
55
56
 
@@ -6,7 +6,7 @@
6
6
  # Lingo -- A full-featured automatic indexing system #
7
7
  # #
8
8
  # Copyright (C) 2005-2007 John Vorhauer #
9
- # Copyright (C) 2007-2012 John Vorhauer, Jens Wille #
9
+ # Copyright (C) 2007-2014 John Vorhauer, Jens Wille #
10
10
  # #
11
11
  # Lingo is free software; you can redistribute it and/or modify it under the #
12
12
  # terms of the GNU Affero General Public License as published by the Free #
@@ -50,6 +50,10 @@ class Lingo
50
50
 
51
51
  private
52
52
 
53
+ def config_present?(config)
54
+ Lingo.find(:config, config) {}
55
+ end
56
+
53
57
  def load_config(*)
54
58
  @config = {}
55
59
  end
@@ -6,7 +6,7 @@
6
6
  # Lingo -- A full-featured automatic indexing system #
7
7
  # #
8
8
  # Copyright (C) 2005-2007 John Vorhauer #
9
- # Copyright (C) 2007-2014 John Vorhauer, Jens Wille #
9
+ # Copyright (C) 2007-2015 John Vorhauer, Jens Wille #
10
10
  # #
11
11
  # Lingo is free software; you can redistribute it and/or modify it under the #
12
12
  # terms of the GNU Affero General Public License as published by the Free #
@@ -32,7 +32,7 @@ class Lingo
32
32
  class Config
33
33
 
34
34
  def initialize(*args)
35
- @deprecated = Hash.new { |h, k| h[k] = true; false }
35
+ @deprecated = Hash.seen
36
36
 
37
37
  @cli, @opts = CLI.new, {}
38
38
 
@@ -42,7 +42,9 @@ class Lingo
42
42
  load_config('language', :lang)
43
43
  load_config('config')
44
44
 
45
- deprecate(:textreader, :text_reader) if Array(self['meeting/attendees']).map(&:keys).flatten.include?('textreader')
45
+ if Array(self['meeting/attendees']).flat_map(&:keys).include?('textreader')
46
+ deprecate(:textreader, :text_reader)
47
+ end
46
48
 
47
49
  if r = get('meeting/attendees', 'text_reader') ||
48
50
  get('meeting/attendees', 'textreader') # DEPRECATE textreader
@@ -56,6 +58,8 @@ class Lingo
56
58
  end
57
59
  end
58
60
 
61
+ attr_reader :language_file, :config_file
62
+
59
63
  def to_h
60
64
  @opts
61
65
  end
@@ -106,10 +110,10 @@ class Lingo
106
110
  @cli.send(:quit, *args)
107
111
  end
108
112
 
109
- def deprecate(old, new, obj = self)
113
+ def deprecate(old, new, obj = self, what = :option)
110
114
  unless @deprecated[[source = obj.class.name.sub(/\ALingo::/, ''), old]]
111
115
  warn(
112
- "DEPRECATION WARNING: #{source} option `#{old}' is deprecated " <<
116
+ "DEPRECATION WARNING: #{source} #{what} `#{old}' is deprecated " <<
113
117
  "and will be removed in Lingo 1.9. Please use `#{new}' instead."
114
118
  )
115
119
  end
@@ -127,6 +131,8 @@ class Lingo
127
131
 
128
132
  def load_config(key, type = key.to_sym)
129
133
  file = Lingo.find(type, @opts[key]) { quit }
134
+ instance_variable_set("@#{type}_file", file)
135
+
130
136
  File.open(file, encoding: ENC) { |f| @opts.update(SafeYAML.load(f)) }
131
137
  rescue Psych::SyntaxError => err
132
138
  err.message << " (in #{file})"
@@ -38,8 +38,8 @@ class Lingo
38
38
  PROG, VERSION, OPTWIDTH = $0, '0.0.2', 18
39
39
  PROGNAME, OPTIONS = File.basename(PROG), {}
40
40
 
41
- COMMANDS, ALIASES = {}, Hash.new { |h, k|
42
- h[k] = COMMANDS.key?(k) ? k : 'usage'
41
+ COMMANDS, ALIASES = {}, Hash.nest { |k|
42
+ COMMANDS.key?(k) ? k : 'usage'
43
43
  }
44
44
 
45
45
  USAGE = <<-EOT
@@ -187,7 +187,7 @@ Usage: #{PROG} <command> [arguments] [options]
187
187
 
188
188
  msg = opts ? [opts, 'Commands:'] : []
189
189
 
190
- aliases = Hash.new { |h, k| h[k] = [] }
190
+ aliases = Hash.nest { [] }
191
191
  ALIASES.each { |k, v| aliases[v] << k }
192
192
 
193
193
  COMMANDS.each { |c, (d, *e)|