memorack 0.0.4 → 0.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: df0fc4ad433a14e26057d9581ce8cbfaea6935ff
4
- data.tar.gz: f14cfd511ca13b7aeaad9c02033b453e865e01b1
3
+ metadata.gz: 86ce4cc169545b6c715a427e966be9ad7a3832a2
4
+ data.tar.gz: 03e628e48a3764a8ec1dc1636ea6f03b60d51b1b
5
5
  SHA512:
6
- metadata.gz: d0c623c98371ea471b8d0a3cf1f59536adf2a8658454560f1018b33b5fb9e583ebc9909e67ada318b8570e5ec3f9b16ea7f14578e11d3f9a81ebafa6e13892fc
7
- data.tar.gz: 73b25aac401e3243ea35267e0afdce120c984b923386a899605dcf8c60ad119efc0e0df0b56578449e44e3c1d6cdb11571a7d4d4fbd1828514c26018f94b12f7
6
+ metadata.gz: 0c3d8523641d17d57fee096e9c715330b64f3a85b8f0952606e9e9f6d302d5a16aa1b1eb0908fc4b566b225bf6e632c50ea1e837ac0f737a2033fb21dcafbc0d
7
+ data.tar.gz: eff59e8755ff34c51c5ad47ab2b92525a95b81628cbb0d1ef2c5c6edeb79ab83eca89313bb764c53b5c19ac7a0d1e62cbb74bdd4471d9b6b8f7e78fc18b9b3b9
data/.gitignore CHANGED
@@ -1,4 +1,4 @@
1
- /VERSION
1
+ /REVISION
2
2
  *.gem
3
3
  *.rbc
4
4
  .bundle
data/HISTORY.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## History
2
2
 
3
+ ### v0.1.0 / 2013-04-15
4
+
5
+ * generate HTMLs for static site
6
+ * add config.json options(requires, public, site.url)
7
+ * change basic theme directory structure
8
+
3
9
  ### v0.0.4 / 2013-04-12
4
10
 
5
11
  * fix link in content
data/README.md CHANGED
@@ -23,6 +23,7 @@ Or install it yourself as:
23
23
  $ memorack theme THEME # Show theme info
24
24
  $ memorack theme -c THEME # Copy theme
25
25
  $ memorack server PATH # Instant Server
26
+ $ memorack build [PATH] # Build static site
26
27
 
27
28
  Standard startup
28
29
 
@@ -49,6 +50,14 @@ OS X (Pow + powder)
49
50
  * [Pow: Zero-configuration Rack server for Mac OS X](http://pow.cx)
50
51
  * `gem install powder`
51
52
 
53
+ Build static site
54
+
55
+ $ memorack create memo
56
+ $ cd memo
57
+ (Customizing...)
58
+ $ memorack build --url http://foo.bar.baz
59
+ Build 'content/' -> '_site'
60
+
52
61
  ## Directory
53
62
 
54
63
  Template
@@ -111,10 +120,25 @@ Add code to `index.html`
111
120
  <script src="/highlight.js/highlight.pack.js"></script>
112
121
  <script>hljs.initHighlightingOnLoad();</script>
113
122
 
123
+ ### org-mode
124
+
125
+ Install
126
+
127
+ $ gem install org-ruby
128
+
129
+ Edit `config.json`
130
+
131
+ {
132
+ "formats": ["markdown", "org"],
133
+ "requires": ["org-ruby"],
134
+ ...
135
+ }
136
+
114
137
  #### mustache variables
115
138
 
116
139
  Basic variables -- `{{VAR}}`
117
140
 
141
+ * `site.url`
118
142
  * `title`
119
143
  * `page.title`
120
144
  * `app.name`
@@ -131,9 +155,9 @@ Special variables -- `{{{VAR}}}`
131
155
 
132
156
  * Template comments translate english
133
157
  * Add customizing tips
134
- * Server test program
135
- * Generate HTMLs for static site
158
+ * More test program
136
159
  * Plugin
160
+ * Generate EPUB3
137
161
 
138
162
  ## Contributing
139
163
 
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- require File.expand_path('../etc/tools/update_version', __FILE__)
1
+ require File.expand_path('../etc/tools/update_revision', __FILE__)
2
2
  require "bundler/gem_tasks"
3
3
 
4
4
  # Spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,14 @@
1
+ def update_revision(path = 'REVISION')
2
+ rev = `git describe --dirty --long 2>/dev/null`.chomp
3
+ rev = `git describe --dirty --long --tags --always`.chomp if rev.empty?
4
+ rev.gsub!(/^v([0-9])/, '\1')
5
+ rev.gsub!(/(^|-)([a-z0-9]+(-dirty)?)$/, '/\2')
6
+ rev.gsub!(/^([0-9.]+)-0\//, '\1/')
7
+
8
+ return if File.exists?(path) && rev == open(path).read.chomp
9
+
10
+ open(path, 'w') { |f| f.puts rev }
11
+ end
12
+
13
+
14
+ update_revision
@@ -0,0 +1,171 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'set'
4
+ require 'fileutils'
5
+ require 'pathname'
6
+ require 'rubygems'
7
+
8
+ require 'memorack/core'
9
+
10
+ module MemoRack
11
+ class Builder < Core
12
+
13
+ DEFAULT_BUILD_OPTIONS = {
14
+ output: '_site',
15
+ prefix: '/',
16
+ suffix: '.html',
17
+ uri_escape: true,
18
+ }
19
+
20
+ def generate(options = {}, &callback)
21
+ options = DEFAULT_BUILD_OPTIONS.merge(options)
22
+
23
+ url = @site[:url]
24
+ options[:prefix] = File.join(url, options[:prefix]) unless url.empty?
25
+
26
+ output = File.expand_path(options[:output])
27
+ dir_init(output)
28
+
29
+ @contents = contents(options)
30
+ @templates = Set.new @contents.files.collect { |file| file[:path] }
31
+
32
+ # トップページを作成する
33
+ content_write(:index, '.html', output) { |template|
34
+ render_with_mustache template, :markdown
35
+ }
36
+
37
+ suffix = options[:suffix]
38
+ suffix = '/index.html' if ['', '/'].member?(suffix)
39
+
40
+ # コンテンツのレンダリングを行う
41
+ @templates.each { |path|
42
+ callback.call(path) if callback
43
+
44
+ content_write(path, suffix, output) { |template|
45
+ render_content(template)
46
+ }
47
+ }
48
+
49
+ # テーマの公開ファイルをコピー
50
+ copy_public(@themes, output, &callback)
51
+
52
+ # 静的ファイルをコピーする
53
+ copy_statics(@root, output, &callback)
54
+ end
55
+
56
+ # ディレクトリを初期化する
57
+ def dir_init(dir)
58
+ if Dir.exists?(dir)
59
+ Dir.glob(File.join(dir, '*'), File::FNM_DOTMATCH) { |path|
60
+ next if /(^|\/)(\.|\.\.)$/ =~ path
61
+
62
+ FileUtils.remove_entry_secure(path)
63
+ }
64
+ else
65
+ FileUtils.mkpath(dir)
66
+ end
67
+ end
68
+
69
+ # テーマから公開用のファイルを収集する
70
+ def public_files
71
+ unless @public_files
72
+ @public_files = Set.new
73
+
74
+ @public.each { |path_info|
75
+ if path_info[-1] == '/'
76
+ @themes.each { |theme|
77
+ if Dir.exists?(File.join(theme, path_info))
78
+ Dir.chdir(theme) { |dir|
79
+ @public_files += Dir.glob(File.join(path_info, '**/*'))
80
+ }
81
+ end
82
+ }
83
+ else
84
+ @public_files << path_info
85
+ end
86
+ }
87
+ end
88
+
89
+ @public_files
90
+ end
91
+
92
+ # コンテンツをファイルに出力する
93
+ def content_write(template, suffix, output)
94
+ begin
95
+ content = yield(template)
96
+ return unless content
97
+
98
+ path = template
99
+ path = path.sub(/\.[^.]*$/, '') if path.kind_of?(String)
100
+
101
+ to = path.to_s + suffix
102
+ to = File.join(output, to)
103
+
104
+ FileUtils.mkpath(File.dirname(to))
105
+ File.write(to, content)
106
+ rescue
107
+ end
108
+ end
109
+
110
+ # ファイルをコピーする
111
+ def copy_file(path, output, &callback)
112
+ return if File.directory?(path)
113
+ return if @templates.include?(path)
114
+
115
+ callback.call(path) if callback
116
+
117
+ if output.kind_of?(Array)
118
+ to = File.join(*output)
119
+ else
120
+ to = File.join(output, path)
121
+ end
122
+
123
+ FileUtils.mkpath(File.dirname(to))
124
+ FileUtils.copy_entry(path, to)
125
+ end
126
+
127
+ # 静的ファイルをコピーする
128
+ def copy_statics(dir, output, &callback)
129
+ Dir.chdir(dir) { |dir|
130
+ Dir.glob('**/*') { |path|
131
+ copy_file(path, output, &callback)
132
+ }
133
+ }
134
+ end
135
+
136
+ # テーマの公開ファイルをコピーする
137
+ def copy_public(views, output, &callback)
138
+ public_files.each { |path_info|
139
+ callback.call(path_info) if callback
140
+
141
+ ext = split_extname(path_info)[1]
142
+
143
+ if css_exts.include?(ext)
144
+ content_write(path_info, '.css', output) { |template|
145
+ content = render_css(template)
146
+ }
147
+ else
148
+ fullpath = file_search(path_info, {views: views}, nil)
149
+ copy_file(fullpath, [output, path_info]) if fullpath
150
+ end
151
+ }
152
+ end
153
+
154
+ #### テンプレート
155
+
156
+ # インデックスを作成
157
+ template :index do
158
+ begin
159
+ render :markdown, 'index.md', {views: @themes}
160
+ rescue
161
+ ''
162
+ end
163
+ end
164
+
165
+ # メニューを作成
166
+ template :menu do
167
+ @contents.generate(StringIO.new).string
168
+ end
169
+
170
+ end
171
+ end
@@ -28,6 +28,7 @@ module MemoRack
28
28
  Usage: #{opts.program_name} create [options] PATH
29
29
  #{opts.program_name} theme [options] [THEME]
30
30
  #{opts.program_name} server [options] PATH
31
+ #{opts.program_name} build [options] [PATH]
31
32
  BANNER
32
33
 
33
34
  opts.separator ""
@@ -174,6 +175,38 @@ module MemoRack
174
175
  abort opts.help if argv.empty?
175
176
  }
176
177
 
178
+ # 静的サイトのビルド
179
+ define_options(:build, '[options] [PATH]') { |opts, argv, options|
180
+ default_options = {
181
+ output: '_site',
182
+ theme: 'custom',
183
+ url: '',
184
+
185
+ local: false,
186
+ prettify: false,
187
+ }
188
+
189
+ options.merge!(default_options)
190
+
191
+ opts.separator ""
192
+ opts.on("-o", "--output DIRECTORY", String,
193
+ t(:output, options)) { |arg| options[:output] = arg }
194
+ opts.on("-t", "--theme THEME", String,
195
+ t(:theme, options)) { |arg| options[:theme] = arg }
196
+ opts.on("--url URL", String,
197
+ t(:url, options)) { |arg| options[:url] = arg }
198
+ opts.on("--local",
199
+ t(:local)) { options[:local] = true }
200
+ opts.on("--prettify",
201
+ t(:prettify)) { options[:prettify] = true }
202
+ opts.on("-h", "--help", t(:help)) { abort opts.help }
203
+
204
+ opts.parse!(argv)
205
+
206
+ options[:url] = 'file://' + File.expand_path(options[:output]) if options[:local]
207
+ options[:suffix] = '' if options[:prettify]
208
+ }
209
+
177
210
 
178
211
  # サブコマンド
179
212
 
@@ -259,5 +292,25 @@ module MemoRack
259
292
  server_options[:app] = app
260
293
  Rack::Server.new(server_options).start
261
294
  end
295
+
296
+ # 静的サイトのビルド
297
+ def memorack_build(options, *argv)
298
+ path = argv.shift
299
+ path = 'content/' unless path
300
+ abort "Directory not exists '#{path}'" unless File.exists?(path)
301
+
302
+ require 'memorack/builder'
303
+ require 'tmpdir'
304
+
305
+ Dir.mktmpdir do |tmpdir|
306
+ site = {}
307
+ site[:url] = File.join(options[:url], '').gsub(/\/$/, '') if options[:url]
308
+
309
+ builder = MemoRack::Builder.new(theme: options[:theme], root: path, tmpdir: tmpdir, site: site)
310
+ builder.generate(options)
311
+ end
312
+
313
+ puts "Build '#{path}' -> '#{options[:output]}'"
314
+ end
262
315
  end
263
316
  end
@@ -0,0 +1,369 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'pathname'
4
+ require 'rubygems'
5
+
6
+ require 'memorack/tilt-mustache'
7
+ require 'memorack/locals'
8
+ require 'memorack/mdmenu'
9
+
10
+ module MemoRack
11
+ class Core
12
+ attr_reader :themes, :options_chain
13
+
14
+ DEFAULT_APP_OPTIONS = {
15
+ root: 'content/',
16
+ themes_folder: 'themes/',
17
+ tmpdir: 'tmp/',
18
+ theme: 'basic',
19
+ markdown: 'redcarpet',
20
+ formats: ['markdown'],
21
+ css: nil,
22
+ suffix: '',
23
+ public: [],
24
+ site: {},
25
+ requires: [],
26
+ directory_watcher: false
27
+ }
28
+
29
+ # テンプレートエンジンのオプション
30
+ DEFAULT_TEMPLATE_OPTIONS = {
31
+ tables: true
32
+ }
33
+
34
+ # テンプレートで使用するローカル変数の初期値
35
+ DEFAULT_LOCALS = {
36
+ title: 'memo'
37
+ }
38
+
39
+ DEFAULT_OPTIONS = DEFAULT_APP_OPTIONS.merge(DEFAULT_TEMPLATE_OPTIONS).merge(DEFAULT_LOCALS)
40
+
41
+ def initialize(options={})
42
+ options = DEFAULT_OPTIONS.merge(to_sym_keys(options))
43
+
44
+ @themes_folders = [options[:themes_folder], File.expand_path('../themes/', __FILE__)]
45
+ read_config(options[:theme], options)
46
+ read_config(DEFAULT_APP_OPTIONS[:theme], options) if @themes.empty?
47
+
48
+ @options = options
49
+
50
+ # DEFAULT_APP_OPTIONS に含まれるキーをすべてインスタンス変数に登録する
51
+ DEFAULT_APP_OPTIONS.each { |key, item|
52
+ instance_variable_set("@#{key}".to_sym, options[key])
53
+
54
+ # @options からテンプレートで使わないものを削除
55
+ @options.delete(key)
56
+ }
57
+
58
+ @requires.each { |lib| require lib }
59
+ @locals = default_locals(@options)
60
+
61
+ use_engine(@markdown)
62
+ end
63
+
64
+ # テーマのパスを取得する
65
+ def theme_path(theme)
66
+ return nil unless theme
67
+
68
+ @themes_folders.each { |folder|
69
+ path = theme && File.join(folder, theme)
70
+ return path if File.exists?(path) && FileTest::directory?(path)
71
+ }
72
+
73
+ nil
74
+ end
75
+
76
+ # デフォルトの locals を生成する
77
+ def default_locals(locals = {})
78
+ locals = Locals[locals]
79
+
80
+ locals[:site] ||= @site
81
+
82
+ locals[:app] ||= Locals[]
83
+ locals[:app][:name] ||= MemoRack::name
84
+ locals[:app][:version] ||= MemoRack::VERSION
85
+ locals[:app][:url] ||= MemoRack::HOMEPAGE
86
+
87
+ locals.define_key(:__menu__) { |hash, key|
88
+ @menu = nil unless @directory_watcher # ファイル監視していない場合はメニューを初期化
89
+ @menu ||= render :markdown, :menu, @options
90
+ }
91
+
92
+ locals
93
+ end
94
+
95
+ # 設定ファイルを読込む
96
+ def read_config(theme, options = {})
97
+ @themes ||= []
98
+ @options_chain = []
99
+ @theme_chain = []
100
+
101
+ begin
102
+ require 'json'
103
+
104
+ while theme
105
+ dir = theme_path(theme)
106
+ break unless dir
107
+ break if @themes.member?(dir)
108
+
109
+ # テーマ・チェインに追加
110
+ @themes << File.join(dir, '')
111
+
112
+ # config の読込み
113
+ path = File.join(dir, 'config.json')
114
+ break unless File.readable?(path)
115
+
116
+ data = File.read(path)
117
+ @options_chain << to_sym_keys(JSON.parse(data))
118
+
119
+ theme = @options_chain.last[:theme]
120
+ end
121
+ rescue
122
+ end
123
+
124
+ # オプションをマージ
125
+ @options_chain.reverse.each { |opts| options.merge!(opts) }
126
+ options
127
+ end
128
+
129
+ # テンプレートエンジンを使用できるようにする
130
+ def use_engine(engine)
131
+ require engine if engine
132
+
133
+ # Tilt で Redcarpet 2.x を使うためのおまじない
134
+ Object.send(:remove_const, :RedcarpetCompat) if defined?(RedcarpetCompat) == 'constant'
135
+ end
136
+
137
+ # css の拡張子リストを作成する
138
+ def css_exts
139
+ @css_exts ||= Set.new ['css', *@css]
140
+ end
141
+
142
+ # ファイルを探す
143
+ def file_search(template, options = {}, exts = enable_exts)
144
+ options = {views: @root}.merge(options)
145
+
146
+ if options[:views].kind_of?(Array)
147
+ err = nil
148
+
149
+ options[:views].each { |views|
150
+ options[:views] = views
151
+
152
+ begin
153
+ path = file_search(template, options, exts)
154
+ return path if path
155
+ rescue Errno::ENOENT => e
156
+ err = e
157
+ end
158
+ }
159
+
160
+ raise err if err
161
+ return nil
162
+ end
163
+
164
+ if exts
165
+ exts.each { |ext|
166
+ path = File.join(options[:views], "#{template}.#{ext}")
167
+ return path if File.exists?(path)
168
+ }
169
+ else
170
+ path = File.join(options[:views], template)
171
+ return path if File.exists?(path)
172
+ end
173
+
174
+ return nil
175
+ end
176
+
177
+ # テンプレートエンジンで render する
178
+ def render(engine, template, options = {}, locals = {})
179
+ options = {views: @root}.merge(options)
180
+
181
+ if template.kind_of?(Pathname)
182
+ path = template
183
+ elsif options[:views].kind_of?(Array)
184
+ err = nil
185
+
186
+ options[:views].each { |views|
187
+ options[:views] = views
188
+
189
+ begin
190
+ return render(engine, template, options, locals)
191
+ rescue Errno::ENOENT => e
192
+ err = e
193
+ end
194
+ }
195
+
196
+ raise err
197
+ else
198
+ fname = template.kind_of?(String) ? template : "#{template}.#{engine}"
199
+ path = File.join(options[:views], fname)
200
+ end
201
+
202
+ engine = Tilt.new(File.join(File.dirname(path), ".#{engine}"), options) {
203
+ method = MemoApp.template_method(template)
204
+
205
+ if method && respond_to?(method)
206
+ data = send(method)
207
+ else
208
+ data = File.binread(path)
209
+ data.force_encoding('UTF-8')
210
+ end
211
+
212
+ data
213
+ }
214
+ engine.render(options, locals).force_encoding('UTF-8')
215
+ end
216
+
217
+ # レイアウトに mustache を適用してテンプレートエンジンでレンダリングする
218
+ def render_with_mustache(template, engine = :markdown, options = {}, locals = {})
219
+ begin
220
+ mustache_templ = options[:mustache] || 'index.html'
221
+
222
+ options = @options.merge(options)
223
+ locals = @locals.merge(locals)
224
+
225
+ locals.define_key(:__content__) { |hash, key|
226
+ if engine
227
+ render engine, template, options
228
+ else
229
+ template
230
+ end
231
+ }
232
+
233
+ locals[:content] = true unless template == :index
234
+ locals[:page] = page = Locals[locals[:page] || {}]
235
+
236
+ page.define_key(:name) { |hash, key|
237
+ unless template == :index
238
+ fname = locals[:path_info]
239
+ fname ||= template.to_s.force_encoding('UTF-8')
240
+ File.basename(fname)
241
+ end
242
+ }
243
+
244
+ page.define_key(:title) { |hash, key|
245
+ page_title = home_title = locals[:title]
246
+ page_name = hash[:name]
247
+ page_title = "#{page_name} | #{home_title}" if page_name
248
+
249
+ page_title
250
+ }
251
+
252
+ render :mustache, mustache_templ, {views: @themes}, locals
253
+ rescue => e
254
+ e.to_s
255
+ end
256
+ end
257
+
258
+ # コンテンツをレンダリングする
259
+ def render_content(path_info, locals = {})
260
+ path, ext = split_extname(path_info)
261
+
262
+ if @suffix == ''
263
+ fullpath = file_search(path_info, @options)
264
+
265
+ if fullpath
266
+ path = path_info
267
+ ext = split_extname(fullpath)[1]
268
+ end
269
+ end
270
+
271
+ return nil unless ext && Tilt.registered?(ext)
272
+
273
+ template = fullpath ? Pathname.new(fullpath) : path.to_sym
274
+ content = render_with_mustache template, ext, {}, locals
275
+ end
276
+
277
+ # CSSをレンダリングする
278
+ def render_css(path_info, locals = {})
279
+ return unless @css
280
+
281
+ path, = split_extname(path_info)
282
+ options = {views: @themes}
283
+
284
+ fullpath = file_search(path, options, css_exts)
285
+ return nil unless fullpath
286
+
287
+ ext = split_extname(fullpath)[1]
288
+
289
+ case ext
290
+ when 'css'
291
+ return File.binread(fullpath)
292
+ when 'scss', 'sass'
293
+ options[:cache_location] = File.expand_path('sass-cache', @tmpdir)
294
+ end
295
+
296
+ render ext, Pathname.new(fullpath), options, locals
297
+ end
298
+
299
+ # 拡張子を取出す
300
+ def split_extname(path)
301
+ return [$1, $2] if /^(.+)\.([^.]+)/ =~ path
302
+
303
+ [path]
304
+ end
305
+
306
+ # キーをシンボルに変換する
307
+ def to_sym_keys(hash)
308
+ hash.inject({}) { |memo, entry|
309
+ key, value = entry
310
+ value = to_sym_keys(value) if value.kind_of?(Hash)
311
+
312
+ memo[key.to_sym] = value
313
+
314
+ memo
315
+ }
316
+ end
317
+
318
+ # Tilt に登録されている拡張子を集める
319
+ def extnames(extname)
320
+ klass = Tilt[extname]
321
+ Tilt.mappings.select { |key, value| value.member?(klass) }.collect { |key, value| key }
322
+ end
323
+
324
+ # 対応フォーマットを取得する
325
+ def collect_formats
326
+ unless @collect_formats
327
+ @collect_formats = {}
328
+
329
+ @formats.each { |item|
330
+ if item.kind_of?(Array)
331
+ @collect_formats[item.first] = item
332
+ elsif item.kind_of?(Hash)
333
+ @collect_formats.merge!(item)
334
+ else
335
+ @collect_formats[item] = extnames(item)
336
+ end
337
+ }
338
+ end
339
+
340
+ @collect_formats
341
+ end
342
+
343
+ # 対応している拡張子
344
+ def enable_exts
345
+ @enable_exts ||= collect_formats.values.flatten
346
+ end
347
+
348
+ # コンテンツファイルの収集する
349
+ def contents(options = {})
350
+ default_options = {prefix: '/', suffix: @suffix, uri_escape: true, formats: collect_formats}
351
+ options = default_options.merge(options)
352
+
353
+ mdmenu = MdMenu.new(options)
354
+ Dir.chdir(@root) { |path| mdmenu.collection('.') }
355
+ mdmenu
356
+ end
357
+
358
+ # テンプレート名
359
+ def self.template_method(name)
360
+ name.kind_of?(Symbol) && "template_#{name}".to_sym
361
+ end
362
+
363
+ # テンプレートを作成する
364
+ def self.template(name, &block)
365
+ define_method(self.template_method(name), &block)
366
+ end
367
+
368
+ end
369
+ end