ichabod 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,206 @@
1
+ Math.guid = function(){
2
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
3
+ var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
4
+ return v.toString(16);
5
+ }).toUpperCase();
6
+ };
7
+
8
+ var Model = Klass.create();
9
+
10
+ // Alias create
11
+ Model.setup = Model.create;
12
+
13
+ Model.extend({
14
+ init: function(){
15
+ this.records = {};
16
+ this.attributes = [];
17
+ },
18
+
19
+ find: function(id){
20
+ var record = this.records[id];
21
+ if ( !record ) throw("Unknown record");
22
+ return record.dup();
23
+ },
24
+
25
+ exists: function(id){
26
+ try {
27
+ return this.find(id);
28
+ } catch (e) {
29
+ return false;
30
+ }
31
+ },
32
+
33
+ populate: function(values){
34
+ // Reset model & records
35
+ this.init();
36
+
37
+ for (var i=0, il = values.length; i < il; i++) {
38
+ var record = this.inst(values[i]);
39
+ record.newRecord = false;
40
+ this.records[record.id] = record;
41
+ }
42
+ },
43
+
44
+ select: function(callback){
45
+ var result = [];
46
+
47
+ for (var key in this.records)
48
+ if (callback(this.records[key]))
49
+ result.push(this.records[key]);
50
+
51
+ return this.dupArray(result);
52
+ },
53
+
54
+ findByAttribute: function(name, value){
55
+ for (var key in this.records)
56
+ if (this.records[key][name] == value)
57
+ return this.records[key].dup();
58
+ },
59
+
60
+ findAllByAttribute: function(name, value){
61
+ return(this.select(function(item){
62
+ return(item[name] == value);
63
+ }));
64
+ },
65
+
66
+ each: function(callback){
67
+ for (var key in this.records) {
68
+ callback(this.records[key]);
69
+ }
70
+ },
71
+
72
+ all: function(){
73
+ return this.dupArray(this.recordsValues());
74
+ },
75
+
76
+ first: function(){
77
+ var record = this.recordsValues()[0];
78
+ return(record && record.dup());
79
+ },
80
+
81
+ last: function(){
82
+ var values = this.recordsValues()
83
+ var record = values[values.length - 1];
84
+ return(record && record.dup());
85
+ },
86
+
87
+ count: function(){
88
+ return this.recordsValues().length;
89
+ },
90
+
91
+ deleteAll: function(){
92
+ for (var key in this.records)
93
+ delete this.records[key];
94
+ },
95
+
96
+ destroyAll: function(){
97
+ for (var key in this.records)
98
+ this.records[key].destroy();
99
+ },
100
+
101
+ update: function(id, atts){
102
+ this.find(id).updateAttributes(atts);
103
+ },
104
+
105
+ create: function(atts){
106
+ var record = this.inst(atts);
107
+ record.save();
108
+ return record;
109
+ },
110
+
111
+ destroy: function(id){
112
+ this.find(id).destroy();
113
+ },
114
+
115
+ // Private
116
+
117
+ recordsValues: function(){
118
+ var result = []
119
+ for (var key in this.records)
120
+ result.push(this.records[key])
121
+ return result;
122
+ },
123
+
124
+ dupArray: function(array){
125
+ return array.map(function(item){
126
+ return item.dup();
127
+ });
128
+ }
129
+ });
130
+
131
+ Model.include({
132
+ newRecord: true,
133
+
134
+ init: function(atts){
135
+ if (atts) this.load(atts);
136
+ },
137
+
138
+ isNew: function(){
139
+ return this.newRecord;
140
+ },
141
+
142
+ validate: function(){ },
143
+
144
+ load: function(attributes){
145
+ for(var name in attributes)
146
+ this[name] = attributes[name];
147
+ },
148
+
149
+ attributes: function(){
150
+ var result = {};
151
+ for(var i in this.parent.attributes) {
152
+ var attr = this.parent.attributes[i];
153
+ result[attr] = this[attr];
154
+ }
155
+ result.id = this.id;
156
+ return result;
157
+ },
158
+
159
+ eql: function(rec){
160
+ return(rec && rec.id === this.id &&
161
+ rec.parent === this.parent);
162
+ },
163
+
164
+ save: function(){
165
+ if (this.validate() == false) return false;
166
+ this.newRecord ? this.create() : this.update();
167
+ },
168
+
169
+ updateAttribute: function(name, value){
170
+ this[name] = value;
171
+ return this.save();
172
+ },
173
+
174
+ updateAttributes: function(attributes){
175
+ this.load(attributes);
176
+ return this.save();
177
+ },
178
+
179
+ destroy: function(){
180
+ delete this.parent.records[this.id];
181
+ },
182
+
183
+ dup: function(){
184
+ return Object.create(this);
185
+ },
186
+
187
+ toJSON: function(){
188
+ return(JSON.stringify(this.attributes()));
189
+ },
190
+
191
+ // Private
192
+
193
+ update: function(){
194
+ this.parent.records[this.id] = this.dup();
195
+ },
196
+
197
+ generateID: function(){
198
+ return Math.guid();
199
+ },
200
+
201
+ create: function(){
202
+ if ( !this.id ) this.id = this.generateID();
203
+ this.newRecord = false;
204
+ this.parent.records[this.id] = this.dup();
205
+ }
206
+ });
@@ -0,0 +1,34 @@
1
+ describe("Model", function(){
2
+ var Asset;
3
+
4
+ beforeEach(function(){
5
+ Asset = Model.setup();
6
+ Asset.attributes = ["name"];
7
+ });
8
+
9
+ it("can create records", function(){
10
+ var asset = Asset.create({name: "test.pdf"});
11
+ expect(Asset.first()).toEqual(asset);
12
+ });
13
+
14
+ it("can update records", function(){
15
+ var asset = Asset.create({name: "test.pdf"});
16
+
17
+ expect(Asset.first().name).toEqual("test.pdf");
18
+
19
+ asset.name = "wem.pdf";
20
+ asset.save();
21
+
22
+ expect(Asset.first().name).toEqual("wem.pdf");
23
+ });
24
+
25
+ it("can destroy records", function(){
26
+ var asset = Asset.create({name: "test.pdf"});
27
+
28
+ expect(Asset.first()).toEqual(asset);
29
+
30
+ asset.destroy();
31
+
32
+ expect(Asset.first()).toBeFalsy();
33
+ });
34
+ });
@@ -0,0 +1,61 @@
1
+ # Help convert objects WebKit's JS returns into things we can use
2
+
3
+ class NSCFNumber
4
+ def inspect
5
+ if Integer(self) == Float(self)
6
+ Integer(self).to_s
7
+ else
8
+ Float(self).to_s
9
+ end
10
+ end
11
+ end
12
+
13
+ class NSCFBoolean
14
+ def inspect
15
+ (self == NSNumber.numberWithBool(true)).to_s
16
+ end
17
+ end
18
+
19
+ class WebScriptObject
20
+ def inspect
21
+ callWebScriptMethod("toString", withArguments:nil)
22
+ end
23
+ end
24
+
25
+ class WebUndefined
26
+ def &(ob)
27
+ false
28
+ end
29
+
30
+ def ^(ob)
31
+ !ob
32
+ end
33
+
34
+ def inspect
35
+ 'undefined'
36
+ end
37
+
38
+ def nil?
39
+ true
40
+ end
41
+
42
+ def to_i
43
+ 0
44
+ end
45
+
46
+ def to_f
47
+ 0.0
48
+ end
49
+
50
+ def to_a
51
+ []
52
+ end
53
+
54
+ def to_s
55
+ ''
56
+ end
57
+
58
+ def |(ob)
59
+ false
60
+ end
61
+ end
@@ -0,0 +1,28 @@
1
+ module Ichabod
2
+ module Delegate
3
+ class Load
4
+ attr_reader :runtime
5
+
6
+ def initialize(runtime)
7
+ @runtime = runtime
8
+ end
9
+
10
+ def webView(sender, didFinishLoadForFrame:frame)
11
+ # Page loaded...
12
+ end
13
+
14
+ def webView(webView, didClearWindowObject:windowScriptObject, forFrame:frame)
15
+ windowScriptObject.setValue(ScriptObject::Ruby.new(runtime), forKey:"Ruby")
16
+ windowScriptObject.setValue(ScriptObject::Ichabod.new(runtime), forKey:"Ichabod")
17
+ end
18
+
19
+ def webView(view, didFailLoadWithError:error, forFrame:frame)
20
+ raise "Failed: #{error.localizedDescription}"
21
+ end
22
+
23
+ def webView(view, didFailProvisionalLoadWithError:error, forFrame:frame)
24
+ raise "Failed: #{error.localizedDescription}"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ module Ichabod
2
+ module Delegate
3
+ class UI
4
+ attr_reader :runtime
5
+
6
+ def initialize(runtime)
7
+ @runtime = runtime
8
+ end
9
+
10
+ def webView(webView, addMessageToConsole:message)
11
+ puts message[:sourceURL] + ":" +
12
+ message[:lineNumber].to_s + " " +
13
+ message[:message]
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,47 @@
1
+ require 'readline'
2
+
3
+ module Ichabod
4
+ class Repl
5
+ Prompt = 'js> '
6
+ AwaitingInput = '?> '
7
+ Result = '=> '
8
+ HistoryFile = File.join(File.expand_path('~'), '.ichabod_history')
9
+
10
+ def self.start(dom = nil)
11
+ load_history
12
+ @parser = Ichabod::Runtime.new(:dom => dom)
13
+
14
+ loop do
15
+ input = Readline.readline(Prompt)
16
+ quit if input.nil?
17
+
18
+ begin
19
+ puts Result + @parser.eval(input).inspect.to_s
20
+ rescue => e
21
+ save_history
22
+ raise e
23
+ else
24
+ Readline::HISTORY.push(input)
25
+ end
26
+ end
27
+ end
28
+
29
+ def self.load_history
30
+ return unless File.exists? HistoryFile
31
+ File.readlines(HistoryFile).each do |line|
32
+ Readline::HISTORY.push line.chomp
33
+ end
34
+ end
35
+
36
+ def self.save_history
37
+ File.open(HistoryFile, 'w') do |f|
38
+ f.puts Readline::HISTORY.to_a.join("\n")
39
+ end
40
+ end
41
+
42
+ def self.quit
43
+ save_history
44
+ exit
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,58 @@
1
+ # http://developer.apple.com/documentation/Cocoa/Reference/WebKit/ObjC_classic/index.html
2
+
3
+ module Ichabod
4
+ class Runtime
5
+ attr_reader :webView
6
+
7
+ def initialize(options = {})
8
+ @webView = WebView.new
9
+ @webView.setFrameLoadDelegate(Delegate::Load.new(self))
10
+ @webView.setUIDelegate(Delegate::UI.new(self))
11
+ load_dom(options[:dom]) if options[:dom]
12
+ end
13
+
14
+ def navigate(url)
15
+ # Local file check
16
+ unless url =~ /^http/
17
+ url = "file://" + File.expand_path(url)
18
+ end
19
+
20
+ url = NSURL.alloc.initWithString(url)
21
+ @webView.mainFrame.loadRequest(NSURLRequest.requestWithURL(url))
22
+ self
23
+ end
24
+ alias_method :open, :navigate
25
+
26
+ def eval(js)
27
+ @webView.windowScriptObject.evaluateWebScript(js)
28
+ end
29
+
30
+ def eval_file(file)
31
+ file = File.expand_path(file.to_s, Ichabod::JS_PATH)
32
+ if File.exists? file
33
+ eval File.read(file)
34
+ elsif File.exists? file + '.js'
35
+ eval File.read(file + '.js')
36
+ end
37
+ end
38
+
39
+ def load_dom(dom, base_url = nil)
40
+ @dom = File.exists?(dom) ? File.read(dom) : dom
41
+ @webView.mainFrame.loadHTMLString(@dom, baseURL:base_url)
42
+ end
43
+
44
+ def to_s
45
+ @dom ? html_source : super
46
+ end
47
+
48
+ def html_source
49
+ eval("document.documentElement.outerHTML")
50
+ end
51
+
52
+ def run
53
+ unless NSApplication.sharedApplication.running?
54
+ NSApplication.sharedApplication.run
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,35 @@
1
+ module Ichabod
2
+ module ScriptObject
3
+ class Ichabod
4
+ attr_reader :runtime
5
+
6
+ def initialize(runtime)
7
+ @runtime = runtime
8
+ end
9
+
10
+ def open(url)
11
+ runtime.open(url)
12
+ end
13
+
14
+ def eval(js)
15
+ runtime.eval(js)
16
+ end
17
+
18
+ def exit(code = 0)
19
+ Kernel.exit(code)
20
+ end
21
+
22
+ def sleep(secs = 0)
23
+ Kernel.sleep(secs)
24
+ end
25
+
26
+ def args
27
+ ARGV
28
+ end
29
+
30
+ def invokeUndefinedMethodFromWebScript(name, withArguments:args)
31
+ send(name, *args)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,43 @@
1
+ module Ichabod
2
+ module ScriptObject
3
+ class Ruby
4
+ attr_reader :runtime
5
+
6
+ def initialize(runtime)
7
+ @runtime = runtime
8
+ end
9
+
10
+ ##
11
+ # Lets us call simple ruby methods
12
+ #
13
+ # Ruby.IO_read(file)
14
+ # Ruby.puts('hi')
15
+ # Ruby.require('uri')
16
+ def invokeUndefinedMethodFromWebScript(name, withArguments:args)
17
+ if respond_to? name
18
+ send(name, *args)
19
+ elsif Kernel.respond_to? name
20
+ Kernel.send(name, *args)
21
+ elsif name =~ /^([A-Z][A-Za-z]+)_(.+)/
22
+ const = Kernel.const_get($1)
23
+ method = $2
24
+
25
+ if const.respond_to? method
26
+ const.send(method, *args)
27
+ elsif const.respond_to?("#{method}?")
28
+ const.send("#{method}?", *args)
29
+ elsif const.respond_to?("#{method}!")
30
+ const.send("#{method}!", *args)
31
+ end
32
+ end
33
+ end
34
+
35
+ ##
36
+ # Ruby('$LOAD_PATH') => array...
37
+ # Ruby('1 + 1') => 2
38
+ def invokeDefaultMethodWithArguments(args)
39
+ eval(args[0])
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,20 @@
1
+ module Ichabod
2
+ module Tests
3
+
4
+ def qunit(url)
5
+ runtime = Ichabod::Runtime.new
6
+ runtime.navigate(url)
7
+ runtime.eval_file "./qunit"
8
+ runtime.run
9
+ end
10
+
11
+ def jasmine(url)
12
+ runtime = Ichabod::Runtime.new
13
+ runtime.navigate(url)
14
+ runtime.eval_file "./jasmine"
15
+ runtime.run
16
+ end
17
+
18
+ extend self
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ module Ichabod
2
+ VERSION = '0.0.1'
3
+
4
+ class Version
5
+ def self.to_s
6
+ VERSION
7
+ end
8
+ end
9
+ end
data/lib/ichabod.rb ADDED
@@ -0,0 +1,38 @@
1
+ #
2
+ # It's pretty much that easy.
3
+ #
4
+
5
+ if !defined?(RUBY_ENGINE) || RUBY_ENGINE != 'macruby'
6
+ abort "Ichabod requires MacRuby. http://www.macruby.org/"
7
+ end
8
+
9
+ framework 'WebKit'
10
+
11
+ require 'ichabod/runtime'
12
+ require 'ichabod/coercion'
13
+ require 'ichabod/script_object/ruby'
14
+ require 'ichabod/script_object/ichabod'
15
+ require 'ichabod/delegate/ui'
16
+ require 'ichabod/delegate/load'
17
+ require 'ichabod/tests'
18
+
19
+ module Ichabod
20
+ JS_PATH = File.join(File.dirname(__FILE__), "js")
21
+
22
+ def self.eval(js)
23
+ Runtime.new.eval(js)
24
+ end
25
+
26
+ def self.eval_file(file)
27
+ contents = File.read(File.expand_path(file))
28
+ eval(contents)
29
+ end
30
+
31
+ def self.parse(dom)
32
+ Runtime.new(:dom => dom).run
33
+ end
34
+
35
+ def self.open(url)
36
+ Runtime.new.open(url).run
37
+ end
38
+ end
data/lib/js/jasmine.js ADDED
@@ -0,0 +1,107 @@
1
+ // Jasmine Reporter mostly inspired/stolen from the jasmine-node project
2
+ // which is under the (under the MIT license)
3
+ // https://github.com/cpojer/jasmine-node
4
+
5
+ window.addEventListener("load", function(){
6
+ jasmine.printRunnerResults = function(runner){
7
+ var results = runner.results();
8
+ var suites = runner.suites();
9
+ var msg = '';
10
+ msg += suites.length + ' test' + ((suites.length === 1) ? '' : 's') + ', ';
11
+ msg += results.totalCount + ' assertion' + ((results.totalCount === 1) ? '' : 's') + ', ';
12
+ msg += results.failedCount + ' failure' + ((results.failedCount === 1) ? '' : 's') + '\n';
13
+ return msg;
14
+ };
15
+
16
+ var ansi = {
17
+ green: '\033[32m',
18
+ red: '\033[31m',
19
+ yellow: '\033[33m',
20
+ none: '\033[0m'
21
+ };
22
+
23
+ var noop = function(){};
24
+
25
+ var ConsoleReporter = function(){};
26
+ ConsoleReporter.fn = ConsoleReporter.prototype;
27
+ var C = ConsoleReporter;
28
+
29
+ C.fn.start = 0;
30
+ C.fn.columnCounter = 0;
31
+ C.fn.elapsed = 0;
32
+ C.fn.record = [];
33
+
34
+ C.fn.log = function(str){
35
+ Ruby.puts(str);
36
+ console.log(str)
37
+ };
38
+
39
+ C.fn.print = function(str){
40
+ Ruby.print(str);
41
+ };
42
+
43
+ C.fn.reportRunnerStarting = function(runner) {
44
+ this.log('Started');
45
+ this.start = Number(new Date);
46
+ };
47
+
48
+ C.fn.reportSuiteResults = function(suite) {
49
+ var specResults = suite.results();
50
+ var path = [];
51
+ while (suite) {
52
+ path.unshift(suite.description);
53
+ suite = suite.parentSuite;
54
+ }
55
+ var description = path.join(' ');
56
+ this.record.push('Spec ' + description);
57
+
58
+ specResults.items_.forEach(function(spec){
59
+ if (spec.failedCount > 0 && spec.description) {
60
+ this.record.push(' it ' + spec.description);
61
+ spec.items_.forEach(function(result){
62
+ this.record.push(' ' + result.trace.stack + '\n');
63
+ });
64
+ }
65
+ });
66
+ };
67
+
68
+ C.fn.reportSpecResults = function(spec) {
69
+ var result = spec.results();
70
+ var msg = '';
71
+ if (result.passed()) {
72
+ msg = ansi.green + '.' + ansi.none;
73
+ } else {
74
+ msg = ansi.red + 'F' + ansi.none;
75
+ }
76
+ this.print(msg);
77
+ if (this.columnCounter ++ < 50) return;
78
+ this.columnCounter = 0;
79
+ this.print('\n');
80
+ };
81
+
82
+ C.fn.reportRunnerResults = function(runner) {
83
+ this.elapsed = (Number(new Date) - this.start) / 1000;
84
+ this.log('\n');
85
+
86
+ var self = this;
87
+ this.record.forEach(function(log){
88
+ self.log(log);
89
+ });
90
+
91
+ this.log('Finished in ' + this.elapsed + ' seconds');
92
+
93
+ var summary = jasmine.printRunnerResults(runner);
94
+ if(runner.results().failedCount === 0 )
95
+ this.log(ansi.green + summary + ansi.none);
96
+ else
97
+ this.log(ansi.red + summary + ansi.none);
98
+
99
+ (this.done||noop)(runner, this.log);
100
+
101
+ Ichabod.exit();
102
+ };
103
+
104
+ C.fn.reportSpecStarting = function(){};
105
+
106
+ window.jasmine.getEnv().addReporter(new ConsoleReporter);
107
+ });