jabs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *~
2
+ .DS_Store
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "site"]
2
+ path = site
3
+ url = git@github.com:collin/jabs.git
data/README ADDED
@@ -0,0 +1,20 @@
1
+ Javascript Abstract Behavior Syntax? What's that?
2
+
3
+ I was working on a project *very* similar to http://github.com/nex3/jabl in the months running up to RubyFringe.
4
+
5
+ Then I saw Hampton's presenation. Oops.
6
+
7
+ Well I had all this work and I wanted to see what it'd take to get it producing usable javascript.
8
+
9
+ I pulled it out of http://github.com/collin/lucky7 and stuck it here.
10
+
11
+ And hacked around a bit, removing some features, adding others.
12
+
13
+ Check it out:
14
+
15
+ (add github to rubygems)
16
+ gem install collin-fold
17
+ git://github.com/collin/jabs.git
18
+ cd jabs
19
+
20
+ Look through /examples
data/Rakefile.rb ADDED
@@ -0,0 +1,66 @@
1
+ require 'rubygems'
2
+ require 'pathname'
3
+ require 'launchy'
4
+
5
+ __DIR__ = Pathname.new(__FILE__).dirname
6
+ $LOAD_PATH << __DIR__ unless $LOAD_PATH.include?(__DIR__)
7
+
8
+ task :default => "spec:all"
9
+
10
+ namespace :spec do
11
+ task :default => :all
12
+
13
+ task :prepare do
14
+ @specs= Pathname.glob(__DIR__ + "rspec" + "**" + "*.rb").join(' ')
15
+ p @specs
16
+ end
17
+
18
+ task :all => :prepare do
19
+ system "spec #{@specs}"
20
+ end
21
+
22
+ task :doc => :prepare do
23
+ system "spec #{@specs} --format specdoc"
24
+ end
25
+ end
26
+
27
+ task :example do#=> "spec:all" do
28
+ require 'lib/jabs'
29
+ examples = __DIR__ + "examples"
30
+ jabs = (examples + "input_with_default.js.jabs").read
31
+ jabs_en = Jabs::Engine.new(jabs)
32
+ js = jabs_en.render
33
+ target = examples + "input_with_default.js"
34
+ js_file = File.new(target, 'w')
35
+ js_file.write(js)
36
+ js_file.close
37
+
38
+ haml = (examples + "layout.html.haml").read
39
+ haml_en = Haml::Engine.new(haml)
40
+ html = haml_en.render :example => target
41
+ html_file = File.new("layout.html", 'w')
42
+ html_file.write(html)
43
+ html_file.close
44
+
45
+ browser = Launchy::Browser.new
46
+ browser.visit(target)
47
+ end
48
+
49
+ begin
50
+ require 'jeweler'
51
+ Jeweler::Tasks.new do |gemspec|
52
+ gemspec.name = "jabs"
53
+ gemspec.summary = "Javascript Abstract Behavior Syntax"
54
+ gemspec.description = "Inspiredby HAML, SASS and JABL by mr Hampton Catlin"
55
+ gemspec.email = "collintmiller@gmail.com"
56
+ gemspec.homepage = "http://github.com/collin/jabs"
57
+ gemspec.authors = ["Collin Miller"]
58
+
59
+ gemspec.add_dependency('fold', '0.5.0')
60
+ gemspec.add_dependency('johnson', '1.1.2')
61
+ gemspec.add_dependency('colored', '1.1')
62
+ end
63
+ Jeweler::GemcutterTasks.new
64
+ rescue LoadError
65
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
66
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/build ADDED
@@ -0,0 +1,29 @@
1
+ #! /usr/bin/ruby
2
+
3
+ $LOAD_PATH << "/home/collin/code/lucky7/lib"
4
+ Lucky7Root = ""
5
+ require 'rubygems'
6
+ require 'lucky7'
7
+ require 'lib/jabs'
8
+ require 'haml'
9
+
10
+ __DIR__ = Pathname.new(__FILE__).dirname.expand_path
11
+
12
+ class JabsExampleBuilder < Lucky7::Builder
13
+ SrcRegex = /src/
14
+ BuildDirectory = ""
15
+
16
+ builds Haml, :files => "/home/collin/code/jabs/examples/src/*.html.haml"
17
+ builds Jabs, :files => "/home/collin/code/jabs/examples/src/*.js.jabs"
18
+
19
+ def build files=modified_files
20
+ super
21
+ require 'pp'
22
+ pp modified_files
23
+ end
24
+
25
+ end
26
+
27
+ # JabsExampleBuilder.new.build_all
28
+ JabsExampleBuilder.new.build_continuously
29
+
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html>
3
+ <head>
4
+ <title>Generated By Jabs</title>
5
+ <meta content='text/html; charset=utf-8' http-equiv='content-type' />
6
+ <script src='../vendor/jquery/jquery-1.2.6.js'></script>
7
+ <script src='editor.js'></script>
8
+ <style>
9
+ span {
10
+ float: left;
11
+ white-space: pre; }
12
+
13
+ #cursor {
14
+ width: 1px;
15
+ height: 16px;
16
+ background: black; }
17
+ </style>
18
+ </head>
19
+ <body>
20
+ <span id='cursor'></span>
21
+ </body>
22
+ </html>
@@ -0,0 +1,58 @@
1
+ var Backspace = 8;
2
+ var Tab = 9;
3
+ var Enter = 13;
4
+ var Shift = 16;
5
+ var Ctrl = 17;
6
+ var Alt = 18;
7
+ var PauseBreak = 19;
8
+ var CapsLock = 20;
9
+ var Escape = 27;
10
+ var PageUp = 33;
11
+ var PageDown = 34;
12
+ var PageEnd = 35;
13
+ var PageHome = 36;
14
+ var Left = 37;
15
+ var Up = 38;
16
+ var Right = 39;
17
+ var Down = 40;
18
+ var Insert = 45;
19
+ var Delete = 46;
20
+ jQuery.fn.blink = function() {
21
+ this.toggle();
22
+ var that = this;
23
+ t = window.setTimeout(function() {
24
+ that.blink();
25
+ }, 1000);
26
+ return this;
27
+ };
28
+ jQuery(function() {
29
+ (function($this) {
30
+ $this.bind("keypress", function(e) {
31
+ e.preventDefault();
32
+ (function($this) {
33
+ $this.show();
34
+ function insert(val) {
35
+ $this.before(val);
36
+ }
37
+ if(e.charCode) {
38
+ insert("<span>" + String.fromCharCode(e.charCode) + "</span>");
39
+ }
40
+ if(e.keyCode === Backspace) {
41
+ $this.prev().remove();
42
+ }
43
+ if(e.keyCode === Left) {
44
+ $this.prev().before($this);
45
+ }
46
+ if(e.keyCode === Right) {
47
+ $this.next().after($this);
48
+ }
49
+ if(e.keyCode === Enter) {
50
+ insert("<br/>");
51
+ }
52
+ })(jQuery("#cursor"));
53
+ });
54
+ (function($this) {
55
+ $this.blink();
56
+ })(jQuery("#cursor"));
57
+ })(jQuery(window));
58
+ });
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2
+ <html>
3
+ <head>
4
+ <title>Generated By Jabs</title>
5
+ <meta content='text/html; charset=utf-8' http-equiv='content-type' />
6
+ <script src='../vendor/jquery/jquery-1.2.6.js'></script>
7
+ <script src='input_with_default.js'></script>
8
+ </head>
9
+ <body>
10
+ <input default_value='hello!' type='text' />
11
+ <input default_value='hello again!' type='text' />
12
+ </body>
13
+ </html>
@@ -0,0 +1,19 @@
1
+ jQuery(function() {
2
+ (function($this) {
3
+ (function($this) {
4
+ $this.bind("blur", function(e) {
5
+ var $this = jQuery(this);
6
+ if($this.attr("value") === "") {
7
+ $this.attr("value", $this.attr("default_value"));
8
+ }
9
+ });
10
+ $this.bind("focus", function(e) {
11
+ var $this = jQuery(this);
12
+ if($this.attr("value") === $this.attr("default_value")) {
13
+ $this.attr("value", "");
14
+ }
15
+ });
16
+ $this.blur();
17
+ })(jQuery("input[default_value]"));
18
+ })(jQuery(window));
19
+ });
@@ -0,0 +1,19 @@
1
+ !!!
2
+ %html
3
+ %head
4
+ %title Generated By Jabs
5
+ %meta{"http-equiv"=>"content-type", "content"=>"text/html; charset=utf-8"}
6
+ %script{:src=> "../vendor/jquery/jquery-1.2.6.js"}
7
+ %script{:src=> "editor.js"}
8
+ %style
9
+ :sass
10
+ span
11
+ :float left
12
+ :white-space pre
13
+
14
+ #cursor
15
+ :width 1px
16
+ :height 16px
17
+ :background black
18
+ %body
19
+ %span#cursor
@@ -0,0 +1,50 @@
1
+ var Backspace = 8;
2
+ var Tab = 9;
3
+ var Enter = 13;
4
+ var Shift = 16;
5
+ var Ctrl = 17;
6
+ var Alt = 18;
7
+ var PauseBreak = 19;
8
+ var CapsLock = 20;
9
+ var Escape = 27;
10
+ var PageUp = 33;
11
+ var PageDown = 34;
12
+ var PageEnd = 35;
13
+ var PageHome = 36;
14
+ var Left = 37;
15
+ var Up = 38;
16
+ var Right = 39;
17
+ var Down = 40;
18
+ var Insert = 45;
19
+ var Delete = 46;
20
+
21
+ jQuery.fn.blink = function() { this.toggle(); var that = this; t = window.setTimeout(function() {that.blink();}, 1000); return this};
22
+
23
+ :ready
24
+ :keypress
25
+ e.preventDefault()
26
+ $#cursor
27
+ .show()
28
+
29
+ fun insert val
30
+ .before(val)
31
+
32
+ if e.charCode
33
+ insert("<span>"+String.fromCharCode(e.charCode)+"</span>")
34
+
35
+ if e.keyCode === Backspace
36
+ .prev().remove()
37
+
38
+ if e.keyCode === Left
39
+ .prev().before($this)
40
+
41
+ if e.keyCode === Right
42
+ .next().after($this)
43
+
44
+ if e.keyCode === Enter
45
+ insert("<br/>")
46
+
47
+
48
+ $#cursor
49
+ .blink()
50
+
@@ -0,0 +1,10 @@
1
+ !!!
2
+ %html
3
+ %head
4
+ %title Generated By Jabs
5
+ %meta{"http-equiv"=>"content-type", "content"=>"text/html; charset=utf-8"}
6
+ %script{:src=> "../vendor/jquery/jquery-1.2.6.js"}
7
+ %script{:src=> "input_with_default.js"}
8
+ %body
9
+ %input{:type=>'text', :default_value => "hello!"}/
10
+ %input{:type=>'text', :default_value => "hello again!"}/
@@ -0,0 +1,10 @@
1
+ :ready
2
+ $input[default_value]
3
+ :blur
4
+ if @value === ''
5
+ @value = @default_value
6
+
7
+ :focus
8
+ if @value === @default_value
9
+ @value = ''
10
+ .blur()
data/lib/jabs.rb ADDED
@@ -0,0 +1,308 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'fold'
5
+ require 'johnson'
6
+
7
+ module Johnson
8
+ module Nodes
9
+ attr_accessor :value
10
+ class FallThrough < Node
11
+ def initialize(_row, _column, value)
12
+ @value = value
13
+ end
14
+ end
15
+ end
16
+
17
+ module Visitors
18
+ class EcmaVisitor
19
+ def visit_SourceElements(o)
20
+ newline = o.value.length > 0 ? "\n" : ' '
21
+ (@depth == 0 ? '' : "{#{newline}") +
22
+ indent {
23
+ o.value.map { |x|
24
+ code = x.accept(self)
25
+ semi = case x
26
+ when Nodes::FallThrough
27
+ ""
28
+ when Nodes::Function, Nodes::While, Nodes::If, Nodes::Try, Nodes::Switch, Nodes::Case, Nodes::Default, Nodes::For, Nodes::ForIn
29
+ code =~ /\}\Z/ ? '' : ';'
30
+ else
31
+ ';'
32
+ end
33
+ "#{indent}#{code}#{semi}"
34
+ }.join("\n")
35
+ } +
36
+ (@depth == 0 ? '' : "#{newline}}")
37
+ end
38
+
39
+ def visit_FallThrough(o)
40
+ o.value
41
+ end
42
+ end
43
+
44
+ class SexpVisitor
45
+ def visit_FallThrough(o)
46
+ [:fall_through, o.value]
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ module Jabs
53
+ include Johnson::Nodes
54
+
55
+ def self.logger
56
+ @logger ||= begin
57
+ #TODO configurable logging
58
+ logger = Logger.new STDOUT
59
+ logger.level = Logger::DEBUG
60
+ logger.progname = "jabs"
61
+ logger.info "Started Logging"
62
+ logger
63
+ end
64
+ end
65
+
66
+ class Precompiler < Fold::Precompiler
67
+ class << self
68
+ attr_accessor :spot_replacements
69
+ end
70
+
71
+ self.spot_replacements = []
72
+
73
+ def self.spot_replace key, &block
74
+ spot_replacements << block if block_given?
75
+ end
76
+
77
+ attr_reader :sexp, :current_sexp
78
+
79
+ def initialize
80
+ super
81
+
82
+ @ready = false
83
+ @current_sexp = []
84
+ @full_sexp = [:source_elements, @current_sexp]
85
+ end
86
+
87
+ folds :Line, // do
88
+ [:fall_through, (Precompiler.do_spot_replace(text, self) + children.map{|child| child.text}.join(""))]
89
+ end
90
+
91
+ folds :Selector, /^\$/ do
92
+ call(function(nil, ["$this"], [:source_elements, render_children]), jquery([:string, text]))
93
+ end
94
+
95
+ folds :SubSelector, /^&/ do
96
+ call(
97
+ function(nil, ["$this"], [:source_elements, render_children]),
98
+ call(access([:name, "$this"], [:name, "find"]), [:string, text])
99
+ )
100
+ end
101
+
102
+ folds :Event, /^:/ do
103
+ event_bind(text, [:name, "$this"], [:source_elements, [parse("var $this = jQuery(this)")] + render_children])
104
+ end
105
+
106
+ folds :Ready, /^:ready/ do
107
+ jquery(function(nil, [], [:source_elements, [call(function(nil, ["$this"], [:source_elements, render_children]), jquery([:name, "window"]))]]))
108
+ end
109
+
110
+ folds :Function, /^fun / do
111
+ parts = text.split(/ /)
112
+ name, arg_names = parts.shift, parts.join('').gsub(' ', '').split(',')
113
+ [:function, name, arg_names, [:source_elements, render_children]]
114
+ end
115
+
116
+ folds :Def, /^def / do
117
+ parts = text.split(/ /)
118
+ name, arg_names = parts.shift, parts.join('').gsub(' ', '').split(',')
119
+ [:assign_expr,
120
+ access(access([:name, "jQuery"], [:name, "fn"]), [:name, name]),
121
+ [:function, nil, arg_names, [:source_elements, [parse("var $this = this")] + render_children]]]
122
+ end
123
+
124
+ if_meta = %q{
125
+ index = parent.children.index(self)
126
+ _next = parent.children.slice index + 1
127
+
128
+ _else = if [Else, ElseIf].include? _next.class
129
+ _next.render
130
+ else
131
+ nil
132
+ end
133
+ [:if, parse(Precompiler.do_spot_replace(text)),[:source_elements, render_children], _else]
134
+ }
135
+
136
+ folds :If, /^if / do
137
+ eval if_meta
138
+ end
139
+
140
+ folds :Unless, /^unless / do
141
+ [:if, [:not, [:parenthesis, parse(Precompiler.do_spot_replace(text))]], [:source_elements, render_children], nil]
142
+ end
143
+
144
+ folds :Else, /^else/ do
145
+ [:source_elements, render_children]
146
+ end
147
+
148
+ folds :ElseIf, /^else if/ do
149
+ eval if_meta
150
+ end
151
+
152
+ folds :DotAccessor, /^\./ do
153
+ index = parent.children.index(self)
154
+ _next = parent.children.slice index + 1
155
+ _text = _next ? _next.text : ""
156
+ if children.any?
157
+ if (_text[/\}\)/])
158
+ [:fall_through, ("$this."+Precompiler.do_spot_replace(text, self) + children.map{|child| child.text}.join(""))]
159
+ else
160
+ call(
161
+ function(nil, ["$this"], [:source_elements, render_children]),
162
+ parse(Precompiler.do_spot_replace(".#{text}", self))
163
+ )
164
+ end
165
+ else
166
+ parse Precompiler.do_spot_replace(".#{text}", self)
167
+ end
168
+ end
169
+
170
+ spot_replace :DotAccessor do |expression, precompiler|
171
+ expression.gsub /(^\.([\w]+)|- \.(.+))(.*)/ do |match|
172
+ "$this#{Precompiler.compile_arguments expression, $1, match, $4}"
173
+ end
174
+ end
175
+
176
+ spot_replace :AccessUpAndCall do |expression, precompiler|
177
+ expression.gsub /\.\.(\..*)/ do |match|
178
+ "$this.prevObject#{Precompiler.do_spot_replace($1)}"
179
+ end
180
+ end
181
+
182
+ spot_replace :AccessUpUp do |expression, precompiler|
183
+ expression.
184
+ gsub(/ \.\.\/|^\.\.\//, "$this.prevObject/").
185
+ gsub(/\/\.\./, ".prevObject")
186
+ end
187
+
188
+ spot_replace :AccessUp do |expression, precompiler|
189
+ expression.gsub /\.\./ do
190
+ "$this.prevObject"
191
+ end
192
+ end
193
+
194
+ spot_replace :AttributeSetter do |expression, precompiler|
195
+ expression.gsub /@([\w]+)[ ]*=[ ]*(.*)/ do |match|
196
+ if $2[0] == ?=
197
+ match
198
+ else
199
+ "$this.attr('#{$1}', #{Precompiler.do_spot_replace $2, precompiler})"
200
+ end
201
+ end
202
+ end
203
+
204
+ spot_replace :AttributeAccessor do |expression, precompiler|
205
+ expression.gsub /@([\w]+)/ do
206
+ "$this.attr('#{$1}')"
207
+ end
208
+ end
209
+
210
+ spot_replace :DanglingThis do |expression, precompiler|
211
+ expression.gsub /prevObject\$this/ do
212
+ "prevObject"
213
+ end
214
+ end
215
+
216
+ def self.do_spot_replace expression, precompiler=nil
217
+ spot_replacements.each do |block|
218
+ expression = block.call(expression, precompiler)
219
+ end
220
+ expression
221
+ end
222
+
223
+ def self.compile_arguments expression, call, real, args
224
+ return real if expression[Regexp.new("^\\#{call}\\(")]
225
+ arguments = []
226
+ if args[/^\./]
227
+ "#{call}()#{do_spot_replace(args).gsub("$this", "")}"
228
+ else
229
+ args.split(/\s|,/).each do |arg|
230
+ arg.gsub!(/:(\w+)/) {%{"#{$1}"}}
231
+ next if arg[/\s/]
232
+ next if arg == ""
233
+ arguments << arg
234
+ end
235
+ "#{call}(#{arguments.join(', ')})"
236
+ end
237
+ end
238
+
239
+ def parse expression
240
+ self.class.do_spot_replace expression, self
241
+ Johnson::Parser.parse(expression).value.first
242
+ end
243
+
244
+ def source block=nil
245
+ source = [:source_elements]
246
+ source << [block] unless(block.nil? or block.empty?)
247
+ source
248
+ end
249
+
250
+ def event_bind event, binds_to, function_sexp=nil
251
+ call(access(binds_to, [:name, "live"]), [:string, event], function(nil, ["e"], function_sexp))
252
+ end
253
+
254
+ def call *args
255
+ [:function_call, args]
256
+ end
257
+
258
+ def onready function_sexp=nil
259
+ event_bind('ready', jquery([:name, "document"]), function_sexp)
260
+ end
261
+
262
+ def access left, right
263
+ [:dot_accessor, right, left]
264
+ end
265
+
266
+ def function name=nil, args=[], function_sexp=nil
267
+ [:function, name, args, function_sexp]
268
+ end
269
+
270
+ def jquery *jquery_arg
271
+ [:function_call, [
272
+ [:name, 'jQuery'],
273
+ *jquery_arg
274
+ ]]
275
+ end
276
+
277
+ def johnsonize(sexp)
278
+ return sexp if sexp.is_a?(Johnson::Nodes::Node)
279
+ return sexp if sexp.class.to_s == "String"
280
+ return [] if sexp === []
281
+ return nil if sexp === nil
282
+ unless sexp.first.class.to_s == "Array"
283
+ if sexp.first.class.to_s == "Symbol"
284
+ klass = eval("Johnson::Nodes::#{sexp.shift.to_s.camelcase}")
285
+ klass.new 0,0, *sexp.map{|n|johnsonize(n)}
286
+ elsif sexp.is_a? Array
287
+ sexp.map{|n|johnsonize(n)}
288
+ else
289
+ sexp
290
+ end
291
+ else
292
+ sexp.map{|n|johnsonize(n)}
293
+ end
294
+ end
295
+ end
296
+
297
+ class Engine < Fold::Engine
298
+
299
+ def render context=nil
300
+ @p = precompiler_class.new
301
+ root = Johnson::Nodes::SourceElements.new 0, 0
302
+ @p.fold(lines).children.each{|c|root << @p.johnsonize(c.render)}
303
+ root.to_ecma
304
+ end
305
+ end
306
+ end
307
+
308
+ puts Jabs::Engine.new($stdin.read).render if $0 == __FILE__