runo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (149) hide show
  1. data/LICENSE +19 -0
  2. data/README.rdoc +120 -0
  3. data/bin/runo +35 -0
  4. data/lib/_error.rb +14 -0
  5. data/lib/_field.rb +260 -0
  6. data/lib/_i18n.rb +141 -0
  7. data/lib/_parser.rb +243 -0
  8. data/lib/_path.rb +86 -0
  9. data/lib/_storage/_storage.rb +213 -0
  10. data/lib/_storage/file.rb +200 -0
  11. data/lib/_storage/sequel.rb +174 -0
  12. data/lib/_storage/temp.rb +73 -0
  13. data/lib/_widget/action_create.rb +23 -0
  14. data/lib/_widget/action_login.rb +22 -0
  15. data/lib/_widget/action_signup.rb +16 -0
  16. data/lib/_widget/action_update.rb +16 -0
  17. data/lib/_widget/crumb.rb +24 -0
  18. data/lib/_widget/done.rb +16 -0
  19. data/lib/_widget/login.rb +25 -0
  20. data/lib/_widget/me.rb +31 -0
  21. data/lib/_widget/message.rb +51 -0
  22. data/lib/_widget/navi.rb +88 -0
  23. data/lib/_widget/submit.rb +49 -0
  24. data/lib/_widget/view_ym.rb +77 -0
  25. data/lib/_workflow/_workflow.rb +89 -0
  26. data/lib/_workflow/attachment.rb +50 -0
  27. data/lib/_workflow/blog.rb +28 -0
  28. data/lib/_workflow/contact.rb +23 -0
  29. data/lib/_workflow/forum.rb +26 -0
  30. data/lib/_workflow/register.rb +39 -0
  31. data/lib/meta/_meta.rb +20 -0
  32. data/lib/meta/group.rb +19 -0
  33. data/lib/meta/id.rb +59 -0
  34. data/lib/meta/owner.rb +21 -0
  35. data/lib/meta/timestamp.rb +118 -0
  36. data/lib/runo.rb +396 -0
  37. data/lib/scalar/checkbox.rb +68 -0
  38. data/lib/scalar/file.rb +144 -0
  39. data/lib/scalar/img.rb +112 -0
  40. data/lib/scalar/password.rb +58 -0
  41. data/lib/scalar/radio.rb +47 -0
  42. data/lib/scalar/select.rb +47 -0
  43. data/lib/scalar/text.rb +38 -0
  44. data/lib/scalar/textarea.rb +35 -0
  45. data/lib/scalar/textarea_pre.rb +14 -0
  46. data/lib/scalar/textarea_wiki.rb +173 -0
  47. data/lib/set/_set.rb +195 -0
  48. data/lib/set/dynamic.rb +177 -0
  49. data/lib/set/static.rb +102 -0
  50. data/lib/set/static_folder.rb +96 -0
  51. data/locale/en/index.po +242 -0
  52. data/locale/index.pot +243 -0
  53. data/locale/ja/index.po +242 -0
  54. data/locale/lazy_parser.rb +54 -0
  55. data/skel/config.ru +27 -0
  56. data/skel/skin/_users/00000000_frank-avatar.jpg +0 -0
  57. data/skel/skin/_users/00000000_frank-avatar_small.jpg +0 -0
  58. data/skel/skin/_users/00000000_frank.yaml +12 -0
  59. data/skel/skin/_users/00000000_root-avatar.jpg +0 -0
  60. data/skel/skin/_users/00000000_root-avatar_small.jpg +0 -0
  61. data/skel/skin/_users/00000000_root.yaml +11 -0
  62. data/skel/skin/_users/css/users.css +21 -0
  63. data/skel/skin/_users/css/users.less +25 -0
  64. data/skel/skin/_users/done.html +42 -0
  65. data/skel/skin/_users/index.html +46 -0
  66. data/skel/skin/_users/index.yaml +3 -0
  67. data/skel/skin/_users/summary.html +40 -0
  68. data/skel/skin/css/base.css +93 -0
  69. data/skel/skin/css/base.less +139 -0
  70. data/skel/skin/css/coax.css +199 -0
  71. data/skel/skin/css/coax.less +244 -0
  72. data/skel/skin/examples/blog/20091214_0001.yaml +8 -0
  73. data/skel/skin/examples/blog/20100630_0001.yaml +8 -0
  74. data/skel/skin/examples/blog/20100630_0002.yaml +14 -0
  75. data/skel/skin/examples/blog/20100701_0001.yaml +8 -0
  76. data/skel/skin/examples/blog/20100701_0002-a-20100701_0001-f.jpg +0 -0
  77. data/skel/skin/examples/blog/20100701_0002-a-20100701_0001-f_small.jpg +0 -0
  78. data/skel/skin/examples/blog/20100701_0002.yaml +19 -0
  79. data/skel/skin/examples/blog/frank/20100701_0001.yaml +10 -0
  80. data/skel/skin/examples/blog/frank/index.yaml +4 -0
  81. data/skel/skin/examples/blog/index.html +51 -0
  82. data/skel/skin/examples/blog/rss.xml +18 -0
  83. data/skel/skin/examples/contact/20100701_0001-file.txt +1 -0
  84. data/skel/skin/examples/contact/20100701_0001.yaml +15 -0
  85. data/skel/skin/examples/contact/20100701_0002.yaml +8 -0
  86. data/skel/skin/examples/contact/20100701_0003.yaml +9 -0
  87. data/skel/skin/examples/contact/index.html +47 -0
  88. data/skel/skin/examples/contact/js/contact.js +13 -0
  89. data/skel/skin/examples/contact/summary.html +54 -0
  90. data/skel/skin/examples/forum/20100701_0001.yaml +41 -0
  91. data/skel/skin/examples/forum/20100701_0002.yaml +25 -0
  92. data/skel/skin/examples/forum/index.html +68 -0
  93. data/skel/skin/examples/forum/summary.html +47 -0
  94. data/skel/skin/examples/index.html +75 -0
  95. data/skel/skin/index.html +41 -0
  96. data/skel/skin/js/base.js +50 -0
  97. data/t/locale/de/index.po +19 -0
  98. data/t/locale/en-GB/index.po +25 -0
  99. data/t/locale/ja/index.po +30 -0
  100. data/t/skin/_users/00000000_test.yaml +3 -0
  101. data/t/skin/_users/index.html +13 -0
  102. data/t/skin/foo/20091120_0001.yaml +7 -0
  103. data/t/skin/foo/bar/20091120_0001.yaml +5 -0
  104. data/t/skin/foo/bar/index.yaml +5 -0
  105. data/t/skin/foo/baz/css/baz.css +1 -0
  106. data/t/skin/foo/css/foo.css +1 -0
  107. data/t/skin/foo/index.html +14 -0
  108. data/t/skin/foo/index.yaml +7 -0
  109. data/t/skin/foo/not_css/foo.css +1 -0
  110. data/t/skin/foo/sub-20100306_0001.yaml +3 -0
  111. data/t/skin/index.yaml +3 -0
  112. data/t/skin/t_attachment/index.html +13 -0
  113. data/t/skin/t_contact/done.html +6 -0
  114. data/t/skin/t_contact/index.html +9 -0
  115. data/t/skin/t_file/index.html +16 -0
  116. data/t/skin/t_img/index.html +14 -0
  117. data/t/skin/t_img/test.jpg +0 -0
  118. data/t/skin/t_select/index.html +9 -0
  119. data/t/skin/t_store/index.html +9 -0
  120. data/t/skin/t_summary/20100326_0001.yaml +3 -0
  121. data/t/skin/t_summary/create.html +9 -0
  122. data/t/skin/t_summary/index.html +9 -0
  123. data/t/skin/t_summary/summary.html +9 -0
  124. data/t/t.rb +27 -0
  125. data/t/test_checkbox.rb +273 -0
  126. data/t/test_field.rb +330 -0
  127. data/t/test_file.rb +900 -0
  128. data/t/test_id.rb +215 -0
  129. data/t/test_img.rb +328 -0
  130. data/t/test_meta.rb +57 -0
  131. data/t/test_parser.rb +1266 -0
  132. data/t/test_password.rb +188 -0
  133. data/t/test_radio.rb +226 -0
  134. data/t/test_role.rb +249 -0
  135. data/t/test_runo.rb +742 -0
  136. data/t/test_runo_call.rb +1286 -0
  137. data/t/test_runo_i18n.rb +318 -0
  138. data/t/test_select.rb +182 -0
  139. data/t/test_set_complex.rb +527 -0
  140. data/t/test_set_dynamic.rb +1504 -0
  141. data/t/test_set_folder.rb +515 -0
  142. data/t/test_set_permit.rb +246 -0
  143. data/t/test_set_static.rb +445 -0
  144. data/t/test_storage.rb +915 -0
  145. data/t/test_text.rb +125 -0
  146. data/t/test_textarea.rb +138 -0
  147. data/t/test_timestamp.rb +473 -0
  148. data/t/test_workflow.rb +367 -0
  149. metadata +345 -0
@@ -0,0 +1,73 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009 Akira FUNAI
5
+
6
+ class Runo::Storage::Temp < Runo::Storage
7
+
8
+ def self.available?
9
+ true
10
+ end
11
+
12
+ def persistent?
13
+ false
14
+ end
15
+
16
+ def val(id = nil)
17
+ @val ||= {}
18
+ id ? @val[id] : @val
19
+ end
20
+
21
+ def build(v)
22
+ @val = v
23
+ self
24
+ end
25
+
26
+ def clear
27
+ @val = {}
28
+ self
29
+ end
30
+
31
+ def store(id, v, ext = :unused)
32
+ if new_id?(id, v)
33
+ old_id = id
34
+ id = new_id v
35
+ return if val[id] # duplicate id
36
+ move(old_id, id) unless old_id == :new_id
37
+ end
38
+ val[id] = v
39
+ id
40
+ end
41
+
42
+ def delete(id)
43
+ @val.delete id
44
+ id
45
+ end
46
+
47
+ def move(old_id, new_id)
48
+ rex = /\A#{old_id}/
49
+ val.keys.each {|id|
50
+ if id =~ rex
51
+ to_id = id.sub(rex, new_id)
52
+ @val[to_id] = @val.delete id
53
+ end
54
+ }
55
+ new_id
56
+ end
57
+
58
+ private
59
+
60
+ def _select_by_id(conds)
61
+ val.keys & conds[:id].to_a
62
+ end
63
+
64
+ def _select_by_d(conds)
65
+ rex_d = /^#{conds[:d]}/
66
+ val.keys.select {|id| id[rex_d] }
67
+ end
68
+
69
+ def _select_all(conds)
70
+ val.keys
71
+ end
72
+
73
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009-2010 Akira FUNAI
5
+
6
+ class Runo::Set::Dynamic
7
+
8
+ private
9
+
10
+ def _g_action_create(arg)
11
+ label = _('create new %{item}...') % {
12
+ :item => _((my[:item].size == 1 && my[:item]['default'][:label]) || my[:item_label])
13
+ }
14
+ (_get_by_action_tmpl(arg) || <<_html) if permit? :create
15
+ <div class="action_create"><a href="#{_g_uri_create arg}">#{label}</a></div>
16
+ _html
17
+ end
18
+
19
+ def _g_uri_create(arg)
20
+ "#{my[:path]}/create.html"
21
+ end
22
+
23
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009-2010 Akira FUNAI
5
+
6
+ class Runo::Set::Static::Folder
7
+
8
+ private
9
+
10
+ def _g_action_login(arg)
11
+ if Runo.client == 'nobody'
12
+ <<_html
13
+ <div class="action_login"><a href="#{my[:dir]}/login.html">#{_ 'login'}</a></div>
14
+ _html
15
+ else
16
+ <<_html
17
+ <div class="action_logout"><a href="#{my[:dir]}/logout.html?_token=#{Runo.token}">#{_ 'logout'}</a></div>
18
+ _html
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009-2010 Akira FUNAI
5
+
6
+ class Runo::Set::Static::Folder
7
+
8
+ private
9
+
10
+ def _g_action_signup(arg)
11
+ (_get_by_action_tmpl(arg) || <<_html) if Runo.client == 'nobody'
12
+ <div class="action_signup"><a href="/_users/create.html">#{_ 'signup'}</a></div>
13
+ _html
14
+ end
15
+
16
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009-2010 Akira FUNAI
5
+
6
+ class Runo::Set::Static
7
+
8
+ private
9
+
10
+ def _g_action_update(arg)
11
+ (_get_by_action_tmpl(arg) || <<_html.chomp) if permit_get?(:action => :update)
12
+ <div class="action_update"><a href="#{_g_uri_update arg}">#{_ 'update...'}</a></div>
13
+ _html
14
+ end
15
+
16
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009-2010 Akira FUNAI
5
+
6
+ class Runo::Set::Static::Folder
7
+
8
+ private
9
+
10
+ def _g_crumb(arg)
11
+ <<_html
12
+ <div class="crumb">
13
+ #{_crumb}</div>
14
+ _html
15
+ end
16
+
17
+ def _crumb
18
+ crumb = <<_html
19
+ <a href="#{Runo.uri}#{my[:dir]}/">#{my[:label] || my[:id]}</a>
20
+ _html
21
+ my[:parent] ? [my[:parent].send(:_crumb), crumb].join(" &raquo;\n") : crumb
22
+ end
23
+
24
+ end
@@ -0,0 +1,16 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009-2010 Akira FUNAI
5
+
6
+ class Runo::Set::Dynamic
7
+
8
+ private
9
+
10
+ def _g_done(arg)
11
+ (_get_by_action_tmpl(arg) || <<_html.chomp) if arg[:orig_action] == :done
12
+ <div class="done">#{_ 'done.'}</div>
13
+ _html
14
+ end
15
+
16
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009-2010 Akira FUNAI
5
+
6
+ class Runo::Set::Dynamic
7
+
8
+ private
9
+
10
+ def _g_login(arg)
11
+ path = Runo::Path.path_of arg[:conds]
12
+ action = arg[:dest_action]
13
+ <<_html
14
+ <form id="form_#{my[:name]}" method="post" action="#{my[:base_path]}/#{path}login.html">
15
+ <div class="login">
16
+ <input type="hidden" name="dest_action" value="#{action}" />
17
+ <label for="login_id">#{_ 'ID'}</label><input type="text" id="login_id" name="id" size="10" value="" />
18
+ <label for="login_pw">#{_ 'Password'}</label><input type="password" id="login_pw" name="pw" size="10" value="" />
19
+ <input type="submit" value="#{_ 'login'}" />
20
+ </div>
21
+ </form>
22
+ _html
23
+ end
24
+
25
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009-2010 Akira FUNAI
5
+
6
+ class Runo::Set::Static::Folder
7
+
8
+ private
9
+
10
+ def _g_me(arg)
11
+ if Runo.client == 'nobody'
12
+ <<_html
13
+ <div class="me">
14
+ #{_g_action_login arg}</div>
15
+ _html
16
+ else
17
+ img = Runo::Set::Static::Folder.root.item('_users', 'main', Runo.client, 'avatar')
18
+ roles = Runo::Workflow.roles my[:roles]
19
+ <<_html
20
+ <div class="me">
21
+ <a href="#{Runo.uri}/_users/id=#{Runo.client}/update.html">
22
+ #{img.send(:_get_by_self_reference, :sub_action => :without_link) if img}
23
+ </a>
24
+ <div class="client">#{Runo.client}</div>
25
+ <div class="roles">(#{roles.join ', '})</div>
26
+ #{_g_action_login arg}</div>
27
+ _html
28
+ end
29
+ end
30
+
31
+ end
@@ -0,0 +1,51 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009-2010 Akira FUNAI
5
+
6
+ class Runo::Set::Dynamic
7
+
8
+ private
9
+
10
+ def _g_message(arg)
11
+ return if arg[:orig_action] == :done && my[:tmpl][:done]
12
+ return unless self == Runo.base
13
+
14
+ if arg[:dest_action]
15
+ message = {:alert => _('please login.')}
16
+ elsif arg[:orig_action] == :preview
17
+ message = {:notice => _('please confirm.')}
18
+ elsif !self.valid? && arg[:orig_action] != :create
19
+ message = {:error => _('malformed input.')}
20
+ elsif Runo.transaction[my[:tid]].is_a? ::Hash
21
+ message = {
22
+ :notice => Runo.transaction[my[:tid]].keys.collect {|item_result|
23
+ n = Runo.transaction[my[:tid]][item_result]
24
+ _('%{result} %{n} %{item}.') % {
25
+ :result => {
26
+ :create => _('created'),
27
+ :update => _('updated'),
28
+ :delete => _('deleted'),
29
+ }[item_result],
30
+ :n => n,
31
+ :item => n_(
32
+ (my[:item].size == 1 && my[:item]['default'][:label]) || my[:item_label],
33
+ '',
34
+ n
35
+ )
36
+ }
37
+ }
38
+ } unless Runo.transaction[my[:tid]].empty?
39
+ Runo.transaction[my[:tid]] = :expired
40
+ end
41
+
42
+ message.keys.collect {|type|
43
+ lis = message[type].collect {|m| " <li>#{Runo::Field.h m}</li>\n" }
44
+ <<_html
45
+ <ul class="message #{type}">
46
+ #{lis}</ul>
47
+ _html
48
+ }.join if message
49
+ end
50
+
51
+ end
@@ -0,0 +1,88 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009-2010 Akira FUNAI
5
+
6
+ class Runo::Set::Dynamic
7
+
8
+ private
9
+
10
+ def _g_navi(arg)
11
+ arg[:navi] ||= @storage.navi(arg[:conds] || {})
12
+ return unless (arg[:orig_action] == :read) && (arg[:navi][:prev] || arg[:navi][:next])
13
+
14
+ div = my[:tmpl][:navi] || '<div class="navi">$(.navi_prev) | $(.navi_p)$(.navi_next)</div>'
15
+ div.gsub(/\$\(\.(navi_prev|navi_next|navi_p|uri_prev|uri_next)\)/) {
16
+ __send__("_g_#{$1}", arg)
17
+ }
18
+ end
19
+
20
+ def _g_navi_prev(arg)
21
+ button = my[:tmpl][:navi_prev] || _('prev')
22
+ (uri = _g_uri_prev(arg)) ? "<a href=\"#{my[:path]}/#{uri}\">#{button}</a>" : button
23
+ end
24
+
25
+ def _g_navi_next(arg)
26
+ button = my[:tmpl][:navi_next] || _('next')
27
+ (uri = _g_uri_next(arg)) ? "<a href=\"#{my[:path]}/#{uri}\">#{button}</a>" : button
28
+ end
29
+
30
+ def _g_navi_p(arg)
31
+ uris = _uri_p(arg)
32
+ return unless uris && uris.size > 1
33
+
34
+ item_tmpl = nil
35
+ div = my[:tmpl][:navi_p] || '<span class="item">$() </span> | '
36
+ div = Runo::Parser.gsub_block(div, 'item') {|open, inner, close|
37
+ item_tmpl = open + inner + close
38
+ '$(.items)'
39
+ }
40
+ div.gsub('$(.items)') {
41
+ uris.collect {|uri|
42
+ p = uri[/p=(\d+)/, 1] || '1'
43
+ if arg[:conds][:p] == p
44
+ item_tmpl.gsub('$()', p)
45
+ else
46
+ item_tmpl.gsub('$()', "<a href=\"#{my[:path]}/#{uri}\">#{p}</a>")
47
+ end
48
+ }.join
49
+ }
50
+ end
51
+
52
+ def _g_uri_prev(arg)
53
+ arg[:navi] ||= @storage.navi(arg[:conds] || {})
54
+ Runo::Path.path_of(arg[:navi][:prev]) + _uri_action(arg) if arg[:navi][:prev]
55
+ end
56
+
57
+ def _g_uri_next(arg)
58
+ arg[:navi] ||= @storage.navi(arg[:conds] || {})
59
+ Runo::Path.path_of(arg[:navi][:next]) + _uri_action(arg) if arg[:navi][:next]
60
+ end
61
+
62
+ def _uri_p(arg)
63
+ arg[:navi] ||= @storage.navi(arg[:conds] || {})
64
+ if arg[:navi][:sibs] && arg[:navi][:sibs].keys.first == :p
65
+ base_conds = arg[:conds].dup
66
+ base_conds.delete :p
67
+ conds = arg[:navi][:sibs].values.first
68
+ if p = arg[:conds][:p]
69
+ range = ['1', conds.last] + ((p.to_i - 5)..(p.to_i + 5)).to_a.collect {|i| i.to_s }
70
+ conds = conds & range
71
+ end
72
+ conds.collect {|cond|
73
+ Runo::Path.path_of(base_conds.merge(:p => cond)) + _uri_action(arg)
74
+ }
75
+ end
76
+ end
77
+
78
+ def _uri_action(arg)
79
+ if [nil, default_action].include?(arg[:orig_action]) && !arg[:sub_action]
80
+ ''
81
+ elsif arg[:sub_action]
82
+ "#{arg[:orig_action]}_#{arg[:sub_action]}.html"
83
+ else
84
+ "#{arg[:orig_action]}.html"
85
+ end
86
+ end
87
+
88
+ end
@@ -0,0 +1,49 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009-2010 Akira FUNAI
5
+
6
+ class Runo::Set::Dynamic
7
+
8
+ private
9
+
10
+ def _g_submit(arg)
11
+ <<_html
12
+ <div class="submit">
13
+ #{_g_submit_commit arg}#{_g_submit_preview arg}#{_g_submit_preview_delete arg}</div>
14
+ _html
15
+ end
16
+
17
+ def _g_submit_commit(arg)
18
+ if collect_item(arg[:conds]).all? {|i| i[:id] =~ Runo::REX::ID_NEW }
19
+ action = :create
20
+ elsif arg[:orig_action] == :preview
21
+ action = arg[:sub_action]
22
+ else
23
+ action = arg[:orig_action]
24
+ end
25
+ <<_html unless my[:preview] == :mandatory && arg[:orig_action] != :preview
26
+ <input name="#{my[:short_name]}.status-public" type="submit" value="#{_ action.to_s}" />
27
+ _html
28
+ end
29
+
30
+ def _g_submit_preview(arg)
31
+ label = _ 'preview'
32
+ <<_html if my[:preview] && arg[:orig_action] != :preview
33
+ <input name="#{my[:short_name]}.action-preview_#{arg[:orig_action]}" type="submit" value="#{label}" />
34
+ _html
35
+ end
36
+
37
+ def _g_submit_preview_delete(arg)
38
+ if (
39
+ permit_get?(arg.merge :action => :delete) &&
40
+ collect_item(arg[:conds]).find {|item| item[:id] !~ Runo::REX::ID_NEW } &&
41
+ arg[:orig_action] != :preview
42
+ )
43
+ <<_html
44
+ <input name="#{my[:short_name]}.action-preview_delete" type="submit" value="#{_ 'delete...'}" />
45
+ _html
46
+ end
47
+ end
48
+
49
+ end