html_namespacing 0.1.5

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,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