37 Commits
v0.1 ... v0.3.0

Author SHA1 Message Date
f2aad98ff9 upped version in step 2015-10-07 15:08:16 +03:00
fbae6a0276 bundler is back, instructions must ahve been for rspec 2015-07-18 12:59:32 +03:00
8873fc9f77 another config test. tests all green at home 2015-07-18 12:46:56 +03:00
02801264bd remove climate id, stored in travis 2015-07-18 12:44:02 +03:00
82be0c42cd fix gemfile 2015-07-18 12:37:33 +03:00
87c39fe903 code climate and badges 2015-07-18 12:33:00 +03:00
9e49f2e725 add travis 2015-07-18 11:57:49 +03:00
e71b8d8010 bumped for release 2015-07-09 22:18:58 +03:00
1a037f616e same fix again 2015-06-19 20:16:40 +03:00
921bb76f83 fix parfait ist scope 2015-06-19 20:13:28 +03:00
9adf0b7e00 minor ref change 2015-06-19 19:48:36 +03:00
0868ef5770 leave numbers in refs 2015-06-19 19:42:55 +03:00
3eb6870bff add named references
object may determine own reference by
defining sof_reference_name
(some tests but surely more needed)
2015-06-19 19:34:27 +03:00
ca803225c6 breadth first traversal
should fix referencing
2015-06-19 16:53:19 +03:00
4754ba60be adds classes derived from array and hash
These are used in parfait and were coming out wrong
should be fixed, but hash tests weak
2015-06-19 12:26:10 +03:00
f00364cd18 shifting code about
writer creates all nodes (not delegated)
Check for class name, just as in members
as is_a will lead to wrong results
2015-06-18 17:52:28 +03:00
78f0108166 push more responsibility down to node
expose simple attribute
write long or short
many test got more compact, good
2015-06-17 21:16:39 +03:00
72fa26b00e make class a value
and minor others
2015-06-16 18:51:30 +03:00
6f9d4af3cd move array and hash
to _node names as that is what they are
2015-06-16 18:38:43 +03:00
293ddb3b69 include list and dictionary as arr/hash 2015-06-16 18:13:27 +03:00
389b5f9e1a same - change as for array 2015-06-16 18:12:43 +03:00
7d01c1193c fix tests according to array change 2015-06-16 18:12:15 +03:00
1ee0f7c006 add space after -
list items start with -
but without the space one can’t distinguish from -1
2015-06-16 18:11:56 +03:00
224bd84b01 fix references (again)
quite tricky logic, but now outputting at lowest level, as intended
2015-06-15 09:07:16 +03:00
4853f944ef add mini test for volatile functionality 2015-06-15 08:26:56 +03:00
037f8d80d2 Add missing comments 2015-06-15 08:21:15 +03:00
941f0a4886 fixes references
only writes references out once (was broken)
and only the first occurrence in write order
2015-06-14 22:55:41 +03:00
5ca9eed89c test for references
still failing
2015-06-14 00:06:49 +03:00
bdd67b3213 small fix
that doesn’t affect tests, hmm
2015-06-14 00:06:17 +03:00
aebbe17252 shorten code 2015-06-14 00:05:10 +03:00
db13ccbe41 improve read me 2015-06-13 23:09:17 +03:00
9492f29e66 split node classes into separate files 2015-06-13 23:09:02 +03:00
b00f49ade6 make to hash and array functions reusable for parfait 2015-05-20 10:41:05 +03:00
217eda2436 refactoring on tests 2015-05-15 21:03:11 +03:00
b5d44bf2f3 split the tests in to files 2015-05-15 20:59:33 +03:00
778d751c55 add test for new feature 2015-05-15 20:59:22 +03:00
c48b7a3403 object may define itself as value
by defining is_value and returning true
but then it must also define to_sof
2015-05-15 20:59:02 +03:00
27 changed files with 843 additions and 338 deletions

10
.travis.yml Normal file
View File

@ -0,0 +1,10 @@
language: ruby
sudo: false
cache: bundler
script:
- CODECLIMATE_REPO_TOKEN=40ed7f464af09c892b27adc2cd171c36b24992d8d527428285bc55c46fe2f0e0 ruby test/test_all.rb
rvm:
- 1.9.3
- 2.0.0
- 2.1.5
- 2.2.0

View File

@ -1,8 +1,11 @@
source "http://rubygems.org"
gem "rake"
gem "salama-object-file" , :path => "."
gem "codeclimate-test-reporter", require: nil
group :development do
gem "minitest"
gem "rubygems-tasks"

View File

@ -1,18 +1,33 @@
PATH
remote: .
specs:
salama-object-file (0.1)
salama-object-file (0.3.0)
GEM
remote: http://rubygems.org/
specs:
minitest (5.6.1)
codeclimate-test-reporter (0.4.8)
simplecov (>= 0.7.1, < 1.0.0)
docile (1.1.5)
json (1.8.3)
minitest (5.8.1)
rake (10.4.2)
rubygems-tasks (0.2.4)
simplecov (0.10.0)
docile (~> 1.1.0)
json (~> 1.8)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
PLATFORMS
ruby
DEPENDENCIES
codeclimate-test-reporter
minitest
rake
rubygems-tasks
salama-object-file!
BUNDLED WITH
1.10.6

View File

@ -1,3 +1,8 @@
[![Build Status](https://travis-ci.org/salama/salama-object-file.svg?branch=master)](https://travis-ci.org/salama/salama-object-file)
[![Gem Version](https://badge.fury.io/rb/salama-object-file.svg)](http://badge.fury.io/rb/salama-object-file)
[![Code Climate](https://codeclimate.com/github/salama/salama-object-file/badges/gpa.svg)](https://codeclimate.com/github/salama/salama-object-file)
[![Test Coverage](https://codeclimate.com/github/salama/salama-object-file/badges/coverage.svg)](https://codeclimate.com/github/salama/salama-object-file)
### Reading the code
Knowing what's going on while coding salama is not so easy: Hence the need to look at code dumps
@ -8,10 +13,11 @@ Hence the need for a code/object file format
I started with yaml, which is nice in that it has a solid implementation, reads and writes,
handles arbitrary objects, handles graphs and is a sort of readable text format.
But the "sort of" started to get to me, because
But the "sort of" started to get to me, because
- 1) it's way to verbose (long files, object groups over many pages) and
- 1) it's way to verbose (long files, object groups over many pages) and
- 2) does not allow for (easy) ordering.
- 3) has no concept of dumping only parts of an object
To fix this i started on Sof, with an eye to expand it.
@ -21,11 +27,18 @@ The main starting goal was quite like yaml, but with
- also short versions of arrays and hashes
- Shorter class names (no ruby/object or even ruby/struct stuff)
- references at the most shallow level
- an easy way to order attributes and specify attributes that should not be serialized
- a way specify attributes that should not be serialized
### Usage
The module's main useful api is
Sof::Writer.write(object_to_derialize)
### Salama Object File
Ok, so we all heard about object files, it's the things compilers create so we don't have to have
Ok, so we all heard about object files, it's the things compilers create so we don't have to have
huge compiles and can link them later.
Much fewer know what they include, and that is not because they are not very useful,
@ -33,13 +46,13 @@ but rather very complicated.
An object machine must off course have it's own object files, because:
- otherwise we'd have to express the object machine in c (nischt gut)
- otherwise we'd have to express the object machine in c formats (nischt gut)
- we would be forced to read the source every time (slow)
- we would have no language independant format
- we would have no language independent format
And i was going to get there, juust not now. I mean i think it's a great idea to have many languages
compile and run on the same object machine.
Not neccessarily my idea, but i haven't seen it pulled off. Not that i will.
And i was going to get there, just not now. I mean i think it's a great idea to have many languages
compile and run on the same object machine.
Not necessarily my idea, but i haven't seen it pulled off. Not that i will.
I just want to be able to read my compiled code!!

View File

@ -1,12 +1,23 @@
# Most of the external functionality is in the writer
# if you want some attributes not written also check volotile
require_relative "sof/util"
require_relative "sof/node"
require_relative "sof/simple_node"
require_relative "sof/object_node"
require_relative "sof/members"
require_relative "sof/volotile"
require_relative "sof/writer"
require_relative "sof/array"
require_relative "sof/hash"
require_relative "sof/array_node"
require_relative "sof/hash_node"
require_relative "sof/occurence"
Class.class_eval do
def to_sof
self.name
end
end
Symbol.class_eval do
def to_sof()
":#{to_s}"

View File

@ -1,51 +0,0 @@
module Sof
class ArrayNode < Node
def initialize ref
super(ref)
@children = []
end
attr_reader :children
def add c
@children << c
end
def out io , level = 0
super
short = true
children.each do |c|
short = false unless c.is_a?(SimpleNode)
end
if(short and children.length < 7 )
short_out(io,level)
else
long_out(io , level)
end
end
private
def short_out(io,level)
io.write("[")
@children.each_with_index do |child , i|
child.out(io , level + 1)
io.write ", " unless (i+1) == children.length
end
io.write("]")
end
def long_out io , level
indent = " " * level
@children.each_with_index do |child , i|
io.write "\n#{indent}" unless i == 0
io.write "-"
child.out(io , level + 1)
end
end
end
end
Array.class_eval do
def to_sof_node(writer , level , ref )
node = Sof::ArrayNode.new(ref)
each do |object|
node.add writer.to_sof_node( object , level + 1)
end
node
end
end

51
lib/sof/array_node.rb Normal file
View File

@ -0,0 +1,51 @@
module Sof
# A ArrayNode is a Node for an Array. See Node for when and how nodes are used in Sof.
# A ArrayNode has a list of children that hold the value node representations for
# the arrays values.
#
class ArrayNode < Node
def initialize ref
super(ref)
@children = []
end
def add c
@children << c
end
def is_simple?
return false if(@children.length > 7 )
short = true
@children.each do |c|
short = false unless c.is_simple?
end
short
end
# This defines the short output which is basically what you would write in ruby
# ie [ value1 , value2 , ...]
# The short is used for 7 or less SimpleNodes
def short_out(io,level)
io.write("[")
@children.each_with_index do |child , i|
child.out(io , level + 1 )
io.write ", " unless (i+1) == @children.length
end
io.write("]")
end
# Arrays start with the minus on each line "-"
# and each line has the value
def long_out io , level
indent = " " * level
@children.each_with_index do |child , i|
io.write "\n#{indent}" unless i == 0
io.write "- "
child.out(io , level + 1)
end
end
end
end

View File

@ -1,60 +0,0 @@
module Sof
class HashNode < Node
def initialize ref
super(ref)
@children = []
end
attr_reader :children
def add key , val
@children << [key,val]
end
def out io , level = 0
super
short = true
children.each do |k,v|
short = false unless k.is_a?(SimpleNode)
short = false unless v.is_a?(SimpleNode)
end
if(short and children.length < 7 )
short_out(io,level)
else
long_out(io , level)
end
end
def short_out(io,level)
io.write("{")
children.each_with_index do |child , i|
key , val = child
key.out(io , level + 1)
io.write " => "
val.out(io , level + 1)
io.write ", " unless (i+1) == children.length
end
io.write("}")
end
def long_out io , level
indent = " " * level
children.each_with_index do |child , i|
key , val = child
io.write "\n#{indent}" unless i == 0
io.write "-"
key.out(io , level + 1)
io.write " => "
val.out(io , level + 1)
end
end
end
end
Hash.class_eval do
def to_sof_node(writer , level , ref)
node = Sof::HashNode.new(ref)
each do |key , object|
k = writer.to_sof_node( key ,level + 1)
v = writer.to_sof_node( object ,level +1)
node.add(k , v)
end
node
end
end

55
lib/sof/hash_node.rb Normal file
View File

@ -0,0 +1,55 @@
module Sof
# A HashNode is a Node for a Hash. See Node for when and how nodes are used in Sof.
# A HashNode has a list of children that hold the key/value node representations for
# the hashes keys and values.
class HashNode < Node
def initialize ref
super(ref)
@children = []
end
def add key , val
@children << [key,val]
end
def is_simple?
return false if(@children.length > 7 )
@children.each do |k,v|
return false unless k.is_simple?
return false unless v.is_simple?
end
true
end
# This defines the short output which is basically what you would write in ruby
# ie { key1 => value1 , ... }
# The short is used for 7 or less SimpleNodes
def short_out(io,level)
io.write("{")
@children.each_with_index do |child , i|
key , val = child
key.out(io , level + 1)
io.write " => "
val.out(io , level + 1)
io.write ", " unless (i+1) == @children.length
end
io.write("}")
end
# The long output is like an array of associations.
# Arrays start with the minus on each line "-"
# and each line has the association key => value, same as used for the {} syntax
def long_out io , level
indent = " " * level
@children.each_with_index do |child , i|
key , val = child
io.write "\n#{indent}" unless i == 0
io.write "- "
key.out(io , level + 1)
io.write " => "
val.out(io , level + 1)
end
end
end
end

View File

@ -1,48 +1,113 @@
module Sof
# Members are members of the graph to be written
# The class collects all reachable objects into a hash for further transformation
class Members
include Util
# initialize with the "root" object
# any object really that then becomes the root.
# the result is easier to read if it really is a root
# All reachable objects are collected into "objects" hash
# The class keeps a counter for references encountered and creates unique
# occurences for each object based on object_id (not == or ===)
def initialize root
@root = root
@counter = 1
@references = []
@objects = {}
@referenced = false
add(root , 0)
add_object( root , 0)
collect_level(0 , [root])
end
attr_reader :objects , :root , :referenced
def add object , level
return if is_value?(object)
attr_reader :objects , :root
private
# add object (as occurence) if it doesn't exist
# return object or nil
def add_object object , level
# see if we we came accross this before
if( occurence = @objects[object.object_id] )
#puts "reset level #{level} at #{occurence.level}"
#puts "reset level #{level} at #{occurence.level}" if occurence.referenced #== 19
if occurence.level > level
occurence.level = level
#always store the most shallow level
occurence.level = level
end
# and only one Occurence for each object, create a reference for the second occurence
unless occurence.referenced
#puts "referencing #{@counter} , at level #{level}/#{occurence.level} "
occurence.set_reference(@counter)
@counter = @counter + 1
# puts "referencing #{@counter} #{occurence.object.name}, at level #{level}/#{occurence.level} " if @counter == 23
# puts "referencing #{@counter} #{occurence.object.name}, at level #{level}/#{occurence.level} " if @counter == 19
if object.respond_to? :sof_reference_name
reference = object.sof_reference_name
reference = reference.to_s.gsub(/\s|\W/ , "") #remove space and stuff
if( @references.include?(reference) or reference.empty?)
reference = "#{reference}-#{@counter}"
@counter = @counter + 1
end
else
reference = @counter.to_s
@counter = @counter + 1
end
occurence.set_reference(reference)
@references << reference
end
return
return nil
end
o = Occurence.new( object , level )
@objects[object.object_id] = o
attributes = attributes_for(object)
attributes.each do |a|
val = get_value( object , a)
add(val , level + 1)
end
if( object.is_a? Array )
object.each do |a|
add(a , level + 1)
# if first time see, create and store Occurence
@objects[object.object_id] = Occurence.new( object , level )
return object
end
# recursively find reachable objects from this level of objects
# this is called from the initialize and is private
# we go through the tree in breadth first (which is a little more effort) to catch lowest
# references.
def collect_level level , objects
next_level = Array.new
#puts "collect level #{level} #{objects.length}"
objects.each do |object|
#puts "collect level #{level} #{object.object_id}"
# not storing simple (value) objects
next if is_value?(object)
case object.class.name
when "Array" , "Parfait::List"
collect_array object , next_level
when "Hash" , "Parfait::Dictionary"
collect_hash object, next_level
else
# and recursively add attributes
attributes = attributes_for(object)
attributes.each do |a|
val = get_value( object , a)
next_level << val
end
#TODO get all superclsses here, but this covers 99% so . . moving on
superclasses = [object.class.superclass.name]
if superclasses.include?( "Array") or superclasses.include?( "Parfait::List")
collect_array object, next_level
end
if superclasses.include?( "Hash") or superclasses.include?( "Parfait::Dictionary")
collect_hash object, next_level
end
end
end
if( object.is_a? Hash )
object.each do |a,b|
add(a , level + 1)
add(b , level + 1)
end
new_objects = next_level.collect { |o| add_object(o , level + 1) }
new_objects.compact!
# recurse , but break off if hit bottom
collect_level( level + 1 , new_objects) unless new_objects.empty?
end
# and hash keys/values
def collect_hash hash , next_level
hash.each do |a,b|
next_level << a
next_level << b
end
end
# and array values
def collect_array array , next_level
array.each do |a|
next_level << a
end
end
end

View File

@ -1,58 +1,65 @@
# We transform objects into a tree of nodes
module Sof
#abstract base class for nodes in the tree
# may be referenced (should be a simple name or number)
# Before writing the objects are transformed into a tree of nodes.
# as the Members (all objects) are a graph (not tree) this is achieved by referencing
#
# There are only two subclasses, SimpleNode and ObejctNode, for simple or not
# The base class only holds the referenced flag
# Node implements the out function which just checks wether a node is_simple?
# and either calls short_out or long_out
#
# Notice that different instances of the same clas may be simple or not
# So deriving classes must implement is_simple? and accordingly long/and or short_out
class Node
include Util
# only has one attribute, the referenced flag
# This could be anything, but we use a simple number counter which is handled in the Members
# class during construction of the occurrence hash
def initialize ref
#puts "node has ref #{self.class}:#{ref}" if ref
@referenced = ref
end
# must be able to output to a stream
attr_reader :referenced
# This ochastrates the output of derived classes to the stream
# It writes any possible reference and sees if the noe is_simple? (see there)
# and calls long / or short_out respectively
def out io ,level
io.write "&#{@referenced} " if @referenced
if( is_simple? )
short_out(io,level)
else
long_out(io,level)
end
end
# Determine wether node is simple, meaning short, in the 30 char region
# The point of hoisting this property into the public api is that
# other nodes may ask of their children and output accordingly
def is_simple?
raise "abstact function is_simple called for #{self}"
end
# helper function to return the output as a string
# ie creates stringio, calls out and returns the string
def as_string(level)
io = StringIO.new
out(io,level)
io.string
end
attr_reader :referenced
end
class SimpleNode < Node
def initialize data , ref = nil
super(ref)
@data = data
private
def short_out io , level
raise "abstact function short_out called for #{self}"
end
attr_reader :data
def out io , level
super(io,level)
io.write(data)
end
end
class ObjectNode < Node
def initialize data , ref
super(ref)
@data = data
@children = []
end
attr_reader :children , :data
def add k , v
@children << [k,v]
end
def out io , level = 0
super
io.write(@data)
indent = " " * (level + 1)
@children.each_with_index do |child , i|
k , v = child
io.write "\n#{indent}"
k.out(io , level + 2)
io.write " "
v.out(io , level + 2)
end
def long_out io , level
raise "abstact function long_out called for #{self}"
end
end
end

78
lib/sof/object_node.rb Normal file
View File

@ -0,0 +1,78 @@
module Sof
# ObjectNode means node with structure
# ie arrays and hashes get transformed into these too, as well as objects with attributes
class ObjectNode < Node
# init with a string, much like a simple node
# structure is added after construction and kept in a children array
def initialize name , ref
super(ref)
@name = name
@simple = {}
@complex = {}
@super = nil # if derived from array or hash
end
# super is a hash or array, if the class of object derives from Hash/Array
def add_super s
@super = s
end
# attributes hold key value pairs
def add k , v
raise "Key should be symbol not #{k}" unless k.is_a? Symbol
if( v.is_simple?)
@simple[k] = v
else
@complex[k] = v
end
end
# simple when no complex attributes and any
# possible super is also simple
def is_simple?
if( @referenced.nil? and @complex.empty? and head.length < 30 )
unless(@super and !@super.is_simple?)
return true
end
end
false
end
# write out at the given level
# level determines the indentation (level * space)
# write out the data and then the children (always key value on one line)
def long_out io , level
io.write(head)
indent = " " * (level + 1)
@complex.each do |k,v|
io.write "\n#{indent}"
io.write ":#{k}"
io.write " "
v.out(io , level + 1)
end
if(@super)
io.write " "
@super.long_out(io,level)
end
end
def short_out io , level
io.write head
if(@super)
if( @super.is_simple? )
@super.short_out(io,level)
else
@super.long_out(io,level)
end
end
end
def head
body = @simple.collect {|a,val| ":#{a} => #{val.as_string(1)}"}.join(", ")
"#{@name}(#{body})"
end
end
end

View File

@ -1,16 +1,22 @@
module Sof
# simple struct like class to wrap an object and hold additionally
# - the shallowest level at which it was seen
# - A possible reference
# - the fact if it has been written (for referenced objects)
class Occurence
def initialize object , level
@object = object
@level = level
@referenced = nil
@referenced = nil
@written = nil
end
def set_reference r
raise "was set #{@referenced}" if @referenced
@referenced = r
end
attr_reader :object , :referenced
attr_accessor :level
attr_accessor :level , :written
end
end

30
lib/sof/simple_node.rb Normal file
View File

@ -0,0 +1,30 @@
module Sof
# What makes a node simple is that it has no further structure
#
# This may mean number/string/symbol, but also tiny arrays or objects with
# very little attributes. In other words simple/object is not the same distinction
# as value/not value
class SimpleNode < Node
# data is a string that is written out in "out" function
def initialize data
super(nil) # simple nodes can not be referenced, always value
@data = data
end
# A SimpleNode is always simple (aha).
# accordingly there is no long_out
def is_simple?
true
end
private
# just write the data given in construcor. simple. hence the name.
def short_out io , level
io.write(@data)
end
end
end

View File

@ -1,22 +1,35 @@
module Sof
# module for a couple of helpers that are needed in Members and Writer
module Util
# "value" is a property meaning simple/ not further structure
# hence int/bool/string etc are values
def is_value? o
return true if o == true
return true if o == false
return true if o == nil
return true if o.class == Fixnum
return true if o.class == Symbol
return true if o.class == String
return true if [true , false , nil].include?(o)
return true if [Fixnum, Symbol, String, Class].include?(o.class)
if o.respond_to? :is_value?
return true if o.is_value?
end
return false
end
# extract an attribute by the given name from the object
# done with instance_variable_get
def get_value(object,name)
object.instance_variable_get "@#{name}".to_sym
end
# return a list of attributes for a given object
# attributes may be supressed with Volotile
# TODO should be able to specify order too
def attributes_for object
Sof::Util.attributes(object)
end
# return a list of attributes for a given object
# attributes may be supressed with Volotile
# TODO should be able to specify order too
def self.attributes( object )
atts = object.instance_variables.collect{|i| i.to_s[1..-1].to_sym } # chop of @
atts - Volotile.attributes(object.class)

View File

@ -1,11 +1,27 @@
module Sof
class Volotile
# Volotile module keeps track of attributes that are not menat to be written
# The idea being similar to private methods. So not every little detail is relevant
# for the object. Some attribuets may be calculated, cached etc,
#
# There is only one useful call for the user, "add" attributes for a given class
#
module Volotile
@@mapping = { }
def self.attributes clazz
@@mapping[clazz] || []
end
# Add attributes that are then ommited from the sof writing process
# The clazz is the real class object (eg String), and thus the
# adding must happen after the class definition, often at the end of the file
# attributes are an array of Symbols
def self.add clazz , attributes
@@mapping[clazz] = attributes
end
private
# return the volotile attributes as an array (or empty array)
def self.attributes clazz
@@mapping[clazz] || []
end
end
end

View File

@ -1,10 +1,29 @@
module Sof
# this function writes the object (and all reachable objects) out as sof
# and returns a string
# For trees or graphs this works best by handing roots
# Internally this is done in three steps:
# - All reachable objects are collected, these are called Occurences and the Members class does
# the collecting. Members holds a hash of occurences
# - A tree of nodes is created from the occurences. Different node classes for different classes
# - The nodes are witten to a steam
def self.write object
writer = Writer.new(Members.new(object) )
writer.write
end
# The writer does the coordinating work of the stages (see write function)
class Writer
include Util
# Initialized with the Members (hash of occurences, see there)
def initialize members
@members = members
end
# main function, creates nodes from the occurences and writes the nodes to a string
# returns the sof formatted string for all objects
def write
node = to_sof_node(@members.root , 0)
io = StringIO.new
@ -12,49 +31,90 @@ module Sof
io.string
end
# create a Node (subclass) for an object at a given level.
# Level is mainly needed for the indenting
# from the object we get the Occurence and decide wether a reference node is needed
# simple objects (with more inner structure) become SimpleNodes
# Any structured object becomes a ObjectNode
# Hash and Array create their own nodes via to_sof_node functions on the classes
def to_sof_node(object , level)
if is_value?(object)
return SimpleNode.new(object.to_sof())
end
occurence = @members.objects[object.object_id]
raise "no object #{object}" unless occurence
if(level > occurence.level )
#puts "#{level} ? #{occurence.level} : ref #{occurence.referenced}"
if( occurence.referenced )
#puts "ref #{occurence.referenced} level #{level} at #{occurence.level}"
return SimpleNode.new("*#{occurence.referenced}")
return SimpleNode.new("->#{occurence.referenced}") unless (level == occurence.level )
if( occurence.written.nil? )
occurence.written = true
else
return SimpleNode.new("->#{occurence.referenced}")
end
end
ref = occurence.referenced
if(object.respond_to? :to_sof_node) #mainly meant for arrays and hashes
object.to_sof_node(self , level , ref )
case object.class.name
when "Array" , "Parfait::List"
# If a class defines to_sof_node it tells the write that it will generate Nodes itself
# this delegates to array_to_sof_node
array_to_sof_node(object , level , ref )
when "Hash" , "Parfait::Dictionary"
# and hash keys/values
hash_to_sof_node( object , level , ref)
else
object_sof_node(object , level , ref )
object_to_sof_node(object , level , ref)
end
end
def object_sof_node( object , level , ref)
if( object.is_a? Class )
return SimpleNode.new( object.name , ref )
end
head = object.class.name + "("
atts = {}
attributes_for(object).each() do |a|
# create an object node from the object
# simple nodes are returned for small objects
# small means only simple attributes and only 30 chars of them
# object nodes are basically arrays (see there)
#
# objects may be derived from array/hash. In that case the ObjectNode gets a super
# (either ArrayNode or HashNode)
def object_to_sof_node( object , level , ref)
node = ObjectNode.new(object.class.name , ref)
attributes_for(object).each() do |a|
val = get_value(object , a)
next if val.nil?
atts[a] = to_sof_node(val , level + 1)
node.add( a , to_sof_node( val , level + 1) )
end
immediate , extended = atts.partition {|a,val| val.is_a?(SimpleNode) }
head += immediate.collect {|a,val| "#{a.to_sof()} => #{val.as_string(level)}"}.join(", ") + ")"
return SimpleNode.new(head) if( ref.nil? and extended.empty? and head.length < 30 )
node = ObjectNode.new(head , ref)
extended.each do |a , val|
node.add( to_sof_node(a,level + 1) , val )
#TODO get all superclsses here, but this covers 99% so . . moving on
superclasses = [object.class.superclass.name]
if superclasses.include?( "Array") or superclasses.include?( "Parfait::List")
node.add_super( array_to_sof_node(object , level , ref ) )
end
if superclasses.include?( "Hash") or superclasses.include?( "Parfait::Dictionary")
node.add_super( hash_to_sof_node(object , level , ref ) )
end
node
end
def self.write object
writer = Writer.new(Members.new(object) )
writer.write
# Creates a ArrayNode (see there) for the Array.
# This mainly involves creating nodes for the children
def array_to_sof_node(array , level , ref )
node = Sof::ArrayNode.new(ref)
array.each do |object|
node.add to_sof_node( object , level + 1)
end
node
end
# Creates a HashNode (see there) for the Hash.
# This mainly involves creating nodes for key value pairs
def hash_to_sof_node(hash , level , ref)
node = Sof::HashNode.new(ref)
hash.each do |key , object|
k = to_sof_node( key ,level + 1)
v = to_sof_node( object ,level + 1)
node.add(k , v)
end
node
end
end
end

View File

@ -2,7 +2,7 @@
Gem::Specification.new do |s|
s.name = 'salama-object-file'
s.version = '0.1'
s.version = '0.3.0'
s.authors = ['Torsten Ruger']
s.email = 'torsten@villataika.fi'

View File

@ -1,4 +1,3 @@
require 'rubygems'
require 'bundler'
begin
@ -8,9 +7,29 @@ rescue Bundler::BundlerError => e
$stderr.puts "Run `bundle install` to install missing gems"
exit e.status_code
end
if ENV['CODECLIMATE_REPO_TOKEN']
require "codeclimate-test-reporter"
CodeClimate::TestReporter.start
end
require "minitest/autorun"
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'test'))
require 'salama-object-file'
module Checker
def check should
out = Sof.write(@out)
same = (should == out)
puts "Shouldda\n#{out}" unless same
assert_equal should , out
end
end
class ObjectWithAttributes
def initialize
@name = "some name"
@number = 1234
end
attr_accessor :extra , :volotile
end
OBJECT_STRING = "ObjectWithAttributes(:name => 'some name', :number => 1234)"
Sof::Volotile.add(ObjectWithAttributes , [:volotile])

View File

@ -1,106 +0,0 @@
require_relative "helper"
require "yaml"
class ObjectWithAttributes
def initialize
@name = "some name"
@number = 1234
end
attr_accessor :extra
end
OBJECT_STRING = "ObjectWithAttributes(:name => 'some name', :number => 1234)"
class BasicSof < MiniTest::Test
def check should
same = (should == @out)
puts "Shouldda\n#{@out}" unless same
assert_equal should , @out
end
def test_true
@out = Sof::Writer.write(true)
check "true"
end
def test_num
@out = Sof::Writer.write(124)
check "124"
end
def test_simple_object
@out = Sof::Writer.write(ObjectWithAttributes.new)
check "#{OBJECT_STRING}"
end
def test_object_extra_array
object = ObjectWithAttributes.new
object.extra = [:sym , 123]
@out = Sof::Writer.write(object)
check "#{OBJECT_STRING}\n :extra [:sym, 123]"
end
def test_simple_array
@out = Sof::Writer.write([true, 1234])
check "[true, 1234]"
end
def test_array_object
@out = Sof::Writer.write([true, 1234 , ObjectWithAttributes.new])
check "-true\n-1234\n-#{OBJECT_STRING}"
end
def test_array_array
@out = Sof::Writer.write([true, 1 , [true , 12 ]])
check "-true\n-1\n-[true, 12]"
end
def test_array_array_reverse
@out = Sof::Writer.write([ [true , 12 ], true, 1])
check "-[true, 12]\n-true\n-1"
end
def test_array_array_array
@out = Sof::Writer.write([true, 1 , [true , 12 , [true , 123 ]]])
check "-true\n-1\n--true\n -12\n -[true, 123]"
end
def test_array_array_object
@out = Sof::Writer.write([true, 1 , [true , 12 , ObjectWithAttributes.new]])
check "-true\n-1\n--true\n -12\n -#{OBJECT_STRING}"
end
def test_simple_hash
@out = Sof::Writer.write({ one: 1 , tru: true })
check "{:one => 1, :tru => true}"
end
def test_hash_object
@out = Sof::Writer.write({ one: 1 , two: ObjectWithAttributes.new })
check "-:one => 1\n-:two => #{OBJECT_STRING}"
end
def test_array_hash
@out = Sof::Writer.write([true, 1 , { one: 1 , tru: true }])
check "-true\n-1\n-{:one => 1, :tru => true}"
end
def test_hash_array
@out = Sof::Writer.write({ one: [1 , ObjectWithAttributes.new] , two: true })
check "-:one => -1\n -#{OBJECT_STRING}\n-:two => true"
end
def test_array_recursive
ar = [true, 1 ]
ar << ar
@out = Sof::Writer.write(ar)
check "&1 [true, 1, *1]"
end
def test_object_recursive
object = ObjectWithAttributes.new
object.extra = object
@out = Sof::Writer.write(object)
check "&1 ObjectWithAttributes(:name => 'some name', :number => 1234, :extra => *1)"
end
def test_object_inline
object = ObjectWithAttributes.new
object.extra = Object.new
@out = Sof::Writer.write(object)
check "ObjectWithAttributes(:name => 'some name', :number => 1234, :extra => Object())"
end
def test_class
@out = Sof::Writer.write(ObjectWithAttributes)
check "ObjectWithAttributes"
end
def test_class_ref
object = ObjectWithAttributes.new
object.extra = ObjectWithAttributes
ar = [object , ObjectWithAttributes]
@out = Sof::Writer.write(ar)
check "-ObjectWithAttributes(:name => 'some name', :number => 1234, :extra => *1)\n-&1 ObjectWithAttributes"
end
end

6
test/test_all.rb Normal file
View File

@ -0,0 +1,6 @@
require_relative "test_basic"
require_relative "test_object"
require_relative "test_ext"
require_relative "test_refs"
require_relative "test_super"
require_relative "test_names"

48
test/test_basic.rb Normal file
View File

@ -0,0 +1,48 @@
require_relative "helper"
class BasicSof < MiniTest::Test
include Checker
def test_true
@out = true
check "true"
end
def test_string
@out = "true"
check "'true'"
end
def test_num
@out = 124
check "124"
end
def test_simple_array
@out = [true, 1234]
check "[true, 1234]"
end
def test_array_array
@out = [true, 1 , [true , 12 ]]
check "[true, 1, [true, 12]]"
end
def test_array_array_reverse
@out = [ [true , 12 ], true, 1]
check "[[true, 12], true, 1]"
end
def test_array_array_array
@out = [true, 1 , [true , 12 , [true , 123 ]]]
check "[true, 1, [true, 12, [true, 123]]]"
end
def test_simple_hash
@out = { :one => 1 , :tru => true }
check "{:one => 1, :tru => true}"
end
def test_array_hash
@out = [true, 1 , { :one => 1 , :tru => true }]
check "[true, 1, {:one => 1, :tru => true}]"
end
def test_array_recursive
ar = [true, 1 ]
ar << ar
@out = ar
check "&1 [true, 1, ->1]"
end
end

31
test/test_ext.rb Normal file
View File

@ -0,0 +1,31 @@
require_relative "helper"
class FailValue
def initialize str
@name = str
end
def is_value?
true
end
end
class BasicValue < FailValue
def to_sof
"'#{@name}'"
end
end
class ObjectSof < MiniTest::Test
include Checker
def test_to_sof
assert_raises NoMethodError do
Sof::Writer.write(FailValue.new("name"))
end
end
def test_basic
@out = BasicValue.new("name")
check "'name'"
end
end

29
test/test_names.rb Normal file
View File

@ -0,0 +1,29 @@
require_relative "helper"
class NamedRef < ObjectWithAttributes
def initialize name
super()
@name = name
end
def sof_reference_name
@name.to_s
end
end
class NamedTest < MiniTest::Test
include Checker
def test_object_one
object = NamedRef.new("one")
object.extra = [object]
@out = [ {:one => object} , object ]
check "- {:one => ->one}\n- &one NamedRef(:name => 'one', :number => 1234, :extra => [->one])"
end
def test_object_two
object = NamedRef.new("one")
object2 = NamedRef.new("two")
object.extra = [object2]
@out = [ {:one => object} , object2 ]
check "- - :one => NamedRef(:name => 'one', :number => 1234, :extra => [->two])\n- &two NamedRef(:name => 'two', :number => 1234)"
end
end

60
test/test_object.rb Normal file
View File

@ -0,0 +1,60 @@
require_relative "helper"
class ObjectSof < MiniTest::Test
include Checker
def test_simple_object
@out = ObjectWithAttributes.new
check "#{OBJECT_STRING}"
end
def test_object_extra_array
object = ObjectWithAttributes.new
object.extra = [:sym , 123]
@out = object
check "ObjectWithAttributes(:name => 'some name', :number => 1234, :extra => [:sym, 123])"
end
def test_array_object
@out = [true, 1234 , ObjectWithAttributes.new]
check "- true\n- 1234\n- #{OBJECT_STRING}"
end
def test_array_array_object
@out = [true, 1 , [true , 12 , ObjectWithAttributes.new]]
check "- true\n- 1\n- - true\n - 12\n - #{OBJECT_STRING}"
end
def test_hash_object
@out = { :one => 1 , :two => ObjectWithAttributes.new }
check "- :one => 1\n- :two => #{OBJECT_STRING}"
end
def test_hash_array
@out = { :one => [1 , ObjectWithAttributes.new] , :two => true }
check "- :one => - 1\n - #{OBJECT_STRING}\n- :two => true"
end
def test_object_recursive
object = ObjectWithAttributes.new
object.extra = object
@out = object
check "&1 ObjectWithAttributes(:name => 'some name', :number => 1234, :extra => ->1)"
end
def test_object_inline
object = ObjectWithAttributes.new
object.extra = Object.new
@out = object
check "ObjectWithAttributes(:name => 'some name', :number => 1234, :extra => Object())"
end
def test_volotile
@out = ObjectWithAttributes.new
@out.volotile = 42
check "#{OBJECT_STRING}"
end
def test_class
@out = ObjectWithAttributes
check "ObjectWithAttributes"
end
def test_class_ref
object = ObjectWithAttributes.new
object.extra = ObjectWithAttributes
ar = [object , ObjectWithAttributes]
@out = ar
check "- ObjectWithAttributes(:name => 'some name', :number => 1234, :extra => ObjectWithAttributes)\n- ObjectWithAttributes"
end
end

42
test/test_refs.rb Normal file
View File

@ -0,0 +1,42 @@
require_relative "helper"
class TestRefs < MiniTest::Test
include Checker
def setup
@hash = {}
@array =[]
end
def fill_some
@hash[:some] = @array
@array[1] = :one
end
def test_one_empty
@out = @array << @hash
check "[{}]"
end
def test_two_empty
@out = @array << @hash
@out << @hash
check "[&1 {}, ->1]"
end
def test_bigger
@out = [ { :one => @array , :two => [{ :three => @array}] } ]
check "[{:one => &1 [], :two => [{:three => ->1}]}]"
end
def test_object_ref
object = ObjectWithAttributes.new
object.extra = [object]
@out = [ {:one => object} , object ]
check "- {:one => ->1}\n- &1 ObjectWithAttributes(:name => 'some name', :number => 1234, :extra => [->1])"
end
def test_object_ref2
object = ObjectWithAttributes.new
object2 = ObjectWithAttributes.new
object.extra = [object2]
@out = [ {:one => object} , object2 ]
check "- - :one => ObjectWithAttributes(:name => 'some name', :number => 1234, :extra => [->2])\n- &2 ObjectWithAttributes(:name => 'some name', :number => 1234)"
end
end

54
test/test_super.rb Normal file
View File

@ -0,0 +1,54 @@
require_relative "helper"
class ASuper < Array
def initialize object
@object = object
end
attr_accessor :object
end
class HSuper < Hash
def initialize object
@object = object
end
attr_accessor :object
end
class TestSuper < MiniTest::Test
include Checker
def test_asuper_empty
@out = ASuper.new( [] )
check "ASuper(:object => [])[]"
end
def test_asuper_with_array
@out = ASuper.new( [1,2,3] )
check "ASuper(:object => [1, 2, 3])[]"
end
def test_asuper_as_array
@out = ASuper.new( nil )
@out << 1 << 2 << 3
check "ASuper()[1, 2, 3]"
end
def test_asuper_as_big_array
@out = ASuper.new( nil )
@out << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8
check "ASuper() - 1\n- 2\n- 3\n- 4\n- 5\n- 6\n- 7\n- 8"
end
def test_asuper_self_ref
@out = ASuper.new( self )
@out.object = @out
check "&1 ASuper(:object => ->1) "
end
def test_asuper_indirect_ref
object = ObjectWithAttributes.new
@out = ASuper.new( object )
object.extra = @out
@out << 1 << 2
check "&1 ASuper()\n :object ObjectWithAttributes(:name => 'some name', :number => 1234, :extra => ->1) - 1\n- 2"
end
def test_hsuper_empty
@out = HSuper.new( {} )
check "HSuper(:object => {}){}"
end
end