ichabod 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ });