microengine 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|