jabs 0.1.0

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.
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__