=begin
==============================================================================
** Loading Data From Text Files V4.0
------------------------------------------------------------------------------
Description:
------------
This Utility module loads data from text files, the text file loaded must
be setup like so.
something some_value
parameter value
parameter value
parameter value
Also has support for comments and block comments within the text file,
commented data will be ignored within the file. This also parses the values
loaded into the data type it is supposed to be, for example if a value was
50 then it will be loaded as 50 and not "50". This effect works on Strings,
Integers, Floats, Ranges, Arrays, Hashes, Regexp, Symbols, TrueClass,
FalseClass, NilClass, and Classes (by calling the new operator example
Color.new(0, 0, 0) will be loaded as a color object). Also includes error
checking and will catch Syntax Errors, missing ] for Array, missing } for
Hash, Range errors, and Regexp Errors and will also point to the line and
the data that is on the line if an unexcepted error is found within the file.
Method List:
------------
Trickster.load_data_from_txt
Classes:
--------
Class File_LoadRxdata
==============================================================================
=end
module Trickster
class File_LoadRxdata < File
#-------------------------------------------------------------------------
# * Public Instance Variables
#-------------------------------------------------------------------------
attr_reader :line
#-------------------------------------------------------------------------
# * Name : Format Readline
# Info : Gets Next Line returns An Array of information on next line
# Author : Trickster
# Call Info : No Arguments
#-------------------------------------------------------------------------
def format_readline
# Get Line
data = self.readline
# Make a Copy
@line = data.dup
# Split it
data = data.split
# Return Data
return data
end
#-------------------------------------------------------------------------
# * Name : Requirements
# Info : Required Data Needed to be loaded
# Author : Trickster
# Call Info : Four Arguments
# Array Data, Data Loaded
# Array Required, Data Required to be loaded
# Array Properties, Names to be loaded
# Boolean Elements, True if Indexes False for Parameters
#-------------------------------------------------------------------------
def requirements(data, required, properties, elements)
required.each do |i|
if (i < properties.size and (data[i].nil? and elements) or
(data.is_a?(Hash) and data[properties[i]].nil? and not elements))
Kernel.print("Error Loading Data \n#{properties[i]} is not defined")
exit
end
end
end
#-------------------------------------------------------------------------
# * Name : Incorrect Name Error
# Info : Generates Incorrect Defining Name Message
# Author : Trickster
# Call Info : No Arguments
#-------------------------------------------------------------------------
def error_incorrect_name
Kernel.print("Error at Line #{lineno}\nIncorrect parameter\nLine: #{line}")
exit
end
#-------------------------------------------------------------------------
# * Name : Array Error
# Info : Generates Missing ] for Array Message
# Author : Trickster
# Call Info : One Argument, Integer Extra, Line Number
#-------------------------------------------------------------------------
def error_array(extra)
Kernel.print("Error at Line #{extra}\nMissing ']' for Array\nLine: #{line}")
exit
end
#-------------------------------------------------------------------------
# * Name : Hash Error (Syntax Error)
# Info : Generates Missing } for Hash Message
# Author : Trickster
# Call Info : One Argument, Integer Extra, Line Number
#-------------------------------------------------------------------------
def error_hash(extra)
Kernel.print("Error at Line #{extra}\nMissing '}' for Hash\nLine: #{line}")
exit
end
#-------------------------------------------------------------------------
# * Name : Syntax Error
# Info : Generates Syntax Error Message
# Author : Trickster
# Call Info : One Argument Integer Extra, Line number
#-------------------------------------------------------------------------
def error_syntax(extra)
Kernel.print("Error at Line #{extra}\nSyntax Error\nLine: #{line}")
exit
end
#-------------------------------------------------------------------------
# * Name : Range Error
# Info : Generates Error Message
# Author : Trickster
# Call Info : One Argument Integer Extra, Line number
#-------------------------------------------------------------------------
def error_range(extra)
Kernel.print("Error at Line #{extra}\nError with Range\nLine: #{line}")
exit
end
#-------------------------------------------------------------------------
# Name : Regexp Fail Error
# Info : Generates Error Message
# Author : Trickster
# Call Info : One Argument Integer Extra, Line number
#-------------------------------------------------------------------------
def error_regexp_fail(extra)
Kernel.print("Error at Line #{extra}\nRegexp Failed\nLine: #{line}")
exit
end
#-------------------------------------------------------------------------
# * Name : Load Next Line
# Info : Reads Next Line of Data and returns false if End of File,
# true if Empty or Commented else the Data
# Author : Trickster
# Call Info : No Arguments
#-------------------------------------------------------------------------
def load_next_line
# Return False if End of File
return false if self.eof?
# Get Data
data = self.format_readline
# Set Comment Flag if text is =begin
@comment = true if data[0] == '=begin'
# If text is =end
if data[0] == '=end'
# Unset Comment Flag
@comment = false
# True
return true
end
# Return true if comment or nothing
return true if data == [] or data[0].include?('#') or @comment
# Return Data
return data
end
#-------------------------------------------------------------------------
# * Name : Test Type
# Info : Tests Data Type and returns sata in the correct format
# Author : Trickster
# Call Info : Three to Four Arguments
# String data, String to be check
# String Line, Line where data was extracted
# Integer Lineno, Line number
# Integer Multi, Multi Lines for error checking
# Comment : Checks for almost all data types
#-------------------------------------------------------------------------
def test_type(data, line, lineno, multi = 0)
# Make a copy of the data
temp = data.dup
# Set extra text if an error is found
extra = (multi > 0) ? "s #{lineno}-#{lineno+multi}" : " #{lineno}"
# If a nil reference
if test_nilclass?(temp)
# Set Data to Nil
data = nil
# If True
elsif test_trueclass?(temp)
# Set to True
data = true
# If False
elsif test_falseclass?(temp)
# Set to false
data = false
# If an array
elsif test_array?(temp)
# Error Array if it doesn't include ]
self.error_array(extra) unless temp.include?(']')
# Evaluate Data
data = eval_data(data, extra)
# If a hash
elsif test_hash?(temp)
# Error Hash if it doesn't include }
self.error_hash(extra) unless temp.include?('}')
# Evaluate Data
data = eval_data(data, extra)
# If a number (Integer)
elsif test_integer?(temp)
# Convert to Integer
data = data.to_i
# If a number (Float)
elsif test_float?(temp)
# Convert to Float
data = data.to_f
# If a Range
elsif test_range?(temp)
begin
# Evaluate Data
data = eval_data(data, extra)
rescue
# Print Range Error
self.error_range(extra)
end
# If a Regexp
elsif test_regexp?(temp)
begin
# Evaluate Data
data = eval_data(data, extra)
rescue RegexpError
# If Regexp Error then print Error
self.error_regexp_fail
end
# If a Symbol
elsif test_symbol?(temp)
# Evaluate Data
data = eval_data(data, extra)
# If an Instance of a Class
elsif test_class?(temp)
# Evaluate Data
data = eval_data(data, extra)
# Elsif a string
elsif test_string?(temp)
# Just Delete First and Last Index
data = data[1...(data.size-1)]
end
# Return Data
return data
end
#-------------------------------------------------------------------------
# * Name : Test NilClass
# Info : Tests if Data is nil
# Author : Trickster
# Call Info : String data - data to check
#-------------------------------------------------------------------------
def test_nilclass?(data)
return data == 'nil'
end
#-------------------------------------------------------------------------
# * Name : Test TrueClass
# Info : Tests if Data is true
# Author : Trickster
# Call Info : String data - data to Check
#-------------------------------------------------------------------------
def test_trueclass?(data)
return data == 'true'
end
#-------------------------------------------------------------------------
# * Name : Test FalseClass
# Info : Tests if Data is false
# Author : Trickster
# Call Info : String data - data to Check
#-------------------------------------------------------------------------
def test_falseclass?(data)
return data == 'false'
end
#-------------------------------------------------------------------------
# * Name : Test Array
# Info : Tests if Data is an array (starts with [)
# Author : Trickster
# Call Info : String data - data to Check
#-------------------------------------------------------------------------
def test_array?(data)
return data[0, 1] == '['
end
#-------------------------------------------------------------------------
# * Name : Test Hash
# Info : Tests if Data is a hash (starts with { and has =>)
# Author : Trickster
# Call Info : String data - data to Check
#-------------------------------------------------------------------------
def test_hash?(data)
return data[0, 1] == '{' && data.include?('=>') && data
end
#-------------------------------------------------------------------------
# * Name : Test Integer
# Info : Tests if Data is an integer (if to_i.to_s == data)
# Author : Trickster
# Call Info : String data - data to Check
#-------------------------------------------------------------------------
def test_integer?(data)
return data.to_i.to_s == data
end
#-------------------------------------------------------------------------
# * Name : Test Float
# Info : Tests if Data is a float (if to_f.to_s == data)
# Author : Trickster
# Call Info : String data - data to Check
#-------------------------------------------------------------------------
def test_float?(data)
return data.to_f.to_s == data
end
#-------------------------------------------------------------------------
# * Name : Test Range
# Info : Tests if Data is a range (includes .. or ...
# and first and last are integers and when .. or ... is removed
# is an integer)
# Author : Trickster
# Call Info : String data - data to Check
#-------------------------------------------------------------------------
def test_range?(data)
# Create Copy
copy = data.dup
# Return false if no .. or ...
return false unless copy.include?('..') or copy.include?('...')
# Substitute .. or ... from data
copy.sub!(/\.{2,3}/, '')
# Return false if leftovers is not a int
return false if not test_integer?(copy)
# Return first and last characters are integers
return test_integer?(data[0,1]) && test_integer?(data[-1,1])
end
#-------------------------------------------------------------------------
# * Name : Test Regexp
# Info : Tests if Data is a regexp (first and last are /)
# Author : Trickster
# Call Info : String data - data to Check
#-------------------------------------------------------------------------
def test_regexp?(data)
return data[0, 1] == '/' && data[-1, 1] == '/'
end
#-------------------------------------------------------------------------
# * Name : Test Symbol
# Info : Tests if Data is a symbol (first is :)
# Author : Trickster
# Call Info : String data - data to Check
#-------------------------------------------------------------------------
def test_symbol?(data)
return data[0, 1] == ':'
end
#-------------------------------------------------------------------------
# * Name : Test Class
# Info : Tests if Data is a class (has .new)
# Author : Trickster
# Call Info : String data - data to Check
#-------------------------------------------------------------------------
def test_class?(data)
return data.include?('.new')
end
#-------------------------------------------------------------------------
# * Name : Test String
# Info : Tests if Data is a string (first and last are ")
# Author : Trickster
# Call Info : String data - data to Check
#-------------------------------------------------------------------------
def test_string?(data)
return data[0] == 34 && data[-1] == 34
end
#-------------------------------------------------------------------------
# * Name : Eval Data
# Info : calls eval on data and catches Syntax Errors
# Author : Trickster
# Call Info : String data, extra - data to Check, error info
#-------------------------------------------------------------------------
def eval_data(data, extra = '')
begin
# Evaluate Data
data = eval(data)
rescue SyntaxError
# If Syntax Error then print Error
self.error_syntax(extra)
end
end
end
#-------------------------------------------------------------------------
# * Name : Load Data From Text file
# Info : Loads data from a text file
# returns an array/hash with the data from text file
# Author : Trickster
# Call Info : Two to Five Arguments
# String File_name, file name to load from
# Array Properties, Defining Values
# Array Required, An Array of Indexes that must be loaded
# defaults to index 0
# Array Multilined, An Array of Indexes that read from multilines
# defaults to nothing, note: for strings only
# Integer Elements, If 0 Elements are arrays (Default)
# If 1 Elements are hashes Key = id
# Else Elements are hashes Key = parameter
#-------------------------------------------------------------------------
def self.load_data_from_txt(file_name, properties, required = [0],
multi_lined = [], elements = 0)
# Initialize local variables
final_data = []
data_txt = elements == 0 ? [] : {}
index = 1
# Load File
file = Trickster::File_LoadRxdata.open(file_name)
# Loop until End of File (EOF)
until file.eof?
# Get line data
data = file.format_readline
# Get Line
line = file.line
# Set Comment Flag if text is =begin
@comment = true if data[0] == '=begin'
# If text is =end
if data[0] == '=end'
# Unset Comment Flag
@comment = false
# Next
next
end
# Next if no data or commented line
next if data == [] or data[0].include?('#') or @comment
# Get id from the properties
id = properties.index(data[0])
# If a new id
if id == 0 and not data_txt.empty?
# Check requirements and return error if not fulfilled
file.requirements(data_txt, required, properties, [0,1].include?(elements))
# Finished reading a piece of data
final_data[index] = data_txt.dup
# Increase index and reset data
index += 1
data_txt.clear
elsif id == nil
# Incorrent Defining name error message
file.error_incorrect_name
end
# Remove defining name and join together
data.delete_at(0)
data = data.join(' ')
# Get line number
lineno = file.lineno
# Start multi line information checking
multi = 1
if multi_lined.include?(id)
# Load next line
next_line = file.load_next_line
# Get first data
first = next_line.is_a?(Array) ? next_line[0] : ""
# Reset flag
flag = false
# While an invalid property and the file is not eof? or data loaded
while (properties.index(first) == nil and (next_line != false or next_line.is_a?(Array)))
# While was excuted once
flag = true
position = file.pos
# add to data if an array
if next_line.is_a?(Array)
# Get property data
first = next_line[0]
if properties.index(first) == nil
data += ' ' + next_line.join(' ')
end
end
# Load next line and reset first
next_line = file.load_next_line
first = ""
# increase multi line count
multi += 1
# break if file.eof? continue if line was a comment
break if next_line == false
next if next_line == true
first = next_line[0]
end
if flag
file.pos = position
end
if next_line.is_a?(Array)
if properties.index(first) == nil
data += next_line.join(' ')
end
end
end
data = file.test_type(data, line, lineno, multi)
data_txt[id] = data if [0,1].include?(elements)
data_txt[properties[id]] = data if not [0,1].include?(elements)
end
# Reached End of File Get last data if any
if not data_txt.empty?
# Check requirements and return error if not fulfilled
file.requirements(data_txt, required, properties, [0,1].include?(elements))
# Finished reading a piece of data
final_data[index] = data_txt.dup
# Increase index and reset data
index += 1
data_txt.clear
end
# Close File
file.close
# return all data compacted
return final_data.compact
end
end