html_namespacing 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,35 @@
1
+ #ifndef HTML_NAMESPACING_H
2
+ #define HTML_NAMESPACING_H
3
+
4
+ #ifdef __cplusplus
5
+ extern "C" {
6
+ #endif
7
+
8
+ typedef struct _HtmlNamespacingAllocationStrategy {
9
+ void *(*malloc)(size_t size);
10
+ void (*free)(void *ptr);
11
+ void *(*realloc)(void *ptr, size_t size);
12
+ } HtmlNamespacingAllocationStrategy;
13
+
14
+ int
15
+ add_namespace_to_html_with_length(
16
+ const char *html,
17
+ size_t html_len,
18
+ const char *ns,
19
+ char **ret,
20
+ size_t *ret_len);
21
+
22
+ int
23
+ add_namespace_to_html_with_length_and_allocation_strategy(
24
+ const char *html,
25
+ size_t html_len,
26
+ const char *ns,
27
+ char **ret,
28
+ size_t *ret_len,
29
+ HtmlNamespacingAllocationStrategy allocation_strategy);
30
+
31
+ #ifdef __cplusplus
32
+ }
33
+ #endif
34
+
35
+ #endif /* HTML_NAMESPACING_H */
@@ -0,0 +1,97 @@
1
+ #include <errno.h>
2
+
3
+ #include "ruby.h"
4
+
5
+ #include "html_namespacing.h"
6
+
7
+ #include <stdio.h>
8
+
9
+ VALUE rb_mHtmlNamespacing = Qnil;
10
+
11
+ static const HtmlNamespacingAllocationStrategy ALLOCATION_STRATEGY = {
12
+ (void *(*)(size_t)) ruby_xmalloc,
13
+ ruby_xfree,
14
+ (void *(*)(void *, size_t)) ruby_xrealloc
15
+ };
16
+
17
+ /*
18
+ * call-seq:
19
+ * HtmlNamespacing.add_namespace_to_html(html, ns) => String
20
+ *
21
+ * Returns new HTML string based on +html+ with the HTML class +ns+ to root
22
+ * elements.
23
+ *
24
+ * If +html+ is nil, returns +nil+.
25
+ *
26
+ * If +ns+ is nil, returns +html+.
27
+ */
28
+
29
+ VALUE
30
+ html_namespacing_add_namespace_to_html(
31
+ VALUE obj,
32
+ VALUE html,
33
+ VALUE ns)
34
+ {
35
+ /*
36
+ * It's almost tempting to manually allocate the RString object to save
37
+ * ourselves the stupid extra copy. (add_namespace_to_html_with_length()
38
+ * implicitly copies the string, and here we are copying it again because
39
+ * Ruby can't convert from a char* to an RString? How lame....)
40
+ *
41
+ * But for now, let's just do the extra copy (implicit in rb_str_new2) and
42
+ * be done with it.
43
+ */
44
+
45
+ const char *html_ptr;
46
+ size_t html_len;
47
+ const char *ns_ptr;
48
+ char *ret_ptr;
49
+ size_t ret_len;
50
+ int rv;
51
+ VALUE ret;
52
+
53
+ if (TYPE(html) == T_NIL) {
54
+ return Qnil;
55
+ }
56
+ Check_Type(html, T_STRING);
57
+
58
+ if (TYPE(ns) == T_NIL) {
59
+ return html;
60
+ }
61
+
62
+ Check_Type(ns, T_STRING);
63
+
64
+ html_ptr = RSTRING_PTR(html);
65
+ html_len = RSTRING_LEN(html);
66
+
67
+ ns_ptr = RSTRING_PTR(ns);
68
+
69
+ rv = add_namespace_to_html_with_length_and_allocation_strategy(
70
+ html_ptr,
71
+ html_len,
72
+ ns_ptr,
73
+ &ret_ptr,
74
+ &ret_len,
75
+ ALLOCATION_STRATEGY);
76
+ if (rv == EINVAL) {
77
+ rb_raise(rb_eArgError, "Badly-formed HTML: %s", html_ptr);
78
+ }
79
+ if (rv != 0) {
80
+ rb_raise(rb_eRuntimeError, "Unknown error in add_namespace_to_html");
81
+ }
82
+
83
+ ret = rb_str_new(ret_ptr, ret_len);
84
+ ruby_xfree(ret_ptr);
85
+
86
+ return ret;
87
+ }
88
+
89
+ /*
90
+ * Holds functions related to HTML namespacing.
91
+ */
92
+ void
93
+ Init_html_namespacing_ext()
94
+ {
95
+ rb_mHtmlNamespacing = rb_define_module("HtmlNamespacing");
96
+ rb_define_module_function(rb_mHtmlNamespacing, "add_namespace_to_html", html_namespacing_add_namespace_to_html, 2);
97
+ }
@@ -0,0 +1,5 @@
1
+ require File.dirname(__FILE__) + '/../ext/html_namespacing/html_namespacing_ext'
2
+
3
+ module HtmlNamespacing
4
+ autoload(:Plugin, File.dirname(__FILE__) + '/html_namespacing/plugin')
5
+ end
@@ -0,0 +1,10 @@
1
+ module HtmlNamespacing
2
+ module Plugin
3
+ autoload(:Rails, File.dirname(__FILE__) + '/plugin/rails')
4
+ autoload(:Sass, File.dirname(__FILE__) + '/plugin/sass')
5
+
6
+ def self.default_relative_path_to_namespace(path)
7
+ path.gsub(/\//, '-')
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,44 @@
1
+ jQuery(function($) {
2
+ function NS_from_node(root_node) {
3
+ var SPLIT_REGEX = /[ \t\n]+/,
4
+ ret = {},
5
+ node_stack = [root_node],
6
+ cur_node,
7
+ class_name_list,
8
+ class_names,
9
+ class_name,
10
+ i,
11
+ j,
12
+ children,
13
+ subnode;
14
+
15
+ while (node_stack.length) {
16
+ cur_node = node_stack.pop();
17
+
18
+ // Populate entry in $.NS
19
+ class_name_list = cur_node.className;
20
+ if (class_name_list) {
21
+ class_names = class_name_list.split(SPLIT_REGEX);
22
+ for (i = 0; i < class_names.length; i++) {
23
+ class_name = class_names[i];
24
+ if (!ret[class_name]) {
25
+ ret[class_name] = [cur_node];
26
+ } else {
27
+ ret[class_name].push(cur_node);
28
+ }
29
+ }
30
+ }
31
+
32
+ // "recurse" to the children, so they are handled in document order
33
+ children = cur_node.childNodes;
34
+ for (var j = children.length - 1; j >= 0; j--) {
35
+ subnode = children[j];
36
+ node_stack.push(subnode);
37
+ }
38
+ }
39
+
40
+ return ret;
41
+ }
42
+
43
+ $.NS = NS_from_node(document.getElementsByTagName('body')[0]);
44
+ });
@@ -0,0 +1 @@
1
+ jQuery(function(b){function a(f){var c=/[ \t\n]+/,m={},o=[f],n,l,p,h,k,g,e,d;while(o.length){n=o.pop();l=n.className;if(l){p=l.split(c);for(k=0;k<p.length;k++){h=p[k];if(!m[h]){m[h]=[n]}else{m[h].push(n)}}}e=n.childNodes;for(var g=e.length-1;g>=0;g--){d=e[g];o.push(d)}}return m}b.NS=a(document.getElementsByTagName("body")[0])});
@@ -0,0 +1,134 @@
1
+ begin
2
+ require 'glob_fu'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'glob_fu'
6
+ end
7
+
8
+ module HtmlNamespacing
9
+ module Plugin
10
+ module Rails
11
+ def self.install(options = {})
12
+ @options = options
13
+ install_rails_2_3(options)
14
+ end
15
+
16
+ # Called by ActionView
17
+ def self.path_to_namespace(relative_path)
18
+ if func = @options[:path_to_namespace_callback]
19
+ func.call(relative_path)
20
+ else
21
+ HtmlNamespacing::Plugin.default_relative_path_to_namespace(relative_path)
22
+ end
23
+ end
24
+
25
+ def self.handle_exception(e, template, view)
26
+ if func = @options[:handle_exception_callback]
27
+ func.call(e, template, view)
28
+ else
29
+ raise(e)
30
+ end
31
+ end
32
+
33
+ def self.javascript_root
34
+ @options[:javascript_root] || RAILS_ROOT + '/app/javascripts/views'
35
+ end
36
+
37
+ def self.javascript_optional_suffix
38
+ @options[:javascript_optional_suffix]
39
+ end
40
+
41
+ def self.template_formats
42
+ @formats ||= Set.new(@options[:template_formats] || ['html'])
43
+ end
44
+
45
+ module Helpers
46
+ def html_namespacing_javascript_tag(framework, options = {})
47
+ files = html_namespacing_files(options[:format])
48
+ root = Pathname.new(::HtmlNamespacing::Plugin::Rails.javascript_root)
49
+
50
+ unless files.empty?
51
+ r = "<script type=\"text/javascript\"><!--//--><![CDATA[//><!--\n"
52
+ r << HtmlNamespaceJs[framework][:top]
53
+ r << "\n"
54
+ files.collect do |path|
55
+ relative_path = Pathname.new(path).relative_path_from(root)
56
+ r << html_namespacing_inline_js(framework, relative_path.to_s, path)
57
+ r << "\n"
58
+ end
59
+ r << "//--><!]]></script>"
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ HtmlNamespaceJs = {
66
+ :jquery => {
67
+ :top => File.open(File.join(File.dirname(__FILE__), 'dom_scan_jquery.js.compressed')) { |f| f.read },
68
+ :each => 'jQuery(function($){var NS=%s,$NS=function(){return $($.NS[NS]||[])};%s});'
69
+ }
70
+ }
71
+
72
+ def html_namespacing_files(format)
73
+ (Array === format ? format : [format]).inject([]) do |r, f|
74
+ r.concat(GlobFu.find(
75
+ html_namespacing_rendered_paths,
76
+ :suffix => 'js',
77
+ :extra_suffix => f && f.to_s,
78
+ :optional_suffix => ::HtmlNamespacing::Plugin::Rails.javascript_optional_suffix,
79
+ :root => ::HtmlNamespacing::Plugin::Rails.javascript_root
80
+ ))
81
+ end
82
+ end
83
+
84
+ def html_namespacing_inline_js(framework, relative_path, absolute_path)
85
+ namespace = ::HtmlNamespacing::Plugin::Rails.path_to_namespace(relative_path.sub(/\..*$/, ''))
86
+ content = File.open(absolute_path) { |f| f.read }
87
+
88
+ HtmlNamespaceJs[framework][:each] % [namespace.to_json, content]
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def self.install_rails_2_3(options = {})
95
+ if options[:javascript]
96
+ ::ActionView::Base.class_eval do
97
+ attr_writer(:html_namespacing_rendered_paths)
98
+ def html_namespacing_rendered_paths
99
+ @html_namespacing_rendered_paths ||= []
100
+ end
101
+ end
102
+ end
103
+
104
+ ::ActionView::Template.class_eval do
105
+ def render_with_html_namespacing(view, local_assigns = {})
106
+ html = render_without_html_namespacing(view, local_assigns)
107
+
108
+ view.html_namespacing_rendered_paths << path_without_format_and_extension if view.respond_to?(:html_namespacing_rendered_paths)
109
+
110
+ if HtmlNamespacing::Plugin::Rails.template_formats.include?(format)
111
+ add_namespace_to_html(html, view)
112
+ else
113
+ html
114
+ end
115
+ end
116
+ alias_method_chain :render, :html_namespacing
117
+
118
+ private
119
+
120
+ def add_namespace_to_html(html, view)
121
+ HtmlNamespacing::add_namespace_to_html(html, html_namespace)
122
+ rescue ArgumentError => e
123
+ HtmlNamespacing::Plugin::Rails.handle_exception(e, self, view)
124
+ html # unless handle_exception() raised something
125
+ end
126
+
127
+ def html_namespace
128
+ HtmlNamespacing::Plugin::Rails.path_to_namespace(path_without_format_and_extension)
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,76 @@
1
+ module HtmlNamespacing
2
+ module Plugin
3
+ module Sass
4
+ def self.install(options = {})
5
+ options[:prefix] ||= 'views'
6
+ options[:callback] ||= lambda { |p| HtmlNamespacing::Plugin.default_relative_path_to_namespace(p) }
7
+ Sass_2_2.install(options)
8
+ end
9
+
10
+ module Sass_2_2
11
+ def self.install(options = {})
12
+ self.allow_updating_partials
13
+ self.add_super_rule_to_tree_node(options)
14
+ self.try_to_enable_memoization
15
+ end
16
+
17
+ private
18
+
19
+ def self.allow_updating_partials
20
+ ::Sass::Plugin.module_eval do
21
+ private
22
+
23
+ # Though a bit over-zealous, who *cares* if we render partial
24
+ # Sass files?
25
+ def forbid_update?(*args)
26
+ false
27
+ end
28
+ end
29
+ end
30
+
31
+ def self.add_super_rule_to_tree_node(namespacing_options)
32
+ ::Sass::Tree::RuleNode.class_eval do
33
+ def to_s_with_namespacing(tabs, super_rules = nil)
34
+ super_rules ||= namespacing_rules
35
+ to_s_without_namespacing(tabs, super_rules)
36
+ end
37
+ alias_method_chain(:to_s, :namespacing)
38
+
39
+ private
40
+
41
+ define_method(:namespacing_prefix) do
42
+ namespacing_options[:prefix]
43
+ end
44
+ define_method(:namespacing_callback) do
45
+ namespacing_options[:callback]
46
+ end
47
+
48
+ def namespacing_regex
49
+ /^#{Regexp.quote(options[:css_location])}\/#{Regexp.quote(namespacing_prefix)}\/(.*)\.css$/
50
+ end
51
+
52
+ def namespace
53
+ @namespace ||= if options[:css_filename] =~ namespacing_regex
54
+ namespacing_callback.call($1.split('.')[0])
55
+ end
56
+ @namespace
57
+ end
58
+
59
+ def namespacing_rules
60
+ namespace && [ ".#{namespace}" ]
61
+ end
62
+ end
63
+ end
64
+
65
+ def self.try_to_enable_memoization
66
+ ::Sass::Tree::RuleNode.class_eval do
67
+ extend ActiveSupport::Memoizable
68
+ memoize :namespacing_rules
69
+ end
70
+ rescue NameError
71
+ # ActiveSupport isn't loaded
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/spec_helper'
4
+
5
+ describe(HtmlNamespacing) do
6
+ private
7
+
8
+ def self.define_test(name, html, ns, expect)
9
+ it("should work with #{name}") do
10
+ f(html, ns).should == expect
11
+ end
12
+ end
13
+
14
+ def self.define_failing_test(name, html)
15
+ it("should fail on #{name}") do
16
+ lambda { f(html, 'X') }.should raise_error(ArgumentError)
17
+ end
18
+ end
19
+
20
+ def f(html, ns)
21
+ HtmlNamespacing::add_namespace_to_html(html, ns)
22
+ end
23
+
24
+ self.define_test('nil HTML', nil, 'X', nil)
25
+ self.define_test('nil namespace', '<div>hello</div>', nil, '<div>hello</div>')
26
+ self.define_test('nil everything', nil, nil, nil)
27
+ self.define_test('plain text', 'hello', 'X', 'hello')
28
+ self.define_test('regular tag', '<div>hello</div>', 'X', '<div class="X">hello</div>')
29
+ self.define_test('empty tag', '<div/>', 'X', '<div class="X"/>')
30
+ self.define_test('empty tag with " />"', '<div />', 'X', '<div class="X" />')
31
+ self.define_test('empty tag with " />" and class', '<div class="A" />', 'X', '<div class="A X" />')
32
+ self.define_test('nested tag', '<div><div>hello</div></div>', 'X', '<div class="X"><div>hello</div></div>')
33
+ self.define_test('two tags', '<div>hello</div><div>goodbye</div>', 'X', '<div class="X">hello</div><div class="X">goodbye</div>')
34
+ self.define_test('existing class= tag with double-quotes', '<div class="foo">bar</div>', 'baz', '<div class="foo baz">bar</div>')
35
+ self.define_test('existing class= tag with single-quotes', "<div class='foo'>bar</div>", 'baz', "<div class='foo baz'>bar</div>")
36
+ self.define_test('other attributes are ignored', '<div id="id" class="foo" style="display:none;">bar</div>', 'baz', '<div id="id" class="foo baz" style="display:none;">bar</div>')
37
+ self.define_test('works with utf-8', '<div class="𝞪">𝟂</div>', '𝞺', '<div class="𝞪 𝞺">𝟂</div>')
38
+ self.define_test('empty tag with existing class=', '<span class="foo"/>', 'bar', '<span class="foo bar"/>')
39
+ self.define_test('works with newlines in tag', "<div\n\nclass\n\n=\n\n'foo'\n\n>bar</div>", 'baz', "<div\n\nclass\n\n=\n\n'foo baz'\n\n>bar</div>")
40
+ self.define_test('works with "\'" within \'"\' attributes', '<div title="Adam\'s House" class="foo">bar</div>', 'baz', '<div title="Adam\'s House" class="foo baz">bar</div>')
41
+ self.define_test('ignores XML prolog', '<?xml version="1.0"?><div>foo</div>', 'X', '<?xml version="1.0"?><div class="X">foo</div>')
42
+ self.define_test('ignores DOCTYPE', '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><div>foo</div>', 'X', '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><div class="X">foo</div>')
43
+ self.define_test('ignores CDATA', '<![CDATA[ignore <div class="foo">]] </div>]]><div>foo</div>', 'X', '<![CDATA[ignore <div class="foo">]] </div>]]><div class="X">foo</div>')
44
+ self.define_test('ignores comments', '<!-- blah <div class="foo">foo</div> - <span/>--><div>foo</div>', 'X', '<!-- blah <div class="foo">foo</div> - <span/>--><div class="X">foo</div>')
45
+ self.define_test('detects CDATA end delimiter correctly', '<![CDATA[ ]>< ]]>', 'X', '<![CDATA[ ]>< ]]>')
46
+ self.define_test('detects comment end delimiter correctly', '<!-- ->< -->', 'X', '<!-- ->< -->')
47
+
48
+ [ 'html', 'head', 'base', 'meta', 'title', 'link', 'script', 'noscript', 'style' ].each do |tag|
49
+ self.define_test("ignores <#{tag}> tags", "<#{tag}>foo</#{tag}>", "X", "<#{tag}>foo</#{tag}>")
50
+ end
51
+
52
+ self.define_failing_test('unclosed tag', '<div>foo')
53
+ self.define_failing_test('closing tag', 'foo</div>')
54
+ self.define_failing_test('wrong attr syntax', '<div foo=bar>foo</div>')
55
+ end