voruby2-preview 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ require 'symphony/symphony'
@@ -0,0 +1,247 @@
1
+ class HomogeneousArray < Array
2
+ attr_reader :klass
3
+
4
+ def initialize(of_class, *spec)
5
+ of_class = [of_class] if !of_class.is_a?(Array)
6
+ @klass = of_class
7
+
8
+ spec.flatten.each { |obj| raise_type_error(obj) if !self.acceptable_type?(obj) }
9
+
10
+ super(*spec)
11
+ end
12
+
13
+ def raise_type_error(obj)
14
+ raise TypeError, "wrong argument type of #{obj.class} #{obj.inspect} (expected #{self.klass})"
15
+ end
16
+
17
+ def []=(*assign_spec)
18
+ raise_type_error(assign_spec.last) if !self.acceptable_type?(assign_spec.last)
19
+ super(*assign_spec)
20
+ end
21
+
22
+ def <<(obj)
23
+ raise_type_error(obj) if !self.acceptable_type?(obj)
24
+ super(obj)
25
+ end
26
+
27
+ def replace(array_of_obj)
28
+ array_of_obj.each { |obj| raise_type_error(obj) if !self.acceptable_type?(obj) }
29
+ super(array_of_obj)
30
+ end
31
+
32
+ def ==(ary)
33
+ return false if !ary.respond_to?(:size)
34
+ return false if self.size != ary.size
35
+
36
+ self.each_with_index do |el, i|
37
+ return false if !(el == ary[i])
38
+ end
39
+
40
+ return true
41
+ end
42
+
43
+ protected
44
+ def acceptable_type?(obj)
45
+ self.klass.find_all{ |k| obj.is_a?(k) }.size > 0
46
+ end
47
+ end
48
+
49
+ class MinimumSizedHomogeneousArray < HomogeneousArray
50
+ attr_reader :min_size
51
+
52
+ def initialize(min_size, of_class, *spec)
53
+ raise_size_error(spec.last.size, min_size) if spec.last.size < min_size
54
+
55
+ super(of_class, *spec)
56
+ @min_size = min_size
57
+ end
58
+
59
+ def delete(obj)
60
+ raise_size_error(self.size - 1, self.min_size) if self.size - 1 < self.min_size
61
+ super(obj)
62
+ end
63
+
64
+ def delete_at(index)
65
+ raise_size_error(self.size - 1, self.min_size) if self.size - 1 < self.min_size
66
+ super(index)
67
+ end
68
+
69
+ private
70
+ def raise_size_error(size, min_size=nil)
71
+ raise ArgumentError, "array of size #{size} can not shrink below a capacity of #{min_size || self.min_size}"
72
+ end
73
+ end
74
+
75
+ class AtLeastTwoArray < MinimumSizedHomogeneousArray
76
+ def initialize(of_class, *spec)
77
+ super(2, of_class, *spec)
78
+ end
79
+ end
80
+
81
+ class FixedSizedHomogeneousArray < HomogeneousArray
82
+ attr_reader :capacity
83
+
84
+ def initialize(capacity, of_class, *spec)
85
+ raise_capacity_error(spec.last.size, capacity) if spec.last.size != capacity
86
+
87
+ super(of_class, *spec)
88
+ @capacity = capacity
89
+ end
90
+
91
+ def []=(*assign_spec)
92
+ super(*assign_spec)
93
+ raise_capacity_error(self.size) if self.size != self.capacity
94
+ end
95
+
96
+ def <<(obj)
97
+ raise_capacity_error(self.size + 1) if self.size + 1 != self.capacity
98
+ super(obj)
99
+ end
100
+
101
+ def replace(array_of_obj)
102
+ aise_capacity_error(self.size + array_of_obj.size) if self.size + array_of_obj.size != self.capacity
103
+ super(array_of_obj)
104
+ end
105
+
106
+ def delete(obj); end
107
+ def delete_at(index); end
108
+
109
+ private
110
+ def raise_capacity_error(size, capacity=nil)
111
+ raise ArgumentError, "array incorrect size of #{size} (expected #{capacity || self.capacity})"
112
+ end
113
+ end
114
+
115
+ class Array
116
+ def to_homogeneous(of_type)
117
+ harray = HomogeneousArray.new(of_type)
118
+ self.each{ |item| harray << item }
119
+ harray
120
+ end
121
+ end
122
+
123
+ module Symphony
124
+ module ClassMethods
125
+ DEFAULT_OPTIONS = {
126
+ :of => Object,
127
+ :optional => false,
128
+ :nillable => false
129
+ }
130
+
131
+ attr_reader :symphony_definitions
132
+
133
+ def initialize_symphony_definitions
134
+ @symphony_definitions ||= []
135
+ end
136
+
137
+ def has(multiplicity, name, options={})
138
+ initialize_symphony_definitions()
139
+
140
+ raise ArgumentError, "multiplicity must be an integer or a symbol (i.e. :many)" if !multiplicity.is_a?(Integer) and !multiplicity.is_a?(Symbol)
141
+
142
+ options = DEFAULT_OPTIONS.merge(options)
143
+ defn = {:multiplicity => multiplicity, :method_name => name.to_sym, :options => options}
144
+ @symphony_definitions << defn
145
+
146
+ create_methods(defn)
147
+ end
148
+
149
+ def has_one(name, options)
150
+ has 1, name, options
151
+ end
152
+
153
+ def has_many(name, options)
154
+ has :many, name, options
155
+ end
156
+
157
+ def create_methods(defn)
158
+ # reader
159
+ class_eval "
160
+ def #{defn[:method_name]}
161
+ puts '#{defn[:method_name]}:' + self.class.symphony_definitions.find{ |d| d[:method_name] == :#{defn[:method_name]} }.inspect
162
+ @#{defn[:method_name]} || self.class.symphony_definitions.find{ |d| d[:method_name] == :#{defn[:method_name]} }[:options][:default]
163
+ end
164
+ "
165
+
166
+ # writer
167
+ check_method = (defn[:multiplicity] == :many or defn[:multiplicity] > 1) ? 'check_made_of_only' : 'check_type'
168
+ class_eval "
169
+ def #{defn[:method_name]}=(obj)
170
+ raise TypeError, \"#{defn[:method_name]} is not nillable\" if !#{defn[:options][:nillable]} and obj == nil
171
+ #{check_method}(#{defn[:options][:of]}, obj) if obj != nil or !#{defn[:options][:nillable]}
172
+
173
+ @#{defn[:method_name]} = obj
174
+ end
175
+ "
176
+ end
177
+
178
+ end
179
+
180
+ class << self
181
+ def included(klass)
182
+ super
183
+ klass.__send__(:extend, ClassMethods)
184
+ end
185
+ end
186
+ end
187
+
188
+ class Symphony::Base
189
+ include Symphony
190
+
191
+ def initialize(args={})
192
+ self.class.initialize_symphony_definitions()
193
+ validate(args)
194
+ assign_initial_values(args)
195
+ end
196
+
197
+ def ==(obj)
198
+ return false if !obj.is_a?(self.class)
199
+
200
+ self.class.symphony_definitions.each do |defn|
201
+ return false if self.send(defn[:method_name]) != obj.send(defn[:method_name])
202
+ end
203
+
204
+ return true
205
+ end
206
+
207
+ private
208
+
209
+ def check_type(klass, o)
210
+ raise TypeError, "wrong argument type of #{o.class} #{o.inspect} (expected #{klass})" if !o.is_a?(klass)
211
+ end
212
+
213
+ def check_made_of_only(klass, list)
214
+ list.each { |o| check_type(klass, o) }
215
+ end
216
+
217
+ def validate(args)
218
+ self.class.symphony_definitions.each do |defn|
219
+ raise ArgumentError, "#{defn[:method_name]} is missing but is required" if !defn[:options][:optional] and !args.has_key?(defn[:method_name])
220
+ raise TypeError, "#{defn[:method_name]} is not nillable" if args.has_key?(defn[:method_name]) and args[defn[:method_name]] == nil and !defn[:options][:nillable]
221
+
222
+ values = args[defn[:method_name]]
223
+ values = values.is_a?(Array) ? values : [values]
224
+
225
+ # if defn[:multiplicity].is_a?(Integer)
226
+ # raise ArgumentError, "wrong number of arguments for #{defn[:method_name]} (#{values.size} for #{defn[:multiplicity]})" if values.size != defn[:multiplicity]
227
+ # elsif defn[:multiplicity].is_a?(Symbol)
228
+ # raise ArgumentError, "don't know what a multiplicity of #{defn[:multiplicity]} means" if defn[:multiplicity] != :many
229
+ # end
230
+
231
+ raise ArgumentError, "don't know what a multiplicity of #{defn[:multiplicity]} means" if !defn[:multiplicity].is_a?(Integer) and defn[:multiplicity] != :many
232
+ end
233
+ end
234
+
235
+ def assign_initial_values(args)
236
+ self.class.symphony_definitions.each do |defn|
237
+ values = if !args[defn[:method_name]].is_a?(HomogeneousArray) and args[defn[:method_name]].is_a?(Array)
238
+ args[defn[:method_name]].to_homogeneous(defn[:options][:of])
239
+ else
240
+ check_type(defn[:options][:of], args[defn[:method_name]]) if !defn[:options][:optional] and (!defn[:options][:nillable] and args[defn[:method_name]] != nil)
241
+ args[defn[:method_name]]
242
+ end
243
+
244
+ send("#{defn[:method_name]}=", values)
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,186 @@
1
+ module VORuby
2
+ begin
3
+ require 'rfits'
4
+ rescue LoadError
5
+ @@rfits_available = false
6
+ else
7
+ @@rfits_available = true
8
+ end
9
+
10
+ # Reports whether the RFits[http://rubyforge.org/projects/rfits]
11
+ # FITS reading/writing package is installed or not.
12
+ def self.rfits?
13
+ @@rfits_available
14
+ end
15
+
16
+ # An array-like class that uses an XML::Node to back itself.
17
+ # Designed to work in concert with a VOTable::Base object
18
+ # or at least something with a _node_ method and a _new_
19
+ # method that takes a XML::Node object.
20
+ # Mixes in Enumerable for added functionality.
21
+ class HomogeneousNodeList
22
+ include Enumerable
23
+
24
+ # The XPath expression to the child node(s) of the list.
25
+ attr_reader :xpath
26
+
27
+ # The class of the sub-objects allowed in the list.
28
+ attr_reader :klass
29
+
30
+ # Create a new node list.
31
+ # _node_ is the XML::Node that all operation will be mirrored on.
32
+ # _xpath_ is the the XPath expression used to retrieve the
33
+ # children of _node_ that make up the list.
34
+ # _klass_ is the class of those children.
35
+ #
36
+ # vot = VOTable.new({
37
+ # :version => '1.1',
38
+ # :id => 'my_test_votable',
39
+ # :description => Description.new,
40
+ # :definitions => Definitions.new,
41
+ # :coordinate_systems => [Coosys.new],
42
+ # :params => [Param.new],
43
+ # :infos => [Info.new],
44
+ # :resources => [Resource.new]
45
+ # })
46
+ # node_list = HomogeneousNodeList.new(vot, 'COOSYS', VOTable::V_1::Coosys)
47
+ def initialize(node, xpath, klass)
48
+ @node = node.is_a?(XML::Node) ? node : node.node
49
+ @xpath = xpath
50
+ @klass = klass
51
+ end
52
+
53
+ # Iterate through each member of the list.
54
+ # node_list.each do |obj|
55
+ # puts obj
56
+ # end
57
+ def each
58
+ @node.find(*self.xpath).each do |e|
59
+ yield klass.new(e)
60
+ end
61
+ end
62
+
63
+ # Replace the current member of the list with
64
+ # those specified.
65
+ # node_list.replace(Coosys.new, Coosys.new, Coosys.new)
66
+ def replace(*args)
67
+ self.clear
68
+ args.flatten.each do |a|
69
+ self << a
70
+ end
71
+ end
72
+
73
+ # Append a new member to the end of the list.
74
+ # node_list << Coosys.new
75
+ def <<(obj)
76
+ raise "Expected item of type #{@klass} but was #{obj.class}" if !obj.is_a?(@klass)
77
+
78
+ obj.node.name = self.xpath.first.split(':').last if self.xpath.is_a?(Array)
79
+
80
+ last_in_list = @node.find_first("*[local-name()='#{obj.node.name}'][last()]")
81
+ last_in_list ? last_in_list.next = obj.node : @node.child_add(obj.node)
82
+ end
83
+
84
+ # Prepend a new member to the beginning of the list.
85
+ # node_list.prepend(Coosys.new)
86
+ def prepend(obj)
87
+ raise "Expected item of type #{@klass} but was #{obj.class}" if !obj.is_a?(@klass)
88
+
89
+ obj.node.name = self.xpath.first.split(':').last if self.xpath.is_a?(Array)
90
+
91
+ first_in_list = @node.find_first("*[local-name()='#{obj.node.name}'][1]")
92
+ first_in_list ? first_in_list.prev = obj.node : @node.child_add(obj.node)
93
+ end
94
+
95
+ # Retrieve the ith member of the list. i may be less than one.
96
+ # coosys2 = node_list[1]
97
+ def [](i)
98
+ i = self.length + i if i < 0 # support negative indices
99
+
100
+ expr = self.xpath.is_a?(Array) ?
101
+ ["#{self.xpath.first}[#{i+1}]", self.xpath.last] :
102
+ ["#{self.xpath}[#{i+1}]"]
103
+
104
+ klass.new(@node.find_first(*expr))
105
+ end
106
+
107
+ # Set the ith member of the list. Setting a member to a location
108
+ # longer than the current list, appends that member to the end
109
+ # of the list. In other words, there are no nil members allowed.
110
+ # node_list[1] = Coosys.new
111
+ def []=(i, obj)
112
+ raise "Expected item of type #{@klass} but was #{obj.class}" if !obj.is_a?(@klass)
113
+
114
+ i = self.length + i if i < 0 # support negative indices
115
+
116
+ expr = self.xpath.is_a?(Array) ?
117
+ ["#{self.xpath.first}[#{i+1}]", self.xpath.last] :
118
+ ["#{self.xpath}[#{i+1}]"]
119
+
120
+ obj.node.name = self.xpath.first.split(':').last if self.xpath.is_a?(Array)
121
+
122
+ this_node = @node.find_first(*expr)
123
+ this_node ? this_node.replace_with(obj.node) : self << obj
124
+ end
125
+
126
+ # Delete the ith member in the list.
127
+ # node_list.delete_at(1) # delete the second coordinate system
128
+ def delete_at(i)
129
+ expr = self.xpath.is_a?(Array) ?
130
+ ["#{self.xpath.first}[#{i+1}]", self.xpath.last] :
131
+ ["#{self.xpath}[#{i+1}]"]
132
+ @node.find_first(*expr).remove!
133
+ end
134
+
135
+ # Clear the list of all its members.
136
+ # node_list.clear
137
+ def clear
138
+ @node.find(*self.xpath).each do |el|
139
+ el.remove!
140
+ end
141
+ end
142
+
143
+ # Pick the first member of the list.
144
+ # coosys1 = node_list.first
145
+ def first
146
+ expr = self.xpath.is_a?(Array) ?
147
+ ["#{self.xpath.first}[1]", self.xpath.last] :
148
+ ["#{self.xpath}[1]"]
149
+ klass.new(@node.find_first(*expr))
150
+ end
151
+
152
+ # Pick the last member of the list
153
+ # coosys_last = node_list.last
154
+ def last
155
+ expr = self.xpath.is_a?(Array) ?
156
+ ["#{self.xpath.first}[last()]", self.xpath.last] :
157
+ ["#{self.xpath}[last()]"]
158
+ klass.new(@node.find_first(*expr))
159
+ end
160
+
161
+ # Find the number of members in the list.
162
+ # node_list.length # => 4
163
+ def length
164
+ @node.find(*self.xpath).length
165
+ end
166
+
167
+ # Alias for #length.
168
+ def size
169
+ length
170
+ end
171
+
172
+ # Two lists are equivalent if their lengths are equal
173
+ # and the members of the lists are equal to each other
174
+ # (and in the same order).
175
+ def ==(obj)
176
+ return false if self.length != obj.length
177
+
178
+ self.each_with_index do |item, i|
179
+ return false if obj[i] != item
180
+ end
181
+
182
+ true
183
+ end
184
+ end
185
+
186
+ end
@@ -0,0 +1,121 @@
1
+ class XML::Node
2
+ def replace_with(other)
3
+ self.next = other
4
+ remove!
5
+ end
6
+ end
7
+
8
+ module XML
9
+ module Object
10
+ # A (essentially abstract) class that can be used in the
11
+ # creation of domain objects backed by XML documents.
12
+ class Base
13
+ # The XML::Node used to store the state of the domain object.
14
+ attr_reader :node
15
+
16
+ # Create a domain object from an XML file on disk.
17
+ # _filename_:: the name of the file to parse
18
+ #
19
+ # votable = VOTable::V1_1::VOTable.from_file('myvotable.xml')
20
+ def self.from_file(filename)
21
+ self.new(XML::Document.file(filename))
22
+ end
23
+
24
+ # Get the name of the element when serialized
25
+ # to XML that the object in question will take on.
26
+ # By default, the name of the class.
27
+ def self.element_name
28
+ self.to_s
29
+ end
30
+
31
+ # Create a new domain object from a number of source formats.
32
+ # _xml_ may be one of
33
+ # * a string
34
+ # * an IO object (i.e. a File object)
35
+ # * a LibXML Document (i.e. XML::Document)
36
+ # * a LibXML Node (i.e. XML::Node)
37
+ # * nothing (to create a blank object)
38
+ # * a hash where the keys of the hash represent the method to call
39
+ #
40
+ # Assuming VOTable::V1_1::VOTable is a subclass of Base...
41
+ #
42
+ # votable = VOTable::V1_1::VOTable.new('<VOTABLE version="1.1"/>')
43
+ # votable = VOTable::V1_1::VOTable.new(File.new('myvotable.xml')) # you could also use #from_file
44
+ # votable = VOTable::V1_1::VOTable.new(XML::Document.file('myvotable.xml'))
45
+ # votable = VOTable::V1_1::VOTable.new(XML::Node.new('VOTABLE'))
46
+ # votable = VOTable::V1_1::VOTable.new()
47
+ # votable = VOTable::V1_1::VOTable.new(:version => '1.1')
48
+ def initialize(xml=nil)
49
+ @node = case xml
50
+ when String
51
+ parser = XML::Parser.new
52
+ parser.string = xml
53
+ parser.parse.root
54
+ when IO
55
+ parser = XML::Parser.new
56
+ parser.io = xml
57
+ parser.parse.root
58
+ when XML::Document
59
+ xml.root
60
+ when XML::Node
61
+ xml
62
+ when NilClass
63
+ doc = XML::Document.new
64
+ doc.root = XML::Node.new(self.class.element_name)
65
+ doc.root
66
+ when Hash
67
+ doc = XML::Document.new
68
+ doc.root = XML::Node.new(self.class.element_name)
69
+ doc.root
70
+ else
71
+ raise "XML must be a XML::Document, XML::Node, String or IO object"
72
+ end
73
+
74
+ initialize_members(xml) if xml.is_a?(Hash)
75
+ end
76
+
77
+ # Serialize the domain object to XML.
78
+ def to_s
79
+ self.node.to_s
80
+ end
81
+
82
+ # Equality among domain objects.
83
+ # Always returns false, override to get less brain-dead result.
84
+ def ==(obj)
85
+ false
86
+ end
87
+
88
+ protected
89
+
90
+ # Retrieve the domain object that corresponds to
91
+ # the specified class.
92
+ def get_element(klass)
93
+ subnode = self.node.find_first(self.xpath_for(klass))
94
+ subnode ? klass.new(subnode) : nil
95
+ end
96
+
97
+ # Set the specified domain object that corresponds to
98
+ # the specified class.
99
+ def set_element(klass, e)
100
+ descr_node = self.node.find_first(xpath_for(klass))
101
+ descr_node ? descr_node.replace_with(e.node) : @node.child_add(e.node)
102
+ end
103
+
104
+ # Determine the xpath corresponding to the specified class.
105
+ # By default, this is just the name of the class.
106
+ # obj.xpath_for(Field) # => 'Field'
107
+ def xpath_for(klass)
108
+ klass.to_s
109
+ end
110
+
111
+ private
112
+
113
+ def initialize_members(args)
114
+ args.each do |name, value|
115
+ send("#{name}=", value)
116
+ end
117
+ end
118
+ end
119
+
120
+ end
121
+ end