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,213 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009 Akira FUNAI
5
+
6
+ class Runo::Storage
7
+
8
+ def self.instance(sd)
9
+ if folder = sd[:folder]
10
+ if folder != sd[:parent]
11
+ Temp.new sd
12
+ else
13
+ klass = Runo['storage']['default'].capitalize
14
+ self.const_get(klass).new sd
15
+ end
16
+ else
17
+ Temp.new sd
18
+ end
19
+ end
20
+
21
+ def self.available?
22
+ false
23
+ end
24
+
25
+ attr_reader :sd
26
+
27
+ def initialize(sd)
28
+ @sd = sd
29
+ end
30
+
31
+ def persistent?
32
+ true
33
+ end
34
+
35
+ def order
36
+ @sd[:order]
37
+ end
38
+
39
+ def select(conds = {})
40
+ _cast conds
41
+ item_ids = _select(conds)
42
+ item_ids = _sort(item_ids, conds)
43
+ item_ids = _page(item_ids, conds)
44
+ ((conds[:order] || order) =~ /^-/) ? item_ids.reverse : item_ids
45
+ end
46
+
47
+ def build(v)
48
+ self
49
+ end
50
+
51
+ def clear
52
+ self
53
+ end
54
+
55
+ def store(id, v)
56
+ id
57
+ end
58
+
59
+ def delete(id)
60
+ id
61
+ end
62
+
63
+ def navi(conds)
64
+ conds[:p] = '1' unless conds[:p] || conds[:id] || (@sd[:p_size].to_i < 1)
65
+ navi = {}
66
+ (([:id, :p, :d] & conds.keys) | conds.keys).each {|cid|
67
+ next unless conds[cid] && respond_to?("_sibs_#{cid}", true)
68
+ sibs = __send__("_sibs_#{cid}", conds)
69
+
70
+ c = (c.is_a?(::Array) && c.size < 2) ? conds[cid].first : conds[cid]
71
+ if i = sibs.index(c)
72
+ if !navi[:prev] && i > 0
73
+ navi[:prev] = conds.merge(cid => sibs[i - 1])
74
+ if ![:id, :p].include? cid
75
+ navi[:prev][:id] = _select_without(:id, navi[:prev]).last if conds[:id]
76
+ navi[:prev][:p] = _p_count(navi[:prev]).to_s if conds[:p]
77
+ end
78
+ end
79
+ if !navi[:next] && i < (sibs.size - 1)
80
+ navi[:next] = conds.merge(cid => sibs[i + 1])
81
+ if ![:id, :p].include? cid
82
+ navi[:next][:id] = _select_without(:id, navi[:next]).first if conds[:id]
83
+ navi[:next][:p] = '1' if conds[:p]
84
+ end
85
+ end
86
+ end
87
+ navi[:sibs] ||= {cid => sibs} if navi[:prev] || navi[:next]
88
+
89
+ break if navi[:prev] && navi[:next]
90
+ }
91
+
92
+ navi
93
+ end
94
+
95
+ def last(cid, conds)
96
+ __send__("_sibs_#{cid}", conds).last
97
+ end
98
+
99
+ private
100
+
101
+ def _cast(conds)
102
+ ([:d, :id, :p] & conds.keys).each {|cid|
103
+ case cid
104
+ when :d
105
+ conds[:d] = conds[:d].to_s
106
+ conds[:d] = last(:d, conds) if conds[:d] =~ /9999(99)?(99)?/
107
+ conds[:d] = nil unless conds[:d] =~ Runo::REX::COND_D
108
+ when :id
109
+ conds[:id] = Array(conds[:id]).collect {|id|
110
+ case id
111
+ when '99999999_9999', 'last'
112
+ last(:id, conds)
113
+ when /\A#{Runo::REX::ID_SHORT}\z/
114
+ "00000000_#{id}"
115
+ when Runo::REX::ID, Runo::REX::ID_NEW
116
+ id
117
+ end
118
+ }.uniq.compact
119
+ when :p
120
+ conds[:p] = conds[:p].to_s
121
+ conds[:p] = last(:p, conds) if conds[:p] == 'last'
122
+ conds[:p] = nil unless conds[:p] =~ /^\d+$/
123
+ end
124
+ }
125
+ conds
126
+ end
127
+
128
+ def _select(conds)
129
+ if conds[:id]
130
+ _select_by_id(conds) | (@sd.instance_variable_get(:@item_object).keys & conds[:id].to_a)
131
+ elsif cid = (conds.keys - [:order, :p]).first
132
+ m = "_select_by_#{cid}"
133
+ respond_to?(m, true) ? __send__(m, conds) : []
134
+ else
135
+ _select_all(conds) | @sd.instance_variable_get(:@item_object).keys
136
+ end
137
+ end
138
+
139
+ def _sort(item_ids, conds)
140
+ item_ids.sort
141
+ end
142
+
143
+ def _page(item_ids, conds)
144
+ size = @sd[:p_size].to_i
145
+ return item_ids if size < 1
146
+
147
+ page = conds[:p].to_i
148
+ page = 1 if page < 1
149
+ item_ids[(page - 1) * size, size].to_a
150
+ end
151
+
152
+ def _sibs_id(conds)
153
+ _select_without(:id, conds)
154
+ end
155
+
156
+ def _sibs_p(conds)
157
+ p_count = _p_count(conds)
158
+ p_count ? (1..p_count).collect {|i| i.to_s } : []
159
+ end
160
+
161
+ def _sibs_d(conds)
162
+ rex_d = /^\d{#{conds[:d].length}}/
163
+ _select_without(:id, :p, :d, conds).collect {|id| id[rex_d] }.uniq.compact
164
+ end
165
+
166
+ def _select_without(*cids)
167
+ conds = cids.pop.dup
168
+ cids.each {|cid| conds.delete cid }
169
+ _sort(_select(conds), conds)
170
+ end
171
+
172
+ def _p_count(conds)
173
+ (_select_without(:id, :p, conds).size / @sd[:p_size].to_f).ceil unless @sd[:p_size].to_f == 0
174
+ end
175
+
176
+ def cast_ids(ids)
177
+ ids.to_a.collect {|i|
178
+ id = (i =~ /^[a-z]/) ? "00000000_#{i}" : i
179
+ id if id =~ Runo::REX::ID
180
+ }.compact
181
+ end
182
+
183
+ def new_id(v = {})
184
+ return "00000000_#{v['_id']}" if v.is_a?(::Hash) && v['_id'] =~ /\A#{Runo::REX::ID_SHORT}\z/
185
+
186
+ if v.is_a?(::Hash) && v['_timestamp'] && v['_timestamp']['published'].is_a?(::Time)
187
+ d = v['_timestamp']['published'].strftime '%Y%m%d'
188
+ else
189
+ d = Time.now.strftime '%Y%m%d'
190
+ end
191
+ if max_in_d = _select(:d => d).sort.last
192
+ max_in_d.succ
193
+ else
194
+ d + '_0001'
195
+ end
196
+ end
197
+
198
+ def new_id?(id, v)
199
+ if id == :new_id
200
+ true
201
+ elsif !v.is_a?(::Hash)
202
+ false
203
+ elsif id =~ /_#{Runo::REX::ID_SHORT}\z/
204
+ v['_id'] =~ /\A#{Runo::REX::ID_SHORT}\z/ &&
205
+ id != new_id(v)
206
+ else
207
+ v['_timestamp'] &&
208
+ v['_timestamp']['published'].is_a?(::Time) &&
209
+ id !~ /\A#{v['_timestamp']['published'].strftime('%Y%m%d')}/
210
+ end
211
+ end
212
+
213
+ end
@@ -0,0 +1,200 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009 Akira FUNAI
5
+
6
+ require 'rubygems'
7
+ require 'yaml'
8
+ require 'ya2yaml'
9
+ require 'fileutils'
10
+
11
+ class Runo::Storage::File < Runo::Storage
12
+
13
+ def self.traverse(dir = '/', root = Runo['storage']['File']['data_dir'], &block)
14
+ ::Dir.glob(::File.join root, dir, '*').sort.collect {|file|
15
+ ftype = ::File.ftype file
16
+ base_name = ::File.basename file
17
+ id, ext = base_name.split('.', 2)
18
+ id = "main-#{id}" if id =~ Runo::REX::ID
19
+ full_name = ::File.join(dir, id).gsub(::File::SEPARATOR, '-')
20
+
21
+ if ftype == 'file' && id.sub(/^([^\d\-]+-)+/, '') =~ Runo::REX::ID
22
+ val = nil
23
+ ::File.open(file, 'r') {|f|
24
+ f.flock ::File::LOCK_SH
25
+ f.binmode
26
+ val = f.read
27
+ f.flock ::File::LOCK_UN
28
+ }
29
+ block.call(
30
+ :dir => dir,
31
+ :base_name => base_name,
32
+ :full_name => full_name,
33
+ :ext => ext,
34
+ :val => (ext == 'yaml' ? YAML.load(val) : val)
35
+ )
36
+ elsif ftype == 'directory' && base_name !~ /\A#{Runo::REX::DIR_STATIC}\z/
37
+ self.traverse(::File.join(dir, base_name), root, &block)
38
+ end
39
+ }.compact.flatten
40
+ end
41
+
42
+ def self.load_skel
43
+ self.traverse('/', Runo['skin_dir']) {|entry|
44
+ dir = ::File.join(Runo['storage']['File']['data_dir'], entry[:dir])
45
+ unless ::File.exists? ::File.join(dir, entry[:base_name])
46
+ ::FileUtils.mkpath(dir) unless ::File.directory? dir
47
+ ::FileUtils.cp(
48
+ ::File.join(Runo['skin_dir'], entry[:dir], entry[:base_name]),
49
+ ::File.join(dir, entry[:base_name]),
50
+ {:preserve => true}
51
+ )
52
+ end
53
+ }
54
+ end
55
+
56
+ def self.available?
57
+ Runo['storage']['File'] && Runo['storage']['File']['data_dir']
58
+ end
59
+
60
+ def initialize(sd)
61
+ super
62
+ unless @@loaded ||= false
63
+ entries = ::Dir.glob ::File.join(Runo['storage']['File']['data_dir'], '*')
64
+ self.class.load_skel if entries.empty?
65
+ @@loaded = true
66
+ end
67
+ @dir = ::File.join(Runo['storage']['File']['data_dir'], @sd[:folder][:dir])
68
+ ::FileUtils.mkpath(@dir) unless ::File.directory? @dir
69
+ end
70
+
71
+ def val(id = nil)
72
+ if id
73
+ load id
74
+ else
75
+ # this could be HUGE.
76
+ _select_all({}).inject({}) {|v, id|
77
+ v[id] = load id
78
+ v
79
+ }
80
+ end
81
+ end
82
+
83
+ def build(v)
84
+ clear
85
+ v.each {|id, v| store(id, v) }
86
+ self
87
+ end
88
+
89
+ def clear
90
+ glob.each {|file| ::File.unlink ::File.join(@dir, file) }
91
+ self
92
+ end
93
+
94
+ def store(id, v, ext = nil)
95
+ save(id, v, ext)
96
+ end
97
+
98
+ def delete(id)
99
+ remove_file(id) && id
100
+ end
101
+
102
+ def move(old_id, new_id)
103
+ rename_file(old_id, new_id) && new_id
104
+ end
105
+
106
+ private
107
+
108
+ def _select_by_id(conds)
109
+ glob(conds[:id].to_a).collect {|f| f[/\d.*/][Runo::REX::ID] }.compact
110
+ end
111
+
112
+ def _select_by_d(conds)
113
+ glob(conds[:d].to_s).collect {|f| f[/\.yaml$/] && f[/\d.*/][Runo::REX::ID] }.compact
114
+ end
115
+
116
+ def _select_all(conds)
117
+ glob.collect {|f| f[/\.yaml$/] && f[/\d.*/][Runo::REX::ID] }.compact
118
+ end
119
+
120
+ def glob(id = :all)
121
+ glob_pattern = "#{file_prefix}#{pattern_for id}.[a-z]*"
122
+ ::Dir.chdir(@dir) { ::Dir.glob glob_pattern }
123
+ end
124
+
125
+ def file_prefix
126
+ (@sd[:name] == 'main') ? '' : @sd[:name].sub(/^main-/, '') + '-'
127
+ end
128
+
129
+ def pattern_for(id)
130
+ if id.is_a? Array
131
+ "{#{id.join ','}}"
132
+ elsif id == :all
133
+ '[0-9]*_*'
134
+ elsif id =~ /\A\d{4,8}\z/
135
+ "#{id}*"
136
+ end
137
+ end
138
+
139
+ def load(id)
140
+ v = nil
141
+ file = glob(id.to_a).sort.first
142
+ ::File.open(::File.join(@dir, file), 'r') {|f|
143
+ f.flock ::File::LOCK_SH
144
+ f.binmode
145
+ v = f.read
146
+ f.flock ::File::LOCK_UN
147
+ } if file
148
+ (file[/\.yaml$/] ? YAML.load(v) : v) if v
149
+ end
150
+
151
+ def save(id, v, ext)
152
+ if new_id?(id, v)
153
+ old_id = id
154
+ id = new_id v
155
+ end
156
+
157
+ if ext
158
+ val = v
159
+ ext = 'y' if ext == 'yaml'
160
+ remove_file id
161
+ else
162
+ val = v.ya2yaml(:syck_compatible => true)
163
+ ext = 'yaml'
164
+ end
165
+
166
+ file = "#{file_prefix}#{id}.#{ext}"
167
+ if old_id && f = ::File.open(::File.join(@dir, file), 'a')
168
+ return if f.pos != 0 # duplicate id
169
+ move(old_id, id) unless old_id == :new_id
170
+ end
171
+ ::File.open(::File.join(@dir, file), 'a') {|f|
172
+ f.flock ::File::LOCK_EX
173
+ f.binmode
174
+ f.seek 0
175
+ f.truncate 0
176
+ f << val
177
+ f.flock ::File::LOCK_UN
178
+ f.chmod 0664
179
+ } && id
180
+ end
181
+
182
+ def remove_file(id)
183
+ glob_pattern = "#{file_prefix}#{pattern_for id.to_a}[.-]*"
184
+ files = ::Dir.chdir(@dir) { ::Dir.glob glob_pattern } # may include child files
185
+ files.each {|file|
186
+ ::File.unlink ::File.join(@dir, file)
187
+ }
188
+ end
189
+
190
+ def rename_file(old_id, new_id)
191
+ glob_pattern = "#{file_prefix}#{pattern_for old_id.to_a}*"
192
+ files = ::Dir.chdir(@dir) { ::Dir.glob glob_pattern } # may include child files
193
+ rex = /^\A#{file_prefix}#{old_id}/
194
+ files.each {|file|
195
+ to_file = file.sub(rex, "#{file_prefix}#{new_id}")
196
+ ::File.rename(::File.join(@dir, file), ::File.join(@dir, to_file))
197
+ }
198
+ end
199
+
200
+ end
@@ -0,0 +1,174 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009-2010 Akira FUNAI
5
+
6
+ require 'rubygems'
7
+ require 'sequel'
8
+ require 'yaml'
9
+ require 'ya2yaml'
10
+
11
+ class Runo::Storage::Sequel < Runo::Storage
12
+
13
+ def self.db
14
+ if Runo['storage']['Sequel'] && Runo['storage']['Sequel']['uri']
15
+ @db ||= ::Sequel.connect Runo['storage']['Sequel']['uri']
16
+ self.load_skel unless @db.table_exists? :runo_main
17
+ end
18
+ @db
19
+ end
20
+
21
+ def self.load_skel
22
+ @db.create_table(:runo_main) {
23
+ String :full_name
24
+ String :ext
25
+ String :owner
26
+ String :body
27
+ Blob :binary_body
28
+ primary_key :full_name
29
+ } unless @db.table_exists? :runo_main
30
+ Runo::Storage::File.traverse('/', Runo['skin_dir']) {|entry|
31
+ @db[:runo_main].insert(
32
+ :full_name => entry[:full_name],
33
+ :ext => entry[:ext],
34
+ :owner => entry[:val]['_owner'],
35
+ :body => entry[:val].ya2yaml(:syck_compatible => true),
36
+ :binary_body => (entry[:ext] == 'yaml') ? nil : entry[:val].to_sequel_blob
37
+ ) unless @db[:runo_main][:full_name => entry[:full_name]]
38
+ }
39
+ end
40
+
41
+ def self.available?
42
+ self.db
43
+ end
44
+
45
+ def initialize(sd)
46
+ super
47
+ @dataset = Runo::Storage::Sequel.db[:runo_main]
48
+ @dirname = @sd[:full_name]
49
+ end
50
+
51
+ def val(id = nil)
52
+ if id
53
+ load id
54
+ else
55
+ # this could be HUGE.
56
+ _select_all({}).inject({}) {|v, id|
57
+ v[id] = load id
58
+ v
59
+ }
60
+ end
61
+ end
62
+
63
+ def build(v)
64
+ clear
65
+ v.each {|id, v| store(id, v) } if v
66
+ self
67
+ end
68
+
69
+ def clear
70
+ @dataset.grep(:full_name, _full_name('%')).delete
71
+ self
72
+ end
73
+
74
+ def store(id, v, ext = nil)
75
+ save(id, v, ext)
76
+ end
77
+
78
+ def delete(id)
79
+ @dataset.filter(_conds id).delete &&
80
+ @dataset.grep(:full_name, _full_name("#{id}-%")).and(~:ext => 'yaml').delete &&
81
+ id
82
+ end
83
+
84
+ def move(old_id, new_id)
85
+ rename(old_id, new_id) && new_id
86
+ end
87
+
88
+ private
89
+
90
+ def _select_by_id(conds)
91
+ @dataset.filter(_conds conds[:id]).collect {|v| _id v[:full_name] }
92
+ end
93
+
94
+ def _select_by_d(conds)
95
+ @dataset.
96
+ grep(:full_name, _full_name("#{conds[:d]}%")).
97
+ and(:ext => 'yaml').
98
+ collect {|v| _id v[:full_name] }
99
+ end
100
+
101
+ def _select_all(conds)
102
+ @dataset.
103
+ grep(:full_name, _full_name('%')).
104
+ and(:ext => 'yaml').
105
+ collect {|v| _id v[:full_name] }
106
+ end
107
+
108
+ # TODO: overrride _sort() & _page() (they can be done in _select_*)
109
+
110
+ def _conds(id)
111
+ (id == :all) ? {} : {:full_name => _full_name(id)}
112
+ end
113
+
114
+ def _full_name(id)
115
+ "#{@dirname}-#{id}"
116
+ end
117
+
118
+ def _id(full_name)
119
+ full_name.sub("#{@dirname}-", '')
120
+ end
121
+
122
+ def load(id)
123
+ # TODO: cache in _select_*
124
+ v = @dataset[:full_name => _full_name(id)]
125
+ (v[:ext] == 'yaml' ? YAML.load(v[:body]) : v[:binary_body]) if v
126
+ end
127
+
128
+ def save(id, v, ext)
129
+ Runo::Storage::Sequel.db.transaction {
130
+ if new_id?(id, v)
131
+ old_id = id
132
+ id = new_id v
133
+ end
134
+
135
+ full_name = _full_name id
136
+
137
+ if ext
138
+ ext = 'y' if ext == 'yaml'
139
+ val = {
140
+ :full_name => full_name,
141
+ :ext => ext,
142
+ :binary_body => v.to_sequel_blob,
143
+ }
144
+ else
145
+ val = {
146
+ :full_name => full_name,
147
+ :ext => 'yaml',
148
+ :owner => v['_owner'],
149
+ :body => v.ya2yaml(:syck_compatible => true),
150
+ }
151
+ end
152
+
153
+ if old_id
154
+ return if @dataset[:full_name => full_name] # duplicate id
155
+ move(old_id, id) unless old_id == :new_id
156
+ end
157
+ if @dataset[:full_name => full_name]
158
+ @dataset[:full_name => full_name] = val
159
+ else
160
+ @dataset.insert val
161
+ end
162
+ id
163
+ }
164
+ end
165
+
166
+ def rename(old_id, new_id)
167
+ @dataset.grep(:full_name, _full_name("#{old_id}%")).each {|v|
168
+ @dataset[:full_name => v[:full_name]] = v.merge(
169
+ :full_name => v[:full_name].sub(_full_name(old_id), _full_name(new_id))
170
+ )
171
+ }
172
+ end
173
+
174
+ end