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
@@ -0,0 +1,141 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009 Akira FUNAI
5
+
6
+ module Runo::I18n
7
+
8
+ class Msgstr < String
9
+ def %(*args)
10
+ if args.first.is_a? ::Hash
11
+ self.gsub(/%\{(\w+)\}/) { args.first[$1.intern].to_s }
12
+ else
13
+ ::String.new(self.gsub(/%\{(\w+)\}/, '%s')) % args
14
+ end
15
+ end
16
+ end
17
+
18
+ module REX
19
+ COMMENT = %r{^\s*\#}
20
+ COMMENT_FUZZY = %r{^\s*\#\,\s*fuzzy}
21
+ MSGID = %r{^\s*msgid\s*"(.*?[^\\])"}
22
+ MSGSTR = %r{^\s*msgstr\s*"(.*?[^\\])"}
23
+ MSGSTR_PLURAL = %r{^\s*msgstr\[(\d+)\]\s*"(.*?[^\\])"}
24
+ PLURAL_EXPRESSION = %r{
25
+ ^"Plural-Forms:.*plural=
26
+ ((?:
27
+ n(?=\s*(?:[\+\-\%]|==|!=|>|<|>=|<=))|
28
+ [\d\s\+\-\%\(\)\?\:]+|
29
+ ==|!=|>|<|>=|<=|&&|\|\|)+).*"
30
+ }xi
31
+ end
32
+
33
+ def self.lang
34
+ Thread.current[:lang] || []
35
+ end
36
+
37
+ def self.lang=(http_accept_language)
38
+ Thread.current[:msg] = nil
39
+ tokens = http_accept_language.to_s.split(/,/)
40
+ Thread.current[:lang] = tokens.sort_by {|t| # rfc3282
41
+ [
42
+ (t =~ /q=([\d\.]+)/) ? $1.to_f : 1.0,
43
+ -tokens.index(t)
44
+ ]
45
+ }.reverse.collect {|i|
46
+ if i =~ /([a-z]{1,8})(-[a-z]{1,8})?/i # rfc2616
47
+ $2 ? ($1.downcase + $2.upcase) : $1.downcase
48
+ end
49
+ }
50
+ end
51
+
52
+ def self.domain
53
+ @@domain ||= 'index'
54
+ end
55
+
56
+ def self.domain=(domain)
57
+ @@domain = domain
58
+ end
59
+
60
+ def self.po_dir
61
+ @@po_dir ||= ::File.expand_path('../locale', ::File.dirname(__FILE__))
62
+ end
63
+
64
+ def self.po_dir=(po_dir)
65
+ @@po_dir = po_dir
66
+ end
67
+
68
+ def self.bindtextdomain(domain, po_dir)
69
+ self.domain = domain
70
+ self.po_dir = po_dir
71
+ end
72
+
73
+ def self.msg
74
+ @@msg ||= {}
75
+ @@msg[self.lang] ||= self.find_msg
76
+ Thread.current[:msg] ||= @@msg[self.lang]
77
+ end
78
+
79
+ def self.find_msg(lang = self.lang)
80
+ lang.each {|range|
81
+ [
82
+ range,
83
+ range.sub(/-.*/, ''),
84
+ ].uniq.each {|r|
85
+ po_file = ::File.join(self.po_dir, r, "#{self.domain}.po")
86
+ return open(po_file) {|f| self.parse_msg f } if ::File.readable? po_file
87
+ return {} if r == 'en' # default
88
+ }
89
+ }
90
+ {}
91
+ end
92
+
93
+ def self.parse_msg(f)
94
+ msg = {}
95
+ msgid = nil
96
+ f.each_line {|line|
97
+ case line
98
+ when REX::COMMENT_FUZZY
99
+ msgid = :skip_next
100
+ when REX::COMMENT
101
+ next
102
+ when REX::PLURAL_EXPRESSION
103
+ msg[:plural] = instance_eval "Proc.new {|n| #{$1} }"
104
+ when REX::MSGID
105
+ msgid = (msgid == :skip_next) ? :skip : $1
106
+ when REX::MSGSTR_PLURAL
107
+ if msgid.is_a? ::String
108
+ msg[msgid] = [] unless msg[msgid].is_a? ::Array
109
+ msg[msgid][$1.to_i] = $2
110
+ end
111
+ when REX::MSGSTR
112
+ msg[msgid] = $1 if msgid.is_a? ::String
113
+ end
114
+ }
115
+ msg
116
+ end
117
+
118
+ def self.merge_msg!(m)
119
+ m.delete :plural
120
+ Thread.current[:msg] = self.msg.merge m
121
+ end
122
+
123
+ module_function
124
+
125
+ def _(msgid)
126
+ Runo::I18n::Msgstr.new(Runo::I18n.msg[msgid].to_a.first || msgid)
127
+ end
128
+
129
+ def n_(msgid, msgid_plural, n)
130
+ msgstrs = Runo::I18n.msg[msgid].is_a?(::Array) ? Runo::I18n.msg[msgid] : [msgid, msgid_plural]
131
+ case v = Runo::I18n.msg[:plural] ? Runo::I18n.msg[:plural].call(n) : (n != 1)
132
+ when true
133
+ Runo::I18n::Msgstr.new msgstrs[1]
134
+ when false
135
+ Runo::I18n::Msgstr.new msgstrs[0]
136
+ else
137
+ Runo::I18n::Msgstr.new msgstrs[v.to_i]
138
+ end
139
+ end
140
+
141
+ end
@@ -0,0 +1,243 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009 Akira FUNAI
5
+
6
+ module Runo::Parser
7
+
8
+ module_function
9
+
10
+ def parse_html(html, action = :index, xml = false)
11
+ item = {}
12
+
13
+ html = gsub_block(html, 'runo-\w+') {|open, inner, close|
14
+ id = open[/id="(.+?)"/i, 1] || 'main'
15
+ item[id] = parse_block(open, inner, close, action, xml)
16
+ "$(#{id})"
17
+ }
18
+ html = gsub_action_tmpl(html) {|id, act, open, inner, close|
19
+ id ||= 'main'
20
+ if item[id]
21
+ item[id][:tmpl] ||= {}
22
+ inner = gsub_action_tmpl(inner) {|i, a, *t|
23
+ item[id][:tmpl][a] = t.join
24
+ "$(.#{a})"
25
+ }
26
+ item[id][:tmpl][act] = open + inner + close
27
+ "$(#{id}.#{act})"
28
+ else
29
+ open + inner + close
30
+ end
31
+ }
32
+ item.each {|id, meta|
33
+ m = Proc.new {|a| html.include?("$(#{id}.#{a})") || meta[:tmpl][action].include?("$(.#{a})") }
34
+ supplement_sd(meta[:tmpl][action], meta[:workflow], m)
35
+ html.sub!("$(#{id})", "$(#{id}.message)\\&") unless (
36
+ meta[:workflow] == 'attachment' || m.call('message')
37
+ )
38
+ } unless xml
39
+
40
+ html = gsub_scalar(html) {|id, meta|
41
+ item[id] = meta
42
+ "$(#{id})"
43
+ }
44
+ html.gsub!(/\s*class=".*?"/,'') if xml
45
+
46
+ meta = scrape_meta html
47
+ meta.merge(
48
+ :label => meta[:label] || scrape_label(html),
49
+ :item => item,
50
+ :tmpl => {action => html}
51
+ )
52
+ end
53
+
54
+ def parse_xml(html, action = :index)
55
+ parse_html(html, action, xml = true)
56
+ end
57
+
58
+ def gsub_action_tmpl(html, &block)
59
+ rex_klass = /(?:\w+\-)?(?:action|view|navi|submit|done)\w*/
60
+ gsub_block(html, rex_klass) {|open, inner, close|
61
+ klass = open[/class=(?:"|"[^"]*?\s)(#{rex_klass})(?:"|\s)/, 1]
62
+ id, action = (klass =~ /-/) ? klass.split('-', 2) : [nil, klass]
63
+ block.call(id, action.intern, open, inner, close)
64
+ }
65
+ end
66
+
67
+ def gsub_block(html, class_name, &block)
68
+ rex_open_tag = /\s*<(\w+)[^>]+?class=(?:"|"[^"]*?\s)#{class_name}(?:"|\s).*?>\n?/i
69
+ out = ''
70
+ s = StringScanner.new html
71
+ until s.eos?
72
+ if s.scan rex_open_tag
73
+ open_tag = s[0]
74
+ inner_html, close_tag = scan_inner_html(s, s[1])
75
+ close_tag << "\n" if s.scan /\n/
76
+ out << block.call(open_tag, inner_html, close_tag)
77
+ else
78
+ out << s.scan(/.+?(?=\t| |<|\z)/m)
79
+ end
80
+ end
81
+ out
82
+ end
83
+
84
+ def gsub_scalar(html, &block)
85
+ out = ''
86
+ s = StringScanner.new html
87
+ until s.eos?
88
+ if s.scan /\$\((\w+)(?:\s+|\s*=\s*)([\w\-]+)\s*/m
89
+ out << block.call(s[1], {:klass => s[2]}.merge(scan_tokens s))
90
+ else
91
+ out << s.scan(/.+?(?=\$|\w|<|\z)/m)
92
+ end
93
+ end
94
+ out
95
+ end
96
+
97
+ def scrape_meta(html)
98
+ meta = {}
99
+ html.gsub!(/(?:^\s+)?<meta[^>]*name="runo-([^"]+)[^>]*content="([^"]+).*?>\n?/i) {
100
+ meta[$1.intern] = $2.include?(',') ? $2.split(/\s*,\s*/) : $2
101
+ ''
102
+ }
103
+ meta
104
+ end
105
+
106
+ def scrape_label(html)
107
+ if html.sub!(/\A((?:[^<]*<!--)?[^<]*<[^>]*title=")([^"]+)/, '\\1')
108
+ label_plural = $2.to_s.split(/,/).collect {|s| s.strip }
109
+ label_plural *= 4 if label_plural.size == 1
110
+ label = label_plural.first
111
+ Runo::I18n.msg[label] ||= label_plural
112
+ label
113
+ end
114
+ end
115
+
116
+ def supplement_sd(tmpl, workflow, m)
117
+ tmpl << '$(.navi)' unless m.call 'navi'
118
+ unless workflow == 'attachment'
119
+ tmpl << '$(.submit)' unless m.call 'submit'
120
+ tmpl << '$(.action_create)' unless m.call 'action_create'
121
+ end
122
+ tmpl
123
+ end
124
+
125
+ def supplement_ss(tmpl, action)
126
+ if action == :summary
127
+ tmpl.sub!(
128
+ /\$\(.*?\)/m,
129
+ '<a href="$(.uri_detail)">\&</a>'
130
+ ) unless tmpl.include? '$(.uri_detail)'
131
+ else
132
+ tmpl.sub!(
133
+ /\$\([^\.]*?\)/m,
134
+ '$(.a_update)\&</a>'
135
+ ) unless tmpl.include? '$(.action_update)'
136
+ tmpl.sub!(
137
+ /.*\$\(.*?\)/m,
138
+ '\&$(.hidden)'
139
+ ) unless tmpl.include? '$(.hidden)'
140
+ end
141
+ tmpl
142
+ end
143
+
144
+ def parse_block(open_tag, inner_html, close_tag, action = :index, xml = false)
145
+ open_tag.sub!(/id=".*?"/i, 'id="@(name)"')
146
+ workflow = open_tag[/class=(?:"|".*?\s)runo-(\w+)/, 1]
147
+
148
+ if inner_html =~ /<(\w+).+?class=(?:"|"[^"]*?\s)body(?:"|\s)/i
149
+ item_html = ''
150
+ sd_tmpl = gsub_block(inner_html, 'body') {|open, inner, close|
151
+ item_html = open + inner + close
152
+ '$()'
153
+ }
154
+ else
155
+ item_html = inner_html
156
+ sd_tmpl = '$()'
157
+ end
158
+
159
+ tmpl = {}
160
+ sd_tmpl = gsub_action_tmpl(sd_tmpl) {|id, act, open, inner, close|
161
+ inner = gsub_action_tmpl(inner) {|i, a, *t|
162
+ tmpl[a] = t.join
163
+ "$(.#{a})"
164
+ }
165
+ tmpl[act] = open + inner + close
166
+ "$(.#{act})"
167
+ }
168
+
169
+ tmpl[action] = "#{open_tag}#{sd_tmpl}#{close_tag}"
170
+ tmpl[action].gsub!(/\s*class=".*?"/,'') if xml
171
+
172
+ item_meta = Runo::Parser.parse_html(item_html, action, xml)
173
+ supplement_ss(item_meta[:tmpl][action], action) unless xml || workflow == 'attachment'
174
+
175
+ sd = {
176
+ :klass => 'set-dynamic',
177
+ :workflow => workflow.downcase,
178
+ :tmpl => tmpl,
179
+ :item => {
180
+ 'default' => item_meta,
181
+ },
182
+ }
183
+ (inner_html =~ /\A\s*<!--(.+?)-->/m) ? sd.merge(scan_tokens StringScanner.new($1)) : sd
184
+ end
185
+
186
+ def parse_token(prefix, token, meta = {})
187
+ case prefix
188
+ when ':'
189
+ meta[:default] = token
190
+ when ';'
191
+ meta[:defaults] ||= []
192
+ meta[:defaults] << token
193
+ when ','
194
+ meta[:options] ||= []
195
+ meta[:options] << token
196
+ else
197
+ case token
198
+ when /^(-?\d+)?\.\.(-?\d+)?$/
199
+ meta[:min] = $1.to_i if $1
200
+ meta[:max] = $2.to_i if $2
201
+ when /^(\d+)\*(\d+)$/
202
+ meta[:width] = $1.to_i
203
+ meta[:height] = $2.to_i
204
+ else
205
+ meta[:tokens] ||= []
206
+ meta[:tokens] << token
207
+ end
208
+ end
209
+ meta
210
+ end
211
+
212
+ def scan_inner_html(s, name)
213
+ contents = ''
214
+ gen = 1
215
+ until s.eos? || (gen < 1)
216
+ contents << s.scan(/(.*?)(<#{name}|<\/#{name}>|\z)/m)
217
+ gen += 1 if s[2] == "<#{name}"
218
+ gen -= 1 if s[2] == "</#{name}>"
219
+ end
220
+ contents.gsub!(/\A\n+/, '')
221
+ contents.gsub!(/[\t ]*<\/#{name}>\z/, '')
222
+ [contents, $&]
223
+ end
224
+
225
+ def scan_tokens(s)
226
+ meta = {}
227
+ until s.eos? || s.scan(/\)/)
228
+ prefix = s[1] if s.scan /([:;,])?\s*/
229
+ if s.scan /(["'])(.*?)(\1|$)/
230
+ token = s[2]
231
+ elsif s.scan /[^\s\):;,]+/
232
+ token = s[0]
233
+ end
234
+ prefix ||= ',' if s.scan /(?=,)/ # 1st element of options
235
+ prefix ||= ';' if s.scan /(?=;)/ # 1st element of defaults
236
+
237
+ parse_token(prefix, token, meta)
238
+ s.scan /\s+/
239
+ end
240
+ meta
241
+ end
242
+
243
+ end
@@ -0,0 +1,86 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009 Akira FUNAI
5
+
6
+ module Runo::Path
7
+
8
+ module_function
9
+
10
+ def tid_of(path)
11
+ path[Runo::REX::TID]
12
+ end
13
+
14
+ def steps_of(path)
15
+ _dirname(path).gsub(Runo::REX::PATH_ID, '').split('/').select {|step_or_cond|
16
+ step_or_cond != '' &&
17
+ step_or_cond !~ Regexp.union(Runo::REX::COND, Runo::REX::COND_D, Runo::REX::TID)
18
+ }
19
+ end
20
+
21
+ def base_of(path)
22
+ base = Runo::Set::Static::Folder.root.item(steps_of path)
23
+ if base.is_a? Runo::Set::Static::Folder
24
+ base.item 'main'
25
+ else
26
+ base
27
+ end
28
+ end
29
+
30
+ def conds_of(path)
31
+ dir = _dirname path.gsub(Runo::REX::PATH_ID, '')
32
+ conds = $& ? {:id => sprintf('%.8d_%.4d', $1, $2)} : {}
33
+
34
+ dir.split('/').inject(conds) {|conds, step_or_cond|
35
+ if step_or_cond =~ Runo::REX::COND
36
+ conds[$1.intern] = $2
37
+ elsif step_or_cond =~ Runo::REX::COND_D
38
+ conds[:d] = $&
39
+ end
40
+ conds
41
+ }
42
+ end
43
+
44
+ def action_of(path)
45
+ a = _basename(path).to_s[/^[a-z]+/]
46
+ a.intern if a && a != 'index'
47
+ end
48
+
49
+ def sub_action_of(path)
50
+ a = _basename(path).to_s[/_([a-z]+)/, 1]
51
+ a.intern if a
52
+ end
53
+
54
+ def path_of(conds)
55
+ conds = {} unless conds.is_a? ::Hash
56
+ (
57
+ (conds.keys - [:order, :p, :id]) |
58
+ ([:order, :p, :id] & conds.keys)
59
+ ).collect {|cid|
60
+ if cid == :id
61
+ ids = Array(conds[:id]).collect {|id|
62
+ (id =~ /_(#{Runo::REX::ID_SHORT})$/) ? $1 : (id if Runo::REX::ID)
63
+ }.compact
64
+ if (ids.size == 1) && (ids.first =~ Runo::REX::ID)
65
+ '%s/%d/' % [$1, $2.to_i]
66
+ elsif ids.size > 0
67
+ "id=#{ids.join ','}/"
68
+ end
69
+ elsif cid == :d
70
+ conds[:id] ? '' : "#{conds[:d]}/"
71
+ else
72
+ "#{cid}=#{Array(conds[cid]).join ','}/"
73
+ end
74
+ }.join.sub(%r{/p=1/$}, '/')
75
+ end
76
+
77
+ def _dirname(path) # returns '/foo/bar/' for '/foo/bar/'
78
+ path[%r{^.*/}] || ''
79
+ end
80
+
81
+ def _basename(path) # returns nil for '/foo/bar/'
82
+ path[%r{[^/]+$}]
83
+ end
84
+
85
+ end
86
+