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" source "http://rubygems.org"
gem "rake"
gem "salama-object-file" , :path => "." gem "salama-object-file" , :path => "."
gem "codeclimate-test-reporter", require: nil
group :development do group :development do
gem "minitest" gem "minitest"
gem "rubygems-tasks" gem "rubygems-tasks"

View File

@ -1,18 +1,33 @@
PATH PATH
remote: . remote: .
specs: specs:
salama-object-file (0.1) salama-object-file (0.3.0)
GEM GEM
remote: http://rubygems.org/ remote: http://rubygems.org/
specs: 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) 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 PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
codeclimate-test-reporter
minitest minitest
rake
rubygems-tasks rubygems-tasks
salama-object-file! 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 ### Reading the code
Knowing what's going on while coding salama is not so easy: Hence the need to look at code dumps Knowing what's going on while coding salama is not so easy: Hence the need to look at code dumps
@ -12,6 +17,7 @@ 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. - 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. To fix this i started on Sof, with an eye to expand it.
@ -21,7 +27,14 @@ The main starting goal was quite like yaml, but with
- also short versions of arrays and hashes - also short versions of arrays and hashes
- Shorter class names (no ruby/object or even ruby/struct stuff) - Shorter class names (no ruby/object or even ruby/struct stuff)
- references at the most shallow level - 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 ### Salama Object File
@ -33,13 +46,13 @@ but rather very complicated.
An object machine must off course have it's own object files, because: 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 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 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. compile and run on the same object machine.
Not neccessarily my idea, but i haven't seen it pulled off. Not that i will. 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!! 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/util"
require_relative "sof/node" require_relative "sof/node"
require_relative "sof/simple_node"
require_relative "sof/object_node"
require_relative "sof/members" require_relative "sof/members"
require_relative "sof/volotile" require_relative "sof/volotile"
require_relative "sof/writer" require_relative "sof/writer"
require_relative "sof/array" require_relative "sof/array_node"
require_relative "sof/hash" require_relative "sof/hash_node"
require_relative "sof/occurence" require_relative "sof/occurence"
Class.class_eval do
def to_sof
self.name
end
end
Symbol.class_eval do Symbol.class_eval do
def to_sof() def to_sof()
":#{to_s}" ":#{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 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 class Members
include Util 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 def initialize root
@root = root @root = root
@counter = 1 @counter = 1
@references = []
@objects = {} @objects = {}
@referenced = false add_object( root , 0)
add(root , 0) collect_level(0 , [root])
end end
attr_reader :objects , :root , :referenced attr_reader :objects , :root
def add object , level private
return if is_value?(object) # 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] ) 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 if occurence.level > level
#always store the most shallow level
occurence.level = level occurence.level = level
end end
# and only one Occurence for each object, create a reference for the second occurence
unless occurence.referenced unless occurence.referenced
#puts "referencing #{@counter} , at level #{level}/#{occurence.level} " # puts "referencing #{@counter} #{occurence.object.name}, at level #{level}/#{occurence.level} " if @counter == 23
occurence.set_reference(@counter) # 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 @counter = @counter + 1
end end
return else
reference = @counter.to_s
@counter = @counter + 1
end end
o = Occurence.new( object , level ) occurence.set_reference(reference)
@objects[object.object_id] = o @references << reference
end
return nil
end
# 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 = attributes_for(object)
attributes.each do |a| attributes.each do |a|
val = get_value( object , a) val = get_value( object , a)
add(val , level + 1) next_level << val
end end
if( object.is_a? Array ) #TODO get all superclsses here, but this covers 99% so . . moving on
object.each do |a| superclasses = [object.class.superclass.name]
add(a , level + 1) 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 end
if( object.is_a? Hash )
object.each do |a,b|
add(a , level + 1)
add(b , level + 1)
end 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 end
end end

View File

@ -1,58 +1,65 @@
# We transform objects into a tree of nodes # We transform objects into a tree of nodes
module Sof 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 class Node
include Util 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 def initialize ref
#puts "node has ref #{self.class}:#{ref}" if ref
@referenced = ref @referenced = ref
end 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 def out io ,level
io.write "&#{@referenced} " if @referenced io.write "&#{@referenced} " if @referenced
if( is_simple? )
short_out(io,level)
else
long_out(io,level)
end 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) def as_string(level)
io = StringIO.new io = StringIO.new
out(io,level) out(io,level)
io.string io.string
end end
attr_reader :referenced
private
def short_out io , level
raise "abstact function short_out called for #{self}"
end end
class SimpleNode < Node def long_out io , level
def initialize data , ref = nil raise "abstact function long_out called for #{self}"
super(ref)
@data = data
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
end end
end 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 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 class Occurence
def initialize object , level def initialize object , level
@object = object @object = object
@level = level @level = level
@referenced = nil @referenced = nil
@written = nil
end end
def set_reference r def set_reference r
raise "was set #{@referenced}" if @referenced
@referenced = r @referenced = r
end end
attr_reader :object , :referenced attr_reader :object , :referenced
attr_accessor :level attr_accessor :level , :written
end end
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 Sof
# module for a couple of helpers that are needed in Members and Writer
module Util module Util
# "value" is a property meaning simple/ not further structure
# hence int/bool/string etc are values
def is_value? o def is_value? o
return true if o == true return true if [true , false , nil].include?(o)
return true if o == false return true if [Fixnum, Symbol, String, Class].include?(o.class)
return true if o == nil if o.respond_to? :is_value?
return true if o.class == Fixnum return true if o.is_value?
return true if o.class == Symbol end
return true if o.class == String
return false return false
end end
# extract an attribute by the given name from the object
# done with instance_variable_get
def get_value(object,name) def get_value(object,name)
object.instance_variable_get "@#{name}".to_sym object.instance_variable_get "@#{name}".to_sym
end 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 def attributes_for object
Sof::Util.attributes(object) Sof::Util.attributes(object)
end 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 ) def self.attributes( object )
atts = object.instance_variables.collect{|i| i.to_s[1..-1].to_sym } # chop of @ atts = object.instance_variables.collect{|i| i.to_s[1..-1].to_sym } # chop of @
atts - Volotile.attributes(object.class) atts - Volotile.attributes(object.class)

View File

@ -1,11 +1,27 @@
module Sof 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 = { } @@mapping = { }
def self.attributes clazz
@@mapping[clazz] || [] # Add attributes that are then ommited from the sof writing process
end # 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 def self.add clazz , attributes
@@mapping[clazz] = attributes @@mapping[clazz] = attributes
end end
private
# return the volotile attributes as an array (or empty array)
def self.attributes clazz
@@mapping[clazz] || []
end
end end
end end

View File

@ -1,10 +1,29 @@
module Sof 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 class Writer
include Util include Util
# Initialized with the Members (hash of occurences, see there)
def initialize members def initialize members
@members = members @members = members
end 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 def write
node = to_sof_node(@members.root , 0) node = to_sof_node(@members.root , 0)
io = StringIO.new io = StringIO.new
@ -12,48 +31,89 @@ module Sof
io.string io.string
end 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) def to_sof_node(object , level)
if is_value?(object) if is_value?(object)
return SimpleNode.new(object.to_sof()) return SimpleNode.new(object.to_sof())
end end
occurence = @members.objects[object.object_id] occurence = @members.objects[object.object_id]
raise "no object #{object}" unless occurence 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}" #puts "ref #{occurence.referenced} level #{level} at #{occurence.level}"
return SimpleNode.new("*#{occurence.referenced}") return SimpleNode.new("->#{occurence.referenced}") unless (level == occurence.level )
end if( occurence.written.nil? )
ref = occurence.referenced occurence.written = true
if(object.respond_to? :to_sof_node) #mainly meant for arrays and hashes
object.to_sof_node(self , level , ref )
else else
object_sof_node(object , level , ref ) return SimpleNode.new("->#{occurence.referenced}")
end end
end end
def object_sof_node( object , level , ref) ref = occurence.referenced
if( object.is_a? Class ) case object.class.name
return SimpleNode.new( object.name , ref ) 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_to_sof_node(object , level , ref)
end end
head = object.class.name + "("
atts = {} end
# 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| attributes_for(object).each() do |a|
val = get_value(object , a) val = get_value(object , a)
next if val.nil? next if val.nil?
atts[a] = to_sof_node(val , level + 1) node.add( a , to_sof_node( val , level + 1) )
end end
immediate , extended = atts.partition {|a,val| val.is_a?(SimpleNode) } #TODO get all superclsses here, but this covers 99% so . . moving on
head += immediate.collect {|a,val| "#{a.to_sof()} => #{val.as_string(level)}"}.join(", ") + ")" superclasses = [object.class.superclass.name]
return SimpleNode.new(head) if( ref.nil? and extended.empty? and head.length < 30 ) if superclasses.include?( "Array") or superclasses.include?( "Parfait::List")
node = ObjectNode.new(head , ref) node.add_super( array_to_sof_node(object , level , ref ) )
extended.each do |a , val| end
node.add( to_sof_node(a,level + 1) , val ) if superclasses.include?( "Hash") or superclasses.include?( "Parfait::Dictionary")
node.add_super( hash_to_sof_node(object , level , ref ) )
end end
node node
end end
def self.write object # Creates a ArrayNode (see there) for the Array.
writer = Writer.new(Members.new(object) ) # This mainly involves creating nodes for the children
writer.write 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 end

View File

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

View File

@ -1,4 +1,3 @@
require 'rubygems' require 'rubygems'
require 'bundler' require 'bundler'
begin begin
@ -8,9 +7,29 @@ rescue Bundler::BundlerError => e
$stderr.puts "Run `bundle install` to install missing gems" $stderr.puts "Run `bundle install` to install missing gems"
exit e.status_code exit e.status_code
end end
if ENV['CODECLIMATE_REPO_TOKEN']
require "codeclimate-test-reporter"
CodeClimate::TestReporter.start
end
require "minitest/autorun" 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' 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