| 1 | #!ruby -Ku |
|---|
| 2 | =begin |
|---|
| 3 | Mork フォーマットパーサ |
|---|
| 4 | |
|---|
| 5 | Mork とかいう Mozilla につかわれているクソフォーマットを |
|---|
| 6 | Ruby でパースする |
|---|
| 7 | |
|---|
| 8 | # mork.rb |
|---|
| 9 | # See modifiers http://coderepos.org/share/log/lang/ruby/misc/mork.rb |
|---|
| 10 | # |
|---|
| 11 | # Based on http://www.jwz.org/hacks/mork.pl |
|---|
| 12 | # Copyright © 2004 Jamie Zawinski <jwz@jwz.org> |
|---|
| 13 | # |
|---|
| 14 | # Permission to use, copy, modify, distribute, and sell this software and its |
|---|
| 15 | # documentation for any purpose is hereby granted without fee, provided that |
|---|
| 16 | # the above copyright notice appear in all copies and that both that |
|---|
| 17 | # copyright notice and this permission notice appear in supporting |
|---|
| 18 | # documentation. No representations are made about the suitability of this |
|---|
| 19 | # software for any purpose. It is provided "as is" without express or |
|---|
| 20 | # implied warranty. |
|---|
| 21 | # |
|---|
| 22 | # Created: 3-Mar-2004 by Jamie Zawinski, Anonymous, and Jacob Post. |
|---|
| 23 | =end |
|---|
| 24 | |
|---|
| 25 | require "strscan" |
|---|
| 26 | |
|---|
| 27 | class Mork |
|---|
| 28 | RE = { |
|---|
| 29 | :comment => %r|//.*\n|, |
|---|
| 30 | :key_table => %r/ < \s* < # "< <" |
|---|
| 31 | \( a=c \) > # "(a=c)>" |
|---|
| 32 | (?> ([^>]*) ) > \s* # Grab anything that's not ">" |
|---|
| 33 | /mx, |
|---|
| 34 | |
|---|
| 35 | :value_table => %r/ < ( .*?\) )> \s* /mx, |
|---|
| 36 | |
|---|
| 37 | :table => %r/ \{ -? # "{" or "{-" |
|---|
| 38 | [\da-f]+ : # hex, ":" |
|---|
| 39 | (?> .*?\{ ) # Eat up to a {... |
|---|
| 40 | ((?> .*?\} ) # and then the closing }... |
|---|
| 41 | (?> .*?\} )) # Finally, grab the table section |
|---|
| 42 | \s* /mix, |
|---|
| 43 | |
|---|
| 44 | :row => %r/ ( (?> \[ [^\]]* \] # "["..."]" |
|---|
| 45 | \s*)+ ) # Perhaps repeated many times |
|---|
| 46 | /mx, |
|---|
| 47 | |
|---|
| 48 | :section_begin => %r/ \@\$\$\{ # "@$${" |
|---|
| 49 | ([\dA-F]+) # hex |
|---|
| 50 | \{\@ \s* # "{@" |
|---|
| 51 | /mix, |
|---|
| 52 | :section_end => nil # variable |
|---|
| 53 | } |
|---|
| 54 | |
|---|
| 55 | def initialize(str, age=nil) |
|---|
| 56 | @str = str |
|---|
| 57 | @since = age ? Time.now - age : 0 |
|---|
| 58 | @age = age |
|---|
| 59 | end |
|---|
| 60 | |
|---|
| 61 | def parse_key_table(key_table) |
|---|
| 62 | key_table.gsub!(%r|\s+//.*$|, "") |
|---|
| 63 | pairs = key_table.split(/\(([^\)]+)\)/) |
|---|
| 64 | pairs.each do |pair| |
|---|
| 65 | next unless pair =~ /\S/m |
|---|
| 66 | _, key, val = *pair.match(/([\dA-F]*)[\t\n ]*=[\t\n ]*(.*)/i); |
|---|
| 67 | |
|---|
| 68 | raise "Unparsable" unless val |
|---|
| 69 | |
|---|
| 70 | @key_table[key] = val |
|---|
| 71 | end |
|---|
| 72 | |
|---|
| 73 | end |
|---|
| 74 | |
|---|
| 75 | def parse_value_table(val_part) |
|---|
| 76 | return unless val_part |
|---|
| 77 | |
|---|
| 78 | pairs = val_part.split(/\(([^\)]+)\)/) |
|---|
| 79 | pairs.each do |pair| |
|---|
| 80 | next unless pair =~ /\S/m |
|---|
| 81 | _, key, val = *pair.match(/([\dA-F]*)[\t\n ]*=[\t\n ]*(.*)/i); |
|---|
| 82 | |
|---|
| 83 | unless val |
|---|
| 84 | $stderr.puts "Unparsable" |
|---|
| 85 | next |
|---|
| 86 | end |
|---|
| 87 | |
|---|
| 88 | if val.gsub!(/\$([\dA-F]{2})/) { $1.hex.chr } |
|---|
| 89 | val = val.unpack("S*").pack("U*") |
|---|
| 90 | end |
|---|
| 91 | |
|---|
| 92 | @val_table[key] = val |
|---|
| 93 | end |
|---|
| 94 | end |
|---|
| 95 | |
|---|
| 96 | def parse_table(table_part) |
|---|
| 97 | table_part.gsub!(/\s+/, "") |
|---|
| 98 | re = %r/[^\[]* \[ # find a "[" |
|---|
| 99 | ( [^\]]+ ) \] # capture up to "]" |
|---|
| 100 | /x |
|---|
| 101 | table_part.scan(re) do |m,| |
|---|
| 102 | hash = {} |
|---|
| 103 | id, *cells = m.split(/[()]+/) |
|---|
| 104 | next unless cells |
|---|
| 105 | id.gsub!(/^-/, "") |
|---|
| 106 | id.gsub!(/:.*/, "") |
|---|
| 107 | |
|---|
| 108 | if @row_hash[id] |
|---|
| 109 | hash = @row_hash[id] |
|---|
| 110 | else |
|---|
| 111 | hash = { |
|---|
| 112 | "ID" => id, |
|---|
| 113 | "LastVisitDate" => 0, |
|---|
| 114 | } |
|---|
| 115 | end |
|---|
| 116 | |
|---|
| 117 | cells.each do |cell| |
|---|
| 118 | next unless cell |
|---|
| 119 | |
|---|
| 120 | _, keyi, which, vali = *cell.match( |
|---|
| 121 | /^\^ ([-\dA-F]+) |
|---|
| 122 | ([\^=]) |
|---|
| 123 | (.*) |
|---|
| 124 | $/xi) |
|---|
| 125 | |
|---|
| 126 | unless vali |
|---|
| 127 | $stderr.puts "Unparsable" |
|---|
| 128 | next |
|---|
| 129 | end |
|---|
| 130 | |
|---|
| 131 | key = @key_table[keyi] |
|---|
| 132 | next unless key |
|---|
| 133 | |
|---|
| 134 | val = (which == "=") ? vali : @val_table[vali] |
|---|
| 135 | |
|---|
| 136 | hash[key] = val |
|---|
| 137 | end |
|---|
| 138 | |
|---|
| 139 | @block.call(hash) |
|---|
| 140 | |
|---|
| 141 | @row_hash[id] = hash |
|---|
| 142 | end |
|---|
| 143 | end |
|---|
| 144 | alias parse_row parse_table |
|---|
| 145 | |
|---|
| 146 | def parse_section_begin(section) |
|---|
| 147 | @section = section |
|---|
| 148 | @re[:section_end] = %r/\@\$\$\}#{section}\}\@\s*/s; |
|---|
| 149 | end |
|---|
| 150 | |
|---|
| 151 | def parse_section_end(e) |
|---|
| 152 | @re[:section_end] = nil |
|---|
| 153 | @section = "top level" |
|---|
| 154 | end |
|---|
| 155 | |
|---|
| 156 | def parse_comment(e) |
|---|
| 157 | # Nice boat. |
|---|
| 158 | end |
|---|
| 159 | |
|---|
| 160 | def parse(&block) |
|---|
| 161 | @block = block |
|---|
| 162 | @block = proc {} unless @block |
|---|
| 163 | @str.gsub!(/\r\n/, "\n") |
|---|
| 164 | @str.gsub!(/\r/, "\n") |
|---|
| 165 | @str.gsub!(/\\\\/, "$5C") |
|---|
| 166 | @str.gsub!(/\\\)/, "$29") |
|---|
| 167 | @str.gsub!(/\\\n/, "") |
|---|
| 168 | |
|---|
| 169 | @s = StringScanner.new(@str) |
|---|
| 170 | @section = "top level" |
|---|
| 171 | @re = RE.dup |
|---|
| 172 | @row_hash = {} |
|---|
| 173 | @key_table = {} |
|---|
| 174 | @val_table = {} |
|---|
| 175 | until @s.eos? |
|---|
| 176 | [:key_table, :value_table, :table, :row, :section_begin, :section_end, :comment].each do |k| |
|---|
| 177 | next unless @re[k] |
|---|
| 178 | if @s.scan(@re[k]) |
|---|
| 179 | self.send("parse_#{k}", @s[1]) |
|---|
| 180 | break |
|---|
| 181 | end |
|---|
| 182 | end |
|---|
| 183 | end |
|---|
| 184 | @row_hash |
|---|
| 185 | end |
|---|
| 186 | end |
|---|
| 187 | |
|---|
| 188 | |
|---|
| 189 | if $0 == __FILE__ |
|---|
| 190 | profile_dir = |
|---|
| 191 | case RUBY_PLATFORM |
|---|
| 192 | when /darwin/ |
|---|
| 193 | "~/Library/Application Support/Firefox/Profiles" |
|---|
| 194 | when /win/ |
|---|
| 195 | "~/Application Data/Mozilla/Firefox/Profiles" |
|---|
| 196 | when |
|---|
| 197 | "~/.mozilla/firefox" |
|---|
| 198 | end |
|---|
| 199 | |
|---|
| 200 | |
|---|
| 201 | require "pp" |
|---|
| 202 | require "pathname" |
|---|
| 203 | histfiles = Pathname.glob(File.expand_path("*/history.dat", profile_dir)) |
|---|
| 204 | puts histfiles |
|---|
| 205 | |
|---|
| 206 | # history = File.read(histfiles.first) |
|---|
| 207 | # pp Mork.new(history).parse |
|---|
| 208 | |
|---|
| 209 | # pp histfiles.inject([]) {|r,i| |
|---|
| 210 | # r.concat Mork.new(i.read).parse.map {|k,v| |
|---|
| 211 | # v["URL"] |
|---|
| 212 | # } |
|---|
| 213 | # } |
|---|
| 214 | |
|---|
| 215 | histfiles.each do |i| |
|---|
| 216 | Mork.new(i.read).parse do |hash| |
|---|
| 217 | pp hash |
|---|
| 218 | end |
|---|
| 219 | end |
|---|
| 220 | end |
|---|
| 221 | |
|---|