stalk 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitmodules +3 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +20 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +55 -0
- data/VERSION +1 -0
- data/bin/stalk +37 -0
- data/lib/stalker-gem.rb +0 -0
- data/stalker.gemspec +72 -0
- data/test/helper.rb +18 -0
- data/test/test_stalker-gem.rb +7 -0
- data/vendor/stalker/lib/example_parser.js +72 -0
- data/vendor/stalker/lib/object_compare.js +67 -0
- data/vendor/stalker/lib/shout.js +19 -0
- data/vendor/stalker/lib/stalker.js +109 -0
- data/vendor/stalker/lib/var_parser.js +51 -0
- data/vendor/stalker/stalk.js +32 -0
- data/vendor/stalker/vendor/multipartform.js +202 -0
- data/vendor/stalker/vendor/restler.js +300 -0
- data/vendor/stalker/vendor/underscore-min.js +27 -0
- metadata +124 -0
data/.document
ADDED
data/.gitmodules
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
group :development do
|
9
|
+
gem "shoulda", ">= 0"
|
10
|
+
gem "bundler", "~> 1.0.0"
|
11
|
+
gem "jeweler", "~> 1.6.4"
|
12
|
+
gem "rcov", ">= 0"
|
13
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
git (1.2.5)
|
5
|
+
jeweler (1.6.4)
|
6
|
+
bundler (~> 1.0)
|
7
|
+
git (>= 1.2.5)
|
8
|
+
rake
|
9
|
+
rake (0.9.2)
|
10
|
+
rcov (0.9.10)
|
11
|
+
shoulda (2.11.3)
|
12
|
+
|
13
|
+
PLATFORMS
|
14
|
+
ruby
|
15
|
+
|
16
|
+
DEPENDENCIES
|
17
|
+
bundler (~> 1.0.0)
|
18
|
+
jeweler (~> 1.6.4)
|
19
|
+
rcov
|
20
|
+
shoulda
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Jannis Hermanns
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= stalker-gem
|
2
|
+
|
3
|
+
Description goes here.
|
4
|
+
|
5
|
+
== Contributing to stalker-gem
|
6
|
+
|
7
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
8
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
9
|
+
* Fork the project
|
10
|
+
* Start a feature/bugfix branch
|
11
|
+
* Commit and push until you are happy with your contribution
|
12
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2011 Jannis Hermanns. See LICENSE.txt for
|
18
|
+
further details.
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "stalk"
|
18
|
+
gem.homepage = "http://github.com/jayniz/stalk-gem"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Installs stalker.js for you and adds a convenient stalk command}
|
21
|
+
gem.description = %Q{Takes the javascript of stalker and provides the stalker command that uses node to run stalker.js}
|
22
|
+
gem.email = "jannis@gmail.com"
|
23
|
+
gem.authors = ["Jannis Hermanns"]
|
24
|
+
gem.executables = %w( stalk )
|
25
|
+
gem.files.include 'vendor/**/*.js'
|
26
|
+
# dependencies defined in Gemfile
|
27
|
+
end
|
28
|
+
Jeweler::RubygemsDotOrgTasks.new
|
29
|
+
|
30
|
+
require 'rake/testtask'
|
31
|
+
Rake::TestTask.new(:test) do |test|
|
32
|
+
test.libs << 'lib' << 'test'
|
33
|
+
test.pattern = 'test/**/test_*.rb'
|
34
|
+
test.verbose = true
|
35
|
+
end
|
36
|
+
|
37
|
+
require 'rcov/rcovtask'
|
38
|
+
Rcov::RcovTask.new do |test|
|
39
|
+
test.libs << 'test'
|
40
|
+
test.pattern = 'test/**/test_*.rb'
|
41
|
+
test.verbose = true
|
42
|
+
test.rcov_opts << '--exclude "gems/*"'
|
43
|
+
end
|
44
|
+
|
45
|
+
task :default => :test
|
46
|
+
|
47
|
+
require 'rake/rdoctask'
|
48
|
+
Rake::RDocTask.new do |rdoc|
|
49
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
50
|
+
|
51
|
+
rdoc.rdoc_dir = 'rdoc'
|
52
|
+
rdoc.title = "stalker-gem #{version}"
|
53
|
+
rdoc.rdoc_files.include('README*')
|
54
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
55
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.1
|
data/bin/stalk
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Help us die gracefully
|
4
|
+
def die(code = 0)
|
5
|
+
puts "\nTest your API using stalker.js.\n\nUsage:\n stalk my_endpoints.txt http://host1:80\n\n"
|
6
|
+
exit code
|
7
|
+
end
|
8
|
+
|
9
|
+
# Less than two args can't be good
|
10
|
+
if ARGV.length != 2
|
11
|
+
die
|
12
|
+
end
|
13
|
+
|
14
|
+
# We use node.js to execute things
|
15
|
+
node = `which node`.chomp
|
16
|
+
unless File.executable?(node)
|
17
|
+
puts "Error: Can't execute node, make sure it's in your path (I tried #{node})"
|
18
|
+
end
|
19
|
+
|
20
|
+
# First argument is the definition of the tests, the
|
21
|
+
# rest is hosts to test
|
22
|
+
tests = ARGV.first
|
23
|
+
api = ARGV.last
|
24
|
+
|
25
|
+
# We can't read test definition, we fail!
|
26
|
+
unless File.file?(tests) and File.readable?(tests)
|
27
|
+
puts "Error: Can't open test definition file #{tests}"
|
28
|
+
die
|
29
|
+
end
|
30
|
+
|
31
|
+
# Where is stalker?
|
32
|
+
stalker = File.expand_path File.join(__FILE__, '..', '..', 'vendor', 'stalker', 'stalk.js')
|
33
|
+
|
34
|
+
# Replace our process with node
|
35
|
+
cmd = "#{node} #{stalker} #{api} #{tests}"
|
36
|
+
puts "Running: #{cmd}"
|
37
|
+
exec cmd
|
data/lib/stalker-gem.rb
ADDED
File without changes
|
data/stalker.gemspec
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{stalker}
|
8
|
+
s.version = "0.1.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Jannis Hermanns"]
|
12
|
+
s.date = %q{2011-10-10}
|
13
|
+
s.default_executable = %q{stalk}
|
14
|
+
s.description = %q{Takes the javascript of stalker and provides the stalker command that uses node to run stalker.js}
|
15
|
+
s.email = %q{jannis@gmail.com}
|
16
|
+
s.executables = ["stalk"]
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE.txt",
|
19
|
+
"README.rdoc"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".gitmodules",
|
24
|
+
"Gemfile",
|
25
|
+
"Gemfile.lock",
|
26
|
+
"LICENSE.txt",
|
27
|
+
"README.rdoc",
|
28
|
+
"Rakefile",
|
29
|
+
"VERSION",
|
30
|
+
"bin/stalk",
|
31
|
+
"lib/stalker-gem.rb",
|
32
|
+
"stalker.gemspec",
|
33
|
+
"test/helper.rb",
|
34
|
+
"test/test_stalker-gem.rb",
|
35
|
+
"vendor/stalker/lib/example_parser.js",
|
36
|
+
"vendor/stalker/lib/object_compare.js",
|
37
|
+
"vendor/stalker/lib/shout.js",
|
38
|
+
"vendor/stalker/lib/stalker.js",
|
39
|
+
"vendor/stalker/lib/var_parser.js",
|
40
|
+
"vendor/stalker/stalk.js",
|
41
|
+
"vendor/stalker/vendor/multipartform.js",
|
42
|
+
"vendor/stalker/vendor/restler.js",
|
43
|
+
"vendor/stalker/vendor/underscore-min.js"
|
44
|
+
]
|
45
|
+
s.homepage = %q{http://github.com/jayniz/stalker-gem}
|
46
|
+
s.licenses = ["MIT"]
|
47
|
+
s.require_paths = ["lib"]
|
48
|
+
s.rubygems_version = %q{1.5.2}
|
49
|
+
s.summary = %q{Installs stalker for you and adds a convenient stalker command}
|
50
|
+
|
51
|
+
if s.respond_to? :specification_version then
|
52
|
+
s.specification_version = 3
|
53
|
+
|
54
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
55
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
56
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
57
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
|
58
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
59
|
+
else
|
60
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
61
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
62
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
63
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
64
|
+
end
|
65
|
+
else
|
66
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
67
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
68
|
+
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
69
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
data/test/helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
require 'shoulda'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
+
require 'stalker-gem'
|
16
|
+
|
17
|
+
class Test::Unit::TestCase
|
18
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
var ExampleParser, VarParser, fs, _;
|
2
|
+
fs = require('fs');
|
3
|
+
_ = require('../vendor/underscore-min');
|
4
|
+
VarParser = require('./var_parser').VarParser;
|
5
|
+
ExampleParser = (function() {
|
6
|
+
function ExampleParser() {}
|
7
|
+
ExampleParser.prototype.parse = function(lines) {
|
8
|
+
var parsed;
|
9
|
+
this.lines = lines;
|
10
|
+
this.i = 0;
|
11
|
+
this.data = {
|
12
|
+
request: {},
|
13
|
+
response: {},
|
14
|
+
description: ""
|
15
|
+
};
|
16
|
+
this.current = null;
|
17
|
+
parsed = this.parseLine();
|
18
|
+
return parsed;
|
19
|
+
};
|
20
|
+
ExampleParser.prototype.parseLine = function() {
|
21
|
+
var match;
|
22
|
+
if (match = this.lines[this.i].match(/^##?[ ]*(.*)/)) {
|
23
|
+
this.data.description += match[1] + "\n";
|
24
|
+
} else if (match = this.lines[this.i].match(/^([A-Z]{3,6})([:]?[ ]+)(.*)$/)) {
|
25
|
+
this.parseMethod(match);
|
26
|
+
} else if (match = this.lines[this.i].match(/^Response:[ ]?(.*)$/)) {
|
27
|
+
this.switchMode(match);
|
28
|
+
} else {
|
29
|
+
if (this.current) {
|
30
|
+
this.parseVars();
|
31
|
+
}
|
32
|
+
}
|
33
|
+
return this.advance();
|
34
|
+
};
|
35
|
+
ExampleParser.prototype.parseMethod = function(match) {
|
36
|
+
this.data.method = match[1];
|
37
|
+
this.data.uri = match[3];
|
38
|
+
return this.current = "request";
|
39
|
+
};
|
40
|
+
ExampleParser.prototype.switchMode = function(match) {
|
41
|
+
this.current = "response";
|
42
|
+
return this.data["response_code"] = match[1];
|
43
|
+
};
|
44
|
+
ExampleParser.prototype.parseVars = function() {
|
45
|
+
var stop, vars;
|
46
|
+
stop = (this.current === "request" ? "Response" : null);
|
47
|
+
vars = VarParser.parse(this.lines, this.i, stop);
|
48
|
+
this.i = vars[0]++;
|
49
|
+
return this.data[this.current] = vars[1];
|
50
|
+
};
|
51
|
+
ExampleParser.prototype.advance = function() {
|
52
|
+
this.i++;
|
53
|
+
if (this.lines.length > this.i) {
|
54
|
+
return this.parseLine();
|
55
|
+
} else {
|
56
|
+
return this.result();
|
57
|
+
}
|
58
|
+
};
|
59
|
+
ExampleParser.prototype.result = function() {
|
60
|
+
return this.data;
|
61
|
+
};
|
62
|
+
return ExampleParser;
|
63
|
+
})();
|
64
|
+
ExampleParser.parseFile = function(fileName) {
|
65
|
+
var data, p;
|
66
|
+
data = fs.readFileSync(fileName, 'utf8');
|
67
|
+
p = new ExampleParser();
|
68
|
+
return _.map(data.split("\n##"), function(example) {
|
69
|
+
return p.parse(example.split("\n"));
|
70
|
+
});
|
71
|
+
};
|
72
|
+
exports.ExampleParser = ExampleParser;
|
@@ -0,0 +1,67 @@
|
|
1
|
+
var ObjectCompare, _;
|
2
|
+
_ = require('../vendor/underscore-min');
|
3
|
+
ObjectCompare = {
|
4
|
+
diff: function(needle, haystack) {
|
5
|
+
var diff, diffs;
|
6
|
+
diff = ObjectCompare.contained_in(needle, haystack);
|
7
|
+
diffs = {};
|
8
|
+
_.each(diff, function(d) {
|
9
|
+
var joined_key;
|
10
|
+
joined_key = _.union(d.path, [d.key]).join('][');
|
11
|
+
return diffs["[" + joined_key + "]"] = {
|
12
|
+
expected: d.expected,
|
13
|
+
actual: d.actual
|
14
|
+
};
|
15
|
+
});
|
16
|
+
return diffs;
|
17
|
+
},
|
18
|
+
contained_in: function(needle, haystack, history) {
|
19
|
+
var diffs;
|
20
|
+
if (history == null) {
|
21
|
+
history = [];
|
22
|
+
}
|
23
|
+
diffs = [];
|
24
|
+
_.each(needle, function(val, key) {
|
25
|
+
var expected, found, new_hist;
|
26
|
+
expected = haystack != null ? haystack[key] : void 0;
|
27
|
+
if (val === expected) {
|
28
|
+
return;
|
29
|
+
}
|
30
|
+
if (haystack instanceof Array && needle instanceof Array) {
|
31
|
+
found = false;
|
32
|
+
_.each(haystack, function(he) {
|
33
|
+
var arr_diff, h, v;
|
34
|
+
v = val instanceof Object ? val : {
|
35
|
+
test: val
|
36
|
+
};
|
37
|
+
h = he instanceof Object ? he : {
|
38
|
+
test: he
|
39
|
+
};
|
40
|
+
arr_diff = ObjectCompare.contained_in(v, h);
|
41
|
+
if (arr_diff.length === 0) {
|
42
|
+
return found = true;
|
43
|
+
}
|
44
|
+
});
|
45
|
+
if (found) {
|
46
|
+
return;
|
47
|
+
}
|
48
|
+
return diffs.push(ObjectCompare.diff_obj(history, key, val, expected));
|
49
|
+
} else if (val instanceof Object) {
|
50
|
+
new_hist = _.union(history, [key]);
|
51
|
+
return diffs.push(ObjectCompare.contained_in(val, expected, new_hist));
|
52
|
+
} else {
|
53
|
+
return diffs.push(ObjectCompare.diff_obj(history, key, val, expected));
|
54
|
+
}
|
55
|
+
});
|
56
|
+
return _.flatten(diffs);
|
57
|
+
},
|
58
|
+
diff_obj: function(history, key, expected, actual) {
|
59
|
+
return {
|
60
|
+
path: history,
|
61
|
+
key: key,
|
62
|
+
expected: expected,
|
63
|
+
actual: actual || null
|
64
|
+
};
|
65
|
+
}
|
66
|
+
};
|
67
|
+
exports.ObjectCompare = ObjectCompare;
|
@@ -0,0 +1,19 @@
|
|
1
|
+
var Shout, exec, sys;
|
2
|
+
sys = require('sys');
|
3
|
+
exec = require('child_process').exec;
|
4
|
+
Shout = {
|
5
|
+
green: function(name) {
|
6
|
+
return exec("shout -d ' ' --group stalker --expires-in 300 green '" + name + "' '" + name + "'");
|
7
|
+
},
|
8
|
+
red: function(name, msg) {
|
9
|
+
return exec("shout -d ' ' --group stalker red '" + name + "' '" + name + ": " + (JSON.stringify(msg)) + "'");
|
10
|
+
},
|
11
|
+
report: function(host, summary) {
|
12
|
+
if (summary.success) {
|
13
|
+
return Shout.green("" + host + "_" + summary.test);
|
14
|
+
} else {
|
15
|
+
return Shout.red("" + host + "_" + summary.test, summary.errors);
|
16
|
+
}
|
17
|
+
}
|
18
|
+
};
|
19
|
+
exports.Shout = Shout;
|
@@ -0,0 +1,109 @@
|
|
1
|
+
var ObjectCompare, Stalker, rest, _;
|
2
|
+
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
|
3
|
+
_ = require('../vendor/underscore-min');
|
4
|
+
rest = require('../vendor/restler');
|
5
|
+
ObjectCompare = require('./object_compare').ObjectCompare;
|
6
|
+
Stalker = (function() {
|
7
|
+
function Stalker(host, tests, cb) {
|
8
|
+
this.host = host;
|
9
|
+
this.tests = tests;
|
10
|
+
this.cb = cb;
|
11
|
+
this.current = 0;
|
12
|
+
this.errors = false;
|
13
|
+
}
|
14
|
+
Stalker.prototype.run = function() {
|
15
|
+
return this.next();
|
16
|
+
};
|
17
|
+
Stalker.prototype.next = function(summary) {
|
18
|
+
var t;
|
19
|
+
if (summary == null) {
|
20
|
+
summary = false;
|
21
|
+
}
|
22
|
+
if (summary) {
|
23
|
+
this.cb(summary);
|
24
|
+
}
|
25
|
+
if ((summary != null) && summary.success === false) {
|
26
|
+
this.errors = true;
|
27
|
+
}
|
28
|
+
if (!(t = this.tests[this.current])) {
|
29
|
+
return this.exit();
|
30
|
+
}
|
31
|
+
this.current++;
|
32
|
+
return this.probe(t);
|
33
|
+
};
|
34
|
+
Stalker.prototype.exit = function() {
|
35
|
+
var status;
|
36
|
+
status = this.errors ? 1 : 0;
|
37
|
+
return process.exit(status);
|
38
|
+
};
|
39
|
+
Stalker.prototype.probe = function(def, cb) {
|
40
|
+
var options, uri;
|
41
|
+
if (cb == null) {
|
42
|
+
cb = false;
|
43
|
+
}
|
44
|
+
uri = this.host + def.uri;
|
45
|
+
options = {
|
46
|
+
method: def.method,
|
47
|
+
data: JSON.stringify(def.request)
|
48
|
+
};
|
49
|
+
return rest.request(uri, options).on('complete', __bind(function(data, response) {
|
50
|
+
return this.check_response(data, response, def, cb);
|
51
|
+
}, this)).on('error', __bind(function(data, response) {
|
52
|
+
return this.check_response(data, response, def, cb);
|
53
|
+
}, this));
|
54
|
+
};
|
55
|
+
Stalker.prototype.check_response = function(data, response, def, cb) {
|
56
|
+
var summary;
|
57
|
+
summary = {
|
58
|
+
success: true,
|
59
|
+
test: def.uri,
|
60
|
+
errors: {}
|
61
|
+
};
|
62
|
+
this.check_status(summary, response, def);
|
63
|
+
this.check_object(summary, data, def);
|
64
|
+
if (cb) {
|
65
|
+
return cb(summary);
|
66
|
+
} else {
|
67
|
+
return this.next(summary);
|
68
|
+
}
|
69
|
+
};
|
70
|
+
Stalker.prototype.check_object = function(summary, data, def) {
|
71
|
+
var actual, diffs, expected;
|
72
|
+
if (def.response.body == null) {
|
73
|
+
return true;
|
74
|
+
}
|
75
|
+
actual = JSON.parse(def.response.body);
|
76
|
+
try {
|
77
|
+
expected = JSON.parse(data);
|
78
|
+
diffs = ObjectCompare.diff(actual, expected);
|
79
|
+
if (_.isEmpty(diffs)) {
|
80
|
+
return;
|
81
|
+
}
|
82
|
+
summary.success = false;
|
83
|
+
summary.errors.body = diffs;
|
84
|
+
return false;
|
85
|
+
} catch (e) {
|
86
|
+
summary.success = false;
|
87
|
+
summary.errors.body = {
|
88
|
+
"invalid_syntax": e
|
89
|
+
};
|
90
|
+
return false;
|
91
|
+
}
|
92
|
+
};
|
93
|
+
Stalker.prototype.check_status = function(summary, response, def) {
|
94
|
+
var actual, expected;
|
95
|
+
expected = parseInt(def.response_code);
|
96
|
+
actual = response.statusCode;
|
97
|
+
if (expected === actual) {
|
98
|
+
return true;
|
99
|
+
}
|
100
|
+
summary.errors.status = {
|
101
|
+
expected: expected,
|
102
|
+
actual: actual || null
|
103
|
+
};
|
104
|
+
summary.success = false;
|
105
|
+
return false;
|
106
|
+
};
|
107
|
+
return Stalker;
|
108
|
+
})();
|
109
|
+
exports.Stalker = Stalker;
|
@@ -0,0 +1,51 @@
|
|
1
|
+
var VarParser;
|
2
|
+
VarParser = {
|
3
|
+
parse: function(lines, start, stopword) {
|
4
|
+
var ret;
|
5
|
+
this.lines = lines;
|
6
|
+
this.current = null;
|
7
|
+
this.data = {};
|
8
|
+
this.i = start;
|
9
|
+
this.stop = stopword;
|
10
|
+
ret = this.parseLine();
|
11
|
+
return ret;
|
12
|
+
},
|
13
|
+
parseLine: function() {
|
14
|
+
var match;
|
15
|
+
if (this.stop && this.lines[this.i].substr(0, this.stop.length).toLowerCase() === this.stop.toLowerCase()) {
|
16
|
+
return [this.i - 1, this.data];
|
17
|
+
} else if (match = this.lines[this.i].match(/^([\w]+)\: *(.*)$/)) {
|
18
|
+
this.parseSectionHeader(match);
|
19
|
+
} else if (match = this.lines[this.i].match(/^ (.*)$/)) {
|
20
|
+
this.parseSample(match);
|
21
|
+
} else if (match = this.lines[this.i].match(/^[ ]+([\w-]+)\: *([^ ]?.*)$/)) {
|
22
|
+
this.parseValue(match);
|
23
|
+
} else {
|
24
|
+
|
25
|
+
}
|
26
|
+
return this.advance();
|
27
|
+
},
|
28
|
+
parseSectionHeader: function(match) {
|
29
|
+
return this.current = match[1].toLowerCase();
|
30
|
+
},
|
31
|
+
parseValue: function(match) {
|
32
|
+
this.data[this.current] = this.data[this.current] || {};
|
33
|
+
return this.data[this.current][match[1]] = match[2];
|
34
|
+
},
|
35
|
+
parseSample: function(match) {
|
36
|
+
this.data[this.current] = this.data[this.current] || "";
|
37
|
+
return this.data[this.current] += match[1] + "\n";
|
38
|
+
},
|
39
|
+
advance: function() {
|
40
|
+
this.i++;
|
41
|
+
if (this.lines.length > this.i) {
|
42
|
+
return this.parseLine();
|
43
|
+
} else {
|
44
|
+
return this.result();
|
45
|
+
}
|
46
|
+
},
|
47
|
+
result: function() {
|
48
|
+
return [this.i, this.data];
|
49
|
+
}
|
50
|
+
};
|
51
|
+
exports.VarParser = VarParser;
|
@@ -0,0 +1,32 @@
|
|
1
|
+
var ExampleParser, Shout, Stalker, filename, host, report, stalker, success, tests, verbose, _;
|
2
|
+
_ = require('./vendor/underscore-min');
|
3
|
+
Shout = require('./lib/shout').Shout;
|
4
|
+
Stalker = require('./lib/stalker').Stalker;
|
5
|
+
ExampleParser = require('./lib/example_parser').ExampleParser;
|
6
|
+
if (process.argv.length < 3) {
|
7
|
+
console.log("Usage: node stalk.js [-v] hostname definition.txt");
|
8
|
+
console.log(" e.g. node stalk.js -v http://production.host search_queries.txt");
|
9
|
+
process.exit(1);
|
10
|
+
}
|
11
|
+
filename = _.last(process.argv);
|
12
|
+
host = _.first(_.rest(process.argv, -2));
|
13
|
+
verbose = _.include(process.argv, "-v");
|
14
|
+
tests = ExampleParser.parseFile(filename);
|
15
|
+
success = true;
|
16
|
+
report = function(summary) {
|
17
|
+
var errors, result;
|
18
|
+
result = summary.success === true ? "✔" : "✗";
|
19
|
+
errors = summary.success === true ? "" : " errors " + JSON.stringify(summary.errors);
|
20
|
+
console.log("" + result + " " + summary.test + errors);
|
21
|
+
success = success && summary.success;
|
22
|
+
Shout.report(host, summary);
|
23
|
+
return true;
|
24
|
+
};
|
25
|
+
stalker = new Stalker(host, tests, report);
|
26
|
+
stalker.run();
|
27
|
+
if (verbose) {
|
28
|
+
_.map(tests, function(d) {
|
29
|
+
console.log(d);
|
30
|
+
return console.log("-----------------------------------------------\n");
|
31
|
+
});
|
32
|
+
}
|
@@ -0,0 +1,202 @@
|
|
1
|
+
var fs = require('fs');
|
2
|
+
var sys = require("sys")
|
3
|
+
exports.defaultBoundary = '48940923NODERESLTER3890457293';
|
4
|
+
|
5
|
+
|
6
|
+
// This little object allows us hijack the write method via duck-typing
|
7
|
+
// and write to strings or regular streams that support the write method.
|
8
|
+
function Stream(stream) {
|
9
|
+
//If the user pases a string for stream,we initalize one to write to
|
10
|
+
if (this._isString(stream)) {
|
11
|
+
this.string = "";
|
12
|
+
}
|
13
|
+
this.stream = stream;
|
14
|
+
|
15
|
+
}
|
16
|
+
|
17
|
+
Stream.prototype = {
|
18
|
+
//write to an internal String or to the Stream
|
19
|
+
write: function(data) {
|
20
|
+
if (this.string != undefined) {
|
21
|
+
this.string += data;
|
22
|
+
} else {
|
23
|
+
this.stream.write(data, "binary");
|
24
|
+
}
|
25
|
+
},
|
26
|
+
|
27
|
+
//stolen from underscore.js
|
28
|
+
_isString: function(obj) {
|
29
|
+
return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
function File(path, filename, fileSize, encoding, contentType) {
|
34
|
+
this.path = path;
|
35
|
+
this.filename = filename || this._basename(path);
|
36
|
+
this.fileSize = fileSize;
|
37
|
+
this.encoding = encoding || "binary";
|
38
|
+
this.contentType = contentType || 'application/octet-stream';
|
39
|
+
}
|
40
|
+
|
41
|
+
File.prototype = {
|
42
|
+
_basename: function(path) {
|
43
|
+
var parts = path.split(/\/|\\/);
|
44
|
+
return parts[parts.length - 1];
|
45
|
+
}
|
46
|
+
};
|
47
|
+
|
48
|
+
function Data(filename, contentType, data) {
|
49
|
+
this.filename = filename;
|
50
|
+
this.contentType = contentType || 'application/octet-stream';
|
51
|
+
this.data = data;
|
52
|
+
}
|
53
|
+
|
54
|
+
function Part(name, value, boundary) {
|
55
|
+
this.name = name;
|
56
|
+
this.value = value;
|
57
|
+
this.boundary = boundary;
|
58
|
+
}
|
59
|
+
|
60
|
+
|
61
|
+
Part.prototype = {
|
62
|
+
|
63
|
+
//returns the Content-Disposition header
|
64
|
+
header: function() {
|
65
|
+
var header;
|
66
|
+
|
67
|
+
if (this.value.data) {
|
68
|
+
header = "Content-Disposition: form-data; name=\"" + this.name +
|
69
|
+
"\"; filename=\"" + this.value.filename + "\"\r\n" +
|
70
|
+
"Content-Type: " + this.value.contentType;
|
71
|
+
} if (this.value instanceof File) {
|
72
|
+
header = "Content-Disposition: form-data; name=\"" + this.name +
|
73
|
+
"\"; filename=\"" + this.value.filename + "\"\r\n" +
|
74
|
+
"Content-Length: " + this.value.fileSize + "\r\n" +
|
75
|
+
"Content-Type: " + this.value.contentType;
|
76
|
+
} else {
|
77
|
+
header = "Content-Disposition: form-data; name=\"" + this.name + "\"";
|
78
|
+
}
|
79
|
+
|
80
|
+
return "--" + this.boundary + "\r\n" + header + "\r\n\r\n";
|
81
|
+
},
|
82
|
+
|
83
|
+
//calculates the size of the Part
|
84
|
+
sizeOf: function() {
|
85
|
+
var valueSize;
|
86
|
+
if (this.value instanceof File) {
|
87
|
+
valueSize = this.value.fileSize;
|
88
|
+
} else if (this.value.data) {
|
89
|
+
valueSize = this.value.data.length;
|
90
|
+
} else {
|
91
|
+
valueSize = this.value.length;
|
92
|
+
}
|
93
|
+
return valueSize + this.header().length + 2;
|
94
|
+
},
|
95
|
+
|
96
|
+
// Writes the Part out to a writable stream that supports the write(data) method
|
97
|
+
// You can also pass in a String and a String will be returned to the callback
|
98
|
+
// with the whole Part
|
99
|
+
// Calls the callback when complete
|
100
|
+
write: function(stream, callback) {
|
101
|
+
|
102
|
+
var self = this;
|
103
|
+
|
104
|
+
//first write the Content-Disposition
|
105
|
+
stream.write(this.header());
|
106
|
+
|
107
|
+
//Now write out the body of the Part
|
108
|
+
if (this.value instanceof File) {
|
109
|
+
fs.open(this.value.path, "r", 0666, function (err, fd) {
|
110
|
+
if (err) throw err;
|
111
|
+
|
112
|
+
position = 0;
|
113
|
+
|
114
|
+
(function reader () {
|
115
|
+
fs.read(fd, 1024 * 4, position, "binary", function (er, chunk) {
|
116
|
+
if (er) callback(err);
|
117
|
+
stream.write(chunk);
|
118
|
+
position += 1024 * 4;
|
119
|
+
if (chunk) reader();
|
120
|
+
else {
|
121
|
+
stream.write("\r\n")
|
122
|
+
callback();
|
123
|
+
fs.close(fd);
|
124
|
+
}
|
125
|
+
});
|
126
|
+
})(); // reader()
|
127
|
+
});
|
128
|
+
} else {
|
129
|
+
stream.write(this.value + "\r\n");
|
130
|
+
callback();
|
131
|
+
}
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
//Renamed to MultiPartRequest from Request
|
136
|
+
function MultiPartRequest(data, boundary) {
|
137
|
+
this.encoding = 'binary';
|
138
|
+
this.boundary = boundary || exports.defaultBoundary;
|
139
|
+
this.data = data;
|
140
|
+
this.partNames = this._partNames();
|
141
|
+
}
|
142
|
+
|
143
|
+
MultiPartRequest.prototype = {
|
144
|
+
_partNames: function() {
|
145
|
+
partNames = []
|
146
|
+
for (var name in this.data) {
|
147
|
+
partNames.push(name)
|
148
|
+
}
|
149
|
+
return partNames;
|
150
|
+
},
|
151
|
+
write: function(stream, callback) {
|
152
|
+
var partCount = 0, self = this;
|
153
|
+
|
154
|
+
// wrap the stream in our own Stream object
|
155
|
+
// See the Stream function above for the benefits of this
|
156
|
+
var stream = new Stream(stream);
|
157
|
+
|
158
|
+
// Let each part write itself out to the stream
|
159
|
+
(function writePart() {
|
160
|
+
partName = this.partNames[partCount];
|
161
|
+
part = new Part(partName, self.data[partName], self.boundary);
|
162
|
+
part.write(stream, function (err) {
|
163
|
+
if (err) {
|
164
|
+
callback(err);
|
165
|
+
return;
|
166
|
+
}
|
167
|
+
partCount += 1;
|
168
|
+
if (partCount < self.partNames.length)
|
169
|
+
writePart();
|
170
|
+
else {
|
171
|
+
stream.write('--' + self.boundary + '--' + "\r\n");
|
172
|
+
|
173
|
+
if (callback) callback(stream.string || "");
|
174
|
+
}
|
175
|
+
});
|
176
|
+
})();
|
177
|
+
}
|
178
|
+
}
|
179
|
+
|
180
|
+
var exportMethods = {
|
181
|
+
file: function(path, filename, fileSize, encoding, contentType) {
|
182
|
+
return new File(path, filename, fileSize, encoding, contentType)
|
183
|
+
},
|
184
|
+
data: function(filename, contentType, data) {
|
185
|
+
return new Data(filename, contentType, data);
|
186
|
+
},
|
187
|
+
sizeOf: function(parts, boundary) {
|
188
|
+
var totalSize = 0;
|
189
|
+
boundary = boundary || exports.defaultBoundary;
|
190
|
+
for (var name in parts) totalSize += new Part(name, parts[name], boundary).sizeOf();
|
191
|
+
return totalSize + boundary.length + 6;
|
192
|
+
},
|
193
|
+
write: function(stream, data, callback, boundary) {
|
194
|
+
var r = new MultiPartRequest(data, boundary);
|
195
|
+
r.write(stream, callback);
|
196
|
+
return r;
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
Object.keys(exportMethods).forEach(function(exportMethod) {
|
201
|
+
exports[exportMethod] = exportMethods[exportMethod]
|
202
|
+
})
|
@@ -0,0 +1,300 @@
|
|
1
|
+
var sys = require('sys'),
|
2
|
+
http = require('http'),
|
3
|
+
https = require('https'),
|
4
|
+
url = require('url'),
|
5
|
+
qs = require('querystring'),
|
6
|
+
multipart = require('./multipartform');
|
7
|
+
|
8
|
+
function mixin(target, source) {
|
9
|
+
Object.keys(source).forEach(function(key) {
|
10
|
+
target[key] = source[key];
|
11
|
+
});
|
12
|
+
|
13
|
+
return target;
|
14
|
+
}
|
15
|
+
|
16
|
+
function Request(uri, options) {
|
17
|
+
this.url = url.parse(uri);
|
18
|
+
this.options = options;
|
19
|
+
this.headers = {
|
20
|
+
'Accept': '*/*',
|
21
|
+
'User-Agent': 'Restler for node.js',
|
22
|
+
'Host': this.url.host
|
23
|
+
}
|
24
|
+
|
25
|
+
mixin(this.headers, options.headers || {});
|
26
|
+
|
27
|
+
// set port and method defaults
|
28
|
+
if (!this.url.port) this.url.port = (this.url.protocol == 'https:') ? '443' : '80';
|
29
|
+
if (!this.options.method) this.options.method = (this.options.data) ? 'POST' : 'GET';
|
30
|
+
if (typeof this.options.followRedirects == 'undefined') this.options.followRedirects = true;
|
31
|
+
|
32
|
+
// stringify query given in options of not given in URL
|
33
|
+
if (this.options.query && !this.url.query) {
|
34
|
+
if (typeof this.options.query == 'object')
|
35
|
+
this.url.query = qs.stringify(this.options.query);
|
36
|
+
else this.url.query = this.options.query;
|
37
|
+
}
|
38
|
+
|
39
|
+
this._applyBasicAuth();
|
40
|
+
|
41
|
+
if (this.options.multipart) {
|
42
|
+
this.headers['Content-Type'] = 'multipart/form-data; boundary=' + multipart.defaultBoundary;
|
43
|
+
} else {
|
44
|
+
if (typeof this.options.data == 'object') {
|
45
|
+
this.options.data = qs.stringify(this.options.data);
|
46
|
+
this.headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
47
|
+
this.headers['Content-Length'] = this.options.data.length;
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
var proto = (this.url.protocol == 'https:') ? https : http;
|
52
|
+
|
53
|
+
this.request = proto.request({
|
54
|
+
host: this.url.hostname,
|
55
|
+
port: this.url.port,
|
56
|
+
path: this._fullPath(),
|
57
|
+
method: this.options.method,
|
58
|
+
headers: this.headers
|
59
|
+
});
|
60
|
+
|
61
|
+
this._makeRequest();
|
62
|
+
}
|
63
|
+
|
64
|
+
Request.prototype = new process.EventEmitter();
|
65
|
+
mixin(Request.prototype, {
|
66
|
+
_isRedirect: function(response) {
|
67
|
+
return ([301, 302].indexOf(response.statusCode) >= 0);
|
68
|
+
},
|
69
|
+
_fullPath: function() {
|
70
|
+
var path = this.url.pathname || '/';
|
71
|
+
if (this.url.hash) path += this.url.hash;
|
72
|
+
if (this.url.query) path += '?' + this.url.query;
|
73
|
+
return path;
|
74
|
+
},
|
75
|
+
_applyBasicAuth: function() {
|
76
|
+
var authParts;
|
77
|
+
|
78
|
+
if (this.url.auth) {
|
79
|
+
authParts = this.url.auth.split(':');
|
80
|
+
this.options.username = authParts[0];
|
81
|
+
this.options.password = authParts[1];
|
82
|
+
}
|
83
|
+
|
84
|
+
if (this.options.username && this.options.password) {
|
85
|
+
var b = new Buffer([this.options.username, this.options.password].join(':'));
|
86
|
+
this.headers['Authorization'] = "Basic " + b.toString('base64');
|
87
|
+
}
|
88
|
+
},
|
89
|
+
_responseHandler: function(response) {
|
90
|
+
var self = this;
|
91
|
+
|
92
|
+
if (this._isRedirect(response) && this.options.followRedirects == true) {
|
93
|
+
try {
|
94
|
+
var location = url.resolve(this.url, response.headers['location']);
|
95
|
+
this.options.originalRequest = this;
|
96
|
+
|
97
|
+
request(location, this.options);
|
98
|
+
} catch(e) {
|
99
|
+
self._respond('error', '', 'Failed to follow redirect');
|
100
|
+
}
|
101
|
+
} else {
|
102
|
+
var body = '';
|
103
|
+
|
104
|
+
// TODO Handle different encodings
|
105
|
+
response.setEncoding('utf8');
|
106
|
+
|
107
|
+
response.on('data', function(chunk) {
|
108
|
+
body += chunk;
|
109
|
+
});
|
110
|
+
|
111
|
+
response.on('end', function() {
|
112
|
+
if (self.options.parser) {
|
113
|
+
self.options.parser.call(response, body, function(parsedData) {
|
114
|
+
|
115
|
+
self._fireEvents(parsedData, response);
|
116
|
+
});
|
117
|
+
} else {
|
118
|
+
self._fireEvents(body, response);
|
119
|
+
}
|
120
|
+
});
|
121
|
+
}
|
122
|
+
},
|
123
|
+
_respond: function(type, data, response) {
|
124
|
+
if (this.options.originalRequest) {
|
125
|
+
this.options.originalRequest.emit(type, data, response);
|
126
|
+
} else {
|
127
|
+
this.emit(type, data, response);
|
128
|
+
}
|
129
|
+
},
|
130
|
+
_fireEvents: function(body, response) {
|
131
|
+
if (parseInt(response.statusCode) >= 400) this._respond('error', body, response);
|
132
|
+
else this._respond('success', body, response);
|
133
|
+
|
134
|
+
this._respond(response.statusCode.toString().replace(/\d{2}$/, 'XX'), body, response);
|
135
|
+
this._respond(response.statusCode.toString(), body, response);
|
136
|
+
this._respond('complete', body, response);
|
137
|
+
},
|
138
|
+
_makeRequest: function() {
|
139
|
+
var self = this;
|
140
|
+
|
141
|
+
this.request.on('response', function(response) {
|
142
|
+
self._responseHandler(response);
|
143
|
+
}).on('error', function(err) {
|
144
|
+
self._respond('error', null, err);
|
145
|
+
});
|
146
|
+
},
|
147
|
+
run: function() {
|
148
|
+
var self = this;
|
149
|
+
|
150
|
+
if (this.options.multipart) {
|
151
|
+
multipart.write(this.request, this.options.data, function() {
|
152
|
+
self.request.end();
|
153
|
+
});
|
154
|
+
} else {
|
155
|
+
if (this.options.data) {
|
156
|
+
this.request.write(this.options.data.toString(), this.options.encoding || 'utf8');
|
157
|
+
}
|
158
|
+
this.request.end();
|
159
|
+
}
|
160
|
+
|
161
|
+
return this;
|
162
|
+
}
|
163
|
+
});
|
164
|
+
|
165
|
+
function shortcutOptions(options, method) {
|
166
|
+
options = options || {};
|
167
|
+
options.method = method;
|
168
|
+
options.parser = (typeof options.parser !== "undefined") ? options.parser : parsers.auto;
|
169
|
+
return options;
|
170
|
+
}
|
171
|
+
|
172
|
+
function request(url, options) {
|
173
|
+
return (new Request(url, options)).run();
|
174
|
+
}
|
175
|
+
|
176
|
+
function get(url, options) {
|
177
|
+
return request(url, shortcutOptions(options, 'GET'));
|
178
|
+
}
|
179
|
+
|
180
|
+
function post(url, options) {
|
181
|
+
return request(url, shortcutOptions(options, 'POST'));
|
182
|
+
}
|
183
|
+
|
184
|
+
function put(url, options) {
|
185
|
+
return request(url, shortcutOptions(options, 'PUT'));
|
186
|
+
}
|
187
|
+
|
188
|
+
function del(url, options) {
|
189
|
+
return request(url, shortcutOptions(options, 'DELETE'));
|
190
|
+
}
|
191
|
+
|
192
|
+
var parsers = {
|
193
|
+
auto: function(data, callback) {
|
194
|
+
var contentType = this.headers['content-type'];
|
195
|
+
|
196
|
+
if (contentType) {
|
197
|
+
for (var matcher in parsers.auto.matchers) {
|
198
|
+
|
199
|
+
if (contentType.indexOf(matcher) == 0) {
|
200
|
+
return parsers.auto.matchers[matcher].call(this, data, callback);
|
201
|
+
}
|
202
|
+
}
|
203
|
+
}
|
204
|
+
|
205
|
+
callback(data);
|
206
|
+
},
|
207
|
+
json: function(data, callback) {
|
208
|
+
callback(data && JSON.parse(data));
|
209
|
+
}
|
210
|
+
}
|
211
|
+
|
212
|
+
parsers.auto.matchers = {
|
213
|
+
'application/json': parsers.json
|
214
|
+
};
|
215
|
+
|
216
|
+
try {
|
217
|
+
var yaml = require('yaml');
|
218
|
+
|
219
|
+
parsers.yaml = function(data, callback) {
|
220
|
+
return callback(data && yaml.eval(data));
|
221
|
+
};
|
222
|
+
|
223
|
+
parsers.auto.matchers['application/yaml'] = parsers.yaml;
|
224
|
+
} catch(e) {}
|
225
|
+
|
226
|
+
try {
|
227
|
+
var xml2js = require('xml2js');
|
228
|
+
|
229
|
+
parsers.xml = function(data, callback) {
|
230
|
+
if (data) {
|
231
|
+
var parser = new xml2js.Parser();
|
232
|
+
|
233
|
+
parser.on('end', function(result) {
|
234
|
+
callback(result);
|
235
|
+
});
|
236
|
+
|
237
|
+
parser.parseString(data);
|
238
|
+
} else {
|
239
|
+
callback();
|
240
|
+
}
|
241
|
+
};
|
242
|
+
|
243
|
+
parsers.auto.matchers['application/xml'] = parsers.xml;
|
244
|
+
} catch(e) {}
|
245
|
+
|
246
|
+
|
247
|
+
function Service(defaults) {
|
248
|
+
if (defaults.baseURL) {
|
249
|
+
this.baseURL = defaults.baseURL;
|
250
|
+
delete defaults.baseURL;
|
251
|
+
}
|
252
|
+
|
253
|
+
this.defaults = defaults;
|
254
|
+
}
|
255
|
+
|
256
|
+
mixin(Service.prototype, {
|
257
|
+
request: function(path, options) {
|
258
|
+
return request(this._url(path), this._withDefaults(options));
|
259
|
+
},
|
260
|
+
get: function(path, options) {
|
261
|
+
return get(this._url(path), this._withDefaults(options));
|
262
|
+
},
|
263
|
+
put: function(path, options) {
|
264
|
+
return put(this._url(path), this._withDefaults(options));
|
265
|
+
},
|
266
|
+
post: function(path, options) {
|
267
|
+
return post(this._url(path), this._withDefaults(options));
|
268
|
+
},
|
269
|
+
del: function(path, options) {
|
270
|
+
return del(this._url(path), this._withDefaults(options));
|
271
|
+
},
|
272
|
+
_url: function(path) {
|
273
|
+
if (this.baseURL) return url.resolve(this.baseURL, path);
|
274
|
+
else return path;
|
275
|
+
},
|
276
|
+
_withDefaults: function(options) {
|
277
|
+
var o = mixin({}, this.defaults);
|
278
|
+
return mixin(o, options);
|
279
|
+
}
|
280
|
+
});
|
281
|
+
|
282
|
+
function service(constructor, defaults, methods) {
|
283
|
+
constructor.prototype = new Service(defaults || {});
|
284
|
+
mixin(constructor.prototype, methods);
|
285
|
+
return constructor;
|
286
|
+
}
|
287
|
+
|
288
|
+
mixin(exports, {
|
289
|
+
Request: Request,
|
290
|
+
Service: Service,
|
291
|
+
request: request,
|
292
|
+
service: service,
|
293
|
+
get: get,
|
294
|
+
post: post,
|
295
|
+
put: put,
|
296
|
+
del: del,
|
297
|
+
parsers: parsers,
|
298
|
+
file: multipart.file,
|
299
|
+
data: multipart.data
|
300
|
+
});
|
@@ -0,0 +1,27 @@
|
|
1
|
+
// Underscore.js 1.1.7
|
2
|
+
// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
|
3
|
+
// Underscore is freely distributable under the MIT license.
|
4
|
+
// Portions of Underscore are inspired or borrowed from Prototype,
|
5
|
+
// Oliver Steele's Functional, and John Resig's Micro-Templating.
|
6
|
+
// For all details and documentation:
|
7
|
+
// http://documentcloud.github.com/underscore
|
8
|
+
(function(){var p=this,C=p._,m={},i=Array.prototype,n=Object.prototype,f=i.slice,D=i.unshift,E=n.toString,l=n.hasOwnProperty,s=i.forEach,t=i.map,u=i.reduce,v=i.reduceRight,w=i.filter,x=i.every,y=i.some,o=i.indexOf,z=i.lastIndexOf;n=Array.isArray;var F=Object.keys,q=Function.prototype.bind,b=function(a){return new j(a)};typeof module!=="undefined"&&module.exports?(module.exports=b,b._=b):p._=b;b.VERSION="1.1.7";var h=b.each=b.forEach=function(a,c,b){if(a!=null)if(s&&a.forEach===s)a.forEach(c,b);else if(a.length===
|
9
|
+
+a.length)for(var e=0,k=a.length;e<k;e++){if(e in a&&c.call(b,a[e],e,a)===m)break}else for(e in a)if(l.call(a,e)&&c.call(b,a[e],e,a)===m)break};b.map=function(a,c,b){var e=[];if(a==null)return e;if(t&&a.map===t)return a.map(c,b);h(a,function(a,g,G){e[e.length]=c.call(b,a,g,G)});return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var k=d!==void 0;a==null&&(a=[]);if(u&&a.reduce===u)return e&&(c=b.bind(c,e)),k?a.reduce(c,d):a.reduce(c);h(a,function(a,b,f){k?d=c.call(e,d,a,b,f):(d=a,k=!0)});if(!k)throw new TypeError("Reduce of empty array with no initial value");
|
10
|
+
return d};b.reduceRight=b.foldr=function(a,c,d,e){a==null&&(a=[]);if(v&&a.reduceRight===v)return e&&(c=b.bind(c,e)),d!==void 0?a.reduceRight(c,d):a.reduceRight(c);a=(b.isArray(a)?a.slice():b.toArray(a)).reverse();return b.reduce(a,c,d,e)};b.find=b.detect=function(a,c,b){var e;A(a,function(a,g,f){if(c.call(b,a,g,f))return e=a,!0});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(w&&a.filter===w)return a.filter(c,b);h(a,function(a,g,f){c.call(b,a,g,f)&&(e[e.length]=a)});return e};
|
11
|
+
b.reject=function(a,c,b){var e=[];if(a==null)return e;h(a,function(a,g,f){c.call(b,a,g,f)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=!0;if(a==null)return e;if(x&&a.every===x)return a.every(c,b);h(a,function(a,g,f){if(!(e=e&&c.call(b,a,g,f)))return m});return e};var A=b.some=b.any=function(a,c,d){c=c||b.identity;var e=!1;if(a==null)return e;if(y&&a.some===y)return a.some(c,d);h(a,function(a,b,f){if(e|=c.call(d,a,b,f))return m});return!!e};b.include=b.contains=function(a,c){var b=
|
12
|
+
!1;if(a==null)return b;if(o&&a.indexOf===o)return a.indexOf(c)!=-1;A(a,function(a){if(b=a===c)return!0});return b};b.invoke=function(a,c){var d=f.call(arguments,2);return b.map(a,function(a){return(c.call?c||a:a[c]).apply(a,d)})};b.pluck=function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);var e={computed:-Infinity};h(a,function(a,b,f){b=c?c.call(d,a,b,f):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,
|
13
|
+
c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};h(a,function(a,b,f){b=c?c.call(d,a,b,f):a;b<e.computed&&(e={value:a,computed:b})});return e.value};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,f){return{value:a,criteria:c.call(d,a,b,f)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,b){var d={};h(a,function(a,f){var g=b(a,f);(d[g]||(d[g]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||
|
14
|
+
(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){if(!a)return[];if(a.toArray)return a.toArray();if(b.isArray(a))return f.call(a);if(b.isArguments(a))return f.call(a);return b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?f.call(a,0,b):a[0]};b.rest=b.tail=function(a,b,d){return f.call(a,b==null||d?1:b)};b.last=function(a){return a[a.length-1]};b.compact=function(a){return b.filter(a,
|
15
|
+
function(a){return!!a})};b.flatten=function(a){return b.reduce(a,function(a,d){if(b.isArray(d))return a.concat(b.flatten(d));a[a.length]=d;return a},[])};b.without=function(a){return b.difference(a,f.call(arguments,1))};b.uniq=b.unique=function(a,c){return b.reduce(a,function(a,e,f){if(0==f||(c===!0?b.last(a)!=e:!b.include(a,e)))a[a.length]=e;return a},[])};b.union=function(){return b.uniq(b.flatten(arguments))};b.intersection=b.intersect=function(a){var c=f.call(arguments,1);return b.filter(b.uniq(a),
|
16
|
+
function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a,c){return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=f.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(o&&a.indexOf===o)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(a[d]===c)return d;return-1};b.lastIndexOf=function(a,
|
17
|
+
b){if(a==null)return-1;if(z&&a.lastIndexOf===z)return a.lastIndexOf(b);for(var d=a.length;d--;)if(a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);d=arguments[2]||1;for(var e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};b.bind=function(a,b){if(a.bind===q&&q)return q.apply(a,f.call(arguments,1));var d=f.call(arguments,2);return function(){return a.apply(b,d.concat(f.call(arguments)))}};b.bindAll=function(a){var c=f.call(arguments,1);
|
18
|
+
c.length==0&&(c=b.functions(a));h(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var b=c.apply(this,arguments);return l.call(d,b)?d[b]:d[b]=a.apply(this,arguments)}};b.delay=function(a,b){var d=f.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(f.call(arguments,1)))};var B=function(a,b,d){var e;return function(){var f=this,g=arguments,h=function(){e=null;
|
19
|
+
a.apply(f,g)};d&&clearTimeout(e);if(d||!e)e=setTimeout(h,b)}};b.throttle=function(a,b){return B(a,b,!1)};b.debounce=function(a,b){return B(a,b,!0)};b.once=function(a){var b=!1,d;return function(){if(b)return d;b=!0;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(f.call(arguments));return b.apply(this,d)}};b.compose=function(){var a=f.call(arguments);return function(){for(var b=f.call(arguments),d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=
|
20
|
+
function(a,b){return function(){if(--a<1)return b.apply(this,arguments)}};b.keys=F||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)l.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){h(f.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){h(f.call(arguments,
|
21
|
+
1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,c){if(a===c)return!0;var d=typeof a;if(d!=typeof c)return!1;if(a==c)return!0;if(!a&&c||a&&!c)return!1;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual)return a.isEqual(c);if(c.isEqual)return c.isEqual(a);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return!1;
|
22
|
+
if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return!1;if(a.length&&a.length!==c.length)return!1;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return!1;for(var f in a)if(!(f in c)||!b.isEqual(a[f],c[f]))return!1;return!0};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(l.call(a,c))return!1;return!0};b.isElement=function(a){return!!(a&&a.nodeType==
|
23
|
+
1)};b.isArray=n||function(a){return E.call(a)==="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return!(!a||!l.call(a,"callee"))};b.isFunction=function(a){return!(!a||!a.constructor||!a.call||!a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};b.isNumber=function(a){return!!(a===0||a&&a.toExponential&&a.toFixed)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===!0||a===!1};b.isDate=function(a){return!(!a||!a.getTimezoneOffset||
|
24
|
+
!a.setUTCFullYear)};b.isRegExp=function(a){return!(!a||!a.test||!a.exec||!(a.ignoreCase||a.ignoreCase===!1))};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.noConflict=function(){p._=C;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.mixin=function(a){h(b.functions(a),function(c){H(c,b[c]=a[c])})};var I=0;b.uniqueId=function(a){var b=I++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g};
|
25
|
+
b.template=function(a,c){var d=b.templateSettings;d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(d.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');";d=new Function("obj",d);return c?d(c):d};
|
26
|
+
var j=function(a){this._wrapped=a};b.prototype=j.prototype;var r=function(a,c){return c?b(a).chain():a},H=function(a,c){j.prototype[a]=function(){var a=f.call(arguments);D.call(a,this._wrapped);return r(c.apply(b,a),this._chain)}};b.mixin(b);h(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var b=i[a];j.prototype[a]=function(){b.apply(this._wrapped,arguments);return r(this._wrapped,this._chain)}});h(["concat","join","slice"],function(a){var b=i[a];j.prototype[a]=function(){return r(b.apply(this._wrapped,
|
27
|
+
arguments),this._chain)}});j.prototype.chain=function(){this._chain=!0;return this};j.prototype.value=function(){return this._wrapped}})();
|
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stalk
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jannis Hermanns
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-10-10 00:00:00 +02:00
|
14
|
+
default_executable: stalk
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: shoulda
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :development
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 1.0.0
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: jeweler
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.6.4
|
46
|
+
type: :development
|
47
|
+
prerelease: false
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rcov
|
51
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
type: :development
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: *id004
|
60
|
+
description: Takes the javascript of stalker and provides the stalker command that uses node to run stalker.js
|
61
|
+
email: jannis@gmail.com
|
62
|
+
executables:
|
63
|
+
- stalk
|
64
|
+
extensions: []
|
65
|
+
|
66
|
+
extra_rdoc_files:
|
67
|
+
- LICENSE.txt
|
68
|
+
- README.rdoc
|
69
|
+
files:
|
70
|
+
- .document
|
71
|
+
- .gitmodules
|
72
|
+
- Gemfile
|
73
|
+
- Gemfile.lock
|
74
|
+
- LICENSE.txt
|
75
|
+
- README.rdoc
|
76
|
+
- Rakefile
|
77
|
+
- VERSION
|
78
|
+
- bin/stalk
|
79
|
+
- lib/stalker-gem.rb
|
80
|
+
- stalker.gemspec
|
81
|
+
- test/helper.rb
|
82
|
+
- test/test_stalker-gem.rb
|
83
|
+
- vendor/stalker/lib/example_parser.js
|
84
|
+
- vendor/stalker/lib/object_compare.js
|
85
|
+
- vendor/stalker/lib/shout.js
|
86
|
+
- vendor/stalker/lib/stalker.js
|
87
|
+
- vendor/stalker/lib/var_parser.js
|
88
|
+
- vendor/stalker/stalk.js
|
89
|
+
- vendor/stalker/vendor/multipartform.js
|
90
|
+
- vendor/stalker/vendor/restler.js
|
91
|
+
- vendor/stalker/vendor/underscore-min.js
|
92
|
+
has_rdoc: true
|
93
|
+
homepage: http://github.com/jayniz/stalk-gem
|
94
|
+
licenses:
|
95
|
+
- MIT
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
hash: 776588002380951086
|
107
|
+
segments:
|
108
|
+
- 0
|
109
|
+
version: "0"
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: "0"
|
116
|
+
requirements: []
|
117
|
+
|
118
|
+
rubyforge_project:
|
119
|
+
rubygems_version: 1.5.2
|
120
|
+
signing_key:
|
121
|
+
specification_version: 3
|
122
|
+
summary: Installs stalker.js for you and adds a convenient stalk command
|
123
|
+
test_files: []
|
124
|
+
|