lib/stella/storable.rb in stella-0.5.4 vs lib/stella/storable.rb in stella-0.5.5
- old
+ new
@@ -1,59 +1,162 @@
# TODO: Handle nested hashes and arrays.
+require 'yaml'
+require 'utils/fileutil'
+
module Stella
class Storable
- NICE_TIME_FORMAT = "%Y-%m-%d@%H:%M:%S".freeze unless defined? NICE_TIME_FORMAT
-
- SupportedFormats= {
- 'yaml' => 'yml', # format name => file extension
- 'yml' => 'yml',
- 'csv' => 'csv',
- 'tsv' => 'tsv',
- 'json' => 'json'
- }.freeze unless defined? SupportedFormats
+ NICE_TIME_FORMAT = "%Y-%m-%d@%H:%M:%S".freeze unless defined? NICE_TIME_FORMAT
+ SUPPORTED_FORMATS = %w{tsv csv yaml json}.freeze unless defined? SUPPORTED_FORMATS
attr_reader :format
def format=(v)
- raise "Unsupported format: #{v}" unless SupportedFormats.has_key?(v)
+ raise "Unsupported format: #{v}" unless SUPPORTED_FORMATS.member?(v)
@format = v
end
+ def init
+ self.class.send(:class_variable_set, :@@field_names, []) unless class_variable_defined?(:@@field_names)
+ self.class.send(:class_variable_set, :@@field_types, []) unless class_variable_defined?(:@@field_types)
+ end
+
+ def self.field(args={})
+
+ args = {args => nil} unless args.is_a? Hash
+
+ args.each_pair do |m,t|
+
+ [[:@@field_names, m], [:@@field_types, t]].each do |tuple|
+ class_variable_set(tuple[0], []) unless class_variable_defined?(tuple[0])
+ class_variable_set(tuple[0], class_variable_get(tuple[0]) << tuple[1])
+ end
+
+ next if method_defined?(m)
+
+ # NOTE: I need a way to put these in the caller's namespace... Here's they're shared by all
+ # the subclasses which is not helpful. It will likely involve Kernel#caller and binding.
+ # Maybe class_eval, wraped around def field.
+
+
+ define_method(m) do instance_variable_get("@#{m}") end
+
+ define_method("#{m}=") do |val|
+ instance_variable_set("@#{m}",val)
+ end
+ end
+ end
+
+ def self.field_names
+ class_variable_get(:@@field_names)
+ end
+ def self.field_types
+ class_variable_get(:@@field_types)
+ end
+
def field_names
- raise "You need to override field_names (#{self.class})"
+ self.class.send(:class_variable_get, :@@field_names)
end
- def self.undump(format, file=[])
- #raise "Format not defined (#{@format})" unless self.method_defined?("to_#{@format}")
- #puts "LOAD: from_#{format}"
- send("from_#{format}", file)
+ def field_types
+ self.class.send(:class_variable_get, :@@field_types)
end
+
+ def format=(v)
+ raise "Unsupported format: #{v}" unless SUPPORTED_FORMATS.member?(v)
+ @format = v
+ end
- def dump(format="yaml", with_titles=true)
- #raise "Format not defined (#{@format})" unless self.method_defined?("to_#{@format}")
- #puts "DUMP: to_#{format}"
- self.send("to_#{format}", with_titles)
+ def dump(format=nil, with_titles=true)
+ format ||= @format
+ raise "Format not defined (#{format})" unless SUPPORTED_FORMATS.member?(format)
+ send("to_#{format}", with_titles)
end
+ def self.from_file(file_path=nil, format=nil)
+ raise "Cannot read file (#{file_path})" unless File.exists?(file_path)
+ format = format || File.extname(file_path).tr('.', '')
+ me = send("from_#{format}", FileUtil.read_file_to_array(file_path))
+ me.format = format
+ me
+ end
+ def to_file(file_path=nil, with_titles=true)
+ raise "Cannot store to nil path" if file_path.nil?
+ format = File.extname(file_path).tr('.', '')
+ format ||= @format
+ FileUtil.write_file(file_path, dump(format, with_titles))
+ end
+
+
+ def self.from_hash(from={})
+ me = self.new
+
+ return me if !from || from.empty?
+
+ fnames = field_names
+ fnames.each_with_index do |key,index|
+
+ value = from[key]
+
+ # TODO: Correct this horrible implementation (sorry, me. It's just one of those days.)
+
+ if field_types[index] == Time
+ value = Time.parse(from[key].to_s)
+ elsif field_types[index] == DateTime
+ value = DateTime.parse(from[key].to_s)
+ elsif field_types[index] == TrueClass
+ value = (from[key].to_s == "true")
+ elsif field_types[index] == Float
+ value = from[key].to_f
+ elsif field_types[index] == Integer
+ value = from[key].to_i
+ end
+
+ me.send("#{key}=", value) if self.method_defined?("#{key}=")
+ end
+ me
+ end
def to_hash(with_titles=true)
tmp = {}
-
field_names.each do |fname|
- tmp[fname] = self.send(fname.to_s)
+ tmp[fname] = self.send(fname)
end
-
tmp
end
+
+ def self.from_yaml(from=[])
+ # from is an array of strings
+ from_str = from.join('')
+ hash = YAML::load(from_str)
+ hash = from_hash(hash) if hash.is_a? Hash
+ hash
+ end
def to_yaml(with_titles=true)
- require 'yaml'
to_hash.to_yaml
end
+
+ def self.from_json(from=[])
+ require 'json'
+ # from is an array of strings
+ from_str = from.join('')
+ tmp = JSON::load(from_str)
+ hash_sym = tmp.keys.inject({}) do |hash, key|
+ hash[key.to_sym] = tmp[key]
+ hash
+ end
+ hash_sym = from_hash(hash_sym) if hash_sym.is_a? Hash
+ hash_sym
+ end
+ def to_json(with_titles=true)
+ require 'json'
+ to_hash.to_json
+ end
+
def to_delimited(with_titles=false, delim=',')
values = []
field_names.each do |fname|
values << self.send(fname.to_s) # TODO: escape values
end
@@ -65,54 +168,37 @@
to_delimited(with_titles, "\t")
end
def to_csv(with_titles=false)
to_delimited(with_titles, ',')
end
+ def self.from_tsv(from=[])
+ self.from_delimited(from, "\t")
+ end
+ def self.from_csv(from=[])
+ self.from_delimited(from, ',')
+ end
-
def self.from_delimited(from=[],delim=',')
return if from.empty?
# We grab an instance of the class so we can
hash = {}
fnames = values = []
if (from.size > 1 && !from[1].empty?)
fnames = from[0].chomp.split(delim)
values = from[1].chomp.split(delim)
else
- fnames = self.new.field_names
+ fnames = self.field_names
values = from[0].chomp.split(delim)
end
fnames.each_with_index do |key,index|
next unless values[index]
- number_or_string = (values[index].match(/[\d\.]+/)) ? values[index].to_f : values[index]
- hash[key.to_sym] = number_or_string
+ hash[key.to_sym] = values[index]
end
+ hash = from_hash(hash) if hash.is_a? Hash
hash
end
- def self.from_tsv(from=[])
- self.from_delimited(from, "\t")
- end
- def self.from_csv(from=[])
- self.from_delimited(from, ',')
- end
-
- def self.from_hash(from={})
- return if !from || from.empty?
- me = self.new
- fnames = me.to_hash.keys
- fnames.each do |key|
- # NOTE: this will skip generated values b/c they don't have a setter method
- me.send("#{key}=", from[key]) if self.method_defined?("#{key}=")
- end
- me
- end
- def self.from_yaml(from=[])
- require 'yaml'
- # from is an array of strings
- from_str = from.join('')
- YAML::load(from_str)
- end
-
+
+
end
-end
\ No newline at end of file
+end