lingo 1.8.6 → 1.8.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog +40 -4
  3. data/README +22 -51
  4. data/Rakefile +3 -17
  5. data/config/lingo.cfg +24 -15
  6. data/config/lir.cfg +25 -16
  7. data/dict/de/test_muh.txt +6 -0
  8. data/dict/en/lingo-dic.txt +2 -3
  9. data/lang/de.lang +10 -9
  10. data/lang/en.lang +1 -1
  11. data/lib/lingo.rb +4 -4
  12. data/lib/lingo/attendee.rb +27 -7
  13. data/lib/lingo/attendee/analysis_filter.rb +81 -0
  14. data/lib/lingo/attendee/debug_filter.rb +42 -0
  15. data/lib/lingo/attendee/debugger.rb +2 -11
  16. data/lib/lingo/attendee/decomposer.rb +6 -3
  17. data/lib/lingo/attendee/formatter.rb +6 -6
  18. data/lib/lingo/attendee/hal_filter.rb +94 -0
  19. data/lib/lingo/attendee/lsi_filter.rb +99 -0
  20. data/lib/lingo/attendee/multi_worder.rb +69 -43
  21. data/lib/lingo/attendee/sequencer.rb +32 -19
  22. data/lib/lingo/attendee/synonymer.rb +2 -2
  23. data/lib/lingo/attendee/text_reader.rb +63 -92
  24. data/lib/lingo/attendee/text_writer.rb +12 -21
  25. data/lib/lingo/attendee/tokenizer.rb +32 -21
  26. data/lib/lingo/attendee/variator.rb +3 -3
  27. data/lib/lingo/attendee/vector_filter.rb +7 -9
  28. data/lib/lingo/attendee/word_searcher.rb +3 -3
  29. data/lib/lingo/buffered_attendee.rb +3 -36
  30. data/lib/lingo/config.rb +1 -1
  31. data/lib/lingo/ctl.rb +7 -155
  32. data/lib/lingo/ctl/analysis.rb +136 -0
  33. data/lib/lingo/ctl/files.rb +86 -0
  34. data/lib/lingo/ctl/other.rb +140 -0
  35. data/lib/lingo/database.rb +64 -60
  36. data/lib/lingo/database/crypter.rb +7 -5
  37. data/lib/lingo/error.rb +5 -4
  38. data/lib/lingo/language.rb +13 -5
  39. data/lib/lingo/language/grammar.rb +13 -7
  40. data/lib/lingo/language/token.rb +6 -0
  41. data/lib/lingo/language/word.rb +23 -36
  42. data/lib/lingo/language/word_form.rb +5 -1
  43. data/lib/lingo/srv.rb +2 -2
  44. data/lib/lingo/text_utils.rb +96 -0
  45. data/lib/lingo/version.rb +1 -1
  46. data/lib/lingo/web/views/index.erb +1 -1
  47. data/test/attendee/ts_decomposer.rb +23 -5
  48. data/test/attendee/ts_multi_worder.rb +66 -0
  49. data/test/attendee/ts_sequencer.rb +28 -4
  50. data/test/attendee/ts_text_reader.rb +20 -0
  51. data/test/attendee/ts_tokenizer.rb +20 -0
  52. data/test/attendee/ts_variator.rb +1 -1
  53. data/test/attendee/ts_word_searcher.rb +39 -3
  54. data/test/lir3.txt +12 -0
  55. data/test/ref/artikel.non +1 -12
  56. data/test/ref/artikel.seq +3 -1
  57. data/test/ref/artikel.vec +1 -0
  58. data/test/ref/artikel.vef +35 -34
  59. data/test/ref/artikel.ven +8 -7
  60. data/test/ref/artikel.ver +34 -33
  61. data/test/ref/artikel.vet +2573 -2563
  62. data/test/ref/lir.non +77 -78
  63. data/test/ref/lir.seq +9 -7
  64. data/test/ref/lir.syn +1 -1
  65. data/test/ref/lir.vec +41 -41
  66. data/test/ref/lir.vef +210 -210
  67. data/test/ref/lir.ven +46 -46
  68. data/test/ref/lir.ver +72 -72
  69. data/test/ref/lir.vet +329 -329
  70. data/test/ts_database.rb +166 -62
  71. data/test/ts_language.rb +23 -23
  72. metadata +53 -34
  73. data/lib/lingo/attendee/dehyphenizer.rb +0 -120
  74. data/lib/lingo/attendee/noneword_filter.rb +0 -115
  75. data/test/attendee/ts_noneword_filter.rb +0 -15
@@ -0,0 +1,136 @@
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-2015 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
+ require 'csv'
28
+
29
+ class Lingo
30
+
31
+ module Ctl
32
+
33
+ { stats: [:s, 'Extract statistics from analysis file(s)', 'path...'],
34
+ trans: [:t, 'Transpose columns and rows of analysis file(s)', 'path...']
35
+ }.each { |n, (s, *a)| cmd("analysis#{n}", "a#{s}", *a) }
36
+
37
+ private
38
+
39
+ def do_analysisstats
40
+ require 'nuggets/array/histogram'
41
+
42
+ paths, write = paths_write
43
+ stats, patts = Hash.array(1), Hash.array(1)
44
+
45
+ csv_foreach(paths) { |path, _, token, word, pattern|
46
+ token ? stats[:tokens][path] << token : word ? begin
47
+ stats[:words][path] << word
48
+ patts[word][path] << pattern if pattern
49
+ end : nil
50
+ }
51
+
52
+ stats.each { |k, h| write.(k) { |csv|
53
+ csv << ['file', *c = columns(g = histograms(h))]
54
+ histograms_to_csv(csv, c, g)
55
+ h.values.map(&:size)
56
+ } }
57
+
58
+ write.(:patterns) { |csv|
59
+ csv << ['file', 'word', *c = columns(patts, :values)]
60
+ patts.sort.each { |k, h| histograms_to_csv(csv, c, histograms(h), k) }
61
+ c.size - 1
62
+ }
63
+ end
64
+
65
+ def do_analysistrans
66
+ paths, write = paths_write
67
+
68
+ rows, comm, more, less = Hash.array(1), {}, Hash.array(1), Hash.array(1)
69
+
70
+ csv_foreach(paths) { |path, string, token, word, _|
71
+ rows[token || word][path] << string }
72
+
73
+ c = rows.keys.sort.each { |k|
74
+ a = (h = rows[k]).first.last
75
+
76
+ paths.size == 1 ? comm[k] = a : begin
77
+ comm[k] = a & (o = h.drop(1)).flat_map(&:last)
78
+ o.each { |path, b| more[path][k] = b - a; less[path][k] = a - b }
79
+ end
80
+ }
81
+
82
+ rows.clear
83
+
84
+ write.(:transpose) { |csv| transpose_csv(csv, c, comm) }
85
+
86
+ { transmore: more, transless: less }.each { |k, v| v.each { |path, h|
87
+ csv_writer(path, *paths).(k) { |csv| transpose_csv(csv, c, h) } } }
88
+ end
89
+
90
+ def paths_write
91
+ ARGV.empty? ? missing_arg(:path) : [a = ARGV.each { |x|
92
+ abort "No such file: #{x}" unless File.exist?(x) }, csv_writer(*a)]
93
+ end
94
+
95
+ def csv_writer(*paths)
96
+ name = File.join(File.dirname(paths.first), paths.map { |path|
97
+ File.basename(path.chomp(File.extname(path))) }.uniq.join('-'))
98
+
99
+ lambda { |key, &block| overwrite?(file = "#{name}.#{key}.csv") &&
100
+ puts("#{file}: #{Array(CSV.open(file, 'wb', &block)).join(' / ')}") }
101
+ end
102
+
103
+ def csv_foreach(paths)
104
+ paths.each { |path| CSV.foreach(path, headers: true) { |row|
105
+ yield path, *row.values_at(*%w[string token word pattern]) } }
106
+ end
107
+
108
+ def columns(hash, map = :keys)
109
+ hash.values.map(&map).flatten.uniq.sort
110
+ end
111
+
112
+ def histograms(hash)
113
+ hash.each_with_object({}) { |(k, v), h|
114
+ h[k] = v.histogram.tap { |x| x.default = nil } }
115
+ end
116
+
117
+ def histograms_to_csv(csv, columns, histograms, *args)
118
+ histograms.each { |key, histogram|
119
+ others = histograms.values_at(*histograms.keys - [key])
120
+ others = [{}] if others.empty?
121
+
122
+ csv << args.dup.unshift(key).concat(columns.map { |header|
123
+ value = histogram[header] or next
124
+ value if others.any? { |other| other[header] != value }
125
+ })
126
+ }
127
+ end
128
+
129
+ def transpose_csv(csv, columns, rows)
130
+ csv << columns; values = rows.values_at(*columns); rows.clear
131
+ values.map(&:size).max.times { |i| csv << values.map { |v| v[i] } }
132
+ end
133
+
134
+ end
135
+
136
+ end
@@ -0,0 +1,86 @@
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-2015 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 Ctl
30
+
31
+ { config: %w[c configuration],
32
+ lang: %w[l language],
33
+ dict: %w[d dictionary dictionaries],
34
+ store: %w[s store],
35
+ sample: %w[e sample\ text\ file]
36
+ }.each { |n, (s, q, r)|
37
+ t = n == :store
38
+
39
+ cmd([:list, :l, n], s, "List available #{r || "#{q}s"}", '[name...]') if !t
40
+ cmd([:find, :f, n], s, "Find #{q} in Lingo search path", 'name')
41
+ cmd([:copy, :c, n], s, "Copy #{q} to local Lingo directory", 'name') if !t
42
+ cmd([:clear, :c, n], s, 'Remove store files to force rebuild', 'name') if t
43
+ }
44
+
45
+ private
46
+
47
+ def list(what, doit = true)
48
+ names = Regexp.union(*ARGV.empty? ? '' : ARGV)
49
+
50
+ Lingo.list(what, path: path_for_scope).select { |file|
51
+ File.basename(file) =~ names ? doit ? puts(file) : true : false
52
+ }
53
+ end
54
+
55
+ def find(what, doit = true, path = path_for_scope)
56
+ name = ARGV.shift or missing_arg(:name)
57
+ no_args
58
+
59
+ file = Lingo.find(what, name, path: path) { |err| usage(err) }
60
+ doit ? puts(file) : file
61
+ end
62
+
63
+ def copy(what)
64
+ usage('Source and target are the same.') if OPTIONS[:scope] == :local
65
+
66
+ local_path = path_for_scope(:local)
67
+
68
+ source = find(what, false, path_for_scope || Lingo::PATH - local_path)
69
+ target = File.expand_path(Lingo.basepath(what, source), local_path[0])
70
+
71
+ usage('Source and target are the same.') if source == target
72
+
73
+ return unless overwrite?(target)
74
+
75
+ FileUtils.mkdir_p(File.dirname(target))
76
+ FileUtils.cp(source, target, verbose: true)
77
+ end
78
+
79
+ def do_clearstore
80
+ store = Dir["#{find(:store, false)}.*"]
81
+ FileUtils.rm(store, verbose: true) unless store.empty?
82
+ end
83
+
84
+ end
85
+
86
+ end
@@ -0,0 +1,140 @@
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-2015 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
+ require 'zip'
28
+ Zip.unicode_names = true
29
+
30
+ class Lingo
31
+
32
+ module Ctl
33
+
34
+ { demo: [:d, 'Initialize demo directory', '[path]', 'current directory'],
35
+ archive: [:a, 'Create archive of directory', '[path]', 'current directory'],
36
+ rackup: [:r, 'Print path to rackup file', 'name'],
37
+ path: [:p, 'Print search path for dictionaries and configurations'],
38
+ help: [:h, 'Print help for available commands', '[command...]'],
39
+ version: [:v, 'Print Lingo version number']
40
+ }.each { |n, (s, *a)| cmd(n.to_s, s.to_s, *a) }
41
+
42
+ private
43
+
44
+ def do_archive
45
+ OPTIONS.update(path: ARGV.shift, scope: :local)
46
+ no_args
47
+
48
+ source = File.expand_path(path_for_scope.first)
49
+ target = "#{source}.zip"
50
+
51
+ abort "No such directory: #{source}" unless Dir.exist?(source)
52
+
53
+ return unless overwrite?(target, true)
54
+
55
+ base, name = File.split(source)
56
+
57
+ Dir.chdir(base) {
58
+ Zip::File.open(target, Zip::File::CREATE) { |zipfile|
59
+ Dir[File.join(name, '**', '*')].each { |file|
60
+ zipfile.add(file, file)
61
+ }
62
+ }
63
+ }
64
+
65
+ puts "Directory successfully archived at `#{target}'."
66
+ end
67
+
68
+ def do_demo
69
+ OPTIONS.update(path: ARGV.shift, scope: :system)
70
+ no_args
71
+
72
+ path = path_for_scope(:local).first
73
+
74
+ copy_list(:config) { |i| !File.basename(i).start_with?('test') }
75
+ copy_list(:lang)
76
+ copy_list(:dict) { |i| File.basename(i).start_with?('user') }
77
+ copy_list(:sample)
78
+
79
+ puts "Demo directory successfully initialized at `#{path}'."
80
+ end
81
+
82
+ def do_rackup(doit = true)
83
+ name = ARGV.shift or missing_arg(:name)
84
+ no_args
85
+
86
+ require 'lingo/app'
87
+
88
+ if file = Lingo::App.rackup(name)
89
+ doit ? puts(file) : file
90
+ else
91
+ usage("Invalid app name `#{name.inspect}'.")
92
+ end
93
+ end
94
+
95
+ def do_path
96
+ no_args
97
+ puts path_for_scope || PATH
98
+ end
99
+
100
+ def do_help(opts = nil)
101
+ msg = opts ? [opts, 'Commands:'] : []
102
+
103
+ args = ARGV unless opts || ARGV.empty?
104
+
105
+ aliases = Hash.array
106
+ ALIASES.each { |k, v| aliases[v] << k }
107
+
108
+ COMMANDS.each { |c, (d, *e)|
109
+ a = aliases[c]
110
+ next if args && ([c, *a] & args).empty?
111
+
112
+ c = "#{c} (#{a.join(', ')})" unless a.empty?
113
+
114
+ if opts
115
+ msg << " %-#{OPTWIDTH}s %s" % [c, d]
116
+ else
117
+ msg << "#{c}" << " - #{d}"
118
+ e.each { |i| msg << " + #{i}" }
119
+ end
120
+ }
121
+
122
+ msg.empty? ? abort : abort(msg.join("\n"))
123
+ end
124
+
125
+ def do_version(doit = true)
126
+ no_args
127
+
128
+ msg = "Lingo v#{Lingo::VERSION}"
129
+ doit ? puts(msg) : msg
130
+ end
131
+
132
+ def copy_list(what)
133
+ files = list(what, false)
134
+ files.select! { |i| yield i } if block_given?
135
+ files.each { |file| ARGV.replace([file]); copy(what) }
136
+ end
137
+
138
+ end
139
+
140
+ 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 #
@@ -67,22 +67,33 @@ class Lingo
67
67
  def initialize(id, lingo)
68
68
  @id, @lingo, @config, @db = id, lingo, lingo.database_config(id), nil
69
69
 
70
- @srcfile = Lingo.find(:dict, config['name'], relax: true)
71
- @crypter = config.key?('crypt') && Crypter.new
72
-
73
- @val = Hash.nest { [] }
70
+ @val, @crypt, @srcfile = Hash.array, config.key?('crypt'),
71
+ Lingo.find(:dict, config['name'], relax: true)
74
72
 
75
73
  begin
76
74
  @stofile = Lingo.find(:store, @srcfile)
77
75
  FileUtils.mkdir_p(File.dirname(@stofile))
78
76
  rescue SourceFileNotFoundError => err
79
77
  @stofile = skip_ext = err.id
80
- backend = backend_from_file(@stofile) unless err.name
78
+
79
+ unless err.name
80
+ if name = BACKEND_BY_EXT[File.extname(@stofile)]
81
+ backend = get_backend(name, @stofile)
82
+ else
83
+ raise BackendNotFoundError.new(@stofile)
84
+ end
85
+ end
81
86
  rescue NoWritableStoreError
82
87
  backend = HashStore
83
88
  end
84
89
 
85
- use_backend(backend, skip_ext)
90
+ unless backend ||= get_backend(ENV['LINGO_BACKEND'])
91
+ BACKENDS.find { |name| backend = get_backend(name, nil, true) }
92
+ end
93
+
94
+ extend(@backend = backend || HashStore)
95
+
96
+ @stofile << store_ext unless skip_ext || !respond_to?(:store_ext)
86
97
 
87
98
  convert unless uptodate?
88
99
  end
@@ -131,7 +142,7 @@ class Lingo
131
142
  val.uniq!
132
143
 
133
144
  val = val.join(FLD_SEP)
134
- @crypter ? _set(*@crypter.encode(key, val)) : _set(key, val)
145
+ @crypt ? _set(*Crypter.encode(key, val)) : _set(key, val)
135
146
  end
136
147
 
137
148
  def warn(*msg)
@@ -140,26 +151,13 @@ class Lingo
140
151
 
141
152
  private
142
153
 
143
- def use_backend(backend = nil, skip_ext = false)
144
- [ENV['LINGO_BACKEND'], *BACKENDS].each { |mod|
145
- backend = get_backend(mod) and break if mod
146
- } unless backend
147
-
148
- extend(@backend = backend || HashStore)
149
-
150
- @stofile << store_ext if !skip_ext && respond_to?(:store_ext)
151
- end
152
-
153
- def get_backend(mod)
154
- self.class.const_get("#{mod}Store") if Object.const_defined?(mod)
155
- rescue TypeError, NameError
156
- end
157
-
158
- def backend_from_file(file)
159
- ext = File.extname(file)
154
+ def get_backend(name, file = nil, relax = false)
155
+ return unless name
160
156
 
161
- mod = BACKEND_BY_EXT[ext] or raise BackendNotFoundError.new(file)
162
- get_backend(mod) or raise BackendNotAvailableError.new(mod, file)
157
+ Object.const_get(name)
158
+ self.class.const_get("#{name}Store")
159
+ rescue TypeError, NameError => err
160
+ raise BackendNotAvailableError.new(name, file, err) unless relax
163
161
  end
164
162
 
165
163
  def config_hash
@@ -223,9 +221,9 @@ class Lingo
223
221
  end
224
222
 
225
223
  def _val(key)
226
- if val = _get(@crypter ? Crypter.digest(key) : key)
224
+ if val = _get(@crypt ? Crypter.digest(key) : key)
227
225
  _encode!(val)
228
- @crypter ? @crypter.decode(key, val) : val
226
+ @crypt ? Crypter.decode(key, val) : val
229
227
  end
230
228
  end
231
229
 
@@ -236,32 +234,41 @@ class Lingo
236
234
  def convert(verbose = lingo.config.stderr.tty?)
237
235
  src = Source.get(config.fetch('txt-format', 'key_value'), @id, lingo)
238
236
 
239
- sep, key_map, val_map = prepare_lex
237
+ sep, hyphenate, key_map, val_map = prepare_lex
240
238
 
241
239
  Progress.new(self, src, verbose) { |progress| create {
242
240
  src.each { |key, val|
243
241
  progress << src.pos
244
242
 
245
- if key
246
- key.chomp!('.')
247
-
248
- if sep && key.include?(sep)
249
- key = key.split(sep).map!(&key_map).join(sep)
250
- val = val.map { |v| val_map[v.split(sep)].join(sep) } if val_map
243
+ set_key(src, key, val, sep) { |keys, cnt|
244
+ key = keys.map!(&key_map).join(sep)
245
+ val = val.map { |v| val_map[v.split(sep)].join(sep) } if val_map
251
246
 
252
- if (cnt = key.count(sep)) > 2
253
- self[key.split(sep)[0, 3].join(sep)] = ["#{KEY_REF}#{cnt + 1}"]
254
- end
255
- end
256
- end
247
+ hyphenate.repeated_permutation(cnt - 1) { |h| set_key(src, keys.
248
+ zip(h).join, val, sep) unless h.uniq.size == 1 } if hyphenate
257
249
 
258
- src.set(self, key, val)
250
+ [key, val]
251
+ }
259
252
  }
260
253
 
261
254
  uptodate!
262
255
  } }
263
256
  end
264
257
 
258
+ def set_key(src, key, val, sep, len = 3)
259
+ if key
260
+ key.chomp!('.')
261
+
262
+ if sep && key.include?(sep)
263
+ keys = key.split(sep); cnt = keys.size
264
+ key, val = yield keys, cnt if block_given?
265
+ self[keys[0, len].join(sep)] = ["#{KEY_REF}#{cnt}"] if cnt > len
266
+ end
267
+ end
268
+
269
+ src.set(self, key, val)
270
+ end
271
+
265
272
  def prepare_lex
266
273
  use_lex = config['use-lex'] or return
267
274
 
@@ -275,6 +282,9 @@ class Lingo
275
282
 
276
283
  args = nil
277
284
 
285
+ wac = Language::WA_COMPOUND
286
+ lac = Language::LA_COMPOUND
287
+
278
288
  if inflect = config['inflect']
279
289
  inflect, wc = inflect == true ? %w[s e] : inflect.split(SEP_RE), 'a'
280
290
 
@@ -286,19 +296,16 @@ class Lingo
286
296
  end
287
297
  end
288
298
 
289
- [' ', lambda { |form|
299
+ [sep = ' ', config['hyphenate'] && [sep, '-'], lambda { |form|
290
300
  word = dic.find_word(form)
291
301
 
292
302
  if word.unknown?
293
- compo = gra.find_compound(form)
303
+ comp = gra.find_compound(form)
294
304
 
295
- if compo_form = compo.compo_form
296
- compo_form.form
297
- else
298
- compo.norm
299
- end
305
+ comp.attr == wac && comp.lex_form(lac) ||
306
+ (comp.identified? ? comp.lex_form : comp.form)
300
307
  else
301
- word.norm
308
+ word.identified? ? word.lex_form : word.form
302
309
  end
303
310
  }, inflect && lambda { |forms|
304
311
  inflectables = []
@@ -306,24 +313,21 @@ class Lingo
306
313
  forms.each { |form|
307
314
  word = dic.find_word(word_form = form[re])
308
315
 
309
- if word.identified? and lexical = word.get_class(wc).first
310
- inflectables << form if form == lexical.form
316
+ if word.identified? && _form = word.lex_form(wc)
317
+ inflectables << form if form == _form
311
318
  else
312
319
  unless inflectables.empty?
313
- comp = gra.find_compound(word_form) if word.unknown?
314
- word = comp.head || comp if comp && !comp.unknown?
320
+ word = gra.find_compound_head(word_form) || word if word.unknown?
315
321
 
316
- if word.attr?(*inflect)
317
- suffix = suffixes[word.genders.compact.first]
318
- inflectables.each { |lex_form| lex_form << suffix } if suffix
322
+ if word.attr?(*inflect) && suffix =
323
+ suffixes[word.genders.compact.first]
324
+ inflectables.each { |lex_form| lex_form << suffix }
319
325
  end
320
326
  end
321
327
 
322
- break
328
+ break forms
323
329
  end
324
330
  }
325
-
326
- forms
327
331
  }]
328
332
  end
329
333