adhearsion 0.7.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.
Files changed (48) hide show
  1. data/LICENSE +339 -0
  2. data/Rakefile +108 -0
  3. data/ahn +195 -0
  4. data/lib/adhearsion.rb +402 -0
  5. data/lib/constants.rb +20 -0
  6. data/lib/core_extensions.rb +157 -0
  7. data/lib/database_functions.rb +76 -0
  8. data/lib/rami.rb +822 -0
  9. data/lib/servlet_container.rb +146 -0
  10. data/new_projects/Rakefile +100 -0
  11. data/new_projects/config/adhearsion.sqlite3 +0 -0
  12. data/new_projects/config/adhearsion.yml +11 -0
  13. data/new_projects/config/database.rb +50 -0
  14. data/new_projects/config/database.yml +10 -0
  15. data/new_projects/config/helpers/drb_server.yml +43 -0
  16. data/new_projects/config/helpers/factorial.alien.c.yml +1 -0
  17. data/new_projects/config/helpers/manager_proxy.yml +7 -0
  18. data/new_projects/config/helpers/micromenus.yml +1 -0
  19. data/new_projects/config/helpers/micromenus/collab.rb +55 -0
  20. data/new_projects/config/helpers/micromenus/images/tux.bmp +0 -0
  21. data/new_projects/config/helpers/micromenus/javascripts/builder.js +131 -0
  22. data/new_projects/config/helpers/micromenus/javascripts/controls.js +834 -0
  23. data/new_projects/config/helpers/micromenus/javascripts/dragdrop.js +944 -0
  24. data/new_projects/config/helpers/micromenus/javascripts/effects.js +956 -0
  25. data/new_projects/config/helpers/micromenus/javascripts/prototype.js +2319 -0
  26. data/new_projects/config/helpers/micromenus/javascripts/scriptaculous.js +51 -0
  27. data/new_projects/config/helpers/micromenus/javascripts/slider.js +278 -0
  28. data/new_projects/config/helpers/micromenus/javascripts/unittest.js +557 -0
  29. data/new_projects/config/helpers/micromenus/stylesheets/firefox.css +10 -0
  30. data/new_projects/config/helpers/micromenus/stylesheets/firefox.xul.css +44 -0
  31. data/new_projects/config/helpers/weather.yml +1 -0
  32. data/new_projects/config/helpers/xbmc.yml +1 -0
  33. data/new_projects/config/migration.rb +53 -0
  34. data/new_projects/extensions.rb +56 -0
  35. data/new_projects/helpers/drb_server.rb +32 -0
  36. data/new_projects/helpers/factorial.alien.c +32 -0
  37. data/new_projects/helpers/manager_proxy.rb +43 -0
  38. data/new_projects/helpers/micromenus.rb +374 -0
  39. data/new_projects/helpers/oscar_wilde_quotes.rb +197 -0
  40. data/new_projects/helpers/weather.rb +85 -0
  41. data/new_projects/helpers/xbmc.rb +12 -0
  42. data/new_projects/logs/database.log +0 -0
  43. data/test/core_extensions_test.rb +26 -0
  44. data/test/dial_test.rb +43 -0
  45. data/test/stress_tests/test.rb +13 -0
  46. data/test/stress_tests/test.yml +13 -0
  47. data/test/test_micromenus.rb +0 -0
  48. metadata +131 -0
@@ -0,0 +1,146 @@
1
+ # Adhearsion, open source technology integrator
2
+ # Copyright 2006 Jay Phillips
3
+ #
4
+ # This program is free software; you can redistribute it and/or
5
+ # modify it under the terms of the GNU General Public License
6
+ # as published by the Free Software Foundation; either version 2
7
+ # of the License, or (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
+
18
+ class ServletContainer
19
+
20
+ # TODO: Port Mongrel server here.
21
+ class NativeServer;end
22
+
23
+ require 'gserver'
24
+ class RubyServer < GServer
25
+ def initialize(port, *args)
26
+ @stdlog = $STDOUT
27
+ log "Starting server on port #{port}"
28
+ audit = true
29
+ super(port, *args)
30
+ end
31
+
32
+ def read_variables io
33
+ # When debugging, this method could actually not do any IO operations and just
34
+ # return a development Hash of call variables
35
+ call_variables = {}
36
+ while(line = io.gets.chomp)
37
+ break if line.empty? # Empty lines signify no more variables
38
+ variable = line.split(/:\s*/)
39
+ new_name, new_value = variable.first[4..-1].downcase, variable.last
40
+ call_variables["#{new_name}"] = new_value =~ /^\d+$/ ? Integer(new_value) : new_value
41
+ end
42
+ call_variables
43
+ end
44
+
45
+ # GServer allows all functionality to be packed into
46
+ def serve io
47
+ io.sync = true
48
+ Thread.current[:io] = io
49
+ begin
50
+ call_variables = read_variables io
51
+ Thread.current[:VARS] = call_variables
52
+ Thread.current[:container] = Contexts::Container.new
53
+
54
+ if call_variables.extension == 'h'
55
+ # We've received a notification that a channel's hung up!
56
+ Thread.list.each do |thread|
57
+ if thread[:VARS] && thread[:VARS]['uniqueid'] == call_variables['uniqueid']
58
+ thread[:hungup?] = true
59
+ # Could kill() the Thread here but it may need to finish. We're merciful people.
60
+ log "Received notification that channel #{call_variables['channel']} has been hung up."
61
+ return
62
+ end
63
+ end
64
+ elsif ['t', 'failed'].include? call_variables['extension']
65
+ # Timeout notifications. Not implemented yet...
66
+ io.close
67
+ return
68
+ end
69
+
70
+ log "Executing call with variables: " + call_variables.inspect
71
+
72
+ # TODO Will perform cache checking here.
73
+
74
+ call_variables.each do |k,v|
75
+ Thread.current[:container].run_inside do
76
+ meta_def k do v end
77
+ end
78
+ end
79
+
80
+ # Execute all before_call hooks
81
+ [$BEFORE_CALl_HIGH, $BEFORE_CALL, $BEFORE_CALL_LOW].flatten.compact.each &:call
82
+ +lambda { answer if CONFIG.answer_before_call }
83
+
84
+ # A call hook may decree that the call shouldn't be processed further (e.g. if
85
+ # it processed the call itself). This is done be rewriting the context variable.
86
+ unless Thread.current[:VARS]['context'] == :interrupted
87
+
88
+ begin
89
+ Contexts.new.instance_eval do
90
+ # Interpret the extensions.rb file!
91
+ eval File.read(File.join(Dir.pwd, "extensions.rb"))
92
+ end
93
+ rescue => detail
94
+ log "Exception raised in extensions.rb! " << detail.message
95
+ # TODO: Make error reports more intutive. Use notifications DSL?
96
+ detail.backtrace.each do |msg| log " "*8 << msg end
97
+ end
98
+
99
+ log "Parsing of extensions.rb complete"
100
+ begin
101
+ target_context = call_variables['context']
102
+ if target_context
103
+ Thread.current[:container].run_inside do
104
+ begin
105
+ +send(target_context.to_s.to_sym)
106
+ rescue => e
107
+ STDERR.puts e.inspect
108
+ +lambda {
109
+ play 'were-sorry'
110
+ hangup
111
+ }
112
+ end
113
+ end
114
+ else
115
+ io.close
116
+ log "ASTERISK REQUESTED ROUTING FOR A CONTEXT UNDEFINED IN extensions.rb!!! REQUEST IGNORED!!!"
117
+ return
118
+ end
119
+ rescue => detail
120
+ log "ERROR: #{detail.class.name} => #{detail.inspect}"
121
+ detail.backtrace.each do |msg| log " "*8 << msg end
122
+ end
123
+ log "Call routing complete"
124
+ end
125
+ rescue => detail
126
+ log "Call thread raised an exception! #{detail.message}"
127
+ detail.backtrace.each do |msg| log " "*8 << msg end
128
+ end
129
+
130
+ [$AFTER_CALl_HIGH, $AFTER_CALL, $AFTER_CALL_LOW].flatten.compact.each &:call
131
+ +lambda { hangup if CONFIG.hangup_after_call }
132
+ end
133
+ end
134
+
135
+ def initialize(port=4573, native=false)
136
+ @server = (native ? NativeServer : RubyServer).new port, '0.0.0.0'
137
+ @server.start
138
+ end
139
+
140
+
141
+ def shutdown
142
+ @server.shutdown
143
+ end
144
+
145
+ attr_reader :server
146
+ end
@@ -0,0 +1,100 @@
1
+ require 'rubygems'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+
6
+ ADHEARSION_VERSION = '0.7.0'
7
+ Summary = 'Adhearsion is a professional integration system for
8
+ integrating anything and everything.'
9
+
10
+ #task :default => [:test]
11
+
12
+ #desc "Run the Adhearsion unit tests"
13
+ #task :test do
14
+ # Dir['test/*.rb'].each do |f| require f end
15
+ #end
16
+
17
+ gem_spec = Gem::Specification.new do |s|
18
+
19
+ s.name = 'adhearsion'
20
+ s.rubyforge_project = 'adhearsion'
21
+ s.author = 'Jay Phillips'
22
+ s.email = 'admin -at- jicksta dot com'
23
+ s.version = ADHEARSION_VERSION
24
+ s.summary = Summary
25
+ s.homepage = 'http://adhearsion.com'
26
+
27
+ s.add_dependency 'activerecord', '>= 1.14.4'
28
+ s.add_dependency 'activesupport', '>= 1.3.1'
29
+ s.add_dependency 'rake', '>= 0.7.1'
30
+
31
+ s.platform = Gem::Platform::RUBY
32
+ s.require_path = 'lib'
33
+ s.executables = 'start_adhearsion' # ['start_adhearsion', 'ahn']
34
+ s.bindir = '.'
35
+ #s.extra_rdoc_files = ['LICENSE']
36
+
37
+ include = Dir['**/*']
38
+ exclude = Dir['{log,pkg}/**/*']
39
+
40
+ s.files = include - exclude
41
+
42
+ # test_files = Dir['tests/*.rb'] # Will be added when not so buggy
43
+ end
44
+
45
+ Rake::GemPackageTask.new gem_spec do |pkg|
46
+ pkg.need_zip = false
47
+ pkg.need_tar = false
48
+ end
49
+
50
+ #desc "Generate documentation for Adhearsion"
51
+ #Rake::RDocTask.new do |rdoc|
52
+ # rdoc.rdoc_dir 'doc'
53
+ #end
54
+
55
+ desc "Pulls down the entire wiki in HTML format"
56
+ task :wiki => [:rm_wiki] do
57
+ require 'open-uri'
58
+ File.open "wiki.zip",'a' do |f|
59
+ f.write open('http://adhearsion.stikipad.com/codex/export_html').read
60
+ end
61
+ Dir.mkdir 'docs' unless File.exists? 'docs'
62
+ `unzip -d docs/wiki wiki.zip`
63
+ File.delete 'wiki.zip'
64
+ puts `find docs/wiki`
65
+ end
66
+
67
+ desc "Removes all cached compiled RubyInline shared objects"
68
+ task :purge_objects do
69
+ `rm -rf ~/.ruby_inline/*`
70
+ end
71
+
72
+ desc "Removes the local copy of the wiki"
73
+ task :rm_wiki do
74
+ `rm -rf wiki.zip docs/wiki/`
75
+ end
76
+
77
+ desc "Prepares Adhearsion for a new release"
78
+ task :prepare_release do
79
+ # Remove log files
80
+ Dir['log/*.log'].each do |f|
81
+ puts "Removing file #{f}"
82
+ File.delete f
83
+ end
84
+
85
+ # Check for unversioned files
86
+ unversioned_files = `svn st | grep '^\?' | awk '{ print $2 }'`
87
+ puts "WARNING: These files are not under version control:\n#{unversioned_files}" unless unversioned_files.empty?
88
+ end
89
+
90
+ desc "Simply prints the Adhearsion version."
91
+ task :version do puts ADHEARSION_VERSION end
92
+
93
+ desc 'Create sample databases per the config/migration.rb and database.yml files.'
94
+ task :migrate do
95
+ require 'config/migration'
96
+ Module.constants.each do |c|
97
+ c = Module.const_get(c)
98
+ c.up if c.respond_to?(:superclass) && c.superclass == ActiveRecord::Migration
99
+ end
100
+ end
@@ -0,0 +1,11 @@
1
+ answer_before_call: true
2
+ hangup_after_call: true
3
+
4
+ info:
5
+ name: Codemecca LLC
6
+ main_number: 409/767-2813
7
+ street_address:
8
+ city:
9
+ state:
10
+ zip:
11
+ country: United States
@@ -0,0 +1,50 @@
1
+ # Place any database customizations here. There are several ways you may desire enabling
2
+ # database connectivity:
3
+ #
4
+ # * Daemons (MySQL/PostgreSQL/etc)
5
+ # |
6
+ # | If you're running sophisticated applications for your VoIP server
7
+ # | and desire performance or integration, you'll want a daemon database
8
+ # | management system. MySQL is recommended.
9
+ # '____________________________________________________________
10
+ # * File-based (Sqlite/Sqlite3)
11
+ # |
12
+ # | If you have little desire to integrate your VoIP application's
13
+ # | user and group data with other apps, sqlite/3 is a very
14
+ # | easy solution to get running.
15
+ # '____________________________________________________________
16
+ # * No database
17
+ # |
18
+ # | If you simply have no use for keeping any information about
19
+ # | users, groups, or anything else, you can leave this entire
20
+ # | file commented. You don't even need ActiveRecord installed.
21
+ # '____________________________________________________________
22
+ #
23
+ ### SETTING UP YOUR DATABASE WITH A SAMPLE SCHEMA
24
+ #
25
+ # If you would like run
26
+ #
27
+ # ActiveRecord resources:
28
+ # * http://slash7.com/cheats/activerecord_cheatsheet.pdf
29
+ #
30
+ #
31
+ # Uncomment the =begin/=end blocks to enable database access.
32
+ #=begin
33
+
34
+ require 'active_record'
35
+ ActiveRecord::Base.logger = Logger.new 'logs/database.log', 10, 1.megabyte
36
+ ActiveRecord::Base.establish_connection YAML.load_file('config/database.yml')
37
+
38
+ # When Adhearsion first encounters this Group class, it will automatically associate it
39
+ # to the "groups" database.
40
+ class Group < ActiveRecord::Base
41
+ has_many :user
42
+ end
43
+
44
+ class User < ActiveRecord::Base
45
+ validates_uniqueness_of :name
46
+ validates_presence_of :name
47
+ belongs_to :group
48
+ end
49
+
50
+ #=end
@@ -0,0 +1,10 @@
1
+ adapter: sqlite3 # can also be "sqlite" for sqlite v2
2
+ dbfile: config/adhearsion.sqlite3
3
+
4
+ #### If you want to use MySQL, use these settings instead:
5
+ # adapter: mysql
6
+ # host: localhost
7
+ # database: adhearsion
8
+ # username: root
9
+ # password:
10
+ # # socket: /var/run/mysqld/mysqld.sock # May not be required
@@ -0,0 +1,43 @@
1
+ enabled: false
2
+ host: 127.0.0.1
3
+ port: 9050
4
+
5
+ deny: all
6
+ allow:
7
+ - 127.0.0.1
8
+ - 192.168.1.*
9
+
10
+ # Set your access control permissions above.
11
+ # Values for deny and allow can be several
12
+ # things:
13
+ #
14
+ # * The 'all' keyword can be used to match
15
+ # everything.
16
+ # * A single IP can be given right after
17
+ # the colon.
18
+ # * A single IP with wildcards can be given
19
+ # * Or, combining all of these, a YAML list
20
+ # can be used to specify many policies.
21
+ # See the comments below for more examples.
22
+ #
23
+ # Also note, the "host" field above may also
24
+ # affect the ability for DRb clients to connect
25
+ # to the server. If you wish you receive
26
+ # connections from the 192.168.1.*, listen on
27
+ # the IP associated with this machine on that
28
+ # subnet.
29
+ #
30
+ #
31
+ # USING YAML LISTS
32
+ #
33
+ # YAML allows lists of items to be created by
34
+ # prepending a hyphen to each list element.
35
+ # These lists can be used to refine your
36
+ # access control list better. Example:
37
+ #
38
+ # deny: all
39
+ # allow:
40
+ # - 192.168.1.123
41
+ # - 192.168.1.99
42
+ # - 66.199.34.44
43
+
@@ -0,0 +1 @@
1
+ enabled: false
@@ -0,0 +1,7 @@
1
+ enabled: false
2
+ host: 192.168.1.2
3
+ username: jicksta
4
+ secret: jicksta
5
+ #port: 5038
6
+ #console: 0
7
+ #event_cache: 100
@@ -0,0 +1 @@
1
+ port: 1337
@@ -0,0 +1,55 @@
1
+ heading "Adhearsion Micromenus Home"
2
+
3
+ # A simple example of a helper used in a Micromenu.
4
+ item oscar_wilde_quote + ' - Oscar Wilde'
5
+
6
+ # Use dial plan logic here in the Micromenu!
7
+ call "Check your voicemail!" do
8
+ check_voicemail
9
+ end
10
+
11
+ # If you have Adhearsion's Asterisk Manager Interface configured
12
+ # properly, you can use the guess_sip_user feature.
13
+ item "My User!" do
14
+ guess = guess_sip_user
15
+ item guess ? guess : "Sorry, you're behind a NAT or on a PC."
16
+ end
17
+
18
+ item "Employee Collaboration" do
19
+ item "View SIP users" do
20
+ sip_users = PBX.sip_users
21
+ items sip_users.collect(&:ip)
22
+ end
23
+
24
+ item "Add someone to the conference" do
25
+ # Originate a call into the user
26
+ end
27
+ item "Have Tweedledum call Tweedledee" do
28
+ x = PBX.rami_client.originate 'Context' => 'internal', 'Exten' => '11', 'Priority' => '1', 'Channel' => 'SIP/tweedledum'
29
+ item x.inspect
30
+ end
31
+ end
32
+
33
+ item 'Adhearsion Server Statistics' do
34
+ item 'View Registered SIP Users' do
35
+ PBX.sip_users.each do |u|
36
+ item %(SIP user "#{u.username}" on IP #{u.ip})
37
+ end
38
+ end
39
+ item 'View System Uptime' do
40
+ item `uptime`
41
+ end
42
+ item 'Network' do
43
+ heading 'Network Interface Info'
44
+ `ifconfig eth1`.each_line do |line|
45
+ item line
46
+ end
47
+ end
48
+ end
49
+
50
+ item 'View Users' do
51
+ item 'Select a user to call below.'
52
+ User.find(:all).each { |user| call user.ivr_extension, user.name }
53
+ end
54
+
55
+ image 'tux'
@@ -0,0 +1,131 @@
1
+ // script.aculo.us builder.js v1.6.5, Wed Nov 08 14:17:49 CET 2006
2
+
3
+ // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
+ //
5
+ // script.aculo.us is freely distributable under the terms of an MIT-style license.
6
+ // For details, see the script.aculo.us web site: http://script.aculo.us/
7
+
8
+ var Builder = {
9
+ NODEMAP: {
10
+ AREA: 'map',
11
+ CAPTION: 'table',
12
+ COL: 'table',
13
+ COLGROUP: 'table',
14
+ LEGEND: 'fieldset',
15
+ OPTGROUP: 'select',
16
+ OPTION: 'select',
17
+ PARAM: 'object',
18
+ TBODY: 'table',
19
+ TD: 'table',
20
+ TFOOT: 'table',
21
+ TH: 'table',
22
+ THEAD: 'table',
23
+ TR: 'table'
24
+ },
25
+ // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
26
+ // due to a Firefox bug
27
+ node: function(elementName) {
28
+ elementName = elementName.toUpperCase();
29
+
30
+ // try innerHTML approach
31
+ var parentTag = this.NODEMAP[elementName] || 'div';
32
+ var parentElement = document.createElement(parentTag);
33
+ try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
34
+ parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
35
+ } catch(e) {}
36
+ var element = parentElement.firstChild || null;
37
+
38
+ // see if browser added wrapping tags
39
+ if(element && (element.tagName != elementName))
40
+ element = element.getElementsByTagName(elementName)[0];
41
+
42
+ // fallback to createElement approach
43
+ if(!element) element = document.createElement(elementName);
44
+
45
+ // abort if nothing could be created
46
+ if(!element) return;
47
+
48
+ // attributes (or text)
49
+ if(arguments[1])
50
+ if(this._isStringOrNumber(arguments[1]) ||
51
+ (arguments[1] instanceof Array)) {
52
+ this._children(element, arguments[1]);
53
+ } else {
54
+ var attrs = this._attributes(arguments[1]);
55
+ if(attrs.length) {
56
+ try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
57
+ parentElement.innerHTML = "<" +elementName + " " +
58
+ attrs + "></" + elementName + ">";
59
+ } catch(e) {}
60
+ element = parentElement.firstChild || null;
61
+ // workaround firefox 1.0.X bug
62
+ if(!element) {
63
+ element = document.createElement(elementName);
64
+ for(attr in arguments[1])
65
+ element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
66
+ }
67
+ if(element.tagName != elementName)
68
+ element = parentElement.getElementsByTagName(elementName)[0];
69
+ }
70
+ }
71
+
72
+ // text, or array of children
73
+ if(arguments[2])
74
+ this._children(element, arguments[2]);
75
+
76
+ return element;
77
+ },
78
+ _text: function(text) {
79
+ return document.createTextNode(text);
80
+ },
81
+
82
+ ATTR_MAP: {
83
+ 'className': 'class',
84
+ 'htmlFor': 'for'
85
+ },
86
+
87
+ _attributes: function(attributes) {
88
+ var attrs = [];
89
+ for(attribute in attributes)
90
+ attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
91
+ '="' + attributes[attribute].toString().escapeHTML() + '"');
92
+ return attrs.join(" ");
93
+ },
94
+ _children: function(element, children) {
95
+ if(typeof children=='object') { // array can hold nodes and text
96
+ children.flatten().each( function(e) {
97
+ if(typeof e=='object')
98
+ element.appendChild(e)
99
+ else
100
+ if(Builder._isStringOrNumber(e))
101
+ element.appendChild(Builder._text(e));
102
+ });
103
+ } else
104
+ if(Builder._isStringOrNumber(children))
105
+ element.appendChild(Builder._text(children));
106
+ },
107
+ _isStringOrNumber: function(param) {
108
+ return(typeof param=='string' || typeof param=='number');
109
+ },
110
+ build: function(html) {
111
+ var element = this.node('div');
112
+ $(element).update(html.strip());
113
+ return element.down();
114
+ },
115
+ dump: function(scope) {
116
+ if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope
117
+
118
+ var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
119
+ "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
120
+ "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
121
+ "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
122
+ "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
123
+ "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
124
+
125
+ tags.each( function(tag){
126
+ scope[tag] = function() {
127
+ return Builder.node.apply(Builder, [tag].concat($A(arguments)));
128
+ }
129
+ });
130
+ }
131
+ }