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