xrvg 0.0.1

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.
data/LICENCE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2008 Julien L�onard
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
data/README ADDED
@@ -0,0 +1,88 @@
1
+ = XRVG -- X Ruby Vector Graphics
2
+
3
+ Supporting XRVG version: 0.0.1
4
+
5
+ This package contains XRVG, a Ruby vector graphic programming library.
6
+
7
+ == Download
8
+
9
+ The latest version of XRVG can be found at
10
+
11
+ * http://rubyforge.org/projects/xrvg/
12
+
13
+ == Installation
14
+
15
+ === GEM Installation
16
+
17
+ Download and install XRVG with the following.
18
+
19
+ gem install xrvg
20
+
21
+ === Running the XRVG Test Suite
22
+
23
+ XRVG comes with a Rakefile. To launch XRVG tests, simply go into XRVG install directory, and executes
24
+ rake test
25
+
26
+ == Online Resources
27
+
28
+ === XRVG Reference
29
+
30
+ * XRVG API Documentation http://xrvg.rubyforge.org/rdoc/index.html
31
+
32
+ === Tutorials
33
+
34
+ * http://xrvg.rubyforge.org/XRVGTutorials.html
35
+
36
+ === Road Map
37
+
38
+ TO BE DEFINED
39
+
40
+ == Simple Example
41
+
42
+ To be put in a .rb file
43
+
44
+ require 'xrvg'
45
+
46
+ render = SVGRender[ :filename, "test.svg" ]
47
+ render.add( Circle[] )
48
+ render.end
49
+
50
+ == License
51
+
52
+ XRVG is available under an MIT-style license.
53
+
54
+ :include: LICENCE
55
+
56
+ == Support
57
+
58
+ The XRVG homepage is http://xrvg.rubyforge.org. You can find the XRVG
59
+ RubyForge page at http://rubyforge.org/projects/xrvg.
60
+
61
+ Feel free to submit commits or feature requests. If you send a patch,
62
+ remember to update the corresponding unit tests. In fact, I prefer
63
+ new feature to be submitted in the form of new unit tests.
64
+
65
+ For other information, feel free to ask on the ruby-talk mailing list
66
+ (which is mirrored to comp.lang.ruby) or contact
67
+ mailto:jblondinet@nospam@yahoo.com.
68
+
69
+
70
+ = Other stuff
71
+
72
+ == Credits
73
+
74
+ Thanks for Rake project, whose README file has been adapted to do this one.
75
+
76
+ == Summary
77
+
78
+ Author:: Julien L�onard <jblondinet@nospam@yahoo.com>
79
+ Requires:: Ruby 1.8.0 or later (but not compatible with 1.9.0)
80
+ License:: Copyright 2008 by Julien L�onard.
81
+ Released under an MIT-style license.
82
+
83
+ == Warranty
84
+
85
+ This software is provided "as is" and without any express or
86
+ implied warranties, including, without limitation, the implied
87
+ warranties of merchantibility and fitness for a particular
88
+ purpose.
data/Rakefile ADDED
@@ -0,0 +1,263 @@
1
+ # Rakefile
2
+ require "rake/testtask"
3
+ require "rake/clean"
4
+ require "rake/rdoctask"
5
+ require "rake/gempackagetask"
6
+ #---
7
+ # The name of your project
8
+ PROJECT = "XRVG"
9
+
10
+ # Your name, used in packaging.
11
+ MY_NAME = "Julien L�onard"
12
+
13
+ # Your email address, used in packaging.
14
+ MY_EMAIL = "jblondinet@nospam@yahoo.com"
15
+
16
+ # Short summary of your project, used in packaging.
17
+ PROJECT_SUMMARY = "Ruby vector graphics library"
18
+
19
+ # The project's package name (as opposed to its display name). Used for
20
+ # RubyForge connectivity and packaging.
21
+ UNIX_NAME = "xrvg"
22
+
23
+ # Your RubyForge user name.
24
+ RUBYFORGE_USER = ENV["RUBYFORGE_USER"] || "jblondinet"
25
+
26
+ # Directory on RubyForge where your website's files should be uploaded.
27
+ WEBSITE_DIR = "www"
28
+
29
+ # Output directory for the rdoc html files.
30
+ # If you don't have a custom homepage, and want to use the RDoc
31
+ # index.html as homepage, just set it to WEBSITE_DIR.
32
+ RDOC_HTML_DIR = "#{WEBSITE_DIR}/rdoc"
33
+ #---
34
+ # Variable settings for extension support.
35
+ EXT_DIR = "ext"
36
+ HAVE_EXT = File.directory?(EXT_DIR)
37
+ EXTCONF_FILES = FileList["#{EXT_DIR}/**/extconf.rb"]
38
+ EXT_SOURCES = FileList["#{EXT_DIR}/**/*.{c,h}"]
39
+ # Eventually add other files from EXT_DIR, like "MANIFEST"
40
+ EXT_DIST_FILES = EXT_SOURCES + EXTCONF_FILES
41
+ #---
42
+ REQUIRE_PATHS = ["lib"]
43
+ REQUIRE_PATHS << EXT_DIR if HAVE_EXT
44
+ $LOAD_PATH.concat(REQUIRE_PATHS)
45
+ # This library file defines the RAKEVERSION constant.
46
+ require "#{UNIX_NAME}"
47
+ PROJECT_VERSION = eval("\"#{XRVG_VERSION}\"") # e.g. "1.0.2"
48
+ #---
49
+ # Clobber object files and Makefiles generated by extconf.rb.
50
+ CLOBBER.include("#{EXT_DIR}/**/*.{so,dll,o}", "#{EXT_DIR}/**/Makefile")
51
+ # Clobber .config generated by setup.rb.
52
+ CLOBBER.include(".config")
53
+
54
+ CLEAN.include("test/**/*.svg", "examples/**/*.svg")
55
+ #---
56
+ # Options common to RDocTask AND Gem::Specification.
57
+ # The --main argument specifies which file appears on the index.html page
58
+ GENERAL_RDOC_OPTS = {
59
+ "--title" => "#{PROJECT} API documentation",
60
+ "--main" => "README"
61
+ }
62
+
63
+ # Additional RDoc formatted files, besides the Ruby source files.
64
+ RDOC_FILES = FileList["README"]
65
+ # Remove the following line if you don't want to extract RDoc from
66
+ # the extension C sources.
67
+ # RDOC_FILES.include(EXT_SOURCES)
68
+
69
+ # Ruby library code.
70
+ LIB_DIR = "lib"
71
+ PRE_LIB_FILES = FileList["assertion.rb", "attributable.rb", "color.rb", "frame.rb", "geometry2D.rb", "interpolation.rb", "render.rb", "samplation.rb", "shape.rb", "style.rb", "trace.rb", "utils.rb", "xrvg.rb"]
72
+ LIB_FILES = FileList["#{LIB_DIR}/*.rb"]
73
+
74
+ # Filelist with Test::Unit test cases.
75
+ TEST_FILES = FileList["test/test_*.rb"]
76
+
77
+ # Executable scripts, all non-garbage files under bin/.
78
+ BIN_FILES = FileList["bin/*"]
79
+
80
+ # This filelist is used to create source packages.
81
+ # Include all Ruby and RDoc files.
82
+ DIST_FILES = FileList["./examples/*.rb"]
83
+ DIST_FILES.include(LIB_FILES)
84
+ DIST_FILES.include("Rakefile", "LICENCE")
85
+ DIST_FILES.include(BIN_FILES)
86
+ # DIST_FILES.include("data/**/*", "test/data/**/*")
87
+ # DIST_FILES.include("#{WEBSITE_DIR}/**/*.{html,css}", "man/*.[0-9]")
88
+ # Don't package files which are autogenerated by RDocTask
89
+ DIST_FILES.exclude(/^(\.\/)?#{RDOC_HTML_DIR}(\/|$)/)
90
+ # Include extension source files.
91
+ DIST_FILES.include(EXT_DIST_FILES)
92
+ # Don't package temporary files, perhaps created by tests.
93
+ DIST_FILES.exclude("**/temp_*", "**/*.tmp", "**/*.svg" )
94
+ # Don't get into recursion...
95
+ DIST_FILES.exclude(/^(\.\/)?pkg(\/|$)/)
96
+ #---
97
+ # Run the tests if rake is invoked without arguments.
98
+ task "default" => ["test"]
99
+
100
+ test_task_name = HAVE_EXT ? "run-tests" : "test"
101
+ Rake::TestTask.new(test_task_name) do |t|
102
+ t.test_files = TEST_FILES
103
+ t.libs = REQUIRE_PATHS
104
+ end
105
+ #---
106
+ # Set an environment variable with any configuration options you want to
107
+ # be passed through to "setup.rb config".
108
+ CONFIG_OPTS = ENV["CONFIG"]
109
+ if HAVE_EXT
110
+ file_create ".config" do
111
+ ruby "setup.rb config #{CONFIG_OPTS}"
112
+ end
113
+
114
+ desc "Configure and make extension. " +
115
+ "The CONFIG variable is passed to `setup.rb config'"
116
+ task "make-ext" => ".config" do
117
+ # The -q option suppresses messages from setup.rb.
118
+ ruby "setup.rb -q setup"
119
+ end
120
+
121
+ desc "Run tests after making the extension."
122
+ task "test" do
123
+ Rake::Task["make-ext"].invoke
124
+ Rake::Task["run-tests"].invoke
125
+ end
126
+ end
127
+ #---
128
+ # The "rdoc" task generates API documentation.
129
+ Rake::RDocTask.new("rdoc") do |t|
130
+ t.rdoc_files = RDOC_FILES + LIB_FILES
131
+ t.title = GENERAL_RDOC_OPTS["--title"]
132
+ t.main = GENERAL_RDOC_OPTS["--main"]
133
+ t.rdoc_dir = RDOC_HTML_DIR
134
+ end
135
+ #---
136
+ GEM_SPEC = Gem::Specification.new do |s|
137
+ s.name = UNIX_NAME
138
+ s.version = PROJECT_VERSION
139
+ s.summary = PROJECT_SUMMARY
140
+ s.rubyforge_project = UNIX_NAME
141
+ s.homepage = "http://#{UNIX_NAME}.rubyforge.org/"
142
+ s.author = MY_NAME
143
+ s.email = MY_EMAIL
144
+ s.files = DIST_FILES
145
+ s.test_files = TEST_FILES
146
+ s.executables = BIN_FILES.map { |fn| File.basename(fn) }
147
+ s.has_rdoc = true
148
+ s.extra_rdoc_files = RDOC_FILES
149
+ s.rdoc_options = GENERAL_RDOC_OPTS.to_a.flatten
150
+ if HAVE_EXT
151
+ s.extensions = EXTCONF_FILES
152
+ s.require_paths << EXT_DIR
153
+ end
154
+ end
155
+
156
+ # Now we can generate the package-related tasks.
157
+ Rake::GemPackageTask.new(GEM_SPEC) do |pkg|
158
+ # pkg.need_zip = true
159
+ # pkg.need_tar = true
160
+ end
161
+ #---
162
+ desc "Upload website to RubyForge. " +
163
+ "scp will prompt for your RubyForge password."
164
+ task "publish-website" => ["rdoc"] do
165
+ rubyforge_path = "/var/www/gforge-projects/#{UNIX_NAME}/"
166
+ sh "scp -r #{WEBSITE_DIR}/* " +
167
+ "#{RUBYFORGE_USER}@rubyforge.org:#{rubyforge_path}",
168
+ :verbose => true
169
+ end
170
+ #---
171
+ task "rubyforge-setup" do
172
+ unless File.exist?(File.join(ENV["HOME"], ".rubyforge"))
173
+ puts "rubyforge will ask you to edit its config.yml now."
174
+ puts "Please set the `username' and `password' entries"
175
+ puts "to your RubyForge username and RubyForge password!"
176
+ puts "Press ENTER to continue."
177
+ $stdin.gets
178
+ sh "rubyforge setup", :verbose => true
179
+ end
180
+ end
181
+
182
+ task "rubyforge-login" => ["rubyforge-setup"] do
183
+ # Note: We assume that username and password were set in
184
+ # rubyforge's config.yml.
185
+ sh "rubyforge login", :verbose => true
186
+ end
187
+
188
+ task "publish-packages" => ["package", "rubyforge-login"] do
189
+ # Upload packages under pkg/ to RubyForge
190
+ # This task makes some assumptions:
191
+ # * You have already created a package on the "Files" tab on the
192
+ # RubyForge project page. See pkg_name variable below.
193
+ # * You made entries under package_ids and group_ids for this
194
+ # project in rubyforge's config.yml. If not, eventually read
195
+ # "rubyforge --help" and then run "rubyforge setup".
196
+ pkg_name = ENV["PKG_NAME"] || UNIX_NAME
197
+ cmd = "rubyforge add_release #{UNIX_NAME} #{pkg_name} " +
198
+ "#{PROJECT_VERSION} #{UNIX_NAME}-#{PROJECT_VERSION}"
199
+ cd "pkg" do
200
+ sh(cmd + ".gem", :verbose => true)
201
+ sh(cmd + ".tgz", :verbose => true)
202
+ sh(cmd + ".zip", :verbose => true)
203
+ end
204
+ end
205
+ #---
206
+ # The "lib" task copy selected files in PRE_LIB_FILES into LIB_DIR directory
207
+ desc "Copy source files to create ./lib directory"
208
+ task "lib" do
209
+ remove_dir LIB_DIR
210
+ mkdir LIB_DIR
211
+ PRE_LIB_FILES.to_a.each {|fn| cp( fn, LIB_DIR ) }
212
+ end
213
+
214
+ #---
215
+ # The "prepare-release" task makes sure your tests run, and then generates
216
+ # files for a new release.
217
+ desc "Run tests, generate RDoc and create packages."
218
+ task "prepare-release" => ["clobber"] do
219
+ puts "Preparing release of #{PROJECT} version #{VERSION}"
220
+ Rake::Task["lib"].invoke
221
+ Rake::Task["test"].invoke
222
+ Rake::Task["rdoc"].invoke
223
+ Rake::Task["package"].invoke
224
+ end
225
+
226
+ # The "publish" task is the overarching task for the whole project. It
227
+ # builds a release and then publishes it to RubyForge.
228
+ desc "Publish new release of #{PROJECT}"
229
+ task "publish" => ["prepare-release"] do
230
+ puts "Uploading documentation..."
231
+ Rake::Task["publish-website"].invoke
232
+ puts "Checking for rubyforge command..."
233
+ `rubyforge --help`
234
+ if $? == 0
235
+ puts "Uploading packages..."
236
+ Rake::Task["publish-packages"].invoke
237
+ puts "Release done!"
238
+ else
239
+ puts "Can't invoke rubyforge command."
240
+ puts "Either install rubyforge with 'gem install rubyforge'"
241
+ puts "and retry or upload the package files manually!"
242
+ end
243
+ end
244
+ #---
245
+ # $ rake -T
246
+ # rake clean # Remove any temporary products.
247
+ # rake clobber # Remove any generated file.
248
+ # rake clobber_package # Remove package products
249
+ # rake clobber_rdoc # Remove rdoc products
250
+ # rake package # Build all the packages
251
+ # rake prepare-release # Run tests, generate RDoc and create packages.
252
+ # rake publish # Publish new release of MyProject
253
+ # rake publish-website # Upload website to RubyForge. scp will prompt for your RubyForge password.
254
+ # rake rdoc # Build the rdoc HTML Files
255
+ # rake repackage # Force a rebuild of the package files
256
+ # rake rerdoc # Force a rebuild of the RDOC files
257
+ # rake test # Run tests for test
258
+ #---
259
+
260
+ # TODO
261
+ # - add task example checking
262
+ # - add effective upload with winscp
263
+ # - add xrvg tutorial documentation publishing (emacs process ?)
@@ -0,0 +1,9 @@
1
+ require 'xrvg'
2
+
3
+ render = SVGRender[ :filename, "foreach.svg", :background, "white" ]
4
+ Circle[].samples(10).foreach do |p1,p2|
5
+ render.add( Circle[:center, p1, :radius, 0.1], Style[ :fill, Color.red ] )
6
+ render.add( Circle[:center, p2, :radius, 0.2], Style[ :fill, Color.blue ] )
7
+ render.add( Line[ :exts, [p1,p2] ] )
8
+ end
9
+ render.end
@@ -0,0 +1,7 @@
1
+ require 'xrvg'
2
+
3
+ render = SVGRender[ :filename, "hellocrown.svg" ]
4
+ Circle[].samples( 8 ) do |point|
5
+ render.add( Circle[:center, point, :radius, 0.2 ], Style[ :fill, Color.black ] )
6
+ end
7
+ render.end
@@ -0,0 +1,7 @@
1
+ require 'xrvg'
2
+
3
+ render = SVGRender[ :filename, "hellocrown2.svg" ]
4
+ [Circle[], (0.2..0.1)].samples( 10 ) do |point, radius|
5
+ render.add( Circle[:center, point, :radius, radius ], Style[ :fill, Color.black( 0.5 ) ] )
6
+ end
7
+ render.end
@@ -0,0 +1,9 @@
1
+ require 'xrvg'
2
+
3
+ render = SVGRender[ :filename, "hellocrownrecurse.svg" ]
4
+ Circle[].samples( 8 ) do |point|
5
+ Circle[:center, point, :radius, 0.2 ].samples( 8 ) do |point|
6
+ render.add( Circle[:center, point, :radius, 0.05 ], Style[ :fill, Color.black ] )
7
+ end
8
+ end
9
+ render.end
@@ -0,0 +1,5 @@
1
+ require 'xrvg'
2
+
3
+ SVGRender.[] do |render|
4
+ render.add( Circle[] )
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'xrvg'
2
+
3
+ SVGRender.[] do |render|
4
+ render.add( Circle[] )
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'xrvg'
2
+
3
+ render = SVGRender.new( :filename, "helloworldexpanded.svg" )
4
+ render.add( Circle.new )
5
+ render.end
@@ -0,0 +1,10 @@
1
+ require 'xrvg'
2
+
3
+ render = SVGRender[ :filename, "palette_circle.svg", :background, "white" ]
4
+
5
+ palette = Palette[ :colorlist, [ Color.black, 0.0, Color.blue, 1.0 ] ]
6
+ [Circle[], palette, (0.1..0.02).random()].samples(25) do |point, color, radius|
7
+ render.add( Circle[ :center, point, :radius, radius ], Style[ :fill, color ])
8
+ end
9
+
10
+ render.end
@@ -0,0 +1,10 @@
1
+ require 'xrvg'
2
+
3
+ render = SVGRender[ :filename, "palette_circle.svg", :background, "white" ]
4
+
5
+ palette = Palette[ :colorlist, [ Color.black, 0.0, Color.blue, 1.0 ] ]
6
+ [Circle[], palette, (0.1..0.02).random()].samples(25) do |point, color, radius|
7
+ render.add( Circle[ :center, point, :radius, radius ], Style[ :fill, color ])
8
+ end
9
+
10
+ render.end
@@ -0,0 +1,9 @@
1
+ require 'xrvg'
2
+
3
+ render = SVGRender[ :filename, "uplets.svg", :background, "white" ]
4
+ Circle[].samples(10).uplets do |p1,p2|
5
+ render.add( Circle[:center, p1, :radius, 0.1], Style[ :fill, Color.red ] )
6
+ render.add( Circle[:center, p2, :radius, 0.2], Style[ :fill, Color.blue ] )
7
+ render.add( Line[ :exts, [p1,p2] ] )
8
+ end
9
+ render.end
data/lib/assertion.rb ADDED
@@ -0,0 +1,14 @@
1
+ # assertion utility
2
+ # must be used with care, as expensive
3
+ #
4
+ class AssertionError < StandardError
5
+ end
6
+
7
+ #
8
+ # Assert method, to check for a block, and raise an error if check is not true
9
+ # Assert("1 is different from 0"){ 1 == 0}
10
+ def Assert(message=nil, &block)
11
+ unless(block.call)
12
+ raise AssertionError, (message || "Assertion failed")
13
+ end
14
+ end
@@ -0,0 +1,152 @@
1
+ #
2
+ # Module to be able to declare attribute, initializing them by default, with type checking
3
+ # also define syntaxically simpler builder
4
+ #
5
+ # See :
6
+ # - +Object+
7
+ # - +Attributable+
8
+
9
+ require 'utils'
10
+
11
+ #
12
+ # Object extension to provide the Class[] syntax for building objects, with +Attributable+ module.
13
+ #
14
+ # TODO : must be defined only for Class objects !!
15
+ class Object
16
+
17
+ # operator [] definition for shorter and more recognizable object creation syntax.
18
+ #
19
+ # Especially useful when creating Attributable dependant objects.
20
+ # c = Circle[ :center, p, :radius, 0.1 ]
21
+ def Object.[](*args)
22
+ return self.new( *args )
23
+ end
24
+ end
25
+
26
+
27
+ # Attribute class used by Attributable module
28
+ #
29
+ # Attribute syntax :
30
+ # attribute :attr1 default_value type
31
+ # with :
32
+ # - dvalue nil if no default_value (in that case, attribute is said to be required )
33
+ # - if type is not specified, default_value must be, and type will be the type of this default_value
34
+ # type can be an Array of types
35
+ class Attribute
36
+ attr_accessor :symbol, :default_value, :type
37
+
38
+ def initialize( symbol, default_value, type ) #:nodoc:
39
+ @symbol = symbol
40
+ @default_value = default_value
41
+ @type = type
42
+ end
43
+
44
+ end
45
+
46
+ # Attributable module
47
+ #
48
+ # Allows class attribute definition in a concise and effective way. Can be viewed as a :attr_accessor extension. See also +Attribute+
49
+ #
50
+ # Example :
51
+ # class A
52
+ # attribute :a 1.0; # type is then Float
53
+ # attribute :b # attribute is required
54
+ # end
55
+ #
56
+ # a = A[ :a, 10.0, :b, 2.0 ]
57
+ # a.a => 10.0
58
+ module Attributable
59
+
60
+ module ClassMethods #:nodoc:
61
+ def init_attributes
62
+ if not @attributes
63
+ @attributes = {}
64
+ newattributes = (self.superclass.ancestors.include? Attributable) ? self.superclass.attributes : {}
65
+ @attributes = @attributes.merge( newattributes )
66
+ else
67
+ # Trace("Attributable::init_attributes self #{self} already initialized")
68
+ end
69
+ # Trace("Attributable::init_attributes self #{self} superclass#{self.superclass} superclass ancestors #{self.superclass.ancestors.inspect} result #{@attributes}")
70
+ end
71
+
72
+ def add_attribute( attribute )
73
+ # Trace("Attributable::add_attribute self #{self} attribute #{attribute.inspect}")
74
+ init_attributes
75
+ @attributes[ attribute.symbol ] = attribute
76
+ end
77
+
78
+ def attributes()
79
+ init_attributes
80
+ # Trace("Attributable::attributes self #{self} attributes #{@attributes}")
81
+ return @attributes
82
+ end
83
+
84
+ def checkvaluetype( value, type )
85
+ typeOK = nil
86
+ types = type
87
+ types.each do |type|
88
+ if value.is_a? type
89
+ typeOK = true
90
+ break
91
+ end
92
+ end
93
+ if not typeOK
94
+ raise( "Attributable::checkvaluetype for class #{self} : default_value #{value.inspect} is not of type #{types.inspect}" )
95
+ end
96
+ end
97
+
98
+ def attribute( symbol, default_value=nil, type=nil )
99
+ if (not type and default_value)
100
+ type = [default_value.class]
101
+ elsif type
102
+ if not type.is_a? Array
103
+ type = [type]
104
+ end
105
+ if default_value
106
+ checkvaluetype( default_value, type )
107
+ end
108
+ end
109
+
110
+ self.add_attribute( Attribute.new( symbol, default_value, type ) )
111
+ attr_accessor symbol
112
+ end
113
+
114
+ def instance_variable_set( *args )
115
+ # Trace("Attributable::instance_variable_set self #{self} args #{args}")
116
+ super( *args )
117
+ end
118
+ end
119
+
120
+ def self.included( receiver ) #:nodoc:
121
+ # inherit_attributes; does not work because Module is not inherited by subclasses
122
+ receiver.extend( ClassMethods )
123
+ end
124
+
125
+ def initialize(*args) #:nodoc:
126
+ # first check if every specified attribute is meaningfull for the class
127
+ args.foreach do |symbol, value|
128
+ if not self.class.attributes.key? symbol
129
+ raise( "Attributable::initialize for class #{self} does not have attribute #{symbol}" )
130
+ end
131
+ end
132
+
133
+ # then check specification coherence, and do initialization
134
+ spec = Hash[ *args ]
135
+ self.class.attributes.each_pair do |symbol, attr|
136
+ if spec.key? symbol
137
+ value = spec[ symbol ]
138
+ if attr.type
139
+ self.class.checkvaluetype( value, attr.type )
140
+ end
141
+ # do init after checking
142
+ self.method("#{symbol.to_s}=").call(value)
143
+ else
144
+ if not attr.default_value
145
+ raise( "Attributable::initialize for class #{self} : attribute #{symbol} is required : attributes #{self.class.attributes.inspect}" )
146
+ end
147
+ self.method("#{symbol.to_s}=").call(attr.default_value)
148
+ end
149
+ end
150
+ end
151
+
152
+ end