olelo 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (282) hide show
  1. data/.gitignore +10 -0
  2. data/.gitmodules +3 -0
  3. data/Gemfile +3 -0
  4. data/README.markdown +104 -0
  5. data/Rakefile +92 -0
  6. data/bin/olelo +5 -0
  7. data/config.ru +86 -0
  8. data/config/aspects.rb +256 -0
  9. data/config/config.yml.default +154 -0
  10. data/config/initializers/00-mime_types.rb +29 -0
  11. data/config/initializers/01-slim.rb +2 -0
  12. data/config/interwiki.yml +11 -0
  13. data/doc/AUTHORS +7 -0
  14. data/doc/LICENSE +22 -0
  15. data/lib/olelo.rb +39 -0
  16. data/lib/olelo/application.rb +294 -0
  17. data/lib/olelo/attributes.rb +285 -0
  18. data/lib/olelo/config.rb +88 -0
  19. data/lib/olelo/extensions.rb +252 -0
  20. data/lib/olelo/helper.rb +290 -0
  21. data/lib/olelo/hooks.rb +142 -0
  22. data/lib/olelo/html_safe.rb +29 -0
  23. data/lib/olelo/initializer.rb +76 -0
  24. data/lib/olelo/locale.rb +63 -0
  25. data/lib/olelo/locale.yml +284 -0
  26. data/lib/olelo/menu.rb +101 -0
  27. data/lib/olelo/middleware/blacklist.rb +25 -0
  28. data/lib/olelo/middleware/degrade_mime_type.rb +19 -0
  29. data/lib/olelo/middleware/flash.rb +97 -0
  30. data/lib/olelo/middleware/force_encoding.rb +41 -0
  31. data/lib/olelo/page.rb +266 -0
  32. data/lib/olelo/patch.rb +311 -0
  33. data/lib/olelo/plugin.rb +188 -0
  34. data/lib/olelo/repository.rb +225 -0
  35. data/lib/olelo/routing.rb +223 -0
  36. data/lib/olelo/templates.rb +30 -0
  37. data/lib/olelo/user.rb +132 -0
  38. data/lib/olelo/util.rb +233 -0
  39. data/lib/olelo/version.rb +3 -0
  40. data/lib/olelo/virtualfs.rb +161 -0
  41. data/lib/rack/olelo_patches.rb +33 -0
  42. data/lib/rack/relative_redirect.rb +44 -0
  43. data/lib/rack/static_cache.rb +93 -0
  44. data/lib/yard/addons.rb +1 -0
  45. data/lib/yard/addons/hook_handler.rb +25 -0
  46. data/lib/yard/addons/override_tag.rb +14 -0
  47. data/lib/yard/addons/route_handler.rb +33 -0
  48. data/lib/yard/addons/sanitize_anchor.rb +16 -0
  49. data/olelo.gemspec +31 -0
  50. data/plugins/aspects/changelog.rb +45 -0
  51. data/plugins/aspects/documentbrowser.rb +57 -0
  52. data/plugins/aspects/download.rb +11 -0
  53. data/plugins/aspects/highlight.rb +8 -0
  54. data/plugins/aspects/image.rb +41 -0
  55. data/plugins/aspects/imageinfo.rb +64 -0
  56. data/plugins/aspects/locale.yml +60 -0
  57. data/plugins/aspects/main.rb +199 -0
  58. data/plugins/aspects/pageinfo.rb +37 -0
  59. data/plugins/aspects/source.rb +6 -0
  60. data/plugins/aspects/subpages.rb +44 -0
  61. data/plugins/aspects/text.rb +9 -0
  62. data/plugins/blog/blog.css +1 -0
  63. data/plugins/blog/blog.scss +37 -0
  64. data/plugins/blog/locale.yml +12 -0
  65. data/plugins/blog/main.rb +85 -0
  66. data/plugins/editor/locale.yml +18 -0
  67. data/plugins/editor/markup/main.rb +3 -0
  68. data/plugins/editor/markup/script.js +10 -0
  69. data/plugins/editor/markup/script/00-jquery.textselection.js +267 -0
  70. data/plugins/editor/markup/script/01-olelo.markupeditor.js +116 -0
  71. data/plugins/editor/markup/script/init.js +10 -0
  72. data/plugins/editor/preview.rb +52 -0
  73. data/plugins/editor/recaptcha.rb +56 -0
  74. data/plugins/filters/creole.rb +37 -0
  75. data/plugins/filters/disposition.rb +9 -0
  76. data/plugins/filters/editsection.rb +67 -0
  77. data/plugins/filters/fix_img_tag.rb +16 -0
  78. data/plugins/filters/html_wrapper.rb +12 -0
  79. data/plugins/filters/interwiki.rb +19 -0
  80. data/plugins/filters/link_classifier.rb +26 -0
  81. data/plugins/filters/locale.yml +15 -0
  82. data/plugins/filters/main.rb +202 -0
  83. data/plugins/filters/markdown_nowiki.rb +15 -0
  84. data/plugins/filters/numbering.xsl +93 -0
  85. data/plugins/filters/orgmode.rb +6 -0
  86. data/plugins/filters/rubypants.rb +6 -0
  87. data/plugins/filters/s5/main.rb +32 -0
  88. data/plugins/filters/s5/s5.xsl +118 -0
  89. data/plugins/filters/tilt.rb +17 -0
  90. data/plugins/filters/toc.rb +50 -0
  91. data/plugins/filters/xhtml2latex.xsl +232 -0
  92. data/plugins/filters/xslt.rb +22 -0
  93. data/plugins/gallery/gallery.css +1 -0
  94. data/plugins/gallery/gallery.scss +28 -0
  95. data/plugins/gallery/main.rb +34 -0
  96. data/plugins/misc/fancybox/images/blank.gif +0 -0
  97. data/plugins/misc/fancybox/images/fancy_close.png +0 -0
  98. data/plugins/misc/fancybox/images/fancy_loading.png +0 -0
  99. data/plugins/misc/fancybox/images/fancy_nav_left.png +0 -0
  100. data/plugins/misc/fancybox/images/fancy_nav_right.png +0 -0
  101. data/plugins/misc/fancybox/images/fancy_shadow_e.png +0 -0
  102. data/plugins/misc/fancybox/images/fancy_shadow_n.png +0 -0
  103. data/plugins/misc/fancybox/images/fancy_shadow_ne.png +0 -0
  104. data/plugins/misc/fancybox/images/fancy_shadow_nw.png +0 -0
  105. data/plugins/misc/fancybox/images/fancy_shadow_s.png +0 -0
  106. data/plugins/misc/fancybox/images/fancy_shadow_se.png +0 -0
  107. data/plugins/misc/fancybox/images/fancy_shadow_sw.png +0 -0
  108. data/plugins/misc/fancybox/images/fancy_shadow_w.png +0 -0
  109. data/plugins/misc/fancybox/images/fancy_title_left.png +0 -0
  110. data/plugins/misc/fancybox/images/fancy_title_main.png +0 -0
  111. data/plugins/misc/fancybox/images/fancy_title_over.png +0 -0
  112. data/plugins/misc/fancybox/images/fancy_title_right.png +0 -0
  113. data/plugins/misc/fancybox/images/fancybox-x.png +0 -0
  114. data/plugins/misc/fancybox/images/fancybox-y.png +0 -0
  115. data/plugins/misc/fancybox/images/fancybox.png +0 -0
  116. data/plugins/misc/fancybox/jquery.fancybox.css +1 -0
  117. data/plugins/misc/fancybox/jquery.fancybox.scss +323 -0
  118. data/plugins/misc/fancybox/main.rb +4 -0
  119. data/plugins/misc/fancybox/script.js +37 -0
  120. data/plugins/misc/fancybox/script/00-jquery.mousewheel.js +84 -0
  121. data/plugins/misc/fancybox/script/01-jquery.easing.js +205 -0
  122. data/plugins/misc/fancybox/script/02-jquery.fancybox.js +1156 -0
  123. data/plugins/misc/fancybox/script/init.js +18 -0
  124. data/plugins/misc/system.rb +192 -0
  125. data/plugins/misc/variables.rb +29 -0
  126. data/plugins/misc/webdav.rb +45 -0
  127. data/plugins/repositories/git_grep.rb +69 -0
  128. data/plugins/repositories/gitrb_repository.rb +204 -0
  129. data/plugins/repositories/locale.yml +12 -0
  130. data/plugins/repositories/rugged_repository.rb +454 -0
  131. data/plugins/security/acl.rb +57 -0
  132. data/plugins/security/basic_auth.rb +21 -0
  133. data/plugins/security/locale.yml +21 -0
  134. data/plugins/security/persistent_login.rb +32 -0
  135. data/plugins/security/portal.rb +28 -0
  136. data/plugins/security/private_wiki.rb +24 -0
  137. data/plugins/security/readonly_wiki.rb +25 -0
  138. data/plugins/security/stack.rb +20 -0
  139. data/plugins/security/yamlfile.rb +66 -0
  140. data/plugins/tags/code.rb +6 -0
  141. data/plugins/tags/footnotes.rb +35 -0
  142. data/plugins/tags/gist-embed.css +123 -0
  143. data/plugins/tags/gist.rb +13 -0
  144. data/plugins/tags/html.rb +57 -0
  145. data/plugins/tags/include.rb +20 -0
  146. data/plugins/tags/main.rb +353 -0
  147. data/plugins/tags/math.rb +117 -0
  148. data/plugins/tags/scripting.rb +64 -0
  149. data/plugins/tags/sort.rb +7 -0
  150. data/plugins/tags/tabs.rb +20 -0
  151. data/plugins/treeview/images/collapsed.png +0 -0
  152. data/plugins/treeview/images/expanded.png +0 -0
  153. data/plugins/treeview/images/menu.png +0 -0
  154. data/plugins/treeview/images/tree.png +0 -0
  155. data/plugins/treeview/images/wait.gif +0 -0
  156. data/plugins/treeview/main.rb +16 -0
  157. data/plugins/treeview/script.js +5 -0
  158. data/plugins/treeview/script/00-jquery.treeview.js +164 -0
  159. data/plugins/treeview/script/init.js +25 -0
  160. data/plugins/treeview/treeview.css +1 -0
  161. data/plugins/treeview/treeview.scss +113 -0
  162. data/plugins/utils/assets.rb +74 -0
  163. data/plugins/utils/cache.rb +53 -0
  164. data/plugins/utils/image_magick.rb +34 -0
  165. data/plugins/utils/pygments.css +1 -0
  166. data/plugins/utils/pygments.rb +50 -0
  167. data/plugins/utils/pygments.scss +83 -0
  168. data/plugins/utils/semaphore.rb +50 -0
  169. data/plugins/utils/shell.rb +45 -0
  170. data/plugins/utils/store.rb +315 -0
  171. data/plugins/utils/worker.rb +36 -0
  172. data/plugins/utils/xml.rb +29 -0
  173. data/static/images/favicon.png +0 -0
  174. data/static/script.js +267 -0
  175. data/static/script/00-json2.js +486 -0
  176. data/static/script/01-jstorage.js +217 -0
  177. data/static/script/02-jquery.js +9440 -0
  178. data/static/script/03-jquery.ui.core.js +337 -0
  179. data/static/script/04-jquery.ui.widget.js +502 -0
  180. data/static/script/05-jquery.ui.position.js +517 -0
  181. data/static/script/06-jquery.ui.menu.js +609 -0
  182. data/static/script/07-jquery.ui.autocomplete.js +601 -0
  183. data/static/script/08-olelo.i18n.js +37 -0
  184. data/static/script/09-olelo.unsaved.js +68 -0
  185. data/static/script/10-olelo.historytable.js +40 -0
  186. data/static/script/11-olelo.pagination.js +18 -0
  187. data/static/script/13-olelo.tabwidget.js +57 -0
  188. data/static/script/14-olelo.timeago.js +70 -0
  189. data/static/script/15-olelo.underliner.js +31 -0
  190. data/static/script/16-olelo.ui.combobox.js +32 -0
  191. data/static/script/init.js +48 -0
  192. data/static/themes/atlantis/constants.scss +15 -0
  193. data/static/themes/atlantis/iehacks.scss +38 -0
  194. data/static/themes/atlantis/images/actions/delete.png +0 -0
  195. data/static/themes/atlantis/images/actions/edit.png +0 -0
  196. data/static/themes/atlantis/images/actions/history.png +0 -0
  197. data/static/themes/atlantis/images/actions/home.png +0 -0
  198. data/static/themes/atlantis/images/actions/move.png +0 -0
  199. data/static/themes/atlantis/images/actions/new.png +0 -0
  200. data/static/themes/atlantis/images/actions/page.png +0 -0
  201. data/static/themes/atlantis/images/bg/button.png +0 -0
  202. data/static/themes/atlantis/images/bg/container.png +0 -0
  203. data/static/themes/atlantis/images/bg/content.png +0 -0
  204. data/static/themes/atlantis/images/bg/footer.png +0 -0
  205. data/static/themes/atlantis/images/bg/header.jpg +0 -0
  206. data/static/themes/atlantis/images/bg/header.orig.jpg +0 -0
  207. data/static/themes/atlantis/images/bg/header_gray.jpg +0 -0
  208. data/static/themes/atlantis/images/bug.png +0 -0
  209. data/static/themes/atlantis/images/filetypes/7z.png +0 -0
  210. data/static/themes/atlantis/images/filetypes/_archive.png +0 -0
  211. data/static/themes/atlantis/images/filetypes/_audio.png +0 -0
  212. data/static/themes/atlantis/images/filetypes/_code.png +0 -0
  213. data/static/themes/atlantis/images/filetypes/_linux.png +0 -0
  214. data/static/themes/atlantis/images/filetypes/_picture.png +0 -0
  215. data/static/themes/atlantis/images/filetypes/_video.png +0 -0
  216. data/static/themes/atlantis/images/filetypes/bz2.png +0 -0
  217. data/static/themes/atlantis/images/filetypes/doc.png +0 -0
  218. data/static/themes/atlantis/images/filetypes/flac.png +0 -0
  219. data/static/themes/atlantis/images/filetypes/gz.png +0 -0
  220. data/static/themes/atlantis/images/filetypes/html.png +0 -0
  221. data/static/themes/atlantis/images/filetypes/java.png +0 -0
  222. data/static/themes/atlantis/images/filetypes/jpg.png +0 -0
  223. data/static/themes/atlantis/images/filetypes/midi.png +0 -0
  224. data/static/themes/atlantis/images/filetypes/mp3.png +0 -0
  225. data/static/themes/atlantis/images/filetypes/ogg.png +0 -0
  226. data/static/themes/atlantis/images/filetypes/pdf.png +0 -0
  227. data/static/themes/atlantis/images/filetypes/php.png +0 -0
  228. data/static/themes/atlantis/images/filetypes/png.png +0 -0
  229. data/static/themes/atlantis/images/filetypes/ppt.png +0 -0
  230. data/static/themes/atlantis/images/filetypes/psd.png +0 -0
  231. data/static/themes/atlantis/images/filetypes/rar.png +0 -0
  232. data/static/themes/atlantis/images/filetypes/rb.png +0 -0
  233. data/static/themes/atlantis/images/filetypes/sh.png +0 -0
  234. data/static/themes/atlantis/images/filetypes/tar.png +0 -0
  235. data/static/themes/atlantis/images/filetypes/txt.png +0 -0
  236. data/static/themes/atlantis/images/filetypes/wma.png +0 -0
  237. data/static/themes/atlantis/images/filetypes/xls.png +0 -0
  238. data/static/themes/atlantis/images/filetypes/zip.png +0 -0
  239. data/static/themes/atlantis/images/folder.png +0 -0
  240. data/static/themes/atlantis/images/folder_open.png +0 -0
  241. data/static/themes/atlantis/images/loading.gif +0 -0
  242. data/static/themes/atlantis/images/loading.xcf +0 -0
  243. data/static/themes/atlantis/images/not_found.png +0 -0
  244. data/static/themes/atlantis/images/page.png +0 -0
  245. data/static/themes/atlantis/images/search.png +0 -0
  246. data/static/themes/atlantis/layout.scss +115 -0
  247. data/static/themes/atlantis/menu.scss +99 -0
  248. data/static/themes/atlantis/print.scss +129 -0
  249. data/static/themes/atlantis/screen.scss +495 -0
  250. data/static/themes/atlantis/style.css +3 -0
  251. data/static/themes/lib/autocomplete.scss +39 -0
  252. data/static/themes/lib/headlines.scss +10 -0
  253. data/static/themes/lib/horizontal-list.scss +31 -0
  254. data/static/themes/lib/patch.scss +88 -0
  255. data/static/themes/lib/reset.scss +114 -0
  256. data/static/themes/lib/rounded.scss +46 -0
  257. data/static/themes/lib/shadow.scss +14 -0
  258. data/test/config_test.rb +28 -0
  259. data/test/factory_test.rb +29 -0
  260. data/test/hash_extensions_test.rb +16 -0
  261. data/test/helper.rb +38 -0
  262. data/test/hooks_test.rb +85 -0
  263. data/test/object_extensions_test.rb +20 -0
  264. data/test/page_test.rb +168 -0
  265. data/test/request_test.rb +166 -0
  266. data/test/string_extensions_test.rb +32 -0
  267. data/test/templates_test.rb +39 -0
  268. data/test/util_test.rb +71 -0
  269. data/views/changes.slim +22 -0
  270. data/views/compare.slim +8 -0
  271. data/views/delete.slim +9 -0
  272. data/views/deleted.slim +2 -0
  273. data/views/edit.slim +65 -0
  274. data/views/error.slim +6 -0
  275. data/views/history.slim +20 -0
  276. data/views/layout.slim +38 -0
  277. data/views/login.slim +37 -0
  278. data/views/move.slim +10 -0
  279. data/views/not_found.slim +6 -0
  280. data/views/profile.slim +26 -0
  281. data/views/show.slim +9 -0
  282. metadata +488 -0
@@ -0,0 +1,285 @@
1
+ module Olelo
2
+ # Include module to add attribute editor to a class.
3
+ module Attributes
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ # Attribute data structure
9
+ # @api private
10
+ class Attribute
11
+ include Util
12
+
13
+ # @api private
14
+ attr_reader :key, :name
15
+
16
+ def initialize(parent, name)
17
+ @name = name.to_s
18
+ @key = ['attribute', parent.path, name].compact.join('_')
19
+ end
20
+
21
+ def label
22
+ @label ||= Locale.translate(key, :fallback => titlecase(name))
23
+ end
24
+
25
+ def label_tag
26
+ type = self.class.name.split('::').last.downcase
27
+ title = Locale.translate("type_#{type}", :fallback => titlecase(type))
28
+ %{<label for="#{key}" title="#{escape_html title}">#{escape_html label}</label>}
29
+ end
30
+
31
+ def build_form(attr)
32
+ "#{label_tag}#{field_tag(attr)}<br/>"
33
+ end
34
+
35
+ class String < Attribute
36
+ def field_tag(attr)
37
+ %{<input class="observe" type="text" id="#{key}" name="#{key}" value="#{escape_html attr}"/>}
38
+ end
39
+
40
+ def parse(params)
41
+ value = params[key]
42
+ value if !value.blank?
43
+ end
44
+ end
45
+
46
+ class List < Attribute
47
+ def field_tag(attr)
48
+ %{<input class="observe" type="text" id="#{key}" name="#{key}" value="#{escape_html attr.to_a.join(', ')}"/>}
49
+ end
50
+
51
+ def parse(params)
52
+ value = params[key]
53
+ value.split(/\s*,\s*/) if !value.blank?
54
+ end
55
+ end
56
+
57
+ class Integer < Attribute
58
+ def field_tag(attr)
59
+ %{<input class="observe" type="text" id="#{key}" name="#{key}" value="#{escape_html attr}"/>}
60
+ end
61
+
62
+ def parse(params)
63
+ value = params[key]
64
+ value.to_i if !value.blank?
65
+ end
66
+ end
67
+
68
+ class Boolean < Attribute
69
+ def field_tag(attr)
70
+ %{<input class="observe" type="checkbox" id="#{key}" name="#{key}" value="true"#{attr ? ' checked="checked"' : ''}/>}
71
+ end
72
+
73
+ def build_form(attr)
74
+ "<div class=\"indent\">#{field_tag(attr)}#{label_tag}</div><br/>\n"
75
+ end
76
+
77
+ def parse(params)
78
+ value = params[key]
79
+ true if value == 'true'
80
+ end
81
+ end
82
+
83
+ class Enum < Attribute
84
+ def initialize(parent, name, values = {})
85
+ super(parent, name)
86
+ raise 'Values must be Proc, Hash or Array' unless Proc === values || Hash === values || Array === values
87
+ @values = values
88
+ end
89
+
90
+ def field_tag(attr)
91
+ html = %{<select class="observe" id="#{key}" name="#{key}">
92
+ <option#{values.any? {|value,label| attr == value} ? '' : ' selected="selected"'}></option>}
93
+ values.sort_by(&:last).each do |value,label|
94
+ value_attr = value == label ? '' : %{ value="#{escape_html value}"}
95
+ selected_attr = attr == value ? ' selected="selected"' : ''
96
+ html << %{<option#{value_attr}#{selected_attr}>#{escape_html label}</option>}
97
+ end
98
+ html << '</select>'
99
+ end
100
+
101
+ def parse(params)
102
+ value = params[key]
103
+ value if values.include?(value)
104
+ end
105
+
106
+ private
107
+
108
+ def values
109
+ if Proc === @values
110
+ @values = @values.call
111
+ raise 'Values must be Hash or Array' unless Hash === @values || Array === @values
112
+ end
113
+ @values = Hash[*@values.zip(@values)] if Array === @values
114
+ @values
115
+ end
116
+ end
117
+
118
+ class Suggestions < Enum
119
+ def field_tag(attr)
120
+ %{<input class="observe" type="text" id="#{key}" name="#{key}" value="#{escape_html(values[attr] || attr)}"/>
121
+ <script type="text/javascript">
122
+ $('##{key}').combobox({ source: #{escape_javascript values.values.sort.to_json} });
123
+ </script>}
124
+ end
125
+
126
+ def parse(params)
127
+ value = params[key]
128
+ inverted_values[value] || value if !value.blank?
129
+ end
130
+
131
+ def inverted_values
132
+ @inverted_values ||= values.invert
133
+ end
134
+ end
135
+ end
136
+
137
+ # Data structure for group of attributes
138
+ # @api private
139
+ class AttributeGroup
140
+ include Util
141
+
142
+ # @api private
143
+ attr_reader :name, :path, :children
144
+
145
+ def initialize(parent = nil, name = nil)
146
+ @name = name.to_s
147
+ @path = parent ? [parent.path, name].compact.join('_') : nil
148
+ @children = {}
149
+ end
150
+
151
+ def label
152
+ @label ||= name.blank? ? '' : Locale.translate("group_#{path}", :fallback => titlecase(name))
153
+ end
154
+
155
+ # Build form for this group
156
+ # @return [String] html
157
+ # @api private
158
+ #
159
+ def build_form(attr)
160
+ html = label.blank? ? '' : "<h3>#{escape_html label}</h3>\n"
161
+ html << children.sort_by do |name, child|
162
+ [Attribute === child ? 0 : 1, child.label]
163
+ end.map do |name, child|
164
+ child.build_form(attr ? attr[name] : nil)
165
+ end.join
166
+ end
167
+
168
+ # Parse params and return attribute hash for this group
169
+ # @return [Hash]
170
+ # @api private
171
+ #
172
+ def parse(params)
173
+ attr = {}
174
+ children.each_pair do |name, child|
175
+ value = child.parse(params)
176
+ attr[name] = value if value
177
+ end
178
+ attr.empty? ? nil : attr
179
+ end
180
+ end
181
+
182
+ # DSL class used to initialize AttributeGroup
183
+ class AttributeDSL
184
+ include Util
185
+
186
+ # Initialize DSL with `group`
187
+ #
188
+ # @param [Olelo::Attributes::AttributeGroup] AttributeGroup to modify in this DSL block
189
+ # @yield DSL block
190
+ #
191
+ def initialize(group, &block)
192
+ @group = group
193
+ instance_eval(&block)
194
+ end
195
+
196
+ def string(name, values = nil, &block)
197
+ @group.children[name.to_s] = if values || block
198
+ Attribute::Suggestions.new(@group, name, block ? block : values)
199
+ else
200
+ Attribute::String.new(@group, name)
201
+ end
202
+ end
203
+
204
+ def integer(name)
205
+ @group.children[name.to_s] = Attribute::Integer.new(@group, name)
206
+ end
207
+
208
+ def boolean(name)
209
+ @group.children[name.to_s] = Attribute::Boolean.new(@group, name)
210
+ end
211
+
212
+ def list(name)
213
+ @group.children[name.to_s] = Attribute::List.new(@group, name)
214
+ end
215
+
216
+ def enum(name, values = nil, &block)
217
+ @group.children[name.to_s] = Attribute::Enum.new(@group, name, block ? block : values)
218
+ end
219
+
220
+ # Define attribute group
221
+ #
222
+ # @yield DSL block
223
+ # @return [void]
224
+ # @api public
225
+ #
226
+ def group(name, &block)
227
+ AttributeDSL.new(@group.children[name.to_s] ||= AttributeGroup.new(@group, name), &block)
228
+ end
229
+ end
230
+
231
+ # Extends class with attribute editor DSL
232
+ module ClassMethods
233
+ # Root attribute group
234
+ #
235
+ # @return [AttributeGroup] Root editor group
236
+ # @api private
237
+ #
238
+ def attribute_group
239
+ @attribute_group ||= AttributeGroup.new
240
+ end
241
+
242
+ # Add attribute to the attribute editor
243
+ #
244
+ # @yield DSL block
245
+ # @return [void]
246
+ # @api public
247
+ #
248
+ # @example add string attribute title
249
+ # attributes do
250
+ # string :title
251
+ # end
252
+ #
253
+ # @example add group with multiple attributes
254
+ # attributes do
255
+ # group :acl do
256
+ # list :read
257
+ # list :write
258
+ # end
259
+ #
260
+ def attributes(&block)
261
+ AttributeDSL.new(attribute_group, &block)
262
+ end
263
+ end
264
+
265
+ # Parse attributes from params hash
266
+ #
267
+ # @param [Hash] params submitted params hash
268
+ # @return [Hash] Attributes
269
+ # @api public
270
+ #
271
+ def update_attributes(params)
272
+ self.attributes = self.class.attribute_group.parse(params)
273
+ end
274
+
275
+ # Generate attribute editor form
276
+ #
277
+ # @param [Hash] default_values to use for the form
278
+ # @return [String] Generated html form
279
+ # @api public
280
+ #
281
+ def attribute_editor
282
+ self.class.attribute_group.build_form(attributes).html_safe
283
+ end
284
+ end
285
+ end
@@ -0,0 +1,88 @@
1
+ module Olelo
2
+ class Config
3
+ include Enumerable
4
+
5
+ attr_reader :base, :hash
6
+
7
+ def initialize(base = nil)
8
+ @hash = {}
9
+ @base = base.freeze
10
+ end
11
+
12
+ def [](key)
13
+ key = key.to_s
14
+ if i = key.index('.')
15
+ not_found(key) unless Config === hash[key[0...i]]
16
+ hash[key[0...i]][key[i+1..-1]]
17
+ else
18
+ not_found(key) if !hash.include?(key)
19
+ hash[key]
20
+ end
21
+ end
22
+
23
+ def []=(key, value)
24
+ key = key.to_s
25
+ if i = key.index('.')
26
+ child(key[0...i])[key[i+1..-1]] = value
27
+ elsif Hash === value
28
+ child(key).update(value)
29
+ else
30
+ hash[key] = value.freeze
31
+ end
32
+ end
33
+
34
+ def update(hash)
35
+ hash.each_pair do |key, value|
36
+ self[key] = value
37
+ end
38
+ end
39
+
40
+ def load(file)
41
+ load!(file) if File.file?(file)
42
+ end
43
+
44
+ def load!(file)
45
+ update(YAML.load_file(file))
46
+ end
47
+
48
+ def each(&block)
49
+ hash.each(&block)
50
+ end
51
+
52
+ def self.instance
53
+ @instance ||= Config.new
54
+ end
55
+
56
+ def self.[](key)
57
+ instance[key]
58
+ end
59
+
60
+ def to_hash
61
+ h = Hash.with_indifferent_access
62
+ hash.each_pair do |k, v|
63
+ h[k] = Config === v ? v.to_hash : v
64
+ end
65
+ h
66
+ end
67
+
68
+ def freeze
69
+ hash.freeze
70
+ super
71
+ end
72
+
73
+ private
74
+
75
+ def not_found(key)
76
+ raise(NameError, "Configuration key #{base ? "#{base}.#{key}" : key} not found")
77
+ end
78
+
79
+ def child(key)
80
+ if child = hash[key]
81
+ raise "Configuration key #{key} is already used" unless Config === child
82
+ child
83
+ else
84
+ hash[key] = Config.new(base ? "#{base}.#{key}" : key)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,252 @@
1
+ class Module
2
+ # Generate accessor method with question mark
3
+ #
4
+ # @param [String, Symbol...] attrs Attributes to generate setter for
5
+ # @return [void]
6
+ #
7
+ def attr_reader?(*attrs)
8
+ attrs.each do |a|
9
+ module_eval "def #{a}?; !!@#{a}; end"
10
+ end
11
+ end
12
+
13
+ # Generate attribute setter
14
+ #
15
+ # A setter accepts an argument to
16
+ # set a value. It acts as a getter without an argument.
17
+ #
18
+ # @see Ruby facets
19
+ # @param [String, Symbol...] attrs Attributes to generate setter for
20
+ # @return [void]
21
+ #
22
+ def attr_setter(*attrs)
23
+ code, made = '', []
24
+ attrs.each do |a|
25
+ code << "def #{a}(*a); a.size > 0 ? (@#{a}=a[0]; self) : @#{a} end\n"
26
+ made << a.to_sym
27
+ end
28
+ module_eval(code)
29
+ made
30
+ end
31
+
32
+ # Redefine a module method
33
+ #
34
+ # Replaces alias_method_chain and allows to call
35
+ # overwritten method via super.
36
+ #
37
+ # @param [Symbol, String] name of method
38
+ # @yield New method block
39
+ # @return [void]
40
+ #
41
+ def redefine_method(name, &block)
42
+ if instance_methods(false).any? {|x| x.to_s == name.to_s }
43
+ method = instance_method(name)
44
+ mod = Module.new do
45
+ define_method(name) {|*args| method.bind(self).call(*args) }
46
+ end
47
+ remove_method(name)
48
+ include(mod)
49
+ end
50
+ include(Module.new { define_method(name, &block) })
51
+ end
52
+
53
+ if method(:const_defined?).arity == 1
54
+ # Ruby 1.8
55
+ def local_const_defined?(const)
56
+ const_defined?(const)
57
+ end
58
+ else
59
+ # Ruby 1.9
60
+ def local_const_defined?(const)
61
+ const_defined?(const, false)
62
+ end
63
+ end
64
+ end
65
+
66
+ class Hash
67
+ # Stolen from rails
68
+ class WithIndifferentAccess < Hash
69
+ def initialize(arg = {})
70
+ if Hash === arg
71
+ super()
72
+ update(arg)
73
+ else
74
+ super(arg)
75
+ end
76
+ end
77
+
78
+ alias_method :regular_include, :include?
79
+ alias_method :regular_writer, :[]=
80
+ alias_method :regular_update, :update
81
+
82
+ def default(key = nil)
83
+ if Symbol === key && regular_include(key = key.to_s)
84
+ self[key]
85
+ else
86
+ super
87
+ end
88
+ end
89
+
90
+ def []=(key, value)
91
+ regular_writer(convert_key(key), value)
92
+ value
93
+ end
94
+
95
+ def update(other)
96
+ other.each_pair {|key, value| regular_writer(convert_key(key), value) }
97
+ self
98
+ end
99
+
100
+ alias_method :merge!, :update
101
+
102
+ def key?(key)
103
+ super(convert_key(key))
104
+ end
105
+
106
+ alias_method :include?, :key?
107
+ alias_method :has_key?, :key?
108
+ alias_method :member?, :key?
109
+
110
+ def fetch(key, *extras)
111
+ super(convert_key(key), *extras)
112
+ end
113
+
114
+ def values_at(*indices)
115
+ indices.collect {|key| self[convert_key(key)]}
116
+ end
117
+
118
+ def dup
119
+ WithIndifferentAccess.new(self)
120
+ end
121
+
122
+ def merge(hash)
123
+ self.dup.update(hash)
124
+ end
125
+
126
+ def delete(key)
127
+ super(convert_key(key))
128
+ end
129
+
130
+ def to_hash
131
+ Hash.new(default).merge(self)
132
+ end
133
+
134
+ protected
135
+
136
+ def convert_key(key)
137
+ Symbol === key ? key.to_s : key
138
+ end
139
+ end
140
+
141
+ def with_indifferent_access
142
+ hash = WithIndifferentAccess.new(self)
143
+ hash.default = self.default
144
+ hash
145
+ end
146
+
147
+ def self.with_indifferent_access(arg = {})
148
+ WithIndifferentAccess.new(arg)
149
+ end
150
+ end
151
+
152
+ class Object
153
+ # Metaclass of object
154
+ #
155
+ # @return [Class]
156
+ #
157
+ def metaclass
158
+ (class << self; self; end)
159
+ end
160
+
161
+ # Returns true if object is empty or false
162
+ #
163
+ # @return [Boolean]
164
+ #
165
+ def blank?
166
+ respond_to?(:empty?) ? empty? : !self
167
+ end
168
+
169
+ # Try to call method if it exists or return nil
170
+ #
171
+ # @param [String, Symbol] name Method name
172
+ # @param args Method arguments
173
+ # @return Method result or nil
174
+ #
175
+ def try(name, *args)
176
+ respond_to?(name) ? send(name, *args) : nil
177
+ end
178
+ end
179
+
180
+ class NilClass; def blank?; true; end; end
181
+ class FalseClass; def blank?; true; end; end
182
+ class TrueClass; def blank?; false; end; end
183
+
184
+ class String
185
+ # Faster blank?
186
+ alias blank? empty?
187
+
188
+ # Try to force encoding
189
+ #
190
+ # Force encoding of string and revert
191
+ # to original encoding if string has no valid encoding
192
+ #
193
+ # @param [Encoding, String] enc New encoding
194
+ # @return self
195
+ #
196
+ def try_encoding(enc)
197
+ old_enc = encoding
198
+ if old_enc != enc
199
+ force_encoding(enc)
200
+ force_encoding(old_enc) if !valid_encoding?
201
+ end
202
+ self
203
+ end
204
+
205
+ # Check if string starts with s
206
+ #
207
+ # @param [String] s
208
+ # @return [Boolean]
209
+ #
210
+ def starts_with?(s)
211
+ index(s) == 0
212
+ end
213
+
214
+ # Check if string ends with s
215
+ #
216
+ # @param [String] s
217
+ # @return [Boolean]
218
+ #
219
+ def ends_with?(s)
220
+ rindex(s) == size - s.size
221
+ end
222
+
223
+ # Clean up path (replaces '..', '.' etc.)
224
+ #
225
+ # @return [String] cleaned path
226
+ #
227
+ def cleanpath
228
+ names = []
229
+ split('/').each do |name|
230
+ case name
231
+ when '..'
232
+ names.pop
233
+ when '.'
234
+ when ''
235
+ else
236
+ names.push name
237
+ end
238
+ end
239
+ names.join('/')
240
+ end
241
+
242
+ # Concatenate path components with /
243
+ #
244
+ # Calls {#cleanpath} on result.
245
+ #
246
+ # @param [String] path component
247
+ # @return [String] this string concatenated with path
248
+ #
249
+ def /(path)
250
+ "#{self}/#{path}".cleanpath
251
+ end
252
+ end