├── LICENSE
├── README.md
├── Rakefile
├── example
├── app
│ └── javascripts
│ │ ├── application.js
│ │ └── utils.js
├── config.ru
└── public
│ ├── index.html
│ └── javascripts
│ ├── plain.js
│ └── yabble.js
├── lib
└── rack
│ ├── modulr.rb
│ └── modulr
│ ├── base.rb
│ ├── config.rb
│ ├── options.rb
│ ├── request.rb
│ ├── response.rb
│ ├── source.rb
│ └── version.rb
└── rack-modulr.gemspec
/LICENSE:
--------------------------------------------------------------------------------
1 | == License
2 |
3 | Based on work by Kelly Redding - http://github.com/kelredd/rack-less
4 |
5 | Copyright (c) 2010 Alex MacCaw (info@eribium.org)
6 |
7 | Permission is hereby granted, free of charge, to any person
8 | obtaining a copy of this software and associated documentation
9 | files (the "Software"), to deal in the Software without
10 | restriction, including without limitation the rights to use,
11 | copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the
13 | Software is furnished to do so, subject to the following
14 | conditions:
15 |
16 | The above copyright notice and this permission notice shall be
17 | included in all copies or substantial portions of the Software.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26 | OTHER DEALINGS IN THE SOFTWARE.
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [rack-modulr](http://github.com/maccman/rack-modulr) lets you easily use [Common.JS](http://www.sitepen.com/blog/2010/07/16/asynchronous-commonjs-modules-for-the-browser-and-introducing-transporter/) modules in your Rack/Rails applications.
2 |
3 | ##Prerequisites
4 |
5 | Although not required, it's recommended you use [this custom version](https://github.com/maccman/modulr) of the Modulr gem.
6 |
7 | ##Usage
8 |
9 | For example with Rack:
10 |
11 | require "rack/modulr"
12 |
13 | use Rack::Modulr, :modulr => {:minify => true}
14 | run Proc.new { [200, {"Content-Type" => "text/html"}, []] }
15 |
16 | Or with Rails:
17 |
18 | // Gemfile
19 |
20 | gem "modulr", :git => "git://github.com/maccman/modulr.git"
21 | gem "rack-modulr", :git => "git://github.com/maccman/rack-modulr.git"
22 |
23 | // config/application.rb
24 | require "rack/modulr"
25 | config.middleware.use "Rack::Modulr"
26 |
27 | Then any modules in `app/javascripts` will be automatically parsed by [Modulr](https://github.com/maccman/modulr)
28 |
29 | // app/javascripts/utils.js
30 | exports.sum = function(val1, val2){
31 | return(val1 + val2);
32 | };
33 |
34 | // app/javascripts/application.js
35 | var utils = require("./utils");
36 | console.log(utils.sum(1, 2));
37 |
38 | When the browser requests a module, all its dependencies will be recursively resolved.
39 |
40 | $ curl "http://localhost:5001/javascripts/application.js"
41 |
42 | (function() {
43 | require.define({
44 | 'utils': function(require, exports, module) {
45 | exports.sum = function(val1, val2){
46 | return(val1 + val2);
47 | };
48 | }
49 | });
50 |
51 | require.ensure(['utils'], function(require) {
52 | var utils = require("./utils");
53 | console.log(utils.sum(1, 2));
54 | });
55 | })();
56 |
57 | Modulr injects a module loader library but if you want to use a different one, like [Yabble](https://github.com/jbrantly/yabble), you'll need to pass the `:custom_loader` option to `Rack::Modulr`:
58 |
59 | use Rack::Modulr, :modulr => {:custom_loader => true}
60 |
61 | Rack::Modulr caches the compiled modules in memory. Every request, the request module will be checked, to see if it's mtime has changed. If the module hasn't been changed, it'll be served from memory if possible.
62 |
63 | By defaulting, caching and minification are turned off. To turn them on in the production environment, for example, set the global settings on Rack::Modulr.
64 |
65 | // production.rb
66 | Rack::Modulr.configure do |config|
67 | config.cache = true
68 | config.minify = true
69 | end
70 |
71 | ----------------------------------------------------
72 |
73 | Based on [Kelly Redding's](https://github.com/kelredd) great work on [rack-less](http://github.com/kelredd/rack-less).
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'rake/gempackagetask'
3 |
4 | require 'lib/rack/modulr/version'
5 |
6 | spec = Gem::Specification.new do |s|
7 | s.name = 'rack-modulr'
8 | s.version = RackModulr::Version.to_s
9 | s.summary = "CommonJS modules for Ruby web apps."
10 | s.author = 'Alex MacCaw'
11 | s.email = 'maccman@gmail.com'
12 | s.homepage = 'http://github.com/maccman/rack-modulr'
13 | s.files = %w(README.md Rakefile) + Dir.glob("{lib}/**/*")
14 |
15 | s.add_dependency("rack", [">= 0.4"])
16 | s.add_dependency("modulr", [">= 0.7.1"])
17 | end
18 |
19 | Rake::GemPackageTask.new(spec) do |pkg|
20 | pkg.gem_spec = spec
21 | end
22 |
23 | desc 'Generate the gemspec to serve this gem'
24 | task :gemspec do
25 | file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
26 | File.open(file, 'w') {|f| f << spec.to_ruby }
27 | puts "Created gemspec: #{file}"
28 | end
29 |
30 | task :default => :gem
31 |
--------------------------------------------------------------------------------
/example/app/javascripts/application.js:
--------------------------------------------------------------------------------
1 | var utils = require("./utils");
2 | console.log("Percentage", utils.per(50, 200));
--------------------------------------------------------------------------------
/example/app/javascripts/utils.js:
--------------------------------------------------------------------------------
1 |
2 | exports.per = function(value, total) {
3 | return( (value / total) * 100 );
4 | };
--------------------------------------------------------------------------------
/example/config.ru:
--------------------------------------------------------------------------------
1 | $: << File.join(File.dirname(__FILE__), *%w[ .. lib ])
2 | require "rack/modulr"
3 |
4 | use Rack::Modulr
5 |
6 | Rack::Modulr.configure do |config|
7 | config.custom_loader = true
8 | config.cache = false
9 | config.minify = false
10 | end
11 |
12 | run Rack::Directory.new("public")
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | rack-modulr
5 |
6 |
7 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/example/public/javascripts/plain.js:
--------------------------------------------------------------------------------
1 | // Plain
--------------------------------------------------------------------------------
/example/public/javascripts/yabble.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2010 James Brantly
3 | *
4 | * Permission is hereby granted, free of charge, to any person
5 | * obtaining a copy of this software and associated documentation
6 | * files (the "Software"), to deal in the Software without
7 | * restriction, including without limitation the rights to use,
8 | * copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | * copies of the Software, and to permit persons to whom the
10 | * Software is furnished to do so, subject to the following
11 | * conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be
14 | * included in all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
18 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
20 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
21 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
22 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23 | * OTHER DEALINGS IN THE SOFTWARE.
24 | */
25 |
26 | (function(globalEval) {
27 |
28 | var Yabble = function() {
29 | throw "Synchronous require() is not supported.";
30 | };
31 |
32 | Yabble.unit = {};
33 |
34 | var _moduleRoot = '',
35 | _modules,
36 | _callbacks,
37 | _fetchFunc,
38 | _timeoutLength = 20000,
39 | _mainProgram;
40 |
41 |
42 | var head = document.getElementsByTagName('head')[0];
43 |
44 | // Shortcut to native hasOwnProperty
45 | var hasOwnProperty = Object.prototype.hasOwnProperty;
46 |
47 | // A for..in implementation which uses hasOwnProperty and fixes IE non-enumerable issues
48 | if ((function() {for (var prop in {hasOwnProperty: true}) { return prop; }})() == 'hasOwnProperty') {
49 | var forIn = function(obj, func, ctx) {
50 | for (var prop in obj) {
51 | if (hasOwnProperty.call(obj, prop)) {
52 | func.call(ctx, prop);
53 | }
54 | }
55 | };
56 | }
57 | else {
58 | var ieBadProps = [
59 | 'isPrototypeOf',
60 | 'hasOwnProperty',
61 | 'toLocaleString',
62 | 'toString',
63 | 'valueOf'
64 | ];
65 |
66 | var forIn = function(obj, func, ctx) {
67 | for (var prop in obj) {
68 | if (hasOwnProperty.call(obj, prop)) {
69 | func.call(ctx, prop);
70 | }
71 | }
72 |
73 | for (var i = ieBadProps.length; i--;) {
74 | var prop = ieBadProps[i];
75 | if (hasOwnProperty.call(obj, prop)) {
76 | func.call(ctx, prop);
77 | }
78 | }
79 | };
80 | }
81 |
82 | // Array convenience functions
83 | var indexOf = function(arr, val) {
84 | for (var i = arr.length; i--;) {
85 | if (arr[i] == val) { return i; }
86 | }
87 | return -1;
88 | };
89 |
90 | var removeWhere = function(arr, func) {
91 | var i = 0;
92 | while (i < arr.length) {
93 | if (func.call(null, arr[i], i) === true) {
94 | arr.splice(i, 1);
95 | }
96 | else {
97 | i++;
98 | }
99 | }
100 | };
101 |
102 | var combinePaths = function(relPath, refPath) {
103 | var relPathParts = relPath.split('/');
104 | refPath = refPath || '';
105 | if (refPath.length && refPath.charAt(refPath.length-1) != '/') {
106 | refPath += '/';
107 | }
108 | var refPathParts = refPath.split('/');
109 | refPathParts.pop();
110 | var part;
111 | while (part = relPathParts.shift()) {
112 | if (part == '.') { continue; }
113 | else if (part == '..'
114 | && refPathParts.length
115 | && refPathParts[refPathParts.length-1] != '..') { refPathParts.pop(); }
116 | else { refPathParts.push(part); }
117 | }
118 | return refPathParts.join('/');
119 | };
120 |
121 | // Takes a relative path to a module and resolves it according to the reference path
122 | var resolveModuleId = Yabble.unit.resolveModuleId = function(relModuleId, refPath) {
123 | if (relModuleId.charAt(0) != '.') {
124 | return relModuleId;
125 | }
126 | else {
127 | return combinePaths(relModuleId, refPath);
128 | }
129 | };
130 |
131 | // Takes a module's ID and resolves a URI according to the module root path
132 | var resolveModuleUri = function(moduleId) {
133 | if (moduleId.charAt(0) != '.') {
134 | return _moduleRoot+moduleId+'.js';
135 | }
136 | else {
137 | return this._resolveModuleId(moduleId, _moduleRoot)+'.js';
138 | }
139 | };
140 |
141 | // Returns a module object from the module ID
142 | var getModule = function(moduleId) {
143 | if (!hasOwnProperty.call(_modules, moduleId)) {
144 | return null;
145 | }
146 | return _modules[moduleId];
147 | };
148 |
149 | // Adds a callback which is executed when all deep dependencies are loaded
150 | var addCallback = function(deps, cb) {
151 | _callbacks.push([deps.slice(0), cb]);
152 | };
153 |
154 | // Generic implementation of require.ensure() which takes a reference path to
155 | // use when resolving relative module IDs
156 | var ensureImpl = function(deps, cb, refPath) {
157 | var unreadyModules = [];
158 |
159 | for (var i = deps.length; i--;) {
160 | var moduleId = resolveModuleId(deps[i], refPath),
161 | module = getModule(moduleId);
162 |
163 | if (!areDeepDepsDefined(moduleId)) {
164 | unreadyModules.push(moduleId);
165 | }
166 | }
167 |
168 | if (unreadyModules.length) {
169 | addCallback(unreadyModules, function() {
170 | cb(createRequireFunc(refPath));
171 | });
172 | queueModules(unreadyModules);
173 | }
174 | else {
175 | setTimeout(function() {
176 | cb(createRequireFunc(refPath));
177 | }, 0);
178 | }
179 | };
180 |
181 | // Creates a require function that is passed into module factory functions
182 | // and require.ensure() callbacks. It is bound to a reference path for
183 | // relative require()s
184 | var createRequireFunc = function(refPath) {
185 | var require = function(relModuleId) {
186 | var moduleId = resolveModuleId(relModuleId, refPath),
187 | module = getModule(moduleId);
188 |
189 | if (!module) {
190 | throw "Module not loaded";
191 | }
192 | else if (module.error) {
193 | throw "Error loading module";
194 | }
195 |
196 | if (!module.exports) {
197 | module.exports = {};
198 | var moduleDir = moduleId.substring(0, moduleId.lastIndexOf('/')+1),
199 | injects = module.injects,
200 | args = [];
201 |
202 | for (var i = 0, n = injects.length; i= 0;
442 | });
443 |
444 | transport.modules.push({
445 | id: arguments[0],
446 | factory: arguments[2],
447 | injects: arguments[1]
448 | });
449 | }
450 | return transport;
451 | };
452 |
453 | // Set the uri which forms the conceptual module namespace root
454 | Yabble.setModuleRoot = function(path) {
455 | if (!(/^http(s?):\/\//.test(path))) {
456 | var href = window.location.href;
457 | href = href.substr(0, href.lastIndexOf('/')+1);
458 | path = combinePaths(path, href);
459 | }
460 |
461 | if (path.length && path.charAt(path.length-1) != '/') {
462 | path += '/';
463 | }
464 |
465 | _moduleRoot = path;
466 | };
467 |
468 | // Set a timeout period for async module loading
469 | Yabble.setTimeoutLength = function(milliseconds) {
470 | _timeoutLength = milliseconds;
471 | };
472 |
473 | // Use script tags with wrapped code instead of XHR+eval()
474 | Yabble.useScriptTags = function() {
475 | _fetchFunc = loadModuleByScript;
476 | };
477 |
478 | // Define a module per various transport specifications
479 | Yabble.def = Yabble.define = function() {
480 | var transport = normalizeTransport.apply(null, arguments);
481 |
482 | var unreadyModules = [],
483 | definedModules = [];
484 |
485 | var deps = transport.deps;
486 |
487 | for (var i = transport.modules.length; i--;) {
488 | var moduleDef = transport.modules[i],
489 | moduleId = moduleDef.id,
490 | module = getModule(moduleId);
491 |
492 | if (!module) {
493 | module = _modules[moduleId] = {};
494 | }
495 | module.module = {
496 | id: moduleId,
497 | uri: resolveModuleUri(moduleId)
498 | };
499 |
500 | module.defined = true;
501 | module.deps = deps.slice(0);
502 | module.injects = moduleDef.injects;
503 | module.factory = moduleDef.factory;
504 | definedModules.push(module);
505 | }
506 |
507 | for (var i = deps.length; i--;) {
508 | var moduleId = deps[i],
509 | module = getModule(moduleId);
510 |
511 | if (!module || !areDeepDepsDefined(moduleId)) {
512 | unreadyModules.push(moduleId);
513 | }
514 | }
515 |
516 | if (unreadyModules.length) {
517 | setTimeout(function() {
518 | queueModules(unreadyModules);
519 | }, 0);
520 | }
521 |
522 | fireCallbacks();
523 | };
524 |
525 | Yabble.isKnown = function(moduleId) {
526 | return getModule(moduleId) != null;
527 | };
528 |
529 | Yabble.isDefined = function(moduleId) {
530 | var module = getModule(moduleId);
531 | return !!(module && module.defined);
532 | };
533 |
534 | // Do an async lazy-load of modules
535 | Yabble.ensure = function(deps, cb) {
536 | ensureImpl(deps, cb, '');
537 | };
538 |
539 | // Start an application via a main program module
540 | Yabble.run = function(program, cb) {
541 | program = _mainProgram = resolveModuleId(program, '');
542 | Yabble.ensure([program], function(require) {
543 | require(program);
544 | if (cb != null) { cb(); }
545 | });
546 | };
547 |
548 | // Reset internal state. Used mostly for unit tests.
549 | Yabble.reset = function() {
550 | _mainProgram = null;
551 | _modules = {};
552 | _callbacks = [];
553 |
554 | // Built-in system module
555 | Yabble.define({
556 | 'system': function(require, exports, module) {}
557 | });
558 | };
559 |
560 | Yabble.reset();
561 |
562 | // Export to the require global
563 | window.require = Yabble;
564 | })(function(code) {
565 | return (window.eval || eval)(code, null);
566 | });
--------------------------------------------------------------------------------
/lib/rack/modulr.rb:
--------------------------------------------------------------------------------
1 | require 'rack'
2 | require 'rack/modulr/base'
3 | require 'rack/modulr/config'
4 | require 'rack/modulr/request'
5 | require 'rack/modulr/response'
6 | require 'rack/modulr/source'
7 |
8 | # === Usage
9 | #
10 | # Create with default configs:
11 | # require 'rack/modulr'
12 | # Rack::Modulr.new(app)
13 | #
14 | # Within a rackup file (or with Rack::Builder):
15 | # require 'rack/modulr'
16 | #
17 | # use Rack::Modulr,
18 | # :source => 'app/modulr'
19 | #
20 | # run app
21 |
22 | module Rack::Modulr
23 | MIME_TYPE = "text/javascript"
24 |
25 | class << self
26 | # Configuration accessors for Rack::Modulr
27 | # (see config.rb for details)
28 | def configure
29 | yield config if block_given?
30 | end
31 |
32 | def config
33 | @config ||= Config.new
34 | end
35 |
36 | def config=(value)
37 | @config = value
38 | end
39 | end
40 |
41 | # Create a new Rack::Modulr middleware component
42 | # => the +options+ Hash can be used to specify default configuration values
43 | # => (see Rack::Modulr::Options for possible key/values)
44 | def self.new(app, options={}, &block)
45 | Base.new(app, options, &block)
46 | end
47 | end
--------------------------------------------------------------------------------
/lib/rack/modulr/base.rb:
--------------------------------------------------------------------------------
1 | require 'rack/modulr/options'
2 | require 'rack/modulr/request'
3 | require 'rack/modulr/response'
4 |
5 | module Rack::Modulr
6 | class Base
7 | include Rack::Modulr::Options
8 | YEAR_IN_SECONDS = 31540000
9 |
10 | def initialize(app, options = {})
11 | @app = app
12 | initialize_options options
13 | yield self if block_given?
14 | validate_options
15 | end
16 |
17 | # If CommonJS modules are being requested, this is an endpoint:
18 | # => generate the compiled js
19 | # => respond appropriately
20 | # Otherwise, call on up to the app as normal
21 | def call(env)
22 | @default_options.each { |k,v| env[k] ||= v }
23 | @env = env.dup.freeze
24 |
25 | if (@request = Request.new(@env)).for_modulr?
26 | response = Response.new(@env, source_for(@request))
27 | cache(response) { response.to_rack }
28 | else
29 | @app.call(env)
30 | end
31 | end
32 |
33 | protected
34 |
35 | def cache?
36 | Rack::Modulr.config.cache?
37 | end
38 |
39 | def cache(response)
40 | return yield unless cache?
41 |
42 | env = response.env
43 | headers = response.headers
44 |
45 | headers["Cache-Control"] = "public, must-revalidate"
46 | if env["QUERY_STRING"] == response.md5
47 | headers["Cache-Control"] << ", max-age=#{YEAR_IN_SECONDS}"
48 | end
49 |
50 | headers["ETag"] = %("#{response.md5}")
51 | headers["Last-Modified"] = response.last_modified.httpdate
52 |
53 | if etag = env["HTTP_IF_NONE_MATCH"]
54 | return [304, headers.to_hash, []] if etag == headers["ETag"]
55 | end
56 |
57 | if time = env["HTTP_IF_MODIFIED_SINCE"]
58 | return [304, headers.to_hash, []] if time == headers["Last-Modified"]
59 | end
60 |
61 | yield
62 | end
63 |
64 | def source_for(request)
65 | @source ||= request.source
66 |
67 | previous_last_modified, @last_modified = @last_modified, @source.mtime
68 | unchanged = previous_last_modified == @last_modified
69 |
70 | (unchanged && cache?) ? @source : (@source = request.source)
71 | end
72 |
73 | def validate_options
74 | # ensure a root path is specified and does exists
75 | unless options.has_key?(option_name(:root)) and !options(:root).nil?
76 | raise(ArgumentError, "no :root option set")
77 | end
78 | set :root, File.expand_path(options(:root))
79 |
80 | # ensure a source path is specified and does exists
81 | unless options.has_key?(option_name(:source)) and !options(:source).nil?
82 | raise(ArgumentError, "no :source option set")
83 | end
84 | end
85 | end
86 | end
--------------------------------------------------------------------------------
/lib/rack/modulr/config.rb:
--------------------------------------------------------------------------------
1 | module Rack::Modulr
2 | class Config
3 |
4 | ATTRIBUTES = [:cache, :minify, :modulr]
5 | attr_accessor *ATTRIBUTES
6 |
7 | DEFAULTS = {
8 | :cache => false,
9 | :minify => false,
10 | :modulr => {}
11 | }
12 |
13 | def initialize(settings = {})
14 | ATTRIBUTES.each do |a|
15 | send("#{a}=", settings[a] || DEFAULTS[a])
16 | end
17 | end
18 |
19 | def modulr
20 | @modulr ||= {}
21 | end
22 |
23 | def minify=(val)
24 | self.modulr[:minify] = val
25 | end
26 |
27 | def custom_loader=(val)
28 | self.modulr[:custom_loader] = val
29 | end
30 |
31 | def cache?
32 | !!self.cache
33 | end
34 | end
35 | end
--------------------------------------------------------------------------------
/lib/rack/modulr/options.rb:
--------------------------------------------------------------------------------
1 | module Rack::Modulr
2 | module Options
3 |
4 | # Handles options for Rack::Modulr
5 | # Available options:
6 | # => root
7 | # the app root. the reference point for the
8 | # source and public options
9 | # => source
10 | # the path (relative to the root) where
11 | # CommonJS files are located
12 | # => public
13 | # the path (relative to the root) where
14 | # static files are served
15 | # => hosted_at
16 | # the public HTTP root path for javascripts
17 |
18 | # Note: the following code is heavily influenced by:
19 | # => http://github.com/rtomayko/rack-cache/blob/master/lib/rack/cache/options.rb
20 | # => thanks to rtomayko, I thought his approach was really smart.
21 |
22 | RACK_ENV_NS = "rack-modulr"
23 |
24 | module ClassMethods
25 |
26 | def defaults
27 | {
28 | option_name(:root) => ".",
29 | option_name(:source) => 'app/javascripts',
30 | option_name(:public) => 'public',
31 | option_name(:hosted_at) => '/javascripts',
32 | option_name(:modulr) => {}
33 | }
34 | end
35 |
36 | # Rack::Modulr uses the Rack Environment to store option values. All options
37 | # are stored in the Rack Environment as ".