vcs 0.1 → 0.2.148

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.
Files changed (132) hide show
  1. data/Rakefile +17 -3
  2. data/bin/vcs +57 -34
  3. data/doc/jamis.rb +564 -0
  4. data/ruby_ex/abstract.rb +254 -0
  5. data/ruby_ex/abstract_node.rb +85 -0
  6. data/ruby_ex/algorithms/simulated_annealing.rb +140 -0
  7. data/ruby_ex/array_each_pair.rb +18 -0
  8. data/ruby_ex/ask.rb +101 -0
  9. data/ruby_ex/attributed_class.rb +302 -0
  10. data/ruby_ex/cache.rb +373 -0
  11. data/ruby_ex/checkout.rb +12 -0
  12. data/ruby_ex/choose.rb +271 -0
  13. data/ruby_ex/commands.rb +18 -0
  14. data/ruby_ex/commands/command.rb +401 -0
  15. data/ruby_ex/commands/datas.rb +16 -0
  16. data/ruby_ex/commands/datas/data.rb +33 -0
  17. data/ruby_ex/commands/datas/factory.rb +66 -0
  18. data/ruby_ex/commands/factory.rb +66 -0
  19. data/ruby_ex/commands/helpers.rb +67 -0
  20. data/ruby_ex/commands/pipe.rb +64 -0
  21. data/ruby_ex/commands/runners.rb +17 -0
  22. data/ruby_ex/commands/runners/exec.rb +49 -0
  23. data/ruby_ex/commands/runners/fork.rb +97 -0
  24. data/ruby_ex/commands/runners/runner.rb +107 -0
  25. data/ruby_ex/commands/seq.rb +27 -0
  26. data/ruby_ex/config_file.rb +96 -0
  27. data/ruby_ex/const_regexp.rb +59 -0
  28. data/ruby_ex/daemon.rb +134 -0
  29. data/ruby_ex/diff.rb +667 -0
  30. data/ruby_ex/dlogger.rb +62 -0
  31. data/ruby_ex/drb/dispatcher.rb +252 -0
  32. data/ruby_ex/drb/dispatcher_server_test.rb +29 -0
  33. data/ruby_ex/drb/drb_observable.rb +97 -0
  34. data/ruby_ex/drb/drb_observable_pool.rb +27 -0
  35. data/ruby_ex/drb/drb_service.rb +43 -0
  36. data/ruby_ex/drb/drb_undumped_attributes.rb +55 -0
  37. data/ruby_ex/drb/drb_undumped_indexed_object.rb +54 -0
  38. data/ruby_ex/drb/insecure_protected_methods.rb +103 -0
  39. data/ruby_ex/drb/session_client_test.rb +40 -0
  40. data/ruby_ex/drb/session_manager.rb +246 -0
  41. data/ruby_ex/drb/session_server.rb +53 -0
  42. data/ruby_ex/dtime.rb +143 -0
  43. data/ruby_ex/dumpable_proc.rb +63 -0
  44. data/ruby_ex/exception.rb +32 -0
  45. data/ruby_ex/filetype.rb +229 -0
  46. data/ruby_ex/fileutils_ex.rb +44 -0
  47. data/ruby_ex/fold.rb +58 -0
  48. data/ruby_ex/generate_id.rb +44 -0
  49. data/ruby_ex/hookable.rb +262 -0
  50. data/ruby_ex/hooker.rb +54 -0
  51. data/ruby_ex/inactive_timeout.rb +137 -0
  52. data/ruby_ex/indexed_node.rb +66 -0
  53. data/ruby_ex/io_marshal.rb +100 -0
  54. data/ruby_ex/ioo.rb +194 -0
  55. data/ruby_ex/labeled_node.rb +63 -0
  56. data/ruby_ex/logger_observer.rb +23 -0
  57. data/ruby_ex/md5sum.rb +66 -0
  58. data/ruby_ex/mktemp.rb +208 -0
  59. data/ruby_ex/module/attr_once.rb +36 -0
  60. data/ruby_ex/module/autoload_tree.rb +75 -0
  61. data/ruby_ex/module/hierarchy.rb +335 -0
  62. data/ruby_ex/module/instance_method_visibility.rb +73 -0
  63. data/ruby_ex/module_ex.rb +11 -0
  64. data/ruby_ex/node.rb +80 -0
  65. data/ruby_ex/object_monitor.rb +145 -0
  66. data/ruby_ex/object_monitor_activity.rb +33 -0
  67. data/ruby_ex/observable.rb +140 -0
  68. data/ruby_ex/observable_pool.rb +293 -0
  69. data/ruby_ex/orderedhash.rb +252 -0
  70. data/ruby_ex/pathname_ex.rb +152 -0
  71. data/ruby_ex/pp_hierarchy.rb +29 -0
  72. data/ruby_ex/pseudo_cache.rb +190 -0
  73. data/ruby_ex/queue.rb +56 -0
  74. data/ruby_ex/random_generators.rb +25 -0
  75. data/ruby_ex/random_generators/random_generator.rb +31 -0
  76. data/ruby_ex/random_generators/ruby.rb +23 -0
  77. data/ruby_ex/safe_eval.rb +348 -0
  78. data/ruby_ex/sendmail.rb +215 -0
  79. data/ruby_ex/service_manager.rb +121 -0
  80. data/ruby_ex/session/administrable.rb +120 -0
  81. data/ruby_ex/session/client.rb +153 -0
  82. data/ruby_ex/session/const.rb +18 -0
  83. data/ruby_ex/session/dispatcher.rb +184 -0
  84. data/ruby_ex/session/error.rb +21 -0
  85. data/ruby_ex/session/fetchable.rb +57 -0
  86. data/ruby_ex/session/fetcher.rb +62 -0
  87. data/ruby_ex/session/hookable.rb +26 -0
  88. data/ruby_ex/session/profile.rb +110 -0
  89. data/ruby_ex/session/server.rb +582 -0
  90. data/ruby_ex/session/test/administrable_test.rb +337 -0
  91. data/ruby_ex/session/test/basic_test.rb +523 -0
  92. data/ruby_ex/session/test/dispatcher_test.rb +409 -0
  93. data/ruby_ex/session/test/fetchable_test.rb +119 -0
  94. data/ruby_ex/session/test/sub_server_test.rb +188 -0
  95. data/ruby_ex/shuffle.rb +30 -0
  96. data/ruby_ex/spring.rb +136 -0
  97. data/ruby_ex/spring_set.rb +137 -0
  98. data/ruby_ex/string_ex.rb +28 -0
  99. data/ruby_ex/symtbl.rb +106 -0
  100. data/ruby_ex/synflow.rb +474 -0
  101. data/ruby_ex/test/unit/ui/yaml/testrunner.rb +164 -0
  102. data/ruby_ex/thread_mutex.rb +10 -0
  103. data/ruby_ex/timeout_ex.rb +81 -0
  104. data/ruby_ex/top_down.rb +73 -0
  105. data/ruby_ex/trace.rb +26 -0
  106. data/ruby_ex/uri/druby.rb +81 -0
  107. data/ruby_ex/uri/file.rb +65 -0
  108. data/ruby_ex/uri/ftp_ex.rb +37 -0
  109. data/ruby_ex/uri/http_ex.rb +43 -0
  110. data/ruby_ex/uri/ssh.rb +92 -0
  111. data/ruby_ex/uri/svn.rb +118 -0
  112. data/ruby_ex/uri_ex.rb +45 -0
  113. data/ruby_ex/verbose_object.rb +30 -0
  114. data/ruby_ex/version.rb +66 -0
  115. data/ruby_ex/yaml/basenode_ext.rb +63 -0
  116. data/ruby_ex/yaml/chop_header.rb +23 -0
  117. data/ruby_ex/yaml/transform.rb +449 -0
  118. data/ruby_ex/yaml/yregexpath.rb +76 -0
  119. data/src/changelog.rb +28 -18
  120. data/src/conflict.rb +20 -0
  121. data/src/diff.rb +18 -0
  122. data/src/diffstat.rb +9 -3
  123. data/src/last_changed_date.rb +18 -0
  124. data/src/mail.rb +33 -65
  125. data/src/message.rb +15 -9
  126. data/src/mycommit.rb +29 -14
  127. data/src/news.rb +24 -3
  128. data/src/status.rb +17 -0
  129. data/src/svn.rb +2 -2
  130. data/src/vcs.rb +24 -3
  131. metadata +124 -5
  132. data/lrdetools.rb +0 -12
@@ -0,0 +1,18 @@
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: polrop $
6
+ # $Id: cache.rb 173 2005-03-29 09:41:46Z polrop $
7
+
8
+
9
+ class Array
10
+
11
+ # Basically, do the same as each_with_index but yield the arguments in
12
+ # reverse order. Thus an array can be assume in certain cases to a hash.
13
+ def each_pair(&block)
14
+ each_with_index { |x, i| block[i, x] }
15
+ end
16
+
17
+ end # class Array
18
+
@@ -0,0 +1,101 @@
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
+
5
+ # $LastChangedBy: ertai $
6
+ # $Id: ask.rb 187 2005-04-03 00:11:56Z ertai $
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
+ if defined? TEST_MODE or $0 == __FILE__
45
+
46
+ require 'test/unit'
47
+ class AskTest < Test::Unit::TestCase
48
+
49
+ def ask_checker ( question, default, answer, ref, out, err )
50
+ require 'stringio'
51
+ cin, cout, cerr = StringIO.new, StringIO.new, StringIO.new
52
+ cin.puts answer
53
+ cin.rewind
54
+ res = ask(question, default, cin, cout, cerr)
55
+ cout.rewind
56
+ cerr.rewind
57
+ assert_equal(res, ref, 'bad return value')
58
+ assert_equal(cout.readlines.join, out, 'bad standard output')
59
+ assert_equal(cerr.readlines.join, err, 'bad error output')
60
+ end
61
+
62
+ def test_bad_default
63
+ assert_raise(ArgumentError) { ask_checker('Q', :foo, '', :y, [], []) }
64
+ assert_nothing_raised do
65
+ ask_checker('Q', :y, 'y', :y, 'Q [Y/n]: ', '')
66
+ end
67
+ assert_nothing_raised do
68
+ ask_checker('Q', :n, 'y', :y, 'Q [y/N]: ', '')
69
+ end
70
+ end
71
+
72
+ def test_valid_default_yes
73
+ ask_checker('Q', :y, 'y', :y, 'Q [Y/n]: ', '')
74
+ ask_checker('Q', :y, 'Y', :y, 'Q [Y/n]: ', '')
75
+ ask_checker('Q', :y, 'n', :n, 'Q [Y/n]: ', '')
76
+ ask_checker('Q', :y, 'N', :n, 'Q [Y/n]: ', '')
77
+ ask_checker('Q', :y, '', :y, 'Q [Y/n]: ', '')
78
+ end
79
+
80
+ def test_valid_default_no
81
+ ask_checker('Q', :n, 'y', :y, 'Q [y/N]: ', '')
82
+ ask_checker('Q', :n, 'Y', :y, 'Q [y/N]: ', '')
83
+ ask_checker('Q', :n, 'n', :n, 'Q [y/N]: ', '')
84
+ ask_checker('Q', :n, 'N', :n, 'Q [y/N]: ', '')
85
+ ask_checker('Q', :n, '', :n, 'Q [y/N]: ', '')
86
+ end
87
+
88
+ def test_invalid_answer
89
+ ask_checker('Q', :n, "bad\ny", :y,
90
+ "Q [y/N]: \nQ [y/N]: ", ANSWER_NOT_VALID + "\n")
91
+ ask_checker('Q', :y, "ad\n\n", :y,
92
+ "Q [Y/n]: \nQ [Y/n]: ", ANSWER_NOT_VALID + "\n")
93
+ end
94
+
95
+ def test_ask1
96
+ ask_checker('Q', :y, 'y', :y, 'Q [Y/n]: ', '')
97
+ end
98
+
99
+ end # class AskTest
100
+
101
+ end
@@ -0,0 +1,302 @@
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 212 2005-05-05 20:07:23Z ertai $
5
+
6
+
7
+ # Extension to have inherited, class attributes,
8
+ # Where each class have his own attributes set.
9
+ module AttributedClass
10
+
11
+ class Attribute
12
+
13
+ attr_reader :name, :descr, :klass, :default_proc
14
+ attr_accessor :default
15
+
16
+ @@default_proc = nil
17
+
18
+ def initialize ( name, descr, *args, &block )
19
+ raise ArgumentError, 'need a symbol' unless name.is_a? Symbol
20
+ @name, @descr = name, descr
21
+ @default, @klass = nil, nil
22
+ @mandatory = false
23
+ @visible = true
24
+ if block_given?
25
+ @default_proc = block
26
+ else
27
+ @default_proc = @@default_proc
28
+ end
29
+ other = []
30
+
31
+ args.each do |arg|
32
+ case arg
33
+ when :mandatory then @mandatory = true
34
+ when :invisible then @visible = false
35
+ else other << arg
36
+ end
37
+ end
38
+
39
+ case other.size
40
+ when 1
41
+ if other[0].is_a? Class
42
+ @klass = [other[0]]
43
+ elsif other[0].is_a? Array and other[0].all? { |k| k.is_a? Class }
44
+ @klass = other[0]
45
+ else
46
+ @default = other[0]
47
+ end
48
+ when 2
49
+ a, b = other
50
+ if a.is_a? Class and b.is_a? a
51
+ @klass, @default = [a], b
52
+ elsif a.is_a? Array and
53
+ a.all? { |k| k.is_a? Class } and
54
+ a.any? { |k| b.is_a? k }
55
+ @klass, @default = a, b
56
+ elsif b.is_a? Class and a.is_a? b
57
+ @klass, @default = [b], a
58
+ elsif b.is_a? Array and
59
+ b.all? { |k| k.is_a? Class } and
60
+ b.any? { |k| a.is_a? k }
61
+ @klass, @default = b, a
62
+ else
63
+ raise ArgumentError, 'need a Class and a default value'
64
+ end
65
+ when 0
66
+ else raise ArgumentError, 'too many values'
67
+ end
68
+
69
+ # removed because it causes some probleme when distributed such a class
70
+ #[@name, @descr, @default].each { |x| x.freeze }
71
+ end
72
+
73
+ def visible?
74
+ @visible
75
+ end
76
+
77
+ def invisible?
78
+ !@visible
79
+ end
80
+
81
+ def mandatory?
82
+ @mandatory
83
+ end
84
+
85
+ def valid? ( anObject )
86
+ return false if mandatory? and anObject.nil?
87
+ @klass.nil? or @klass.empty? or @klass.any? { |k| anObject.is_a? k }
88
+ end
89
+
90
+
91
+ def self.set_default_proc ( &block )
92
+ @@default_proc = block
93
+ end
94
+
95
+
96
+ def help ( output=STDERR )
97
+ output << "#@name: #@descr"
98
+ output << " [#@klass]" unless @klass.nil?
99
+ other = []
100
+ other << 'mandatory' if mandatory?
101
+ other << 'invisible' if invisible?
102
+ output << " (#{other.join(', ')})" unless other.empty?
103
+ output
104
+ end
105
+
106
+ def inspect
107
+ help('#<') + '>'
108
+ end
109
+
110
+ end # class Attribute
111
+
112
+
113
+ class MethodAttribute < Attribute
114
+ VISIBILITIES = [ :private, :protected, :public, :unknown ]
115
+ attr_reader :visibility
116
+ def private?
117
+ @visibility == :private
118
+ end
119
+ def protected?
120
+ @visibility == :protected
121
+ end
122
+ def public?
123
+ @visibility == :public
124
+ end
125
+ def initialize ( name, descr, *args )
126
+ vis = args.find { |arg| VISIBILITIES.include? arg }
127
+ args.delete(vis) unless vis.nil?
128
+ @visibility = vis || :unknown
129
+ super(name, descr, *args)
130
+ end
131
+ end # class MethodAttribute
132
+
133
+
134
+ class AttributeError < Exception
135
+
136
+ def initialize ( anObject, anAttr, val=nil )
137
+ if val.nil?
138
+ super "Missing the #{anAttr.name} attribute for `#{anObject}'"
139
+ else
140
+ super "Attribute #{anAttr.name} wait for a `#{anAttr.klass}' not " +
141
+ "`#{val}' in #{anObject}"
142
+ end
143
+ end
144
+
145
+ end # class AttributeError
146
+
147
+
148
+ def self.included ( aClass )
149
+
150
+ aClass.module_eval do
151
+
152
+ def initialize_attributes ( *default_proc_args )
153
+ self.class.attributes.each do |attr|
154
+ next if attr.is_a? MethodAttribute
155
+ if attr.default_proc.nil? and attr.default.nil?
156
+ instance_variable_set("@#{attr.name}", nil)
157
+ else
158
+ unless attr.default_proc.nil?
159
+ pr = attr.default_proc
160
+ if pr.arity == default_proc_args.size
161
+ default = pr[*default_proc_args]
162
+ else
163
+ default = pr[attr, *default_proc_args]
164
+ end
165
+ attr.default = default
166
+ end
167
+ send "#{attr.name}=", attr.default
168
+ end
169
+ end
170
+ end
171
+
172
+ def self.set_default_proc ( &block )
173
+ Attribute.set_default_proc(&block)
174
+ end
175
+
176
+ def each_attribute ( &block )
177
+ self.class.attributes.each do |attr|
178
+ block[attr, send(attr.name)]
179
+ end
180
+ end
181
+
182
+ def each_variable_attribute ( &block )
183
+ self.class.attributes.each do |attr|
184
+ next if attr.is_a? MethodAttribute
185
+ block[attr, send(attr.name)]
186
+ end
187
+ end
188
+
189
+ def each_method_attribute ( &block )
190
+ self.class.attributes.each do |attr|
191
+ next unless attr.is_a? MethodAttribute
192
+ block[attr, send(attr.name)]
193
+ end
194
+ end
195
+
196
+ def check_attribute ( attr, anObject )
197
+ unless attr.valid? anObject
198
+ raise AttributeError.new(self, attr, anObject)
199
+ end
200
+ end
201
+
202
+ def check_attributes
203
+ each_variable_attribute(&method(:check_attribute))
204
+ end
205
+
206
+ def self.help ( output=STDERR )
207
+ output.puts "#{self.to_s}:"
208
+ attributes.each do |attr|
209
+ output << ' '
210
+ attr.help(output)
211
+ output.puts '.'
212
+ end
213
+ end
214
+
215
+ def self.attributes
216
+ unless defined? @attributes
217
+ if superclass and superclass.respond_to? :attributes
218
+ @attributes = superclass.attributes.dup
219
+ else
220
+ @attributes = []
221
+ end
222
+ end
223
+ @attributes
224
+ end
225
+
226
+ def self.attribute ( name, descr, *args, &block )
227
+ attr = Attribute.new(name, descr, *args, &block)
228
+ unless method_defined? name
229
+ if att = get_attribute(name, MethodAttribute)
230
+ attributes.delete(att)
231
+ end
232
+ attr_reader name
233
+ end
234
+ meth = "#{name}="
235
+ unless method_defined? meth
236
+ if att = get_attribute(meth, MethodAttribute)
237
+ attributes.delete(att)
238
+ end
239
+ attr_writer name
240
+ end
241
+ attributes << attr
242
+ end
243
+
244
+ def self.method_attribute ( name, descr, *args, &block )
245
+ # unless method_defined? name
246
+ # raise ArgumentError, "Undefined method #{name}"
247
+ # end
248
+ attr = MethodAttribute.new(name, descr, *args, &block)
249
+ attributes << attr
250
+ end
251
+
252
+ def self.get_attribute ( name, type=nil )
253
+ if type.nil?
254
+ attributes.find { |attr| attr.name == name }
255
+ else
256
+ attributes.find { |attr| attr.name == name && attr.is_a?(type) }
257
+ end
258
+ end
259
+
260
+ end
261
+
262
+ end
263
+
264
+ end # module AttributedClass
265
+
266
+
267
+
268
+ if defined? TEST_MODE or $0 == __FILE__
269
+
270
+ require 'test/unit'
271
+ class AttributedClassTest < Test::Unit::TestCase
272
+
273
+ def test_simple
274
+ c = Class.new
275
+ assert_nothing_raised do
276
+ c.module_eval { include AttributedClass }
277
+ end
278
+ assert_equal([], c.attributes)
279
+ assert_nothing_raised do
280
+ c.module_eval { attribute :foo, 'foofoo', 42 }
281
+ end
282
+ cc, d, dd = nil, nil, nil
283
+ assert_nothing_raised { cc = c.new }
284
+ assert_equal(nil, cc.foo)
285
+ assert_nothing_raised { cc.initialize_attributes }
286
+ assert_equal(42, cc.foo)
287
+ assert_nothing_raised { cc.foo = 32 }
288
+ assert_equal(32, cc.foo)
289
+ assert_nothing_raised { d = Class.new(c) }
290
+ assert_nothing_raised do
291
+ d.module_eval { attribute :bar, 'foofoo', :mandatory }
292
+ end
293
+ assert_nothing_raised { dd = d.new }
294
+ assert_nothing_raised { dd.initialize_attributes }
295
+ assert_raise(AttributedClass::AttributeMissingError) do
296
+ dd.check_attributes
297
+ end
298
+ end
299
+
300
+ end # class AttributedClassTest
301
+
302
+ end
@@ -0,0 +1,373 @@
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: cache.rb 186 2005-04-03 00:07:45Z ertai $
7
+
8
+
9
+ require 'pathname'
10
+ require 'md5'
11
+ require 'tempfile'
12
+ require 'fileutils'
13
+
14
+
15
+ class Cache
16
+
17
+ #
18
+ # Constants
19
+ #
20
+ REPOSITORY = '/var/cache/ruby_ex_cache'
21
+ MAX_SIZE = 50 * 1024 * 1024 # 50 MB
22
+ BLOCK_SIZE = 1024 # octets
23
+
24
+ #
25
+ # Attributs
26
+ #
27
+ attr_reader :repository, :max_size, :size
28
+
29
+ #
30
+ # Constructor
31
+ #
32
+ def initialize(repository=REPOSITORY, max_size=MAX_SIZE)
33
+ @repository = Pathname.new(repository)
34
+ @max_size = check_max_size(max_size)
35
+ create
36
+ end
37
+
38
+ #
39
+ # Methods
40
+ #
41
+ def create
42
+ @size = 0
43
+ @atime = []
44
+ if @repository.directory?
45
+ @repository.each_entry do |p|
46
+ next if p.to_s =~ /^\./
47
+ full_p = @repository + p
48
+ @atime << full_p
49
+ @size += full_p.size
50
+ end
51
+ @atime.sort! { |a, b| a.atime <=> b.atime }
52
+ else
53
+ @repository.mkdir
54
+ end
55
+ end
56
+
57
+ def clear
58
+ @repository.each_entry do |p|
59
+ (@repository + p).delete unless p.to_s =~ /^\./
60
+ end
61
+ @size = 0
62
+ @atime.clear
63
+ end
64
+
65
+ def recreate
66
+ clear
67
+ create
68
+ end
69
+
70
+ def open(md5sum=nil, mode='r', perm=0644, &block)
71
+ case mode
72
+ when 'r': read(md5sum, &block)
73
+ when 'w': write(perm, &block)
74
+ else
75
+ raise(ArgumentError, "`#{mode}' - bad mode")
76
+ end
77
+ end
78
+
79
+ def read(md5sum, &block)
80
+ p = @repository + md5sum.to_s
81
+ p.open('r', &block)
82
+ @atime.delete(p)
83
+ @atime << p
84
+ p
85
+ end
86
+
87
+ def write(perm=0644, &block)
88
+ tmp = nil
89
+ p = nil
90
+ begin
91
+ md5 = Digest::MD5.new
92
+ tmp = Tempfile.new('cache')
93
+ new_size = 0
94
+ while (str = block[])
95
+ tmp.write(str)
96
+ md5 << str
97
+ new_size += str.size
98
+ end
99
+ tmp.close
100
+ p = @repository + md5.to_s
101
+ if p.exist?
102
+ @atime.delete(p)
103
+ else
104
+ @size += new_size
105
+ end
106
+ FileUtils.move(tmp.path, p)
107
+ p.chmod(perm)
108
+ rescue
109
+ tmp.delete if tmp
110
+ raise
111
+ end
112
+ @atime << p
113
+ adjust_size
114
+ p
115
+ end
116
+
117
+ def import(filename)
118
+ p = nil
119
+ File.open(filename) do |f|
120
+ p = write(File.stat(filename).mode) { f.eof? ? nil : f.read(BLOCK_SIZE) }
121
+ end
122
+ p
123
+ end
124
+
125
+ def export(md5sum, dst_filename)
126
+ p = @repository + md5sum.to_s
127
+ FileUtils.copy_entry(p, dst_filename)
128
+ update_entry(p)
129
+ p
130
+ end
131
+
132
+ def present?(md5sum)
133
+ p = @repository + md5sum.to_s
134
+ p.exist? ? p : nil;
135
+ end
136
+
137
+ def each
138
+ @atime.each { |p| yield(p) }
139
+ end
140
+
141
+ def [](index)
142
+ @atime[index]
143
+ end
144
+
145
+ def max_size=(max_size)
146
+ @max_size = check_max_size(max_size)
147
+ adjust_size
148
+ end
149
+
150
+ def nb_files
151
+ @atime.size
152
+ end
153
+
154
+ protected
155
+ def adjust_size
156
+ while @size > @max_size
157
+ p = @atime.shift
158
+ @size -= p.size
159
+ p.delete
160
+ end
161
+ @size
162
+ end
163
+
164
+ protected
165
+ def check_max_size(max_size)
166
+ unless max_size > 0
167
+ raise(ArgumentError, "`#{max_size}' - bad cache maximum size")
168
+ end
169
+ max_size
170
+ end
171
+
172
+ end # class Cache
173
+
174
+
175
+ #
176
+ # Unit test suite
177
+ #
178
+ if defined? TEST_MODE or __FILE__ == $0
179
+
180
+
181
+ require 'test/unit/ui/yaml/testrunner'
182
+ require 'md5sum'
183
+
184
+
185
+ class CacheTest < Test::Unit::TestCase
186
+
187
+ #
188
+ # Tests
189
+ #
190
+ def test_simple
191
+ tmp_cache = nil
192
+ begin
193
+ tmp_cache = Tempfile.new('cache')
194
+ repo = Pathname.new(tmp_cache.path)
195
+ tmp_cache.delete
196
+ cache = nil
197
+ assert_nothing_raised { cache = Cache.new(repo) }
198
+ assert_equal(0, cache.nb_files, 'bad nb_files')
199
+ assert_nothing_raised { cache.create }
200
+ assert_equal(0, cache.nb_files, 'bad nb_files')
201
+ assert_nothing_raised { cache.clear }
202
+ assert_equal(0, cache.nb_files, 'bad nb_files')
203
+ assert_nothing_raised { cache.create }
204
+ assert_equal(0, cache.nb_files, 'bad nb_files')
205
+ assert_raises(Errno::ENOENT) { cache.open('toto', 'r') { |f| f.gets } }
206
+ assert_equal(0, cache.nb_files, 'bad nb_files')
207
+ assert_equal(0, cache.size)
208
+
209
+ data = [ "toto\n", "tata\n" ]
210
+ p1 = cache.open(nil, 'w') do
211
+ data.shift
212
+ end
213
+ assert_equal(File.md5sum(p1.to_s).to_s, p1.basename.to_s, 'bad md5 open')
214
+ assert_equal(1, cache.nb_files, 'bad nb_files')
215
+ assert_equal(p1, cache[0])
216
+ assert_equal(10, cache.size)
217
+ assert_equal(p1, cache.present?(p1.basename))
218
+ tmp_p = cache.read(p1) do |f|
219
+ assert_equal("toto\n", f.gets)
220
+ assert_equal("tata\n", f.gets)
221
+ assert(f.eof?)
222
+ end
223
+ assert_equal(tmp_p, p1)
224
+ check_atime(cache)
225
+ assert_equal(1, cache.nb_files, 'bad nb_files')
226
+ assert(cache.present?(p1))
227
+ assert_equal(p1, cache[0])
228
+ assert(! cache.present?('foo'))
229
+
230
+ tmp_file = Tempfile.new('cache')
231
+ system("man printf > #{tmp_file.path} 2> /dev/null")
232
+ md5 = File.md5sum(tmp_file.path)
233
+ p2 = cache.import(tmp_file.path)
234
+ assert_equal(md5.to_s, p2.basename.to_s, 'bad name after import')
235
+ assert(p2.exist?, 'file do not exist after import')
236
+ assert(FileUtils.cmp(tmp_file.path, p2.to_s), 'bad import')
237
+ assert_equal(md5.to_s, File.md5sum(p2.to_s).to_s, 'bad md5 import')
238
+ tmp_file.delete
239
+ assert_equal(2, cache.nb_files, 'bad nb_files')
240
+ assert_equal(p2, cache[1])
241
+ check_atime(cache)
242
+
243
+ data = [ "foo\n", "bar\n" ]
244
+ p3 = cache.open(nil, 'w') do
245
+ data.shift
246
+ end
247
+ assert_equal(File.md5sum(p3.to_s).to_s, p3.basename.to_s, 'bad md5 open')
248
+ assert_equal(3, cache.nb_files, 'bad nb_files')
249
+ assert_equal(p3, cache[2])
250
+ assert_equal(p3, cache.present?(p3.basename))
251
+
252
+ assert_equal(p1, cache[0], 'p1 first')
253
+ assert_equal(p2, cache[1], 'p2 second')
254
+ assert_equal(p3, cache[2], 'p3 third')
255
+ assert_equal(p1, cache.read(p1) { |f| f.gets })
256
+ assert_equal(p2, cache[0], 'p2 first')
257
+ assert_equal(p3, cache[1], 'p3 second')
258
+ assert_equal(p1, cache[2], 'p1 third')
259
+ check_atime(cache)
260
+ assert_equal(3, cache.nb_files, 'bad nb_files')
261
+
262
+
263
+ new_cache = nil
264
+ assert_nothing_raised { new_cache = Cache.new(repo) }
265
+ assert_equal(3, new_cache.nb_files, 'bad nb_files in new cache')
266
+ check_atime(new_cache)
267
+ assert_equal(cache.size, new_cache.size)
268
+
269
+ assert_equal(3, cache.nb_files, 'bad nb_files')
270
+ assert(cache.size > 1024)
271
+ cache.max_size = 1024
272
+ assert(cache.size < 1024)
273
+ assert_equal(p1.size + p3.size, cache.size)
274
+ assert_equal(2, cache.nb_files, 'bad nb_files')
275
+ assert_equal(p3, cache[0], 'p3 first')
276
+ assert_equal(p1, cache[1], 'p1 second')
277
+
278
+ cache.clear
279
+ assert_equal(0, cache.nb_files, 'bad nb_files')
280
+ assert_equal(0, cache.size)
281
+ ensure
282
+ repo.rmtree if repo
283
+ end
284
+ end
285
+
286
+ def test_write_twice_the_same
287
+ tmp_cache = nil
288
+ begin
289
+ tmp_cache = Tempfile.new('cache')
290
+ repo = Pathname.new(tmp_cache.path)
291
+ tmp_cache.delete
292
+ cache = nil
293
+ assert_nothing_raised { cache = Cache.new(repo) }
294
+ assert_equal(0, cache.nb_files, 'bad nb_files')
295
+ assert_equal(0, cache.size)
296
+
297
+ data = [ "toto\n", "tata\n" ]
298
+ d = data.dup
299
+ p1 = cache.open(nil, 'w') do
300
+ d.shift
301
+ end
302
+
303
+ assert_equal(File.md5sum(p1.to_s).to_s, p1.basename.to_s, 'bad md5 open')
304
+ assert_equal(1, cache.nb_files, 'bad nb_files')
305
+ assert_equal(p1, cache[0])
306
+ assert_equal(10, cache.size)
307
+ assert_equal(p1, cache.present?(p1.basename))
308
+
309
+ data = [ "toto\n", "tata\n" ]
310
+ d = data.dup
311
+ p2 = cache.open(nil, 'w') do
312
+ d.shift
313
+ end
314
+
315
+ assert_equal(File.md5sum(p1.to_s).to_s, p1.basename.to_s, 'bad md5 open')
316
+ assert_equal(1, cache.nb_files, 'bad nb_files')
317
+ assert_equal(p1, cache[0])
318
+ assert_equal(10, cache.size)
319
+ assert_equal(p1, cache.present?(p1.basename))
320
+ ensure
321
+ repo.rmtree if repo
322
+ end
323
+ end
324
+
325
+ def test_nested_write
326
+ tmp_cache = nil
327
+ begin
328
+ tmp_cache = Tempfile.new('cache')
329
+ repo = Pathname.new(tmp_cache.path)
330
+ tmp_cache.delete
331
+ cache = nil
332
+ assert_nothing_raised { cache = Cache.new(repo) }
333
+ assert_equal(0, cache.nb_files, 'bad nb_files')
334
+ assert_equal(0, cache.size)
335
+
336
+ data = [ "toto\n", "tata\n" ]
337
+ d1 = data.dup
338
+ p1 = cache.open(nil, 'w') do
339
+ d2 = data.dup
340
+ p2 = cache.open(nil, 'w') do
341
+ d2.shift
342
+ end
343
+ d1.shift
344
+ end
345
+
346
+ assert_equal(File.md5sum(p1.to_s).to_s, p1.basename.to_s, 'bad md5 open')
347
+ assert_equal(1, cache.nb_files, 'bad nb_files')
348
+ assert_equal(p1, cache[0])
349
+ assert_equal(10, cache.size)
350
+ assert_equal(p1, cache.present?(p1.basename))
351
+
352
+ ensure
353
+ repo.rmtree if repo
354
+ end
355
+ end
356
+
357
+ #
358
+ # Utilities
359
+ #
360
+ protected
361
+ def check_atime(cache)
362
+ j = 0
363
+ for i in 1...cache.nb_files do
364
+ assert(cache[j].atime <= cache[i].atime, "bad atime order for #{i}")
365
+ j = i
366
+ end
367
+ end
368
+
369
+ end # class CacheTest
370
+
371
+
372
+ end
373
+