honkster-screw-unit-server 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. data/CHANGES +48 -0
  2. data/README.markdown +359 -0
  3. data/Rakefile +43 -0
  4. data/VERSION.yml +5 -0
  5. data/bin/screw_unit +6 -0
  6. data/bin/screw_unit_server +14 -0
  7. data/core/CHANGES +10 -0
  8. data/core/EXAMPLE.html +68 -0
  9. data/core/LICENSE +22 -0
  10. data/core/README.markdown +307 -0
  11. data/core/example/models/cat.js +5 -0
  12. data/core/example/models/man.js +17 -0
  13. data/core/example/spec/matchers/have.js +8 -0
  14. data/core/example/spec/models/cat_spec.js +31 -0
  15. data/core/example/spec/models/man_spec.js +34 -0
  16. data/core/example/spec/spec_helper.js +5 -0
  17. data/core/example/spec/suite.html +25 -0
  18. data/core/lib/jquery-1.3.2.js +4376 -0
  19. data/core/lib/jquery.fn.js +30 -0
  20. data/core/lib/jquery.print.js +109 -0
  21. data/core/lib/screw.behaviors.js +93 -0
  22. data/core/lib/screw.builder.js +95 -0
  23. data/core/lib/screw.css +90 -0
  24. data/core/lib/screw.events.js +45 -0
  25. data/core/lib/screw.matchers.js +244 -0
  26. data/core/spec/behaviors_spec.js +166 -0
  27. data/core/spec/matchers_spec.js +372 -0
  28. data/core/spec/print_spec.js +158 -0
  29. data/core/spec/spec_helper.js +0 -0
  30. data/core/spec/suite.html +19 -0
  31. data/core/spec/with_screw_context_spec.js +9 -0
  32. data/init.rb +0 -0
  33. data/lib/screw_unit.rb +21 -0
  34. data/spec/functional/functional_spec.rb +25 -0
  35. data/spec/functional/functional_spec_helper.rb +42 -0
  36. data/spec/functional/functional_spec_server_starter.rb +68 -0
  37. data/spec/functional_suite.rb +10 -0
  38. data/spec/spec_suite.rb +2 -0
  39. data/standalone.ru +3 -0
  40. data/vendor/js-test-core/CHANGES +31 -0
  41. data/vendor/js-test-core/README +6 -0
  42. data/vendor/js-test-core/Rakefile +73 -0
  43. data/vendor/js-test-core/bin/js-test-client +0 -0
  44. data/vendor/js-test-core/bin/js-test-server +6 -0
  45. data/vendor/js-test-core/lib/js_test_core.rb +38 -0
  46. data/vendor/js-test-core/lib/js_test_core/app.rb +11 -0
  47. data/vendor/js-test-core/lib/js_test_core/client.rb +139 -0
  48. data/vendor/js-test-core/lib/js_test_core/configuration.rb +65 -0
  49. data/vendor/js-test-core/lib/js_test_core/representations.rb +13 -0
  50. data/vendor/js-test-core/lib/js_test_core/representations/dir.html.rb +25 -0
  51. data/vendor/js-test-core/lib/js_test_core/representations/frameworks.rb +3 -0
  52. data/vendor/js-test-core/lib/js_test_core/representations/not_found.html.rb +16 -0
  53. data/vendor/js-test-core/lib/js_test_core/representations/page.html.rb +41 -0
  54. data/vendor/js-test-core/lib/js_test_core/representations/suite.html.rb +57 -0
  55. data/vendor/js-test-core/lib/js_test_core/representations/suites.rb +3 -0
  56. data/vendor/js-test-core/lib/js_test_core/representations/suites/screw_unit.html.rb +81 -0
  57. data/vendor/js-test-core/lib/js_test_core/resources.rb +15 -0
  58. data/vendor/js-test-core/lib/js_test_core/resources/core_file.rb +19 -0
  59. data/vendor/js-test-core/lib/js_test_core/resources/file.rb +62 -0
  60. data/vendor/js-test-core/lib/js_test_core/resources/implementations_deprecation.rb +12 -0
  61. data/vendor/js-test-core/lib/js_test_core/resources/not_found.rb +28 -0
  62. data/vendor/js-test-core/lib/js_test_core/resources/resource.rb +16 -0
  63. data/vendor/js-test-core/lib/js_test_core/resources/spec_file.rb +51 -0
  64. data/vendor/js-test-core/lib/js_test_core/resources/web_root.rb +15 -0
  65. data/vendor/js-test-core/lib/js_test_core/server.rb +84 -0
  66. data/vendor/js-test-core/public/js_test_server.js +541 -0
  67. data/vendor/js-test-core/spec/example_framework/JsTestCore.css +0 -0
  68. data/vendor/js-test-core/spec/example_framework/JsTestCore.js +0 -0
  69. data/vendor/js-test-core/spec/example_framework/subdir/SubDirFile.js +0 -0
  70. data/vendor/js-test-core/spec/example_root/favicon.ico +0 -0
  71. data/vendor/js-test-core/spec/example_root/javascripts/foo.js +3 -0
  72. data/vendor/js-test-core/spec/example_root/javascripts/large_file.js +59 -0
  73. data/vendor/js-test-core/spec/example_root/javascripts/subdir/bar.js +1 -0
  74. data/vendor/js-test-core/spec/example_root/robots.txt +0 -0
  75. data/vendor/js-test-core/spec/example_root/stylesheets/example.css +3 -0
  76. data/vendor/js-test-core/spec/example_spec/custom_dir_and_suite/passing_spec.js +8 -0
  77. data/vendor/js-test-core/spec/example_spec/custom_suite.html +8 -0
  78. data/vendor/js-test-core/spec/example_spec/failing_spec.js +7 -0
  79. data/vendor/js-test-core/spec/example_spec/foo/failing_spec.js +8 -0
  80. data/vendor/js-test-core/spec/example_spec/foo/passing_spec.js +8 -0
  81. data/vendor/js-test-core/spec/example_spec/passing_spec.js +8 -0
  82. data/vendor/js-test-core/spec/functional/functional_spec.rb +25 -0
  83. data/vendor/js-test-core/spec/functional/functional_spec_helper.rb +42 -0
  84. data/vendor/js-test-core/spec/functional/functional_spec_server_starter.rb +77 -0
  85. data/vendor/js-test-core/spec/functional_suite.rb +10 -0
  86. data/vendor/js-test-core/spec/spec_helpers/be_http.rb +32 -0
  87. data/vendor/js-test-core/spec/spec_helpers/example_group.rb +36 -0
  88. data/vendor/js-test-core/spec/spec_helpers/fake_selenium_driver.rb +16 -0
  89. data/vendor/js-test-core/spec/spec_helpers/show_test_exceptions.rb +22 -0
  90. data/vendor/js-test-core/spec/spec_suite.rb +3 -0
  91. data/vendor/js-test-core/spec/specs/failing_spec.js +0 -0
  92. data/vendor/js-test-core/spec/unit/js_test_core/client_spec.rb +196 -0
  93. data/vendor/js-test-core/spec/unit/js_test_core/configuration_spec.rb +50 -0
  94. data/vendor/js-test-core/spec/unit/js_test_core/resources/core_file_spec.rb +60 -0
  95. data/vendor/js-test-core/spec/unit/js_test_core/resources/file_spec.rb +81 -0
  96. data/vendor/js-test-core/spec/unit/js_test_core/resources/implementations_deprecation_spec.rb +18 -0
  97. data/vendor/js-test-core/spec/unit/js_test_core/resources/not_found_spec.rb +51 -0
  98. data/vendor/js-test-core/spec/unit/js_test_core/resources/spec_file_spec.rb +149 -0
  99. data/vendor/js-test-core/spec/unit/js_test_core/resources/web_root_spec.rb +28 -0
  100. data/vendor/js-test-core/spec/unit/js_test_core/server_spec.rb +66 -0
  101. data/vendor/js-test-core/spec/unit/unit_spec_helper.rb +30 -0
  102. data/vendor/js-test-core/spec/unit_suite.rb +10 -0
  103. data/vendor/js-test-core/standalone.ru +1 -0
  104. data/vendor/js-test-core/vendor/lucky-luciano/README.markdown +77 -0
  105. data/vendor/js-test-core/vendor/lucky-luciano/lib/lucky_luciano.rb +5 -0
  106. data/vendor/js-test-core/vendor/lucky-luciano/lib/lucky_luciano/resource.rb +142 -0
  107. data/vendor/js-test-core/vendor/lucky-luciano/lib/lucky_luciano/resource/path.rb +24 -0
  108. data/vendor/js-test-core/vendor/lucky-luciano/lib/lucky_luciano/rspec.rb +4 -0
  109. data/vendor/js-test-core/vendor/lucky-luciano/lib/lucky_luciano/rspec/be_http.rb +32 -0
  110. data/vendor/js-test-core/vendor/lucky-luciano/spec/lucky_luciano/resource_spec.rb +276 -0
  111. data/vendor/js-test-core/vendor/lucky-luciano/spec/spec_helper.rb +48 -0
  112. data/vendor/js-test-core/vendor/lucky-luciano/spec/spec_suite.rb +4 -0
  113. data/vendor/js-test-core/vendor/lucky-luciano/syntax_ideas.txt +34 -0
  114. metadata +201 -0
@@ -0,0 +1,15 @@
1
+ dir = File.dirname(__FILE__)
2
+
3
+ module JsTestCore
4
+ module Resources
5
+ end
6
+ end
7
+
8
+ require "#{dir}/resources/resource"
9
+ require "#{dir}/resources/file"
10
+ require "#{dir}/resources/spec_file"
11
+ require "#{dir}/resources/core_file"
12
+ require "#{dir}/resources/web_root"
13
+ require "#{dir}/resources/not_found"
14
+
15
+ require "#{dir}/resources/implementations_deprecation"
@@ -0,0 +1,19 @@
1
+ module JsTestCore
2
+ module Resources
3
+ class CoreFile < File
4
+ map "/core"
5
+
6
+ get "/?" do
7
+ do_get
8
+ end
9
+
10
+ get "*" do
11
+ do_get
12
+ end
13
+
14
+ def absolute_path
15
+ @absolute_path ||= ::File.expand_path("#{framework_path}#{relative_path.gsub(%r{^/core}, "")}")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,62 @@
1
+ module JsTestCore
2
+ module Resources
3
+ class File < Resource
4
+ def self.render_file(absolute_path)
5
+ extension = ::File.extname(absolute_path)
6
+ content_type = MIME_TYPES[extension] || 'text/html'
7
+ headers = {
8
+ 'Content-Type' => content_type,
9
+ 'Last-Modified' => ::File.mtime(absolute_path).rfc822
10
+ }
11
+ [200, headers, ::File.read(absolute_path)]
12
+ end
13
+
14
+ map "*"
15
+
16
+ MIME_TYPES = {
17
+ '.html' => 'text/html',
18
+ '.htm' => 'text/html',
19
+ '.js' => 'text/javascript',
20
+ '.css' => 'text/css',
21
+ '.png' => 'image/png',
22
+ '.jpg' => 'image/jpeg',
23
+ '.jpeg' => 'image/jpeg',
24
+ '.gif' => 'image/gif',
25
+ }
26
+
27
+ get "*" do
28
+ do_get
29
+ end
30
+
31
+ def relative_path
32
+ @relative_path ||= request.path_info
33
+ end
34
+
35
+ def absolute_path
36
+ @absolute_path ||= ::File.expand_path("#{root_path}#{relative_path}")
37
+ end
38
+
39
+ protected
40
+
41
+ def do_get
42
+ if ::File.exists?(absolute_path)
43
+ if ::File.directory?(absolute_path)
44
+ render_dir
45
+ else
46
+ render_file
47
+ end
48
+ else
49
+ pass
50
+ end
51
+ end
52
+
53
+ def render_dir
54
+ Representations::Dir.new(:relative_path => relative_path, :absolute_path => absolute_path).to_s
55
+ end
56
+
57
+ def render_file
58
+ self.class.render_file(absolute_path)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,12 @@
1
+ module JsTestCore
2
+ module Resources
3
+ class ImplementationsDeprecation < Resource
4
+ map "/implementations"
5
+
6
+ get "*" do
7
+ new_path = File.path("javascripts", *params["splat"])
8
+ [301, {'Location' => new_path}, "This page has been moved to #{new_path}"]
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,28 @@
1
+ module JsTestCore
2
+ module Resources
3
+ class NotFound < Resource
4
+ map "*"
5
+
6
+ get "/" do
7
+ call
8
+ end
9
+
10
+ put "/" do
11
+ call
12
+ end
13
+
14
+ post "/" do
15
+ call
16
+ end
17
+
18
+ delete "/" do
19
+ call
20
+ end
21
+
22
+ def call
23
+ body = Representations::NotFound.new(:message => "File #{request.path_info} not found").to_s
24
+ [ 404, { "Content-Type" => "text/html" }, body ]
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,16 @@
1
+ module JsTestCore
2
+ module Resources
3
+ class Resource < LuckyLuciano::Resource
4
+ protected
5
+
6
+ def spec_path; server.spec_path; end
7
+ def root_path; server.root_path; end
8
+ def framework_path; server.framework_path; end
9
+ def root_url; server.root_url; end
10
+
11
+ def server
12
+ JsTestCore::Configuration
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,51 @@
1
+ module JsTestCore
2
+ module Resources
3
+ class SpecFile < ::JsTestCore::Resources::File
4
+ map "/specs"
5
+
6
+ get "/?" do
7
+ do_get
8
+ end
9
+
10
+ get "*" do
11
+ do_get
12
+ end
13
+
14
+ protected
15
+
16
+ def do_get
17
+ if ::File.exists?(absolute_path)
18
+ if ::File.directory?(absolute_path)
19
+ spec_files = ::Dir["#{absolute_path}/**/*_spec.js"].map do |file|
20
+ ["#{relative_path}#{file.gsub(absolute_path, "")}"]
21
+ end
22
+ get_generated_spec(absolute_path, spec_files)
23
+ else
24
+ super
25
+ end
26
+ elsif ::File.exists?("#{absolute_path}.js")
27
+ get_generated_spec("#{absolute_path}.js", ["#{relative_path}.js"])
28
+ else
29
+ pass
30
+ end
31
+ end
32
+
33
+ def get_generated_spec(real_path, spec_files)
34
+ html = render_spec(spec_files)
35
+ headers = {
36
+ 'Content-Type' => "text/html",
37
+ 'Last-Modified' => ::File.mtime(real_path).rfc822
38
+ }
39
+ [200, headers, html]
40
+ end
41
+
42
+ def render_spec(spec_files)
43
+ JsTestCore.suite_representation_class.new(:spec_files => spec_files).to_s
44
+ end
45
+
46
+ def absolute_path
47
+ @absolute_path ||= ::File.expand_path("#{spec_path}#{relative_path.gsub(%r{^/specs}, "")}")
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,15 @@
1
+ module JsTestCore
2
+ module Resources
3
+ class WebRoot < Resource
4
+ map "/"
5
+
6
+ get("") do
7
+ "<html><head></head><body>Welcome to the Js Test Server. Click the following link to run you <a href=/specs>spec suite</a>.</body></html>"
8
+ end
9
+
10
+ get("js_test_server.js") do
11
+ File.render_file(Configuration.js_test_core_js_path)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,84 @@
1
+ module JsTestCore
2
+ class Server
3
+ class << self
4
+ attr_writer :instance
5
+ def instance
6
+ @instance ||= new
7
+ end
8
+ end
9
+
10
+ DEFAULTS = {
11
+ :host => "0.0.0.0",
12
+ :port => 8080,
13
+ :spec_path => File.expand_path("./spec/javascripts"),
14
+ :root_path => File.expand_path("./public"),
15
+ }
16
+
17
+ def cli(*argv)
18
+ opts = Trollop.options(argv) do
19
+ opt(
20
+ :framework_name,
21
+ "The name of the test framework you want to use. e.g. --framework-name=jasmine",
22
+ :type => String,
23
+ :default => DEFAULTS[:framework_name]
24
+ )
25
+ opt(
26
+ :framework_path,
27
+ "The name of the test framework you want to use. e.g. --framework-path=./specs/jasmine_core",
28
+ :type => String,
29
+ :default => DEFAULTS[:framework_path]
30
+ )
31
+ opt(
32
+ :spec_path,
33
+ "The path to the spec files. e.g. --spec-path=./specs",
34
+ :type => String,
35
+ :default => DEFAULTS[:spec_path]
36
+ )
37
+ opt(
38
+ :root_path,
39
+ "The root path of the server. e.g. --root-path=./public",
40
+ :type => String,
41
+ :default => DEFAULTS[:root_path]
42
+ )
43
+ opt(
44
+ :port,
45
+ "The server port",
46
+ :type => Integer,
47
+ :default => DEFAULTS[:port]
48
+ )
49
+ end
50
+
51
+ JsTestCore.port = opts[:port]
52
+ JsTestCore.framework_name = opts[:framework_name]
53
+ JsTestCore.framework_path = opts[:framework_path]
54
+ JsTestCore.spec_path = opts[:spec_path]
55
+ JsTestCore.root_path = opts[:root_path]
56
+ STDOUT.puts "root-path is #{JsTestCore.root_path}"
57
+ STDOUT.puts "spec-path is #{JsTestCore.spec_path}"
58
+ start
59
+ end
60
+
61
+ def start
62
+ require "thin"
63
+ Thin::Runner.new([
64
+ "--port", JsTestCore.port.to_s,
65
+ "--rackup", File.expand_path(rackup_path),
66
+ "start"]
67
+ ).run!
68
+ end
69
+
70
+ def standalone_rackup(rack_builder)
71
+ require "sinatra"
72
+
73
+ rack_builder.use JsTestCore::App
74
+ rack_builder.run Sinatra::Application
75
+ end
76
+
77
+ protected
78
+
79
+ def rackup_path
80
+ dir = File.dirname(__FILE__)
81
+ File.expand_path("#{dir}/../../standalone.ru")
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,541 @@
1
+ (function() {
2
+ function JsTestServer() {
3
+ };
4
+ window.JsTestServer = JsTestServer;
5
+
6
+ JsTestServer.status = function() {
7
+ throw "You must implement the JsTestServer.status method";
8
+ };
9
+
10
+ JsTestServer.Assets = {};
11
+ JsTestServer.Assets.use_cache_buster = false; // TODO: NS/CTI - make this configurable from the UI.
12
+ var required_paths = [];
13
+ var included_stylesheets = {};
14
+ var cache_buster = parseInt(new Date().getTime()/(1*1000));
15
+
16
+ function tag(name, attributes) {
17
+ var html = "<" + name;
18
+ for(var attribute in attributes) {
19
+ html += (" " + attribute + "='" + attributes[attribute]) + "'";
20
+ };
21
+ html += "></";
22
+ html += name;
23
+ html += ">";
24
+ return html;
25
+ }
26
+
27
+ JsTestServer.Assets.require = function(javascript_path, onload) {
28
+ if(!required_paths[javascript_path]) {
29
+ var full_path = javascript_path + ".js";
30
+ if (JsTestServer.Assets.use_cache_buster) {
31
+ full_path += '?' + cache_buster;
32
+ }
33
+ document.write(tag("script", {src: full_path, type: 'text/javascript'}));
34
+ if(onload) {
35
+ var scripts = document.getElementsByTagName('script');
36
+ scripts[scripts.length-1].onload = onload;
37
+ }
38
+ required_paths[javascript_path] = true;
39
+ }
40
+ };
41
+
42
+ JsTestServer.Assets.stylesheet = function(stylesheet_path) {
43
+ if(!included_stylesheets[stylesheet_path]) {
44
+ var full_path = stylesheet_path + ".css";
45
+ if(JsTestServer.Assets.use_cache_buster) {
46
+ full_path += '?' + cache_buster;
47
+ }
48
+ document.write(tag("link", {rel: 'stylesheet', type: 'text/css', href: full_path}));
49
+ included_stylesheets[stylesheet_path] = true;
50
+ }
51
+ };
52
+
53
+ window.require = JsTestServer.Assets.require;
54
+ window.stylesheet = JsTestServer.Assets.stylesheet;
55
+ })();
56
+
57
+
58
+ //////////////////////////////////////////////////////////////////
59
+ // Inlining json2.js library because there should only be one js script tag
60
+ // and its simpler to inline rather than cat the js together on the server.
61
+ //////////////////////////////////////////////////////////////////
62
+
63
+ /*
64
+ http://www.JSON.org/json2.js
65
+ 2009-06-29
66
+
67
+ Public Domain.
68
+
69
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
70
+
71
+ See http://www.JSON.org/js.html
72
+
73
+ This file creates a global JSON object containing two methods: stringify
74
+ and parse.
75
+
76
+ JSON.stringify(value, replacer, space)
77
+ value any JavaScript value, usually an object or array.
78
+
79
+ replacer an optional parameter that determines how object
80
+ values are stringified for objects. It can be a
81
+ function or an array of strings.
82
+
83
+ space an optional parameter that specifies the indentation
84
+ of nested structures. If it is omitted, the text will
85
+ be packed without extra whitespace. If it is a number,
86
+ it will specify the number of spaces to indent at each
87
+ level. If it is a string (such as '\t' or '&nbsp;'),
88
+ it contains the characters used to indent at each level.
89
+
90
+ This method produces a JSON text from a JavaScript value.
91
+
92
+ When an object value is found, if the object contains a toJSON
93
+ method, its toJSON method will be called and the result will be
94
+ stringified. A toJSON method does not serialize: it returns the
95
+ value represented by the name/value pair that should be serialized,
96
+ or undefined if nothing should be serialized. The toJSON method
97
+ will be passed the key associated with the value, and this will be
98
+ bound to the object holding the key.
99
+
100
+ For example, this would serialize Dates as ISO strings.
101
+
102
+ Date.prototype.toJSON = function (key) {
103
+ function f(n) {
104
+ // Format integers to have at least two digits.
105
+ return n < 10 ? '0' + n : n;
106
+ }
107
+
108
+ return this.getUTCFullYear() + '-' +
109
+ f(this.getUTCMonth() + 1) + '-' +
110
+ f(this.getUTCDate()) + 'T' +
111
+ f(this.getUTCHours()) + ':' +
112
+ f(this.getUTCMinutes()) + ':' +
113
+ f(this.getUTCSeconds()) + 'Z';
114
+ };
115
+
116
+ You can provide an optional replacer method. It will be passed the
117
+ key and value of each member, with this bound to the containing
118
+ object. The value that is returned from your method will be
119
+ serialized. If your method returns undefined, then the member will
120
+ be excluded from the serialization.
121
+
122
+ If the replacer parameter is an array of strings, then it will be
123
+ used to select the members to be serialized. It filters the results
124
+ such that only members with keys listed in the replacer array are
125
+ stringified.
126
+
127
+ Values that do not have JSON representations, such as undefined or
128
+ functions, will not be serialized. Such values in objects will be
129
+ dropped; in arrays they will be replaced with null. You can use
130
+ a replacer function to replace those with JSON values.
131
+ JSON.stringify(undefined) returns undefined.
132
+
133
+ The optional space parameter produces a stringification of the
134
+ value that is filled with line breaks and indentation to make it
135
+ easier to read.
136
+
137
+ If the space parameter is a non-empty string, then that string will
138
+ be used for indentation. If the space parameter is a number, then
139
+ the indentation will be that many spaces.
140
+
141
+ Example:
142
+
143
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
144
+ // text is '["e",{"pluribus":"unum"}]'
145
+
146
+
147
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
148
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
149
+
150
+ text = JSON.stringify([new Date()], function (key, value) {
151
+ return this[key] instanceof Date ?
152
+ 'Date(' + this[key] + ')' : value;
153
+ });
154
+ // text is '["Date(---current time---)"]'
155
+
156
+
157
+ JSON.parse(text, reviver)
158
+ This method parses a JSON text to produce an object or array.
159
+ It can throw a SyntaxError exception.
160
+
161
+ The optional reviver parameter is a function that can filter and
162
+ transform the results. It receives each of the keys and values,
163
+ and its return value is used instead of the original value.
164
+ If it returns what it received, then the structure is not modified.
165
+ If it returns undefined then the member is deleted.
166
+
167
+ Example:
168
+
169
+ // Parse the text. Values that look like ISO date strings will
170
+ // be converted to Date objects.
171
+
172
+ myData = JSON.parse(text, function (key, value) {
173
+ var a;
174
+ if (typeof value === 'string') {
175
+ a =
176
+ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
177
+ if (a) {
178
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
179
+ +a[5], +a[6]));
180
+ }
181
+ }
182
+ return value;
183
+ });
184
+
185
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
186
+ var d;
187
+ if (typeof value === 'string' &&
188
+ value.slice(0, 5) === 'Date(' &&
189
+ value.slice(-1) === ')') {
190
+ d = new Date(value.slice(5, -1));
191
+ if (d) {
192
+ return d;
193
+ }
194
+ }
195
+ return value;
196
+ });
197
+
198
+
199
+ This is a reference implementation. You are free to copy, modify, or
200
+ redistribute.
201
+
202
+ This code should be minified before deployment.
203
+ See http://javascript.crockford.com/jsmin.html
204
+
205
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
206
+ NOT CONTROL.
207
+ */
208
+
209
+ /*jslint evil: true */
210
+
211
+ /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
212
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
213
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
214
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
215
+ test, toJSON, toString, valueOf
216
+ */
217
+
218
+ // Create a JSON object only if one does not already exist. We create the
219
+ // methods in a closure to avoid creating global variables.
220
+
221
+ (function(JsTestServer) {
222
+ var JSON = JSON || {};
223
+ JsTestServer.JSON = JSON;
224
+
225
+ (function () {
226
+
227
+ function f(n) {
228
+ // Format integers to have at least two digits.
229
+ return n < 10 ? '0' + n : n;
230
+ }
231
+
232
+ if (typeof Date.prototype.toJSON !== 'function') {
233
+
234
+ Date.prototype.toJSON = function (key) {
235
+
236
+ return isFinite(this.valueOf()) ?
237
+ this.getUTCFullYear() + '-' +
238
+ f(this.getUTCMonth() + 1) + '-' +
239
+ f(this.getUTCDate()) + 'T' +
240
+ f(this.getUTCHours()) + ':' +
241
+ f(this.getUTCMinutes()) + ':' +
242
+ f(this.getUTCSeconds()) + 'Z' : null;
243
+ };
244
+
245
+ String.prototype.toJSON =
246
+ Number.prototype.toJSON =
247
+ Boolean.prototype.toJSON = function (key) {
248
+ return this.valueOf();
249
+ };
250
+ }
251
+
252
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
253
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
254
+ gap,
255
+ indent,
256
+ meta = { // table of character substitutions
257
+ '\b': '\\b',
258
+ '\t': '\\t',
259
+ '\n': '\\n',
260
+ '\f': '\\f',
261
+ '\r': '\\r',
262
+ '"' : '\\"',
263
+ '\\': '\\\\'
264
+ },
265
+ rep;
266
+
267
+
268
+ function quote(string) {
269
+
270
+ // If the string contains no control characters, no quote characters, and no
271
+ // backslash characters, then we can safely slap some quotes around it.
272
+ // Otherwise we must also replace the offending characters with safe escape
273
+ // sequences.
274
+
275
+ escapable.lastIndex = 0;
276
+ return escapable.test(string) ?
277
+ '"' + string.replace(escapable, function (a) {
278
+ var c = meta[a];
279
+ return typeof c === 'string' ? c :
280
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
281
+ }) + '"' :
282
+ '"' + string + '"';
283
+ }
284
+
285
+
286
+ function str(key, holder) {
287
+
288
+ // Produce a string from holder[key].
289
+
290
+ var i, // The loop counter.
291
+ k, // The member key.
292
+ v, // The member value.
293
+ length,
294
+ mind = gap,
295
+ partial,
296
+ value = holder[key];
297
+
298
+ // If the value has a toJSON method, call it to obtain a replacement value.
299
+
300
+ if (value && typeof value === 'object' &&
301
+ typeof value.toJSON === 'function') {
302
+ value = value.toJSON(key);
303
+ }
304
+
305
+ // If we were called with a replacer function, then call the replacer to
306
+ // obtain a replacement value.
307
+
308
+ if (typeof rep === 'function') {
309
+ value = rep.call(holder, key, value);
310
+ }
311
+
312
+ // What happens next depends on the value's type.
313
+
314
+ switch (typeof value) {
315
+ case 'string':
316
+ return quote(value);
317
+
318
+ case 'number':
319
+
320
+ // JSON numbers must be finite. Encode non-finite numbers as null.
321
+
322
+ return isFinite(value) ? String(value) : 'null';
323
+
324
+ case 'boolean':
325
+ case 'null':
326
+
327
+ // If the value is a boolean or null, convert it to a string. Note:
328
+ // typeof null does not produce 'null'. The case is included here in
329
+ // the remote chance that this gets fixed someday.
330
+
331
+ return String(value);
332
+
333
+ // If the type is 'object', we might be dealing with an object or an array or
334
+ // null.
335
+
336
+ case 'object':
337
+
338
+ // Due to a specification blunder in ECMAScript, typeof null is 'object',
339
+ // so watch out for that case.
340
+
341
+ if (!value) {
342
+ return 'null';
343
+ }
344
+
345
+ // Make an array to hold the partial results of stringifying this object value.
346
+
347
+ gap += indent;
348
+ partial = [];
349
+
350
+ // Is the value an array?
351
+
352
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
353
+
354
+ // The value is an array. Stringify every element. Use null as a placeholder
355
+ // for non-JSON values.
356
+
357
+ length = value.length;
358
+ for (i = 0; i < length; i += 1) {
359
+ partial[i] = str(i, value) || 'null';
360
+ }
361
+
362
+ // Join all of the elements together, separated with commas, and wrap them in
363
+ // brackets.
364
+
365
+ v = partial.length === 0 ? '[]' :
366
+ gap ? '[\n' + gap +
367
+ partial.join(',\n' + gap) + '\n' +
368
+ mind + ']' :
369
+ '[' + partial.join(',') + ']';
370
+ gap = mind;
371
+ return v;
372
+ }
373
+
374
+ // If the replacer is an array, use it to select the members to be stringified.
375
+
376
+ if (rep && typeof rep === 'object') {
377
+ length = rep.length;
378
+ for (i = 0; i < length; i += 1) {
379
+ k = rep[i];
380
+ if (typeof k === 'string') {
381
+ v = str(k, value);
382
+ if (v) {
383
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
384
+ }
385
+ }
386
+ }
387
+ } else {
388
+
389
+ // Otherwise, iterate through all of the keys in the object.
390
+
391
+ for (k in value) {
392
+ if (Object.hasOwnProperty.call(value, k)) {
393
+ v = str(k, value);
394
+ if (v) {
395
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
396
+ }
397
+ }
398
+ }
399
+ }
400
+
401
+ // Join all of the member texts together, separated with commas,
402
+ // and wrap them in braces.
403
+
404
+ v = partial.length === 0 ? '{}' :
405
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
406
+ mind + '}' : '{' + partial.join(',') + '}';
407
+ gap = mind;
408
+ return v;
409
+ }
410
+ }
411
+
412
+ // If the JSON object does not yet have a stringify method, give it one.
413
+
414
+ if (typeof JSON.stringify !== 'function') {
415
+ JSON.stringify = function (value, replacer, space) {
416
+
417
+ // The stringify method takes a value and an optional replacer, and an optional
418
+ // space parameter, and returns a JSON text. The replacer can be a function
419
+ // that can replace values, or an array of strings that will select the keys.
420
+ // A default replacer method can be provided. Use of the space parameter can
421
+ // produce text that is more easily readable.
422
+
423
+ var i;
424
+ gap = '';
425
+ indent = '';
426
+
427
+ // If the space parameter is a number, make an indent string containing that
428
+ // many spaces.
429
+
430
+ if (typeof space === 'number') {
431
+ for (i = 0; i < space; i += 1) {
432
+ indent += ' ';
433
+ }
434
+
435
+ // If the space parameter is a string, it will be used as the indent string.
436
+
437
+ } else if (typeof space === 'string') {
438
+ indent = space;
439
+ }
440
+
441
+ // If there is a replacer, it must be a function or an array.
442
+ // Otherwise, throw an error.
443
+
444
+ rep = replacer;
445
+ if (replacer && typeof replacer !== 'function' &&
446
+ (typeof replacer !== 'object' ||
447
+ typeof replacer.length !== 'number')) {
448
+ throw new Error('JSON.stringify');
449
+ }
450
+
451
+ // Make a fake root object containing our value under the key of ''.
452
+ // Return the result of stringifying the value.
453
+
454
+ return str('', {'': value});
455
+ };
456
+ }
457
+
458
+
459
+ // If the JSON object does not yet have a parse method, give it one.
460
+
461
+ if (typeof JSON.parse !== 'function') {
462
+ JSON.parse = function (text, reviver) {
463
+
464
+ // The parse method takes a text and an optional reviver function, and returns
465
+ // a JavaScript value if the text is a valid JSON text.
466
+
467
+ var j;
468
+
469
+ function walk(holder, key) {
470
+
471
+ // The walk method is used to recursively walk the resulting structure so
472
+ // that modifications can be made.
473
+
474
+ var k, v, value = holder[key];
475
+ if (value && typeof value === 'object') {
476
+ for (k in value) {
477
+ if (Object.hasOwnProperty.call(value, k)) {
478
+ v = walk(value, k);
479
+ if (v !== undefined) {
480
+ value[k] = v;
481
+ } else {
482
+ delete value[k];
483
+ }
484
+ }
485
+ }
486
+ }
487
+ return reviver.call(holder, key, value);
488
+ }
489
+
490
+
491
+ // Parsing happens in four stages. In the first stage, we replace certain
492
+ // Unicode characters with escape sequences. JavaScript handles many characters
493
+ // incorrectly, either silently deleting them, or treating them as line endings.
494
+
495
+ cx.lastIndex = 0;
496
+ if (cx.test(text)) {
497
+ text = text.replace(cx, function (a) {
498
+ return '\\u' +
499
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
500
+ });
501
+ }
502
+
503
+ // In the second stage, we run the text against regular expressions that look
504
+ // for non-JSON patterns. We are especially concerned with '()' and 'new'
505
+ // because they can cause invocation, and '=' because it can cause mutation.
506
+ // But just to be safe, we want to reject all unexpected forms.
507
+
508
+ // We split the second stage into 4 regexp operations in order to work around
509
+ // crippling inefficiencies in IE's and Safari's regexp engines. First we
510
+ // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
511
+ // replace all simple value tokens with ']' characters. Third, we delete all
512
+ // open brackets that follow a colon or comma or that begin the text. Finally,
513
+ // we look to see that the remaining characters are only whitespace or ']' or
514
+ // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
515
+
516
+ if (/^[\],:{}\s]*$/.
517
+ test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
518
+ replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
519
+ replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
520
+
521
+ // In the third stage we use the eval function to compile the text into a
522
+ // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
523
+ // in JavaScript: it can begin a block or an object literal. We wrap the text
524
+ // in parens to eliminate the ambiguity.
525
+
526
+ j = eval('(' + text + ')');
527
+
528
+ // In the optional fourth stage, we recursively walk the new structure, passing
529
+ // each name/value pair to a reviver function for possible transformation.
530
+
531
+ return typeof reviver === 'function' ?
532
+ walk({'': j}, '') : j;
533
+ }
534
+
535
+ // If the text is not JSON parseable, then a SyntaxError is thrown.
536
+
537
+ throw new SyntaxError('JSON.parse');
538
+ };
539
+ }
540
+ }());
541
+ })(JsTestServer);