dbf2/recomb.rb

#!/usr/local/bin/ruby -Ke
#
# $Id: recomb.rb 378 2006-02-13 00:39:25Z aamine $
#
# 入力dbfファイルから、2つのデフォルトフィールド(日時と雨量)の組を
# 抜き出して指定したフィールドを加え、それらをレコードとするdbfファ
# イルを出力する
# 出力フィールドは、入力リストの先頭ファイルの同名フィールドの定義
# とする。文字型フィールドを出力指定すると、そのいずれか1つでも値
# のないレコードに対しては、レコードを出力しない
# 入力は、コマンドラインで指定したリストファイルを順に読込む。
# 出力は、コマンドラインで指定した出力ファイルに続けて書き込む。
#
# Example:
# ./recomb.rb -f pntid,name,area -o output.dbf input1.dbf input2.dbf ...
#

require 'dbf'
require 'optparse'

def main
  outfile = nil
  addfields = []
  parser = OptionParser.new
  parser.banner = "Usage: #{$0} [-f NAME,NAME...] -o PATH PATH..."
  parser.on('-f', '--fields=NAME,NAME', 'Adding field names.') {|names|
    addfields = names.split(',')
  }
  parser.on('-o', '--output=PATH', 'Name of output file.') {|path|
    outfile = path
  }
  parser.on('--help', 'Prints this message and quit.') {
    puts parser.help
    exit 0
  }
  def parser.error(msg = nil)
    $stderr.puts msg if msg
    $stderr.puts help()
    exit 1
  end
  begin
    parser.parse!
  rescue OptionParser::ParseError => err
    parser.error err.message
  end
  parser.error 'no output file' unless outfile
  parser.error 'no input file' if ARGV.empty?
  _main outfile, ARGV, addfields
end

def _main(outfile, infiles, addfields)
  schema_initialized = false
  DBF::RecordSet.open(outfile, 'c') {|dbout|
    infiles.each do |path|
      DBF::RecordSet.open(path, 'r') {|dbin|
        unless schema_initialized
          init_schema dbout, dbin, addfields
          schema_initialized = true
        end
        rebuild dbout, dbin, addfields
      }
    end
  }
end

RAINFALL_DATA_SPEC = [10, 4]

def init_schema(dbout, dbin, addfields)
  dbout.add_string_field 'datetime', DATETIME_WIDTH
  dbout.add_numeric_field 'rainfall', *RAINFALL_DATA_SPEC
  addfields.each do |name|
    dbout.add_field dbin.field(name).dup
  end
end

def rebuild(dbout, dbin, addfields)
  rfrecs = rainfall_records(dbin)
  dbin.each_record do |rec|
    next unless valid_record?(rec, addfields)
    rfrecs.each do |datetime, name|
      dbout.append {|r|
        r.datetime = datetime
        r.rainfall = rec[name]
        addfields.each do |n|
          r[n] = rec[n]
        end
      }
    end
  end
end

# ALL needed fields must contain non-space chars.
def valid_record?(record, needed_fields)
  needed_fields.map {|name| record.field(name) }\
      .all? {|f| not (f.string_field? and f.value.gsub(/ /, "").empty?) }
end

def rainfall_records(dbin)
  dbin.fields\
      .select {|field| rainfall_field?(field.name) }\
      .map {|field| [extract_datetime(field.name), field.name] }
end

# Format of rainfall field  e.g. T039250030
RAINFALL_FIELD_RE = /\AT(\d\d)([\da-f])(\d\d)(\d\d)(\d0)\z/

def rainfall_field?(name)
  RAINFALL_FIELD_RE.match(name)
end

DATETIME_WIDTH = 20

# rainfall field name -> datetime string
def extract_datetime(fieldname)
  m = RAINFALL_FIELD_RE.match(fieldname)
  year = m[1]; month = m[2].hex; date = m[3]
  hour = m[4]; minute = m[5]
  "20#{year}/#{month}/#{date} #{hour}:#{minute}:00"
end

main