innate 2009.04.12 → 2009.05

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/AUTHORS +10 -0
  2. data/CHANGELOG +360 -0
  3. data/MANIFEST +8 -8
  4. data/README.md +1 -9
  5. data/Rakefile +7 -5
  6. data/example/app/whywiki_erb/start.rb +1 -1
  7. data/innate.gemspec +9 -5
  8. data/lib/innate.rb +6 -13
  9. data/lib/innate/action.rb +29 -33
  10. data/lib/innate/cache.rb +1 -1
  11. data/lib/innate/cache/drb.rb +5 -5
  12. data/lib/innate/cache/file_based.rb +3 -0
  13. data/lib/innate/cache/marshal.rb +4 -1
  14. data/lib/innate/cache/memory.rb +1 -2
  15. data/lib/innate/cache/yaml.rb +4 -1
  16. data/lib/innate/current.rb +11 -13
  17. data/lib/innate/dynamap.rb +5 -0
  18. data/lib/innate/helper.rb +11 -9
  19. data/lib/innate/helper/aspect.rb +9 -9
  20. data/lib/innate/helper/cgi.rb +3 -0
  21. data/lib/innate/helper/link.rb +2 -2
  22. data/lib/innate/helper/redirect.rb +1 -1
  23. data/lib/innate/helper/render.rb +70 -7
  24. data/lib/innate/helper/send_file.rb +9 -1
  25. data/lib/innate/log/color_formatter.rb +19 -13
  26. data/lib/innate/node.rb +38 -35
  27. data/lib/innate/options/dsl.rb +5 -2
  28. data/lib/innate/request.rb +1 -1
  29. data/lib/innate/response.rb +1 -7
  30. data/lib/innate/route.rb +4 -0
  31. data/lib/innate/session.rb +16 -14
  32. data/lib/innate/state.rb +10 -11
  33. data/lib/innate/state/accessor.rb +8 -8
  34. data/lib/innate/traited.rb +15 -10
  35. data/lib/innate/version.rb +1 -1
  36. data/lib/innate/view.rb +41 -4
  37. data/lib/innate/view/erb.rb +1 -2
  38. data/lib/innate/view/etanni.rb +9 -12
  39. data/spec/innate/action/layout.rb +0 -3
  40. data/spec/innate/helper/flash.rb +0 -3
  41. data/spec/innate/helper/link.rb +8 -0
  42. data/spec/innate/helper/redirect.rb +11 -0
  43. data/spec/innate/helper/render.rb +32 -0
  44. data/spec/innate/node/node.rb +1 -0
  45. data/spec/innate/options.rb +5 -1
  46. data/tasks/authors.rake +30 -0
  47. data/tasks/release.rake +48 -9
  48. data/tasks/ycov.rake +84 -0
  49. metadata +21 -13
  50. data/lib/innate/core_compatibility/basic_object.rb +0 -10
  51. data/lib/innate/core_compatibility/string.rb +0 -3
  52. data/lib/innate/state/fiber.rb +0 -74
  53. data/lib/innate/state/thread.rb +0 -47
  54. data/spec/innate/state/fiber.rb +0 -58
  55. data/spec/innate/state/thread.rb +0 -40
@@ -46,6 +46,9 @@ module Innate
46
46
  Rack::Utils.escape_html(input.to_s).gsub(/#([{@$]@?)/, '#\1')
47
47
  end
48
48
  alias h html_and_code_escape
49
+
50
+ # aliases are ignored by module_function...
51
+ module_function :u, :h
49
52
  end
50
53
  end
51
54
  end
@@ -28,12 +28,12 @@ module Innate
28
28
  hashes, names = args.partition{|arg| arg.respond_to?(:merge!) }
29
29
  hashes.each{|to_merge| hash.merge!(to_merge) }
30
30
 
31
+ escape = Rack::Utils.method(:escape)
31
32
  location = route_location(self)
32
- front = Array[location, name, *names].join('/').squeeze('/')
33
+ front = Array[location, name, *names.map{|n| escape[n]}].join('/').squeeze('/')
33
34
 
34
35
  return URI(front) if hash.empty?
35
36
 
36
- escape = Rack::Utils.method(:escape)
37
37
  query = hash.map{|k, v| "#{escape[k]}=#{escape[v]}" }.join(';')
38
38
  URI("#{front}?#{query}")
39
39
  end
@@ -57,7 +57,7 @@ module Innate
57
57
  end
58
58
 
59
59
  def raw_redirect(target, options = {}, &block)
60
- header = {'Location' => target.to_s}
60
+ header = response.header.merge('Location' => target.to_s)
61
61
  status = options[:status] || 302
62
62
  body = options[:body] || redirect_body(target)
63
63
 
@@ -5,7 +5,6 @@ module Innate
5
5
  #
6
6
  # @example of added functionality
7
7
  # YourController.render_partial(:foo, :x => 42)
8
- #
9
8
  def self.included(into)
10
9
  into.extend(self)
11
10
  end
@@ -20,6 +19,14 @@ module Innate
20
19
  #
21
20
  # As usual, patches welcome.
22
21
  #
22
+ # @example usage
23
+ #
24
+ # render_full('/blog/article/1')
25
+ # render_full('/blog/article/1', :lang => :de)
26
+ #
27
+ # Please note that you have to give the full path in the same way you'd
28
+ # do in a direct request with curl or a browser.
29
+ #
23
30
  # @api external
24
31
  # @see Mock.session
25
32
  # @author manveru
@@ -38,17 +45,44 @@ module Innate
38
45
  end
39
46
 
40
47
  # Renders an action without any layout.
48
+ # You can further tweak the action to be rendered by passing a block.
49
+ #
50
+ # @example usage
51
+ #
52
+ # render_partial(:index)
53
+ # render_partial(:index, :title => :foo)
54
+ #
55
+ # Please note that you only have to supply the action name, if your
56
+ # action requires arguments then you have to pass a name suitable for
57
+ # that.
58
+ #
59
+ # @example usage with action that requires arguments
60
+ #
61
+ # # requires two arguments
62
+ # def foo(a, b)
63
+ # end
64
+ #
65
+ # # pass two suitable arguments
66
+ # render_partial('foo/1/2')
67
+ #
41
68
  # @api external
42
69
  # @see render_custom
43
70
  # @author manveru
44
71
  def render_partial(action_name, variables = {})
45
72
  render_custom(action_name, variables) do |action|
46
73
  action.layout = nil
74
+ yield(action) if block_given?
47
75
  end
48
76
  end
49
77
 
50
78
  # Renders an action view, doesn't execute any methods and won't wrap it
51
79
  # into a layout.
80
+ # You can further tweak the action to be rendered by passing a block.
81
+ #
82
+ # @example usage
83
+ #
84
+ # render_view(:index)
85
+ # render_view(:index, :title => :foo)
52
86
  #
53
87
  # @api external
54
88
  # @see render_custom
@@ -57,11 +91,42 @@ module Innate
57
91
  render_custom(action_name, variables) do |action|
58
92
  action.layout = nil
59
93
  action.method = nil
94
+ yield(action) if block_given?
95
+ end
96
+ end
97
+
98
+ # Use the given file as a template and render it in the same scope as
99
+ # the current action.
100
+ # The +filename+ may be an absolute path or relative to the process
101
+ # working directory.
102
+ #
103
+ # @example usage
104
+ #
105
+ # path = '/home/manveru/example/app/todo/view/index.xhtml'
106
+ # render_file(path)
107
+ # render_file(path, :title => :foo)
108
+ #
109
+ # Ramaze will emit a warning if you try to render an Action without a
110
+ # method or view template, but will still try to render it.
111
+ # The usual {Action#valid?} doesn't apply here, as sometimes you just
112
+ # cannot have a method associated with a template.
113
+ #
114
+ # @api external
115
+ # @see render_custom
116
+ # @author manveru
117
+ def render_file(filename, variables = {})
118
+ render_custom(action.path, variables) do |action|
119
+ action.layout = nil
120
+ action.method = nil
121
+ action.view = filename
122
+ yield(action) if block_given?
60
123
  end
61
124
  end
62
125
 
63
126
  def render_custom(action_name, variables = {})
64
- action = resolve(action_name.to_s)
127
+ unless action = resolve(action_name.to_s)
128
+ raise(ArgumentError, "No Action %p on #{self}" % [action_name])
129
+ end
65
130
 
66
131
  action.sync_variables(self.action)
67
132
  action.instance = action.node.new
@@ -69,11 +134,9 @@ module Innate
69
134
 
70
135
  yield(action) if block_given?
71
136
 
72
- if action.valid?
73
- action.render
74
- else
75
- Log.warn("Invalid action: %p" % action)
76
- end
137
+ valid_action = action.view || action.method
138
+ Log.warn("Empty action: %p" % [action]) unless valid_action
139
+ action.render
77
140
  end
78
141
  end
79
142
  end
@@ -3,12 +3,20 @@ module Innate
3
3
  module SendFile
4
4
  # Not optimally performing but convenient way to send files by their
5
5
  # filename.
6
- def send_file(filename, content_type = nil)
6
+ #
7
+ # I think we should remove this from the default helpers and move it into
8
+ # Ramaze, the functionality is almost never used, the naming is ambigous,
9
+ # and it doesn't use the send_file capabilities of frontend servers.
10
+ #
11
+ # So for now, I'll mark it for deprecation
12
+ def send_file(filename, content_type = nil, content_disposition = nil)
7
13
  content_type ||= Rack::Mime.mime_type(::File.extname(filename))
14
+ content_disposition ||= File.basename(filename)
8
15
 
9
16
  response.body = ::File.readlines(filename, 'rb')
10
17
  response['Content-Length'] = ::File.size(filename).to_s
11
18
  response['Content-Type'] = content_type
19
+ response['Content-Disposition'] = content_disposition
12
20
  response.status = 200
13
21
 
14
22
  throw(:respond, response)
@@ -1,19 +1,25 @@
1
1
  class Logger
2
2
  # Extended Formatter that supports ANSI colors.
3
+ #
4
+ # The basic mapping of ANSI colors is as follows:
5
+ #
6
+ # | reset | bold | dark | underline | blink | negative
7
+ # MOD | 0 | 1 | 2 | 4 | 5 | 7
8
+ #
9
+ # | black | red | green | yellow | blue | magenta | cyan | white
10
+ # FG | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37
11
+ # BG | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47
12
+ #
13
+ # The output is done by: "\e[#{mod};#{fg};#{bg}m#{string}\e[0m"
14
+ # The suffix is to reset the terminal to the original state again.
3
15
  class ColorFormatter < Formatter
4
16
  LEVEL_COLOR = {
5
- 'DEBUG' => :blue,
6
- 'INFO' => :white,
7
- 'WARN' => :yellow,
8
- 'ERROR' => :red,
9
- 'FATAL' => :red,
10
- 'UNKNOWN' => :green,
11
- }
12
-
13
- COLOR_CODE = {
14
- :reset => 0, :bold => 1, :dark => 2, :underline => 4, :blink => 5,
15
- :negative => 7, :black => 30, :red => 31, :green => 32, :yellow => 33,
16
- :blue => 34, :magenta => 35, :cyan => 36, :white => 37,
17
+ 'DEBUG' => "\e[0;34;40m%s\e[0m", # blue on black
18
+ 'INFO' => "\e[0;37;40m%s\e[0m", # white on black
19
+ 'WARN' => "\e[0;33;40m%s\e[0m", # yellow on black
20
+ 'ERROR' => "\e[0;31;40m%s\e[0m", # red on black
21
+ 'FATAL' => "\e[0;35;40m%s\e[0m", # red on black
22
+ 'UNKNOWN' => "\e[0;32;40m%s\e[0m", # green on black
17
23
  }
18
24
 
19
25
  FORMAT_TIME = "%Y-%m-%d %H:%M:%S"
@@ -33,7 +39,7 @@ class Logger
33
39
  end
34
40
 
35
41
  def colorize(string, severity)
36
- "\e[#{COLOR_CODE[LEVEL_COLOR[severity]]}m#{string}\e[0m"
42
+ LEVEL_COLOR[severity] % string
37
43
  end
38
44
 
39
45
  def self.color?(logdev)
data/lib/innate/node.rb CHANGED
@@ -244,7 +244,7 @@ module Innate
244
244
  # with feedback in your logs.
245
245
  #
246
246
  # A lot of functionality in here relies on the fact that call is executed
247
- # within Innate::STATE.wrap which populates the variables used by Trinity.
247
+ # within Current#call which populates the variables used by Trinity.
248
248
  # So if you use the Node directly as a middleware make sure that you #use
249
249
  # Innate::Current as a middleware before it.
250
250
  #
@@ -261,11 +261,7 @@ module Innate
261
261
  path << '/' if path.empty?
262
262
 
263
263
  response.reset
264
- response = try_resolve(path)
265
-
266
- Current.session.flush(response)
267
-
268
- response.finish
264
+ try_resolve(path).finish
269
265
  end
270
266
 
271
267
  # Let's try to find some valid action for given +path+.
@@ -364,7 +360,7 @@ module Innate
364
360
  def resolve(path)
365
361
  name, wish, engine = find_provide(path)
366
362
  node = (respond_to?(:ancestors) && respond_to?(:new)) ? self : self.class
367
- action = Action.create(:node => node, :wish => wish, :engine => engine)
363
+ action = Action.create(:node => node, :wish => wish, :engine => engine, :path => path)
368
364
 
369
365
  if content_type = node.ancestral_trait["#{wish}_content_type"]
370
366
  action.options = {:content_type => content_type}
@@ -742,8 +738,6 @@ module Innate
742
738
  #
743
739
  # Since Innate supports multiple paths to templates the +path+ has to be an
744
740
  # Array that may be nested one level.
745
- # The +path+ is then translated by {Node#path_glob} and the +wish+ by
746
- # {Node#ext_glob}.
747
741
  #
748
742
  # @example Usage to find available templates
749
743
  #
@@ -779,7 +773,6 @@ module Innate
779
773
  #
780
774
  # @api external
781
775
  # @see Node#find_view Node#to_layout Node#find_aliased_view
782
- # Node#path_glob Node#ext_glob
783
776
  # @author manveru
784
777
  def to_template(path, wish)
785
778
  to_view(path, wish) || to_layout(path, wish)
@@ -802,22 +795,27 @@ module Innate
802
795
 
803
796
  def update_mapping_shared(paths)
804
797
  mapping = {}
798
+ paths.reject!{|path| !File.directory?(path) }
805
799
 
806
800
  provides.each do |wish_key, engine|
807
801
  wish = wish_key[/(.*)_handler/, 1]
808
- ext_glob = ext_glob(wish)
802
+ exts = possible_exts_for(wish)
809
803
 
810
804
  paths.reverse_each do |path|
811
- ::Dir.glob(::File.join(path, "/**/*.#{ext_glob}")) do |file|
812
- case file.sub(path, '').gsub('/', '__')
813
- when /^(.*)\.(.*)\.(.*)$/
814
- action_name, wish_ext, engine_ext = $1, $2, $3
815
- when /^(.*)\.(.*)$/
816
- action_name, wish_ext, engine_ext = $1, wish, $2
805
+ Find.find(path) do |file|
806
+ exts.each do |ext|
807
+ next unless file =~ ext
808
+
809
+ case file.sub(path, '').gsub('/', '__')
810
+ when /^(.*)\.(.*)\.(.*)$/
811
+ action_name, wish_ext, engine_ext = $1, $2, $3
812
+ when /^(.*)\.(.*)$/
813
+ action_name, wish_ext, engine_ext = $1, wish, $2
814
+ end
815
+
816
+ mapping[wish_ext] ||= {}
817
+ mapping[wish_ext][action_name] = file
817
818
  end
818
-
819
- mapping[wish_ext] ||= {}
820
- mapping[wish_ext][action_name] = file
821
819
  end
822
820
  end
823
821
  end
@@ -825,32 +823,37 @@ module Innate
825
823
  return mapping
826
824
  end
827
825
 
826
+ # Answer with an array of possible paths in order of significance for
827
+ # template lookup of the given +mappings+.
828
+ #
829
+ # @param [#map] An array two Arrays of inner and outer directories.
830
+ #
831
+ # @return [Array]
832
+ # @see update_view_mappings update_layout_mappings update_template_mappings
833
+ # @author manveru
828
834
  def possible_paths_for(mappings)
829
- root_mappings.map{|root_mapping|
830
- mappings.first.map{|outer_mapping|
831
- mappings.last.map{|inner_mapping|
832
- File.join(root_mapping, outer_mapping, inner_mapping, '/')
833
- }
834
- }
835
- }.flatten
835
+ root_mappings.map{|root|
836
+ mappings.first.map{|inner|
837
+ mappings.last.map{|outer|
838
+ ::File.join(root, inner, outer, '/') }}}.flatten
836
839
  end
837
840
 
838
- # Produce a glob that can be processed by Dir::[] matching the extensions
839
- # associated with the given +wish+.
841
+ # Answer with an array of possible extensions in order of significance for
842
+ # the given +wish+.
840
843
  #
841
844
  # @param [#to_s] wish the extension (no leading '.')
842
845
  #
843
- # @return [String] glob matching the valid exts for the given +wish+
846
+ # @return [Array] list of exts valid for this +wish+
844
847
  #
845
848
  # @api internal
846
849
  # @see Node#to_template View::exts_of Node#provides
847
850
  # @author manveru
848
- def ext_glob(wish)
851
+ def possible_exts_for(wish)
849
852
  pr = provides
850
853
  return unless engine = pr["#{wish}_handler"]
851
- engine_exts = View.exts_of(engine).join(',')
852
- represented = [*wish].map{|k| "#{k}." }.join(',')
853
- "{%s,}{%s}" % [represented, engine_exts]
854
+ View.exts_of(engine).map{|e_ext|
855
+ [[*wish].map{|w_ext| /#{w_ext}\.#{e_ext}$/ }, /#{e_ext}$/]
856
+ }.flatten
854
857
  end
855
858
 
856
859
  # For compatibility with new Kernel#binding behaviour in 1.9
@@ -929,7 +932,7 @@ module Innate
929
932
  # @author manveru
930
933
  def layout_mappings
931
934
  paths = [*ancestral_trait[:layouts]]
932
- paths = [mapping] if paths.empty?
935
+ paths = ['/'] if paths.empty?
933
936
 
934
937
  [[*options.layouts].flatten, [*paths].flatten]
935
938
  end
@@ -78,7 +78,8 @@ module Innate
78
78
  # :doc, :value keys will be ignored
79
79
  def option(doc, key, value, other = {}, &block)
80
80
  trigger = block || other[:trigger]
81
- convert = {:doc => doc.to_s, :value => value, :trigger => trigger}
81
+ convert = {:doc => doc.to_s, :value => value}
82
+ convert[:trigger] = trigger if trigger
82
83
  @hash[key.to_sym] = other.merge(convert)
83
84
  end
84
85
  alias o option
@@ -115,7 +116,9 @@ module Innate
115
116
  # @param [Array] keys
116
117
  # @param [Object] value
117
118
  def set_value(keys, value)
118
- get(*keys)[:value] = value
119
+ got = get(*keys)
120
+ return got[:value] = value if got
121
+ raise(IndexError, "There is no option available for %p" % [keys])
119
122
  end
120
123
 
121
124
  # Retrieve only the :value from the value hash if found via +keys+.
@@ -56,7 +56,7 @@ module Innate
56
56
  # * values_at
57
57
 
58
58
  class Request < Rack::Request
59
- # Currently handled request from Innate::STATE[:request]
59
+ # Currently handled request from Thread.current[:request]
60
60
  # Call it from anywhere via Innate::Request.current
61
61
  def self.current
62
62
  Current.request
@@ -1,9 +1,4 @@
1
1
  module Innate
2
-
3
- # In order to reset the body contents we also need to reset the length set by
4
- # Response#write - until I can submit a patch to Rack and the next release we
5
- # just do this.
6
-
7
2
  class Response < Rack::Response
8
3
  include Optioned
9
4
 
@@ -12,8 +7,6 @@ module Innate
12
7
  :headers, {'Content-Type' => 'text/html'}
13
8
  end
14
9
 
15
- attr_accessor :length
16
-
17
10
  def reset
18
11
  self.status = 200
19
12
  self.header.delete('Content-Type')
@@ -24,6 +17,7 @@ module Innate
24
17
 
25
18
  def finish
26
19
  options.headers.each{|key, value| self[key] ||= value }
20
+ Current.session.flush(self)
27
21
  super
28
22
  end
29
23
  end
data/lib/innate/route.rb CHANGED
@@ -5,6 +5,10 @@ module Innate
5
5
  #
6
6
  # This middleware should wrap Innate::DynaMap.
7
7
  #
8
+ # Please note that Rack::File is put before Route and Rewrite, that means
9
+ # that you cannot apply routes to static files unless you add your own route
10
+ # middleware before.
11
+ #
8
12
  # String routers are the simplest way to route in Innate. One path is
9
13
  # translated into another:
10
14
  #
@@ -9,12 +9,12 @@ module Innate
9
9
  # You may store anything in here that you may also store in the corresponding
10
10
  # store, usually it's best to keep it to things that are safe to Marshal.
11
11
  #
12
- # The default time of expiration is *
13
- #
12
+ # The default time of expiration is:
14
13
  # Time.at(2147483647) # => Tue Jan 19 12:14:07 +0900 2038
15
14
  #
16
15
  # Hopefully we all have 64bit systems by then.
17
-
16
+ #
17
+ # The Session instance is compatible with the specification of rack.session.
18
18
  class Session
19
19
  include Optioned
20
20
 
@@ -40,13 +40,17 @@ module Innate
40
40
  @flash = Flash.new(self)
41
41
  end
42
42
 
43
- def []=(key, value)
43
+ # Rack interface
44
+
45
+ def store(key, value)
44
46
  cache_sid[key] = value
45
47
  end
48
+ alias []= store
46
49
 
47
- def [](key)
50
+ def fetch(key, value = nil)
48
51
  cache_sid[key]
49
52
  end
53
+ alias [] fetch
50
54
 
51
55
  def delete(key)
52
56
  cache_sid.delete(key)
@@ -57,9 +61,7 @@ module Innate
57
61
  @cache_sid = nil
58
62
  end
59
63
 
60
- def cache_sid
61
- @cache_sid ||= cache[sid] || {}
62
- end
64
+ # Additional interface
63
65
 
64
66
  def flush(response = @response)
65
67
  return if !@cache_sid or @cache_sid.empty?
@@ -70,6 +72,12 @@ module Innate
70
72
  set_cookie(response)
71
73
  end
72
74
 
75
+ private
76
+
77
+ def cache_sid
78
+ @cache_sid ||= cache[sid] || {}
79
+ end
80
+
73
81
  def sid
74
82
  @sid ||= cookie || generate_sid
75
83
  end
@@ -78,12 +86,6 @@ module Innate
78
86
  @request.cookies[options.key]
79
87
  end
80
88
 
81
- def inspect
82
- cache.inspect
83
- end
84
-
85
- private
86
-
87
89
  def cache
88
90
  Innate::Cache.session
89
91
  end