microengine 0.0.1
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/LICENSE +674 -0
- data/README +102 -0
- data/Rakefile +67 -0
- data/access.log +1 -0
- data/bin/microengine +0 -0
- data/content/403/en.body +2 -0
- data/content/403/en.header +1 -0
- data/content/403/layout +1 -0
- data/content/403/ru.body +2 -0
- data/content/403/ru.header +1 -0
- data/content/404/en.body +5 -0
- data/content/404/en.header +1 -0
- data/content/404/layout +1 -0
- data/content/404/ru.body +5 -0
- data/content/404/ru.header +1 -0
- data/content/WARNING +2 -0
- data/content/en.body +2 -0
- data/content/en.header +1 -0
- data/content/layout +1 -0
- data/content/ru.body +2 -0
- data/content/ru.header +1 -0
- data/layouts/WARNING +2 -0
- data/layouts/default/en.yaml +7 -0
- data/layouts/default/layout.rhtml +18 -0
- data/layouts/default/ru.yaml +7 -0
- data/lib/microengine/admin.rb +268 -0
- data/lib/microengine/admin_exception.rb +31 -0
- data/lib/microengine/admin_page.rb +199 -0
- data/lib/microengine/assambler.rb +192 -0
- data/lib/microengine/dispatcher.rb +125 -0
- data/lib/microengine/file_cache.rb +62 -0
- data/lib/microengine/html/deleter.rhtml +11 -0
- data/lib/microengine/html/editor.rhtml +34 -0
- data/lib/microengine/html/en.yaml +33 -0
- data/lib/microengine/html/error.rhtml +6 -0
- data/lib/microengine/html/refresher.rhtml +7 -0
- data/lib/microengine/html/ru.yaml +33 -0
- data/lib/microengine/html/untranslater.rhtml +11 -0
- data/lib/microengine/http.rb +213 -0
- data/lib/microengine/initializer.rb +93 -0
- data/lib/microengine/memory_cache.rb +53 -0
- data/password +20 -0
- data/public/microengine.fcgi +5 -0
- data/public/styles/handheld.css +0 -0
- data/public/styles/print.css +3 -0
- data/public/styles/screen.css +18 -0
- metadata +106 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
=begin
|
2
|
+
Load cached pages directly from files.
|
3
|
+
Copyright (C) 2008 Andrey "A.I." Sitnik <andrey@sitnik.ru>
|
4
|
+
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
6
|
+
it under the terms of the GNU General Public License as published by
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
8
|
+
(at your option) any later version.
|
9
|
+
|
10
|
+
This program is distributed in the hope that it will be useful,
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
GNU General Public License for more details.
|
14
|
+
|
15
|
+
You should have received a copy of the GNU General Public License
|
16
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
=end
|
18
|
+
|
19
|
+
module Microengine
|
20
|
+
# Load cached pages directly from files. MemoryCache is more faster.
|
21
|
+
class FileCache
|
22
|
+
# Available languages
|
23
|
+
attr_reader :langs
|
24
|
+
|
25
|
+
# Refresh cache
|
26
|
+
def refresh
|
27
|
+
@langs = {}
|
28
|
+
cache '/'
|
29
|
+
end
|
30
|
+
|
31
|
+
# Return XHTML page content
|
32
|
+
def get(path, lang)
|
33
|
+
lang.sub!(/[^[:alpha:]_-]/, '')
|
34
|
+
IO.read MICROENGINE_ROOT + '/cache/' + path + 'content.' + lang + '.html'
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
|
39
|
+
# Load to memory neccasary resourses
|
40
|
+
def cache(dir)
|
41
|
+
langs = []
|
42
|
+
path = MICROENGINE_ROOT + '/cache/' + dir
|
43
|
+
Dir.foreach path do |entry|
|
44
|
+
if '.' == entry[0..0]
|
45
|
+
next
|
46
|
+
elsif File.directory? path + entry + '/'
|
47
|
+
cache dir + entry + '/'
|
48
|
+
elsif 'content.' == entry[0..7] and '.html' == entry[-5..-1]
|
49
|
+
langs.push entry[8..-6]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
if not langs.empty?
|
53
|
+
cache_content dir, langs
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Cache directory languages
|
58
|
+
def cache_content(dir, langs)
|
59
|
+
@langs[dir] = langs
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<form id="admin-deleter" method="post" action="/admin/do/delete<%= @path %>">
|
2
|
+
<input type="hidden" name="lang" value="<%= @lang %>" />
|
3
|
+
<div>
|
4
|
+
<% if @show_password %>
|
5
|
+
<%= @translation['password'] %>:
|
6
|
+
<input type="password" name="password" />
|
7
|
+
<% end %>
|
8
|
+
<input type="submit" value="<%= @translation['deleter']['delete'] %>" />
|
9
|
+
</div>
|
10
|
+
</form>
|
11
|
+
<%= @body %>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<form id="admin-editor" method="post" action="/admin/do/save<%= @path %>">
|
2
|
+
<input type="hidden" name="lang" value="<%= @lang %>" />
|
3
|
+
<div>
|
4
|
+
<%= @translation['editor']['layout'] %>:
|
5
|
+
<select name="layout">
|
6
|
+
<% @layouts.each do |layout| %>
|
7
|
+
<% if @layout == layout %>
|
8
|
+
<option selected="selected"><%= layout %></option>
|
9
|
+
<% elsif @untranslated_layouts.include? layout %>
|
10
|
+
<option disabled="disabled">
|
11
|
+
<%= layout %>
|
12
|
+
(<%= @translation['editor']['untranslated'].sub('%langs', @untranslated_layouts[layout].join(', ')) %>)
|
13
|
+
</option>
|
14
|
+
<% else %>
|
15
|
+
<option><%= layout %></option>
|
16
|
+
<% end %>
|
17
|
+
<% end %>
|
18
|
+
</select>
|
19
|
+
</div>
|
20
|
+
<div>
|
21
|
+
<%= @translation['editor']['header'] %>:
|
22
|
+
<textarea name="header" rows="3" style="width: 100%;"><%= @header %></textarea>
|
23
|
+
</div>
|
24
|
+
<div>
|
25
|
+
<textarea name="body" rows="40" style="width: 100%"><%= @body %></textarea>
|
26
|
+
</div>
|
27
|
+
<div style="margin-top: 5px;">
|
28
|
+
<% if @show_password %>
|
29
|
+
<%= @translation['password'] %>:
|
30
|
+
<input type="password" name="password" />
|
31
|
+
<% end %>
|
32
|
+
<input type="submit" value="<%= @translation['editor']['save'] %>" />
|
33
|
+
</div>
|
34
|
+
</form>
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# English global translation
|
2
|
+
|
3
|
+
password: Password
|
4
|
+
|
5
|
+
error:
|
6
|
+
title: Error
|
7
|
+
untranslated_layout: Layout %layout isn't translated to %lang.
|
8
|
+
no_lang: Variable 'lang' with page language wasn't sended.
|
9
|
+
no_body: Variable 'body' with page content wasn't sended.
|
10
|
+
no_header: Variable 'header' with page header wasn't sended.
|
11
|
+
admin_subdir: You can't edit /admin/ subdirs.
|
12
|
+
cant_write: Microengine can't change files. Ensure, that all files in /content/ is chmod 0666.
|
13
|
+
delete_system: This is system page and you can't deleted it.
|
14
|
+
cant_cache: Cache wasn't refreshed. See system log for details.
|
15
|
+
|
16
|
+
editor:
|
17
|
+
title: Edit
|
18
|
+
header: Header
|
19
|
+
layout: Layout
|
20
|
+
untranslated: Untranslated on %langs
|
21
|
+
save: Save
|
22
|
+
|
23
|
+
untranslater:
|
24
|
+
title: Delete translation
|
25
|
+
delete: Delete translation
|
26
|
+
|
27
|
+
deleter:
|
28
|
+
title: Delete page
|
29
|
+
delete: Delete page and all it's translations
|
30
|
+
|
31
|
+
refresher:
|
32
|
+
title: Refresh cache
|
33
|
+
refresh: Refresh page cache
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<form id="admin-refresher" method="post" action="/admin/do/refresh/">
|
2
|
+
<% if @show_password %>
|
3
|
+
<%= @translation['password'] %>:
|
4
|
+
<input type="password" name="password" />
|
5
|
+
<% end %>
|
6
|
+
<input type="submit" value="<%= @translation['refresher']['refresh'] %>" />
|
7
|
+
</form>
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Russian global translation
|
2
|
+
|
3
|
+
password: Пароль
|
4
|
+
|
5
|
+
error:
|
6
|
+
title: Ошибка
|
7
|
+
untranslated_layout: Шаблон %layout не переведён на %lang.
|
8
|
+
no_lang: Не передана переменная lang с языком страницы.
|
9
|
+
no_body: Не передана переменная body с содержимым страницы.
|
10
|
+
no_header: Не передана переменная header с заголовком страницы.
|
11
|
+
admin_subdir: Вы не можете изменять подкаталоги /admin/ .
|
12
|
+
cant_write: Microengine не может изменить файл. Убедитесь, что все файлы в /content/ имеют права доступа 0666.
|
13
|
+
delete_system: Это системная страница и вы не можете её удалить.
|
14
|
+
cant_cache: Кеш не был обновлён. Смотрите детали в системном журнале.
|
15
|
+
|
16
|
+
editor:
|
17
|
+
title: Правка
|
18
|
+
header: Заголовок
|
19
|
+
layout: Шаблон
|
20
|
+
untranslated: Непереведён на %langs
|
21
|
+
save: Сохранить
|
22
|
+
|
23
|
+
untranslater:
|
24
|
+
title: Удаление перевода
|
25
|
+
delete: Удалить перевод
|
26
|
+
|
27
|
+
deleter:
|
28
|
+
title: Удаление страницы
|
29
|
+
delete: Удалить страницу и все её переводы
|
30
|
+
|
31
|
+
refresher:
|
32
|
+
title: Обновление кеша
|
33
|
+
refresh: Обновить кеш
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<form id="admin-untranslater" method="post" action="/admin/do/untranslate<%= @path %>">
|
2
|
+
<input type="hidden" name="lang" value="<%= @lang %>" />
|
3
|
+
<div>
|
4
|
+
<% if @show_password %>
|
5
|
+
<%= @translation['password'] %>:
|
6
|
+
<input type="password" name="password" />
|
7
|
+
<% end %>
|
8
|
+
<input type="submit" value="<%= @translation['untranslater']['delete'] %>" />
|
9
|
+
</div>
|
10
|
+
</form>
|
11
|
+
<%= @body %>
|
@@ -0,0 +1,213 @@
|
|
1
|
+
=begin
|
2
|
+
API to easy work with HTTP.
|
3
|
+
Copyright (C) 2008 Andrey "A.I." Sitnik <andrey@sitnik.ru>
|
4
|
+
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
6
|
+
it under the terms of the GNU General Public License as published by
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
8
|
+
(at your option) any later version.
|
9
|
+
|
10
|
+
This program is distributed in the hope that it will be useful,
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
GNU General Public License for more details.
|
14
|
+
|
15
|
+
You should have received a copy of the GNU General Public License
|
16
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
=end
|
18
|
+
|
19
|
+
require 'cgi'
|
20
|
+
|
21
|
+
module Microengine
|
22
|
+
# Basic operation with HTTP request
|
23
|
+
class HTTP
|
24
|
+
attr_reader :env
|
25
|
+
attr_reader :url
|
26
|
+
attr_reader :post
|
27
|
+
attr_reader :langs
|
28
|
+
attr_reader :agent
|
29
|
+
attr_reader :ip
|
30
|
+
attr_reader :cookie
|
31
|
+
attr_reader :get
|
32
|
+
attr_accessor :status
|
33
|
+
attr_accessor :content
|
34
|
+
|
35
|
+
def initialize(request)
|
36
|
+
@out = request.out
|
37
|
+
@env = request.env
|
38
|
+
@agent = request.env['HTTP_USER_AGENT']
|
39
|
+
@ip = request.env['REMOTE_ADDR']
|
40
|
+
@langs = format_langs request.env['HTTP_ACCEPT_LANGUAGE']
|
41
|
+
@url = normalize_url format_url(request.env['REQUEST_URI'])
|
42
|
+
@get = format_get request.env['QUERY_STRING']
|
43
|
+
@cookie = format_cookie request.env['HTTP_COOKIE']
|
44
|
+
@headers = []
|
45
|
+
@status = '200 OK'
|
46
|
+
@content = 'text/html'
|
47
|
+
@request = request
|
48
|
+
end
|
49
|
+
|
50
|
+
def post
|
51
|
+
if @post.nil?
|
52
|
+
@post = format_post @request.in
|
53
|
+
end
|
54
|
+
@post
|
55
|
+
end
|
56
|
+
|
57
|
+
# Is URL start with string
|
58
|
+
def url_start?(string)
|
59
|
+
@url[0...string.length] == string
|
60
|
+
end
|
61
|
+
|
62
|
+
# Is HTTP headers already sended to user
|
63
|
+
def header_sended?
|
64
|
+
@header_sended
|
65
|
+
end
|
66
|
+
|
67
|
+
# Send content to user
|
68
|
+
def << (content)
|
69
|
+
if not header_sended?
|
70
|
+
send_headers
|
71
|
+
end
|
72
|
+
@out.print content
|
73
|
+
end
|
74
|
+
|
75
|
+
# Redirect to another URL
|
76
|
+
def redirect(url)
|
77
|
+
@status = '301 Moved Permanently'
|
78
|
+
if '/' == url[0]
|
79
|
+
url = 'http://' + @env['HTTP_HOST'] + url
|
80
|
+
end
|
81
|
+
header 'Location', url
|
82
|
+
send_headers
|
83
|
+
finish
|
84
|
+
end
|
85
|
+
|
86
|
+
# Set header
|
87
|
+
def header(name, value)
|
88
|
+
if header_sended?
|
89
|
+
Initializer.logger.warning 'Header already sended'
|
90
|
+
end
|
91
|
+
@headers << [name, value]
|
92
|
+
end
|
93
|
+
|
94
|
+
# Set cookie
|
95
|
+
def set_cookie(name, value, days=nil)
|
96
|
+
cookie = name + '=' + value + '; path=/'
|
97
|
+
if not days.nil?
|
98
|
+
time = Time.now + (days * 86400)
|
99
|
+
cookie += '; expires=' + time.gmtime.strftime('%a %d-%b-%Y %H:%M:S %Z')
|
100
|
+
end
|
101
|
+
header 'Set-Cookie', cookie
|
102
|
+
@cookie[name] = value
|
103
|
+
end
|
104
|
+
|
105
|
+
# Finish HTTP request
|
106
|
+
def finish
|
107
|
+
@request.finish
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
# Format accept languages from string to array
|
113
|
+
def format_langs(langs)
|
114
|
+
if langs.nil? or '' == langs
|
115
|
+
return []
|
116
|
+
end
|
117
|
+
langs = langs.split(',')
|
118
|
+
langs.map! do |lang|
|
119
|
+
index = lang.index(';')
|
120
|
+
if not index.nil?
|
121
|
+
lang[0...index]
|
122
|
+
else
|
123
|
+
lang
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Format GET variables in URI as array
|
129
|
+
def format_get(query)
|
130
|
+
if query.empty?
|
131
|
+
return {}
|
132
|
+
end
|
133
|
+
|
134
|
+
vars = {}
|
135
|
+
query = query.split('&').each do |var|
|
136
|
+
var = var.split('=')
|
137
|
+
if 2 == var.length
|
138
|
+
vars[var[0]] = var[1]
|
139
|
+
else
|
140
|
+
vars[var[0]] = true
|
141
|
+
end
|
142
|
+
end
|
143
|
+
vars
|
144
|
+
end
|
145
|
+
|
146
|
+
# Remove GET variables from URI
|
147
|
+
def format_url(url)
|
148
|
+
index = url.index('?')
|
149
|
+
if index.nil?
|
150
|
+
url
|
151
|
+
else
|
152
|
+
url[0...index]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# Format cookies as array
|
157
|
+
def format_cookie(string)
|
158
|
+
if string.nil? or string.empty?
|
159
|
+
return {}
|
160
|
+
end
|
161
|
+
|
162
|
+
cookies = {}
|
163
|
+
string = string.split(';')
|
164
|
+
string.each do |item|
|
165
|
+
index = item.index('=')
|
166
|
+
cookies[item[0...index].strip] = item[index+1..-1].strip
|
167
|
+
end
|
168
|
+
cookies
|
169
|
+
end
|
170
|
+
|
171
|
+
# Format POST variables
|
172
|
+
def format_post(input)
|
173
|
+
if 'application/x-www-form-urlencoded' != @env['CONTENT_TYPE']
|
174
|
+
return {}
|
175
|
+
end
|
176
|
+
|
177
|
+
post = input.read
|
178
|
+
if post.nil?
|
179
|
+
return {}
|
180
|
+
end
|
181
|
+
|
182
|
+
vars = {}
|
183
|
+
post.split('&').each do |var|
|
184
|
+
var = var.split '='
|
185
|
+
if var[1].nil?
|
186
|
+
var[1] = ''
|
187
|
+
end
|
188
|
+
vars[var[0]] = CGI.unescape(var[1].nil? ? '' : var[1])
|
189
|
+
end
|
190
|
+
|
191
|
+
vars
|
192
|
+
end
|
193
|
+
|
194
|
+
# Send HTTP headers
|
195
|
+
def send_headers
|
196
|
+
@headers.each do |header|
|
197
|
+
@out.print header.join(': ') + "\r\n"
|
198
|
+
end
|
199
|
+
@out.print "Content-type: #{@content}\r\n"
|
200
|
+
@out.print "Status: #{@status}\r\n"
|
201
|
+
@out.print "\r\n"
|
202
|
+
@header_sended = true
|
203
|
+
end
|
204
|
+
|
205
|
+
# Security check and normalizing URL
|
206
|
+
def normalize_url(url)
|
207
|
+
if '/' != url[-1..-1]
|
208
|
+
url += '/'
|
209
|
+
end
|
210
|
+
url.sub(/\/\.?\.\//, '/')
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
=begin
|
2
|
+
Initialize system constants, load modules and start Microengine.
|
3
|
+
Copyright (C) 2008 Andrey "A.I." Sitnik <andrey@sitnik.ru>
|
4
|
+
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
6
|
+
it under the terms of the GNU General Public License as published by
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
8
|
+
(at your option) any later version.
|
9
|
+
|
10
|
+
This program is distributed in the hope that it will be useful,
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
GNU General Public License for more details.
|
14
|
+
|
15
|
+
You should have received a copy of the GNU General Public License
|
16
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
=end
|
18
|
+
|
19
|
+
require 'logger'
|
20
|
+
require 'yaml'
|
21
|
+
require 'dispatcher'
|
22
|
+
require 'assambler'
|
23
|
+
require 'file_cache'
|
24
|
+
require 'memory_cache'
|
25
|
+
|
26
|
+
module Microengine
|
27
|
+
# Load configs, create pages cache if necessary
|
28
|
+
class Initializer
|
29
|
+
|
30
|
+
# Initialize microengine
|
31
|
+
def initialize
|
32
|
+
logger = Logger.new(STDERR)
|
33
|
+
logger.level = Logger::WARN
|
34
|
+
config = load_config
|
35
|
+
|
36
|
+
admin = Admin.new
|
37
|
+
dispatcher = Dispatcher.new
|
38
|
+
assambler = Assambler.new
|
39
|
+
if 'memory' == config['cache']
|
40
|
+
cache = MemoryCache.new
|
41
|
+
elsif 'file' == config['cache']
|
42
|
+
cache = FileCache.new
|
43
|
+
else
|
44
|
+
logger.warn "Unknown value '#{config['cache']}' for 'cache' in config/settings.yaml . Will be used file cache."
|
45
|
+
cache = MemoryCache.new
|
46
|
+
end
|
47
|
+
|
48
|
+
[admin, dispatcher, assambler].each do |object|
|
49
|
+
object.logger = logger
|
50
|
+
object.config = config
|
51
|
+
end
|
52
|
+
[admin, dispatcher].each do |object|
|
53
|
+
object.cache = cache
|
54
|
+
end
|
55
|
+
admin.shadow = load_shadow
|
56
|
+
admin.dispatcher = dispatcher
|
57
|
+
admin.assambler = assambler
|
58
|
+
dispatcher.spiders = load_seach_spiders
|
59
|
+
dispatcher.admin = admin
|
60
|
+
|
61
|
+
begin
|
62
|
+
assambler.refresh
|
63
|
+
cache.refresh
|
64
|
+
rescue Exception => e
|
65
|
+
logger.error "Cache was not created. " + e.message + "\n" + e.backtrace.join("\n")
|
66
|
+
end
|
67
|
+
dispatcher.run
|
68
|
+
rescue Exception => e
|
69
|
+
logger.error(e)
|
70
|
+
end
|
71
|
+
|
72
|
+
# Load settings
|
73
|
+
def load_config
|
74
|
+
YAML::load_file(MICROENGINE_ROOT + '/config/settings.yaml')
|
75
|
+
rescue
|
76
|
+
raise "Can't open settings file. Please ensure that config/settings.yaml exists and can be read by engine."
|
77
|
+
end
|
78
|
+
|
79
|
+
# Load admin password
|
80
|
+
def load_shadow
|
81
|
+
YAML::load_file(MICROENGINE_ROOT + '/config/shadow.yaml')
|
82
|
+
rescue
|
83
|
+
raise "Can't open shadow file. Please ensure that config/shadow.yaml exists and can be read by engine."
|
84
|
+
end
|
85
|
+
|
86
|
+
# Load search spider list
|
87
|
+
def load_seach_spiders
|
88
|
+
IO.readlines(MICROENGINE_ROOT + '/config/search spiders')
|
89
|
+
rescue
|
90
|
+
raise "Can't open spiders file. Please ensure that '/config/search spiders' exists and can be read by engine."
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|