rpgmaker-linux/Kawariki-patches/libs/PreloadIni.rb
2024-11-27 19:54:06 +02:00

439 lines
17 KiB
Ruby

# INI file tools for replacing Win32API usage
# Key and Section names are case-preserving
# Authors: Taeyeon Mori
module Preload
module Ini
# ********** Machinery **********
class DummyReadFile
def each_line(&p)
end
end
class IniBase
def initialize(file)
@file = file
@section = ""
@section_lc = ""
end
attr_reader :section, :section_lc
def self.open(filename)
if block_given? then
File.open(filename, self::FileMode) do |file|
yield self.new file
end
else
return self.new File.new(filename, self::FileMode)
end
end
end
class IniWriter < IniBase
FileMode = "wt"
def initialize(file)
super file
@newline = 1
end
def writeLine(line=nil)
if line.nil? || line.empty? then
@file.write "\r\n"
@newline += 1
else
@file.write "#{line}\r\n"
@newline = 0
end
end
def writeComment(text)
writeLine "; #{text}"
end
def writeSection(name)
lc = name.downcase
return if lc == @section_lc
writeLine if @newline < 1
writeLine "[#{name}]"
@section = name
@section_lc = lc
end
def writeKey(key, value)
value = value.to_s
value = "\"#{value}\"" if value.strip != value
writeLine "#{key}=#{value}"
end
def writeEntry(section, key, value)
writeSection section
writeKey key, value
end
def forward(token, *args)
# Can receive tokens from IniReader directly
case token
when :comment
writeComment *args
when :section
writeSection *args
when :key
writeKey *args
when :line
writeLine *args
when :empty
writeLine
when :eof
else
raise "Unknown token: #{token}"
end
end
end
class IniReader < IniBase
FileMode = "rt"
def self.open(filename)
# Pretend file is empty if it doesn't exist
if !File.exist? filename then
if block_given? then
yield self.new DummyReadFile.new
else
return self.new DummyReadFile.new
end
else
return super
end
end
def readComment(line)
line.slice!(0, line[1] == " " ? 2 : 1)
[line]
end
def readSection(line)
raise "Malformed section header: '#{line}'" if line[0] != '[' || line[-1] != ']'
@section = line[1...-1]
@section_lc = @section.downcase
return [@section]
end
def readKey(line)
key, value = line.split('=', 2)
value.strip!
# Allow quoting to keep surrounding whitespace
value = value[1...-1] if value.size > 1 && "'\"".include?(value[0]) && value[0] == value[-1]
[key.strip, value]
rescue ArgumentError => e
puts "Error processing line: #{line.inspect} - #{e.message}"
nil
end
def readLine(line)
if line.valid_encoding?
line.strip!
else
# Optionally, handle the invalid encoding here:
# You can choose to skip the line, replace invalid characters, or log it.
line = line.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '?')
end
return [:empty] if line.empty?
return :comment, *readComment(line) if line[0] == ';'
return :section, *readSection(line) if line[0] == '['
return :key, *readKey(line) if line.include? '='
# Just pass it through as last resort
return :line, line
end
def read
raise "Must be called with block" unless block_given?
last = nil
@file.each_line do |line|
tok = readLine line
# Collapse empty lines
next if tok[0] == :empty && last == :empty
last = tok[0]
yield tok
end
yield :eof
end
end
# ********** Simple API **********
# Read
def self.readIniString(filename, section, key)
# Intended to be compatible with ReadPrivateProfileString
section_lc = section.downcase
key_lc = key.downcase
found_section = section.empty?
IniReader.open filename do |ir|
ir.read do |type, *args|
case type
when :section
found_section = ir.section_lc == section_lc
when :key
fkey, fval = *args
return fval if found_section && fkey.downcase == key_lc
end
end
end
end
def self.readIniStrings(filename, section, keys=nil)
result = {}
section_lc = section.downcase
keys_lc = {}
keys.each {|key| keys_lc[key.downcase] = key} unless keys.nil?
found_section = section.empty?
IniReader.open filename do |ir|
ir.read do |type, *args|
case type
when :section
found_section = ir.section_lc == section_lc
when :key
if found_section then
fkey, fval = *args
if keys.nil? then
result[fkey] = fval
else
zkey = keys_lc[fkey.downcase]
result[zkey] = fval unless zkey.nil?
end
end
end
end
end
return result
end
def self.readIniPaths(filename, paths=nil)
result = {}
paths_lc = {}
paths.each {|path| lc = path.downcase; paths_lc[lc] = path unless paths_lc.include? lc} unless paths.nil?
section_all = paths.nil? || paths.include?("/*")
section_all_name = nil
IniReader.open filename do |ir|
ir.read do |type, *args|
case type
when :section
unless paths.nil? then
section_all_name = paths_lc["#{ir.section_lc}/*"]
section_all = !section_all_name.nil?
section_all_name.slice!(-2...) if section_all
end
when :key
fkey, fval = *args
if section_all then
fpath = "#{section_all_name.nil? ? ir.section : section_all_name}/#{fkey}"
result[fpath] = fval
else
fpath = "#{ir.section_lc}/#{fkey.downcase}"
result[paths_lc[fpath]] = fval if paths_lc.key? fpath
end
end
end
end
result
end
def self.readIniSections(filename)
# Get a list of sections in the file
# Note: may contain (possibly case-mismatched) duplicates.
sections = []
IniReader.open filename do |ir|
ir.read do |type, *args|
case type
when :section
sections.push args[0]
end
end
end
return sections
end
def self.readIniKeys(filename, section)
# Get a list of keys in a section
# Note: may contain (possibly case-mismatched) duplicates.
keys = []
section_lc = section.downcase
found_section = section.empty?
IniReader.open filename do |ir|
ir.read do |type, *args|
case type
when :section
found_section = ir.section_lc == section_lc
when :key
keys.push args[0] if found_section
end
end
end
return keys
end
# Write
def self.writeIniString(filename, section, key, value)
# Intended to be compatible with WritePrivateProfileString
# Write to new file then rename over top.
section_lc = section.downcase
key_lc = key.downcase unless key.nil?
found_section = section.empty?
written = key.nil? || value.nil? # Delete instead if nil
temp_name = "#{filename}.tmp"
IniWriter.open temp_name do |iw|
IniReader.open filename do |ir|
ir.read do |type, *args|
case type
when :section
# Insert new key before leaving section
if found_section && !written then
iw.writeKey key, value
written = true
end
# Start new section or omit whole section if key == nil
found_section = ir.section_lc == section_lc
iw.writeSection ir.section unless found_section && key.nil?
when :key
fkey, fval = *args
if found_section then
if fkey.downcase == key_lc then
# Replace matching key
iw.writeKey key, value unless written
written = true
elsif !key.nil? then
# Copy other keys
iw.writeKey fkey, fval
end
else
iw.writeKey fkey, fval
end
when :eof
# Add to end of file if not found earlier
iw.writeEntry section, key, value unless written
else
iw.forward type, *args
end
end
end
end
File.rename(temp_name, filename)
end
def self.writeIniStrings(filename, section, hash)
# Write to new file then rename over top.
section_lc = section.downcase
hash_lc = {}
written = []
hash.each_pair do |key, value|
key_lc = key.downcase
hash_lc[key_lc] = [key, value] unless value.nil?
written.push key_lc if value.nil?
end
found_section = section.empty?
temp_name = "#{filename}.tmp"
IniWriter.open temp_name do |iw|
IniReader.open filename do |ir|
ir.read do |type, *args|
case type
when :section
# Insert new keys before leaving section
if found_section && !hash_lc.empty? then
hash_lc.each_pair {|key_lc, kv| iw.writeKey *kv}
written.push *hash_lc.keys
hash_lc.clear
end
# Start new section or omit whole section if key == nil
found_section = ir.section_lc == section_lc
iw.writeSection ir.section
when :key
fkey, fval = *args
if found_section then
fkey_lc = fkey.downcase
entry = hash_lc.delete fkey_lc
if !entry.nil? then
# Replace matching key
iw.writeKey *entry
written.push fkey_lc
next
elsif written.include? fkey_lc then
next
end
end
iw.writeKey fkey, fval
when :eof
# Add to end of file if not found earlier
if !hash_lc.empty? then
iw.writeSection section
hash_lc.each_value {|key, value| iw.writeKey key, value}
end
else
iw.forward type, *args
end
end
end
end
File.rename(temp_name, filename)
end
def self.writeIniPaths(filename, hash)
# Write to new file then rename over top.
sections = {}
hash.each_pair do |path, value|
section, _, key = path.rpartition '/'
section_lc = section.downcase
sections[section_lc] = {name: section, written: [], keys: {}} unless sections.key? section_lc
sections[section_lc][:keys][key.downcase] = [key, value]
end
current_section = sections[""]
temp_name = "#{filename}.tmp"
IniWriter.open temp_name do |iw|
IniReader.open filename do |ir|
ir.read do |type, *args|
case type
when :section
# Write new keys before leaving section
if !current_section.nil? then
current_section[:keys].each_value {|key, value| iw.writeKey key, value}
current_section[:written].push *current_section[:keys].keys
current_section[:keys].clear
end
# new section
iw.writeSection ir.section
current_section = sections[ir.section_lc]
when :key
fkey, fval = *args
fkey_lc = fkey.downcase
# Replace matching key
if !current_section.nil? then
replacement = current_section[:keys].delete fkey_lc
if !replacement.nil? then
iw.writeKey *replacement unless replacement[1].nil?
current_section[:written].push fkey_lc
next
elsif current_section[:written].include? fkey_lc then
next
end
end
# Copy other keys
iw.writeKey fkey, fval
when :eof
# Add sections not previously seen
sections.each_value do |sect|
if !sect[:keys].empty? then
iw.writeSection sect[:name]
sect[:keys].each_value do |key, value|
iw.writeKey key, value
end
end
end
else
iw.forward type, *args
end
end
end
end
File.rename(temp_name, filename)
end
end
end