runo 0.1.0

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 (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