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.
Files changed (47) hide show
  1. data/LICENSE +674 -0
  2. data/README +102 -0
  3. data/Rakefile +67 -0
  4. data/access.log +1 -0
  5. data/bin/microengine +0 -0
  6. data/content/403/en.body +2 -0
  7. data/content/403/en.header +1 -0
  8. data/content/403/layout +1 -0
  9. data/content/403/ru.body +2 -0
  10. data/content/403/ru.header +1 -0
  11. data/content/404/en.body +5 -0
  12. data/content/404/en.header +1 -0
  13. data/content/404/layout +1 -0
  14. data/content/404/ru.body +5 -0
  15. data/content/404/ru.header +1 -0
  16. data/content/WARNING +2 -0
  17. data/content/en.body +2 -0
  18. data/content/en.header +1 -0
  19. data/content/layout +1 -0
  20. data/content/ru.body +2 -0
  21. data/content/ru.header +1 -0
  22. data/layouts/WARNING +2 -0
  23. data/layouts/default/en.yaml +7 -0
  24. data/layouts/default/layout.rhtml +18 -0
  25. data/layouts/default/ru.yaml +7 -0
  26. data/lib/microengine/admin.rb +268 -0
  27. data/lib/microengine/admin_exception.rb +31 -0
  28. data/lib/microengine/admin_page.rb +199 -0
  29. data/lib/microengine/assambler.rb +192 -0
  30. data/lib/microengine/dispatcher.rb +125 -0
  31. data/lib/microengine/file_cache.rb +62 -0
  32. data/lib/microengine/html/deleter.rhtml +11 -0
  33. data/lib/microengine/html/editor.rhtml +34 -0
  34. data/lib/microengine/html/en.yaml +33 -0
  35. data/lib/microengine/html/error.rhtml +6 -0
  36. data/lib/microengine/html/refresher.rhtml +7 -0
  37. data/lib/microengine/html/ru.yaml +33 -0
  38. data/lib/microengine/html/untranslater.rhtml +11 -0
  39. data/lib/microengine/http.rb +213 -0
  40. data/lib/microengine/initializer.rb +93 -0
  41. data/lib/microengine/memory_cache.rb +53 -0
  42. data/password +20 -0
  43. data/public/microengine.fcgi +5 -0
  44. data/public/styles/handheld.css +0 -0
  45. data/public/styles/print.css +3 -0
  46. data/public/styles/screen.css +18 -0
  47. 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,6 @@
1
+ <h1><%= @translation['error']['title'] %></h1>
2
+ <% if 'untranslated_layout' == @code %>
3
+ <p><%= @translation['error'][@code].sub('%layout', @layout).sub('%lang', @old_lang) %></p>
4
+ <% else %>
5
+ <p><%= @translation['error'][@code] %></p>
6
+ <% end %>
@@ -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