ruby_ex 0.1.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/AUTHORS +51 -0
- data/ChangeLog +1763 -0
- data/NEWS +3 -0
- data/README +1 -0
- data/Rakefile +8 -0
- data/SPEC.dyn.yml +10 -0
- data/SPEC.gem.yml +269 -0
- data/SPEC.yml +36 -0
- data/src/abstract.rb +253 -0
- data/src/abstract_node.rb +85 -0
- data/src/algorithms.rb +12 -0
- data/src/algorithms/simulated_annealing.rb +142 -0
- data/src/ask.rb +100 -0
- data/src/attributed_class.rb +303 -0
- data/src/cache.rb +350 -0
- data/src/checkout.rb +12 -0
- data/src/choose.rb +271 -0
- data/src/commands.rb +20 -0
- data/src/commands/command.rb +492 -0
- data/src/commands/datas.rb +16 -0
- data/src/commands/datas/composite.rb +31 -0
- data/src/commands/datas/data.rb +65 -0
- data/src/commands/datas/factory.rb +69 -0
- data/src/commands/datas/temp.rb +26 -0
- data/src/commands/factory.rb +67 -0
- data/src/commands/helpers.rb +81 -0
- data/src/commands/pipe.rb +66 -0
- data/src/commands/runners.rb +16 -0
- data/src/commands/runners/exec.rb +50 -0
- data/src/commands/runners/fork.rb +130 -0
- data/src/commands/runners/runner.rb +140 -0
- data/src/commands/runners/system.rb +57 -0
- data/src/commands/seq.rb +32 -0
- data/src/config_file.rb +95 -0
- data/src/const_regexp.rb +57 -0
- data/src/daemon.rb +135 -0
- data/src/diff.rb +665 -0
- data/src/dlogger.rb +62 -0
- data/src/drb/drb_observable.rb +95 -0
- data/src/drb/drb_observable_pool.rb +27 -0
- data/src/drb/drb_service.rb +44 -0
- data/src/drb/drb_undumped_attributes.rb +56 -0
- data/src/drb/drb_undumped_indexed_object.rb +55 -0
- data/src/drb/insecure_protected_methods.rb +101 -0
- data/src/drb_ex.rb +12 -0
- data/src/dumpable_proc.rb +57 -0
- data/src/filetype.rb +229 -0
- data/src/generate_id.rb +44 -0
- data/src/histogram.rb +222 -0
- data/src/hookable.rb +283 -0
- data/src/hooker.rb +54 -0
- data/src/indexed_node.rb +65 -0
- data/src/io_marshal.rb +99 -0
- data/src/ioo.rb +193 -0
- data/src/labeled_node.rb +62 -0
- data/src/logger_observer.rb +24 -0
- data/src/md5sum.rb +70 -0
- data/src/module/autoload_tree.rb +65 -0
- data/src/module/hierarchy.rb +334 -0
- data/src/module/instance_method_visibility.rb +71 -0
- data/src/node.rb +81 -0
- data/src/object_monitor.rb +143 -0
- data/src/object_monitor_activity.rb +34 -0
- data/src/observable.rb +138 -0
- data/src/observable_pool.rb +291 -0
- data/src/orderedhash.rb +252 -0
- data/src/pp_hierarchy.rb +30 -0
- data/src/random_generators.rb +29 -0
- data/src/random_generators/random_generator.rb +33 -0
- data/src/random_generators/ruby.rb +25 -0
- data/src/ruby_ex.rb +124 -0
- data/src/safe_eval.rb +346 -0
- data/src/sendmail.rb +214 -0
- data/src/service_manager.rb +122 -0
- data/src/shuffle.rb +30 -0
- data/src/spring.rb +134 -0
- data/src/spring_set.rb +134 -0
- data/src/symtbl.rb +108 -0
- data/src/synflow.rb +474 -0
- data/src/thread_mutex.rb +11 -0
- data/src/timeout_ex.rb +79 -0
- data/src/trace.rb +26 -0
- data/src/uri/druby.rb +78 -0
- data/src/uri/file.rb +63 -0
- data/src/uri/ftp_ex.rb +36 -0
- data/src/uri/http_ex.rb +41 -0
- data/src/uri/pgsql.rb +136 -0
- data/src/uri/ssh.rb +87 -0
- data/src/uri/svn.rb +113 -0
- data/src/uri_ex.rb +71 -0
- data/src/verbose_object.rb +70 -0
- data/src/yaml/basenode_ext.rb +63 -0
- data/src/yaml/chop_header.rb +24 -0
- data/src/yaml/transform.rb +450 -0
- data/src/yaml/yregexpath.rb +76 -0
- data/test/algorithms/simulated_annealing_test.rb +102 -0
- data/test/check-pkg-ruby_ex.yml +15 -0
- data/test/check-ruby_ex.yml +12 -0
- data/test/resources/autoload_tree/A.rb +11 -0
- data/test/resources/autoload_tree/B.rb +10 -0
- data/test/resources/autoload_tree/foo/C.rb +18 -0
- data/test/resources/foo.txt +6 -0
- data/test/sanity-suite.yml +12 -0
- data/test/sanity/multiple-requires.yml +20 -0
- data/test/sanity/single-requires.yml +24 -0
- data/test/test-unit-setup.rb +6 -0
- data/test/unit-suite.yml +14 -0
- metadata +269 -0
@@ -0,0 +1,85 @@
|
|
1
|
+
# Copyright: Copyright (c) 2004 Nicolas Despres. All rights reserved.
|
2
|
+
# Author: Nicolas Despres <polrop@lrde.epita.fr>.
|
3
|
+
# License: Gnu General Public License.
|
4
|
+
|
5
|
+
# $LastChangedBy: ertai $
|
6
|
+
# $Id: abstract_node.rb 266 2005-06-01 14:27:18Z ertai $
|
7
|
+
|
8
|
+
|
9
|
+
require 'ruby_ex'
|
10
|
+
require 'abstract'
|
11
|
+
|
12
|
+
class AbstractNode
|
13
|
+
include Abstract
|
14
|
+
|
15
|
+
def initialize(data=nil, *sub_nodes)
|
16
|
+
@data = data
|
17
|
+
self.each_node { |sub_node| check_sub_node_type(sub_node) }
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :data, :sub_nodes
|
21
|
+
|
22
|
+
def [](index)
|
23
|
+
@sub_nodes[index]
|
24
|
+
end
|
25
|
+
|
26
|
+
def []=(index, sub_node)
|
27
|
+
check_sub_node_type(sub_node)
|
28
|
+
@sub_nodes[index] = sub_node
|
29
|
+
end
|
30
|
+
|
31
|
+
def merge!(sub_nodes)
|
32
|
+
sub_nodes.each { |index, sub_node| self[index] = sub_node }
|
33
|
+
end
|
34
|
+
|
35
|
+
def each_pair(&block)
|
36
|
+
@sub_nodes.each_pair { |index, sub_node| block[index, sub_node] }
|
37
|
+
end
|
38
|
+
|
39
|
+
def each_node(&block)
|
40
|
+
@sub_nodes.each_pair { |index, sub_node| block[sub_node] }
|
41
|
+
end
|
42
|
+
|
43
|
+
alias :each :each_node
|
44
|
+
|
45
|
+
def each_index(&block)
|
46
|
+
@sub_nodes.each_pair { |index, sub_node| block[index] }
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete(index)
|
50
|
+
@sub_nodes.delete(index)
|
51
|
+
end
|
52
|
+
|
53
|
+
def nb_sub_nodes
|
54
|
+
@sub_nodes.size
|
55
|
+
end
|
56
|
+
|
57
|
+
alias size nb_sub_nodes
|
58
|
+
alias length nb_sub_nodes
|
59
|
+
|
60
|
+
def leaf?
|
61
|
+
@sub_nodes.empty?
|
62
|
+
end
|
63
|
+
|
64
|
+
def pre_depth_first(index=nil, &block)
|
65
|
+
block[index, self]
|
66
|
+
@sub_nodes.each_pair do |index, sub_node|
|
67
|
+
sub_node.pre_depth_first(index, &block)
|
68
|
+
end
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
# FIXME: implement me
|
73
|
+
# def breadth_first(&block)
|
74
|
+
# end
|
75
|
+
|
76
|
+
protected
|
77
|
+
def check_sub_node_type(sub_node)
|
78
|
+
unless sub_node.is_a?(self.class)
|
79
|
+
raise(TypeError, "`#{sub_node}' - must be a #{self.class}")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end # class AbstractNode
|
84
|
+
|
85
|
+
|
data/src/algorithms.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# Copyright:: Copyright (c) 2005 Nicolas Pouillard. All rights reserved.
|
2
|
+
# Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
|
3
|
+
# License:: Gnu General Public License.
|
4
|
+
# Revision:: $Id: algorithms.rb 258 2005-06-01 00:22:51Z ertai $
|
5
|
+
|
6
|
+
require 'module/autoload_tree'
|
7
|
+
|
8
|
+
module Algorithms
|
9
|
+
|
10
|
+
autoloaded_module(__FILE__)
|
11
|
+
|
12
|
+
end # module Algorithms
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
|
2
|
+
# Copyright:: Copyright (c) 2005 Nicolas Pouillard. All rights reserved.
|
3
|
+
# License:: GNU General Public License (GPL).
|
4
|
+
# Revision:: $Id$
|
5
|
+
|
6
|
+
require 'ruby_ex'
|
7
|
+
require 'choose'
|
8
|
+
|
9
|
+
module Algorithms
|
10
|
+
|
11
|
+
class SimulatedAnnealing
|
12
|
+
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# obj = MySimulatedAnnealingObject.new
|
16
|
+
#
|
17
|
+
# SimulatedAnnealing.new(
|
18
|
+
# :support => obj,
|
19
|
+
# :global_thresold => 200,
|
20
|
+
# :step_multiplicator => 0.63,
|
21
|
+
# :initial_temperature => 10,
|
22
|
+
# :initial_cost => obj.cost,
|
23
|
+
# :iteration_modulus => 500, # display status once a 500
|
24
|
+
# :step_modulus => 5000 # change the temperature once a 5000
|
25
|
+
# )
|
26
|
+
#
|
27
|
+
def initialize ( opts )
|
28
|
+
@support = opts[:support]
|
29
|
+
@global_thresold = opts[:global_thresold]
|
30
|
+
@step_multiplicator = opts[:step_multiplicator]
|
31
|
+
@temperature = opts[:initial_temperature]
|
32
|
+
@cur_cost = opts[:initial_cost]
|
33
|
+
@output = opts[:output] || STDOUT
|
34
|
+
@iteration_modulus = opts[:iteration_modulus] || 1
|
35
|
+
@step_modulus = opts[:step_modulus] || 1
|
36
|
+
@generator = opts[:generator]
|
37
|
+
@progression = ' '
|
38
|
+
@diff_cost = 0
|
39
|
+
@iteration = 0
|
40
|
+
@probability_threshold = 0
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def run
|
45
|
+
@output.puts '---'
|
46
|
+
while @cur_cost > @global_thresold
|
47
|
+
# puts "Iteration(#{it}) #{@cur_cost}"
|
48
|
+
@transition = @support.choose_transition(@generator)
|
49
|
+
@diff_cost = @support.transition_cost(@cur_cost, @transition)
|
50
|
+
|
51
|
+
@iteration += 1
|
52
|
+
if (@iteration % @step_modulus).zero?
|
53
|
+
@temperature *= @step_multiplicator
|
54
|
+
end
|
55
|
+
|
56
|
+
if @diff_cost > 0
|
57
|
+
@probability_threshold = Math.exp(- @diff_cost.to_f / @temperature)
|
58
|
+
@proba = choose_probability(@generator)
|
59
|
+
if @proba < @probability_threshold
|
60
|
+
progression '+'
|
61
|
+
else
|
62
|
+
progression ' '
|
63
|
+
next
|
64
|
+
end
|
65
|
+
else
|
66
|
+
@probability_threshold = 1
|
67
|
+
@proba = 1
|
68
|
+
progression((@diff_cost.zero?)? '=' : '-')
|
69
|
+
end
|
70
|
+
|
71
|
+
@support.apply_transition(@transition)
|
72
|
+
@cur_cost += @diff_cost
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def disp ( output=@output )
|
78
|
+
args = [ @progression, @diff_cost.abs, @temperature,
|
79
|
+
@proba, @probability_threshold, @cur_cost ]
|
80
|
+
fmt = '- { diff: %s%-7d, temp: %-4f, ' +
|
81
|
+
'prob: %-7f, thres: %-7f, cost: %-5s }'
|
82
|
+
output.puts fmt % args
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def summary
|
87
|
+
@output.puts %Q[
|
88
|
+
|---
|
89
|
+
|Found a mininum: #@cur_cost
|
90
|
+
|Temperature at the end: #@temperature
|
91
|
+
|Nb iterations: #@iteration
|
92
|
+
].head_cut!
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
def progression ( x )
|
97
|
+
@progression = x
|
98
|
+
disp if (@iteration % @iteration_modulus).zero?
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
def choose_probability ( generator=nil )
|
103
|
+
1.0.choose(generator)
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
module Support
|
108
|
+
|
109
|
+
def included ( aModule )
|
110
|
+
|
111
|
+
aModule.module_eval do
|
112
|
+
|
113
|
+
#
|
114
|
+
# Return a transition which can be applied by apply_transition.
|
115
|
+
#
|
116
|
+
def choose_transition ( generator=nil )
|
117
|
+
raise NotImplementedError
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Return a cost difference
|
122
|
+
#
|
123
|
+
def transition_cost ( current_cost, transition )
|
124
|
+
raise NotImplementedError
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
# Apply the given transition to your current object.
|
129
|
+
#
|
130
|
+
def apply_transition ( transition )
|
131
|
+
raise NotImplementedError
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
end # module Support
|
139
|
+
|
140
|
+
end # class SimulatedAnnealing
|
141
|
+
|
142
|
+
end # module Algorithms
|
data/src/ask.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
|
2
|
+
# Copyright:: Copyright (c) 2004, 2005 Nicolas Pouillard. All rights reserved.
|
3
|
+
# License:: GNU General Public License (GPL).
|
4
|
+
# Revision:: $Id: ask.rb 266 2005-06-01 14:27:18Z ertai $
|
5
|
+
|
6
|
+
require 'ruby_ex'
|
7
|
+
|
8
|
+
ANSWERS = [ :y, :n ]
|
9
|
+
ANSWER_NOT_VALID = 'Not a valid answer, please answer correctly'
|
10
|
+
|
11
|
+
# `ask', ask the user to answer, to your question.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
# ask('Commiting, are you sure', :n)
|
15
|
+
#
|
16
|
+
# produce => Commiting, are you sure (y/N):
|
17
|
+
# and wait your answer.
|
18
|
+
def ask ( aQuestion, theDefaultAnswer=:y, cin=STDIN, cout=STDOUT, cerr=STDERR )
|
19
|
+
|
20
|
+
yn = case theDefaultAnswer
|
21
|
+
when :y then ' [Y/n]: '
|
22
|
+
when :n then ' [y/N]: '
|
23
|
+
else raise ArgumentError, "not valid default answer #{theDefaultAnswer}"
|
24
|
+
end
|
25
|
+
|
26
|
+
loop do
|
27
|
+
cout.print aQuestion, yn
|
28
|
+
cout.flush
|
29
|
+
|
30
|
+
answer = cin.readline.chomp.downcase
|
31
|
+
|
32
|
+
return theDefaultAnswer if answer.empty?
|
33
|
+
|
34
|
+
answer = answer.to_sym
|
35
|
+
|
36
|
+
return answer if ANSWERS.include? answer
|
37
|
+
|
38
|
+
cerr.puts ANSWER_NOT_VALID
|
39
|
+
cout.puts
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
test_section __FILE__ do
|
45
|
+
|
46
|
+
class AskTest < Test::Unit::TestCase
|
47
|
+
|
48
|
+
def ask_checker ( question, default, answer, ref, out, err )
|
49
|
+
require 'stringio'
|
50
|
+
cin, cout, cerr = StringIO.new, StringIO.new, StringIO.new
|
51
|
+
cin.puts answer
|
52
|
+
cin.rewind
|
53
|
+
res = ask(question, default, cin, cout, cerr)
|
54
|
+
cout.rewind
|
55
|
+
cerr.rewind
|
56
|
+
assert_equal(res, ref, 'bad return value')
|
57
|
+
assert_equal(cout.readlines.join, out, 'bad standard output')
|
58
|
+
assert_equal(cerr.readlines.join, err, 'bad error output')
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_bad_default
|
62
|
+
assert_raise(ArgumentError) { ask_checker('Q', :foo, '', :y, [], []) }
|
63
|
+
assert_nothing_raised do
|
64
|
+
ask_checker('Q', :y, 'y', :y, 'Q [Y/n]: ', '')
|
65
|
+
end
|
66
|
+
assert_nothing_raised do
|
67
|
+
ask_checker('Q', :n, 'y', :y, 'Q [y/N]: ', '')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_valid_default_yes
|
72
|
+
ask_checker('Q', :y, 'y', :y, 'Q [Y/n]: ', '')
|
73
|
+
ask_checker('Q', :y, 'Y', :y, 'Q [Y/n]: ', '')
|
74
|
+
ask_checker('Q', :y, 'n', :n, 'Q [Y/n]: ', '')
|
75
|
+
ask_checker('Q', :y, 'N', :n, 'Q [Y/n]: ', '')
|
76
|
+
ask_checker('Q', :y, '', :y, 'Q [Y/n]: ', '')
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_valid_default_no
|
80
|
+
ask_checker('Q', :n, 'y', :y, 'Q [y/N]: ', '')
|
81
|
+
ask_checker('Q', :n, 'Y', :y, 'Q [y/N]: ', '')
|
82
|
+
ask_checker('Q', :n, 'n', :n, 'Q [y/N]: ', '')
|
83
|
+
ask_checker('Q', :n, 'N', :n, 'Q [y/N]: ', '')
|
84
|
+
ask_checker('Q', :n, '', :n, 'Q [y/N]: ', '')
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_invalid_answer
|
88
|
+
ask_checker('Q', :n, "bad\ny", :y,
|
89
|
+
"Q [y/N]: \nQ [y/N]: ", ANSWER_NOT_VALID + "\n")
|
90
|
+
ask_checker('Q', :y, "ad\n\n", :y,
|
91
|
+
"Q [Y/n]: \nQ [Y/n]: ", ANSWER_NOT_VALID + "\n")
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_ask1
|
95
|
+
ask_checker('Q', :y, 'y', :y, 'Q [Y/n]: ', '')
|
96
|
+
end
|
97
|
+
|
98
|
+
end # class AskTest
|
99
|
+
|
100
|
+
end
|
@@ -0,0 +1,303 @@
|
|
1
|
+
# Copyright:: Copyright (c) 2004, 2005 Nicolas Pouillard. All rights reserved.
|
2
|
+
# Author:: Nicolas Pouillard <ertai@lrde.epita.fr>.
|
3
|
+
# License:: Gnu General Public License.
|
4
|
+
# Revision:: $Id: attributed_class.rb 266 2005-06-01 14:27:18Z ertai $
|
5
|
+
|
6
|
+
|
7
|
+
require 'ruby_ex'
|
8
|
+
|
9
|
+
# Extension to have inherited, class attributes,
|
10
|
+
# Where each class have his own attributes set.
|
11
|
+
module AttributedClass
|
12
|
+
|
13
|
+
class Attribute
|
14
|
+
|
15
|
+
attr_reader :name, :descr, :klass, :default_proc
|
16
|
+
attr_accessor :default
|
17
|
+
|
18
|
+
@@default_proc = nil
|
19
|
+
|
20
|
+
def initialize ( name, descr, *args, &block )
|
21
|
+
raise ArgumentError, 'need a symbol' unless name.is_a? Symbol
|
22
|
+
@name, @descr = name, descr
|
23
|
+
@default, @klass = nil, nil
|
24
|
+
@mandatory = false
|
25
|
+
@visible = true
|
26
|
+
if block_given?
|
27
|
+
@default_proc = block
|
28
|
+
else
|
29
|
+
@default_proc = @@default_proc
|
30
|
+
end
|
31
|
+
other = []
|
32
|
+
|
33
|
+
args.each do |arg|
|
34
|
+
case arg
|
35
|
+
when :mandatory then @mandatory = true
|
36
|
+
when :invisible then @visible = false
|
37
|
+
else other << arg
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
case other.size
|
42
|
+
when 1
|
43
|
+
if other[0].is_a? Class
|
44
|
+
@klass = [other[0]]
|
45
|
+
elsif other[0].is_a? Array and other[0].all? { |k| k.is_a? Class }
|
46
|
+
@klass = other[0]
|
47
|
+
else
|
48
|
+
@default = other[0]
|
49
|
+
end
|
50
|
+
when 2
|
51
|
+
a, b = other
|
52
|
+
if a.is_a? Class and b.is_a? a
|
53
|
+
@klass, @default = [a], b
|
54
|
+
elsif a.is_a? Array and
|
55
|
+
a.all? { |k| k.is_a? Class } and
|
56
|
+
a.any? { |k| b.is_a? k }
|
57
|
+
@klass, @default = a, b
|
58
|
+
elsif b.is_a? Class and a.is_a? b
|
59
|
+
@klass, @default = [b], a
|
60
|
+
elsif b.is_a? Array and
|
61
|
+
b.all? { |k| k.is_a? Class } and
|
62
|
+
b.any? { |k| a.is_a? k }
|
63
|
+
@klass, @default = b, a
|
64
|
+
else
|
65
|
+
raise ArgumentError, 'need a Class and a default value'
|
66
|
+
end
|
67
|
+
when 0
|
68
|
+
else raise ArgumentError, 'too many values'
|
69
|
+
end
|
70
|
+
|
71
|
+
# removed because it causes some probleme when distributed such a class
|
72
|
+
#[@name, @descr, @default].each { |x| x.freeze }
|
73
|
+
end
|
74
|
+
|
75
|
+
def visible?
|
76
|
+
@visible
|
77
|
+
end
|
78
|
+
|
79
|
+
def invisible?
|
80
|
+
!@visible
|
81
|
+
end
|
82
|
+
|
83
|
+
def mandatory?
|
84
|
+
@mandatory
|
85
|
+
end
|
86
|
+
|
87
|
+
def valid? ( anObject )
|
88
|
+
return false if mandatory? and anObject.nil?
|
89
|
+
@klass.nil? or @klass.empty? or @klass.any? { |k| anObject.is_a? k }
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def self.set_default_proc ( &block )
|
94
|
+
@@default_proc = block
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def help ( output=STDERR )
|
99
|
+
output << "#@name: #@descr"
|
100
|
+
output << " [#@klass]" unless @klass.nil?
|
101
|
+
other = []
|
102
|
+
other << 'mandatory' if mandatory?
|
103
|
+
other << 'invisible' if invisible?
|
104
|
+
output << " (#{other.join(', ')})" unless other.empty?
|
105
|
+
output
|
106
|
+
end
|
107
|
+
|
108
|
+
def inspect
|
109
|
+
help('#<') + '>'
|
110
|
+
end
|
111
|
+
|
112
|
+
end # class Attribute
|
113
|
+
|
114
|
+
|
115
|
+
class MethodAttribute < Attribute
|
116
|
+
VISIBILITIES = [ :private, :protected, :public, :unknown ]
|
117
|
+
attr_reader :visibility
|
118
|
+
def private?
|
119
|
+
@visibility == :private
|
120
|
+
end
|
121
|
+
def protected?
|
122
|
+
@visibility == :protected
|
123
|
+
end
|
124
|
+
def public?
|
125
|
+
@visibility == :public
|
126
|
+
end
|
127
|
+
def initialize ( name, descr, *args )
|
128
|
+
vis = args.find { |arg| VISIBILITIES.include? arg }
|
129
|
+
args.delete(vis) unless vis.nil?
|
130
|
+
@visibility = vis || :unknown
|
131
|
+
super(name, descr, *args)
|
132
|
+
end
|
133
|
+
end # class MethodAttribute
|
134
|
+
|
135
|
+
|
136
|
+
class AttributeError < Exception
|
137
|
+
|
138
|
+
def initialize ( anObject, anAttr, val=nil )
|
139
|
+
if val.nil?
|
140
|
+
super "Missing the #{anAttr.name} attribute for `#{anObject}'"
|
141
|
+
else
|
142
|
+
super "Attribute #{anAttr.name} wait for a `#{anAttr.klass}' not " +
|
143
|
+
"`#{val}' in #{anObject}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end # class AttributeError
|
148
|
+
|
149
|
+
|
150
|
+
def self.included ( aClass )
|
151
|
+
|
152
|
+
aClass.module_eval do
|
153
|
+
|
154
|
+
def initialize_attributes ( *default_proc_args )
|
155
|
+
self.class.attributes.each do |attr|
|
156
|
+
next if attr.is_a? MethodAttribute
|
157
|
+
if attr.default_proc.nil? and attr.default.nil?
|
158
|
+
instance_variable_set("@#{attr.name}", nil)
|
159
|
+
else
|
160
|
+
unless attr.default_proc.nil?
|
161
|
+
pr = attr.default_proc
|
162
|
+
if pr.arity == default_proc_args.size
|
163
|
+
default = pr[*default_proc_args]
|
164
|
+
else
|
165
|
+
default = pr[attr, *default_proc_args]
|
166
|
+
end
|
167
|
+
attr.default = default
|
168
|
+
end
|
169
|
+
send "#{attr.name}=", attr.default
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def self.set_default_proc ( &block )
|
175
|
+
Attribute.set_default_proc(&block)
|
176
|
+
end
|
177
|
+
|
178
|
+
def each_attribute ( &block )
|
179
|
+
self.class.attributes.each do |attr|
|
180
|
+
block[attr, send(attr.name)]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def each_variable_attribute ( &block )
|
185
|
+
self.class.attributes.each do |attr|
|
186
|
+
next if attr.is_a? MethodAttribute
|
187
|
+
block[attr, send(attr.name)]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def each_method_attribute ( &block )
|
192
|
+
self.class.attributes.each do |attr|
|
193
|
+
next unless attr.is_a? MethodAttribute
|
194
|
+
block[attr, send(attr.name)]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def check_attribute ( attr, anObject )
|
199
|
+
unless attr.valid? anObject
|
200
|
+
raise AttributeError.new(self, attr, anObject)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def check_attributes
|
205
|
+
each_variable_attribute(&method(:check_attribute))
|
206
|
+
end
|
207
|
+
|
208
|
+
def self.help ( output=STDERR )
|
209
|
+
output.puts "#{self.to_s}:"
|
210
|
+
attributes.each do |attr|
|
211
|
+
output << ' '
|
212
|
+
attr.help(output)
|
213
|
+
output.puts '.'
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def self.attributes
|
218
|
+
unless defined? @attributes
|
219
|
+
if superclass and superclass.respond_to? :attributes
|
220
|
+
@attributes = superclass.attributes.dup
|
221
|
+
else
|
222
|
+
@attributes = []
|
223
|
+
end
|
224
|
+
end
|
225
|
+
@attributes
|
226
|
+
end
|
227
|
+
|
228
|
+
def self.attribute ( name, descr, *args, &block )
|
229
|
+
attr = Attribute.new(name, descr, *args, &block)
|
230
|
+
unless method_defined? name
|
231
|
+
if att = get_attribute(name, MethodAttribute)
|
232
|
+
attributes.delete(att)
|
233
|
+
end
|
234
|
+
attr_reader name
|
235
|
+
end
|
236
|
+
meth = "#{name}="
|
237
|
+
unless method_defined? meth
|
238
|
+
if att = get_attribute(meth, MethodAttribute)
|
239
|
+
attributes.delete(att)
|
240
|
+
end
|
241
|
+
attr_writer name
|
242
|
+
end
|
243
|
+
attributes << attr
|
244
|
+
end
|
245
|
+
|
246
|
+
def self.method_attribute ( name, descr, *args, &block )
|
247
|
+
# unless method_defined? name
|
248
|
+
# raise ArgumentError, "Undefined method #{name}"
|
249
|
+
# end
|
250
|
+
attr = MethodAttribute.new(name, descr, *args, &block)
|
251
|
+
attributes << attr
|
252
|
+
end
|
253
|
+
|
254
|
+
def self.get_attribute ( name, type=nil )
|
255
|
+
if type.nil?
|
256
|
+
attributes.find { |attr| attr.name == name }
|
257
|
+
else
|
258
|
+
attributes.find { |attr| attr.name == name && attr.is_a?(type) }
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
265
|
+
|
266
|
+
end # module AttributedClass
|
267
|
+
|
268
|
+
|
269
|
+
|
270
|
+
test_section __FILE__ do
|
271
|
+
|
272
|
+
class AttributedClassTest < Test::Unit::TestCase
|
273
|
+
|
274
|
+
def test_simple
|
275
|
+
c = Class.new
|
276
|
+
assert_nothing_raised do
|
277
|
+
c.module_eval { include AttributedClass }
|
278
|
+
end
|
279
|
+
assert_equal([], c.attributes)
|
280
|
+
assert_nothing_raised do
|
281
|
+
c.module_eval { attribute :foo, 'foofoo', 42 }
|
282
|
+
end
|
283
|
+
cc, d, dd = nil, nil, nil
|
284
|
+
assert_nothing_raised { cc = c.new }
|
285
|
+
assert_equal(nil, cc.foo)
|
286
|
+
assert_nothing_raised { cc.initialize_attributes }
|
287
|
+
assert_equal(42, cc.foo)
|
288
|
+
assert_nothing_raised { cc.foo = 32 }
|
289
|
+
assert_equal(32, cc.foo)
|
290
|
+
assert_nothing_raised { d = Class.new(c) }
|
291
|
+
assert_nothing_raised do
|
292
|
+
d.module_eval { attribute :bar, 'foofoo', :mandatory }
|
293
|
+
end
|
294
|
+
assert_nothing_raised { dd = d.new }
|
295
|
+
assert_nothing_raised { dd.initialize_attributes }
|
296
|
+
assert_raise(AttributedClass::AttributeError) do
|
297
|
+
dd.check_attributes
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
end # class AttributedClassTest
|
302
|
+
|
303
|
+
end
|