voruby2-preview 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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