ratch 0.4.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. data/COPYING +17 -669
  2. data/HISTORY +6 -0
  3. data/MANIFEST +36 -0
  4. data/METADATA +14 -0
  5. data/NEWS +7 -0
  6. data/README +67 -17
  7. data/bin/ratch +5 -78
  8. data/demo/tryme-task.ratch +12 -0
  9. data/demo/tryme1.ratch +6 -0
  10. data/lib/ratch/core_ext.rb +6 -0
  11. data/lib/ratch/core_ext/facets.rb +1 -0
  12. data/lib/ratch/core_ext/filetest.rb +52 -0
  13. data/lib/ratch/core_ext/object.rb +8 -0
  14. data/lib/ratch/core_ext/pathname.rb +38 -0
  15. data/lib/ratch/core_ext/string.rb +44 -0
  16. data/lib/ratch/{dsl/console.rb → core_ext/to_console.rb} +2 -76
  17. data/lib/ratch/core_ext/to_list.rb +29 -0
  18. data/lib/ratch/dsl.rb +494 -49
  19. data/lib/ratch/index.rb +4 -0
  20. data/lib/ratch/io.rb +116 -0
  21. data/lib/ratch/pathglob.rb +73 -0
  22. data/lib/ratch/plugin.rb +55 -0
  23. data/lib/ratch/runmode.rb +69 -0
  24. data/lib/ratch/script.rb +52 -0
  25. data/lib/ratch/service.rb +33 -0
  26. data/lib/ratch/task.rb +249 -0
  27. data/lib/ratch/task2.rb +298 -0
  28. data/test/README +1 -0
  29. data/test/test_helper.rb +4 -0
  30. data/test/test_task.rb +46 -0
  31. metadata +90 -150
  32. data/CHANGES +0 -22
  33. data/TODO +0 -2
  34. data/bin/lt +0 -56
  35. data/bin/ludo +0 -14
  36. data/bin/manifest +0 -451
  37. data/bin/ratch-find +0 -21
  38. data/demo/WILMA +0 -1
  39. data/demo/XR +0 -9
  40. data/demo/lib/foo/foo.rb +0 -7
  41. data/demo/p.rb +0 -9
  42. data/demo/r.rb +0 -6
  43. data/demo/t.rb +0 -3
  44. data/demo/task/config.yaml +0 -4
  45. data/demo/task/one +0 -6
  46. data/demo/task/simplebuild +0 -15
  47. data/demo/task/stats +0 -4
  48. data/demo/task/task +0 -6
  49. data/demo/task/tryme +0 -10
  50. data/lib/ratch/dsl/argv.rb +0 -112
  51. data/lib/ratch/dsl/batch.rb +0 -232
  52. data/lib/ratch/dsl/build.rb +0 -174
  53. data/lib/ratch/dsl/email.rb +0 -108
  54. data/lib/ratch/dsl/file.rb +0 -205
  55. data/lib/ratch/dsl/meta.rb +0 -125
  56. data/lib/ratch/dsl/options.rb +0 -98
  57. data/lib/ratch/dsl/setup.rb +0 -124
  58. data/lib/ratch/dsl/sign.rb +0 -243
  59. data/lib/ratch/dsl/stage.rb +0 -147
  60. data/lib/ratch/dsl/task.rb +0 -139
  61. data/lib/ratch/dsl/upload.rb +0 -436
  62. data/lib/ratch/dsl/zip.rb +0 -59
  63. data/lib/ratch/extra/email.rb +0 -5
  64. data/lib/ratch/extra/stage.rb +0 -5
  65. data/lib/ratch/extra/zip.rb +0 -5
  66. data/lib/ratch/manager.rb +0 -53
  67. data/lib/ratch/manifest.rb +0 -540
  68. data/lib/ratch/metadata/information.rb +0 -258
  69. data/lib/ratch/metadata/package.rb +0 -108
  70. data/lib/ratch/metadata/project.rb +0 -523
  71. data/lib/ratch/metadata/release.rb +0 -108
  72. data/lib/ratch/support/errors.rb +0 -4
  73. data/lib/ratch/support/filename.rb +0 -18
  74. data/lib/ratch/support/filetest.rb +0 -29
  75. data/lib/ratch/toolset/ruby/announce +0 -224
  76. data/lib/ratch/toolset/ruby/compile +0 -49
  77. data/lib/ratch/toolset/ruby/install +0 -77
  78. data/lib/ratch/toolset/ruby/notes +0 -185
  79. data/lib/ratch/toolset/ruby/pack/gem +0 -93
  80. data/lib/ratch/toolset/ruby/pack/tgz +0 -46
  81. data/lib/ratch/toolset/ruby/pack/zip +0 -46
  82. data/lib/ratch/toolset/ruby/publish +0 -57
  83. data/lib/ratch/toolset/ruby/release +0 -8
  84. data/lib/ratch/toolset/ruby/setup +0 -1616
  85. data/lib/ratch/toolset/ruby/stamp +0 -33
  86. data/lib/ratch/toolset/ruby/stats +0 -138
  87. data/lib/ratch/toolset/ruby/test/crosstest +0 -305
  88. data/lib/ratch/toolset/ruby/test/extest +0 -129
  89. data/lib/ratch/toolset/ruby/test/isotest +0 -293
  90. data/lib/ratch/toolset/ruby/test/load +0 -39
  91. data/lib/ratch/toolset/ruby/test/loadtest +0 -28
  92. data/lib/ratch/toolset/ruby/test/syntax +0 -29
  93. data/lib/ratch/toolset/ruby/test/test +0 -26
  94. data/lib/ratch/toolset/sandbox/query +0 -11
  95. data/man/ratch.man +0 -73
  96. data/meta/MANIFEST +0 -130
  97. data/meta/config.yaml +0 -9
  98. data/meta/icli.yaml +0 -16
  99. data/meta/project.yaml +0 -20
  100. data/meta/ratch.roll +0 -2
  101. data/meta/xProjectInfo +0 -41
  102. data/task/clobber/package +0 -10
  103. data/task/man +0 -14
  104. data/task/publish +0 -57
  105. data/task/release +0 -9
  106. data/task/setup +0 -1616
  107. data/task/stats +0 -138
@@ -0,0 +1,4 @@
1
+ module Ratch
2
+ VERSION = "1.0.0"
3
+ end
4
+
@@ -0,0 +1,116 @@
1
+ require 'facets/consoleutils'
2
+
3
+ module Ratch
4
+
5
+ # = Ratch IO
6
+ #
7
+ # The IO class is used to cleanly separate out the
8
+ # basic input/output "dialog" between user and script.
9
+ #
10
+ class IO
11
+
12
+ #
13
+ attr :runmode
14
+
15
+ #
16
+ def initialize(runmode)
17
+ @runmode = runmode
18
+ end
19
+
20
+ def force? ; runmode.force? ; end
21
+ def quiet? ; runmode.quiet? ; end
22
+ def trace? ; runmode.trace? ; end
23
+ def debug? ; runmode.debug? ; end
24
+ def dryrun? ; runmode.dryrun? ; end
25
+ def noharm? ; runmode.noharm? ; end
26
+
27
+ # Internal status report.
28
+ #
29
+ # Only output if dryrun or trace mode.
30
+ #
31
+ def status(message)
32
+ if runmode.dryrun? or runmode.trace?
33
+ puts message
34
+ end
35
+ end
36
+
37
+ # Convenient method to get simple console reply.
38
+ #
39
+ def ask(question, answers=nil)
40
+ print "#{question}"
41
+ print " [#{answers}] " if answers
42
+ until inp = $stdin.gets ; sleep 1 ; end
43
+ inp.strip
44
+ end
45
+
46
+ # Ask for a password. (FIXME: only for unix so far)
47
+ #
48
+ def password(prompt=nil)
49
+ msg ||= "Enter Password: "
50
+ inp = ''
51
+ print "#{prompt} "
52
+ begin
53
+ #system "stty -echo"
54
+ #inp = gets.chomp
55
+ until inp = $stdin.gets
56
+ sleep 1
57
+ end
58
+ ensure
59
+ #system "stty echo"
60
+ end
61
+ return inp.chomp
62
+ end
63
+
64
+ def print(str)
65
+ super(str) unless quiet?
66
+ end
67
+
68
+ def puts(str)
69
+ super(str) unless quiet?
70
+ end
71
+
72
+ #
73
+ #
74
+ def printline(left, right='', options={})
75
+ return if runmode.quiet?
76
+
77
+ separator = options[:seperator] || options[:sep] || ' '
78
+ padding = options[:padding] || options[:pad] || 0
79
+
80
+ left, right = left.to_s, right.to_s
81
+
82
+ left_size = left.size
83
+ right_size = right.size
84
+
85
+ left = colorize(left)
86
+ right = colorize(right)
87
+
88
+ l = padding
89
+ r = -(right_size + padding + 1)
90
+
91
+ line = separator * screen_width
92
+ line[l, left_size] = left if left_size != 0
93
+ line[r, right_size] = right if right_size != 0
94
+
95
+ puts line
96
+ end
97
+
98
+ #
99
+ def colorize(text)
100
+ return text unless text.color
101
+ if PLATFORM =~ /win/
102
+ text.to_s
103
+ else
104
+ ANSICode.send(text.color){ text.to_s }
105
+ end
106
+ end
107
+
108
+ #
109
+ def screen_width
110
+ ConsoleUtils.screen_width
111
+ end
112
+
113
+ end
114
+
115
+ end
116
+
@@ -0,0 +1,73 @@
1
+ # NOT USED. Expiremental.
2
+
3
+ require 'pathname'
4
+
5
+ =begin
6
+ class Pathname
7
+
8
+ def glob(*options)
9
+ opts = 0
10
+ options.each do |option|
11
+ case option
12
+ when :nocase
13
+ opts += File::FNM_CASEFOLD
14
+ else
15
+ opts += option
16
+ end
17
+ end
18
+
19
+ self.class.glob(to_s, opts).collect{ |f| Pathname.new(f) }
20
+ end
21
+
22
+ end
23
+ =end
24
+
25
+ # How does this differ from facets/filelist?
26
+
27
+ class PathGlob #:nodoc:
28
+
29
+ #
30
+ attr :patterns
31
+ attr :options
32
+
33
+ def <<(pattern)
34
+ patterns << pattern
35
+ end
36
+
37
+ #
38
+ def match?(path)
39
+ patterns.find{ |pattern| File.fnmatch?(pattern, path) }
40
+ end
41
+
42
+ #
43
+ def match(*options)
44
+ opts = 0
45
+ options.each do
46
+ case options
47
+ when :nocase
48
+ opts += File::FNM_CASEFOLD
49
+ end
50
+ end
51
+ patterns.collect{ |pattern| File.glob(pattern, opts) }.flatten
52
+ end
53
+
54
+ def file?(*options)
55
+ match(options).all?{ |f| File.file?(f) }
56
+ end
57
+
58
+ def directory?(*options)
59
+ match(options).all?{ |f| File.directory?(f) }
60
+ end
61
+
62
+ private
63
+
64
+ def initialize(*patterns)
65
+ @patterns = patterns
66
+ end
67
+
68
+ def self.[](*patterns)
69
+ new(*patterns)
70
+ end
71
+
72
+ end
73
+
@@ -0,0 +1,55 @@
1
+ module Ratch
2
+
3
+ # = PLugin
4
+ #
5
+ # A Plugin is essentially a delegated Service class..
6
+ #
7
+ # The plugin acts a base class for ecapsulating batch routines.
8
+ # This helps to keep the main batch context free of the clutter
9
+ # of private supporting methods.
10
+ #
11
+ # Plugins are tightly coupled to the batch context,
12
+ # which allows them to call on the context easily.
13
+ # However this means plugins cannot be used independent
14
+ # of a batch context, and changes in the batch context
15
+ # can cause effects in plujgin behvior that can be harder
16
+ # to track down and fix if a bug arises.
17
+ #
18
+ # Unless the tight coupling of a plugin is required, use the
19
+ # loose coupling of a Service class instead.
20
+
21
+ class Plugin
22
+
23
+ # The batch context.
24
+ attr :context
25
+
26
+ alias_method :project, :context
27
+
28
+ private
29
+
30
+ #
31
+ def initialize(context, options=nil)
32
+ @context = context
33
+
34
+ raise TypeError, "context must be a subclass of Ratch::DSL" unless context.is_a?(Ratch::DSL)
35
+
36
+ initialize_defaults
37
+
38
+ options ||= {}
39
+ options.each do |k, v|
40
+ send("#{k}=", v) if respond_to?("#{k}=")
41
+ end
42
+ end
43
+
44
+ def initialize_defaults
45
+ end
46
+
47
+ # TODO: This should be optional? How?
48
+ def method_missing(s, *a, &b)
49
+ @context.send(s, *a, &b)
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+
@@ -0,0 +1,69 @@
1
+ module Ratch
2
+
3
+ # = Runmode
4
+ #
5
+ # The Runmode class encapsulates common options for command line scripts.
6
+ # The built-in modes are:
7
+ #
8
+ # force
9
+ # trace
10
+ # debug
11
+ # dryrun -or- noharm
12
+ # silent -or- quiet
13
+ # verbose
14
+ #
15
+ class Runmode
16
+
17
+ def self.load_argv!
18
+ options = {
19
+ :force => %w{--force}.any?{ |x| ARGV.delete(x) },
20
+ :trace => %w{--trace}.any?{ |x| ARGV.delete(x) },
21
+ :debug => %w{--debug}.any?{ |x| ARGV.delete(x) },
22
+ :dryrun => %w{--noharm --dryrun --dry-run}.any?{ |x| ARGV.delete(x) },
23
+ :silent => %w{--silent --quiet}.any?{ |x| ARGV.delete(x) },
24
+ :verbose => %w{--verbose}.any?{ |x| ARGV.delete(x) }
25
+ }
26
+ new(options)
27
+ end
28
+
29
+ def initialize(options={})
30
+ options.rekey(&:to_sym)
31
+
32
+ @force = options[:force]
33
+ @trace = options[:trace]
34
+ @debug = options[:debug]
35
+ @noharm = options[:noharm] || options[:dryrun]
36
+ @silent = options[:silent] || options[:quiet]
37
+ @verbose = options[:verbose]
38
+ end
39
+
40
+ attr_accessor :force
41
+
42
+ attr_accessor :trace
43
+
44
+ attr_accessor :verbose
45
+
46
+ attr_accessor :silent
47
+
48
+ attr_accessor :debug
49
+
50
+ attr_accessor :noharm
51
+
52
+ alias_method :dryrun, :noharm
53
+ alias_method :dryrun=,:noharm
54
+
55
+ alias_method :quiet, :silent
56
+ alias_method :quiet=, :silent=
57
+
58
+ def force? ; @force ; end
59
+ def trace? ; @trace ; end
60
+ def debug? ; @debug ; end
61
+ def noharm? ; @noharm ; end
62
+ def dryrun? ; @noharm ; end
63
+ def quiet? ; @silent ; end
64
+ def verbose? ; @verbose ; end
65
+
66
+ end
67
+
68
+ end
69
+
@@ -0,0 +1,52 @@
1
+ require 'ratch/dsl'
2
+ #require 'annotatable'
3
+
4
+ module Ratch
5
+
6
+ # = Ratch Script
7
+ #
8
+ # The Ratch Script class is used to run stand-alone ratch scripts.
9
+ # Yep, this is actaully a class named exactly for what it is.
10
+ # How rare.
11
+ #
12
+ class Script < DSL
13
+ #include Annotatable
14
+
15
+ #
16
+ #annotation :cmd
17
+
18
+ #
19
+ #annotation :opt
20
+
21
+ #
22
+ #def commands
23
+ # c = {}
24
+ # self.class.annotations.each do |n, a|
25
+ # if a.key?(:cmd)
26
+ # c[n] = a[:cmd]
27
+ # end
28
+ # end
29
+ # c
30
+ #end
31
+
32
+ #
33
+ #def initialize
34
+ # @noharm = %w{-n --noharm --dryrun --dry-run}.any?{ |a| ARGV.delete(a) }
35
+ # @verbose = %w{--verbose}.any?{|a| ARGV.delete(a) }
36
+ # @quiet = %w{--quiet}.any?{|a| ARGV.delete(a) }
37
+ # @force = %w{--force}.any?{|a| ARGV.delete(a) }
38
+ # @debug = %w{--debug}.any?{|a| ARGV.delete(a) }
39
+ # @trace = %w{--trace}.any?{|a| ARGV.delete(a) }
40
+ #
41
+ # super
42
+ #end
43
+
44
+ # @noharm ||= %w{noharm n dryrun dry-run}.any?{|a| commandline.options[a] }
45
+ # @verbose ||= %w{verbose}.any?{|a| commandline.options[a] }
46
+ # @force ||= %w{force}.any?{|a| commandline.options[a] }
47
+ # @debug ||= %w{debug}.any?{|a| commandline.options[a] }
48
+ # @trace ||= %w{trace}.any?{|a| commandline.options[a] }
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,33 @@
1
+ require 'ratch/dsl'
2
+
3
+ module Ratch
4
+
5
+ # = Service
6
+ #
7
+ # In particular this means creating module for RunModes and FileUtils
8
+ # which uses it, as these are the primary couplings between the batch
9
+ # context and the services that are shared by all.
10
+
11
+ class Service < DSL
12
+
13
+ private
14
+
15
+ #
16
+ def initialize(options=nil)
17
+ options ||= {}
18
+
19
+ initialize_defaults
20
+
21
+ options.each do |k, v|
22
+ send("#{k}=", v) if respond_to?("#{k}=")
23
+ end
24
+ end
25
+
26
+ #
27
+ def initialize_defaults
28
+ end
29
+
30
+ end
31
+
32
+ end
33
+
@@ -0,0 +1,249 @@
1
+
2
+ module Taskable
3
+
4
+ def self.included(base)
5
+ base.extend(Dsl)
6
+ end
7
+
8
+ # Run a task. Better name?
9
+ def run(target)
10
+ #t = self.class.tasks(target)
11
+ #t.run(self)
12
+ send("#{target}:target")
13
+ end
14
+
15
+ module Dsl
16
+ # Without an argument, returns list of tasks defined for this class.
17
+ #
18
+ # If a task's target name is given, will return the first
19
+ # task mathing the name found in the class' inheritance chain.
20
+ # This is important ot ensure task are inherited in the same manner
21
+ # that methods are.
22
+ def tasks(target=nil)
23
+ if target
24
+ target = target.to_sym
25
+ anc = ancestors.select{|a| a < Taskable}
26
+ t = nil; anc.find{|a| t = a.tasks[target]}
27
+ return t
28
+ else
29
+ @tasks ||= {}
30
+ end
31
+ end
32
+
33
+ # Set a description to be used by then next defined task in this class.
34
+ def desc(description)
35
+ @desc = description
36
+ end
37
+
38
+ # Define a task.
39
+ def task(target_and_requisite, &function)
40
+ target, requisite, function = *Task.parse_arguments(target_and_requisite, &function)
41
+ task = tasks[target.to_sym] ||= (
42
+ tdesc = @desc
43
+ @desc = nil
44
+ Task.new(self, target, tdesc) #, reqs, actions)
45
+ )
46
+ task.update(requisite, &function)
47
+ define_method("#{target}:target"){ task.run(self) } # or use #run?
48
+ define_method("#{target}:task", &function) # TODO: in 1.9 use instance_exec instead.
49
+ end
50
+ end
51
+
52
+ # = Task Class
53
+ #
54
+ class Task
55
+ attr :base
56
+ attr :target
57
+ attr :requisite
58
+ attr :function
59
+ attr :description
60
+
61
+ def initialize(base, target, description=nil, requisite=nil, &function)
62
+ @base = base
63
+ @target = target.to_sym
64
+ @description = description
65
+ @requisite = requisite || []
66
+ @function = function
67
+ end
68
+
69
+ #
70
+ def update(requisite, &function)
71
+ @requisite.concat(requisite).uniq!
72
+ @function = function if function
73
+ end
74
+
75
+ #
76
+ def prerequisite
77
+ base.ancestors.select{|a| a < Taskable}.collect{ |a|
78
+ a.tasks[target].requisite
79
+ }.flatten.uniq
80
+ end
81
+
82
+ # invoke target
83
+ def run(object)
84
+ rd = rule_dag
85
+ rd.each do |t|
86
+ object.send("#{t}:task")
87
+ end
88
+ end
89
+
90
+ #
91
+ #def call(object)
92
+ # object.instance_eval(&function)
93
+ #end
94
+
95
+ # Collect task dependencies for running.
96
+ def rule_dag(cache=[])
97
+ prerequisite.each do |r|
98
+ next if cache.include?(r)
99
+ t = base.tasks[r]
100
+ t.rule_dag(cache)
101
+ #cache << dep
102
+ end
103
+ cache << target.to_s
104
+ cache
105
+ end
106
+
107
+ #
108
+ def self.parse_arguments(name_and_reqs, &action)
109
+ if Hash===name_and_reqs
110
+ target = name_and_reqs.keys.first.to_s
111
+ reqs = [name_and_reqs.values.first].flatten
112
+ else
113
+ target = name_and_reqs.to_s
114
+ reqs = []
115
+ end
116
+ return target, reqs, action
117
+ end
118
+ end
119
+
120
+ # = File Task Class
121
+ #
122
+ class FileTask < Task
123
+
124
+ def needed?
125
+ if prerequisite.empty?
126
+ dated = true
127
+ elsif File.exist?(target)
128
+ mtime = File.mtime(target)
129
+ dated = prerequisite.find do |file|
130
+ !File.exist?(file) || File.mtime(file) > mtime
131
+ end
132
+ else
133
+ dated = true
134
+ end
135
+ return dated
136
+ end
137
+
138
+ #
139
+ def call(object)
140
+ object.instance_eval(&function) if needed?
141
+ end
142
+ end
143
+
144
+ end
145
+
146
+
147
+ =begin
148
+ # turn yaml file into tasks
149
+ def parse(file)
150
+ script = YAML.load(File.new(file.to_s))
151
+
152
+ imports = script.delete('import') || []
153
+ #plugins = script.delete('plugin') || []
154
+ srvs = script.delete('services') || {}
155
+ tgts = script.delete('targets') || {}
156
+
157
+ imports.each do |import|
158
+ path = Reap::Domain::LIB_DIRECTORY + 'systems' + (import + '.reap').to_s
159
+ parse(path)
160
+ end
161
+
162
+ srvs.each do |label, options|
163
+ type = options.delete('type')
164
+ @services[label] = domain.send("#{type}_service") # FIXME
165
+ end
166
+
167
+ tgts.each do |target, options|
168
+ @targets[target] = Task.new(self, target, options)
169
+ end
170
+ end
171
+ =end
172
+
173
+
174
+
175
+
176
+
177
+
178
+ =begin
179
+ # Collect task dependencies for running.
180
+ def self.rule_dag(target, cache=[])
181
+ t = tasks[target.to_sym]
182
+ d = t.prerequisite
183
+ d.each do |r|
184
+ next if cache.include?(r)
185
+ rule_dag(r, cache)
186
+ #cache << dep
187
+ end
188
+
189
+ # file requirements
190
+ #q = self.class.ann(name, :reqs) || []
191
+ #q.each do |req|
192
+ # path = Pathname.new(req)
193
+ # next if r.include?(path)
194
+ # mat = annotations.select{ |n, a| File.fnmatch?(a[:file].first, req) if a[:file] }.compact
195
+ # mat.each do |n, a|
196
+ # rule_dag(n, r)
197
+ # end
198
+ # r << path
199
+ #end
200
+
201
+ cache << target.to_s
202
+ return cache
203
+ end
204
+
205
+ # invoke target
206
+ def run(target)
207
+ target = target.to_sym
208
+ rd = self.class.rule_dag(target)
209
+ rd.each do |t|
210
+ send("#{t}:task")
211
+ end
212
+ end
213
+ =end
214
+
215
+ =begin
216
+ if target == name.to_s
217
+ tasks[target].call
218
+ else
219
+ case action
220
+ when Pathname
221
+ raise unless action.exist?
222
+ else
223
+ run_rec(action)
224
+ end
225
+ end
226
+ end
227
+ end
228
+
229
+ def run_rec(action)
230
+ # creates a file?
231
+ dated = true
232
+ if creates = self.class.ann(action, :file)
233
+ if self.class.ann(name, :reqs).empty?
234
+ dated = true
235
+ elsif File.exist?(creates)
236
+ mtime = File.mtime(creates)
237
+ dated = self.class.ann(name, :reqs).find do |file|
238
+ !File.exist?(file) || File.mtime(file) > mtime
239
+ end
240
+ else
241
+ dated = true
242
+ end
243
+ end
244
+ return unless dated
245
+ send(action)
246
+ end
247
+ =end
248
+
249
+