html_minifier 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ /.project
data/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "vendor/html-minifier"]
2
+ path = vendor/html-minifier
3
+ url = https://github.com/kangax/html-minifier.git
data/.travis.yml ADDED
@@ -0,0 +1,19 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - jruby
6
+ before_script: "git submodule update --init --recursive"
7
+ env:
8
+ - EXECJS_RUNTIME=RubyRacer
9
+ - EXECJS_RUNTIME=RubyRhino
10
+ matrix:
11
+ exclude:
12
+ - rvm: 1.8.7
13
+ env: EXECJS_RUNTIME=RubyRhino
14
+ - rvm: 1.9.2
15
+ env: EXECJS_RUNTIME=RubyRhino
16
+ - rvm: 1.9.3
17
+ env: EXECJS_RUNTIME=RubyRhino
18
+ - rvm: jruby
19
+ env: EXECJS_RUNTIME=RubyRacer
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in gemspec
4
+ gemspec
5
+
6
+ # Depend on defined ExecJS runtime
7
+ execjs_runtimes = {
8
+ "RubyRacer" => "therubyracer",
9
+ "RubyRhino" => "therubyrhino",
10
+ "Mustang" => "mustang"
11
+ }
12
+
13
+ if ENV["EXECJS_RUNTIME"] && execjs_runtimes[ENV["EXECJS_RUNTIME"]]
14
+ gem execjs_runtimes[ENV["EXECJS_RUNTIME"]], :group => :development
15
+ end
16
+
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # HtmlMinifier
2
+ [![Build Status](https://secure.travis-ci.org/stereobooster/html_minifier.png?branch=master)](http://travis-ci.org/stereobooster/html_minifier)
3
+
4
+ Ruby wrapper for [html-minifier](https://github.com/kangax/html-minifier/).
5
+
6
+ ## Installation
7
+
8
+ `html_minifier` is available as ruby gem.
9
+
10
+ $ gem install html_minifier
11
+
12
+ Ensure that your environment has a JavaScript interpreter supported by [ExecJS](https://github.com/sstephenson/execjs). Usually, installing therubyracer gem is the best alternative.
13
+
14
+ ## Usage
15
+
16
+ ```ruby
17
+ require 'html_minifier'
18
+
19
+ HtmlMinifier.minify(File.read("source.html"))
20
+ ```
21
+
22
+ Or you can use it with rake
23
+
24
+ ```ruby
25
+ require "html_minifier/task"
26
+ HtmlMinifier::Task.new :html do |t|
27
+ t.pattern = 'static/**/*.html'
28
+ end
29
+ ```
30
+
31
+ When initializing `HtmlMinifier`, you can pass options
32
+
33
+ ```ruby
34
+ HtmlMinifier::minifier.new( <options> ).minify(source)
35
+ # Or
36
+ HtmlMinifier.minify(source, <options>)
37
+ ```
38
+
39
+ ## TODO
40
+
41
+ - add more tests
42
+ - add color reporter. Maybe [colorize](https://github.com/fazibear/colorize)
43
+ - add cli
data/Rakefile ADDED
@@ -0,0 +1,61 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ [:build, :install, :release].each do |task_name|
4
+ Rake::Task[task_name].prerequisites << :spec
5
+ end
6
+
7
+ require 'submodule'
8
+ Submodule::Task.new do |t|
9
+ t.branch = "gh-pages"
10
+
11
+ t.test do
12
+ js = []
13
+ js << File.open(File.expand_path("../lib/js/console.js", __FILE__), "r:UTF-8").read
14
+ %w{htmlparser htmllint htmlminifier}.each do |i|
15
+ js << File.open("src/#{i}.js", "r:UTF-8").read
16
+ end
17
+ js << File.open("tests/qunit.js", "r:UTF-8").read
18
+ js << File.open(File.expand_path("../spec/qunit_helper.js", __FILE__), "r:UTF-8").read
19
+ %w{minify_test lint_test}.each do |i|
20
+ js << File.open("tests/#{i}.js", "r:UTF-8").read
21
+ end
22
+
23
+ require "execjs"
24
+ context = ExecJS.compile js.join("\n")
25
+ result = context.exec "return QUnit.result();"
26
+ if result["fail"] > 0 && result["assertions"].respond_to?(:each)
27
+ puts "Failures:"
28
+ i = 1
29
+ result["assertions"].each do |test, details|
30
+ puts " #{i}) #{test}"
31
+ puts " Failure/Error: #{details[0]}"
32
+ puts " expected: #{details[1].inspect}"
33
+ puts " got: #{details[2].inspect}"
34
+ i+=1
35
+ end
36
+ end
37
+ # (#{result['pass_asserions']}) (#{result['fail_asserions']})
38
+ puts "Pass: #{result['pass']}, Fail: #{result['fail']}"
39
+ if result["fail"] > 0
40
+ abort
41
+ end
42
+ end
43
+
44
+ t.after_pull do
45
+ %w{htmlparser htmllint htmlminifier}.each do |i|
46
+ cp "vendor/html-minifier/src/#{i}.js", "lib/js/#{i}.js"
47
+ sh "git add lib/js/#{i}.js"
48
+ end
49
+ end
50
+ end
51
+
52
+ require "rspec/core/rake_task"
53
+ RSpec::Core::RakeTask.new
54
+
55
+ task :default => [:spec, "submodule:test"]
56
+
57
+ #desc "Generate code coverage"
58
+ # RSpec::Core::RakeTask.new(:coverage) do |t|
59
+ # t.rcov = true
60
+ # t.rcov_opts = ["--exclude", "spec"]
61
+ # end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "html_minifier/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "html_minifier"
7
+ s.version = HtmlMinifier::VERSION
8
+ s.authors = ["slavic"]
9
+ s.email = ["stereobooster@gmail.com"]
10
+ s.homepage = "https://github.com/stereobooster/html_minifier"
11
+ s.summary = %q{Ruby wrapper for kangax html-minifier}
12
+ s.description = %q{Ruby wrapper for kangax html-minifier}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_development_dependency "rspec"
20
+ s.add_development_dependency "submodule", ">= 0.1.0"
21
+ s.add_runtime_dependency "rake"
22
+
23
+ s.add_dependency "multi_json", ">= 1.3"
24
+ s.add_dependency "execjs"
25
+ end
File without changes
@@ -0,0 +1,52 @@
1
+ # encoding: UTF-8
2
+
3
+ require "execjs"
4
+ require "multi_json"
5
+
6
+ module HtmlMinifier
7
+
8
+ class Minifier
9
+ Error = ExecJS::Error
10
+
11
+ SourceBasePath = File.expand_path("../../js/", __FILE__)
12
+
13
+ def initialize(options = nil)
14
+ if options.instance_of? Hash then
15
+ @options = options.dup
16
+ @log = @options.delete :log
17
+ elsif options.nil?
18
+ @options = nil
19
+ else
20
+ raise 'Unsupported option for HtmlMinifier: ' + options.to_s
21
+ end
22
+
23
+ js = %w{console htmlparser htmllint htmlminifier}.map do |i|
24
+ File.open("#{SourceBasePath}/#{i}.js", "r:UTF-8").read
25
+ end.join("\n")
26
+ @context = ExecJS.compile(js)
27
+ end
28
+
29
+ def minify(source)
30
+ source = source.respond_to?(:read) ? source.read : source.to_s
31
+ js = []
32
+ if @options.nil? then
33
+ js << "var min = minify(#{MultiJson.dump(source)});"
34
+ else
35
+ js << "var min = minify(#{MultiJson.dump(source)}, #{MultiJson.dump(@options)});"
36
+ end
37
+ js << "return {min:min, logs:console.clear()};"
38
+ result = @context.exec js.join("\n")
39
+ if @log.respond_to?(:info)
40
+ result["logs"].each do |i|
41
+ @log.info i
42
+ end
43
+ end
44
+ result["min"]
45
+ end
46
+ end
47
+
48
+ def self.minify(source, options = nil)
49
+ Minifier.new(options).minify(source)
50
+ end
51
+
52
+ end
File without changes
@@ -0,0 +1,4 @@
1
+ module HtmlMinifier
2
+ VERSION = "0.0.2"
3
+ SUBMODULE = "7032d3d3d97aabf0a2c96c8155ed077b455aca31"
4
+ end
@@ -0,0 +1,6 @@
1
+ require "html_minifier/version"
2
+ require "html_minifier/minifier"
3
+
4
+ module HtmlMinifier
5
+ # Your code goes here...
6
+ end
data/lib/js/console.js ADDED
@@ -0,0 +1,16 @@
1
+ (function (global) {
2
+ var logs = [];
3
+ global.console = {
4
+ log: function (message) {
5
+ logs.push(message);
6
+ },
7
+ get: function () {
8
+ return logs;
9
+ },
10
+ clear: function () {
11
+ var ret = logs;
12
+ logs = [];
13
+ return ret;
14
+ }
15
+ };
16
+ }(this));
data/lib/js/exports.js ADDED
@@ -0,0 +1,16 @@
1
+ var exports = (function () {
2
+ var logs = [];
3
+ return {
4
+ console: {
5
+ log: function (message) {
6
+ logs.push(message);
7
+ },
8
+ get: function () {
9
+ return logs;
10
+ },
11
+ clear: function () {
12
+ logs = [];
13
+ }
14
+ }
15
+ };
16
+ })();
@@ -0,0 +1,150 @@
1
+ /*!
2
+ * HTMLLint (to be used in conjunction with HTMLMinifier)
3
+ *
4
+ * Copyright (c) 2010 Juriy "kangax" Zaytsev
5
+ * Licensed under the MIT license.
6
+ *
7
+ */
8
+
9
+ (function(global){
10
+
11
+ function isPresentationalElement(tag) {
12
+ return (/^(?:b|i|big|small|hr|blink|marquee)$/).test(tag);
13
+ }
14
+ function isDeprecatedElement(tag) {
15
+ return (/^(?:applet|basefont|center|dir|font|isindex|menu|s|strike|u)$/).test(tag);
16
+ }
17
+ function isEventAttribute(attrName) {
18
+ return (/^on[a-z]+/).test(attrName);
19
+ }
20
+ function isStyleAttribute(attrName) {
21
+ return ('style' === attrName.toLowerCase());
22
+ }
23
+ function isDeprecatedAttribute(tag, attrName) {
24
+ return (
25
+ (attrName === 'align' &&
26
+ (/^(?:caption|applet|iframe|img|imput|object|legend|table|hr|div|h[1-6]|p)$/).test(tag)) ||
27
+ (attrName === 'alink' && tag === 'body') ||
28
+ (attrName === 'alt' && tag === 'applet') ||
29
+ (attrName === 'archive' && tag === 'applet') ||
30
+ (attrName === 'background' && tag === 'body') ||
31
+ (attrName === 'bgcolor' && (/^(?:table|t[rdh]|body)$/).test(tag)) ||
32
+ (attrName === 'border' && (/^(?:img|object)$/).test(tag)) ||
33
+ (attrName === 'clear' && tag === 'br') ||
34
+ (attrName === 'code' && tag === 'applet') ||
35
+ (attrName === 'codebase' && tag === 'applet') ||
36
+ (attrName === 'color' && (/^(?:base(?:font)?)$/).test(tag)) ||
37
+ (attrName === 'compact' && (/^(?:dir|[dou]l|menu)$/).test(tag)) ||
38
+ (attrName === 'face' && (/^base(?:font)?$/).test(tag)) ||
39
+ (attrName === 'height' && (/^(?:t[dh]|applet)$/).test(tag)) ||
40
+ (attrName === 'hspace' && (/^(?:applet|img|object)$/).test(tag)) ||
41
+ (attrName === 'language' && tag === 'script') ||
42
+ (attrName === 'link' && tag === 'body') ||
43
+ (attrName === 'name' && tag === 'applet') ||
44
+ (attrName === 'noshade' && tag === 'hr') ||
45
+ (attrName === 'nowrap' && (/^t[dh]$/).test(tag)) ||
46
+ (attrName === 'object' && tag === 'applet') ||
47
+ (attrName === 'prompt' && tag === 'isindex') ||
48
+ (attrName === 'size' && (/^(?:hr|font|basefont)$/).test(tag)) ||
49
+ (attrName === 'start' && tag === 'ol') ||
50
+ (attrName === 'text' && tag === 'body') ||
51
+ (attrName === 'type' && (/^(?:li|ol|ul)$/).test(tag)) ||
52
+ (attrName === 'value' && tag === 'li') ||
53
+ (attrName === 'version' && tag === 'html') ||
54
+ (attrName === 'vlink' && tag === 'body') ||
55
+ (attrName === 'vspace' && (/^(?:applet|img|object)$/).test(tag)) ||
56
+ (attrName === 'width' && (/^(?:hr|td|th|applet|pre)$/).test(tag))
57
+ );
58
+ }
59
+ function isInaccessibleAttribute(attrName, attrValue) {
60
+ return (
61
+ attrName === 'href' &&
62
+ (/^\s*javascript\s*:\s*void\s*(\s+0|\(\s*0\s*\))\s*$/i).test(attrValue)
63
+ );
64
+ }
65
+
66
+ function Lint() {
67
+ this.log = [ ];
68
+ this._lastElement = null;
69
+ this._isElementRepeated = false;
70
+ }
71
+
72
+ Lint.prototype.testElement = function(tag) {
73
+ if (isDeprecatedElement(tag)) {
74
+ this.log.push(
75
+ '<li>Found <span class="deprecated-element">deprecated</span> <strong><code>&lt;' +
76
+ tag + '&gt;</code></strong> element</li>');
77
+ }
78
+ else if (isPresentationalElement(tag)) {
79
+ this.log.push(
80
+ '<li>Found <span class="presentational-element">presentational</span> <strong><code>&lt;' +
81
+ tag + '&gt;</code></strong> element</li>');
82
+ }
83
+ else {
84
+ this.checkRepeatingElement(tag);
85
+ }
86
+ };
87
+
88
+ Lint.prototype.checkRepeatingElement = function(tag) {
89
+ if (tag === 'br' && this._lastElement === 'br') {
90
+ this._isElementRepeated = true;
91
+ }
92
+ else if (this._isElementRepeated) {
93
+ this._reportRepeatingElement();
94
+ this._isElementRepeated = false;
95
+ }
96
+ this._lastElement = tag;
97
+ };
98
+
99
+ Lint.prototype._reportRepeatingElement = function() {
100
+ this.log.push('<li>Found <code>&lt;br></code> sequence. Try replacing it with styling.</li>');
101
+ };
102
+
103
+ Lint.prototype.testAttribute = function(tag, attrName, attrValue) {
104
+ if (isEventAttribute(attrName)) {
105
+ this.log.push(
106
+ '<li>Found <span class="event-attribute">event attribute</span> (<strong>',
107
+ attrName, '</strong>) on <strong><code>&lt;' + tag + '&gt;</code></strong> element</li>');
108
+ }
109
+ else if (isDeprecatedAttribute(tag, attrName)) {
110
+ this.log.push(
111
+ '<li>Found <span class="deprecated-attribute">deprecated</span> <strong>' +
112
+ attrName + '</strong> attribute on <strong><code>&lt;', tag, '&gt;</code></strong> element</li>');
113
+ }
114
+ else if (isStyleAttribute(attrName)) {
115
+ this.log.push(
116
+ '<li>Found <span class="style-attribute">style attribute</span> on <strong><code>&lt;', tag, '&gt;</code></strong> element</li>');
117
+ }
118
+ else if (isInaccessibleAttribute(attrName, attrValue)) {
119
+ this.log.push(
120
+ '<li>Found <span class="inaccessible-attribute">inaccessible attribute</span> '+
121
+ '(on <strong><code>&lt;', tag, '&gt;</code></strong> element)</li>');
122
+ }
123
+ };
124
+
125
+ Lint.prototype.testChars = function(chars) {
126
+ this._lastElement = '';
127
+ if (/(&nbsp;\s*){2,}/.test(chars)) {
128
+ this.log.push('<li>Found repeating <strong><code>&amp;nbsp;</code></strong> sequence. Try replacing it with styling.</li>');
129
+ }
130
+ };
131
+
132
+ Lint.prototype.test = function(tag, attrName, attrValue) {
133
+ this.testElement(tag);
134
+ this.testAttribute(tag, attrName, attrValue);
135
+ };
136
+
137
+ Lint.prototype.populate = function(writeToElement) {
138
+ if (this._isElementRepeated) {
139
+ this._reportRepeatingElement();
140
+ }
141
+ var report;
142
+ if (this.log.length && writeToElement) {
143
+ report = '<ol>' + this.log.join('') + '</ol>';
144
+ writeToElement.innerHTML = report;
145
+ }
146
+ };
147
+
148
+ global.HTMLLint = Lint;
149
+
150
+ })(typeof exports === 'undefined' ? this : exports);
@@ -0,0 +1,406 @@
1
+ /*!
2
+ * HTMLMinifier v0.4.4
3
+ * http://kangax.github.com/html-minifier/
4
+ *
5
+ * Copyright (c) 2010 Juriy "kangax" Zaytsev
6
+ * Licensed under the MIT license.
7
+ *
8
+ */
9
+
10
+ (function(global){
11
+
12
+ var log, HTMLParser;
13
+ if (global.console && global.console.log) {
14
+ log = function(message) {
15
+ // "preserving" `this`
16
+ global.console.log(message);
17
+ };
18
+ }
19
+ else {
20
+ log = function(){ };
21
+ }
22
+
23
+ if (global.HTMLParser) {
24
+ HTMLParser = global.HTMLParser;
25
+ }
26
+ else if (typeof require === 'function') {
27
+ HTMLParser = require('./htmlparser').HTMLParser;
28
+ }
29
+
30
+ function trimWhitespace(str) {
31
+ return str.replace(/^\s+/, '').replace(/\s+$/, '');
32
+ }
33
+ if (String.prototype.trim) {
34
+ trimWhitespace = function(str) {
35
+ return str.trim();
36
+ };
37
+ }
38
+
39
+ function collapseWhitespace(str) {
40
+ return str.replace(/\s+/g, ' ');
41
+ }
42
+
43
+ function isConditionalComment(text) {
44
+ return (/\[if[^\]]+\]/).test(text);
45
+ }
46
+
47
+ function isEventAttribute(attrName) {
48
+ return (/^on[a-z]+/).test(attrName);
49
+ }
50
+
51
+ function canRemoveAttributeQuotes(value) {
52
+ // http://www.w3.org/TR/html4/intro/sgmltut.html#attributes
53
+ // avoid \w, which could match unicode in some implementations
54
+ return (/^[a-zA-Z0-9-._:]+$/).test(value);
55
+ }
56
+
57
+ function attributesInclude(attributes, attribute) {
58
+ for (var i = attributes.length; i--; ) {
59
+ if (attributes[i].name.toLowerCase() === attribute) {
60
+ return true;
61
+ }
62
+ }
63
+ return false;
64
+ }
65
+
66
+ function isAttributeRedundant(tag, attrName, attrValue, attrs) {
67
+ attrValue = trimWhitespace(attrValue.toLowerCase());
68
+ return (
69
+ (tag === 'script' &&
70
+ attrName === 'language' &&
71
+ attrValue === 'javascript') ||
72
+
73
+ (tag === 'form' &&
74
+ attrName === 'method' &&
75
+ attrValue === 'get') ||
76
+
77
+ (tag === 'input' &&
78
+ attrName === 'type' &&
79
+ attrValue === 'text') ||
80
+
81
+ (tag === 'script' &&
82
+ attrName === 'charset' &&
83
+ !attributesInclude(attrs, 'src')) ||
84
+
85
+ (tag === 'a' &&
86
+ attrName === 'name' &&
87
+ attributesInclude(attrs, 'id')) ||
88
+
89
+ (tag === 'area' &&
90
+ attrName === 'shape' &&
91
+ attrValue === 'rect')
92
+ );
93
+ }
94
+
95
+ function isScriptTypeAttribute(tag, attrName, attrValue) {
96
+ return (
97
+ tag === 'script' &&
98
+ attrName === 'type' &&
99
+ trimWhitespace(attrValue.toLowerCase()) === 'text/javascript'
100
+ );
101
+ }
102
+
103
+ function isStyleLinkTypeAttribute(tag, attrName, attrValue) {
104
+ return (
105
+ (tag === 'style' || tag === 'link') &&
106
+ attrName === 'type' &&
107
+ trimWhitespace(attrValue.toLowerCase()) === 'text/css'
108
+ );
109
+ }
110
+
111
+ function isBooleanAttribute(attrName) {
112
+ return (/^(?:checked|disabled|selected|readonly)$/).test(attrName);
113
+ }
114
+
115
+ function isUriTypeAttribute(attrName, tag) {
116
+ return (
117
+ ((/^(?:a|area|link|base)$/).test(tag) && attrName === 'href') ||
118
+ (tag === 'img' && (/^(?:src|longdesc|usemap)$/).test(attrName)) ||
119
+ (tag === 'object' && (/^(?:classid|codebase|data|usemap)$/).test(attrName)) ||
120
+ (tag === 'q' && attrName === 'cite') ||
121
+ (tag === 'blockquote' && attrName === 'cite') ||
122
+ ((tag === 'ins' || tag === 'del') && attrName === 'cite') ||
123
+ (tag === 'form' && attrName === 'action') ||
124
+ (tag === 'input' && (attrName === 'src' || attrName === 'usemap')) ||
125
+ (tag === 'head' && attrName === 'profile') ||
126
+ (tag === 'script' && (attrName === 'src' || attrName === 'for'))
127
+ );
128
+ }
129
+
130
+ function isNumberTypeAttribute(attrName, tag) {
131
+ return (
132
+ ((/^(?:a|area|object|button)$/).test(tag) && attrName === 'tabindex') ||
133
+ (tag === 'input' && (attrName === 'maxlength' || attrName === 'tabindex')) ||
134
+ (tag === 'select' && (attrName === 'size' || attrName === 'tabindex')) ||
135
+ (tag === 'textarea' && (/^(?:rows|cols|tabindex)$/).test(attrName)) ||
136
+ (tag === 'colgroup' && attrName === 'span') ||
137
+ (tag === 'col' && attrName === 'span') ||
138
+ ((tag === 'th' || tag == 'td') && (attrName === 'rowspan' || attrName === 'colspan'))
139
+ );
140
+ }
141
+
142
+ function cleanAttributeValue(tag, attrName, attrValue) {
143
+ if (isEventAttribute(attrName)) {
144
+ return trimWhitespace(attrValue).replace(/^javascript:\s*/i, '').replace(/\s*;$/, '');
145
+ }
146
+ else if (attrName === 'class') {
147
+ return collapseWhitespace(trimWhitespace(attrValue));
148
+ }
149
+ else if (isUriTypeAttribute(attrName, tag) || isNumberTypeAttribute(attrName, tag)) {
150
+ return trimWhitespace(attrValue);
151
+ }
152
+ else if (attrName === 'style') {
153
+ return trimWhitespace(attrValue).replace(/\s*;\s*$/, '');
154
+ }
155
+ return attrValue;
156
+ }
157
+
158
+ function cleanConditionalComment(comment) {
159
+ return comment
160
+ .replace(/^(\[[^\]]+\]>)\s*/, '$1')
161
+ .replace(/\s*(<!\[endif\])$/, '$1');
162
+ }
163
+
164
+ function removeCDATASections(text) {
165
+ return text
166
+ // "/* <![CDATA[ */" or "// <![CDATA["
167
+ .replace(/^(?:\s*\/\*\s*<!\[CDATA\[\s*\*\/|\s*\/\/\s*<!\[CDATA\[.*)/, '')
168
+ // "/* ]]> */" or "// ]]>"
169
+ .replace(/(?:\/\*\s*\]\]>\s*\*\/|\/\/\s*\]\]>)\s*$/, '');
170
+ }
171
+
172
+ var reStartDelimiter = {
173
+ // account for js + html comments (e.g.: //<!--)
174
+ 'script': /^\s*(?:\/\/)?\s*<!--.*\n?/,
175
+ 'style': /^\s*<!--\s*/
176
+ };
177
+ var reEndDelimiter = {
178
+ 'script': /\s*(?:\/\/)?\s*-->\s*$/,
179
+ 'style': /\s*-->\s*$/
180
+ };
181
+ function removeComments(text, tag) {
182
+ return text.replace(reStartDelimiter[tag], '').replace(reEndDelimiter[tag], '');
183
+ }
184
+
185
+ function isOptionalTag(tag) {
186
+ return (/^(?:html|t?body|t?head|tfoot|tr|option)$/).test(tag);
187
+ }
188
+
189
+ var reEmptyAttribute = new RegExp(
190
+ '^(?:class|id|style|title|lang|dir|on(?:focus|blur|change|click|dblclick|mouse(' +
191
+ '?:down|up|over|move|out)|key(?:press|down|up)))$');
192
+
193
+ function canDeleteEmptyAttribute(tag, attrName, attrValue) {
194
+ var isValueEmpty = /^(["'])?\s*\1$/.test(attrValue);
195
+ if (isValueEmpty) {
196
+ return (
197
+ (tag === 'input' && attrName === 'value') ||
198
+ reEmptyAttribute.test(attrName));
199
+ }
200
+ return false;
201
+ }
202
+
203
+ function canRemoveElement(tag) {
204
+ return tag !== 'textarea';
205
+ }
206
+
207
+ function canCollapseWhitespace(tag) {
208
+ return !(/^(?:script|style|pre|textarea)$/.test(tag));
209
+ }
210
+
211
+ function canTrimWhitespace(tag) {
212
+ return !(/^(?:pre|textarea)$/.test(tag));
213
+ }
214
+
215
+ function normalizeAttribute(attr, attrs, tag, options) {
216
+
217
+ var attrName = attr.name.toLowerCase(),
218
+ attrValue = attr.escaped,
219
+ attrFragment;
220
+
221
+ if ((options.removeRedundantAttributes &&
222
+ isAttributeRedundant(tag, attrName, attrValue, attrs))
223
+ ||
224
+ (options.removeScriptTypeAttributes &&
225
+ isScriptTypeAttribute(tag, attrName, attrValue))
226
+ ||
227
+ (options.removeStyleLinkTypeAttributes &&
228
+ isStyleLinkTypeAttribute(tag, attrName, attrValue))) {
229
+ return '';
230
+ }
231
+
232
+ attrValue = cleanAttributeValue(tag, attrName, attrValue);
233
+
234
+ if (!options.removeAttributeQuotes ||
235
+ !canRemoveAttributeQuotes(attrValue)) {
236
+ attrValue = '"' + attrValue + '"';
237
+ }
238
+
239
+ if (options.removeEmptyAttributes &&
240
+ canDeleteEmptyAttribute(tag, attrName, attrValue)) {
241
+ return '';
242
+ }
243
+
244
+ if (options.collapseBooleanAttributes &&
245
+ isBooleanAttribute(attrName)) {
246
+ attrFragment = attrName;
247
+ }
248
+ else {
249
+ attrFragment = attrName + '=' + attrValue;
250
+ }
251
+
252
+ return (' ' + attrFragment);
253
+ }
254
+
255
+
256
+ function setDefaultTesters(options) {
257
+
258
+ var defaultTesters = ['canCollapseWhitespace','canTrimWhitespace'];
259
+
260
+ for (var i = 0, len = defaultTesters.length; i < len; i++) {
261
+ if (!options[defaultTesters[i]]) {
262
+ options[defaultTesters[i]] = function() {
263
+ return false;
264
+ }
265
+ }
266
+ }
267
+ }
268
+
269
+ function minify(value, options) {
270
+
271
+ options = options || { };
272
+ value = trimWhitespace(value);
273
+ setDefaultTesters(options);
274
+
275
+ var results = [ ],
276
+ buffer = [ ],
277
+ currentChars = '',
278
+ currentTag = '',
279
+ currentAttrs = [],
280
+ stackNoTrimWhitespace = [],
281
+ stackNoCollapseWhitespace = [],
282
+ lint = options.lint,
283
+ t = new Date()
284
+
285
+ function _canCollapseWhitespace(tag, attrs) {
286
+ return canCollapseWhitespace(tag) || options.canTrimWhitespace(tag, attrs);
287
+ }
288
+
289
+ function _canTrimWhitespace(tag, attrs) {
290
+ return canTrimWhitespace(tag) || options.canTrimWhitespace(tag, attrs);
291
+ }
292
+
293
+ HTMLParser(value, {
294
+ start: function( tag, attrs, unary ) {
295
+ tag = tag.toLowerCase();
296
+ currentTag = tag;
297
+ currentChars = '';
298
+ currentAttrs = attrs;
299
+
300
+ // set whitespace flags for nested tags (eg. <code> within a <pre>)
301
+ if (options.collapseWhitespace) {
302
+ if (!_canTrimWhitespace(tag, attrs)) {
303
+ stackNoTrimWhitespace.push(tag);
304
+ }
305
+ if (!_canCollapseWhitespace(tag, attrs)) {
306
+ stackNoCollapseWhitespace.push(tag);
307
+ }
308
+ }
309
+
310
+ buffer.push('<', tag);
311
+
312
+ lint && lint.testElement(tag);
313
+
314
+ for ( var i = 0, len = attrs.length; i < len; i++ ) {
315
+ lint && lint.testAttribute(tag, attrs[i].name.toLowerCase(), attrs[i].escaped);
316
+ buffer.push(normalizeAttribute(attrs[i], attrs, tag, options));
317
+ }
318
+
319
+ buffer.push('>');
320
+ },
321
+ end: function( tag ) {
322
+ // check if current tag is in a whitespace stack
323
+ if (options.collapseWhitespace) {
324
+ if (stackNoTrimWhitespace.length &&
325
+ tag == stackNoTrimWhitespace[stackNoTrimWhitespace.length - 1]) {
326
+ stackNoTrimWhitespace.pop();
327
+ }
328
+ if (stackNoCollapseWhitespace.length &&
329
+ tag == stackNoCollapseWhitespace[stackNoCollapseWhitespace.length - 1]) {
330
+ stackNoCollapseWhitespace.pop();
331
+ }
332
+ }
333
+
334
+ var isElementEmpty = currentChars === '' && tag === currentTag;
335
+ if ((options.removeEmptyElements && isElementEmpty && canRemoveElement(tag))) {
336
+ // remove last "element" from buffer, return
337
+ buffer.splice(buffer.lastIndexOf('<'));
338
+ return;
339
+ }
340
+ else if (options.removeOptionalTags && isOptionalTag(tag)) {
341
+ // noop, leave start tag in buffer
342
+ return;
343
+ }
344
+ else {
345
+ // push end tag to buffer
346
+ buffer.push('</', tag.toLowerCase(), '>');
347
+ results.push.apply(results, buffer);
348
+ }
349
+ // flush buffer
350
+ buffer.length = 0;
351
+ currentChars = '';
352
+ },
353
+ chars: function( text ) {
354
+ if (currentTag === 'script' || currentTag === 'style') {
355
+ if (options.removeCommentsFromCDATA) {
356
+ text = removeComments(text, currentTag);
357
+ }
358
+ if (options.removeCDATASectionsFromCDATA) {
359
+ text = removeCDATASections(text);
360
+ }
361
+ }
362
+ if (options.collapseWhitespace) {
363
+ if (!stackNoTrimWhitespace.length && _canTrimWhitespace(currentTag, currentAttrs)) {
364
+ text = trimWhitespace(text);
365
+ }
366
+ if (!stackNoCollapseWhitespace.length && _canCollapseWhitespace(currentTag, currentAttrs)) {
367
+ text = collapseWhitespace(text);
368
+ }
369
+ }
370
+ currentChars = text;
371
+ lint && lint.testChars(text);
372
+ buffer.push(text);
373
+ },
374
+ comment: function( text ) {
375
+ if (options.removeComments) {
376
+ if (isConditionalComment(text)) {
377
+ text = '<!--' + cleanConditionalComment(text) + '-->';
378
+ }
379
+ else {
380
+ text = '';
381
+ }
382
+ }
383
+ else {
384
+ text = '<!--' + text + '-->';
385
+ }
386
+ buffer.push(text);
387
+ },
388
+ doctype: function(doctype) {
389
+ buffer.push(options.useShortDoctype ? '<!DOCTYPE html>' : collapseWhitespace(doctype));
390
+ }
391
+ });
392
+
393
+ results.push.apply(results, buffer)
394
+ var str = results.join('');
395
+ log('minified in: ' + (new Date() - t) + 'ms');
396
+ return str;
397
+ }
398
+
399
+ // for CommonJS enviroments, export everything
400
+ if ( typeof exports !== "undefined" ) {
401
+ exports.minify = minify;
402
+ } else {
403
+ global.minify = minify;
404
+ }
405
+
406
+ }(this));
@@ -0,0 +1,327 @@
1
+ /*
2
+ * HTML Parser By John Resig (ejohn.org)
3
+ * Modified by Juriy "kangax" Zaytsev
4
+ * Original code by Erik Arvidsson, Mozilla Public License
5
+ * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
6
+ *
7
+ * // Use like so:
8
+ * HTMLParser(htmlString, {
9
+ * start: function(tag, attrs, unary) {},
10
+ * end: function(tag) {},
11
+ * chars: function(text) {},
12
+ * comment: function(text) {}
13
+ * });
14
+ *
15
+ * // or to get an XML string:
16
+ * HTMLtoXML(htmlString);
17
+ *
18
+ * // or to get an XML DOM Document
19
+ * HTMLtoDOM(htmlString);
20
+ *
21
+ * // or to inject into an existing document/DOM node
22
+ * HTMLtoDOM(htmlString, document);
23
+ * HTMLtoDOM(htmlString, document.body);
24
+ *
25
+ */
26
+
27
+ (function(global){
28
+
29
+ // Regular Expressions for parsing tags and attributes
30
+ var startTag = /^<(\w+)((?:\s*[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
31
+ endTag = /^<\/(\w+)[^>]*>/,
32
+ attr = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g,
33
+ doctype = /^<!DOCTYPE [^>]+>/i;
34
+
35
+ // Empty Elements - HTML 4.01
36
+ var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");
37
+
38
+ // Block Elements - HTML 4.01
39
+ var block = makeMap("address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,script,table,tbody,td,tfoot,th,thead,tr,ul");
40
+
41
+ // Inline Elements - HTML 4.01
42
+ var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");
43
+
44
+ // Elements that you can, intentionally, leave open
45
+ // (and which close themselves)
46
+ var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");
47
+
48
+ // Attributes that have their values filled in disabled="disabled"
49
+ var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");
50
+
51
+ // Special Elements (can contain anything)
52
+ var special = makeMap("script,style");
53
+
54
+ var reCache = { }, stackedTag, re;
55
+
56
+ var HTMLParser = global.HTMLParser = function( html, handler ) {
57
+ var index, chars, match, stack = [], last = html;
58
+ stack.last = function(){
59
+ return this[ this.length - 1 ];
60
+ };
61
+
62
+ while ( html ) {
63
+ chars = true;
64
+
65
+ // Make sure we're not in a script or style element
66
+ if ( !stack.last() || !special[ stack.last() ] ) {
67
+
68
+ // Comment
69
+ if ( html.indexOf("<!--") === 0 ) {
70
+ index = html.indexOf("-->");
71
+
72
+ if ( index >= 0 ) {
73
+ if ( handler.comment )
74
+ handler.comment( html.substring( 4, index ) );
75
+ html = html.substring( index + 3 );
76
+ chars = false;
77
+ }
78
+ }
79
+ else if ( match = doctype.exec( html )) {
80
+ if ( handler.doctype )
81
+ handler.doctype( match[0] );
82
+ html = html.substring( match[0].length );
83
+ chars = false;
84
+
85
+ // end tag
86
+ } else if ( html.indexOf("</") === 0 ) {
87
+ match = html.match( endTag );
88
+
89
+ if ( match ) {
90
+ html = html.substring( match[0].length );
91
+ match[0].replace( endTag, parseEndTag );
92
+ chars = false;
93
+ }
94
+
95
+ // start tag
96
+ } else if ( html.indexOf("<") === 0 ) {
97
+ match = html.match( startTag );
98
+
99
+ if ( match ) {
100
+ html = html.substring( match[0].length );
101
+ match[0].replace( startTag, parseStartTag );
102
+ chars = false;
103
+ }
104
+ }
105
+
106
+ if ( chars ) {
107
+ index = html.indexOf("<");
108
+
109
+ var text = index < 0 ? html : html.substring( 0, index );
110
+ html = index < 0 ? "" : html.substring( index );
111
+
112
+ if ( handler.chars )
113
+ handler.chars( text );
114
+ }
115
+
116
+ } else {
117
+
118
+ stackedTag = stack.last().toLowerCase();
119
+ reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp("([\\s\\S]*?)<\/" + stackedTag + "[^>]*>", "i"));
120
+
121
+ html = html.replace(reStackedTag, function(all, text) {
122
+ if (stackedTag !== 'script' && stackedTag !== 'style') {
123
+ text = text
124
+ .replace(/<!--([\s\S]*?)-->/g, "$1")
125
+ .replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, "$1");
126
+ }
127
+
128
+ if ( handler.chars )
129
+ handler.chars( text );
130
+
131
+ return "";
132
+ });
133
+
134
+ parseEndTag( "", stackedTag );
135
+ }
136
+
137
+ if ( html == last )
138
+ throw "Parse Error: " + html;
139
+ last = html;
140
+ }
141
+
142
+ // Clean up any remaining tags
143
+ parseEndTag();
144
+
145
+ function parseStartTag( tag, tagName, rest, unary ) {
146
+ if ( block[ tagName ] ) {
147
+ while ( stack.last() && inline[ stack.last() ] ) {
148
+ parseEndTag( "", stack.last() );
149
+ }
150
+ }
151
+
152
+ if ( closeSelf[ tagName ] && stack.last() == tagName ) {
153
+ parseEndTag( "", tagName );
154
+ }
155
+
156
+ unary = empty[ tagName ] || !!unary;
157
+
158
+ if ( !unary )
159
+ stack.push( tagName );
160
+
161
+ if ( handler.start ) {
162
+ var attrs = [];
163
+
164
+ rest.replace(attr, function(match, name) {
165
+ var value = arguments[2] ? arguments[2] :
166
+ arguments[3] ? arguments[3] :
167
+ arguments[4] ? arguments[4] :
168
+ fillAttrs[name] ? name : "";
169
+ attrs.push({
170
+ name: name,
171
+ value: value,
172
+ escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //"
173
+ });
174
+ });
175
+
176
+ if ( handler.start )
177
+ handler.start( tagName, attrs, unary );
178
+ }
179
+ }
180
+
181
+ function parseEndTag( tag, tagName ) {
182
+ // If no tag name is provided, clean shop
183
+ if ( !tagName )
184
+ var pos = 0;
185
+
186
+ // Find the closest opened tag of the same type
187
+ else
188
+ for ( var pos = stack.length - 1; pos >= 0; pos-- )
189
+ if ( stack[ pos ] == tagName )
190
+ break;
191
+
192
+ if ( pos >= 0 ) {
193
+ // Close all the open elements, up the stack
194
+ for ( var i = stack.length - 1; i >= pos; i-- )
195
+ if ( handler.end )
196
+ handler.end( stack[ i ] );
197
+
198
+ // Remove the open elements from the stack
199
+ stack.length = pos;
200
+ }
201
+ }
202
+ };
203
+
204
+ global.HTMLtoXML = function( html ) {
205
+ var results = "";
206
+
207
+ HTMLParser(html, {
208
+ start: function( tag, attrs, unary ) {
209
+ results += "<" + tag;
210
+
211
+ for ( var i = 0; i < attrs.length; i++ )
212
+ results += " " + attrs[i].name + '="' + attrs[i].escaped + '"';
213
+
214
+ results += (unary ? "/" : "") + ">";
215
+ },
216
+ end: function( tag ) {
217
+ results += "</" + tag + ">";
218
+ },
219
+ chars: function( text ) {
220
+ results += text;
221
+ },
222
+ comment: function( text ) {
223
+ results += "<!--" + text + "-->";
224
+ }
225
+ });
226
+
227
+ return results;
228
+ };
229
+
230
+ global.HTMLtoDOM = function( html, doc ) {
231
+ // There can be only one of these elements
232
+ var one = makeMap("html,head,body,title");
233
+
234
+ // Enforce a structure for the document
235
+ var structure = {
236
+ link: "head",
237
+ base: "head"
238
+ };
239
+
240
+ if ( !doc ) {
241
+ if ( typeof DOMDocument != "undefined" )
242
+ doc = new DOMDocument();
243
+ else if ( typeof document != "undefined" && document.implementation && document.implementation.createDocument )
244
+ doc = document.implementation.createDocument("", "", null);
245
+ else if ( typeof ActiveX != "undefined" )
246
+ doc = new ActiveXObject("Msxml.DOMDocument");
247
+
248
+ } else
249
+ doc = doc.ownerDocument ||
250
+ doc.getOwnerDocument && doc.getOwnerDocument() ||
251
+ doc;
252
+
253
+ var elems = [],
254
+ documentElement = doc.documentElement ||
255
+ doc.getDocumentElement && doc.getDocumentElement();
256
+
257
+ // If we're dealing with an empty document then we
258
+ // need to pre-populate it with the HTML document structure
259
+ if ( !documentElement && doc.createElement ) (function(){
260
+ var html = doc.createElement("html");
261
+ var head = doc.createElement("head");
262
+ head.appendChild( doc.createElement("title") );
263
+ html.appendChild( head );
264
+ html.appendChild( doc.createElement("body") );
265
+ doc.appendChild( html );
266
+ })();
267
+
268
+ // Find all the unique elements
269
+ if ( doc.getElementsByTagName )
270
+ for ( var i in one )
271
+ one[ i ] = doc.getElementsByTagName( i )[0];
272
+
273
+ // If we're working with a document, inject contents into
274
+ // the body element
275
+ var curParentNode = one.body;
276
+
277
+ HTMLParser( html, {
278
+ start: function( tagName, attrs, unary ) {
279
+ // If it's a pre-built element, then we can ignore
280
+ // its construction
281
+ if ( one[ tagName ] ) {
282
+ curParentNode = one[ tagName ];
283
+ return;
284
+ }
285
+
286
+ var elem = doc.createElement( tagName );
287
+
288
+ for ( var attr in attrs )
289
+ elem.setAttribute( attrs[ attr ].name, attrs[ attr ].value );
290
+
291
+ if ( structure[ tagName ] && typeof one[ structure[ tagName ] ] != "boolean" )
292
+ one[ structure[ tagName ] ].appendChild( elem );
293
+
294
+ else if ( curParentNode && curParentNode.appendChild )
295
+ curParentNode.appendChild( elem );
296
+
297
+ if ( !unary ) {
298
+ elems.push( elem );
299
+ curParentNode = elem;
300
+ }
301
+ },
302
+ end: function( tag ) {
303
+ elems.length -= 1;
304
+
305
+ // Init the new parentNode
306
+ curParentNode = elems[ elems.length - 1 ];
307
+ },
308
+ chars: function( text ) {
309
+ curParentNode.appendChild( doc.createTextNode( text ) );
310
+ },
311
+ comment: function( text ) {
312
+ // create comment node
313
+ }
314
+ });
315
+
316
+ return doc;
317
+ };
318
+
319
+ function makeMap(str){
320
+ var obj = {}, items = str.split(",");
321
+ for ( var i = 0; i < items.length; i++ ) {
322
+ obj[ items[i] ] = true;
323
+ obj[ items[i].toUpperCase() ] = true;
324
+ }
325
+ return obj;
326
+ }
327
+ })(typeof exports === 'undefined' ? this : exports);
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+ require "html_minifier"
3
+
4
+ class TestLogger
5
+ attr_accessor :logs
6
+
7
+ def initialize
8
+ @logs = []
9
+ end
10
+
11
+ def info message
12
+ @logs << message
13
+ end
14
+ end
15
+
16
+ describe "HtmlMinifier" do
17
+
18
+ it "it works" do
19
+ html = '<p>foo</p>'
20
+ HtmlMinifier.minify(html).should eq html
21
+ end
22
+
23
+ it "it logs" do
24
+ html = '<p>foo</p>'
25
+ log = TestLogger.new
26
+ HtmlMinifier.minify(html, :log => log).should eq html
27
+ log.logs.length.should eq 1
28
+ end
29
+ end
@@ -0,0 +1,44 @@
1
+ (function(QUnit) {
2
+
3
+ QUnit.init();
4
+ QUnit.config.blocking = false;
5
+ QUnit.config.autorun = true;
6
+ QUnit.config.updateRate = 0;
7
+
8
+ var assertions,
9
+ result = {
10
+ pass: 0,
11
+ fail: 0,
12
+ pass_asserions: 0,
13
+ fail_asserions: 0,
14
+ assertions: {},
15
+ tests: 0
16
+ };
17
+
18
+ QUnit.testDone(function (r) {
19
+ if (r.failed > 0) {
20
+ result.fail += 1;
21
+ result.assertions[(r.module ? r.module + ':' : '') + r.name] = assertions;
22
+ assertions = null;
23
+ } else {
24
+ result.pass += 1;
25
+ }
26
+ result.fail_asserions += r.failed;
27
+ result.pass_asserions += r.passed;
28
+ result.tests++;
29
+ });
30
+
31
+ QUnit.log(function (r) {
32
+ if (!r.result) {
33
+ assertions = [r.message, r.actual, r.expected];
34
+ }
35
+ });
36
+
37
+ QUnit.result = function () {
38
+ // run the tests
39
+ // QUnit.begin();
40
+ // QUnit.start();
41
+ return result;
42
+ };
43
+
44
+ }( QUnit ));
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: html_minifier
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - slavic
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-22 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &28832952 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *28832952
25
+ - !ruby/object:Gem::Dependency
26
+ name: submodule
27
+ requirement: &28832652 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 0.1.0
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *28832652
36
+ - !ruby/object:Gem::Dependency
37
+ name: rake
38
+ requirement: &28832400 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *28832400
47
+ - !ruby/object:Gem::Dependency
48
+ name: multi_json
49
+ requirement: &28832076 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *28832076
58
+ - !ruby/object:Gem::Dependency
59
+ name: execjs
60
+ requirement: &28831824 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *28831824
69
+ description: Ruby wrapper for kangax html-minifier
70
+ email:
71
+ - stereobooster@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - .gitmodules
78
+ - .travis.yml
79
+ - Gemfile
80
+ - README.md
81
+ - Rakefile
82
+ - html_minifier.gemspec
83
+ - lib/html_minifier.rb
84
+ - lib/html_minifier/linter.rb
85
+ - lib/html_minifier/minifier.rb
86
+ - lib/html_minifier/task.rb
87
+ - lib/html_minifier/version.rb
88
+ - lib/js/console.js
89
+ - lib/js/exports.js
90
+ - lib/js/htmllint.js
91
+ - lib/js/htmlminifier.js
92
+ - lib/js/htmlparser.js
93
+ - spec/html_minifier_spec.rb
94
+ - spec/qunit_helper.js
95
+ homepage: https://github.com/stereobooster/html_minifier
96
+ licenses: []
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ! '>='
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ segments:
108
+ - 0
109
+ hash: -143587247
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ segments:
117
+ - 0
118
+ hash: -143587247
119
+ requirements: []
120
+ rubyforge_project:
121
+ rubygems_version: 1.8.15
122
+ signing_key:
123
+ specification_version: 3
124
+ summary: Ruby wrapper for kangax html-minifier
125
+ test_files: []