voikko 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE +21 -0
- data/README.md +44 -0
- data/lib/voikko/version.rb +6 -0
- data/lib/voikko.rb +269 -0
- metadata +64 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 75cea670fe743bc6a519289cbed7089191dd23be56c8bf5391690c5a7ac8b847
|
|
4
|
+
data.tar.gz: 4d75c2e84b678dfa56d32aa423869926284c0c09cea9cea221f63615198bb0b8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8fb7bbac4b9f7ed072e94a15476ca6189737c217dbd2ef1935ab0d469a139eca9a71cfd6de4a90afde7de7d8fb5b8cd7df58a8c6ba865d9f726fc4bc2240ca89
|
|
7
|
+
data.tar.gz: d87866cc079d8f7084d742b9e2e31045495829a3ae6c37d913d4a689a5c87b0152fbd959249908bc29a27532a49d1979f883ff55e5460d835e37c11c50d51e04
|
data/CHANGELOG.md
ADDED
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Martin Vysny
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# ruby-voikko
|
|
2
|
+
|
|
3
|
+
Ruby bindings for [libvoikko](https://voikko.puimula.org/), the Finnish morphological analyser
|
|
4
|
+
that also powers Finnish spellchecking in LibreOffice. The gem is pure Ruby and calls the
|
|
5
|
+
system's `libvoikko` through FFI; the library and its Finnish dictionary come from your
|
|
6
|
+
distro's packages.
|
|
7
|
+
|
|
8
|
+
## Requirements
|
|
9
|
+
|
|
10
|
+
- Linux with `libvoikko1` and the Finnish dictionary `voikko-fi`:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
sudo apt install libvoikko1 voikko-fi
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- Ruby 3.3 or newer.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
gem 'voikko'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
require 'voikko'
|
|
28
|
+
|
|
29
|
+
Voikko.open(language: 'fi') do |voikko|
|
|
30
|
+
kadulla = voikko.analyze('kadulla').first
|
|
31
|
+
kadulla.base_form #=> "katu"
|
|
32
|
+
kadulla.sijamuoto #=> "ulkoolento"
|
|
33
|
+
voikko.base_forms('voi') #=> ["voida", "voi"]
|
|
34
|
+
voikko.base_forms('HELSINGISSÄ') #=> ["Helsinki"]
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`Voikko.open` closes the handle after the block; `Voikko.new` leaves that to `#close`.
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
MIT, see [LICENSE](LICENSE). The gem ships no Voikko code or data: libvoikko
|
|
43
|
+
(MPL 1.1 / GPL 2+ / LGPL 2.1+) and `voikko-fi` (GPL 2) are installed and licensed separately
|
|
44
|
+
by your distro.
|
data/lib/voikko.rb
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ffi'
|
|
4
|
+
require_relative 'voikko/version'
|
|
5
|
+
|
|
6
|
+
# A handle on libvoikko, the Finnish morphological analyser, bound through FFI to the system's
|
|
7
|
+
# +libvoikko.so.1+. Open one per language and ask it about words:
|
|
8
|
+
#
|
|
9
|
+
# Voikko.open(language: 'fi') do |voikko|
|
|
10
|
+
# kadulla = voikko.analyze('kadulla').first
|
|
11
|
+
# kadulla.base_form #=> "katu"
|
|
12
|
+
# kadulla.sijamuoto #=> "ulkoolento"
|
|
13
|
+
# voikko.base_forms('voi') #=> ["voida", "voi"]
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# A handle holds about 0.5 MB of native memory that Ruby's GC cannot see: prefer {.open}, which
|
|
17
|
+
# closes it after the block, or call {#close} yourself. A forgotten handle is still freed by a
|
|
18
|
+
# finalizer once unreachable. After {#close}, every method but {#close} and {#closed?} raises
|
|
19
|
+
# {Error}.
|
|
20
|
+
#
|
|
21
|
+
# Named +Voikko+, not +Voikko::Analyzer+, because libvoikko also spells, hyphenates and checks
|
|
22
|
+
# grammar; those methods (+#spell+, +#suggest+, +#hyphenate+) would sit here, and are not bound
|
|
23
|
+
# yet. Upstream's Python and Java bindings name the class +Voikko+ too.
|
|
24
|
+
#
|
|
25
|
+
# == Thread-safety
|
|
26
|
+
#
|
|
27
|
+
# Thread-safe: every call into the handle runs under the handle's own mutex, since libvoikko's
|
|
28
|
+
# handle silently returns wrong analyses when two threads share it. Threads that want analyses
|
|
29
|
+
# in parallel open a handle each.
|
|
30
|
+
class Voikko
|
|
31
|
+
# libvoikko refused: a dictionary failed to open, or the handle is already closed.
|
|
32
|
+
class Error < StandardError; end
|
|
33
|
+
|
|
34
|
+
# One reading of a word, as libvoikko gives it: a frozen +Hash{String => String}+ of its
|
|
35
|
+
# attributes, with a reader for each key libvoikko defines.
|
|
36
|
+
#
|
|
37
|
+
# analysis = voikko.analyze('kadulla').first
|
|
38
|
+
# analysis.base_form #=> "katu"
|
|
39
|
+
# analysis.word_class #=> "nimisana"
|
|
40
|
+
# analysis.tense #=> nil, a noun has none
|
|
41
|
+
# analysis['SIJAMUOTO'] #=> "ulkoolento"
|
|
42
|
+
# analysis.to_h #=> {"BASEFORM" => "katu", "CLASS" => "nimisana", ...}
|
|
43
|
+
#
|
|
44
|
+
# The readers are {KEYS}: libvoikko's closed key enum, each key in snake_case and untranslated
|
|
45
|
+
# (+sijamuoto+, not +case+), except +CLASS+ → +word_class+ since +#class+ is taken. Each returns
|
|
46
|
+
# +nil+ where the dictionary's backend or the word class has no such attribute; no key is
|
|
47
|
+
# present for every backend. Values stay libvoikko's strings: +negative+ is +"true"+,
|
|
48
|
+
# +"false"+ or +"both"+, +person+ is +"1"+. +FSTOUTPUT+ passes through like any other key.
|
|
49
|
+
# {#[]} and {#to_h} reach a key a later libvoikko adds.
|
|
50
|
+
#
|
|
51
|
+
# @!attribute [r] attributes
|
|
52
|
+
# @return [Hash{String => String}] libvoikko's attributes, keyed by its upper-case names.
|
|
53
|
+
Analysis = Data.define(:attributes)
|
|
54
|
+
|
|
55
|
+
# Reopened rather than given as a +Data.define+ block: a constant inside that block would
|
|
56
|
+
# land on {Voikko}.
|
|
57
|
+
class Analysis
|
|
58
|
+
# libvoikko's attribute keys, each mapped to the name of its reader on {Analysis}.
|
|
59
|
+
# @return [Hash{String => Symbol}]
|
|
60
|
+
KEYS = {
|
|
61
|
+
'BASEFORM' => :base_form, 'CLASS' => :word_class, 'COMPARISON' => :comparison,
|
|
62
|
+
'FOCUS' => :focus, 'FSTOUTPUT' => :fst_output, 'KYSYMYSLIITE' => :kysymysliite,
|
|
63
|
+
'MALAGA_VAPAA_JALKIOSA' => :malaga_vapaa_jalkiosa, 'MOOD' => :mood,
|
|
64
|
+
'NEGATIVE' => :negative, 'NUMBER' => :number, 'PARTICIPLE' => :participle,
|
|
65
|
+
'PERSON' => :person, 'POSSESSIVE' => :possessive,
|
|
66
|
+
'POSSIBLE_GEOGRAPHICAL_NAME' => :possible_geographical_name,
|
|
67
|
+
'REQUIRE_FOLLOWING_VERB' => :require_following_verb, 'SIJAMUOTO' => :sijamuoto,
|
|
68
|
+
'STRUCTURE' => :structure, 'TENSE' => :tense, 'WEIGHT' => :weight,
|
|
69
|
+
'WORDBASES' => :word_bases, 'WORDIDS' => :word_ids
|
|
70
|
+
}.freeze
|
|
71
|
+
|
|
72
|
+
KEYS.each { |key, reader| define_method(reader) { attributes[key] } }
|
|
73
|
+
|
|
74
|
+
# @param key [String] a libvoikko key, upper case: +'SIJAMUOTO'+.
|
|
75
|
+
# @return [String, nil] its value, or +nil+ when this analysis lacks it.
|
|
76
|
+
def [](key) = attributes[key]
|
|
77
|
+
|
|
78
|
+
# @return [Hash{String => String}] the attributes themselves, not +Data+'s
|
|
79
|
+
# +{attributes: {...}}+.
|
|
80
|
+
def to_h = attributes
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# The C functions, bound by the versioned soname: the bare +libvoikko.so+ ships only with
|
|
84
|
+
# +libvoikko-dev+. A missing library re-raises +LoadError+, so a caller that makes the gem
|
|
85
|
+
# optional still rescues it, with the packages to install appended.
|
|
86
|
+
module Lib
|
|
87
|
+
extend FFI::Library
|
|
88
|
+
|
|
89
|
+
begin
|
|
90
|
+
ffi_lib 'libvoikko.so.1'
|
|
91
|
+
rescue LoadError => e
|
|
92
|
+
raise LoadError, "#{e.message}\nlibvoikko is not installed: apt install libvoikko1 voikko-fi"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
attach_function :voikkoGetVersion, [], :string
|
|
96
|
+
attach_function :voikkoInit, %i[pointer string string], :pointer
|
|
97
|
+
attach_function :voikkoTerminate, [:pointer], :void
|
|
98
|
+
attach_function :voikkoAnalyzeWordCstr, %i[pointer string], :pointer
|
|
99
|
+
attach_function :voikko_free_mor_analysis, [:pointer], :void
|
|
100
|
+
attach_function :voikko_mor_analysis_keys, [:pointer], :pointer
|
|
101
|
+
attach_function :voikko_mor_analysis_value_cstr, %i[pointer pointer], :pointer
|
|
102
|
+
attach_function :voikko_free_mor_analysis_value_cstr, [:pointer], :void
|
|
103
|
+
end
|
|
104
|
+
private_constant :Lib
|
|
105
|
+
|
|
106
|
+
# @return [String] the system libvoikko's version: +"4.3.3"+.
|
|
107
|
+
def self.library_version = Lib.voikkoGetVersion
|
|
108
|
+
|
|
109
|
+
# Opens a handle, yields it and closes it after the block, the way +File.open+ sits beside
|
|
110
|
+
# +File.new+: the block form is where {#close} runs without the caller remembering it.
|
|
111
|
+
# Without a block it is {#initialize}.
|
|
112
|
+
#
|
|
113
|
+
# Voikko.open(language: 'fi') { |voikko| voikko.base_forms('kadulla') } #=> ["katu"]
|
|
114
|
+
#
|
|
115
|
+
# @param language [String] (see #initialize)
|
|
116
|
+
# @param path [String, nil] (see #initialize)
|
|
117
|
+
# @yieldparam voikko [Voikko] the open handle.
|
|
118
|
+
# @return [Object, Voikko] the block's value, or the open handle when no block is given.
|
|
119
|
+
# @raise [TypeError] (see #initialize)
|
|
120
|
+
# @raise [Error] (see #initialize)
|
|
121
|
+
def self.open(language:, path: nil)
|
|
122
|
+
voikko = new(language:, path:)
|
|
123
|
+
return voikko unless block_given?
|
|
124
|
+
|
|
125
|
+
begin
|
|
126
|
+
yield voikko
|
|
127
|
+
ensure
|
|
128
|
+
voikko.close
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Required although +voikko-fi+ is the only dictionary anyone installs: libvoikko serves other
|
|
133
|
+
# languages, and the keyword costs one word at the single call site.
|
|
134
|
+
#
|
|
135
|
+
# @param language [String] a language tag: +'fi'+, +'fi-FI'+ or +'fi-x-standard'+.
|
|
136
|
+
# @param path [String, nil] an extra directory to search for dictionaries, besides the system
|
|
137
|
+
# ones; a directory that does not exist is ignored.
|
|
138
|
+
# @raise [TypeError] when +language+ is not a String (+:fi+ is not), or +path+ neither a String
|
|
139
|
+
# nor +nil+.
|
|
140
|
+
# @raise [Error] when no installed dictionary serves +language+; the message names +voikko-fi+.
|
|
141
|
+
def initialize(language:, path: nil)
|
|
142
|
+
unless language.is_a?(String)
|
|
143
|
+
raise TypeError, "language must be a String like 'fi', got #{language.inspect}"
|
|
144
|
+
end
|
|
145
|
+
unless path.nil? || path.is_a?(String)
|
|
146
|
+
raise TypeError, "path must be a String or nil, got #{path.inspect}"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
error = FFI::MemoryPointer.new(:pointer)
|
|
150
|
+
handle = Lib.voikkoInit(error, language, path)
|
|
151
|
+
if handle.null?
|
|
152
|
+
reason = error.read_pointer
|
|
153
|
+
reason = reason.null? ? 'unknown error' : reason.read_string
|
|
154
|
+
raise Error, "libvoikko has no dictionary for language #{language.inspect}: #{reason}. " \
|
|
155
|
+
'The Finnish one is the voikko-fi package: apt install voikko-fi'
|
|
156
|
+
end
|
|
157
|
+
@handle = FFI::AutoPointer.new(handle, Lib.method(:voikkoTerminate))
|
|
158
|
+
@mutex = Mutex.new
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Every reading libvoikko finds for +word+, in its order. Case is ignored, and one word often
|
|
162
|
+
# has several readings:
|
|
163
|
+
#
|
|
164
|
+
# voikko.analyze('kadulla').map(&:base_form) #=> ["katu"]
|
|
165
|
+
# voikko.analyze('voi').size #=> 5
|
|
166
|
+
# voikko.analyze('xyzzy') #=> []
|
|
167
|
+
#
|
|
168
|
+
# An empty word, or one over 255 bytes, is no word and gets +[]+ too. The word is not
|
|
169
|
+
# Unicode-normalised: NFD input gets +[]+.
|
|
170
|
+
#
|
|
171
|
+
# @param word [String] one word, no surrounding whitespace; transcoded to UTF-8 if need be.
|
|
172
|
+
# @return [Array<Analysis>] the readings; +[]+ for an unknown word.
|
|
173
|
+
# @raise [ArgumentError] when +word+ holds a NUL byte, which libvoikko would silently cut the
|
|
174
|
+
# word at, or cannot be read as UTF-8.
|
|
175
|
+
# @raise [Error] when this handle is closed.
|
|
176
|
+
def analyze(word)
|
|
177
|
+
word = utf8_word(word)
|
|
178
|
+
with_handle do |handle|
|
|
179
|
+
analyses = Lib.voikkoAnalyzeWordCstr(handle, word)
|
|
180
|
+
next [] if analyses.null?
|
|
181
|
+
|
|
182
|
+
begin
|
|
183
|
+
null_terminated(analyses).map { |analysis| Analysis.new(attributes_of(analysis)) }
|
|
184
|
+
ensure
|
|
185
|
+
Lib.voikko_free_mor_analysis(analyses)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# The dictionary forms of +word+, one per distinct {Analysis#base_form}, in libvoikko's order.
|
|
191
|
+
#
|
|
192
|
+
# voikko.base_forms('voi') #=> ["voida", "voi"]
|
|
193
|
+
# voikko.base_forms('HELSINGISSÄ') #=> ["Helsinki"]
|
|
194
|
+
#
|
|
195
|
+
# Keeps the dictionary's case: downcasing is a stemming policy, and the caller's.
|
|
196
|
+
#
|
|
197
|
+
# @param word [String] (see #analyze)
|
|
198
|
+
# @return [Array<String>] the base forms; +[]+ for an unknown word.
|
|
199
|
+
# @raise [ArgumentError] (see #analyze)
|
|
200
|
+
# @raise [Error] (see #analyze)
|
|
201
|
+
def base_forms(word) = analyze(word).filter_map(&:base_form).uniq
|
|
202
|
+
|
|
203
|
+
# Frees the handle's native memory now rather than at GC. Idempotent. Named after Ruby's
|
|
204
|
+
# +IO#close+, not libvoikko's +voikkoTerminate+, so it reads like every other resource.
|
|
205
|
+
#
|
|
206
|
+
# @return [void]
|
|
207
|
+
def close
|
|
208
|
+
@mutex.synchronize do
|
|
209
|
+
@handle&.free
|
|
210
|
+
@handle = nil
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# @return [Boolean] whether {#close} has run.
|
|
215
|
+
def closed? = @mutex.synchronize { @handle.nil? }
|
|
216
|
+
|
|
217
|
+
private
|
|
218
|
+
|
|
219
|
+
def with_handle
|
|
220
|
+
@mutex.synchronize do
|
|
221
|
+
raise Error, 'this Voikko is closed' unless @handle
|
|
222
|
+
|
|
223
|
+
yield @handle
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def utf8_word(word)
|
|
228
|
+
begin
|
|
229
|
+
utf8 = word.encode(Encoding::UTF_8)
|
|
230
|
+
rescue EncodingError => e
|
|
231
|
+
raise ArgumentError, "word cannot be read as UTF-8: #{e.message}"
|
|
232
|
+
end
|
|
233
|
+
raise ArgumentError, "word is not valid UTF-8: #{word.inspect}" unless utf8.valid_encoding?
|
|
234
|
+
raise ArgumentError, "word holds a NUL byte: #{word.inspect}" if utf8.include?("\0")
|
|
235
|
+
|
|
236
|
+
utf8
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# @param array [FFI::Pointer] a NULL-terminated C array of pointers.
|
|
240
|
+
# @return [Array<FFI::Pointer>] its elements, the NULL excluded.
|
|
241
|
+
def null_terminated(array)
|
|
242
|
+
elements = []
|
|
243
|
+
offset = 0
|
|
244
|
+
until (element = array.get_pointer(offset)).null?
|
|
245
|
+
elements << element
|
|
246
|
+
offset += FFI::Pointer.size
|
|
247
|
+
end
|
|
248
|
+
elements
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Copies one analysis into Ruby strings, so that nothing outlives the C array freed after it.
|
|
252
|
+
#
|
|
253
|
+
# @param analysis [FFI::Pointer] a +voikko_mor_analysis+.
|
|
254
|
+
# @return [Hash{String => String}] its attributes, frozen.
|
|
255
|
+
def attributes_of(analysis)
|
|
256
|
+
null_terminated(Lib.voikko_mor_analysis_keys(analysis)).filter_map do |key|
|
|
257
|
+
value = Lib.voikko_mor_analysis_value_cstr(analysis, key)
|
|
258
|
+
next if value.null?
|
|
259
|
+
|
|
260
|
+
begin
|
|
261
|
+
[utf8_string(key), utf8_string(value)]
|
|
262
|
+
ensure
|
|
263
|
+
Lib.voikko_free_mor_analysis_value_cstr(value)
|
|
264
|
+
end
|
|
265
|
+
end.to_h.freeze
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def utf8_string(pointer) = pointer.read_string.force_encoding(Encoding::UTF_8).freeze
|
|
269
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: voikko
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Martin Vysny
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: ffi
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.17'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.17'
|
|
26
|
+
email:
|
|
27
|
+
- martin@vysny.me
|
|
28
|
+
executables: []
|
|
29
|
+
extensions: []
|
|
30
|
+
extra_rdoc_files: []
|
|
31
|
+
files:
|
|
32
|
+
- CHANGELOG.md
|
|
33
|
+
- LICENSE
|
|
34
|
+
- README.md
|
|
35
|
+
- lib/voikko.rb
|
|
36
|
+
- lib/voikko/version.rb
|
|
37
|
+
homepage: https://github.com/mvysny/ruby-voikko
|
|
38
|
+
licenses:
|
|
39
|
+
- MIT
|
|
40
|
+
metadata:
|
|
41
|
+
source_code_uri: https://github.com/mvysny/ruby-voikko
|
|
42
|
+
changelog_uri: https://github.com/mvysny/ruby-voikko/blob/master/CHANGELOG.md
|
|
43
|
+
bug_tracker_uri: https://github.com/mvysny/ruby-voikko/issues
|
|
44
|
+
documentation_uri: https://rubydoc.info/gems/voikko
|
|
45
|
+
rubygems_mfa_required: 'true'
|
|
46
|
+
rdoc_options: []
|
|
47
|
+
require_paths:
|
|
48
|
+
- lib
|
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.3'
|
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '0'
|
|
59
|
+
requirements:
|
|
60
|
+
- 'libvoikko1 and voikko-fi from your distro: apt install libvoikko1 voikko-fi'
|
|
61
|
+
rubygems_version: 3.6.7
|
|
62
|
+
specification_version: 4
|
|
63
|
+
summary: Ruby bindings for libvoikko, the Finnish morphological analyser, via FFI.
|
|
64
|
+
test_files: []
|