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,25 @@
1
+ # coding: binary
2
+ # Coding is required for StringIO
3
+ module Olelo
4
+ module Middleware
5
+ class Blacklist
6
+ NULL_IO = StringIO.new('')
7
+
8
+ def initialize(app, options)
9
+ @app = app
10
+ @list = options[:blacklist]
11
+ end
12
+
13
+ def call(env)
14
+ if %w(POST PUT DELETE).include?(env['REQUEST_METHOD']) && @list.include?(Rack::Request.new(env).ip)
15
+ env.delete('rack.request.form_vars')
16
+ env.delete('rack.request.form_hash')
17
+ env.delete('rack.request.form_input')
18
+ env['rack.input'] = NULL_IO
19
+ env['REQUEST_METHOD'] = 'GET'
20
+ end
21
+ @app.call(env)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ module Olelo
2
+ module Middleware
3
+ class DegradeMimeType
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ status, header, body = @app.call(env)
10
+ if env['HTTP_ACCEPT'].to_s !~ %r{application/xhtml\+xml}
11
+ if header['Content-Type'] =~ %r{\Aapplication/xhtml\+xml(;?.*)\Z}
12
+ header['Content-Type'] = "text/html#{$1}"
13
+ end
14
+ end
15
+ [status, header, body]
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,97 @@
1
+ module Olelo
2
+ module Middleware
3
+ class Flash
4
+ # Implements bracket accessors for storing and retrieving flash entries.
5
+ class FlashHash
6
+ def initialize(session)
7
+ @session = session
8
+ raise 'No session available' if !session
9
+ end
10
+
11
+ # Remove an entry from the session and return its value. Cache result in
12
+ # the instance cache.
13
+ def [](key)
14
+ key = key.to_sym
15
+ cache[key] ||= values.delete(key)
16
+ end
17
+
18
+ # Store the entry in the session, updating the instance cache as well.
19
+ def []=(key,val)
20
+ key = key.to_sym
21
+ cache[key] = values[key] = val
22
+ end
23
+
24
+ # Store a flash entry for only the current request, swept regardless of
25
+ # whether or not it was actually accessed
26
+ def now
27
+ cache
28
+ end
29
+
30
+ # Checks for the presence of a flash entry without retrieving or removing
31
+ # it from the cache or store.
32
+ def include?(key)
33
+ key = key.to_sym
34
+ cache.keys.include?(key) || values.keys.include?(key)
35
+ end
36
+
37
+ # Clear the hash
38
+ def clear
39
+ cache.clear
40
+ @session.delete(:olelo_flash)
41
+ end
42
+
43
+ # Hide the underlying olelo.flash session key and only expose values stored
44
+ # in the flash.
45
+ def inspect
46
+ "#<FlashHash @values=#{values.inspect} @cache=#{cache.inspect}>"
47
+ end
48
+
49
+ # Human readable for logging.
50
+ def to_s
51
+ values.inspect
52
+ end
53
+
54
+ private
55
+
56
+ # Maintain an instance-level cache of retrieved flash entries. These
57
+ # entries will have been removed from the session, but are still available
58
+ # through the cache.
59
+ def cache
60
+ @cache ||= {}
61
+ end
62
+
63
+ # Helper to access flash entries from olelo.flash session value. This key
64
+ # is used to prevent collisions with other user-defined session values.
65
+ def values
66
+ @session[:olelo_flash] ||= {}
67
+ end
68
+ end
69
+
70
+ def initialize(app, options = {})
71
+ @app = app
72
+ @hash = Class.new(FlashHash)
73
+ [*options[:accessors]].compact.each do |key|
74
+ key = key.to_sym
75
+ @hash.class_eval do
76
+ define_method(key) {|*a| a.size > 0 ? (self[key] = a[0]) : self[key] }
77
+ define_method("#{key}=") {|val| self[key] = val }
78
+ define_method("#{key}!") {|val| cache[key] = val }
79
+ end
80
+ end
81
+ [*options[:set_accessors]].compact.each do |key|
82
+ key = key.to_sym
83
+ @hash.class_eval do
84
+ define_method(key) {|*val| val.size > 0 ? (self[key] ||= Set.new).merge(val) : self[key] }
85
+ define_method("#{key}!") {|*val| val.size > 0 ? (cache[key] ||= Set.new).merge(val) : cache[key] }
86
+ end
87
+ end
88
+ end
89
+
90
+ def call(env)
91
+ session = env['rack.session']
92
+ env['olelo.flash'] ||= @hash.new(session)
93
+ @app.call(env)
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,41 @@
1
+ module Olelo
2
+ module Middleware
3
+ class ForceEncoding
4
+ def initialize(app, encoding = Encoding.default_external)
5
+ @app, @encoding = app, encoding
6
+ end
7
+
8
+ def call(env)
9
+ request = Rack::Request.new(env)
10
+ encode(env)
11
+ encode(request.params)
12
+ @app.call(env)
13
+ end
14
+
15
+ private
16
+
17
+ def encode(x)
18
+ case x
19
+ when Hash
20
+ y = x.frozen? ? {} : x
21
+ x.each { |k,v| y[k] = encode(v) }
22
+ y
23
+ when Array
24
+ y = x.frozen? ? [] : x
25
+ x.each_with_index {|v,i| y[i] = encode(v) }
26
+ y
27
+ when String
28
+ # Try to force encoding and revert to old encoding if this doesn't work
29
+ if x.encoding != @encoding
30
+ x = x.dup if x.frozen?
31
+ x.try_encoding(@encoding)
32
+ else
33
+ x
34
+ end
35
+ else
36
+ x
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
data/lib/olelo/page.rb ADDED
@@ -0,0 +1,266 @@
1
+ module Olelo
2
+ # Wiki page object
3
+ class Page
4
+ include Util
5
+ include Hooks
6
+ include Attributes
7
+
8
+ has_around_hooks :move, :delete, :save
9
+
10
+ attributes do
11
+ string :title
12
+ boolean :no_title
13
+ string :description
14
+ string :mime do
15
+ Config['mime_suggestions'].inject({}) do |hash, mime|
16
+ comment = MimeMagic.new(mime).comment
17
+ hash[mime] = comment.blank? ? mime : "#{comment} (#{mime})"
18
+ hash
19
+ end
20
+ end
21
+ end
22
+
23
+ # Pattern for valid paths
24
+ # @api public
25
+ PATH_PATTERN = '[^\s](?:.*[^\s]+)?'.freeze
26
+ PATH_REGEXP = /^#{PATH_PATTERN}$/
27
+
28
+ # Mime type for empty page
29
+ # @api public
30
+ EMPTY_MIME = MimeMagic.new('inode/x-empty')
31
+
32
+ # Mime type for directory
33
+ # @api public
34
+ DIRECTORY_MIME = MimeMagic.new('inode/directory')
35
+
36
+ attr_reader :path, :tree_version
37
+
38
+ def initialize(path, tree_version = nil, parent = nil)
39
+ @path, @tree_version, @parent = path.to_s.cleanpath.freeze, tree_version, parent
40
+ Page.check_path(@path)
41
+ end
42
+
43
+ def self.transaction(&block)
44
+ raise 'Transaction already running' if Thread.current[:olelo_tx]
45
+ Thread.current[:olelo_tx] = []
46
+ repository.transaction(&block)
47
+ ensure
48
+ Thread.current[:olelo_tx] = nil
49
+ end
50
+
51
+ def self.current_transaction
52
+ Thread.current[:olelo_tx] || raise('No transaction running')
53
+ end
54
+
55
+ def self.commit(comment)
56
+ tree_version = repository.commit(comment)
57
+ current_transaction.each {|proc| proc.call(tree_version) }
58
+ current_transaction.clear
59
+ end
60
+
61
+ # Throws exceptions if access denied, returns nil if not found
62
+ def self.find(path, tree_version = nil)
63
+ path = path.to_s.cleanpath
64
+ check_path(path)
65
+ tree_version = repository.get_version(tree_version) unless Version === tree_version
66
+ Page.new(path, tree_version) if tree_version && repository.path_exists?(path, tree_version)
67
+ end
68
+
69
+ # Throws if not found
70
+ def self.find!(path, tree_version = nil)
71
+ find(path, tree_version) || raise(NotFound, path)
72
+ end
73
+
74
+ # Head version
75
+ def head?
76
+ new? || tree_version.head?
77
+ end
78
+
79
+ def root?
80
+ path.empty?
81
+ end
82
+
83
+ def next_version
84
+ init_versions
85
+ @next_version
86
+ end
87
+
88
+ def previous_version
89
+ init_versions
90
+ @previous_version
91
+ end
92
+
93
+ def version
94
+ init_versions
95
+ @version
96
+ end
97
+
98
+ def history(skip = nil, limit = nil)
99
+ raise 'Page is new' if new?
100
+ repository.get_history(path, skip, limit)
101
+ end
102
+
103
+ def parent
104
+ @parent ||= Page.find(path/'..', tree_version) || Page.new(path/'..', tree_version) if !root?
105
+ end
106
+
107
+ def move(destination)
108
+ raise 'Page is not head' unless head?
109
+ raise 'Page is new' if new?
110
+ destination = destination.to_s.cleanpath
111
+ Page.check_path(destination)
112
+ raise :already_exists.t(:page => destination) if Page.find(destination)
113
+ with_hooks(:move, destination) { repository.move(path, destination) }
114
+ after_commit {|tree_version| update(destination, tree_version) }
115
+ end
116
+
117
+ def delete
118
+ raise 'Page is not head' unless head?
119
+ raise 'Page is new' if new?
120
+ with_hooks(:delete) { repository.delete(path) }
121
+ after_commit {|tree_version| update(path, nil) }
122
+ end
123
+
124
+ def diff(from, to)
125
+ raise 'Page is new' if new?
126
+ repository.diff(path, from, to)
127
+ end
128
+
129
+ def new?
130
+ !tree_version
131
+ end
132
+
133
+ def name
134
+ i = path.rindex('/')
135
+ name = i ? path[i+1..-1] : path
136
+ end
137
+
138
+ def title
139
+ attributes['title'] || (root? ? :root.t : name)
140
+ end
141
+
142
+ def extension
143
+ i = path.index('.')
144
+ i ? path[i+1..-1] : ''
145
+ end
146
+
147
+ def attributes
148
+ @attributes ||= deep_copy(saved_attributes)
149
+ end
150
+
151
+ def saved_attributes
152
+ @saved_attributes ||= new? ? {} : repository.get_attributes(path, tree_version)
153
+ end
154
+
155
+ def attributes=(a)
156
+ a ||= {}
157
+ if attributes != a
158
+ @attributes = a
159
+ @mime = nil
160
+ end
161
+ end
162
+
163
+ def saved_content
164
+ @saved_content ||= new? ? '' : repository.get_content(path, tree_version)
165
+ end
166
+
167
+ def content
168
+ @content ||= saved_content
169
+ end
170
+
171
+ def content=(c)
172
+ if content != c
173
+ @mime = nil
174
+ @content = c
175
+ end
176
+ end
177
+
178
+ def modified?
179
+ content != saved_content || attributes != saved_attributes
180
+ end
181
+
182
+ def save
183
+ raise 'Page is not head' unless head?
184
+ raise :already_exists.t(:page => path) if new? && Page.find(path)
185
+ with_hooks(:save) do
186
+ repository.set_content(path, content)
187
+ repository.set_attributes(path, attributes)
188
+ end
189
+ after_commit {|tree_version| update(path, tree_version) }
190
+ end
191
+
192
+ def mime
193
+ @mime ||= detect_mime
194
+ end
195
+
196
+ def children
197
+ @children ||=
198
+ if new?
199
+ []
200
+ else
201
+ repository.get_children(path, tree_version).sort.map do |name|
202
+ Page.new(path/name, tree_version, self)
203
+ end
204
+ end
205
+ end
206
+
207
+ def self.default_mime
208
+ mime = Config['mime'].find {|m| m.include? '/'}
209
+ mime ? MimeMagic.new(mime) : nil
210
+ end
211
+
212
+ private
213
+
214
+ def update(path, tree_version)
215
+ @path = path.freeze
216
+ @tree_version = tree_version
217
+ @version = @next_version = @previous_version =
218
+ @parent = @children = @mime =
219
+ @attributes = @saved_attributes =
220
+ @content = @saved_content = nil
221
+ end
222
+
223
+ def after_commit(&block)
224
+ Page.current_transaction << block
225
+ end
226
+
227
+ def self.check_path(path)
228
+ raise :invalid_path.t if !(path.blank? || path =~ PATH_REGEXP) || !valid_xml_chars?(path)
229
+ end
230
+
231
+ def detect_mime
232
+ return MimeMagic.new(attributes['mime']) if attributes['mime']
233
+ Config['mime'].each do |mime|
234
+ mime = if mime == 'extension'
235
+ MimeMagic.by_extension(extension)
236
+ elsif %w(content magic).include?(mime)
237
+ if !new?
238
+ if content.blank?
239
+ children.empty? ? EMPTY_MIME : DIRECTORY_MIME
240
+ else
241
+ MimeMagic.by_magic(content)
242
+ end
243
+ end
244
+ else
245
+ MimeMagic.new(mime)
246
+ end
247
+ return mime if mime
248
+ end
249
+ end
250
+
251
+ def init_versions
252
+ if !@version && @tree_version
253
+ raise 'Page is new' if new?
254
+ @previous_version, @version, @next_version = repository.get_path_version(path, tree_version)
255
+ end
256
+ end
257
+
258
+ def repository
259
+ Repository.instance
260
+ end
261
+
262
+ def self.repository
263
+ Repository.instance
264
+ end
265
+ end
266
+ end