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,53 @@
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: session_server.rb 91 2005-01-09 22:24:40Z polrop $
7
+
8
+
9
+ require 'drb/extserv'
10
+
11
+
12
+ module DRb
13
+
14
+ class SessionServer < ExtServ
15
+
16
+ attr_reader :name
17
+
18
+ alias sid name
19
+
20
+ end # class SessionServer
21
+
22
+ end # module DRb
23
+
24
+
25
+ if __FILE__ == $0
26
+
27
+ require 'drb/drb'
28
+ require 'drb/extserv'
29
+ require 'yaml'
30
+ require 'tempfile'
31
+
32
+ class Foo
33
+
34
+ def hello
35
+ 'hello'
36
+ end
37
+
38
+ def salut
39
+ 'salut'
40
+ end
41
+
42
+ end # class SessionManagerTest
43
+
44
+
45
+ raise "usage: #{$0} <uri> <name>" unless ARGV.size == 2
46
+ uri = ARGV.shift
47
+ name = ARGV.shift
48
+
49
+ server = DRb::DRbServer.new("druby://localhost:0", Foo.new, {})
50
+ ext_srv = DRb::SessionServer.new(uri, name, server)
51
+ server.thread.join
52
+
53
+ end
@@ -0,0 +1,143 @@
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: dtime.rb 146 2005-02-01 11:02:42Z polrop $
7
+
8
+
9
+ require 'module/attr_once'
10
+
11
+
12
+ class DTime
13
+
14
+ def initialize(delta)
15
+ @delta = delta.abs
16
+ @min, @sec = @delta.divmod(60)
17
+ @hour, @min = @min.floor.divmod(60)
18
+ @day, @hour = @hour.divmod(24)
19
+ end
20
+
21
+ attr_reader :delta, :sec, :min, :hour, :day
22
+
23
+ def to_s
24
+ @delta.to_s.freeze
25
+ end
26
+
27
+ def inspect
28
+ "#@day days #@hour hours #@min mins #@sec secs".freeze
29
+ end
30
+
31
+ def to_a
32
+ [ @day, @hour, @min, @sec ].freeze
33
+ end
34
+
35
+ def to_i
36
+ @delta.to_i
37
+ end
38
+
39
+ alias to_f delta
40
+
41
+ def hash
42
+ @delta.hash
43
+ end
44
+
45
+ def to_hash
46
+ { :days => @day, :hours => @hour, :mins => @min, :secs => @sec }.freeze
47
+ end
48
+
49
+ def floor
50
+ @delta.floor
51
+ end
52
+
53
+ def round
54
+ @delta.round
55
+ end
56
+
57
+ def to_yaml(opts={})
58
+ ((@day != 0 ? "#@day days " : '') +
59
+ (@hour != 0 ? "#@hour hours " : '') +
60
+ (@min != 0 ? "#@min mins " : '') +
61
+ "#@sec secs").to_yaml(opts)
62
+ end
63
+
64
+ attr_once :to_s, :inspect, :to_a, :to_i, :hash, :to_hash, :floor,
65
+ :round, :to_yaml
66
+
67
+ end # class DTime
68
+
69
+
70
+ if defined? TEST_MODE or __FILE__ == $0
71
+
72
+
73
+ require 'test/unit/ui/yaml/testrunner'
74
+ require 'yaml'
75
+
76
+
77
+ class DTimeTest < Test::Unit::TestCase
78
+
79
+ def test_simple
80
+ d = DTime.new(260.33)
81
+ assert_equal(260, d.delta.floor)
82
+ assert_equal(20, d.sec.floor)
83
+ assert_equal(4, d.min)
84
+ assert_equal(0, d.hour)
85
+ assert_equal(0, d.day)
86
+ end
87
+
88
+ def test_complex
89
+ d = DTime.new(265678.42000)
90
+ assert_equal(265678, d.delta.floor)
91
+ assert_equal(58, d.sec.floor)
92
+ assert_equal(47, d.min)
93
+ assert_equal(1, d.hour)
94
+ assert_equal(3, d.day)
95
+ assert_equal([ 3, 1, 47 ], d.to_a[0..2])
96
+ assert_equal(58, d.to_a[3].floor)
97
+ h = d.to_hash
98
+ assert_equal(3, h[:days])
99
+ assert_equal(1, h[:hours])
100
+ assert_equal(47, h[:mins])
101
+ assert_equal(58, h[:secs].floor)
102
+ end
103
+
104
+ def test_conversion
105
+ d = DTime.new(260.75)
106
+ assert_equal('260.75', d.to_s)
107
+ assert_equal('0 days 0 hours 4 mins 20.75 secs', d.inspect)
108
+ assert_equal(260, d.to_f.floor)
109
+ assert_equal(260, d.to_i)
110
+ assert_equal(260, d.floor)
111
+ assert_equal(261, d.round)
112
+ end
113
+
114
+ def test_negative
115
+ d = DTime.new(-265678.42000)
116
+ assert_equal(265678, d.delta.floor)
117
+ assert_equal(58, d.sec.floor)
118
+ assert_equal(47, d.min)
119
+ assert_equal(1, d.hour)
120
+ assert_equal(3, d.day)
121
+ end
122
+
123
+ def test_marshal
124
+ d = DTime.new(-265678.42000)
125
+ assert_equal(d.floor, Marshal.load(Marshal.dump(d)).floor)
126
+ end
127
+
128
+ def test_to_yaml
129
+ d = DTime.new(265678.42000)
130
+ assert_equal('--- 3 days 1 hours 47 mins 58.4199999999837 secs', d.to_yaml)
131
+ d = DTime.new(5678.42000)
132
+ assert_equal('--- 1 hours 34 mins 38.4200000000001 secs', d.to_yaml)
133
+ d = DTime.new(158.42000)
134
+ assert_equal('--- 2 mins 38.42 secs', d.to_yaml)
135
+ d = DTime.new(58.42000)
136
+ assert_equal('--- 58.42 secs', d.to_yaml)
137
+ end
138
+
139
+ end # class DTimeTest
140
+
141
+
142
+ end
143
+
@@ -0,0 +1,63 @@
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
+
5
+ # $LastChangedBy: ertai $
6
+ # $Id: dumpable_proc.rb 190 2005-04-04 14:46:40Z ertai $
7
+
8
+ require 'yaml'
9
+ require 'delegate'
10
+
11
+ class DumpableProc < DelegateClass(Proc)
12
+
13
+ def initialize ( str )
14
+ @str = str.freeze
15
+ super(eval("proc { #@str }"))
16
+ end
17
+
18
+ def _dump ( depth )
19
+ Marshal.dump([self.class, @str])
20
+ end
21
+
22
+ def self._load ( data )
23
+ klass, str = Marshal.load(data)
24
+ klass.new(str)
25
+ end
26
+
27
+ def to_yaml ( opts={} )
28
+ "#{to_yaml_type} #@str"
29
+ end
30
+
31
+ def to_s
32
+ @str
33
+ end
34
+
35
+ def to_yaml_type
36
+ '!proc'
37
+ end
38
+
39
+ end
40
+
41
+ YAML.add_builtin_type('!proc') do |type, val|
42
+ DumpableProc.new(val.to_s)
43
+ end
44
+
45
+ if defined? TEST_MODE or $0 == __FILE__
46
+
47
+ require 'test/unit'
48
+
49
+ class DumpableProcTest < Test::Unit::TestCase
50
+
51
+ def test_simple
52
+ p = DumpableProc.new('|x| 3 + x')
53
+ assert_equal(7, p[4])
54
+ str = nil
55
+ assert_nothing_raised { str = Marshal.dump(p) }
56
+ p2 = nil
57
+ assert_nothing_raised { p2 = Marshal.load(str) }
58
+ assert_equal(9, p2[6])
59
+ end
60
+
61
+ end
62
+
63
+ end
@@ -0,0 +1,32 @@
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
+ # Revision:: $Id: exception.rb 200 2005-04-22 22:55:31Z ertai $
5
+
6
+
7
+ class Exception
8
+
9
+ #FIXME: write a ruby_pp which print exception backtrace exactly as ruby does
10
+ # by cuting long and repetitive backtrace.
11
+
12
+ def long_pp
13
+ str = short_pp + "\n"
14
+ backtrace[1..-1].each { |x| str += " from #{x}\n" } if backtrace
15
+ str.chomp!
16
+ str
17
+ end
18
+
19
+ def short_pp
20
+ if backtrace.nil?
21
+ tiny_pp
22
+ else
23
+ "#{backtrace[0]}: #{tiny_pp}"
24
+ end
25
+ end
26
+
27
+ def tiny_pp
28
+ exc_name = inspect.sub(/^#<(\w+):.+$/, '\1')
29
+ "#{self} (#{exc_name})"
30
+ end
31
+
32
+ end # class Exception
@@ -0,0 +1,229 @@
1
+ require 'pathname_ex'
2
+ require 'abstract'
3
+
4
+ module FileType
5
+
6
+ @@subclasses = []
7
+
8
+ def self.register ( klass )
9
+ @@subclasses << klass
10
+ end
11
+
12
+ class Generic
13
+ include Abstract
14
+
15
+ attr_reader :path, :base, :ext
16
+
17
+ def initialize ( path )
18
+ @path = Pathname.new(path)
19
+ re = self.class.extension
20
+ raise ArgumentError, "bad class #{self.class}" if re.nil?
21
+ unless @path.to_s =~ re
22
+ raise ArgumentError, "#{@path} do not match /#{re.source}/"
23
+ end
24
+ @base, @ext = $`, $&
25
+ if @ext.empty?
26
+ @base = self
27
+ else
28
+ @base = FileType.guess(@base)
29
+ end
30
+ end
31
+
32
+ def extsplit
33
+ [@base, @ext]
34
+ end
35
+
36
+ def self.inherited ( klass )
37
+ FileType.register(klass)
38
+ end
39
+
40
+ def to_s
41
+ @path.to_s
42
+ end
43
+
44
+ def self.extension
45
+ const_get(:EXTENSION)
46
+ end
47
+
48
+ def self.match_type ( path, max, best )
49
+ ext_re = self.extension
50
+ if path.to_s =~ ext_re and $&.size > max
51
+ return [$&.size, self]
52
+ end
53
+ return [max, best]
54
+ end
55
+
56
+ def + ( arg )
57
+ @path + arg.to_s
58
+ end
59
+
60
+ end # class Generic
61
+
62
+ class Unknown < Generic
63
+ include Concrete
64
+ EXTENSION = /(\.[^.\/]+)?$/
65
+ end # class Unknown
66
+
67
+ class ExtractError < Exception
68
+ end
69
+
70
+ module Extractable
71
+
72
+ def self.included ( aClass )
73
+
74
+ aClass.module_eval do
75
+
76
+ def mk_cmd
77
+ base, ext = @base.extsplit
78
+ @tmp = TempPath.new(base.path.basename, ext)
79
+ cmd = self.class.const_get(:EXTRACT_COMMAND).gsub(/%I/, @path.to_s)
80
+ cmd.gsub!(/%O/, @tmp.to_s)
81
+ cmd
82
+ end
83
+
84
+ def extract
85
+ cmd = mk_cmd
86
+ unless system(cmd)
87
+ @tmp.rmtree if @tmp.exist?
88
+ err = "
89
+ |Cannot extract a file:
90
+ | path: #{to_s}
91
+ | type: #{self.class}
92
+ | command: #{cmd}
93
+ | exit status: #{$? >> 8}
94
+ "
95
+ raise ExtractError, err.gsub(/^\s*|/, '')
96
+ end
97
+ FileType.guess(@tmp)
98
+ end
99
+
100
+ alias :default :extract
101
+
102
+ end
103
+
104
+ end
105
+
106
+ end # module Extractable
107
+
108
+ # FIXME Use the zlib if gzip not available.
109
+ class Gz < Generic
110
+ include Extractable
111
+ include Concrete
112
+ EXTENSION = /(\.(gz|z|Z)|-gz|-z|_z)$/
113
+ EXTRACT_COMMAND = 'gzip -d -c "%I" > "%O"'
114
+ end # class Gz
115
+
116
+ class Bz2 < Generic
117
+ include Extractable
118
+ include Concrete
119
+ EXTENSION = /\.(bz2|bz)$/
120
+ EXTRACT_COMMAND = 'bzip2 -d -c "%I" > "%O"'
121
+ end # class Bz2
122
+
123
+ class Zip < Generic
124
+ include Extractable
125
+ include Concrete
126
+ EXTENSION = /\.zip$/
127
+ EXTRACT_COMMAND = 'unzip -p "%I" > "%O"'
128
+ end # class Zip
129
+
130
+ module ExtractableDir
131
+
132
+ def self.included ( aClass )
133
+
134
+ aClass.module_eval do
135
+
136
+ include Extractable
137
+
138
+ alias :mk_cmd_extractable :mk_cmd
139
+
140
+ def mk_cmd
141
+ cmd = mk_cmd_extractable
142
+ @tmp.mkpath
143
+ @log = TempPath.new
144
+ cmd.gsub!(/%L/, @log.to_s)
145
+ end
146
+
147
+ alias :extract_extractable :extract
148
+
149
+ def extract
150
+ dir = extract_extractable
151
+ # We want the longest common path
152
+ longest = nil
153
+ @log.each_line do |line|
154
+ path = line.split(/\//)
155
+ longest = path if longest.nil?
156
+ longest &= path
157
+ raise CorruptedTarball, self if longest.empty?
158
+ end
159
+ dir.path + longest.join('/')
160
+ end
161
+
162
+ end
163
+
164
+ end
165
+
166
+ end # module ExtractableDir
167
+
168
+ class Tar < Generic
169
+ include ExtractableDir
170
+ include Concrete
171
+ EXTENSION = /\.tar$/
172
+ EXTRACT_COMMAND = 'tar xvf "%I" -C "%O" > "%L"'
173
+ end # class Tar
174
+
175
+ class TarGz < Generic
176
+ include ExtractableDir
177
+ include Concrete
178
+ EXTENSION = /\.(tar\.gz|tgz)$/
179
+ EXTRACT_COMMAND = 'tar xvzf "%I" -C "%O" > %L'
180
+ end # class TarGz
181
+
182
+ class TarBz2 < Generic
183
+ include ExtractableDir
184
+ include Concrete
185
+ EXTENSION = /\.(tar\.bz2|tbz2|tbz)$/
186
+ EXTRACT_COMMAND = 'bzip2 -c -d %I | tar xvf - -C "%O" > "%L"'
187
+ end # class TarBz2
188
+
189
+ class Directory < Generic
190
+ include Concrete
191
+
192
+ EXTENSION = /()$/
193
+
194
+ def self.match_type ( path, max, best )
195
+ if File.directory? path
196
+ return [path.to_s.size, self]
197
+ end
198
+ return [max, best]
199
+ end
200
+
201
+ end # class Directory
202
+
203
+ def self.guess_class ( path )
204
+ lazy_init
205
+ max = -1
206
+ best = Unknown
207
+ @@subclasses.each do |klass|
208
+ max, best = klass.match_type(path, max, best)
209
+ end
210
+ return best
211
+ end
212
+
213
+ def self.guess ( path )
214
+ guess_class(path).new(path)
215
+ end
216
+
217
+ def self.lazy_init
218
+ return if defined? @@init
219
+ @@init = true
220
+ @@subclasses.delete_if do |klass|
221
+ klass.abstract? or not (klass.is_a? Class) # and constants.include? klass)
222
+ end
223
+ @@subclasses.each do |klass|
224
+ ext = klass.extension
225
+ raise ArgumentError, "Bad extension #{ext}" unless ext.is_a? Regexp
226
+ end
227
+ end
228
+
229
+ end # module FileType