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