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
data/lib/olelo/user.rb ADDED
@@ -0,0 +1,132 @@
1
+ module Olelo
2
+ class AuthenticationError < RuntimeError
3
+ def status
4
+ :forbidden
5
+ end
6
+ end
7
+
8
+ class AccessDenied < RuntimeError
9
+ def initialize
10
+ super(:access_denied.t)
11
+ end
12
+
13
+ def status
14
+ :forbidden
15
+ end
16
+ end
17
+
18
+ class User
19
+ include Util
20
+ attr_reader :name, :groups
21
+ attr_accessor :email
22
+
23
+ def initialize(name, email, groups = nil)
24
+ @name, @email, @groups = name, email, Set.new(groups.to_a)
25
+ end
26
+
27
+ def change_password(oldpassword, password, confirm)
28
+ User.validate_password(password, confirm)
29
+ User.service.change_password(self, oldpassword, password)
30
+ end
31
+
32
+ def modify(&block)
33
+ copy = dup
34
+ block.call(copy)
35
+ validate
36
+ User.service.update(copy)
37
+ instance_variables.each do |name|
38
+ instance_variable_set(name, copy.instance_variable_get(name))
39
+ end
40
+ end
41
+
42
+ def validate
43
+ check do |errors|
44
+ errors << :invalid_email.t if email !~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
45
+ errors << :invalid_name.t if name !~ /[\w\.\-\+_]+/
46
+ end
47
+ end
48
+
49
+ class Service
50
+ include Util
51
+ extend Factory
52
+
53
+ class NotSupportedError < AuthenticationError
54
+ def initialize(name)
55
+ super(:auth_unsupported.t(:name => name))
56
+ end
57
+ end
58
+
59
+ def find(name)
60
+ raise NotImplementedError
61
+ end
62
+
63
+ def authenticate(name, password)
64
+ raise NotImplementedError
65
+ end
66
+
67
+ def create(user, password)
68
+ raise NotSupportedError('create')
69
+ end
70
+
71
+ def update(user)
72
+ raise NotSupportedError('update')
73
+ end
74
+
75
+ def change_password(user, oldpassword, password)
76
+ raise NotSupportedError('change_password')
77
+ end
78
+ end
79
+
80
+ class<< self
81
+ def current
82
+ Thread.current[:olelo_user]
83
+ end
84
+
85
+ def current=(user)
86
+ Thread.current[:olelo_user] = user
87
+ end
88
+
89
+ def logged_in?
90
+ current && current.groups.include?('user')
91
+ end
92
+
93
+ def service
94
+ @service ||= Service[Config['authentication.service']].new(Config['authentication'][Config['authentication.service']])
95
+ end
96
+
97
+ def validate_password(password, confirm)
98
+ check do |errors|
99
+ errors << :passwords_do_not_match.t if password != confirm
100
+ errors << :empty_password.t if password.blank?
101
+ end
102
+ end
103
+
104
+ def anonymous(request)
105
+ ip = request.ip || 'unknown-ip'
106
+ name = request.remote_host ? "#{request.remote_host} (#{ip})" : ip
107
+ new(name, "anonymous@#{ip}")
108
+ end
109
+
110
+ def find!(name)
111
+ service.find(name)
112
+ end
113
+
114
+ def find(name)
115
+ find!(name) rescue nil
116
+ end
117
+
118
+ def authenticate(name, password)
119
+ service.authenticate(name, password).tap {|user| user.groups << 'user' }
120
+ end
121
+
122
+ def create(name, password, confirm, email)
123
+ validate_password(password, confirm)
124
+ user = new(name, email, %w(user))
125
+ user.validate
126
+ service.create(user, password)
127
+ user
128
+ end
129
+ end
130
+
131
+ end
132
+ end
data/lib/olelo/util.rb ADDED
@@ -0,0 +1,233 @@
1
+ module Olelo
2
+ class NotFound < NameError
3
+ def initialize(id)
4
+ super(:not_found.t(:id => id), id)
5
+ end
6
+
7
+ def status
8
+ :not_found
9
+ end
10
+ end
11
+
12
+ class MultiError < StandardError
13
+ attr_accessor :messages
14
+
15
+ def initialize(messages)
16
+ @messages = messages
17
+ end
18
+
19
+ def message
20
+ @messages.join(', ')
21
+ end
22
+ end
23
+
24
+ module Factory
25
+ def registry
26
+ @registry ||= superclass.try(:registry) || {}
27
+ end
28
+
29
+ def register(name, klass)
30
+ name = name.to_s
31
+ raise(ArgumentError, "Implementation '#{name}' already exists for '#{self.name}'") if registry.key?(name)
32
+ registry[name] = klass
33
+ end
34
+
35
+ def [](name)
36
+ registry[name.to_s] || raise(NameError, "Implementation '#{name}' for '#{self.name}' not found")
37
+ end
38
+ end
39
+
40
+ class BlockFile < ::File
41
+ alias to_path path
42
+
43
+ def each
44
+ rewind
45
+ while part = read(8192)
46
+ yield part
47
+ end
48
+ end
49
+ end
50
+
51
+ module Util
52
+ def self.included(base)
53
+ base.extend(Util)
54
+ end
55
+
56
+ extend self
57
+
58
+ def check
59
+ errors = []
60
+ yield(errors)
61
+ raise MultiError, errors if !errors.empty?
62
+ end
63
+
64
+ # Like CGI.escape but escapes space not as +
65
+ def escape(s)
66
+ s = s.to_s
67
+ s.gsub(/([^a-zA-Z0-9_.-]+)/) do
68
+ '%' + $1.unpack('H2' * $1.bytesize).join('%').upcase
69
+ end
70
+ end
71
+
72
+ # Like CGI.unescape but does not unescape +
73
+ def unescape(s)
74
+ s = s.to_s
75
+ enc = s.encoding
76
+ s.gsub(/((?:%[0-9a-fA-F]{2})+)/) do
77
+ [$1.delete('%')].pack('H*').force_encoding(enc)
78
+ end
79
+ end
80
+
81
+ def unescape_backslash(s)
82
+ s = s.to_s
83
+ enc = s.encoding
84
+ s.gsub(/\\([0-7]{3})/) { $1.to_i(8).chr.force_encoding(enc) }.
85
+ gsub(/\\x([\da-f]{2})/i) { $1.to_i(16).chr.force_encoding(enc) }
86
+ end
87
+
88
+ # Escape html entities in string
89
+ #
90
+ # @param [String] s String
91
+ # @return [String] Escaped string
92
+ def escape_html(s)
93
+ CGI.escapeHTML(s.to_s)
94
+ end
95
+
96
+ # Unescape html entities in string
97
+ #
98
+ # @param [String] s String with escaped html entities
99
+ # @return [String] Unescaped string
100
+ def unescape_html(s)
101
+ CGI.unescapeHTML(s.to_s)
102
+ end
103
+
104
+ # Hash used by {#escape_javascript}
105
+ # @api private
106
+ JAVASCRIPT_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' }
107
+
108
+ # Escape javascript code for embedding in html
109
+ #
110
+ # @param [String] s String
111
+ # @return [String] Escaped string
112
+ def escape_javascript(s)
113
+ s.to_s.gsub(/[&><]/) { |x| JAVASCRIPT_ESCAPE[x] }
114
+ end
115
+
116
+ # Compute md5 hash of string
117
+ #
118
+ # @param [String] String
119
+ # @return [String] md5 hash of string
120
+ #
121
+ def md5(s)
122
+ s = Digest::MD5.hexdigest(s)
123
+ s.force_encoding(Encoding::ASCII)
124
+ s
125
+ end
126
+
127
+ # Compute sha256 hash of string
128
+ #
129
+ # @param [String] String
130
+ # @return [String] sha256 hash of string
131
+ #
132
+ def sha256(s)
133
+ s = Digest::SHA256.hexdigest(s)
134
+ s.force_encoding(Encoding::ASCII)
135
+ s
136
+ end
137
+
138
+ # Build query from parameter hash
139
+ #
140
+ # Similar to {Rack::Utils#build_query} but sorts parameters.
141
+ # This allows the query string to be used as key for caching etc.
142
+ #
143
+ # @param [Hash] params Parameter hash
144
+ # @return [String] query string
145
+ #
146
+ def build_query(params)
147
+ params.map {|k,v| [k.to_s, v ] }.sort.map do |k, v|
148
+ if v.class == Array
149
+ build_query(v.map { |x| [k, x] })
150
+ else
151
+ "#{escape(k)}=#{escape(v.to_s)}"
152
+ end
153
+ end.join('&')
154
+ end
155
+
156
+ # Creates deep copy of object by abusing `Marshal`
157
+ # This method is slow and not adequate for huge objects.
158
+ # It can only copy objects which are serializable.
159
+ #
160
+ # @param [Object] Serializable object
161
+ # @return [Object] Deep copy of object
162
+ def deep_copy(object)
163
+ Marshal.load(Marshal.dump(object))
164
+ end
165
+
166
+ # Decode base64 encoded string
167
+ #
168
+ # @param [String] Base64 encoded string
169
+ # @return [String] Decoded string
170
+ def decode64(s)
171
+ s.unpack('m').first
172
+ end
173
+
174
+ # Encode string as base64
175
+ #
176
+ # @param [String] String
177
+ # @return [String] Base64 encoded string
178
+ def encode64(s)
179
+ [s].pack('m').gsub(/\n/, '')
180
+ end
181
+
182
+ # Truncate string if it is too long and add omission
183
+ #
184
+ # @param [String] String to truncate
185
+ # @param [Integer] Maximum length
186
+ # @param [String] Omission string, e.g. ellipsis
187
+ # @return [String] Truncated string
188
+ def truncate(s, max, omission = '...')
189
+ s = s.to_s
190
+ if s.length > max
191
+ s[0...max] + omission
192
+ else
193
+ s
194
+ end
195
+ end
196
+
197
+ # Capitalizes all the words to create a nicer looking title
198
+ #
199
+ # @param [String] lowercase_string_with_underscore
200
+ # @return [String] Lowercase String With Underscore
201
+ def titlecase(s)
202
+ s.to_s.tr('_', ' ').split(/\s+/).map(&:capitalize).join(' ')
203
+ end
204
+
205
+ # Used by {#valid_xml_chars?}
206
+ # @api private
207
+ VALID_XML_CHARS = [
208
+ 0x9, 0xA, 0xD,
209
+ (0x20..0xD7FF),
210
+ (0xE000..0xFFFD),
211
+ (0x10000..0x10FFFF)
212
+ ]
213
+
214
+ # Check if string contains only characters which are valid in XML
215
+ #
216
+ # @see http://www.w3.org/TR/REC-xml/#charsets XML charset
217
+ # @param [String] s
218
+ # @return [Boolean]
219
+ def valid_xml_chars?(s)
220
+ s = s.to_s
221
+ if s.encoding == Encoding::UTF_8
222
+ return false if !s.valid_encoding?
223
+ else
224
+ s = s.dup if s.frozen?
225
+ return false if s.try_encoding(Encoding::UTF_8).encoding != Encoding::UTF_8
226
+ end
227
+ s.codepoints do |n|
228
+ return false if !VALID_XML_CHARS.any? {|v| v === n }
229
+ end
230
+ true
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,3 @@
1
+ module Olelo
2
+ VERSION = '0.9.0'
3
+ end
@@ -0,0 +1,161 @@
1
+ module Olelo
2
+ class VirtualFS
3
+ def read(name)
4
+ raise NotImplementedError
5
+ end
6
+
7
+ def glob(*names)
8
+ raise NotImplementedError
9
+ end
10
+
11
+ def mtime(name)
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def open(name)
16
+ [read(name)]
17
+ end
18
+
19
+ def size(name)
20
+ read(name).bytesize
21
+ end
22
+
23
+ def mime(name)
24
+ MimeMagic.by_path(name) || MimeMagic.new('application/octet-stream')
25
+ end
26
+
27
+ class VirtualFile
28
+ attr_reader :fs, :name
29
+
30
+ def initialize(fs, name)
31
+ @fs, @name = fs, name
32
+ end
33
+
34
+ def read
35
+ fs.read(name)
36
+ end
37
+
38
+ def open
39
+ fs.open(name)
40
+ end
41
+
42
+ def mtime
43
+ @mtime ||= fs.mtime(name)
44
+ end
45
+
46
+ def size
47
+ @size ||= fs.size(name)
48
+ end
49
+
50
+ def mime
51
+ @fs.mime(name)
52
+ end
53
+ end
54
+
55
+ class Native < VirtualFS
56
+ def initialize(dir)
57
+ @dir = dir
58
+ end
59
+
60
+ # @override
61
+ def read(name)
62
+ File.read(File.join(@dir, name))
63
+ end
64
+
65
+ # @override
66
+ def glob(*names)
67
+ names.map do |name|
68
+ Dir[File.join(@dir, name)].select {|f| File.file?(f) }
69
+ end.flatten.each do |f|
70
+ yield(VirtualFile.new(self, f[@dir.length+1..-1]))
71
+ end
72
+ end
73
+
74
+ # @override
75
+ def open(name)
76
+ BlockFile.open(File.join(@dir, name), 'rb')
77
+ end
78
+
79
+ # @override
80
+ def mtime(name)
81
+ File.mtime(File.join(@dir, name))
82
+ end
83
+
84
+ # @override
85
+ def size(name)
86
+ File.stat(File.join(@dir, name)).size
87
+ end
88
+ end
89
+
90
+ class Embedded < VirtualFS
91
+ def initialize(file)
92
+ @file = file
93
+ @cache = {}
94
+ end
95
+
96
+ # @override
97
+ def read(name)
98
+ @cache[name] ||=
99
+ begin
100
+ code, data = File.read(@file).split('__END__')
101
+ content = nil
102
+ data.to_s.each_line do |line|
103
+ if line =~ /^@@\s*([^\s]+)\s*/
104
+ if name == $1
105
+ content = ''
106
+ elsif content
107
+ break
108
+ end
109
+ elsif content
110
+ content << line
111
+ end
112
+ end
113
+ content || raise(IOError, "#{name} not found")
114
+ end
115
+ end
116
+
117
+ # @override
118
+ def glob(*names)
119
+ code, data = File.read(@file).split('__END__')
120
+ data.to_s.each_line do |line|
121
+ if line =~ /^@@\s*([^\s]+)\s*/ && names.any? {|pattern| File.fnmatch(pattern, $1) }
122
+ yield(VirtualFile.new(self, $1))
123
+ end
124
+ end
125
+ end
126
+
127
+ # @override
128
+ def mtime(name)
129
+ File.mtime(@file)
130
+ end
131
+ end
132
+
133
+ class Union < VirtualFS
134
+ def initialize(*fs)
135
+ @fs = fs.compact
136
+ end
137
+
138
+ # @override
139
+ def glob(*names, &block)
140
+ @fs.each {|fs| fs.glob(*names, &block) }
141
+ end
142
+
143
+ %w(read mtime open size mime).each do |method|
144
+ class_eval %{
145
+ def #{method}(*args)
146
+ result = nil
147
+ @fs.any? do |fs|
148
+ begin
149
+ result = fs.#{method}(*args)
150
+ rescue
151
+ end
152
+ end || raise(IOError, "#{method}(\#{args.map(&:inspect).join(', ')}) failed")
153
+ result
154
+ end
155
+ }
156
+ end
157
+
158
+ end
159
+
160
+ end
161
+ end