StructとYAMLの仲を取り持つHash

StructとYAMLとHashが連携する機会が多いので最も頑張っているHashにヘルパーさんをつけることにした

HashHelperとその働き
# coding: CP932
require "yaml"
require "pp"

module HashHelper

  module ClassMethods

    def new_each_block
      new { |hash, key| hash[key] = self.new_each_block }
    end

  end
  
  module InstanceMethods

    def to_struct
      Struct.new( *self.keys )
            .new( *self.values.map{ |e| Hash === e ? e.to_struct : e } )
    end

  end
      
  def self.included(receiver)
    receiver.extend         ClassMethods
    receiver.send :include, InstanceMethods
  end

end

class Hash
  include HashHelper
end



module YOML

  class << self

    def dump( obj, io=nil )
      sio = StringIO.new
      ( io || sio ) << YAML.dump_stream(obj).gsub(/":?.+"/) { |s| eval(s) }
      io ? io.close : sio.rewind && sio.read
    end

  end

end

if __FILE__ == $0

@dic = Hash.new_each_block
@dic[:EJ][:I][:am][:a][:satisfied][:customer] = "そちらでの買い物に大変満足しています"
@dic[:EJ][:I][:am][:a][:Christian]            = "私はクリスチャンです"
@dic[:EJ][:I][:am][:a][:dancer]               = "【映画】華麗なるダンサー◆英1972"
@dic[:EJ][:I][:got][:burned][:Christian]      = "火傷をした"
@dic[:EJ][:I][:got][:carried][:away]          = %w{調子に乗り過ぎた はしゃぎ過ぎた 悪乗りし過ぎた}
# 日本語メソッドは作れるが呼び出すのは困難
@dic[:JE][:私は][:クリスチャン][:です]        = "I am a Christian"

pp @dic

YOML.dump( @dic, File.open( "dic.yaml", "w" ) )
dic = YAML.load( File.open( "dic.yaml" ) ).to_struct

puts dic.EJ.I.am.a.satisfied.customer
puts dic.EJ.I.am.a.dancer
puts dic.EJ.I.got.carried.away*"/"

end
出力結果(ハッシュ)
{:EJ=>
  {:I=>
    {:am=>
      {:a=>
        {:satisfied=>{:customer=>"そちらでの買い物に大変満足しています"},
         :Christian=>"私はクリスチャンです",
         :dancer=>"【映画】華麗なるダンサー◆英1972"}},
     :got=>
      {:burned=>{:Christian=>"火傷をした"},
       :carried=>{:away=>["調子に乗り過ぎた", "はしゃぎ過ぎた", "悪乗りし過ぎた"]}}}},
 :JE=>{:私は=>{:クリスチャン=>{:です=>"I am a Christian"}}}}
出力結果(ハッシュの値)
そちらでの買い物に大変満足しています
【映画】華麗なるダンサー◆英1972
調子に乗り過ぎた/はしゃぎ過ぎた/悪乗りし過ぎた

YAML.dumpの全角文字バイナリ文字列化ちょい対策



YAMLのエンジンにPsychを利用するという本来の対策ができなかった(libyamlの入れ方がわからない)
Windowsでのlibyaml入れ方教えてー!エライ人!!


と困ったままというわけにはいかないので取りあえずeval使って全角文字に戻した

YOML
# coding: CP932
require "yaml"

module YOML
  class << self
    def dump( obj, io=nil )
      sio = StringIO.new
      ( io || sio ) << YAML.dump_stream(obj).gsub(/":?.+"/) { |s| eval(s) }
      io ? io.close : sio.rewind && sio.read
    end
  end
end


if __FILE__ == $0

months  = {

  OLD:  {

    睦月:         1,
    如月:         2,
    弥生:         3,
    卯月:         4,
    皐月:         5,
    水無月:       6,
    文月:         7,
    葉月:         8,
    長月:         9,
    神無月:       10,
    霜月:         11,
    師走:         12,

  },

  :KANJI  =>  {

    "一月"    =>  1,
    "二月"    =>  2,
    "三月"    =>  3,
    "四月"    =>  4,
    "五月"    =>  5,
    "六月"    =>  6,
    "七月"    =>  7,
    "八月"    =>  8,
    "九月"    =>  9,
    "十月"    =>  10,
    "十一月"  =>  11,
    "十二月"  =>  12,

  },

}

puts YAML.dump( months )
puts YOML.dump( months )
YAML.dump( months, File.open( "months.yaml", "w" ) )
YOML.dump( months, File.open( "months.yoml", "w" ) )

end

evalを使わなくて良い方法も教えてー!エライ人!!

YAML.dump出力
--- 
:OLD: 
  ":\x96r\x8C\x8E": 1
  ":\x94@\x8C\x8E": 2
  ":\x96\xED\x90\xB6": 3
  ":\x89K\x8C\x8E": 4
  ":\x8EH\x8C\x8E": 5
  ":\x90\x85\x96\xB3\x8C\x8E": 6
  ":\x95\xB6\x8C\x8E": 7
  ":\x97t\x8C\x8E": 8
  ":\x92\xB7\x8C\x8E": 9
  ":\x90_\x96\xB3\x8C\x8E": 10
  ":\x91\x9A\x8C\x8E": 11
  ":\x8Et\x91\x96": 12
:KANJI: 
  "\x88\xEA\x8C\x8E": 1
  "\x93\xF1\x8C\x8E": 2
  "\x8EO\x8C\x8E": 3
  "\x8El\x8C\x8E": 4
  "\x8C\xDC\x8C\x8E": 5
  "\x98Z\x8C\x8E": 6
  "\x8E\xB5\x8C\x8E": 7
  "\x94\xAA\x8C\x8E": 8
  "\x8B\xE3\x8C\x8E": 9
  "\x8F\\\x8C\x8E": 10
  "\x8F\\\x88\xEA\x8C\x8E": 11
  "\x8F\\\x93\xF1\x8C\x8E": 12
YOML.dump出力
--- 
:OLD: 
  :睦月: 1
  :如月: 2
  :弥生: 3
  :卯月: 4
  :皐月: 5
  :水無月: 6
  :文月: 7
  :葉月: 8
  :長月: 9
  :神無月: 10
  :霜月: 11
  :師走: 12
:KANJI: 
  一月: 1
  二月: 2
  三月: 3
  四月: 4
  五月: 5
  六月: 6
  七月: 7
  八月: 8
  九月: 9
  十月: 10
  十一月: 11
  十二月: 12

メモ:修正した行は?修正してない行は?

ちょっとした確認依頼にちょっとしたコードで対応できるRubyって素敵
依頼内容を聞きつつコードを書くのが当たり前になりつつある(^^ゞ

# files
before  = File.open("before.csv").lines.to_a
after   = File.open("after.csv").lines.to_a

# conditions
matched     = lambda{|(b, a)| b==a}
mismatched  = lambda{|(b, a)| b!=a}

# select+map
matched_before    = before.zip(after).grep(matched){|(b, a)| b}
matched_after     = before.zip(after).grep(matched){|(b, a)| a}
mismatched_before = before.zip(after).grep(mismatched){|(b, a)| b}
mismatched_after  = before.zip(after).grep(mismatched){|(b, a)| a}

# display
{
  "matched:before"    =>  matched_before,
  "matched:after"     =>  matched_after,
  "mismatched:before" =>  mismatched_before,
  "mismatched:after"  =>  mismatched_after,
}.each{|header, data| puts [header, data]}



業務運用担当者なので電話での問い合わせ対応を結構するのだが、話しながら
irbを立ち上げて対応していることが多い(^^)
ファイルやディレクトリの検索、ファイル内検索、話相手に確認を取りながら
柔軟に調べる必要がある場合などで本当に役に立っている


Windowsも標準装備にしてくれないかなー

メモ:遺物

# coding: CP932
require "tapp"
require "inifile"   # gem install inifile
#require "yaml"
require "json"
require "xmlsimple" # gem install xml-simple

def to_json(hash)
  hash.to_json.encode(__ENCODING__)
end

def to_xml(hash)
  XmlSimple.xml_out(hash)
end

Dir["./**/*.ini"].each do |path|

  basename = File.basename(path, ".*")

  IniFile.new(path).tapp.to_h.tap do |hash|

    %w{json xml}.each do |to|

      File.open("#{basename}.#{to}", "w") do |fp|
        fp << send("to_#{to}", hash)
      end

    end

  end

end

メモ:ダメだ(>_<)

# coding: CP932

# 生成パターン
PATTERN = {
  SEQ:    -> size, n {1.upto(1.0/0).take(n).map{|i| "%0#{size}d"%i}}.curry,
  HIRA:   -> size, n {(''..'').cycle.take(size * n).each_cons(size).map(&:join)}.curry,
  KATA:   -> size, n {((''..'').to_a+(''..'').to_a).cycle.take(size * n).each_cons(size).map(&:join)}.curry,
  KANJI:  -> size, n {(''..'').cycle.take(size * n).each_cons(size).map(&:join)}.curry,
}

# 区切り文字
DELIMITTER  = "\t"

# 列定義
cols  = [
  PATTERN[:SEQ][6],
  PATTERN[:HIRA][3],
  PATTERN[:KATA][4],
  PATTERN[:KANJI][5],
]

# 行定義
rows = Array.new(25)

# データ生成
rows.tap{|rows|
  puts  rows.zip( *cols.map{ |col| col[rows.size] } )
            .map { |data| data.drop(1).join("\t") }
}

出力

000001  ぁあぃ  ァアィイ        亜唖娃阿哀
000002  あぃい  アィイゥ        唖娃阿哀愛
000003  ぃいぅ  ィイゥウ        娃阿哀愛挨
000004  いぅう  イゥウェ        阿哀愛挨姶
000005  ぅうぇ  ゥウェエ        哀愛挨姶逢
000006  うぇえ  ウェエォ        愛挨姶逢葵
000007  ぇえぉ  ェエォオ        挨姶逢葵茜
000008  えぉお  エォオカ        姶逢葵茜穐
000009  ぉおか  ォオカガ        逢葵茜穐悪
000010  おかが  オカガキ        葵茜穐悪握
000011  かがき  カガキギ        茜穐悪握渥
000012  がきぎ  ガキギク        穐悪握渥旭
000013  きぎく  キギクグ        悪握渥旭葦
000014  ぎくぐ  ギクグケ        握渥旭葦芦
000015  くぐけ  クグケゲ        渥旭葦芦鯵
000016  ぐけげ  グケゲコ        旭葦芦鯵梓
000017  けげこ  ケゲコゴ        葦芦鯵梓圧
000018  げこご  ゲコゴサ        芦鯵梓圧斡
000019  こごさ  コゴサザ        鯵梓圧斡扱
000020  ごさざ  ゴサザシ        梓圧斡扱宛
000021  さざし  サザシジ        圧斡扱宛姐
000022  ざしじ  ザシジス        斡扱宛姐虻
000023  しじす  シジスズ        扱宛姐虻飴
000024  じすず  ジスズセ        宛姐虻飴絢
000025  すずせ  スズセゼ        姐虻飴絢綾

メモ:固定長開始位置

DATAファイルオブジェクト
ps_data.rb

DATA.lines.map(&:chomp).tap{|sizes|
  puts sizes.inject([1]){|poses, size| poses << poses.last + size.to_i}.zip(sizes)
        .map{|(pos, size)| "#{pos}\t#{size}"}
}

__END__
21
1
6
1
4
1
16
1
20
1
2
1
69
1
26
1
16
1
16
1
16
1
8
1
26
1
16
1
2



標準入力
サイズ入力(終了はCtrl+Z)→サイズファイル作成

copy con sizes.dat

ps_argf.rb

ARGF.lines.map(&:chomp).tap{|sizes|
  puts sizes.inject([1]){|poses, size| poses << poses.last + size.to_i}.zip(sizes)
        .map{|(pos, size)| "#{pos}\t#{size}"}
}

実行

type sizes.dat | ruby ps_argf.rb

結果(DATA版、ARGF版)

1	21
22	1
23	6
29	1
30	4
34	1
35	16
51	1
52	20
72	1
73	2
75	1
76	69
145	1
146	26
172	1
173	16
189	1
190	16
206	1
207	16
223	1
224	8
232	1
233	26
259	1
260	16
276	1
277	2
279