1#!/usr/bin/env ruby
2#
3# Copyright(C) 2006 Brazil
4#     All rights reserved.
5#     This is free software with ABSOLUTELY NO WARRANTY.
6#
7# You can redistribute it and/or modify it under the terms of
8# the GNU General Public License version 2.
9
10require 'senna_api'
11# todo: load 'senna/senna_api_dl' if 'senna_api' is unavailable
12
13module Senna
14  INDEX_NORMALIZE               =      0x0001
15  INDEX_SPLIT_ALPHA             =      0x0002
16  INDEX_SPLIT_DIGIT             =      0x0004
17  INDEX_SPLIT_SYMBOL            =      0x0008
18  INDEX_MORPH_ANALYSE           =      0x0000
19  INDEX_NGRAM                   =      0x0010
20  INDEX_DELIMITED               =      0x0020
21  INDEX_ENABLE_SUFFIX_SEARCH    =      0x0100
22  INDEX_DISABLE_SUFFIX_SEARCH   =      0x0200
23  INDEX_WITH_VACUUM             =      0x8000
24
25  SYM_MAX_KEY_SIZE              =      0x2000
26  SYM_WITH_SIS                  =  0x80000000
27
28  SNIP_NORMALIZE                =      0x0001
29  SNIP_COPY_TAG                 =      0x0002
30  SNIP_SKIP_LEADING_SPACES      =      0x0004
31  SNIP_MAP_HTMLENCODE           =          -1
32
33  RC_SUCCESS                    =      0x0000
34  RC_MEMORY_EXHAUSTED           =      0x0001
35  RC_INVALID_FORMAT             =      0x0002
36  RC_FILE_OPERATION_ERROR       =      0x0003
37  RC_INVALID_ARGUMENT           =      0x0004
38  RC_OTHER_ERROR                =      0x0005
39
40  ENC_DEFAULT                   =      0x0000
41  ENC_NONE                      =      0x0001
42  ENC_EUC_JP                    =      0x0002
43  ENC_UTF8                      =      0x0003
44  ENC_SJIS                      =      0x0004
45
46  REC_DOCUMENT                  =      0x0000
47  REC_SECTION                   =      0x0001
48  REC_POSITION                  =      0x0002
49  REC_USERDEF                   =      0x0003
50  REC_NONE                      =      0x0004
51
52  SEL_OR                        =      0x0000
53  SEL_AND                       =      0x0001
54  SEL_BUT                       =      0x0002
55  SEL_ADJUST                    =      0x0003
56  SEL_OPERATOR                  =      0x0004
57
58  SEL_EXACT                     =      0x0000
59  SEL_PARTIAL                   =      0x0001
60  SEL_UNSPLIT                   =      0x0002
61  SEL_NEAR                      =      0x0003
62  SEL_NEAR2                     =      0x0004
63  SEL_SIMILAR                   =      0x0005
64  SEL_TERM_EXTRACT              =      0x0006
65  SEL_PREFIX                    =      0x0007
66  SEL_SUFFIX                    =      0x0008
67
68  SORT_DESCENDING               =      0x0000
69  SORT_ASCENDING                =      0x0001
70
71  LOG_NONE                      =      0x0000
72  LOG_EMERG                     =      0x0001
73  LOG_ALERT                     =      0x0002
74  LOG_CRIT                      =      0x0003
75  LOG_ERROR                     =      0x0004
76  LOG_WARNING                   =      0x0005
77  LOG_NOTICE                    =      0x0006
78  LOG_INFO                      =      0x0007
79  LOG_DEBUG                     =      0x0008
80  LOG_DUMP                      =      0x0009
81
82  LOG_TIME                      =      0x0001
83  LOG_TITLE                     =      0x0002
84  LOG_MESSAGE                   =      0x0004
85  LOG_LOCATION                  =      0x0008
86
87  DEFAULT_PORT                  =      10041
88
89  CTX_MORE                      =      0x0001
90  CTX_TAIL                      =      0x0002
91  CTX_HEAD                      =      0x0004
92  CTX_QUIET                     =      0x0008
93  CTX_QUIT                      =      0x0010
94
95  STR_REMOVEBLANK               =      0x0001
96  STR_WITH_CTYPES               =      0x0002
97  STR_WITH_CHECKS               =      0x0004
98
99  class Index
100    attr_reader :handle
101    attr_reader :key_size, :flags, :initial_n_segments, :encoding
102    attr_reader :nrecords_keys, :file_size_keys
103    attr_reader :nrecords_lexicon, :file_size_lexicon
104    attr_reader :inv_seg_size, :inv_chunk_size
105
106    def Index.callback(handle)
107      proc { Senna::sen_index_close(handle) }
108    end
109
110    def Index::create(path, key_size=0, flags=0, initial_n_segments=0, encoding=ENC_DEFAULT)
111      handle = Senna::sen_index_create(path, key_size, flags, initial_n_segments, encoding)
112      return new(handle)
113    end
114
115    def Index::open(path)
116      handle = Senna::sen_index_open(path)
117      return new(handle)
118    end
119
120    def initialize(handle)
121      @handle = handle
122      ObjectSpace.define_finalizer(self, Index.callback(handle))
123    end
124
125    def upd(id, oldvalue, newvalue)
126      id = [id].pack('L') if Fixnum === id
127      oldvalue ||= ''
128      newvalue ||= ''
129      Senna::sen_index_upd(@handle, id, oldvalue, oldvalue.length,
130                           newvalue, newvalue.length)
131    end
132
133    def update(id, section, oldvalue, newvalue)
134      # section must be >= 1
135      id = [id].pack('L') if Fixnum === id
136      oldvalue = Values::open(oldvalue) if String === oldvalue
137      newvalue = Values::open(newvalue) if String === newvalue
138      oldvalue ||= Values::open
139      newvalue ||= Values::open
140      Senna::sen_index_update(@handle, id, section, oldvalue.handle, newvalue.handle)
141    end
142
143    def sel(query)
144      r = Senna::sen_index_sel(@handle, query, query.length)
145      return Records.new(r) if r
146    end
147
148    def select(query, records=nil, op=SEL_OR, optarg=nil)
149      records ||= Records.new
150      Senna::sen_index_select(@handle, query, query.length, records.handle, op, optarg)
151      return records
152    end
153
154    def query(query, default_op=SEL_OR, max_exprs=32, encoding=ENC_DEFAULT)
155      return unless String === query
156      q = Senna::sen_query_open(query, query.length, default_op, max_exprs, encoding)
157      r = Senna::sen_records_open(REC_DOCUMENT, REC_NONE, 0)
158      Senna::sen_query_exec(@handle, q, r, SEL_OR)
159      Senna::sen_query_close(q)
160      return Records.new(r)
161    end
162
163    def keys
164      Senna::Sym.new(Senna::sen_index_keys(@handle))
165    end
166
167    def lexicon
168      Senna::Sym.new(Senna::sen_index_lexicon(@handle))
169    end
170
171    def inv
172      Senna::sen_index_inv(@handle)
173    end
174
175    def close
176      rc = Senna::sen_index_close(@handle)
177      @handle = nil
178      ObjectSpace.undefine_finalizer(self)
179      return rc
180    end
181
182    def path
183      size, = Senna::sen_index_path(@handle, 1)
184      _,res = Senna::sen_index_path(@handle, size)
185      return res
186    end
187
188    def remove
189      _path = path
190      self.close
191      Senna::sen_index_remove(_path)
192    end
193
194    def rename(newpath)
195      _path = path
196      close
197      Senna::sen_index_rename(_path, newpath)
198      @handle = Senna::sen_index_open(newpath)
199      ObjectSpace.define_finalizer(self, Index.callback(@handle))
200    end
201
202    def inv_check(term)
203      e = InvEntry.new(self, term)
204
205      printf("key:            %10s\n", e.key)
206      printf("term_id:        %10d\n", e.term_id)
207
208      case e.info_rc
209      when 0
210        puts('no entry in array')
211      when 1
212        puts('entry is void')
213      when 2
214        printf("entry:    %10d\n", e.entry)
215      when 3
216        printf("entry:    %10d\n", e.entry)
217        puts('no buffer in inv')
218      when 4
219        printf("entry:          %10d\n", e.entry)
220        printf("pocket:         %10d\n", e.pocket)
221        printf("chunk:          %10d\n", e.chunk)
222        printf("chunk_size:     %10d\n", e.chunk_size)
223        printf("buffer_free:    %10d\n", e.buffer_free)
224        printf("nterms:         %10d\n", e.nterms)
225        printf("nterms_void:    %10d\n", e.nterms_void)
226        printf("tid_in_buffer:  %10d\n", e.tid_in_buffer)
227        printf("size_in_chunk:  %10d\n", e.size_in_chunk)
228        printf("pos_in_chunk:   %10d\n", e.pos_in_chunk)
229        printf("size_in_buffer: %10d\n", e.size_in_buffer)
230        printf("pos_in_buffer:  %10d\n", e.pos_in_buffer)
231      end
232    end
233
234    def key_check(key)
235      case key
236      when String
237        str = key
238        rid = keys.at(key)
239      when Fixnum
240        _,str = keys.key(key)
241        rid = key
242      else
243        return
244      end
245      pocket = keys.pocket(rid)
246      printf("str:            %10s\n", str)
247      printf("rid:            %10d\n", rid)
248      printf("pocket:         %10d\n", pocket)
249    end
250
251    def info
252      info = Senna::sen_index_info(@handle)
253      @key_size, @flags, @initial_n_segments, @encoding,
254        @nrecords_keys, @file_size_keys,
255        @nrecords_lexicon, @file_size_lexicon,
256        @inv_seg_size, @inv_chunk_size = info
257      return info
258    end
259
260    def query_exec(query, records=nil, op=SEL_OR)
261      records ||= Records.new
262      Senna::sen_query_exec(@handle, query.handle, records.handle, op)
263      return records
264    end
265  end
266
267  class InvEntry
268    attr_reader :index, :key, :term_id
269    attr_reader :info_rc, :entry, :pocket, :chunk, :chunk_size, :buffer_free
270    attr_reader :nterms, :nterms_void, :tid_in_buffer, :size_in_chunk
271    attr_reader :pos_in_chunk, :size_in_buffer, :pos_in_buffer
272    def initialize(index, term)
273      @index = index
274      case term
275      when String
276        @key = term
277        @term_id = index.lexicon.at(term)
278      when Fixnum
279        _, @key = index.lexicon.key(term)
280        @term_id = term
281      else
282        return
283      end
284      info = Senna::sen_inv_entry_info(index.inv, @term_id)
285      @info_rc, @entry, @pocket, @chunk, @chunk_size, @buffer_free,
286        @nterms, @nterms_void, @tid_in_buffer, @size_in_chunk,
287        @pos_in_chunk, @size_in_buffer, @pos_in_buffer =
288        Senna::sen_inv_entry_info(index.inv, term_id)
289    end
290  end
291
292  class Sym
293    attr_reader :handle, :path
294    def Sym.callback(handle)
295      proc { Senna::sen_sym_close(handle) }
296    end
297
298    def Sym::create(path, key_size=0, flags=0, encoding=0)
299      handle = Senna::sen_sym_create(path, key_size, flags, encoding)
300      return new(handle, path)
301    end
302
303    def index_create_with_keys(path, flags=0, initial_n_segments=0, encoding=0)
304      handle = Senna::sen_index_create_with_keys(path, @handle, flags, initial_n_segments, encoding)
305      return Senna::Index.new(handle)
306    end
307
308    def index_open_with_keys(path)
309      handle = Senna::sen_index_open_with_keys(path, @handle)
310      return Senna::Index.new(handle)
311    end
312
313    def Sym::open(path)
314      handle = Senna::sen_sym_open(path)
315      return new(handle, path)
316    end
317
318    def initialize(handle, path=nil)
319      @handle = handle
320      @path = path
321      ObjectSpace.define_finalizer(self, Sym.callback(@handle)) if path
322    end
323
324    def close
325      return unless @path
326      rc = Senna::sen_sym_close(@handle)
327      @handle = nil
328      ObjectSpace.undefine_finalizer(self)
329      return rc
330    end
331
332    def remove
333      return unless @path
334      self.close
335      Senna::sen_sym_remove(@path)
336    end
337
338    def info
339      inf = Senna::sen_sym_info(@handle)
340      return {'key_size' => inf[1], 'flags' => inf[2], 'encoding' => inf[3], 'nrecords' => inf[4], 'file_size' => inf[5]}
341    end
342
343    def get(key)
344      Senna::sen_sym_get(@handle, key)
345    end
346
347    def at(key)
348      Senna::sen_sym_at(@handle, key)
349    end
350
351    def del(key)
352      Senna::sen_sym_del(@handle, key)
353    end
354
355    def key(id)
356      Senna::sen_sym_key(@handle, id, SYM_MAX_KEY_SIZE)
357    end
358
359    def pocket_get(id)
360      Senna::sen_sym_pocket_get(@handle, id)
361    end
362
363    def pocket_set(id, value)
364      Senna::sen_sym_pocket_set(@handle, id, value)
365    end
366
367    def size
368      Senna::sen_sym_size(@handle)
369    end
370
371    def next(id)
372      Senna::sen_sym_next(@handle, id)
373    end
374
375    def terms2array(terms)
376      return nil unless terms
377      res = []
378      c = Senna::sen_set_cursor_open(terms)
379      loop {
380        eh,id,_ = Senna::sen_set_cursor_next(c, 4, -1)
381        break unless eh
382        _,str = Senna::sen_sym_key(@handle, id.unpack('L')[0], SYM_MAX_KEY_SIZE)
383        res << str
384      }
385      Senna::sen_set_cursor_close(c)
386      res
387    end
388
389    def prefix_search(key)
390      terms2array(Senna::sen_sym_prefix_search(@handle, key))
391    end
392
393    def suffix_search(key)
394      terms2array(Senna::sen_sym_suffix_search(@handle, key))
395    end
396
397    def common_prefix_search(key)
398      id = Senna::sen_sym_common_prefix_search(@handle, key)
399      _,str = Senna::sen_sym_key(@handle, id, SYM_MAX_KEY_SIZE)
400      return str
401    end
402  end
403
404  class Element
405    attr_reader :phandle, :handle, :key, :value
406    def initialize(phandle, handle)
407      @phandle = phandle
408      @handle = handle
409      @key, @value = Senna::sen_record_info(@phandle, @handle)
410    end
411  end
412
413  class Set
414    attr_reader :handle
415    def Set.callback(handle)
416      proc { Senna::sen_set_close(handle) }
417    end
418
419    def Set::open(keysize, value_size, index_size)
420      handle = Senna::sen_set_open(keysize, value_size, index_size);
421      return new(handle)
422    end
423
424    def initialize(handle, closable=true)
425      @handle = handle
426      @closable = closable
427      ObjectSpace.define_finalizer(self, Set.callback(handle)) if closable
428    end
429
430    def close
431      return unless @closable
432      rc = Senna::sen_set_close(@handle)
433      @handle = nil
434      ObjectSpace.undefine_finalizer(self)
435      return rc
436    end
437
438    def info
439      inf = Senna::sen_set_info(@handle)
440      return { 'key_size' => inf[0], 'value_size' => inf[1], 'n_entries' => inf[2] }
441    end
442
443    def []=(key, value)
444      return get(key, value)
445    end
446
447    def get(key, value)
448      eh, _ = Senna::sen_set_get(@handle, key, value)
449      return eh
450    end
451
452    def [](key)
453      _, value = Senna::sen_set_at(@handle, key)
454      return value
455    end
456
457    def at(key)
458      eh, _ = Senna::sen_set_at(@handle, key)
459      return eh
460    end
461
462    def del(eh)
463      Senna::sen_set_del(@handle, eh)
464    end
465
466    def cursor_open
467      Senna::sen_set_cursor_open(@handle)
468    end
469
470    def cursor_next(cursor)
471      eh, _, _ = Senna::sen_set_cursor_next(cursor)
472      return eh
473    end
474
475    def cursor_close(cursor)
476      Senna::sen_set_cursor_close(cursor)
477    end
478
479    def element_info(eh, key=0, value=0)
480      Senna::sen_set_element_info(@handle, eh, key, value)
481    end
482
483    def +(obj)
484      union(obj)
485    end
486
487    def union(obj)
488      Senna::sen_set_union(@handle, obj)
489    end
490
491    def -(obj)
492      subtract(obj)
493    end
494
495    def subtract(obj)
496      Senna::sen_set_subtract(@handle, obj)
497    end
498
499    def *(obj)
500      intersect(obj)
501    end
502
503    def intersect(obj)
504      Senna::sen_set_intersect(@handle, obj)
505    end
506
507    def difference(obj)
508      Senna::sen_set_difference(@handle, obj)
509    end
510
511    def sort(limit=0, optarg=nil)
512      Senna::sen_set_sort(@handle, limit, optarg)
513    end
514  end
515
516  class Records
517    attr_reader :handle
518    def Records.callback(handle)
519      proc { Senna::sen_records_close(handle) }
520    end
521
522    def Records::open(record_unit=REC_DOCUMENT, subrec_unit=REC_NONE, max_n_subrecs=0)
523      handle = Senna::sen_records_open(record_unit, subrec_unit, max_n_subrecs)
524      return new(handle)
525    end
526
527    def initialize(handle=nil, closable=true)
528      handle ||= Senna::sen_records_open(REC_DOCUMENT, REC_NONE, 0)
529      @handle = handle
530      @closable = closable
531      ObjectSpace.define_finalizer(self, Records.callback(handle)) if closable
532    end
533
534    def nhits
535      Senna::sen_records_nhits(@handle)
536    end
537
538    def each
539      while rec = self.next do
540        yield self.curr_key, self.curr_score
541      end
542    end
543
544    def find(key)
545      key = [key].pack('L') if Fixnum === key
546      Senna::sen_records_find(@handle, key)
547    end
548
549    def rewind
550      Senna::sen_records_rewind(@handle)
551    end
552
553    def next
554      size,_,score = Senna::sen_records_next(@handle, 0)
555      if size > 0
556        _, res = Senna::sen_records_curr_key(@handle, size)
557      else
558        res = nil
559      end
560      return res
561    end
562
563    def curr_rec(bufsize=0)
564      rh = Senna::sen_records_curr_rec(@handle)
565      return Record.new(@handle, rh, bufsize)
566    end
567
568    def at(key, section, pos, bufsize=0)
569      rh, _, _ = Senna::sen_records_at(@handle, key, section, pos)
570      return Record.new(@handle, rh, bufsize)
571    end
572
573    def curr_key
574      size, res = Senna::sen_records_curr_key(@handle, 128)
575      return res if size <= 128
576      _, res = Senna::sen_records_curr_key(@handle, size)
577      return res
578    end
579
580    def curr_score
581      return Senna::sen_records_curr_score(@handle)
582    end
583
584    def sort(limit, opt=nil)
585      return Senna::sen_records_sort(@handle, limit, opt)
586    end
587
588    def group(limit, opt=nil)
589      return Senna::sen_records_group(@handle, limit, opt)
590    end
591
592    def close
593      return unless @closable
594      rc = Senna::sen_records_close(@handle)
595      @handle = nil
596      self.invalidation
597      return rc
598    end
599
600    def invalidation
601      @handle = nil
602      ObjectSpace.undefine_finalizer(self)
603    end
604
605    def records_restruct(subj, newhandle)
606      self.invalidation
607      subj.invalidation
608      @handle = newhandle
609      ObjectSpace.define_finalizer(self, Records.callback(handle))
610    end
611
612    def union(subj)
613      newhandle = Senna::sen_records_union(@handle, subj.handle)
614      self.records_restruct(subj, newhandle)
615    end
616
617    def subtract(subj)
618      newhandle = Senna::sen_records_subtract(@handle, subj.handle)
619      self.records_restruct(subj, newhandle)
620    end
621
622    def intersect(subj)
623      newhandle = Senna::sen_records_intersect(@handle, subj.handle)
624      self.records_restruct(subj, newhandle)
625    end
626
627    def difference(subj)
628      Senna::sen_records_difference(@handle, subj.handle)
629    end
630
631    def sort(limit, optarg=nil)
632      Senna::sen_records_sort(@handle, limit, optarg)
633    end
634
635    def group(limit, optarg=nil)
636      Senna::sen_records_group(@handle, limit, optarg)
637    end
638  end
639
640  class Record
641    attr_reader :phandle, :handle, :key, :keysize, :section, :pos, :score, :n_subrecs
642    def initialize(phandle, handle, bufsize=0)
643      @phandle = phandle
644      @handle = handle
645      rc, @key, @keysize, @section, @pos, @score, @n_subrecs =
646        Senna::sen_record_info(@phandle, @handle, bufsize)
647    end
648
649    def subrec(index, bufsize=0)
650      return SubRecord.new(@phandle, @handle, index, bufsize)
651    end
652  end
653
654  class SubRecord
655    attr_reader :phandle, :handle, :index, :key, :keysize, :section, :pos, :score
656    def initialize(phandle, handle, index, bufsize=0)
657      @phandle = phandle
658      @handle = handle
659      @index = index
660      rc, @key, @keysize, @section, @pos, @score =
661        Senna::sen_record_subrec_info(@phandle, @handle, index, bufsize)
662    end
663  end
664
665  class Values
666    attr_reader :handle
667    def Values.callback(handle)
668      proc { Senna::sen_values_close(handle) }
669    end
670
671    def Values::open(str=nil, weight=0)
672      handle = Senna::sen_values_open
673      ret = new(handle)
674      ret.add(str, weight) unless str.nil?
675      return ret
676    end
677
678    def add(str, weight=0)
679      Senna::sen_values_add(@handle, str, str.length, weight)
680    end
681
682    def initialize(handle)
683      @handle = handle
684      ObjectSpace.define_finalizer(self, Values.callback(@handle))
685    end
686
687    def close
688      rc = Senna::sen_values_close(@handle)
689      @handle = nil
690      ObjectSpace.undefine_finalizer(self)
691      return rc
692    end
693  end
694
695  class Query
696    attr_reader :handle
697    def Query.callback(handle)
698      proc { Senna::sen_query_close(handle) }
699    end
700
701    def Query::open(str, default_op=SEL_OR, max_exprs=32, encoding=ENC_DEFAULT)
702      handle = Senna::sen_query_open(str, str.length, default_op, max_exprs, encoding)
703      return new(handle)
704    end
705
706    def initialize(handle)
707      @handle = handle
708      ObjectSpace.define_finalizer(self, Query.callback(@handle))
709    end
710
711    def close
712      rc = Senna::sen_query_close(@handle)
713      @handle = nil
714      ObjectSpace.undefine_finalizer(self)
715      return rc
716    end
717
718    def rest
719      len, _ = Senna::sen_query_rest(@handle, 0)
720      return Senna::sen_query_rest(@handle, len)[1]
721    end
722
723    def exec(index, records=nil, op=SEL_OR)
724      records ||= Records.new
725      Senna::sen_query_exec(index.handle, @handle, records.handle, op)
726      return records
727    end
728
729    def term
730      # TODO: implement!
731      raise NameError, 'Query#term is not implemented'
732    end
733
734    def scan(strs, flags)
735      # TODO: implement!
736      # [found, score]
737      raise NameError, 'Query#scan is not implemented'
738    end
739
740    def snip(flags, width, max_results, tags)
741      # TODO: implement!
742      # Snip.new(handle)
743      raise NameError, 'Query#snip is not implemented'
744    end
745  end
746
747  class Snip
748    def Snip.callback(handle)
749      proc { Senna::sen_snip_close(handle) }
750    end
751
752    def Snip::open(conds=nil, width=100, max_results=3, opentag='', closetag='', map=nil, enc=nil, flags=SNIP_NORMALIZE)
753      enc ||= [nil, 'NONE', 'EUC', 'UTF8', 'SJIS'].index($KCODE) || 0
754      case conds
755      when Array
756        condkeys = conds
757        condvals = Hash.new([nil, nil])
758      when Hash
759        condkeys = conds.keys
760        condvals = conds
761      when String
762        condkeys = [conds]
763        condvals = { conds => [nil, nil] }
764      else
765        condkeys = []
766      end
767      handle = Senna::sen_snip_open(enc, flags, width, max_results,
768                                    opentag, opentag.length,
769                                    closetag, closetag.length,
770                                    map)
771      condkeys.each {|c|
772        opentag_len = condvals[c][0].nil? ? 0 : condvals[c][0].length
773        closetag_len = condvals[c][1].nil? ? 0 : condvals[c][1].length
774        Senna::sen_snip_add_cond(handle, c, c.length,
775                                 condvals[c][0], opentag_len,
776                                 condvals[c][1], closetag_len)
777      }
778      return Snip.new(handle)
779    end
780
781    def initialize(handle)
782      @handle = handle
783      ObjectSpace.define_finalizer(self, Snip.callback(@handle))
784    end
785
786    def add(cond, opentag=nil, closetag=nil)
787      opentag_len = opentag.nil? ? 0 : opentag.length
788      closetag_len = closetag.nil? ? 0 : closetag.length
789      Senna::sen_snip_add_cond(@handle, cond, cond.length,
790                               opentag, opentag_len,
791                               closetag, closetag_len)
792    end
793
794    def exec(str)
795      rc, n, maxsize = Senna::sen_snip_exec(@handle, str, str.length)
796      res = []
797      for i in 0..n-1
798        rc, r = Senna::sen_snip_get_result(@handle, i, maxsize)
799        res << r if rc == 0
800      end
801      return res
802    end
803
804    def close
805      rc = Senna::sen_snip_close(@handle)
806      @handle = nil
807      ObjectSpace.undefine_finalizer(self)
808      return rc
809    end
810  end
811
812  class Ctx
813    def Ctx::connect(host='localhost', port=DEFAULT_PORT)
814      handle = Senna::sen_ctx_connect(host, port, 0)
815      return new(handle, nil)
816    end
817
818    def Ctx::open(path)
819      db = Senna::sen_db_open(path)
820      raise "sen_db_open(#{path}) failed" unless db
821      handle = Senna::sen_ctx_open(db, 1)
822      return new(handle, db)
823    end
824
825    def Ctx::create(path, flags=0, encoding=ENC_DEFAULT)
826      db = Senna::sen_db_create(path, flags, encoding)
827      raise "sen_db_create(#{path}) failed" unless db
828      handle = Senna::sen_ctx_open(db, 1)
829      return new(handle, db)
830    end
831
832    def Ctx.callback(handle, db)
833      proc {
834        Senna::sen_ctx_close(handle)
835        Senna::sen_db_close(db) if db
836      }
837    end
838
839    def initialize(handle, db)
840      @handle = handle
841      @db = db
842      ObjectSpace.define_finalizer(self, Ctx.callback(@handle, @db))
843    end
844
845    def send(message)
846      return Senna::sen_ctx_send(@handle, message, message.length, 0)
847    end
848
849    def recv()
850      return Senna::sen_ctx_recv(@handle)
851    end
852
853    def exec(*messages)
854      res = nil
855      messages.each {|message|
856        rc = send(message)
857        raise IOError, 'send error' if rc != RC_SUCCESS
858        res = []
859        loop {
860          rc, value, flags = recv
861          raise IOError, 'recv error' if rc != RC_SUCCESS
862          res << value
863          break if (flags & CTX_MORE) == 0
864        }
865      }
866      res
867    end
868
869  end
870
871  def Senna::get_select_optarg(mode, similarity_threshold_max_interval=0, weight_vector=nil, &func)
872    i = Senna::SelectOptarg.new
873    i.mode = mode
874    case mode
875    when SEL_NEAR
876      i.max_interval = similarity_threshold_max_interval
877    when SEL_SIMILAR
878      i.similarity_threshold = similarity_threshold_max_interval
879    end
880    i.weight_vector = weight_vector
881    i.func = proc {|records, docid, secno, func_arg| r = Records.new(records, false); return func.call(r, docid, secno) || 0} if func
882    return i
883  end
884
885  def Senna::get_group_optarg(mode, key_size, &func)
886    i = Senna::GroupOptarg.new
887    i.mode = mode
888    i.func = proc {|records, rh, func_arg| r = Record.new(records, rh, key_size); return func.call(r) || 0} if func
889    i.key_size = key_size
890    return i
891  end
892
893  def Senna::get_sort_optarg(mode, &compar)
894    i = Senna::SortOptarg.new
895    i.mode = mode
896    i.compar = proc {|records1, rh1, records2, rh2, compar_arg|
897      r1 = Record.new(records1, rh1);
898      r2 = Record.new(records2, rh2);
899      return compar.call(r1, r2) || 0} if compar
900    return i
901  end
902
903  def Senna::get_set_sort_optarg(mode, &compar)
904    i = Senna::SetSortOptarg.new
905    i.mode = mode
906    i.compar = proc {|set1, eh1, set2, eh2, compar_arg|
907      s1 = Set.new(set1, false);
908      s2 = Set.new(set2, false);
909      return compar.call(s1, eh1, s2, eh2) || 0} if compar
910    return i
911  end
912
913  def Senna::logger_info_set(level, flags, &func)
914    @@logger_info = LoggerInfo.new
915    @@logger_info.max_level = level
916    @@logger_info.flags = flags
917    @@logger_info.func = func
918    sen_logger_info_set(@@logger_info)
919  end
920
921  def Senna::log(level, fmt, *args)
922    msg = format(fmt, *args)
923    sen_logger_put(level, '', 0, '', '%s', msg)
924  end
925
926  def Senna::str_normalize(str, encoding, flags)
927    l, = sen_str_normalize(str, str.size, encoding, flags, 0)
928    return sen_str_normalize(str, str.size, encoding, flags, l + 1)[1]
929  end
930end
931