lingo 1.8.5 → 1.8.6

Sign up to get free protection for your applications and to get access to all the features.
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)|