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
@@ -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
+