Class: Bijou::Processor
- Inherits:
-
Object
- Object
- Bijou::Processor
- Defined in:
- lib/bijou/processor.rb
Overview
The processor encapsulates the loading and parsing of a component class and any referenced containers or components.
Constant Summary collapse
- ComponentPrefix =
'Component_'
- CacheSuffix =
'_cache'
- CacheSubdir =
'cache'
- PathSepRE =
/\/|\\/
Class Method Summary collapse
- .class_from_filename(prefix, filename) ⇒ Object
- .create_component(context, class_text, class_name) ⇒ Object
- .ensure_directory_exists_for_file(filename) ⇒ Object
-
.execute(context, class_text, class_name, args = {}) ⇒ Object
A convenience function that parses and renders a class.
-
.handle_other(config, path_info) ⇒ Object
This helper method can be used with certain web server adapters to simplify the process of serving normal content types.
- .is_cache_stale(filename, cachename) ⇒ Object
Instance Method Summary collapse
-
#cache_check(config, filename, cachename) ⇒ Object
Returns true if the file is cached and the cache is fresh.
- #create_class_object(class_name, context) ⇒ Object
-
#get_cache_path(config, path) ⇒ Object
Returns true if the file is cached and the cache is fresh.
-
#get_cache_root(config) ⇒ Object
Returns a cache directory based on the configuration.
-
#initialize ⇒ Processor
constructor
A new instance of Processor.
-
#load(path, cfg = nil) ⇒ Object
This is the workhorse of the processor.
- #load_component(context, path) ⇒ Object
-
#load_component_callback(context, path, args) ⇒ Object
When a component is requested, either directly, by context.invoke, or indirectly, using the <& …
-
#load_container_callback(context, path) ⇒ Object
Handles loading of containers.
- #trace(msg) ⇒ Object
Constructor Details
#initialize ⇒ Processor
Returns a new instance of Processor.
27 28 29 |
# File 'lib/bijou/processor.rb', line 27 def initialize() @trace = false end |
Class Method Details
.class_from_filename(prefix, filename) ⇒ Object
361 362 363 |
# File 'lib/bijou/processor.rb', line 361 def self.class_from_filename(prefix, filename) return prefix + Digest::SHA1.hexdigest(filename.downcase).downcase end |
.create_component(context, class_text, class_name) ⇒ Object
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/bijou/processor.rb', line 273 def self.create_component(context, class_text, class_name) # # If the class is already in memory, we'll need to undefine it. # Otherwise, the new definition would be appended to the old one. # module_name = "Object" # REVIEW: This is the default right now. if Object.const_defined? module_name module_ref = Object.const_get(module_name) if module_ref.const_defined? class_name module_ref.remove_const(class_name) end end # REVIEW: If we can scope eval, we can use a random name. eval(class_text) component_class = eval(class_name) component_object = component_class.new(context) context.add_component(component_object) return component_object end |
.ensure_directory_exists_for_file(filename) ⇒ Object
389 390 391 392 393 394 395 396 397 398 399 400 401 |
# File 'lib/bijou/processor.rb', line 389 def self.ensure_directory_exists_for_file filename if filename !~ PathSepRE raise "Invalid filename" end n = filename.rindex(PathSepRE) path = filename[0, n] if !File.directory?(path) # NOTE: mkdir_p works with or without a / terminator. FileUtils.mkdir_p(path) end end |
.execute(context, class_text, class_name, args = {}) ⇒ Object
A convenience function that parses and renders a class. It will not load or cache component documents, like load_component, nor will containers or components be automatically handled. The caller must set callbacks to properly handle these load events.
301 302 303 304 305 306 307 |
# File 'lib/bijou/processor.rb', line 301 def self.execute(context, class_text, class_name, args={}) component_object = self.create_component(context, class_text, class_name) context.render(args) return context.output end |
.handle_other(config, path_info) ⇒ Object
This helper method can be used with certain web server adapters to simplify the process of serving normal content types. Many web servers will delegate specific requests to the Bijou adapter, based on the request. Some servers do not delegate based on file name patterns and thus the adapter must disambiguate and serve such requests.
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 |
# File 'lib/bijou/processor.rb', line 314 def self.handle_other(config, path_info) content_types = [ [ 'txt', 'text/plain' ], [ 'htm', 'text/html' ], [ 'html', 'text/html' ], [ 'css', 'text/css' ], [ 'gif', 'image/gif' ], [ 'jpg', 'image/jpeg' ], [ 'jpeg', 'image/jpeg' ], [ 'png', 'image/png' ], [ 'tif', 'image/tiff' ], [ 'tiff', 'image/tiff' ], [ 'bmp', 'image/x-ms-bmp' ], ] path = File.(path_info, config.document_root) content_type = 'text/plain' content_types.each {|type| ext = type[0] if path_info =~ /\.#{ext}$/ content_type = type[1] break end } if File.exists?(path) file = File.new(path, "r") response = { 'status' => 200, 'type' => content_type, 'body' => file.read } file.close else response = { 'status' => 404, 'type' => content_type, 'body' => '' } end return response end |
.is_cache_stale(filename, cachename) ⇒ Object
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
# File 'lib/bijou/processor.rb', line 365 def self.is_cache_stale(filename, cachename) parsefile = true if !File.exists?(cachename) return true # The file hasn't been cached yet. end cachemod = File.stat(cachename).mtime if !File.exists?(filename) raise "Source file not found" end sourcemod = File.stat(filename).mtime if sourcemod <= cachemod # The file has been cached and the source hasn't been touched. return false end # The cached file is older than the source. return true end |
Instance Method Details
#cache_check(config, filename, cachename) ⇒ Object
Returns true if the file is cached and the cache is fresh.
32 33 34 35 36 37 38 39 40 |
# File 'lib/bijou/processor.rb', line 32 def cache_check(config, filename, cachename) if !config.cache # Don't cache anything trace "don't cache" return false end return !Processor.is_cache_stale(filename, cachename) end |
#create_class_object(class_name, context) ⇒ Object
84 85 86 87 |
# File 'lib/bijou/processor.rb', line 84 def create_class_object(class_name, context) component_class = eval(class_name) return component_class.new(context) end |
#get_cache_path(config, path) ⇒ Object
Returns true if the file is cached and the cache is fresh.
73 74 75 76 77 78 79 80 81 82 |
# File 'lib/bijou/processor.rb', line 73 def get_cache_path(config, path) cache_root = get_cache_root(config) cache_path = File.(path, cache_root) if config.cache_ext cache_path << config.cache_ext end return cache_path end |
#get_cache_root(config) ⇒ Object
Returns a cache directory based on the configuration.
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/bijou/processor.rb', line 47 def get_cache_root(config) if config.cache_root # Store in the cache subdirectory of the cache root. We do this is # in case we need to store additional files. cache_root = File.('cache', config.cache_root) elsif config.cache_ext # Use the specified extension and store in the document root. cache_root = config.document_root else # Neither specified; create a cache root parallel to the doc root. # Strip the trailing slash. cache_root = File.('', config.document_root) # Give it a different path name, parallel to the document root. cache_root << CacheSuffix # Store in the cache subdirectory of the cache root. We do this is # in case we need to store additional files. cache_root = File.('cache', cache_root) end return cache_root end |
#load(path, cfg = nil) ⇒ Object
This is the workhorse of the processor. It utilizes several classes, including the parser, to load and parse a component in preparation for rendering. The return value is a Context object that may be used to render the component one or more times, each with a different set of arguments.
The cfg argument may be either a Bijou::Config or a Bijou::Context.
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/bijou/processor.rb', line 122 def load(path, cfg=nil) context = nil config = nil # Initialize the context and config. if cfg if cfg.kind_of?(Bijou::Config) config = cfg elsif cfg.kind_of?(Bijou::Context) context = cfg config = context.config else raise "If specified, cfg must be a Context or Config object" end else # Provide a default. config = Bijou::Config.new end if !context # Create the top-level context. context = Bijou::Context.new(config) end if config.document_root if !File.directory?(config.document_root) raise "The documet_root is not a valid directory." end else # NOTE: This modifies the configuration. config.document_root = Dir.getwd end # We want the processor to handle container and component creation. context.container_callback = method(:load_container_callback) context.component_callback = method(:load_component_callback) # Load the top-level component as a normal component. load_component(context, path) end |
#load_component(context, path) ⇒ Object
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 |
# File 'lib/bijou/processor.rb', line 163 def load_component(context, path) config = context.config if !config.document_root raise "A document_root must be specified" end path = path.strip # TODO: Consolidate tracing to a single path. trace "load_component #{path}" context.trace(Bijou::Log::Info, "load_component(#{path})") # The caller should strip the slash, but we do it here as a safeguard. if path[0,1] =~ PathSepRE path = path[1..-1] end filename = File.(path, config.document_root) trace "filename #{filename}" if !File.exists?(filename) raise Bijou::FileNotFound.new(filename), "File not found: #{filename}" end classname = Processor.class_from_filename(ComponentPrefix, filename) trace "classname #{classname}" # context.trace(Bijou::Log::Info, "get_cache_path #{config.cache_root || ''}"); cachename = get_cache_path(config, path) trace "cachename #{cachename}" component_class = nil if !cache_check(config, filename, cachename) context.trace(Bijou::Log::Info, "parse and load into cache #{cachename}"); Processor.ensure_directory_exists_for_file cachename parser = Bijou::Parser.new trace "parse #{classname}" source_file = File.new(filename) parser.parse_file(classname, source_file, path) source_file.close class_text = parser.render(classname, filename, cachename, config.component_base, config.require_list) # Write the new class text to the cache file. if parser.diagnostics.errors.length == 0 && class_text object_file = File.open(cachename, "w") object_file.write(class_text) object_file.close else File.delete(cachename) if File.exist?(cachename) raise Bijou::ParseError.new(filename, parser.diagnostics), "Parse error for file '#{path}'" end else begin # # If the class is already in memory, avoid hitting the disk. # trace "try read from memory #{classname}" component_object = create_class_object(classname, context) context.add_component(component_object) rescue NameError trace "not in memory" end if component_object context.trace(Bijou::Log::Info, " create from memory #{cachename}"); else context.trace(Bijou::Log::Info, " create from cache #{cachename}"); # # Otherewise, read the class text from the cache. # trace "read from cache #{cachename}" object_file = File.open(cachename, "r") class_text = object_file.read object_file.close end end if !component_object # # Create component using class text, while putting the definition # into memory. # trace "create_component" begin component_object = Processor.create_component(context, class_text, classname) rescue SyntaxError raise Bijou::EvalError.new(filename, cachename, $!.), "Syntax error in file '#{path}'" end end trace "load finished #{path}" # Caller will need to call context.render(args) return context end |
#load_component_callback(context, path, args) ⇒ Object
When a component is requested, either directly, by context.invoke, or indirectly, using the <& … &> syntax, the context delegates the loading of the component to the owner of the context (if the component_callback was registered).
103 104 105 106 107 108 109 110 111 112 |
# File 'lib/bijou/processor.rb', line 103 def load_component_callback(context, path, args) trace "component_callback #{path}" subcontext = context.clone load_component(subcontext, path) subcontext.render(args) return subcontext.output end |
#load_container_callback(context, path) ⇒ Object
Handles loading of containers. The context invokes this callback when a component is loaded and the component has an associated container. The context class is environment agnostic and thus doesn’t know how to load a container. Instead, it delegates the mechanics to the context owner.
93 94 95 96 97 |
# File 'lib/bijou/processor.rb', line 93 def load_container_callback(context, path) trace "container_callback #{path}" load_component(context, path) end |
#trace(msg) ⇒ Object
42 43 44 |
# File 'lib/bijou/processor.rb', line 42 def trace(msg) puts msg if @trace end |