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
data/README ADDED
@@ -0,0 +1,102 @@
1
+ = MicroEngine - fast and minimalistic site engine
2
+
3
+ MicroEngine is a fast, simple and minimalistic site engine. It be created for
4
+ simple sites, which only share information (maybe in some languages) and
5
+ has web interface to edit, create and delete pages.
6
+
7
+ MicroEngine has a good support for i18n. User languages will be detected
8
+ automatically. Search engines will be storage page on all languages.
9
+
10
+ MicroEngine didn't use database. Information is storage in files and may be
11
+ cached in memory on engine loading.
12
+
13
+ It's ideal for simple homepages.
14
+
15
+ == Install
16
+
17
+ === Depends
18
+ MicroEngine used only FastCGI, so your web server must be support it.
19
+ Apache must has mod_rewrite and mod_fastcgi modules.
20
+
21
+ === Gems
22
+
23
+ # gem install microengine
24
+ $ cd /SITE/DIR/
25
+ $ microengine ./
26
+ $ ./password YOUR_PASSWORD
27
+
28
+ === All-include package
29
+ It is good choise for hosting where you hasn't root password or access to gem.
30
+ Just download archive with all libraries and unpack it to neccesary dir.
31
+
32
+ $ tar -xf microengine-*.tar.bz2
33
+ $ mv microengine-* /SITE/DIR/
34
+ $ cd /SITE/DIR/
35
+ $ ./password YOUR_PASSWORD
36
+
37
+ === Apache example config
38
+ After install MicroEngine files you must add this information to Apache config
39
+ file. Note, if .htaccess isn't support, you must copy content of
40
+ /PATH/TO/MICROENGINE/ROOT/public/.htaccess in <VirtualHost *> tag.
41
+
42
+ NameVirtualHost *
43
+ <VirtualHost *>
44
+ ServerName YOUR_SITE.COM
45
+ DocumentRoot /PATH/TO/MICROENGINE/ROOT/public/
46
+ </VirtualHost>
47
+
48
+ === Security
49
+ Note that anyone can catch you admin password in Internet. So change http://
50
+ to https:// in admin links to use SSL (if you web server support it).
51
+
52
+ == Layouts
53
+ You must always open www.YOUR_SITE.COM/admin/refresh/ after any
54
+ changing of layout to refresh cache.
55
+
56
+ Layout contain design and other HTML code, which was repeated in pages.
57
+ Page contain header and body section, which will be put in layout.
58
+
59
+ Layout is saved in /layout/LAYOUT_NAME/ dir. This dir must contain layout.rhtml
60
+ file and YAML files for translations with name like LANG.yaml (LANG is a
61
+ language code). All translation files must contain language.code and
62
+ language.title values.
63
+
64
+ layout.rhtml is a simple ERB file with HTML. You may use Ruby code in <% and %>
65
+ tags. To insert some variable value use <%= variable %> .
66
+ See http://en.wikipedia.org/wiki/ERuby for details.
67
+
68
+ There are some predefined variables in ERB:
69
+ * header - Will be contain page header. Must be located in <head> tag.
70
+ * body - Will be contain page body. Must be located in <body> tag.
71
+ * languages - Will be contain HTML list with available page translations.
72
+ * translation - Hash with layout translation form YAML files in layout dir.
73
+ * path - path to current page.
74
+
75
+ == Usage
76
+ === Web interface
77
+ To edit or create page open URL www.YOUR_SITE.COM/admin/edit/PATH .
78
+ You must change PATH to path for you page. For example, if you want to create
79
+ page www.example.com/films/best/ open www.example.com/admin/edit/films/best/
80
+
81
+ To delete translation open www.YOUR_SITE.COM/admin/untranslate/PATH?lang=LANG .
82
+ LANG must be changed to language code of deleting translation. For example,
83
+ if you want to delete French translation of www.example.com/resume/ page
84
+ input www.example.com/admin/untranslate/resume/?lang=fr
85
+
86
+ To delete page (and all translations) open www.YOUR_SITE.COM/admin/delete/PATH .
87
+
88
+ === Manually edit
89
+ You must always open www.YOUR_SITE.COM/admin/refresh/ after any file changing to
90
+ refresh cache.
91
+
92
+ You can also edit site manually. Site content saved in /content/ dir.
93
+ 'layout' file contain layout name. There are files LANG.body and LANG.header
94
+ (LANG is a language code) for all translations exists with body and header,
95
+ respectively.
96
+
97
+ == License
98
+ Microengine is licensed under the GNU General Public License version 3.
99
+ You can read it in LICENSE file or in http://www.gnu.org/licenses/gpl.html .
100
+
101
+ == Author
102
+ Andrey "A.I." Sitnik <andrey@sitnik.ru>
data/Rakefile ADDED
@@ -0,0 +1,67 @@
1
+ require 'rake'
2
+ require 'rake/rdoctask'
3
+ require 'rake/gempackagetask'
4
+ require 'rake/contrib/rubyforgepublisher'
5
+
6
+ PKG_NAME = "microengine"
7
+ PKG_VERSION = "0.0.1"
8
+
9
+ RUBY_FORGE_PROJECT = "microengine"
10
+ RUBY_FORGE_USER = "iskin"
11
+
12
+ PKG_FILES = FileList[
13
+ "bin/*",
14
+ "content/**/*",
15
+ "layouts/**/*",
16
+ "lib/microengine/**/*",
17
+ "public/**/*",
18
+ "access.log",
19
+ "LICENSE",
20
+ "password",
21
+ "Rakefile",
22
+ "README"]
23
+
24
+ # RDoc
25
+ Rake::RDocTask.new do |rd|
26
+ rd.main = "README"
27
+ rd.rdoc_files.include("README", "lib/microengine/*.rb")
28
+ rd.title = "MicroEngine"
29
+ rd.options << "--all"
30
+ end
31
+
32
+ # Gem
33
+ spec = Gem::Specification.new do |s|
34
+ s.platform = Gem::Platform::RUBY
35
+ s.name = PKG_NAME
36
+ s.version = PKG_VERSION
37
+ s.summary = "MicroEngine is a fast, simple and minimalistic site engine."
38
+ s.description = <<-EOF
39
+ MicroEngine is a fast, simple and minimalistic site engine.
40
+ It contain good i18n support and web interface to edit, create and delete pages.
41
+ It be created for simple sites, which only share information
42
+ (maybe in some languages) like homepages.
43
+ EOF
44
+
45
+ s.add_dependency 'fastcgi'
46
+
47
+ s.files = PKG_FILES
48
+ s.require_path = 'lib'
49
+ s.bindir = "bin"
50
+ s.executables = ["microengine"]
51
+ s.default_executable = "microengine"
52
+
53
+ s.author = 'Andrey "A.I." Sitnik'
54
+ s.email = "andrey@sitnik.ru"
55
+ s.homepage = "http://microengine.rubyforge.org"
56
+ s.rubyforge_project = RUBY_FORGE_PROJECT
57
+ end
58
+
59
+ Rake::GemPackageTask.new(spec) do |pkg|
60
+ pkg.gem_spec = spec
61
+ end
62
+
63
+ # All-include package
64
+ Rake::PackageTask.new(PKG_NAME, PKG_VERSION) do |pkg|
65
+ pkg.need_tar_bz2 = true
66
+ pkg.package_files = PKG_FILES
67
+ end
data/access.log ADDED
@@ -0,0 +1 @@
1
+ E, [2008-01-26T20:17:16.989961 #10063] ERROR -- : /home/ai/Разработка/microengine/public/../lib/../access.log
data/bin/microengine ADDED
File without changes
@@ -0,0 +1,2 @@
1
+ <h1>Access denied</h1>
2
+ <p>Wrong password. Wait 5 seconds, <a href="javascript:history.back()">go back</a> and repeat it.</p>
@@ -0,0 +1 @@
1
+ <title>403 - Access denied</title>
@@ -0,0 +1 @@
1
+ default
@@ -0,0 +1,2 @@
1
+ <h1>Доступ запрещён</h1>
2
+ <p>Пароль неправильный. Подождите 5 секунд, <a href="javascript:history.back()">вернитесь</a> и повторите его.</p>
@@ -0,0 +1 @@
1
+ <title>403 - Доступ запрещён</title>
@@ -0,0 +1,5 @@
1
+ <h1>This page isn't exist</h1>
2
+ <ul>
3
+ <li>Page may change address or be deleted. Maybe you can find it from <a href="/">home page</a>.</li>
4
+ <li>If you entered the address manually please check your spelling.</li>
5
+ </ul>
@@ -0,0 +1 @@
1
+ <title>404 - This page isn't exist</title>
@@ -0,0 +1 @@
1
+ default
@@ -0,0 +1,5 @@
1
+ <h1>Такой страницы нет</h1>
2
+ <ul>
3
+ <li>Страница могла сменить адрес или быть удалена. Поищите её с <a href="/">главной страницы</a>.</li>
4
+ <li>Если вы вводили адрес вручную, проверьте его ещё раз.</li>
5
+ </ul>
@@ -0,0 +1 @@
1
+ <title>404 - Такой страницы нет</title>
data/content/WARNING ADDED
@@ -0,0 +1,2 @@
1
+ You must refresh cache by open http://YOU_SITE/admin/refresh/ in browser
2
+ after any changing content.
data/content/en.body ADDED
@@ -0,0 +1,2 @@
1
+ <h1>It's work!</h1>
2
+ <p>MicroEngine has been installed and now it's ready to use.</p>
data/content/en.header ADDED
@@ -0,0 +1 @@
1
+ <title>It's work!</title>
data/content/layout ADDED
@@ -0,0 +1 @@
1
+ default
data/content/ru.body ADDED
@@ -0,0 +1,2 @@
1
+ <h1>Работает!</h1>
2
+ <p>MicroEngine установлен и готов к работе.</p>
data/content/ru.header ADDED
@@ -0,0 +1 @@
1
+ <title>Работает!</title>
data/layouts/WARNING ADDED
@@ -0,0 +1,2 @@
1
+ You must refresh cache by open http://YOU_SITE/admin/refresh/ in browser
2
+ after any changing layouts and it's translations.
@@ -0,0 +1,7 @@
1
+ # English layout translation
2
+
3
+ language:
4
+ code: en
5
+ title: English
6
+
7
+ edit: Edit
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html xmlns="http://www.w3.org/1999/xhtml" lang="<%= translation['language']['code'] %>" xml:lang="<%= translation['language']['code'] %>">
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
5
+ <meta http-equiv="Content-Language" content="<%= translation['language']['code'] %>" />
6
+ <link rel="stylesheet" type="text/css" media="screen" href="/styles/screen.css" />
7
+ <link rel="stylesheet" type="text/css" media="print" href="/styles/print.css" />
8
+ <link rel="stylesheet" type="text/css" media="handheld" href="/styles/handheld.css" />
9
+ <%= header %>
10
+ </head>
11
+ <body>
12
+ <%= body %>
13
+ <%= languages %>
14
+ <div id="admin">
15
+ <a href="/admin/edit<%= path %>"><%= translation['edit'] %></a>
16
+ </div>
17
+ </body>
18
+ </html>
@@ -0,0 +1,7 @@
1
+ # Russian layout translation
2
+
3
+ language:
4
+ code: ru
5
+ title: Русский
6
+
7
+ edit: Править
@@ -0,0 +1,268 @@
1
+ =begin
2
+ Logic to edit site from web interface.
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 'admin_page'
20
+ require 'admin_exception'
21
+ require 'fileutils'
22
+
23
+ module Microengine
24
+ # Change content and create/delete pages by web interface
25
+ class Admin
26
+
27
+ attr_writer :logger
28
+ attr_writer :shadow
29
+ attr_accessor :cache
30
+ attr_accessor :config
31
+ attr_accessor :dispatcher
32
+ attr_accessor :assambler
33
+
34
+ def initialize
35
+ @blocked = []
36
+ begin
37
+ @access_logger = Logger.new(MICROENGINE_ROOT + '/access.log', 7, 1048576)
38
+ @access_logger.level = Logger::WARN
39
+ rescue
40
+ @access_logger = Logger.new(STDERR)
41
+ @access_logger.warn "Can't access /access.log . Ensure, that it exists and is chmod 0666."
42
+ end
43
+ end
44
+
45
+ # Check HTTP request for admin actions
46
+ def dispatch(http)
47
+ if http.url_start? '/admin/'
48
+ if http.url_start? '/admin/do/'
49
+ command, path = http.url[10..-1].split('/', 2)
50
+
51
+ if not http.post['password'].nil?
52
+ password = http.post['password']
53
+ elsif not http.cookie['password'].nil?
54
+ password = http.cookie['password']
55
+ else
56
+ @dispatcher.not_found http
57
+ return true
58
+ end
59
+
60
+ if not blocked? http.ip and right? password
61
+ if http.cookie['password'].nil?
62
+ http.set_cookie 'password', password
63
+ end
64
+
65
+ begin
66
+ case command
67
+ when 'save':
68
+ save http, path
69
+ when 'delete':
70
+ delete http, path
71
+ when 'untranslate':
72
+ untranslate http, path
73
+ when 'refresh':
74
+ refresh_cache
75
+ http.redirect '/'
76
+ else
77
+ dispatcher.not_found http
78
+ end
79
+ rescue AdminException => e
80
+ AdminPage.new(http, path, self).error(e.message)
81
+ end
82
+ else
83
+ #TODO delete cookie
84
+ block http.ip, 5
85
+ @access_logger.warn 'Wrong password from ' + http.ip
86
+ http.status = '403 Forbidden'
87
+ http << @cache.get('/403/', dispatcher.language(http, @cache.langs['/403/']))
88
+ end
89
+ else
90
+ command, path = http.url[7..-1].split('/', 2)
91
+
92
+ case command
93
+ when 'edit':
94
+ AdminPage.new(http, path, self).editor
95
+ when 'untranslate':
96
+ AdminPage.new(http, path, self).untranslater
97
+ when 'delete':
98
+ AdminPage.new(http, path, self).deleter
99
+ when 'refresh':
100
+ AdminPage.new(http, path, self).refresher
101
+ else
102
+ dispatcher.not_found http
103
+ end
104
+
105
+ end
106
+ true
107
+
108
+ else
109
+ false
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ # Save page content
116
+ def save(http, path)
117
+ lang = http.post['lang']
118
+ if lang.nil?
119
+ raise AdminException, 'no_lang'
120
+ end
121
+ if http.post['body'].nil? or '' == http.post['body']
122
+ raise AdminException, 'no_body'
123
+ end
124
+ if http.post['header'].nil?
125
+ raise AdminException, 'no_header'
126
+ end
127
+ if 'admin/' == path[0..5]
128
+ raise AdminException, 'admin_subdir'
129
+ end
130
+
131
+
132
+ begin
133
+ FileUtils.makedirs MICROENGINE_ROOT + '/content/' + path
134
+ {'body' => "#{lang}.body", 'header' => "#{lang}.header", 'layout' => 'layout'}.each do |post, file|
135
+ filename = "/content/#{path}#{file}"
136
+ file = File.new MICROENGINE_ROOT + filename, 'w'
137
+ file.write http.post[post]
138
+ file.close
139
+ end
140
+ rescue
141
+ raise AdminException, 'cant_write'
142
+ end
143
+
144
+ refresh_cache
145
+ http.redirect '/' + path
146
+ end
147
+
148
+ # Delete page translation
149
+ def untranslate(http, path)
150
+ lang = http.post['lang']
151
+ if lang.nil?
152
+ raise AdminException, 'no_lang'
153
+ end
154
+
155
+ ['body', 'header'].each do |type|
156
+ filename = "/content/#{path}#{lang}.#{type}"
157
+ file = MICROENGINE_ROOT + filename
158
+ if not File.exists? file
159
+ http.redirect '/' + path
160
+ end
161
+
162
+ begin
163
+ File.delete file
164
+ rescue
165
+ raise AdminException, 'cant_write'
166
+ end
167
+ end
168
+
169
+ refresh_cache
170
+ http.redirect '/' + path
171
+ end
172
+
173
+ # Delete page
174
+ def delete(http, path)
175
+ if ['404/', '403/', ''].include? path
176
+ raise AdminException, 'delete_system'
177
+ end
178
+
179
+ dir = MICROENGINE_ROOT + '/content/' + path
180
+ if not File.exists? dir + 'layout'
181
+ http.redirect '/'
182
+ return
183
+ end
184
+
185
+ Dir.foreach(dir) do |file|
186
+ if File.directory? dir + file
187
+ next
188
+ elsif file =~ /^(layout|[^\.]+\.(body|header))$/
189
+ begin
190
+ File.delete dir + file
191
+ rescue
192
+ raise AdminException, 'cant_write'
193
+ end
194
+ end
195
+ end
196
+
197
+ # Delete empty dirs
198
+ dir = path
199
+ while true
200
+ fullpath = MICROENGINE_ROOT + '/content/' + dir
201
+ if 2 == Dir.entries(fullpath).length
202
+ begin
203
+ Dir.rmdir fullpath
204
+ rescue
205
+ break
206
+ end
207
+ else
208
+ break
209
+ end
210
+ index = dir.rindex('/', -2)
211
+ if index.nil?
212
+ break
213
+ end
214
+ dir = dir[0..index]
215
+ end
216
+
217
+ refresh_cache
218
+ http.redirect '/'
219
+ end
220
+
221
+ # Check admin assword
222
+ def right?(password)
223
+ hash = SHA1::sha1(password + @shadow['salt']).to_s
224
+ if hash == @shadow['hash']
225
+ true
226
+ else
227
+ false
228
+ end
229
+ end
230
+
231
+ # Block IP for bruteforce defence
232
+ def block(ip, seconds)
233
+ @blocked.push [ip, Time.now + seconds]
234
+ end
235
+
236
+ # Check is IP blocked
237
+ def blocked?(ip)
238
+ now = Time.now
239
+ # Delete old
240
+ @blocked.each do |blocked|
241
+ if blocked[1] < now
242
+ @blocked.delete blocked
243
+ else
244
+ break
245
+ end
246
+ end
247
+
248
+ # Check
249
+ @blocked.each do |blocked|
250
+ if blocked[0] == ip
251
+ return true
252
+ end
253
+ end
254
+ false
255
+ end
256
+
257
+ # Recreate cache
258
+ def refresh_cache
259
+ begin
260
+ @assambler.refresh
261
+ @cache.refresh
262
+ rescue Exception => e
263
+ AdminPage.new(http, self).error('cant_cache')
264
+ raise "Cache wasn't refreshed. " + e.message
265
+ end
266
+ end
267
+ end
268
+ end