mirror of
https://github.com/bakustarver/rpgmakermlinux-cicpoffs.git
synced 2025-06-09 18:55:57 +02:00
439 lines
17 KiB
Ruby
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
|