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.
- data/.gitignore +10 -0
- data/.gitmodules +3 -0
- data/Gemfile +3 -0
- data/README.markdown +104 -0
- data/Rakefile +92 -0
- data/bin/olelo +5 -0
- data/config.ru +86 -0
- data/config/aspects.rb +256 -0
- data/config/config.yml.default +154 -0
- data/config/initializers/00-mime_types.rb +29 -0
- data/config/initializers/01-slim.rb +2 -0
- data/config/interwiki.yml +11 -0
- data/doc/AUTHORS +7 -0
- data/doc/LICENSE +22 -0
- data/lib/olelo.rb +39 -0
- data/lib/olelo/application.rb +294 -0
- data/lib/olelo/attributes.rb +285 -0
- data/lib/olelo/config.rb +88 -0
- data/lib/olelo/extensions.rb +252 -0
- data/lib/olelo/helper.rb +290 -0
- data/lib/olelo/hooks.rb +142 -0
- data/lib/olelo/html_safe.rb +29 -0
- data/lib/olelo/initializer.rb +76 -0
- data/lib/olelo/locale.rb +63 -0
- data/lib/olelo/locale.yml +284 -0
- data/lib/olelo/menu.rb +101 -0
- data/lib/olelo/middleware/blacklist.rb +25 -0
- data/lib/olelo/middleware/degrade_mime_type.rb +19 -0
- data/lib/olelo/middleware/flash.rb +97 -0
- data/lib/olelo/middleware/force_encoding.rb +41 -0
- data/lib/olelo/page.rb +266 -0
- data/lib/olelo/patch.rb +311 -0
- data/lib/olelo/plugin.rb +188 -0
- data/lib/olelo/repository.rb +225 -0
- data/lib/olelo/routing.rb +223 -0
- data/lib/olelo/templates.rb +30 -0
- data/lib/olelo/user.rb +132 -0
- data/lib/olelo/util.rb +233 -0
- data/lib/olelo/version.rb +3 -0
- data/lib/olelo/virtualfs.rb +161 -0
- data/lib/rack/olelo_patches.rb +33 -0
- data/lib/rack/relative_redirect.rb +44 -0
- data/lib/rack/static_cache.rb +93 -0
- data/lib/yard/addons.rb +1 -0
- data/lib/yard/addons/hook_handler.rb +25 -0
- data/lib/yard/addons/override_tag.rb +14 -0
- data/lib/yard/addons/route_handler.rb +33 -0
- data/lib/yard/addons/sanitize_anchor.rb +16 -0
- data/olelo.gemspec +31 -0
- data/plugins/aspects/changelog.rb +45 -0
- data/plugins/aspects/documentbrowser.rb +57 -0
- data/plugins/aspects/download.rb +11 -0
- data/plugins/aspects/highlight.rb +8 -0
- data/plugins/aspects/image.rb +41 -0
- data/plugins/aspects/imageinfo.rb +64 -0
- data/plugins/aspects/locale.yml +60 -0
- data/plugins/aspects/main.rb +199 -0
- data/plugins/aspects/pageinfo.rb +37 -0
- data/plugins/aspects/source.rb +6 -0
- data/plugins/aspects/subpages.rb +44 -0
- data/plugins/aspects/text.rb +9 -0
- data/plugins/blog/blog.css +1 -0
- data/plugins/blog/blog.scss +37 -0
- data/plugins/blog/locale.yml +12 -0
- data/plugins/blog/main.rb +85 -0
- data/plugins/editor/locale.yml +18 -0
- data/plugins/editor/markup/main.rb +3 -0
- data/plugins/editor/markup/script.js +10 -0
- data/plugins/editor/markup/script/00-jquery.textselection.js +267 -0
- data/plugins/editor/markup/script/01-olelo.markupeditor.js +116 -0
- data/plugins/editor/markup/script/init.js +10 -0
- data/plugins/editor/preview.rb +52 -0
- data/plugins/editor/recaptcha.rb +56 -0
- data/plugins/filters/creole.rb +37 -0
- data/plugins/filters/disposition.rb +9 -0
- data/plugins/filters/editsection.rb +67 -0
- data/plugins/filters/fix_img_tag.rb +16 -0
- data/plugins/filters/html_wrapper.rb +12 -0
- data/plugins/filters/interwiki.rb +19 -0
- data/plugins/filters/link_classifier.rb +26 -0
- data/plugins/filters/locale.yml +15 -0
- data/plugins/filters/main.rb +202 -0
- data/plugins/filters/markdown_nowiki.rb +15 -0
- data/plugins/filters/numbering.xsl +93 -0
- data/plugins/filters/orgmode.rb +6 -0
- data/plugins/filters/rubypants.rb +6 -0
- data/plugins/filters/s5/main.rb +32 -0
- data/plugins/filters/s5/s5.xsl +118 -0
- data/plugins/filters/tilt.rb +17 -0
- data/plugins/filters/toc.rb +50 -0
- data/plugins/filters/xhtml2latex.xsl +232 -0
- data/plugins/filters/xslt.rb +22 -0
- data/plugins/gallery/gallery.css +1 -0
- data/plugins/gallery/gallery.scss +28 -0
- data/plugins/gallery/main.rb +34 -0
- data/plugins/misc/fancybox/images/blank.gif +0 -0
- data/plugins/misc/fancybox/images/fancy_close.png +0 -0
- data/plugins/misc/fancybox/images/fancy_loading.png +0 -0
- data/plugins/misc/fancybox/images/fancy_nav_left.png +0 -0
- data/plugins/misc/fancybox/images/fancy_nav_right.png +0 -0
- data/plugins/misc/fancybox/images/fancy_shadow_e.png +0 -0
- data/plugins/misc/fancybox/images/fancy_shadow_n.png +0 -0
- data/plugins/misc/fancybox/images/fancy_shadow_ne.png +0 -0
- data/plugins/misc/fancybox/images/fancy_shadow_nw.png +0 -0
- data/plugins/misc/fancybox/images/fancy_shadow_s.png +0 -0
- data/plugins/misc/fancybox/images/fancy_shadow_se.png +0 -0
- data/plugins/misc/fancybox/images/fancy_shadow_sw.png +0 -0
- data/plugins/misc/fancybox/images/fancy_shadow_w.png +0 -0
- data/plugins/misc/fancybox/images/fancy_title_left.png +0 -0
- data/plugins/misc/fancybox/images/fancy_title_main.png +0 -0
- data/plugins/misc/fancybox/images/fancy_title_over.png +0 -0
- data/plugins/misc/fancybox/images/fancy_title_right.png +0 -0
- data/plugins/misc/fancybox/images/fancybox-x.png +0 -0
- data/plugins/misc/fancybox/images/fancybox-y.png +0 -0
- data/plugins/misc/fancybox/images/fancybox.png +0 -0
- data/plugins/misc/fancybox/jquery.fancybox.css +1 -0
- data/plugins/misc/fancybox/jquery.fancybox.scss +323 -0
- data/plugins/misc/fancybox/main.rb +4 -0
- data/plugins/misc/fancybox/script.js +37 -0
- data/plugins/misc/fancybox/script/00-jquery.mousewheel.js +84 -0
- data/plugins/misc/fancybox/script/01-jquery.easing.js +205 -0
- data/plugins/misc/fancybox/script/02-jquery.fancybox.js +1156 -0
- data/plugins/misc/fancybox/script/init.js +18 -0
- data/plugins/misc/system.rb +192 -0
- data/plugins/misc/variables.rb +29 -0
- data/plugins/misc/webdav.rb +45 -0
- data/plugins/repositories/git_grep.rb +69 -0
- data/plugins/repositories/gitrb_repository.rb +204 -0
- data/plugins/repositories/locale.yml +12 -0
- data/plugins/repositories/rugged_repository.rb +454 -0
- data/plugins/security/acl.rb +57 -0
- data/plugins/security/basic_auth.rb +21 -0
- data/plugins/security/locale.yml +21 -0
- data/plugins/security/persistent_login.rb +32 -0
- data/plugins/security/portal.rb +28 -0
- data/plugins/security/private_wiki.rb +24 -0
- data/plugins/security/readonly_wiki.rb +25 -0
- data/plugins/security/stack.rb +20 -0
- data/plugins/security/yamlfile.rb +66 -0
- data/plugins/tags/code.rb +6 -0
- data/plugins/tags/footnotes.rb +35 -0
- data/plugins/tags/gist-embed.css +123 -0
- data/plugins/tags/gist.rb +13 -0
- data/plugins/tags/html.rb +57 -0
- data/plugins/tags/include.rb +20 -0
- data/plugins/tags/main.rb +353 -0
- data/plugins/tags/math.rb +117 -0
- data/plugins/tags/scripting.rb +64 -0
- data/plugins/tags/sort.rb +7 -0
- data/plugins/tags/tabs.rb +20 -0
- data/plugins/treeview/images/collapsed.png +0 -0
- data/plugins/treeview/images/expanded.png +0 -0
- data/plugins/treeview/images/menu.png +0 -0
- data/plugins/treeview/images/tree.png +0 -0
- data/plugins/treeview/images/wait.gif +0 -0
- data/plugins/treeview/main.rb +16 -0
- data/plugins/treeview/script.js +5 -0
- data/plugins/treeview/script/00-jquery.treeview.js +164 -0
- data/plugins/treeview/script/init.js +25 -0
- data/plugins/treeview/treeview.css +1 -0
- data/plugins/treeview/treeview.scss +113 -0
- data/plugins/utils/assets.rb +74 -0
- data/plugins/utils/cache.rb +53 -0
- data/plugins/utils/image_magick.rb +34 -0
- data/plugins/utils/pygments.css +1 -0
- data/plugins/utils/pygments.rb +50 -0
- data/plugins/utils/pygments.scss +83 -0
- data/plugins/utils/semaphore.rb +50 -0
- data/plugins/utils/shell.rb +45 -0
- data/plugins/utils/store.rb +315 -0
- data/plugins/utils/worker.rb +36 -0
- data/plugins/utils/xml.rb +29 -0
- data/static/images/favicon.png +0 -0
- data/static/script.js +267 -0
- data/static/script/00-json2.js +486 -0
- data/static/script/01-jstorage.js +217 -0
- data/static/script/02-jquery.js +9440 -0
- data/static/script/03-jquery.ui.core.js +337 -0
- data/static/script/04-jquery.ui.widget.js +502 -0
- data/static/script/05-jquery.ui.position.js +517 -0
- data/static/script/06-jquery.ui.menu.js +609 -0
- data/static/script/07-jquery.ui.autocomplete.js +601 -0
- data/static/script/08-olelo.i18n.js +37 -0
- data/static/script/09-olelo.unsaved.js +68 -0
- data/static/script/10-olelo.historytable.js +40 -0
- data/static/script/11-olelo.pagination.js +18 -0
- data/static/script/13-olelo.tabwidget.js +57 -0
- data/static/script/14-olelo.timeago.js +70 -0
- data/static/script/15-olelo.underliner.js +31 -0
- data/static/script/16-olelo.ui.combobox.js +32 -0
- data/static/script/init.js +48 -0
- data/static/themes/atlantis/constants.scss +15 -0
- data/static/themes/atlantis/iehacks.scss +38 -0
- data/static/themes/atlantis/images/actions/delete.png +0 -0
- data/static/themes/atlantis/images/actions/edit.png +0 -0
- data/static/themes/atlantis/images/actions/history.png +0 -0
- data/static/themes/atlantis/images/actions/home.png +0 -0
- data/static/themes/atlantis/images/actions/move.png +0 -0
- data/static/themes/atlantis/images/actions/new.png +0 -0
- data/static/themes/atlantis/images/actions/page.png +0 -0
- data/static/themes/atlantis/images/bg/button.png +0 -0
- data/static/themes/atlantis/images/bg/container.png +0 -0
- data/static/themes/atlantis/images/bg/content.png +0 -0
- data/static/themes/atlantis/images/bg/footer.png +0 -0
- data/static/themes/atlantis/images/bg/header.jpg +0 -0
- data/static/themes/atlantis/images/bg/header.orig.jpg +0 -0
- data/static/themes/atlantis/images/bg/header_gray.jpg +0 -0
- data/static/themes/atlantis/images/bug.png +0 -0
- data/static/themes/atlantis/images/filetypes/7z.png +0 -0
- data/static/themes/atlantis/images/filetypes/_archive.png +0 -0
- data/static/themes/atlantis/images/filetypes/_audio.png +0 -0
- data/static/themes/atlantis/images/filetypes/_code.png +0 -0
- data/static/themes/atlantis/images/filetypes/_linux.png +0 -0
- data/static/themes/atlantis/images/filetypes/_picture.png +0 -0
- data/static/themes/atlantis/images/filetypes/_video.png +0 -0
- data/static/themes/atlantis/images/filetypes/bz2.png +0 -0
- data/static/themes/atlantis/images/filetypes/doc.png +0 -0
- data/static/themes/atlantis/images/filetypes/flac.png +0 -0
- data/static/themes/atlantis/images/filetypes/gz.png +0 -0
- data/static/themes/atlantis/images/filetypes/html.png +0 -0
- data/static/themes/atlantis/images/filetypes/java.png +0 -0
- data/static/themes/atlantis/images/filetypes/jpg.png +0 -0
- data/static/themes/atlantis/images/filetypes/midi.png +0 -0
- data/static/themes/atlantis/images/filetypes/mp3.png +0 -0
- data/static/themes/atlantis/images/filetypes/ogg.png +0 -0
- data/static/themes/atlantis/images/filetypes/pdf.png +0 -0
- data/static/themes/atlantis/images/filetypes/php.png +0 -0
- data/static/themes/atlantis/images/filetypes/png.png +0 -0
- data/static/themes/atlantis/images/filetypes/ppt.png +0 -0
- data/static/themes/atlantis/images/filetypes/psd.png +0 -0
- data/static/themes/atlantis/images/filetypes/rar.png +0 -0
- data/static/themes/atlantis/images/filetypes/rb.png +0 -0
- data/static/themes/atlantis/images/filetypes/sh.png +0 -0
- data/static/themes/atlantis/images/filetypes/tar.png +0 -0
- data/static/themes/atlantis/images/filetypes/txt.png +0 -0
- data/static/themes/atlantis/images/filetypes/wma.png +0 -0
- data/static/themes/atlantis/images/filetypes/xls.png +0 -0
- data/static/themes/atlantis/images/filetypes/zip.png +0 -0
- data/static/themes/atlantis/images/folder.png +0 -0
- data/static/themes/atlantis/images/folder_open.png +0 -0
- data/static/themes/atlantis/images/loading.gif +0 -0
- data/static/themes/atlantis/images/loading.xcf +0 -0
- data/static/themes/atlantis/images/not_found.png +0 -0
- data/static/themes/atlantis/images/page.png +0 -0
- data/static/themes/atlantis/images/search.png +0 -0
- data/static/themes/atlantis/layout.scss +115 -0
- data/static/themes/atlantis/menu.scss +99 -0
- data/static/themes/atlantis/print.scss +129 -0
- data/static/themes/atlantis/screen.scss +495 -0
- data/static/themes/atlantis/style.css +3 -0
- data/static/themes/lib/autocomplete.scss +39 -0
- data/static/themes/lib/headlines.scss +10 -0
- data/static/themes/lib/horizontal-list.scss +31 -0
- data/static/themes/lib/patch.scss +88 -0
- data/static/themes/lib/reset.scss +114 -0
- data/static/themes/lib/rounded.scss +46 -0
- data/static/themes/lib/shadow.scss +14 -0
- data/test/config_test.rb +28 -0
- data/test/factory_test.rb +29 -0
- data/test/hash_extensions_test.rb +16 -0
- data/test/helper.rb +38 -0
- data/test/hooks_test.rb +85 -0
- data/test/object_extensions_test.rb +20 -0
- data/test/page_test.rb +168 -0
- data/test/request_test.rb +166 -0
- data/test/string_extensions_test.rb +32 -0
- data/test/templates_test.rb +39 -0
- data/test/util_test.rb +71 -0
- data/views/changes.slim +22 -0
- data/views/compare.slim +8 -0
- data/views/delete.slim +9 -0
- data/views/deleted.slim +2 -0
- data/views/edit.slim +65 -0
- data/views/error.slim +6 -0
- data/views/history.slim +20 -0
- data/views/layout.slim +38 -0
- data/views/login.slim +37 -0
- data/views/move.slim +10 -0
- data/views/not_found.slim +6 -0
- data/views/profile.slim +26 -0
- data/views/show.slim +9 -0
- 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,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
|