bike 0.2.1

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 (151) hide show
  1. data/LICENSE +19 -0
  2. data/README.rdoc +124 -0
  3. data/bin/bike +35 -0
  4. data/lib/_error.rb +14 -0
  5. data/lib/_field.rb +260 -0
  6. data/lib/_i18n.rb +144 -0
  7. data/lib/_parser.rb +256 -0
  8. data/lib/_path.rb +86 -0
  9. data/lib/_storage/_storage.rb +215 -0
  10. data/lib/_storage/file.rb +201 -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/bike.rb +396 -0
  32. data/lib/meta/_meta.rb +20 -0
  33. data/lib/meta/group.rb +19 -0
  34. data/lib/meta/id.rb +59 -0
  35. data/lib/meta/owner.rb +21 -0
  36. data/lib/meta/timestamp.rb +118 -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 +196 -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 +73 -0
  95. data/skel/skin/index.html +39 -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/qux/index.html +8 -0
  111. data/t/skin/foo/qux/moo/index.html +6 -0
  112. data/t/skin/foo/sub-20100306_0001.yaml +3 -0
  113. data/t/skin/index.yaml +3 -0
  114. data/t/skin/t_attachment/index.html +13 -0
  115. data/t/skin/t_contact/done.html +6 -0
  116. data/t/skin/t_contact/index.html +9 -0
  117. data/t/skin/t_file/index.html +16 -0
  118. data/t/skin/t_img/index.html +14 -0
  119. data/t/skin/t_img/test.jpg +0 -0
  120. data/t/skin/t_select/index.html +9 -0
  121. data/t/skin/t_store/index.html +9 -0
  122. data/t/skin/t_summary/20100326_0001.yaml +3 -0
  123. data/t/skin/t_summary/create.html +9 -0
  124. data/t/skin/t_summary/index.html +9 -0
  125. data/t/skin/t_summary/summary.html +9 -0
  126. data/t/t.rb +27 -0
  127. data/t/test_bike.rb +768 -0
  128. data/t/test_call.rb +1281 -0
  129. data/t/test_checkbox.rb +273 -0
  130. data/t/test_field.rb +330 -0
  131. data/t/test_file.rb +900 -0
  132. data/t/test_i18n.rb +325 -0
  133. data/t/test_id.rb +215 -0
  134. data/t/test_img.rb +328 -0
  135. data/t/test_meta.rb +57 -0
  136. data/t/test_parser.rb +1516 -0
  137. data/t/test_password.rb +188 -0
  138. data/t/test_radio.rb +226 -0
  139. data/t/test_role.rb +249 -0
  140. data/t/test_select.rb +182 -0
  141. data/t/test_set_complex.rb +527 -0
  142. data/t/test_set_dynamic.rb +1504 -0
  143. data/t/test_set_folder.rb +515 -0
  144. data/t/test_set_permit.rb +246 -0
  145. data/t/test_set_static.rb +468 -0
  146. data/t/test_storage.rb +915 -0
  147. data/t/test_text.rb +125 -0
  148. data/t/test_textarea.rb +138 -0
  149. data/t/test_timestamp.rb +473 -0
  150. data/t/test_workflow.rb +367 -0
  151. metadata +347 -0
@@ -0,0 +1,77 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009-2010 Akira FUNAI
5
+
6
+ class Bike::Set::Dynamic
7
+
8
+ private
9
+
10
+ def _g_view_ym(arg)
11
+ return unless permit? :read
12
+
13
+ uris = _uri_ym arg
14
+ return unless uris && uris.size > 1
15
+
16
+ year_tmpl = month_tmpl = nil
17
+ div = my[:tmpl][:view_ym] || <<'_tmpl'
18
+ <div class="view_ym">
19
+ <span class="y">
20
+ $(.y) |
21
+ <span class="m">$()</span>
22
+ <br/>
23
+ </span>
24
+ </div>
25
+ _tmpl
26
+ div = Bike::Parser.gsub_block(div, 'y') {|open, inner, close|
27
+ inner = Bike::Parser.gsub_block(inner, 'm') {|*t|
28
+ month_tmpl = t.join
29
+ '$(.months)'
30
+ }
31
+ year_tmpl = open + inner + close
32
+ '$(.years)'
33
+ }
34
+ years = uris.inject({}) {|y, u|
35
+ year = u[/(\d{4})\d\d\/$/, 1]
36
+ y[year] ||= []
37
+ y[year] << u
38
+ y
39
+ }
40
+ p = (my[:order] =~ /^-/) ? 'p=last/' : ''
41
+ div.gsub('$(.years)') {
42
+ years.keys.sort.collect {|year|
43
+ year_tmpl.gsub('$(.y)', year).gsub('$(.months)') {
44
+ years[year].collect {|uri|
45
+ d = uri[/(\d{6})\//, 1]
46
+ y = d[/^\d{4}/]
47
+ m = d[/\d\d$/]
48
+ month_tmpl.gsub(/\$\((?:\.(ym|m))?\)/) {
49
+ label = ($1 == 'ym') ? _label_ym(y, m) : _label_m(m)
50
+ (arg[:conds][:d] == d) ?
51
+ "<span class=\"current\">#{label}</span>" :
52
+ "<a href=\"#{my[:path]}/#{uri}#{p}\">#{label}</a>"
53
+ }
54
+ }.join
55
+ }
56
+ }.join
57
+ }
58
+ end
59
+
60
+ def _uri_ym(arg)
61
+ @storage.__send__(:_sibs_d, :d => '000000').collect {|ym|
62
+ Bike::Path.path_of :d => ym
63
+ }
64
+ end
65
+
66
+ def _label_ym(y, m)
67
+ _('%{year}/%{month}') % {
68
+ :year => y,
69
+ :month => _label_m(m)
70
+ }
71
+ end
72
+
73
+ def _label_m(m)
74
+ _ Date::ABBR_MONTHNAMES[m.to_i]
75
+ end
76
+
77
+ end
@@ -0,0 +1,89 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009 Akira FUNAI
5
+
6
+ class Bike::Workflow
7
+
8
+ include Bike::I18n
9
+
10
+ DEFAULT_META = {
11
+ :item_label => Bike::I18n.n_('item', 'items', 1),
12
+ }
13
+ DEFAULT_SUB_ITEMS = {}
14
+
15
+ ROLE_ADMIN = 0b10000
16
+ ROLE_GROUP = 0b01000
17
+ ROLE_OWNER = 0b00100
18
+ ROLE_USER = 0b00010
19
+ ROLE_NONE = 0b00001
20
+
21
+ PERM = {
22
+ :create => 0b11111,
23
+ :read => 0b11111,
24
+ :update => 0b11111,
25
+ :delete => 0b11111,
26
+ }
27
+
28
+ def self.instance(sd)
29
+ klass = sd[:workflow].to_s.capitalize
30
+ if klass != ''
31
+ self.const_get(klass).new sd
32
+ else
33
+ self.new sd
34
+ end
35
+ end
36
+
37
+ def self.roles(roles)
38
+ %w(admin group owner user none).select {|r|
39
+ roles & const_get("ROLE_#{r.upcase}") > 0
40
+ }.collect{|r| Bike::I18n._ r }
41
+ end
42
+
43
+ attr_reader :sd
44
+
45
+ def initialize(sd)
46
+ @sd = sd
47
+ end
48
+
49
+ def default_sub_items
50
+ self.class.const_get :DEFAULT_SUB_ITEMS
51
+ end
52
+
53
+ def permit?(roles, action)
54
+ case action
55
+ when :login, :done, :message
56
+ true
57
+ when :preview
58
+ # TODO: permit?(roles, action, sub_action = nil)
59
+ (roles & self.class.const_get(:PERM)[:read].to_i) > 0
60
+ else
61
+ (roles & self.class.const_get(:PERM)[action].to_i) > 0
62
+ end
63
+ end
64
+
65
+ def _get(arg)
66
+ @sd.instance_eval {
67
+ if arg[:action] == :create
68
+ item_instance '_001'
69
+ _get_by_tmpl({:action => :create, :conds => {:id => '_001'}}, my[:tmpl][:index])
70
+ end
71
+ }
72
+ end
73
+
74
+ def _hide?(arg)
75
+ (arg[:p_action] && arg[:p_action] != :read) ||
76
+ (arg[:orig_action] == :read && arg[:action] == :submit)
77
+ end
78
+
79
+ def before_commit
80
+ end
81
+
82
+ def after_commit
83
+ end
84
+
85
+ def next_action(base)
86
+ (!base.result || base.result.values.all? {|item| item.permit? :read }) ? :read_detail : :done
87
+ end
88
+
89
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009 Akira FUNAI
5
+
6
+ class Bike::Workflow::Attachment < Bike::Workflow
7
+
8
+ DEFAULT_META = {
9
+ :p_size => 0,
10
+ :item_label => Bike::I18n.n_('attachment', 'attachments', 1),
11
+ }
12
+
13
+ PERM = {
14
+ :create => 0b00000,
15
+ :read => 0b00000,
16
+ :update => 0b00000,
17
+ :delete => 0b00000,
18
+ }
19
+
20
+ def permit?(roles, action)
21
+ (action == :login) ||
22
+ (@sd[:parent] && @sd[:parent].permit?(action))
23
+ end
24
+
25
+ def _get(arg)
26
+ @sd.instance_eval {
27
+ if arg[:action] == :create || arg[:action] == :update
28
+ new_item = item_instance '_001'
29
+
30
+ item_outs = _g_default(arg) {|item, item_arg|
31
+ action = item[:id][Bike::REX::ID_NEW] ? :create : :delete
32
+ button_tmpl = my[:tmpl][:"submit_#{action}"] || <<_html.chomp
33
+ <input type="submit" name="@(short_name).action-#{action}" value="#{_ action.to_s}" />
34
+ _html
35
+ button = item.send(:_get_by_tmpl, {}, button_tmpl)
36
+ item_arg[:action] = :create if action == :create
37
+ item_tmpl = item[:tmpl][:index].sub(/[\w\W]*\$\(.*?\)/, "\\&#{button}")
38
+ item.send(:_get_by_tmpl, item_arg, item_tmpl)
39
+ }
40
+ tmpl = my[:tmpl][:index].gsub('$()', item_outs)
41
+ _get_by_tmpl({:p_action => arg[:p_action], :action => :update}, tmpl)
42
+ end
43
+ }
44
+ end
45
+
46
+ def _hide?(arg)
47
+ arg[:action] == :submit
48
+ end
49
+
50
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009 Akira FUNAI
5
+
6
+ class Bike::Workflow::Blog < Bike::Workflow
7
+
8
+ DEFAULT_META = {
9
+ :p_size => 10,
10
+ :conds => {:d => '999999', :p => 'last'},
11
+ :order => '-id',
12
+ :item_label => Bike::I18n.n_('entry', 'entries', 1),
13
+ }
14
+
15
+ DEFAULT_SUB_ITEMS = {
16
+ '_owner' => {:klass => 'meta-owner'},
17
+ '_group' => {:klass => 'meta-group'},
18
+ '_timestamp' => {:klass => 'meta-timestamp'},
19
+ }
20
+
21
+ PERM = {
22
+ :create => 0b11000,
23
+ :read => 0b11111,
24
+ :update => 0b10100,
25
+ :delete => 0b10100,
26
+ }
27
+
28
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009 Akira FUNAI
5
+
6
+ class Bike::Workflow::Contact < Bike::Workflow
7
+
8
+ DEFAULT_META = {
9
+ :p_size => 10,
10
+ :conds => {:p => 'last'},
11
+ :item_label => Bike::I18n.n_('item', 'items', 1),
12
+ }
13
+
14
+ DEFAULT_SUB_ITEMS = {}
15
+
16
+ PERM = {
17
+ :create => 0b00011,
18
+ :read => 0b11000,
19
+ :update => 0b00000,
20
+ :delete => 0b11000,
21
+ }
22
+
23
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009-2010 Akira FUNAI
5
+
6
+ class Bike::Workflow::Forum < Bike::Workflow
7
+
8
+ DEFAULT_META = {
9
+ :conds => {:p => 'last'},
10
+ :p_size => 10,
11
+ :item_label => Bike::I18n.n_('post', 'posts', 1),
12
+ }
13
+
14
+ DEFAULT_SUB_ITEMS = {
15
+ '_owner' => {:klass => 'meta-owner'},
16
+ '_timestamp' => {:klass => 'meta-timestamp'},
17
+ }
18
+
19
+ PERM = {
20
+ :create => 0b11110,
21
+ :read => 0b11111,
22
+ :update => 0b11000,
23
+ :delete => 0b11000,
24
+ }
25
+
26
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009-2010 Akira FUNAI
5
+
6
+ class Bike::Workflow::Register < Bike::Workflow
7
+
8
+ DEFAULT_META = {
9
+ :p_size => 0,
10
+ :conds => {:p => '1'},
11
+ :order => 'id',
12
+ :item_label => Bike::I18n.n_('item', 'items', 1),
13
+ }
14
+
15
+ DEFAULT_SUB_ITEMS = {
16
+ '_owner' => {:klass => 'meta-owner'},
17
+ '_timestamp' => {:klass => 'meta-timestamp'},
18
+ }
19
+
20
+ PERM = {
21
+ :create => 0b11001,
22
+ :read => 0b11100,
23
+ :update => 0b11100,
24
+ :delete => 0b11100,
25
+ }
26
+
27
+ def before_commit
28
+ @sd.send(:pending_items).each {|id, item|
29
+ if id =~ Bike::REX::ID_NEW
30
+ item.item('_owner').instance_variable_set(:@val, item.item('_id').val)
31
+ end
32
+ }
33
+ end
34
+
35
+ def next_action(base)
36
+ (Bike.client == 'nobody') ? :done : super
37
+ end
38
+
39
+ end
@@ -0,0 +1,396 @@
1
+ # encoding: UTF-8
2
+
3
+ # Author:: Akira FUNAI
4
+ # Copyright:: Copyright (c) 2009 Akira FUNAI
5
+
6
+ class Bike
7
+
8
+ lib_dir = ::File.dirname __FILE__
9
+
10
+ require "#{lib_dir}/_i18n.rb"
11
+ I18n.bindtextdomain('index', ::File.expand_path('../locale', lib_dir))
12
+
13
+ Dir["#{lib_dir}/_*.rb"].sort.each {|file| require file }
14
+ Dir["#{lib_dir}/[a-z]*/*.rb"].sort.each {|file| require file }
15
+ Dir["#{lib_dir}/_*/*.rb"].sort.each {|file| require file }
16
+
17
+ module REX
18
+ ID_SHORT = /[a-z][a-z0-9]*/
19
+ ID = /^(\d{8})_(\d{4,}|#{ID_SHORT})/
20
+ ID_NEW = /^_\d/
21
+ COND = /^(.+?)=(.+)$/
22
+ COND_D = /^(19\d\d|2\d\d\d|9999)\d{0,4}$/
23
+ PATH_ID = /\/((?:19|2\d)\d{6})\/(\d+)/
24
+ TID = /\d{10}\.\d+/
25
+ DIR_STATIC = /css|js|img|imgs|image|images/
26
+ end
27
+
28
+ def self.config(config)
29
+ @config = config
30
+ end
31
+
32
+ def self.[](name)
33
+ @config ||= {}
34
+ @config[name]
35
+ end
36
+
37
+ def self.current
38
+ Thread.current
39
+ end
40
+
41
+ def self.session
42
+ self.current[:session] || ($fake_session ||= {})
43
+ end
44
+
45
+ def self.transaction
46
+ self.session[:transaction] ||= {}
47
+ end
48
+
49
+ def self.client
50
+ self.session[:client] ||= 'nobody'
51
+ end
52
+
53
+ def self.client=(id)
54
+ self.session[:client] = id
55
+ end
56
+
57
+ def self.token
58
+ self.session[:token] ||= rand(36 ** 32).to_s(36)
59
+ end
60
+
61
+ def self.base
62
+ self.current[:base]
63
+ end
64
+
65
+ def self.uri
66
+ self.current[:uri]
67
+ end
68
+
69
+ def self.libdir
70
+ ::File.dirname __FILE__
71
+ end
72
+
73
+ def self.static(env)
74
+ @static ||= Rack::Directory.new Bike['skin_dir']
75
+ response = @static.call env
76
+
77
+ if response.first == 404
78
+ until ::File.readable? ::File.join(
79
+ Bike['skin_dir'],
80
+ env['PATH_INFO'].sub(%r{(/#{Bike::REX::DIR_STATIC}/).*}, '\\1')
81
+ )
82
+ env['PATH_INFO'].sub!(%r{/[^/]+(?=/#{Bike::REX::DIR_STATIC}/)}, '') || break
83
+ end
84
+ @static.call env
85
+ else
86
+ response
87
+ end
88
+ end
89
+
90
+ def call(env)
91
+ uri = "#{env['rack.url_scheme']}://#{env['HTTP_HOST']}#{env['SCRIPT_NAME']}"
92
+ req = Rack::Request.new env
93
+ method = req.request_method.downcase
94
+ params = params_from_request req
95
+ path = req.path_info
96
+ tid = Bike::Path.tid_of path
97
+
98
+ static_path = ::File.expand_path path
99
+ return Bike.static(env) if (
100
+ static_path =~ %r{/#{Bike::REX::DIR_STATIC}/} &&
101
+ static_path !~ %r{/#{Bike::REX::TID}/} &&
102
+ static_path !~ %r{/#{Bike::REX::ID.to_s.sub('^','')}/}
103
+ )
104
+
105
+ Bike::I18n.lang = env['HTTP_ACCEPT_LANGUAGE']
106
+
107
+ Bike.current[:env] = env
108
+ Bike.current[:uri] = uri
109
+ Bike.current[:req] = req
110
+ Bike.current[:session] = env['rack.session']
111
+
112
+ if Bike.transaction[tid].is_a? Bike::Field
113
+ base = Bike.transaction[tid].item Bike::Path.steps_of(path.sub(/\A.*#{Bike::REX::TID}/, ''))
114
+ else
115
+ base = Bike::Path.base_of path
116
+ end
117
+ return response_not_found unless base
118
+
119
+ base[:tid] = tid
120
+ Bike.current[:base] = base
121
+
122
+ begin
123
+ if params[:action] == :logout && params[:token] == Bike.token
124
+ logout(base, params)
125
+ elsif method == 'get'
126
+ get(base, params)
127
+ elsif params[:action] == :login
128
+ login(base, params)
129
+ elsif params[:action] == :preview
130
+ preview(base, params)
131
+ elsif params[:token] != Bike.token
132
+ response_forbidden(:body => 'invalid token')
133
+ elsif Bike.transaction[tid] && !Bike.transaction[tid].is_a?(Bike::Field)
134
+ response_unprocessable_entity(:body => 'transaction expired')
135
+ else
136
+ begin
137
+ post(base, params)
138
+ rescue Bike::Error::Forbidden
139
+ response_forbidden
140
+ end
141
+ end
142
+ rescue Bike::Error::Forbidden
143
+ if params[:action] && Bike.client == 'nobody'
144
+ params[:dest_action] = (method == 'post') ? :index : params[:action]
145
+ params[:action] = :login
146
+ end
147
+ response_unprocessable_entity(:body => _get(base, params)) rescue response_forbidden
148
+ # TODO: rescue Error::System etc.
149
+ end
150
+ end
151
+
152
+ private
153
+
154
+ def login(base, params)
155
+ user = Bike::Set::Static::Folder.root.item('_users', 'main', params['id'].to_s)
156
+ if user && params['pw'].to_s.crypt(user.val('password')) == user.val('password')
157
+ Bike.client = params['id']
158
+ else
159
+ Bike.client = nil
160
+ raise Bike::Error::Forbidden
161
+ end
162
+ path = Bike::Path.path_of params[:conds]
163
+ action = (params['dest_action'] =~ /\A\w+\z/) ? params['dest_action'] : 'index'
164
+ response_see_other(
165
+ :location => "#{Bike.uri}#{base[:path]}/#{path}#{action}.html"
166
+ )
167
+ end
168
+
169
+ def logout(base, params)
170
+ Bike.client = nil
171
+ path = Bike::Path.path_of params[:conds]
172
+ response_see_other(
173
+ :location => "#{Bike.uri}#{base[:path]}/#{path}index.html"
174
+ )
175
+ end
176
+
177
+ def get(base, params)
178
+ if base.is_a? Bike::File
179
+ body = (params[:sub_action] == :small) ? base.thumbnail : base.body
180
+ response_ok(
181
+ :headers => {
182
+ 'Content-Type' => base.val['type'],
183
+ 'Content-Length' => body.to_s.size.to_s,
184
+ },
185
+ :body => body
186
+ )
187
+ else
188
+ response_ok :body => _get(base, params)
189
+ end
190
+ end
191
+
192
+ def preview(base, params)
193
+ Bike.transaction[base[:tid]] ||= base if base[:tid] =~ Bike::REX::TID
194
+
195
+ base.update params
196
+ if base.commit(:temp) || params[:sub_action] == :delete
197
+ id_step = result_step(base, params)
198
+ action = "preview_#{params[:sub_action]}"
199
+ response_see_other(
200
+ :location => "#{Bike.uri}/#{base[:tid]}/#{id_step}#{action}.html"
201
+ )
202
+ else
203
+ params = {:action => :update}
204
+ params[:conds] = {:id => base.errors.keys}
205
+ return response_unprocessable_entity(:body => _get(base, params))
206
+ end
207
+ end
208
+
209
+ def post(base, params)
210
+ Bike.transaction[base[:tid]] ||= base if base[:tid] =~ Bike::REX::TID
211
+
212
+ base.update params
213
+ if params[:status]
214
+ if base[:folder].commit :persistent
215
+ Bike.transaction[base[:tid]] = result_summary base
216
+ action = base.workflow.next_action base
217
+ id_step = result_step(base, params) if base[:parent] == base[:folder] && action != :done
218
+ response_see_other(
219
+ :location => "#{Bike.uri}/#{base[:tid]}#{base[:path]}/#{id_step}#{action}.html"
220
+ )
221
+ else
222
+ params = {:action => :update}
223
+ params[:conds] = {:id => base.errors.keys}
224
+ response_unprocessable_entity :body => _get(base, params)
225
+ end
226
+ else
227
+ base.commit :temp
228
+ id_step = result_step(base, params)
229
+ response_see_other(
230
+ :location => "#{Bike.uri}/#{base[:tid]}/#{id_step}update.html"
231
+ )
232
+ end
233
+ end
234
+
235
+ def result_summary(base)
236
+ (base.result || {}).values.inject({}) {|summary, item|
237
+ item_result = item.result.is_a?(::Symbol) ? item.result : :update
238
+ summary[item_result] = summary[item_result].to_i + 1
239
+ summary
240
+ }
241
+ end
242
+
243
+ def result_step(base, params)
244
+ if base.result
245
+ id = base.result.values.collect {|item| item[:id] }
246
+ else
247
+ id = params.keys.select {|id|
248
+ id.is_a?(::String) && (id[Bike::REX::ID] || id[Bike::REX::ID_NEW])
249
+ }
250
+ end
251
+ Bike::Path.path_of(:id => id)
252
+ end
253
+
254
+ def _get(f, params)
255
+ params[:action] ||= f.default_action
256
+ until f.is_a? Bike::Set::Static::Folder
257
+ params = {
258
+ :action => (f.default_action == :read) ? :read : nil,
259
+ :sub_action => f.send(:summary?, params) ? nil : (params[:sub_action] || :detail),
260
+ f[:id] => params,
261
+ }
262
+ params[:conds] = {:id => f[:id]} if f[:parent].is_a? Bike::Set::Dynamic
263
+ f = f[:parent]
264
+ end if f.is_a? Bike::Set::Dynamic
265
+
266
+ f.get params
267
+ end
268
+
269
+ def params_from_request(req)
270
+ params = {
271
+ :action => Bike::Path.action_of(req.path_info),
272
+ :sub_action => Bike::Path.sub_action_of(req.path_info),
273
+ }
274
+ params.merge! rebuild_params(req.params)
275
+
276
+ params[:conds] ||= {}
277
+ params[:conds].merge! Bike::Path.conds_of(req.path_info)
278
+
279
+ params
280
+ end
281
+
282
+ def rebuild_params(src)
283
+ src.keys.sort.reverse.inject({}) {|params, key|
284
+ name, special = key.split('.', 2)
285
+ steps = name.split '-'
286
+
287
+ if special
288
+ special_id, special_val = special.split('-', 2)
289
+ else
290
+ item_id = steps.pop
291
+ end
292
+
293
+ hash = steps.inject(params) {|v, k| v[k] ||= {} }
294
+ val = src[key]
295
+
296
+ if special_id == 'action'
297
+ action, sub_action = (special_val || val).split('_', 2)
298
+ hash[:action] = action.intern
299
+ hash[:sub_action] = sub_action.intern if sub_action
300
+ elsif special_id == 'status'
301
+ hash[:status] = (special_val || val).intern
302
+ elsif special_id == 'conds'
303
+ hash[:conds] ||= {}
304
+ hash[:conds][special_val.intern] = val
305
+ elsif hash[item_id].is_a? ::Hash
306
+ hash[item_id][:self] = val
307
+ elsif item_id == '_token'
308
+ hash[:token] = val
309
+ else
310
+ hash[item_id] = val
311
+ end
312
+
313
+ params
314
+ }
315
+ end
316
+
317
+ def response_ok(result = {})
318
+ body = result[:body].to_s
319
+ return response_not_found(result) if body.empty?
320
+ [
321
+ 200,
322
+ (
323
+ result[:headers] ||
324
+ {
325
+ 'Content-Type' => 'text/html',
326
+ 'Content-Length' => body.size.to_s,
327
+ }
328
+ ),
329
+ [body],
330
+ ]
331
+ end
332
+
333
+ def response_no_content(result = {})
334
+ [
335
+ 204,
336
+ (result[:headers] || {}),
337
+ []
338
+ ]
339
+ end
340
+
341
+ def response_see_other(result = {})
342
+ body = <<_html
343
+ <a href="#{result[:location]}">see other</a>
344
+ _html
345
+ [
346
+ 303,
347
+ {
348
+ 'Content-Type' => 'text/html',
349
+ 'Content-Length' => body.size.to_s,
350
+ 'Location' => result[:location],
351
+ },
352
+ [body]
353
+ ]
354
+ end
355
+
356
+ def response_forbidden(result = {})
357
+ body = result[:body] || 'Forbidden'
358
+ [
359
+ 403,
360
+ {
361
+ 'Content-Type' => 'text/html',
362
+ 'Content-Length' => body.size.to_s,
363
+ },
364
+ [body],
365
+ ]
366
+ end
367
+
368
+ def response_not_found(result = {})
369
+ body = result[:body] || 'Not Found'
370
+ [
371
+ 404,
372
+ {
373
+ 'Content-Type' => 'text/html',
374
+ 'Content-Length' => body.size.to_s,
375
+ },
376
+ [body]
377
+ ]
378
+ end
379
+
380
+ def response_unprocessable_entity(result = {})
381
+ body = result[:body].to_s
382
+ return response_not_found(result) if body.empty?
383
+ [
384
+ 422,
385
+ (
386
+ result[:headers] ||
387
+ {
388
+ 'Content-Type' => 'text/html',
389
+ 'Content-Length' => body.size.to_s,
390
+ }
391
+ ),
392
+ [body],
393
+ ]
394
+ end
395
+
396
+ end