nitro 0.3.0 → 0.4.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 (148) hide show
  1. data/ChangeLog +284 -0
  2. data/{LICENCE → LICENSE} +1 -1
  3. data/README +13 -17
  4. data/RELEASES +13 -1
  5. data/Rakefile +1 -9
  6. data/bin/cluster.rb +5 -5
  7. data/examples/blog/README +45 -0
  8. data/examples/blog/apache.conf +0 -0
  9. data/examples/blog/app.rb +21 -0
  10. data/examples/blog/config.rb +88 -0
  11. data/examples/blog/lib/blog.rb +104 -0
  12. data/examples/blog/log/app.log +117 -0
  13. data/examples/blog/root/comments.xhtml +38 -0
  14. data/examples/blog/root/entry_form.xhtml +18 -0
  15. data/examples/blog/root/index.xhtml +43 -0
  16. data/examples/blog/root/login.xhtml +24 -0
  17. data/examples/blog/root/m/bubbles.gif +0 -0
  18. data/examples/blog/root/m/comments_curve.gif +0 -0
  19. data/examples/blog/root/m/down.gif +0 -0
  20. data/examples/blog/root/m/footer_bg.gif +0 -0
  21. data/examples/blog/root/m/garrow.gif +0 -0
  22. data/examples/blog/root/m/gbull.gif +0 -0
  23. data/examples/blog/root/m/grbull.gif +0 -0
  24. data/examples/blog/root/m/h1_bg.gif +0 -0
  25. data/examples/blog/root/m/header_bg.gif +0 -0
  26. data/examples/blog/root/m/obull.gif +0 -0
  27. data/examples/blog/root/m/page_bg.gif +0 -0
  28. data/examples/blog/root/m/side_title_bg.gif +0 -0
  29. data/examples/blog/root/m/sidebar_bg.gif +0 -0
  30. data/examples/blog/root/recent_posts.xhtml +14 -0
  31. data/examples/blog/root/style.css +201 -0
  32. data/examples/blog/root/style.xsl +118 -0
  33. data/examples/blog/root/view_entry.xhtml +29 -0
  34. data/examples/og/run.rb +27 -29
  35. data/examples/tiny/README +3 -4
  36. data/examples/tiny/app.rb +6 -16
  37. data/examples/tiny/config.rb +30 -0
  38. data/examples/tiny/log/app.log +23 -0
  39. data/examples/tiny/root/{index.sx → index.xhtml} +3 -6
  40. data/lib/{n/std.rb → nitro.rb} +9 -4
  41. data/lib/{n → nitro}/application.rb +13 -7
  42. data/lib/{n → nitro}/config.rb +38 -8
  43. data/lib/{n → nitro}/events.rb +1 -1
  44. data/lib/{n → nitro}/l10n.rb +1 -1
  45. data/lib/{n → nitro}/logger.rb +8 -8
  46. data/lib/{n → nitro}/macros.rb +3 -3
  47. data/lib/{n → nitro}/mixins.rb +1 -1
  48. data/lib/nitro/properties.rb +301 -0
  49. data/lib/{n → nitro}/server.rb +2 -2
  50. data/lib/{n → nitro}/server/appserver.rb +14 -5
  51. data/lib/{n → nitro}/server/cluster.rb +5 -5
  52. data/lib/{n → nitro}/server/cookie.rb +1 -1
  53. data/lib/nitro/server/dispatcher.rb +66 -0
  54. data/lib/{n → nitro}/server/filters.rb +7 -7
  55. data/lib/{n → nitro}/server/filters/autologin.rb +3 -3
  56. data/lib/{n → nitro}/server/fragment.rb +3 -3
  57. data/lib/{n → nitro}/server/handlers.rb +3 -3
  58. data/lib/nitro/server/render.rb +200 -0
  59. data/lib/{n → nitro}/server/request.rb +6 -6
  60. data/lib/{n → nitro}/server/requestpart.rb +5 -5
  61. data/lib/{n → nitro}/server/script.rb +3 -3
  62. data/lib/{n → nitro}/server/server.rb +4 -4
  63. data/lib/{n → nitro}/server/session.rb +3 -3
  64. data/lib/nitro/server/shaders.rb +165 -0
  65. data/lib/{n → nitro}/server/user.rb +1 -1
  66. data/lib/nitro/server/webrick.rb +175 -0
  67. data/lib/nitro/service.rb +25 -0
  68. data/lib/{n → nitro}/sitemap.rb +2 -2
  69. data/lib/{n → nitro}/ui/date-select.rb +0 -0
  70. data/lib/{n → nitro}/ui/pager.rb +1 -1
  71. data/lib/{n → nitro}/ui/popup.rb +1 -1
  72. data/lib/{n → nitro}/ui/select.rb +1 -1
  73. data/lib/{n → nitro}/ui/tabs.rb +1 -1
  74. data/lib/{n → nitro}/utils/array.rb +1 -1
  75. data/lib/{n → nitro}/utils/cache.rb +1 -1
  76. data/lib/{n → nitro}/utils/gfx.rb +1 -1
  77. data/lib/{n → nitro}/utils/hash.rb +1 -1
  78. data/lib/{n → nitro}/utils/html.rb +1 -1
  79. data/lib/{n → nitro}/utils/http.rb +1 -1
  80. data/lib/{n → nitro}/utils/mail.rb +1 -1
  81. data/lib/{n → nitro}/utils/number.rb +1 -1
  82. data/lib/{n → nitro}/utils/pool.rb +1 -1
  83. data/lib/{n → nitro}/utils/string.rb +19 -95
  84. data/lib/{n → nitro}/utils/template.rb +0 -0
  85. data/lib/{n → nitro}/utils/time.rb +1 -1
  86. data/lib/{n → nitro}/utils/uri.rb +3 -3
  87. data/lib/nitro/version.rb +11 -0
  88. data/lib/{n/og.rb → og.rb} +61 -31
  89. data/lib/{n/og → og}/backend.rb +13 -7
  90. data/lib/{n/og → og}/backends/mysql.rb +43 -39
  91. data/lib/{n/og → og}/backends/psql.rb +42 -38
  92. data/lib/{n/og → og}/connection.rb +21 -9
  93. data/lib/{n/og → og}/meta.rb +18 -12
  94. data/lib/xsl/base.xsl +11 -88
  95. data/test/n/server/tc_cookie.rb +1 -1
  96. data/test/n/server/tc_filters.rb +1 -1
  97. data/test/n/server/tc_request.rb +3 -3
  98. data/test/n/server/tc_requestpart.rb +2 -2
  99. data/test/n/server/tc_session.rb +1 -1
  100. data/test/n/tc_events.rb +1 -1
  101. data/test/n/tc_og.rb +16 -18
  102. data/test/n/tc_properties.rb +22 -18
  103. data/test/n/tc_sitemap.rb +2 -2
  104. data/test/n/ui/tc_pager.rb +4 -4
  105. data/test/n/utils/tc_cache.rb +1 -1
  106. data/test/n/utils/tc_hash.rb +1 -1
  107. data/test/n/utils/tc_html.rb +1 -1
  108. data/test/n/utils/tc_http.rb +1 -1
  109. data/test/n/utils/tc_number.rb +1 -1
  110. data/test/n/utils/tc_strings.rb +1 -46
  111. data/test/n/utils/tc_uri.rb +1 -1
  112. metadata +101 -108
  113. data/examples/simple/README +0 -42
  114. data/examples/simple/app.rb +0 -31
  115. data/examples/simple/conf/apache.conf +0 -100
  116. data/examples/simple/conf/config.rb +0 -72
  117. data/examples/simple/conf/debug-config.rb +0 -26
  118. data/examples/simple/conf/live-config.rb +0 -26
  119. data/examples/simple/conf/requires.rb +0 -43
  120. data/examples/simple/ctl +0 -32
  121. data/examples/simple/env.rb +0 -32
  122. data/examples/simple/install.rb +0 -12
  123. data/examples/simple/lib/articles/entities.rb +0 -37
  124. data/examples/simple/lib/articles/lc-en.rb +0 -36
  125. data/examples/simple/lib/articles/methods.rb +0 -55
  126. data/examples/simple/lib/articles/part.rb +0 -57
  127. data/examples/simple/root/add-article.sx +0 -15
  128. data/examples/simple/root/article-form.ss +0 -20
  129. data/examples/simple/root/comments-form.ss +0 -16
  130. data/examples/simple/root/comments.si +0 -30
  131. data/examples/simple/root/index.sx +0 -44
  132. data/examples/simple/root/shader/shader.xsl +0 -100
  133. data/examples/simple/root/shader/style.css +0 -9
  134. data/examples/simple/root/view-article.sx +0 -29
  135. data/examples/tiny/conf/config.rb +0 -62
  136. data/examples/tiny/conf/requires.rb +0 -33
  137. data/examples/tiny/ctl +0 -16
  138. data/lib/n/parts.rb +0 -157
  139. data/lib/n/properties.rb +0 -199
  140. data/lib/n/server/dispatcher.rb +0 -55
  141. data/lib/n/server/handlers/code-handler.rb +0 -182
  142. data/lib/n/server/handlers/page-handler.rb +0 -612
  143. data/lib/n/server/webrick.rb +0 -283
  144. data/lib/n/shaders.rb +0 -166
  145. data/lib/n/sync/clc.rb +0 -110
  146. data/lib/n/sync/handler.rb +0 -229
  147. data/lib/n/sync/server.rb +0 -176
  148. data/lib/p/README +0 -1
@@ -1,199 +0,0 @@
1
- # = Properties support
2
- #
3
- # code:
4
- # * George Moschovitis <gm@navel.gr>
5
- # design:
6
- # * Anastastios Koutoumanos <ak@navel.gr>
7
- # * Elias Karakoulakis <ekarak@ktismata.com>
8
- #
9
- # (c) 2004 Navel, all rights reserved.
10
- # $Id: properties.rb 116 2004-10-29 16:26:32Z gmosx $
11
-
12
- require "n/utils/array"
13
- require "n/utils/hash"
14
-
15
- module N
16
-
17
- # Property Metadata
18
- #
19
- # Ruby attributes are typeless and generally this is good. Some times
20
- # we need extra metadata though, for example in relational mapping, or
21
- # web form population.
22
- #
23
- # Only Fixnums, Strings, Floats, Times, Booleans are converted.
24
- #
25
- # The default = methods do not force the types. A special
26
- # __force_set method should be used instead.
27
- #
28
- # TODO:
29
- # Perhaps a sync is needed in evals (!!!!)
30
- #
31
- # === Design:
32
- #
33
- # - add default value in the extra sql
34
- #
35
- class Property
36
- # the symbol of the property
37
- attr_accessor :symbol
38
- # the string representation of the symbol
39
- attr_accessor :name
40
- # the class of the property
41
- attr_accessor :klass
42
- # extra sql options for the property. if set,
43
- # it must contain the sql_type for this class, the
44
- # default is overriden.
45
- attr_accessor :sql
46
-
47
- def initialize(symbol, klass, sql = nil)
48
- @symbol, @klass = symbol, klass
49
- @sql = sql
50
- @name = @symbol.to_s()
51
- end
52
-
53
- def ==(other)
54
- return @symbol == other.symbol
55
- end
56
- end
57
-
58
- end # module
59
-
60
- class Module
61
-
62
- # Properties
63
- # An array is used to enforce order.
64
- attr_accessor :__props
65
-
66
- # Metadata
67
- # A hash of module metadata.
68
- attr_accessor :__meta
69
-
70
- # Define a property (== typed attribute)
71
- # based on draks property redesign!
72
- #
73
- # ex:
74
- # prop_accessor String, :title, :body
75
- # prop_accessor String, "char(10) NOT NULL", :title, :body
76
- #
77
- def prop_accessor(*params)
78
- klass = params.shift
79
-
80
- # if the next param is a String, it is
81
- # extra sql metadata.
82
- if params.first.is_a?(String)
83
- sql = params.shift
84
- else
85
- sql = nil
86
- end
87
-
88
- # the rest of params are symbols
89
-
90
- @__props = N::SafeArray.new() unless @__props
91
-
92
- params.each do |s|
93
- add_prop(N::Property.new(s, klass, sql))
94
- end
95
- end
96
- # to be compatible with ruby parlance
97
- alias_method :prop, :prop_accessor
98
-
99
- # Add the property
100
- # Optimize this
101
- #
102
- def add_prop(prop)
103
- if idx = @__props.index(prop)
104
- # override in case of duplicates. Keep the order of the props.
105
- @__props[idx] = prop
106
- else
107
- @__props << prop
108
- end
109
-
110
- # Precompile the property read/write methods
111
-
112
- s, klass = prop.symbol, prop.klass
113
-
114
- module_eval %{
115
- def #{s}
116
- return @#{s}
117
- end
118
-
119
- def #{s}=(val)
120
- @#{s} = val
121
- end
122
-
123
- def __force_#{s}(val)
124
- @#{s} = } + case klass.name
125
- when Fixnum.name
126
- "val.to_i()"
127
- when String.name
128
- "val.to_s()"
129
- when Float.name
130
- "val.to_f()"
131
- when Time.name
132
- "Time.parse(val.to_s())"
133
- when TrueClass.name, FalseClass.name
134
- "val.to_i() > 0"
135
- else
136
- "val"
137
- end + %{
138
- end
139
- }
140
- end
141
-
142
- # Attach metadata
143
- #
144
- def meta(key, val)
145
- @__meta = N::SafeHash.new unless @__meta
146
- add_meta(key, val)
147
- end
148
-
149
- # Add the metadata
150
- # TODO: Optimize this
151
- #
152
- def add_meta(key, val)
153
- @__meta[key] = [] unless @__meta[key]
154
-
155
- # guard against duplicates, no need to keep order.
156
- @__meta[key].delete_if { |v| val == v }
157
- @__meta[key] << val
158
- end
159
-
160
- # This method is typically called before including other
161
- # modules to preserve properties order.
162
- #
163
- def inherit_meta(mod = superclass)
164
- # concat props.
165
- if mod.__props
166
- @__props = N::SafeArray.new unless @__props
167
-
168
- mod.__props.each { |p|
169
- add_prop(p)
170
- }
171
- end
172
-
173
- # concat metadata
174
- if mod.__meta
175
- mod.__meta.each { |k, val|
176
- val.each { |v|
177
- meta(k, v)
178
- } if val
179
- }
180
- end
181
- end
182
-
183
- # Override the default include method to also include the metadata.
184
- # WARNING: can cause infinite loop if overriden again!
185
- #
186
- alias_method :old_include, :include
187
-
188
- # A new version of include that includes the properties too.
189
- #
190
- def include(*modules)
191
- old_include(*modules)
192
-
193
- for m in modules
194
- inherit_meta(m)
195
- end
196
- end
197
-
198
- end
199
-
@@ -1,55 +0,0 @@
1
- module N
2
-
3
- #
4
- #
5
- class Dispatcher
6
-
7
- def process(request)
8
- service = ...
9
- method = ...
10
- query_string = ...
11
- end
12
-
13
- end
14
-
15
- end # module
16
-
17
- __END__
18
-
19
- /login
20
- /logout
21
-
22
- /service/method?params
23
-
24
-
25
- /fora/view_forum/1
26
-
27
- /fora/view_forum?fid=1
28
-
29
-
30
- /fora/view_forum.sx
31
-
32
- if file exists evaluate in the controller a render method.
33
-
34
- .xhtml
35
- .xinc
36
-
37
- class ForaController
38
-
39
- def view_forum
40
- @parent = Forum.db_get(request["pid"])
41
- end
42
-
43
- def render_view_forum
44
- end
45
-
46
- end
47
-
48
- <?r for message in @parent.messages ?>
49
- <li>#{message.title}</li>
50
- <?r end ?>
51
-
52
-
53
- one method, different views
54
- html,
55
- xml (for rpc)
@@ -1,182 +0,0 @@
1
- # code:
2
- # * George Moschovitis <gm@navel.gr>
3
- #
4
- # (c) 2004 Navel, all rights reserved.
5
- # $Id: code-handler.rb 112 2004-10-27 10:59:55Z gmosx $
6
-
7
- require "cgi"
8
- require "singleton"
9
- require "sync"
10
-
11
- require "n/utils/cache"
12
- require "n/utils/uri"
13
- require "n/server/script"
14
- require "n/server/fragment"
15
- require "n/server/handlers"
16
-
17
- module N
18
-
19
- # = CodeHandler
20
- #
21
- # web server handler that executes ruby code (logic)
22
- # Caching is NOT SUPPORTED (and not needed anyway)
23
- #
24
- # TODO:
25
- #
26
- # probably extend PageHandler from CodeHandler and not vice versa.
27
- #
28
- class CodeHandler < ScriptHandler
29
-
30
- # process is called ONLY for top level scripts.
31
- #
32
- # no need for synchronization, the page-script is thread safe.
33
- # if you use thread-unsafe code in your script you are responsible
34
- # for synchronization.
35
- #
36
- # TODO:
37
- # add timing code here
38
- #
39
- def process(request)
40
- # gmosx, FIXME: temporarily here, move somewhere ELSE!
41
-
42
- request.locale = $lc_map[request.user.locale] || $lc_en
43
- script = get_compiled_script(request.path)
44
- fragment = eval_script(script, request)
45
-
46
- # walk the filter pipeline
47
- super
48
-
49
- return fragment, script
50
- end
51
-
52
- # evaluate the request script in the context of the current request.
53
- # returns the output of the script, ie the fragment body.
54
- #
55
- # === Design:
56
- #
57
- # I think that the script caching logic should be here, because it nicelly
58
- # encapsulates transform and compile.
59
- #
60
- def eval_script(script, request)
61
-
62
- $log.debug "Evaluating, #{request.path}" if $DBG
63
-
64
- fragment = Fragment.new(:key)
65
-
66
- # try to render the script, report errors.
67
- begin
68
- fragment.body = script.__render(request)
69
- rescue N::ScriptExitException => see
70
- # the script raised a ScripteExitException to force a premature
71
- # end. Surpress this error!
72
- rescue => ex
73
- log_error(request, ex)
74
- end
75
-
76
- return fragment
77
- end
78
-
79
- # === Output:
80
- # defcode: the code that customizes the base script class.
81
- # pagecode: the code that renders the fragment.
82
- #
83
- # === REMARKS:
84
- # - this method is evaluated in compile time, so we cannot pass
85
- # or use the request/request pair. gmosx: NO, we do pass request,
86
- # some data can and SHOULD be used to prepare the transform.
87
- #
88
- def transform_script(path, hash, shader = nil)
89
-
90
- path = overload_path(path)
91
-
92
- # load the page text from the script
93
- pagecode = ""
94
-
95
- pagecode = File.read("#$root_dir/#{path}")
96
-
97
- # convert to ruby code
98
- pagecode.gsub!(/<<</, "__out << %{")
99
- pagecode.gsub!(/>>>/, "}")
100
-
101
- script = %{
102
- class CodeScript_#{hash} < CodeScript
103
- def initialize(path)
104
- super
105
- @pagecode = <<-'PAGECODE'
106
- #{pagecode}
107
- PAGECODE
108
- @defcode = nil
109
- end
110
- def __render(request)
111
- __out = ""
112
- lc = request.locale
113
- #{pagecode}
114
- return __out
115
- end
116
- end
117
- return CodeScript_#{hash}.new("#{path}")
118
- }
119
-
120
- return script
121
- end
122
-
123
- # try to get the script from the cache. invalidates the compiled version and return nil
124
- # if the script is modified since compile time. Also takes active shader and localization
125
- # into account. If script is not compiled, transform and compile it.
126
- #
127
- # Output:
128
- # the compiled script. Throws exception if the script does not exist.
129
- #
130
- def get_compiled_script(path, hash = nil)
131
- compiled_script = nil
132
- key = calc_script_key(path, hash)
133
-
134
- # gmosx: $reload_scripts is typically set to true when debugging
135
- # statically included files (.ss)
136
- unless $reload_scripts
137
-
138
- compiled_script = @@compiled_script_cache[key]
139
-
140
- # gmosx: monitor scripts should be explicit!
141
- if $srv_monitor_scripts and compiled_script and (File.mtime(compiled_script.path) > compiled_script.__create_time)
142
- $log.debug "Script '#{path}' externaly modified, recompiling" if $DBG
143
- compiled_script = nil
144
- end
145
-
146
- end
147
-
148
- unless compiled_script
149
- # the script is not cached, so load, transform and compile it!
150
- $log.debug "Compiling script '#{key}'" if $DBG
151
-
152
- script = transform_script(path, key)
153
- compiled_script = compile_script(script)
154
-
155
- @@compiled_script_cache[key] = compiled_script
156
- end
157
-
158
- return compiled_script
159
- end
160
-
161
- def calc_script_key(path, hash)
162
- return "#{path}#{hash}".gsub(/[^\w]/, "")
163
- end
164
-
165
- end # CodeHandler
166
-
167
- # = Codescript
168
- #
169
- # encapsulates the script defining Ruby Code to execute.
170
- # effectively acts as a Generator.
171
- #
172
- # === Requirements:
173
- #
174
- # - the generated class should be a singleton, no need to stress
175
- # the garbage collector.
176
- # - the render method should be thread safe.
177
- #
178
- class CodeScript < Script
179
-
180
- end # CodeScript
181
-
182
- end # module
@@ -1,612 +0,0 @@
1
- # code:
2
- # * George Moschovitis <gm@navel.gr>
3
- # * Anastasios Koutoumanos <ak@navel.gr>
4
- #
5
- # (c) 2004 Navel, all rights reserved.
6
- # $Id: page-handler.rb 112 2004-10-27 10:59:55Z gmosx $
7
-
8
- require "cgi"
9
- require "singleton"
10
- require "sync"
11
-
12
- require "n/utils/cache"
13
- require "n/utils/uri"
14
- require "n/server/script"
15
- require "n/server/fragment"
16
- require "n/server/handlers"
17
-
18
- module N
19
-
20
- # = PageHandler
21
- #
22
- # web server handler that render xml pages (.sx scripts).
23
- # This is the main handler of the Nitro Application Server.
24
- #
25
- # The handler evaluates the given script. The result of
26
- # this evaluation is called a Fragment. The result of a top
27
- # level script, ie a top level fragments is called a page.
28
- #
29
- # == Advantages over the original .rx format:
30
- #
31
- # - xml based
32
- # - strips <!-- comments
33
- # - allows multiline comments
34
- # - allows pre-transformation with xlst (free transformation)
35
- # - allows validation of xhtml
36
- # - allows syntax highlighting
37
- #
38
- # == TODO:
39
- #
40
- # - move shader selection in a script method, that can
41
- # be overriden (user specific shaders).
42
- # - compress the output some more, every byte
43
- # counts (bandwidth == money)
44
- # - use something like FormValidator to validate parameters
45
- # for sub-fragments.
46
- #
47
- # === FIXME:
48
- # - correct encoding of fragment hash
49
- # (query string, shader, user etc)
50
- #
51
- # === Design:
52
- #
53
- # break the rendering process in sub methods to make testable
54
- # and verifiable (and easier to read).
55
- #
56
- # Statically included scripts (.ss) SHOULD be valid xml files (even
57
- # though they are not required) to be automatically verifiable and
58
- # compatible with editors. The xml prologue (<?xml / <root>) is removed.
59
- #
60
- # Be carefull about statically included files scope issues. This is the
61
- # reason why we do not use statically included files everywhere.
62
- #
63
- # === WARNING:
64
- #
65
- # no need to lock cache io, we use a safe cache!
66
- #
67
- class PageHandler < ScriptHandler
68
-
69
- # process is called ONLY for top level scripts.
70
- #
71
- # no need for synchronization, the page-script is thread safe.
72
- # if you use thread-unsafe code in your script you are responsible
73
- # for synchronization.
74
- #
75
- # TODO:
76
- # add timing code here
77
- #
78
- def process(request)
79
- # gmosx, FIXME: temporarily here, move somewhere ELSE!
80
-
81
- request.locale = $lc_map[request.user.locale] || $lc_en
82
- request.shader = calc_shader(request)
83
- request.tag = calc_tag(request)
84
- request.top_script = script = get_compiled_script(request.path, request.tag, request.shader)
85
-
86
- # Action requests should be uncacheable, to be safe.
87
- # No need for a check here! all actions should use consume which
88
- # sets the uncacheable flag.
89
- #
90
- # request.uncacheable = true if request.delete("*act*")
91
- # request.uncacheable = true if request.query_string =~ /\$.*\$/
92
-
93
- script.__init_render(request)
94
-
95
- # dont cache admin pages, FIXME: use less checks here!!
96
-
97
- if script.__cache?(request) and (not request.uncacheable) and
98
- (not request.session["ADMIN_MODE"]) and (not $reload_scripts)
99
- if etag = script.__etag(request)
100
- request.headers["cache-control"] = "pubic, must-revalidate"
101
- request.headers["etag"] = etag
102
- end
103
- else
104
- # gmosx, FIXME: add the correct cache control header here!!
105
- etag = nil
106
- end
107
-
108
- if etag && etag == request.headers["IF-NONE-MATCH"]
109
- request.set_not_modified!
110
- else
111
- fragment = eval_script(script, request)
112
- end
113
-
114
- # walk the filter pipeline
115
- super
116
-
117
- return fragment, script
118
- end
119
-
120
- # Process sub level scripts.
121
- #
122
- def sub_process(request)
123
- script = get_compiled_script(request.path, request.tag, request.shader)
124
- script.__init_render(request)
125
- fragment = eval_script(script, request)
126
-
127
- return fragment, script
128
- end
129
-
130
- # evaluate the request script in the context of the current request.
131
- # returns the output of the script, ie the fragment body.
132
- #
133
- # === Design:
134
- #
135
- # I think that the script caching logic should be here, because it nicelly
136
- # encapsulates transform and compile.
137
- #
138
- def eval_script(script, request)
139
-
140
- etag = script.__etag(request)
141
-
142
- unless fragment = script.__cache_get(etag)
143
-
144
- # no suitable fragment exists in the cache, so render the script.
145
- $log.debug "Rendering, #{request.path}" if $DBG
146
-
147
- fragment = Fragment.new
148
-
149
- # try to render the script, report errors.
150
- begin
151
- fragment.body = script.__render(request)
152
-
153
- if script.__cache?(request)
154
- script.__cache_put(etag, fragment) unless request.uncacheable
155
- end
156
- rescue N::ScriptExitException => see
157
- # the script raised a ScripteExitException to force a premature
158
- # end. Surpress this error!
159
- rescue => ex
160
- log_error(request, ex)
161
- end
162
-
163
- end
164
-
165
- return fragment
166
- end
167
-
168
- # === Output:
169
- # defcode: the code that customizes the base script class.
170
- # pagecode: the code that renders the fragment.
171
- #
172
- # === TODO:
173
- # - we have to keep the original script code to apply multiple shaders
174
- #
175
- # === REMARKS:
176
- # - this method is evaluated in compile time, so we cannot pass
177
- # or use the request/request pair. gmosx: NO, we do pass request,
178
- # some data can and SHOULD be used to prepare the transform.
179
- #
180
- def transform_script(path, hash, shader = nil)
181
-
182
- path = overload_path(path)
183
-
184
- initcode = ""
185
- defcode = ""
186
- sub_scripts = []
187
- filename = "#$root_dir/#{path}"
188
- cached_filename = ".cache/#{shader}#{path}.rb"
189
-
190
- # load the page text from the script.
191
- # check if a cached transformed script exists.
192
- # also staticaly includes all <?include xxx ?> files.
193
- # this also validates the xhtml (cool side-effect).
194
- begin
195
- document = ::File.read(filename)
196
-
197
- # calculate mtime
198
- mtime = ::File.stat(filename).mtime
199
- document.scan(/<\?include xl:href="(.*?)"(.*)\?>/) { |match|
200
- match = overload_path(match[0])
201
- imtime = ::File.stat("#$root_dir/#{match}").mtime
202
- mtime = imtime if imtime > mtime
203
- }
204
- # also check the shader mtime
205
- # FIXME: even better should check mtime now!
206
- mtime = shader.mtime if shader.mtime > mtime
207
-
208
- # calculate subscripts
209
- document.scan(/<x:include xl:href="(.*?)"(.*)>/) { |match|
210
- match = overload_path(match[0])
211
- sub_path = "#{match.split("?")[0]}"
212
- sub_scripts << get_compiled_script(sub_path, hash, shader)
213
- }
214
-
215
- # check if a cached transformed script exists
216
- # FIXME: encode shader and shader mtime.
217
- if false # ::File.exists?(cached_filename) and ::File.stat(cached_filename).mtime > mtime
218
- return ::File.read(cached_filename), sub_scripts
219
- end
220
-
221
- $log.debug "Transforming '#{path}'" if $DBG
222
-
223
- # static includes
224
- # the target file is included at compile time.
225
- #
226
- # gmosx: must be xformed before the <?r pi.
227
- # ex:
228
- # <?include xl:href="root/myfile.sx" ?>
229
- #
230
- document.gsub!(/<\?include xl:href="(.*?)"(.*)\?>/) { |match|
231
- # gmosx: xmm match matches the whole string.
232
- match = overload_path($1)
233
- load_statically_included("#$root_dir/#{match}")
234
- }
235
-
236
- # dynamic includes
237
- # the target file is included at run time
238
- #
239
- document.gsub!(/<x:include xl:href="(.*?)"(.*)(.?)\/>/) { |match|
240
- "<?r __out << __include('#$1', request) ?>"
241
- }
242
-
243
- # expand method macro
244
- #
245
- document.gsub!(/\@\?(.*?)(['|"|\s])/, '#{_a(request, %^\1^)}\2')
246
-
247
- # localisation
248
- # gmosx, OPTIMIZE in the future i could pre translate the strings!
249
- document.gsub!(/\|:(.*?)\|/, '#{lc[:\1]}')
250
- document.gsub!(/\!:(.*?)\|(.*?)\!/, '#{lc[:\1].call(\2)}')
251
-
252
- # extract the definition code. The <?def .. ?> will
253
- # be ignored afterwards.
254
- #
255
- document.scan(/<\?def(.*?)\?>/m) { |match|
256
- defcode << $1 << "\n"
257
- }
258
-
259
- # extract the initialization phase code. The <?i .. ?> will
260
- # be ignored afterwards.
261
- #
262
- document.scan(/<\?i(.*?)\?>/m) { |match|
263
- initcode << $1 << "\n"
264
- }
265
-
266
- rescue Exception, StandardError => e
267
- $log.error pp_exception(e)
268
- raise RuntimeError.new
269
- end
270
-
271
- # pre-transform the script.
272
- pagecode = shader.transform(document)
273
-
274
- # preprocess the script to convert to valid ruby code.
275
-
276
- # strip the xml header! (interracts with the following gsub!)
277
- # FIXME: perhaps the xslt could strip this?
278
- pagecode.gsub!(/<\?xml.*\?>/, "")
279
-
280
- # xform the processing instructions, use <?ruby, <?rb, <?r as
281
- # a marker.
282
- pagecode.gsub!(/\?>/, "\n__out << %{")
283
- pagecode.gsub!(/<\?ruby /, "}\n")
284
- pagecode.gsub!(/<\?ruby /, "}\n")
285
- pagecode.gsub!(/<\?r /, "}\n")
286
-
287
- # tml, TODO: resolve static injects! scan injects and update the metadata
288
- # in the page-graph.
289
-
290
- pagecode = %@__out << %{#{pagecode}}@
291
-
292
- # gmosx: unescape quotes etc, that are escaped by the xslt processor.
293
- # can we avoid this ???
294
- # yes: the xslt processors escapes code in #{ } brackets, we should
295
- # use <?= ?> brackets.
296
- #
297
- # The new version of render supports output buffering ala php.
298
- # __out_buffers keeps a stack of buffers.
299
-
300
- pagecode = CGI.unescapeHTML(pagecode)
301
- key = calc_script_key(path, hash)
302
-
303
- script = %{
304
- class PageScript#{key} < PageScript
305
- def initialize(path)
306
- super
307
- @key = "#{key}"
308
- end
309
- #{defcode}
310
- def __init_render(request)
311
- #{initcode}
312
- end
313
- def __render(request)
314
- lc = request.locale
315
-
316
- __out_buffers = nil
317
-
318
- __out = ""
319
- if request.is_top?
320
- __out = %|<?xml version="1.0"?>
321
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">|
322
- end
323
-
324
- #{pagecode}
325
- return __out
326
- end
327
- end
328
- compiled_script = PageScript#{key}.new("#{path}")
329
- }
330
-
331
- # remove excessive white space
332
- # gmosx: FIXME: i can do better here: remove leading space per
333
- # line for example.
334
- script.squeeze!(" \t")
335
-
336
- # cache the transformed script.
337
- #
338
- dir = N::StringUtils.directory_from_path(cached_filename)
339
- FileUtils.mkdir_p(dir)
340
- File.open(cached_filename, "w") { |f|
341
- f << script
342
- }
343
-
344
- return script, sub_scripts
345
- end
346
-
347
- # try to get the script from the cache. invalidates the compiled version and return nil
348
- # if the script is modified since compile time. Also takes active shader and localization
349
- # into account. If script is not compiled, transform and compile it.
350
- #
351
- # Output:
352
- # the compiled script. Throws exception if the script does not exist.
353
- #
354
- def get_compiled_script(path, hash, shader)
355
- compiled_script = nil
356
- key = calc_script_key(path, hash)
357
-
358
- # gmosx: $reload_scripts is typically set to true when debugging
359
- # statically included files (.ss)
360
- unless $reload_scripts
361
-
362
- compiled_script = @@compiled_script_cache[key]
363
-
364
- # gmosx: monitor scripts should be explicit!
365
- if $srv_monitor_scripts and compiled_script and (File.mtime(compiled_script.path) > compiled_script.__create_time)
366
- $log.debug "Script '#{path}' externaly modified, recompiling" if $DBG
367
- compiled_script = nil
368
- end
369
-
370
- end
371
-
372
- unless compiled_script
373
- # the script is not cached, so load, transform and compile it!
374
- $log.debug "Compiling script '#{key}'" if $DBG
375
-
376
- script, sub_scripts = transform_script(path, hash, shader)
377
- compiled_script = compile_script(script)
378
- compiled_script.sub_scripts = sub_scripts unless sub_scripts.empty?
379
-
380
- @@compiled_script_cache[key] = compiled_script
381
- end
382
-
383
- return compiled_script
384
- end
385
-
386
- #-----------------------------------------------------------------------------
387
- # Hooks
388
-
389
- # Calculate shader for this request.
390
- #
391
- # TODO: allow per include shader!
392
- #
393
- def calc_shader(request)
394
- return $default_shader
395
- # return $shaders[request.user.shader]
396
- end
397
-
398
- # Standard encoding/modification/customization of the fragment_hash.
399
- # Override this in your application to provide customized encoding.
400
- # For example encode the shader id.
401
- #
402
- def calc_tag(request)
403
- return "#{request.locale[:locale]}#{request.shader.name}"
404
- end
405
-
406
- def calc_script_key(path, hash)
407
- return "#{path}#{hash}".gsub(/[^\w]/, "")
408
- end
409
-
410
- #-----------------------------------------------------------------------------
411
- # Utils
412
-
413
- # loads a script and prepares it for statically inclusion by removing
414
- # the optional xml prologue/epilogue.
415
- #
416
- def load_statically_included(filename)
417
- $log.debug "Statically including '#{filename}'" if $DBG
418
-
419
- code = File.read(filename)
420
- code.gsub!(/<\?xml.*\?>/, "")
421
- code.gsub!(/<\/?root(.*?)>/m, " ");
422
-
423
- return code
424
- end
425
-
426
- end # PageHandler
427
-
428
- # = Pagescript
429
- #
430
- # encapsulates the script defining a specific xml page.
431
- # effectively acts as a Generator.
432
- #
433
- # === Requirements:
434
- #
435
- # - the generated class should be a singleton, no need to stress
436
- # the garbage collector.
437
- # - the render method should be thread safe.
438
- #
439
- # == Design:
440
- #
441
- # - only pass request and request for simplicity
442
- # __session can be deducted from request, and __user/__errors from
443
- # __session.
444
- #
445
- # === Injection:
446
- #
447
- # Injection is the inclusion of subscripts in a super (parent) script.
448
- # There are two types of inclusion, dynamic and static. In contrary to what
449
- # you may believe, dynamix inclusion IS needed in cases where you decide
450
- # what tou include at runtime. A synthesized "my" page is a good example.
451
- # However, n1 overused dynamic inclusion. In most cases static inclusion
452
- # works just fine. Another interesting optimization is when you dynamically
453
- # include but not recursivelly. The full, recursive and dynamic include is
454
- # really seldomly needed. However in our implementation there is no difference
455
- # between inject_once / inject_recursive.
456
- #
457
- # === Class generation example:
458
- #
459
- # WARNING: the example may be deprecated!
460
- #
461
- # source script:
462
- #
463
- # <?xml version='1.0'?>
464
- # <?ruby
465
- # a = 5
466
- # b = request["bval"]
467
- # ?>
468
- # <html>
469
- # <body>
470
- # this is it a = #{a}, b = #{b} and c = #{request["cval"]}<br/>
471
- # cool huh?
472
- # </body>
473
- # </html>
474
- #
475
- # generated class:
476
- #
477
- # class PageScript__index
478
- # def render(request)
479
- # out = ""
480
- # a = 5
481
- # b = request["bval"]
482
- # __out << %{
483
- # <html>
484
- # <body>
485
- # this is it a = #{a}, b = #{b} and c = #{request["cval"]}<br/>
486
- # cool huh?
487
- # </body>
488
- # </html>
489
- # }
490
- # return __out
491
- # end
492
- # end
493
- #
494
- # === Future:
495
- #
496
- # - use catbuffer for optimized appends.
497
- #
498
- class PageScript < Script
499
- # <x:include xl:href='...'/> implementation
500
- # Dynamically include ("inject") a subpage (fragment) in this page.
501
- #
502
- # gmosx, SOS: This is a new version for dynamic inclusion that doesnt
503
- # generate subrequest objects. For use with new (v3) code.
504
- #
505
- # === Design:
506
- #
507
- # To keep this method simple (and as a small optimization) we used
508
- # to not allow a query string when including. Pass the parameters
509
- # as request arguments or parameters. Here is an example:
510
- #
511
- # <?r request_set_arg("max_message", 5); request["admin"] = true ?>
512
- # <x:include xl:href='*parts/messages/view-messages.xi'/>
513
- #
514
- # However to support legacy code, to follow the REST design guidelines,
515
- # and because it turned out to be easy thanks to the refactoring, the
516
- # query string is supported. Passing params through the request object
517
- # is suggested though (and essential to pass ruby objects). The query
518
- # string of the included string is converted to arguments to avoid
519
- # poluting the parent request query string.
520
- #
521
- # Still, the preferred way is to use the args hash to pass special
522
- # arguments to included scripts.
523
- # <?r request.set_arg("maxitems", 5) ?>
524
- # <x:include xl:href="p/list.si" />
525
- #
526
- # If you want to include the parents query string for caching purposes
527
- # call with parent = true!
528
- #
529
- # TODO: add unit test for this.
530
- #
531
- def __include(uri, request, parent = false)
532
- begin
533
- $log.devel "Including #{uri}"
534
-
535
- # get script real path and type
536
- script_path, type, args, qs = N::UriUtils.decode(uri)
537
-
538
- # update the dependencies set, we only need the key as flag,
539
- # so just insert true. Calculating the dependencies this
540
- # way is simple and not too inefficient.
541
- #
542
- # @dependencies[script_path] = true
543
-
544
- request.level += 1
545
- request.path = script_path
546
-
547
- # Build the cache key for this request.
548
- # attach the query string of the parent. Needed for
549
- # example in fragments with pagers. Do NOT do this
550
- # by default, or excessive numbers of fragments will
551
- # be generated.
552
- if parent
553
- request.fragment_hash = "#{qs}#{request.query_string}"
554
- else
555
- request.fragment_hash = qs
556
- end
557
-
558
- # add arguments passes through the query string.
559
- request.parameters.update(args)
560
-
561
- # TODO: select by regexp
562
- # FIXME: hacky implementation, NO Need for this test, always
563
- # PageHandler.
564
- if handler = $srv_extension_map[type]
565
- handler = handler[1]
566
- else
567
- $log.error "No handler for extension '#{type}'"
568
- unless N::StringUtils.valid?(type)
569
- $log.error "Perhaps you have a syntax error in your include statement or you didnt pass the uri"
570
- end
571
- end
572
-
573
- fragment, script = handler.sub_process(request)
574
-
575
- # restore parent request
576
- request.level -= 1
577
-
578
- # gmosx: one idea was to clear the args here to avoid a class of bugs.
579
- # But i think it is better to trust the developer to do this, thus
580
- # giving him greater flexibility.
581
- # gmosx: NO !!! practice shows that it is NOT GOOD to trust the
582
- # developer :)
583
- # Hmm clearing the args also poses problems though :(
584
- #
585
- # request.args.clear()
586
-
587
- return fragment.body
588
-
589
- rescue ScriptHandlerError => e
590
- # allready handled!
591
- rescue Exception, StandardError => e
592
- # gmosx: this block is used to catch possible errors in the inject
593
- # implementation if we do not catch these errors here, they
594
- # propagate to the caller that prints a NON usefull message, that
595
- # can waste a developers time.
596
- #
597
- $log.error "error in INCLUDE IMPLEMENTATION while including: #{uri}"
598
- # gmosx: too noisy, only uncomment if you get the above error!!
599
- $log.error pp_exception(e)
600
-
601
- # Following our design goal of chaos reduction (ie small changes
602
- # should result in small results) we drink the exception and
603
- # output an error flag. So the rest of the page is rendered and
604
- # the server error handler is NOT triggered.
605
- end
606
-
607
- return "(error)"
608
- end
609
-
610
- end # PageScript
611
-
612
- end # module