runo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (149) hide show
  1. data/LICENSE +19 -0
  2. data/README.rdoc +120 -0
  3. data/bin/runo +35 -0
  4. data/lib/_error.rb +14 -0
  5. data/lib/_field.rb +260 -0
  6. data/lib/_i18n.rb +141 -0
  7. data/lib/_parser.rb +243 -0
  8. data/lib/_path.rb +86 -0
  9. data/lib/_storage/_storage.rb +213 -0
  10. data/lib/_storage/file.rb +200 -0
  11. data/lib/_storage/sequel.rb +174 -0
  12. data/lib/_storage/temp.rb +73 -0
  13. data/lib/_widget/action_create.rb +23 -0
  14. data/lib/_widget/action_login.rb +22 -0
  15. data/lib/_widget/action_signup.rb +16 -0
  16. data/lib/_widget/action_update.rb +16 -0
  17. data/lib/_widget/crumb.rb +24 -0
  18. data/lib/_widget/done.rb +16 -0
  19. data/lib/_widget/login.rb +25 -0
  20. data/lib/_widget/me.rb +31 -0
  21. data/lib/_widget/message.rb +51 -0
  22. data/lib/_widget/navi.rb +88 -0
  23. data/lib/_widget/submit.rb +49 -0
  24. data/lib/_widget/view_ym.rb +77 -0
  25. data/lib/_workflow/_workflow.rb +89 -0
  26. data/lib/_workflow/attachment.rb +50 -0
  27. data/lib/_workflow/blog.rb +28 -0
  28. data/lib/_workflow/contact.rb +23 -0
  29. data/lib/_workflow/forum.rb +26 -0
  30. data/lib/_workflow/register.rb +39 -0
  31. data/lib/meta/_meta.rb +20 -0
  32. data/lib/meta/group.rb +19 -0
  33. data/lib/meta/id.rb +59 -0
  34. data/lib/meta/owner.rb +21 -0
  35. data/lib/meta/timestamp.rb +118 -0
  36. data/lib/runo.rb +396 -0
  37. data/lib/scalar/checkbox.rb +68 -0
  38. data/lib/scalar/file.rb +144 -0
  39. data/lib/scalar/img.rb +112 -0
  40. data/lib/scalar/password.rb +58 -0
  41. data/lib/scalar/radio.rb +47 -0
  42. data/lib/scalar/select.rb +47 -0
  43. data/lib/scalar/text.rb +38 -0
  44. data/lib/scalar/textarea.rb +35 -0
  45. data/lib/scalar/textarea_pre.rb +14 -0
  46. data/lib/scalar/textarea_wiki.rb +173 -0
  47. data/lib/set/_set.rb +195 -0
  48. data/lib/set/dynamic.rb +177 -0
  49. data/lib/set/static.rb +102 -0
  50. data/lib/set/static_folder.rb +96 -0
  51. data/locale/en/index.po +242 -0
  52. data/locale/index.pot +243 -0
  53. data/locale/ja/index.po +242 -0
  54. data/locale/lazy_parser.rb +54 -0
  55. data/skel/config.ru +27 -0
  56. data/skel/skin/_users/00000000_frank-avatar.jpg +0 -0
  57. data/skel/skin/_users/00000000_frank-avatar_small.jpg +0 -0
  58. data/skel/skin/_users/00000000_frank.yaml +12 -0
  59. data/skel/skin/_users/00000000_root-avatar.jpg +0 -0
  60. data/skel/skin/_users/00000000_root-avatar_small.jpg +0 -0
  61. data/skel/skin/_users/00000000_root.yaml +11 -0
  62. data/skel/skin/_users/css/users.css +21 -0
  63. data/skel/skin/_users/css/users.less +25 -0
  64. data/skel/skin/_users/done.html +42 -0
  65. data/skel/skin/_users/index.html +46 -0
  66. data/skel/skin/_users/index.yaml +3 -0
  67. data/skel/skin/_users/summary.html +40 -0
  68. data/skel/skin/css/base.css +93 -0
  69. data/skel/skin/css/base.less +139 -0
  70. data/skel/skin/css/coax.css +199 -0
  71. data/skel/skin/css/coax.less +244 -0
  72. data/skel/skin/examples/blog/20091214_0001.yaml +8 -0
  73. data/skel/skin/examples/blog/20100630_0001.yaml +8 -0
  74. data/skel/skin/examples/blog/20100630_0002.yaml +14 -0
  75. data/skel/skin/examples/blog/20100701_0001.yaml +8 -0
  76. data/skel/skin/examples/blog/20100701_0002-a-20100701_0001-f.jpg +0 -0
  77. data/skel/skin/examples/blog/20100701_0002-a-20100701_0001-f_small.jpg +0 -0
  78. data/skel/skin/examples/blog/20100701_0002.yaml +19 -0
  79. data/skel/skin/examples/blog/frank/20100701_0001.yaml +10 -0
  80. data/skel/skin/examples/blog/frank/index.yaml +4 -0
  81. data/skel/skin/examples/blog/index.html +51 -0
  82. data/skel/skin/examples/blog/rss.xml +18 -0
  83. data/skel/skin/examples/contact/20100701_0001-file.txt +1 -0
  84. data/skel/skin/examples/contact/20100701_0001.yaml +15 -0
  85. data/skel/skin/examples/contact/20100701_0002.yaml +8 -0
  86. data/skel/skin/examples/contact/20100701_0003.yaml +9 -0
  87. data/skel/skin/examples/contact/index.html +47 -0
  88. data/skel/skin/examples/contact/js/contact.js +13 -0
  89. data/skel/skin/examples/contact/summary.html +54 -0
  90. data/skel/skin/examples/forum/20100701_0001.yaml +41 -0
  91. data/skel/skin/examples/forum/20100701_0002.yaml +25 -0
  92. data/skel/skin/examples/forum/index.html +68 -0
  93. data/skel/skin/examples/forum/summary.html +47 -0
  94. data/skel/skin/examples/index.html +75 -0
  95. data/skel/skin/index.html +41 -0
  96. data/skel/skin/js/base.js +50 -0
  97. data/t/locale/de/index.po +19 -0
  98. data/t/locale/en-GB/index.po +25 -0
  99. data/t/locale/ja/index.po +30 -0
  100. data/t/skin/_users/00000000_test.yaml +3 -0
  101. data/t/skin/_users/index.html +13 -0
  102. data/t/skin/foo/20091120_0001.yaml +7 -0
  103. data/t/skin/foo/bar/20091120_0001.yaml +5 -0
  104. data/t/skin/foo/bar/index.yaml +5 -0
  105. data/t/skin/foo/baz/css/baz.css +1 -0
  106. data/t/skin/foo/css/foo.css +1 -0
  107. data/t/skin/foo/index.html +14 -0
  108. data/t/skin/foo/index.yaml +7 -0
  109. data/t/skin/foo/not_css/foo.css +1 -0
  110. data/t/skin/foo/sub-20100306_0001.yaml +3 -0
  111. data/t/skin/index.yaml +3 -0
  112. data/t/skin/t_attachment/index.html +13 -0
  113. data/t/skin/t_contact/done.html +6 -0
  114. data/t/skin/t_contact/index.html +9 -0
  115. data/t/skin/t_file/index.html +16 -0
  116. data/t/skin/t_img/index.html +14 -0
  117. data/t/skin/t_img/test.jpg +0 -0
  118. data/t/skin/t_select/index.html +9 -0
  119. data/t/skin/t_store/index.html +9 -0
  120. data/t/skin/t_summary/20100326_0001.yaml +3 -0
  121. data/t/skin/t_summary/create.html +9 -0
  122. data/t/skin/t_summary/index.html +9 -0
  123. data/t/skin/t_summary/summary.html +9 -0
  124. data/t/t.rb +27 -0
  125. data/t/test_checkbox.rb +273 -0
  126. data/t/test_field.rb +330 -0
  127. data/t/test_file.rb +900 -0
  128. data/t/test_id.rb +215 -0
  129. data/t/test_img.rb +328 -0
  130. data/t/test_meta.rb +57 -0
  131. data/t/test_parser.rb +1266 -0
  132. data/t/test_password.rb +188 -0
  133. data/t/test_radio.rb +226 -0
  134. data/t/test_role.rb +249 -0
  135. data/t/test_runo.rb +742 -0
  136. data/t/test_runo_call.rb +1286 -0
  137. data/t/test_runo_i18n.rb +318 -0
  138. data/t/test_select.rb +182 -0
  139. data/t/test_set_complex.rb +527 -0
  140. data/t/test_set_dynamic.rb +1504 -0
  141. data/t/test_set_folder.rb +515 -0
  142. data/t/test_set_permit.rb +246 -0
  143. data/t/test_set_static.rb +445 -0
  144. data/t/test_storage.rb +915 -0
  145. data/t/test_text.rb +125 -0
  146. data/t/test_textarea.rb +138 -0
  147. data/t/test_timestamp.rb +473 -0
  148. data/t/test_workflow.rb +367 -0
  149. metadata +345 -0
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010 Akira FUNAI <akira -at- funai -dot- com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a
4
+ copy of this software and associated documentation files (the "Software"),
5
+ to deal in the Software without restriction, including without limitation
6
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
7
+ and/or sell copies of the Software, and to permit persons to whom the
8
+ Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19
+ DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,120 @@
1
+ = Runo
2
+
3
+ Runo is the easiest web application framework ever.
4
+ You can make various apps such like blog, forum, contact form by putting single (or several) HTML file with a bit of special markups. The view rules. There is no model definition, no server restart, and no coding at all.
5
+
6
+ == Features
7
+
8
+ * Model / View / Controller defined dynamically by good old HTML.
9
+ * Any block tags (<ul>, <table> etc.) can be an "app", by adding a workflow class like "runo-blog".
10
+ * The contents inside the app tag will be the "model".
11
+ * The model is defined by special tags, like "$(foo = text 1..32)" or "$(bar = select a,b,c)".
12
+ * You can make nested apps inside an app (e.g. blog comments inside a blog entry).
13
+ * Authentication / Authorization / User admin are built in by default.
14
+ * Storage can be plain YAML files (default), or any RDB via Sequel.
15
+ * Runo itself is a Rack application.
16
+
17
+ == Quick Start
18
+
19
+ This is a quick tutorial for making a simple web application using Runo.
20
+
21
+ === Step 1 - Install Runo
22
+
23
+ Open a command line terminal. Type:
24
+
25
+ gem install runo
26
+
27
+ If you want to make thumbnails for submitted images, you have to install QuickMagick as well.
28
+ http://rubyforge.org/projects/quickmagick/
29
+
30
+ === Step 2 - Initialize a Runo Directory
31
+
32
+ In a command line terminal, go to an empty directory where you want to build Runo projects.
33
+ Then type:
34
+
35
+ runo init
36
+
37
+ This will create a "runo" directory, which includes:
38
+
39
+ runo/
40
+ config.ru
41
+ skin/
42
+
43
+ === Step 3 - Start a Test Server
44
+
45
+ In the terminal, change to the newly created "runo" directory.
46
+ Type:
47
+
48
+ runo run
49
+
50
+ === Step 4 - Try some examples
51
+
52
+ Now open this URL with your browser.
53
+
54
+ http://localhost:9292/
55
+
56
+ You will see the default homepage of Runo.
57
+ There is some links to examples such like Blog, Contact Form, Forum, etc. Just see what can be done by plain HTML files.
58
+
59
+ === Step 5 - Create Your Own App
60
+
61
+ Let's call our app "fab".
62
+ It is a web-based forum where logged in users can submit their own articles, and everybody can read them.
63
+ We put links to signup / login at bottom of the app.
64
+
65
+ Create a new directory under runo/skin/:
66
+
67
+ mkdir skin/fab/
68
+
69
+ Then create a new HTML file with your favorite editor:
70
+
71
+ edit skin/fab/index.html
72
+
73
+ The content is: (You can just copy & paste, of course)
74
+
75
+ <html>
76
+ <head>
77
+ <title>fab forum</title>
78
+ <link rel="stylesheet" href="/css/base.css" type="text/css" media="screen" />
79
+ </head>
80
+
81
+ <body>
82
+ <h1><a href=".">fab forum</a></h1>
83
+
84
+ <ul id="main" class="runo-forum">
85
+ <!-- 1..5 desc may_preview -->
86
+ <li>
87
+ <h2>$(title = text 32 1..32)</h2>
88
+ <div>
89
+ $(content = textarea-pre 60*10 1..)
90
+ <div>by $(_owner) @ $(_timestamp)</div>
91
+ </div>
92
+ </li>
93
+ </ul>
94
+
95
+ <p>$(.action_signup)$(.me)</p>
96
+ </body>
97
+ </html>
98
+
99
+ === Step 6 - That's it!
100
+
101
+ See the URL:
102
+
103
+ http://localhost:9292/fab/
104
+
105
+ You have just created your first app. Open a bottle of your favorite drink and make yourself comfortable.
106
+ When you need more complicated tricks, HTMLs under skin/examples/ will be your help. More detailed documents are on the way.
107
+
108
+ == Note on Patches/Pull Requests
109
+
110
+ * Fork the project.
111
+ * Make your feature addition or bug fix.
112
+ * Add tests for it. This is important so I don't break it in a
113
+ future version unintentionally.
114
+ * Commit, do not mess with rakefile, version, or history.
115
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
116
+ * Send me a pull request. Bonus points for topic branches.
117
+
118
+ == Copyright
119
+
120
+ Copyright (c) 2010 Akira FUNAI <akira -at- funai -dot- com>. See LICENSE for details.
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ # Author:: Akira FUNAI
5
+ # Copyright:: Copyright (c) 2009-2010 Akira FUNAI
6
+
7
+ require 'rubygems'
8
+
9
+ $LOAD_PATH.unshift(::File.expand_path('../lib',::File.dirname(__FILE__)))
10
+ require 'runo'
11
+
12
+ case ARGV.shift
13
+ when 'init'
14
+ runo_dir = ::File.expand_path './runo'
15
+
16
+ if ::File.exists? runo_dir
17
+ $stderr.puts "#{runo_dir} already exists."
18
+ exit 1
19
+ else
20
+ $stderr.puts "Setting up Runo files in #{runo_dir}..."
21
+ ::FileUtils.cp_r(
22
+ ::File.expand_path('../skel',Runo.libdir),
23
+ './runo',
24
+ :verbose => :true
25
+ )
26
+ end
27
+ when 'run'
28
+ ::Dir.chdir(File.exists?('./config.ru') ? '.' : './runo') {
29
+ $stderr.puts "Starting a server with Runo files in #{::File.expand_path '.'}..."
30
+ system('rackup',*ARGV)
31
+ }
32
+ else
33
+ $stderr.puts 'Usage: runo {init|run [Rack options]}'
34
+ exit 1
35
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009 Akira FUNAI
5
+
6
+ module Runo::Error
7
+
8
+ class Forbidden < StandardError
9
+ end
10
+
11
+ class NotFound < StandardError
12
+ end
13
+
14
+ end
@@ -0,0 +1,260 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009 Akira FUNAI
5
+
6
+ require 'rubygems'
7
+ require 'rack/utils'
8
+
9
+ $KCODE = 'UTF8'
10
+
11
+ class Runo::Field
12
+
13
+ include Runo::I18n
14
+
15
+ DEFAULT_META = {}
16
+
17
+ def self.instance(meta = {})
18
+ k = meta[:klass].to_s.split(/-/).inject(Runo) {|c, name|
19
+ name = name.capitalize
20
+ c.const_get(name)
21
+ }
22
+ k.new(meta) if k < self
23
+ end
24
+
25
+ def self.h(val)
26
+ Rack::Utils.escape_html val.to_s
27
+ end
28
+
29
+ attr_reader :action, :result
30
+
31
+ def initialize(meta = {})
32
+ @meta = self.class.const_get(:DEFAULT_META).merge meta
33
+ @val = val_cast(nil)
34
+ end
35
+
36
+ def inspect
37
+ caller.grep(/`inspect'/).empty? ? super : "<#{self.class} name=\"#{my[:name]}\">"
38
+ end
39
+
40
+ def [](id, *arg)
41
+ respond_to?("meta_#{id}") ? __send__("meta_#{id}", *arg) : @meta[id]
42
+ end
43
+
44
+ def []=(id, v)
45
+ @meta[id] = v
46
+ end
47
+
48
+ def val
49
+ @val
50
+ end
51
+
52
+ def item(*item_steps)
53
+ item_steps = item_steps.first if item_steps.first.is_a? ::Array
54
+ item_steps.empty? ? self : nil # scalar has no item
55
+ end
56
+
57
+ def find_ancestor(&block)
58
+ f = self
59
+ block.call(f) ? (return f) : (f = f[:parent]) until f.nil?
60
+ end
61
+
62
+ def meta_name
63
+ my[:parent] && !my[:parent].is_a?(Runo::Set::Static::Folder) ?
64
+ "#{my[:parent][:name]}-#{my[:id]}" : my[:id]
65
+ end
66
+
67
+ def meta_full_name
68
+ my[:parent] ? "#{my[:parent][:full_name]}-#{my[:id]}" : my[:id]
69
+ end
70
+
71
+ def meta_short_name
72
+ return '' if Runo.base && my[:full_name] == Runo.base[:full_name]
73
+ my[:parent] && Runo.base && my[:parent][:full_name] != Runo.base[:full_name] ?
74
+ "#{my[:parent][:short_name]}-#{my[:id]}" : my[:id]
75
+ end
76
+
77
+ def meta_folder
78
+ find_ancestor {|f| f.is_a? Runo::Set::Static::Folder }
79
+ end
80
+
81
+ def meta_sd
82
+ find_ancestor {|f| f.is_a? Runo::Set::Dynamic }
83
+ end
84
+
85
+ def meta_client
86
+ Runo.client
87
+ end
88
+
89
+ def meta_owner
90
+ @meta[:owner] || (my[:parent] ? my[:parent][:owner] : 'root')
91
+ end
92
+
93
+ def meta_owners
94
+ (my[:parent] ? my[:parent][:owners] : ['root']) | @meta[:owner].to_a
95
+ end
96
+
97
+ def meta_admins
98
+ (my[:parent] ? my[:parent][:admins] : ['root']) | @meta[:admin].to_a
99
+ end
100
+
101
+ def meta_group
102
+ @meta[:group] || (my[:parent] ? my[:parent][:group] : [])
103
+ end
104
+
105
+ def meta_roles
106
+ roles = 0
107
+ roles |= Runo::Workflow::ROLE_ADMIN if my[:admins].include? my[:client]
108
+ roles |= Runo::Workflow::ROLE_GROUP if my[:group].include? my[:client]
109
+ roles |= Runo::Workflow::ROLE_OWNER if my[:owner] == my[:client]
110
+ roles |= (Runo.client == 'nobody') ? Runo::Workflow::ROLE_NONE : Runo::Workflow::ROLE_USER
111
+ roles
112
+ end
113
+
114
+ def permit?(action)
115
+ return false if action == :create && @result == :load
116
+ return true unless my[:sd]
117
+ return true if my[:sd].workflow.permit?(my[:roles], action)
118
+ return true if find_ancestor {|f| f[:id] =~ Runo::REX::ID_NEW } # descendant of a new item
119
+ end
120
+
121
+ def default_action
122
+ return :read unless my[:sd]
123
+ actions = my[:sd].workflow.class.const_get(:PERM).keys - [:read, :create, :update]
124
+ ([:read, :create, :update] + actions).find {|action| permit? action }
125
+ end
126
+
127
+ def get(arg = {})
128
+ if permit_get? arg
129
+ _get(arg)
130
+ else
131
+ if arg[:action]
132
+ raise Runo::Error::Forbidden
133
+ else
134
+ arg[:action] = default_action
135
+ end
136
+ arg[:action] ? _get(arg) : 'xxx'
137
+ end
138
+ end
139
+
140
+ def load_default
141
+ post :load_default
142
+ end
143
+
144
+ def load(v = nil)
145
+ post :load, v
146
+ end
147
+
148
+ def create(v = nil)
149
+ post :create, v
150
+ end
151
+
152
+ def update(v = nil)
153
+ post :update, v
154
+ end
155
+
156
+ def delete
157
+ post :delete
158
+ end
159
+
160
+ def post(action, v = nil)
161
+ raise Runo::Error::Forbidden unless permit_post?(action, v)
162
+
163
+ if _post action, val_cast(v)
164
+ @result = (action == :load || action == :load_default) ? action : nil
165
+ @action = (action == :load || action == :load_default) ? nil : action
166
+ else
167
+ @result = nil
168
+ end
169
+ self
170
+ end
171
+
172
+ def commit(type = :temp)
173
+ if valid?
174
+ @result = @action
175
+ @action = nil if type == :persistent
176
+ self
177
+ end
178
+ end
179
+
180
+ def pending?
181
+ @action ? true : false
182
+ end
183
+
184
+ def valid?
185
+ errors.empty?
186
+ end
187
+
188
+ def empty?
189
+ val.to_s == ''
190
+ end
191
+
192
+ def errors
193
+ []
194
+ end
195
+
196
+ private
197
+
198
+ def my
199
+ self
200
+ end
201
+
202
+ def _get(arg)
203
+ _get_by_method arg
204
+ end
205
+
206
+ def _get_by_method(arg)
207
+ m = "_g_#{arg[:action]}"
208
+ respond_to?(m, true) ? __send__(m, arg) : _g_default(arg)
209
+ end
210
+ alias :_get_by_self_reference :_get_by_method
211
+
212
+ def _g_empty(arg)
213
+ end
214
+ alias :_g_login :_g_empty
215
+ alias :_g_done :_g_empty
216
+ alias :_g_message :_g_empty
217
+
218
+ def _g_default(arg)
219
+ Runo::Field.h val
220
+ end
221
+
222
+ def _g_errors(arg)
223
+ <<_html unless _g_valid? arg
224
+ <span class="error_message">#{errors.join "<br/>\n"}</span>
225
+ _html
226
+ end
227
+
228
+ def _g_class(arg)
229
+ c = self.class.to_s.sub('Runo::', '').gsub('::', '-').downcase
230
+ _g_valid?(arg) ? c : "#{c} error"
231
+ end
232
+
233
+ def _g_valid?(arg)
234
+ valid? || (arg[:action] == :create && !item.pending?)
235
+ end
236
+
237
+ def _post(action, v)
238
+ case action
239
+ when :load_default
240
+ @val = val_cast(my[:defaults] || my[:default])
241
+ when :load, :create, :update
242
+ @val = v if @val != v
243
+ when :delete
244
+ true
245
+ end
246
+ end
247
+
248
+ def permit_get?(arg)
249
+ permit? arg[:action]
250
+ end
251
+
252
+ def permit_post?(action, v)
253
+ action == :load || action == :load_default || permit?(action)
254
+ end
255
+
256
+ def val_cast(v)
257
+ v
258
+ end
259
+
260
+ end