olelo 0.9.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.
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