class LibCDB::CDB

See README.

Constants

LIBCDB_VERSION

The TinyCDB library version.

VERSION

Attributes

io[R]

The underlying IO object.

mode[R]

The current IO mode, either r or w.

Public Class Methods

dump(path[, target[, separator]]) → target click to toggle source

Opens path for reading and shovels each record dump into target (followed by separator, if present). Returns target.

# File lib/libcdb.rb, line 106
def dump(path, target = '', separator = $\ || $/)
  open(path) { |cdb|
    cdb.each_dump { |dump|
      target << dump
      target << separator if separator
    }

    target
  }
end
foreach(path) { |key, val| ... } click to toggle source
foreach(path, key) { |val| ... }

Opens path for reading and iterates over each key/value pair, or, if key is given, each value for key.

# File lib/libcdb.rb, line 97
def foreach(path, *key)
  open(path) { |cdb| cdb.each(*key) { |val| yield val } }
end
load(path, dump) → aCDB click to toggle source

Opens path for writing and loads dump into the database. dump may be a string or an IO object. Returns the (unclosed) CDB object.

# File lib/libcdb.rb, line 122
def load(path, dump)
  require 'strscan'

  s, n, e = nil, 0, lambda { |m| s.eos? ?
    raise("Unexpected end of input (#{m} at #{n}).") :
    raise("#{m} at #{n}:#{s.pos}: #{s.peek(16).inspect}") }

  cdb = open(path, 'w+')

  dump.each_line { |line|
    n += 1

    s = StringScanner.new(line)

    e['Record identifier expected'] unless s.scan(/\+/)

    e['Key length expected'] unless s.scan(/\d+/)
    klen = s.matched.to_i

    e['Length separator expected'] unless s.scan(/,/)

    e['Value length expected'] unless s.scan(/\d+/)
    vlen = s.matched.to_i

    e['Key separator expected'] unless s.scan(/:/)

    key = ''
    klen.times { key << s.get_byte }

    e['Value separator expected'] unless s.scan(/->/)

    value = ''
    vlen.times { value << s.get_byte }

    e['Record terminator expected'] unless s.scan(/\n/)
    e['Unexpected data'] unless s.eos?

    cdb.store(key, value)
  }

  cdb
end
load_file(path, file) → aCDB click to toggle source

Loads the dump at file into the database at path (see load).

# File lib/libcdb.rb, line 169
def load_file(path, file)
  File.open(file, 'rb') { |f| self.load(path, f) }
end
new(io[, mode]) → aCDB click to toggle source

Creates a new CDB object to interface with io. mode must be the same mode io was opened in, either r or w. Responds to both Reader and Writer methods interchangeably by reopening io in the corresponding mode and instantiating a new Reader or Writer object with it. Note that io will be truncated each time it's opened for writing.

# File lib/libcdb.rb, line 256
def initialize(io, mode = MODE_WRITE)
  @io, @mode = io, mode

  case mode
    when MODE_READ  then open_read
    when MODE_WRITE then open_write
    else raise ArgumentError, "illegal access mode #{mode.inspect}"
  end
end
open(path[, mode]) → aReader | aWriter | aCDB click to toggle source
open(path[, mode]) { |cdb| ... }

Opens path with mode. If a block is given, yields a cdb object according to mode (see below) and returns the return value of the block; the object is closed afterwards. Otherwise just returns the object.

r

Reader

w

Writer

r+

CDB (initially opened for reading)

w+

CDB (initially opened for writing)

# File lib/libcdb.rb, line 62
def open(path, mode = MODE_READ)
  klass, args = _open_args(path, mode)

  cdb = begin
    klass.new(*args)
  rescue
    args.first.close
    raise
  end

  if block_given?
    begin
      yield cdb
    ensure
      err = $!

      begin
        cdb.close
      rescue
        raise unless err
      end

      raise err if err
    end
  else
    cdb
  end
end
print_stats(path) → aHash click to toggle source

Prints the stats on path.

stats(path) → aHash click to toggle source

Returns a hash with the stats on path.

# File lib/libcdb.rb, line 177
def stats(path)
  {}.tap { |stats| open(path) { |cdb|
    stats[:records] = cnt = cdb.total

    stats[:keys]   = khash = { min: Float::INFINITY, avg: 0, max: 0 }
    stats[:values] = vhash = khash.dup

    stats[:hash] = Hash.new(0).update(distances: Hash.new([0, 0]))

    khash[:min] = vhash[:min] = 0 and break if cnt.zero?

    ktot, vtot, update = 0, 0, lambda { |h, s| s.bytesize.tap { |l|
      h[:min] = l if l < h[:min]
      h[:max] = l if l > h[:max]
    } }

    cdb.each_key   { |k| ktot += update[khash, k] }
    cdb.each_value { |v| vtot += update[vhash, v] }

    khash[:avg] = (ktot + cnt / 2) / cnt
    vhash[:avg] = (vtot + cnt / 2) / cnt

    # TODO: hash table stats
  } }
end

Private Class Methods

_open_args(path, mode) click to toggle source
# File lib/libcdb.rb, line 232
def _open_args(path, mode)
  klass, args = self, []

  case mode
    when 'r+'       then args = [mode = MODE_READ]
    when 'w+'       then args = [       MODE_WRITE]
    when MODE_READ  then klass        = Reader
    when MODE_WRITE then klass,  mode = Writer, 'w+'
    else raise ArgumentError, "illegal access mode #{mode.inspect}"
  end

  [klass, args.unshift(File.open(path, mode + 'b'))]
end

Public Instance Methods

close → nil click to toggle source

Closes both the reader and the writer, as well as io. Doesn't raise an IOError if either of them is already closed.

# File lib/libcdb.rb, line 366
def close
  close_read(false)
  close_write(false)
  io.close unless io.closed?
end
close_read([strict]) → nil click to toggle source

If cdb is currently opened for reading, closes the reader (and io with it). Otherwise, if strict is true, raises an IOError.

# File lib/libcdb.rb, line 338
def close_read(strict = true)
  if read?
    @reader.close
    @reader = nil
  elsif strict
    raise IOError, 'not opened for reading'
  end
end
close_write([strict]) → nil click to toggle source

If cdb is currently opened for writing, closes the writer (and io with it). Otherwise, if strict is true, raises an IOError.

# File lib/libcdb.rb, line 352
def close_write(strict = true)
  if write?
    @writer.close
    @writer = nil
  elsif strict
    raise IOError, 'not opened for writing'
  end
end
closed? → true | false | nil click to toggle source

Whether cdb is closed. See closed_read? and closed_write?.

# File lib/libcdb.rb, line 392
def closed?
  read? ? closed_read? : write? ? closed_write? : nil
end
closed_read? → true | false | nil click to toggle source

Whether reader is closed if cdb is currently opened for reading.

# File lib/libcdb.rb, line 376
def closed_read?
  reader.closed? if read?
end
closed_write? → true | false | nil click to toggle source

Whether writer is closed if cdb is currently opened for writing.

# File lib/libcdb.rb, line 384
def closed_write?
  writer.closed? if write?
end
open_read → aReader click to toggle source

Opens cdb for reading and reopens io accordingly. Closes writer if open.

# File lib/libcdb.rb, line 318
def open_read
  close_write(false)
  @reader = Reader.new(reopen(MODE_READ))
end
open_write → aWriter click to toggle source

Opens cdb for writing and reopens io accordingly. Closes reader if open. Note that io will be truncated.

# File lib/libcdb.rb, line 328
def open_write
  close_read(false)
  @writer = Writer.new(reopen(MODE_WRITE))
end
read? → true | false click to toggle source

Whether cdb is currently opened for reading.

# File lib/libcdb.rb, line 301
def read?
  !!@reader
end
reader → aReader click to toggle source

The Reader object associated with cdb.

# File lib/libcdb.rb, line 276
def reader
  @reader ||= open_read
end
write? → true | false click to toggle source

Whether cdb is currently opened for writing.

# File lib/libcdb.rb, line 309
def write?
  !!@writer
end
writer → aWriter click to toggle source

The Writer object associated with cdb.

# File lib/libcdb.rb, line 284
def writer
  @writer ||= open_write
end

Private Instance Methods

reopen([mode]) → anIO click to toggle source

Reopens io in mode and returns it.

# File lib/libcdb.rb, line 402
def reopen(new_mode = MODE_READ)
  return io if mode == new_mode

  @mode = new_mode
  new_mode += '+' if new_mode == MODE_WRITE

  io.reopen(io.path, new_mode + 'b')
end