jsmin-ffi 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,46 @@
1
+ h2. FFI Javascript minifier
2
+
3
+ h3. Description
4
+
5
+ This library was created in order to provide a reasonably fast Javascript
6
+ minifier on JRuby platform.
7
+ Both native Java and Ruby implementations were found to be in several orders
8
+ of magnitude slower.
9
+ The only change from the original C implementation, is that it was changed
10
+ to C++ (in order to make global variables become instance variables),
11
+ and it works with buffers rather than stdin/stdout now.
12
+
13
+ h3. Synopsis
14
+
15
+ <pre>
16
+ require 'jsmin_ffi'
17
+ input = IO.read('prototype.js')
18
+ begin
19
+ output = JsminFFI.minify!(input)
20
+ File.open('output.js', 'w') {|f| f.write(output)}
21
+ rescue Jsmin::ParseError => e
22
+ $stderr.puts "Cannot minify: #{e}"
23
+ end
24
+ </pre>
25
+
26
+ or use a native extension:
27
+ <pre>
28
+ require 'Jsmin'
29
+ input = IO.read('jquery.js')
30
+ begin
31
+ output = Jsmin.minify(input)
32
+ File.open('output.js', 'w') {|f| f.write(output)}
33
+ rescue Jsmin::ParseError => e
34
+ $stderr.puts "Cannot minify: #{e}"
35
+ end
36
+ </pre>
37
+
38
+ h3. Bugs
39
+
40
+ The memory allocated in the C++ code might not be freed when using FFI.
41
+ It depends on how FFI handles the returned char*.
42
+
43
+ h3. See also
44
+
45
+ For the original implementation, please see:
46
+ http://www.crockford.com/javascript/jsmin.html
@@ -0,0 +1,14 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = 'jsmin-ffi'
5
+ gemspec.summary = 'Very fast jsmin implementation using FFI'
6
+ gemspec.email = 'romanbsd@yahoo.com'
7
+ gemspec.homepage = 'http://github.com/romanbsd/jsmin-ffi'
8
+ gemspec.description = gemspec.summary
9
+ gemspec.authors = ['Roman Shterenzon']
10
+ gemspec.add_dependency('ffi', '>=0.3.5')
11
+ end
12
+ rescue LoadError
13
+ end
14
+ Jeweler::RubygemsDotOrgTasks.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.2
@@ -0,0 +1,2 @@
1
+ require 'mkmf'
2
+ create_makefile('Jsmin')
@@ -0,0 +1,306 @@
1
+ /* jsmin.c
2
+ 2011-01-22
3
+
4
+ Copyright (c) 2002 Douglas Crockford (www.crockford.com)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
7
+ this software and associated documentation files (the "Software"), to deal in
8
+ the Software without restriction, including without limitation the rights to
9
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10
+ of the Software, and to permit persons to whom the Software is furnished to do
11
+ so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ The Software shall be used for Good, not Evil.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ SOFTWARE.
25
+ */
26
+
27
+ #include <stdlib.h>
28
+ #include <stdio.h>
29
+ #include <string.h>
30
+ #include "jsmin.h"
31
+
32
+ Jsmin::Jsmin()
33
+ {
34
+ index_in = 0;
35
+ index_out = 0;
36
+ theLookahead = EOF;
37
+ output_buf = NULL;
38
+ input_buf = NULL;
39
+ }
40
+
41
+ Jsmin::~Jsmin() {
42
+ if (output_buf) {
43
+ free(output_buf);
44
+ }
45
+ }
46
+ /* isAlphanum -- return true if the character is a letter, digit, underscore,
47
+ dollar sign, or non-ASCII character.
48
+ */
49
+
50
+ int Jsmin::isAlphanum(int c)
51
+ {
52
+ return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
53
+ (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' ||
54
+ c > 126);
55
+ }
56
+
57
+
58
+ /* get -- return the next character from stdin. Watch out for lookahead. If
59
+ the character is a control character, translate it to a space or
60
+ linefeed.
61
+ */
62
+
63
+ int Jsmin::get()
64
+ {
65
+ int c = theLookahead;
66
+ theLookahead = EOF;
67
+ if ( index_in >= m_size ) {
68
+ return EOF;
69
+ }
70
+ if (c == EOF) {
71
+ c = input_buf[index_in++];
72
+ }
73
+ if (c >= ' ' || c == '\n' || c == EOF) {
74
+ return c;
75
+ }
76
+ if (c == '\r') {
77
+ return '\n';
78
+ }
79
+ return ' ';
80
+ }
81
+
82
+
83
+ /* peek -- get the next character without getting it.
84
+ */
85
+
86
+ int Jsmin::peek()
87
+ {
88
+ theLookahead = get();
89
+ return theLookahead;
90
+ }
91
+
92
+
93
+ /* next -- get the next character, excluding comments. peek() is used to see
94
+ if a '/' is followed by a '/' or '*'.
95
+ */
96
+
97
+ int Jsmin::next()
98
+ {
99
+ int c = get();
100
+ if (c == '/') {
101
+ switch (peek()) {
102
+ case '/':
103
+ for (;;) {
104
+ c = get();
105
+ if (c <= '\n') {
106
+ return c;
107
+ }
108
+ }
109
+ case '*':
110
+ get();
111
+ for (;;) {
112
+ switch (get()) {
113
+ case '*':
114
+ if (peek() == '/') {
115
+ get();
116
+ return ' ';
117
+ }
118
+ break;
119
+ case EOF:
120
+ throw("!Unterminated comment");
121
+ }
122
+ }
123
+ default:
124
+ return c;
125
+ }
126
+ }
127
+ return c;
128
+ }
129
+
130
+
131
+ /* action -- do something! What you do is determined by the argument:
132
+ 1 Output A. Copy B to A. Get the next B.
133
+ 2 Copy B to A. Get the next B. (Delete A).
134
+ 3 Get the next B. (Delete B).
135
+ action treats a string as a single character. Wow!
136
+ action recognizes a regular expression if it is preceded by ( or , or =.
137
+ */
138
+
139
+ void Jsmin::action(int d)
140
+ {
141
+ switch (d) {
142
+ case 1:
143
+ output_buf[index_out++] = theA;
144
+ case 2:
145
+ theA = theB;
146
+ if (theA == '\'' || theA == '"') {
147
+ for (;;) {
148
+ output_buf[index_out++] = theA;
149
+ theA = get();
150
+ if (theA == theB) {
151
+ break;
152
+ }
153
+ if (theA == '\\') {
154
+ output_buf[index_out++] = theA;
155
+ theA = get();
156
+ }
157
+ if (theA == EOF) {
158
+ throw("!Unterminated string literal");
159
+ }
160
+ }
161
+ }
162
+ case 3:
163
+ theB = next();
164
+ if (theB == '/' && (theA == '(' || theA == ',' || theA == '=' ||
165
+ theA == ':' || theA == '[' || theA == '!' ||
166
+ theA == '&' || theA == '|' || theA == '?' ||
167
+ theA == '{' || theA == '}' || theA == ';' ||
168
+ theA == '\n')) {
169
+ output_buf[index_out++] = theA;
170
+ output_buf[index_out++] = theB;
171
+ for (;;) {
172
+ theA = get();
173
+ if (theA == '[') {
174
+ for (;;) {
175
+ output_buf[index_out++] = theA;
176
+ theA = get();
177
+ if (theA == ']') {
178
+ break;
179
+ }
180
+ if (theA == '\\') {
181
+ output_buf[index_out++] = theA;
182
+ theA = get();
183
+ }
184
+ if (theA == EOF) {
185
+ throw("!Unterminated set in Regular Expression literal");
186
+ }
187
+ }
188
+ } else if (theA == '/') {
189
+ break;
190
+ } else if (theA =='\\') {
191
+ output_buf[index_out++] = theA;
192
+ theA = get();
193
+ }
194
+ if (theA == EOF) {
195
+ throw("!Unterminated Regular Expression literal");
196
+ }
197
+ output_buf[index_out++] = theA;
198
+ }
199
+ theB = next();
200
+ }
201
+ }
202
+ }
203
+
204
+
205
+ /* minify -- Copy the input to the output, deleting the characters which are
206
+ insignificant to JavaScript. Comments will be removed. Tabs will be
207
+ replaced with spaces. Carriage returns will be replaced with linefeeds.
208
+ Most spaces and linefeeds will be removed.
209
+ */
210
+
211
+ char* Jsmin::minify(const char *original)
212
+ {
213
+ input_buf = original;
214
+ index_in = 0;
215
+ index_out = 0;
216
+
217
+ if (output_buf != NULL) {
218
+ free(output_buf);
219
+ output_buf = NULL;
220
+ }
221
+ m_size = strlen(original);
222
+ output_buf = (char *)malloc(sizeof(char) * (m_size+1));
223
+
224
+ theA = ';';
225
+ action(3);
226
+ while (theA != EOF) {
227
+ switch (theA) {
228
+ case ' ':
229
+ if (isAlphanum(theB)) {
230
+ action(1);
231
+ } else {
232
+ action(2);
233
+ }
234
+ break;
235
+ case '\n':
236
+ switch (theB) {
237
+ case '{':
238
+ case '[':
239
+ case '(':
240
+ case '+':
241
+ case '-':
242
+ action(1);
243
+ break;
244
+ case ' ':
245
+ action(3);
246
+ break;
247
+ default:
248
+ if (isAlphanum(theB)) {
249
+ action(1);
250
+ } else {
251
+ action(2);
252
+ }
253
+ }
254
+ break;
255
+ default:
256
+ switch (theB) {
257
+ case ' ':
258
+ if (isAlphanum(theA)) {
259
+ action(1);
260
+ break;
261
+ }
262
+ action(3);
263
+ break;
264
+ case '\n':
265
+ switch (theA) {
266
+ case '}':
267
+ case ']':
268
+ case ')':
269
+ case '+':
270
+ case '-':
271
+ case '"':
272
+ case '\'':
273
+ action(1);
274
+ break;
275
+ default:
276
+ if (isAlphanum(theA)) {
277
+ action(1);
278
+ } else {
279
+ action(3);
280
+ }
281
+ }
282
+ break;
283
+ default:
284
+ action(1);
285
+ break;
286
+ }
287
+ }
288
+ }
289
+ output_buf[index_out] = 0;
290
+ return output_buf;
291
+ }
292
+
293
+ extern "C" {
294
+ char* minify(const char *in)
295
+ {
296
+ char *out;
297
+ Jsmin m;
298
+ try {
299
+ out = strdup(m.minify(in));
300
+ }
301
+ catch (char const *e) {
302
+ out = strdup(e);
303
+ }
304
+ return out;
305
+ }
306
+ }
@@ -0,0 +1,27 @@
1
+ #ifndef JSMIN_H
2
+ #define JSMIN_H
3
+
4
+ class Jsmin
5
+ {
6
+ public:
7
+ Jsmin();
8
+ ~Jsmin();
9
+ char* minify(const char *);
10
+
11
+ private:
12
+ int theA;
13
+ int theB;
14
+ int theLookahead;
15
+ size_t index_in;
16
+ size_t index_out;
17
+ const char *input_buf;
18
+ char *output_buf;
19
+ size_t m_size;
20
+
21
+ int isAlphanum(int c);
22
+ int get();
23
+ int peek();
24
+ int next();
25
+ void action(int d);
26
+ };
27
+ #endif
@@ -0,0 +1,51 @@
1
+ /* This trivial wrapper can be used for native extension
2
+ * currently unused.
3
+ */
4
+
5
+ #include <ruby.h>
6
+
7
+ #ifndef RSTRING_PTR
8
+ #define RSTRING_PTR(str) (RSTRING(str)->ptr)
9
+ #endif
10
+
11
+ extern char *minify(char *);
12
+
13
+ static VALUE rb_eParseError;
14
+
15
+ /*
16
+ * call-seq:
17
+ * Jsmin.minify(str) -> new_str
18
+ *
19
+ * Returns a new string object containing a minified copy of <i>str</i>.
20
+ * May raise Jsmin::ParseError if parsing fails.
21
+ */
22
+ static VALUE minify_wrap(VALUE self, VALUE arg)
23
+ {
24
+ char *input;
25
+ char *res;
26
+ VALUE str, rv;
27
+
28
+ str = StringValue(arg);
29
+ input = RSTRING_PTR(str);
30
+
31
+ res = minify(input);
32
+
33
+ if (res[0] == '!') {
34
+ rv = rb_str_new2(res+1);
35
+ free(res);
36
+ rb_raise(rb_eParseError, "%s", RSTRING_PTR(rv));
37
+ }
38
+
39
+ rv = rb_str_new2(res);
40
+ free(res);
41
+
42
+ return rv;
43
+ }
44
+
45
+ static VALUE mJsmin;
46
+
47
+ void Init_Jsmin(void) {
48
+ mJsmin = rb_define_module("Jsmin");
49
+ rb_eParseError = rb_define_class_under(mJsmin, "ParseError", rb_eRuntimeError);
50
+ rb_define_module_function(mJsmin, "minify", minify_wrap, 1);
51
+ }
@@ -0,0 +1,48 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{jsmin-ffi}
8
+ s.version = "0.2.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Roman Shterenzon"]
12
+ s.date = %q{2011-03-28}
13
+ s.description = %q{Very fast jsmin implementation using FFI}
14
+ s.email = %q{romanbsd@yahoo.com}
15
+ s.extensions = ["ext/extconf.rb"]
16
+ s.extra_rdoc_files = [
17
+ "README.textile"
18
+ ]
19
+ s.files = [
20
+ "README.textile",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "ext/extconf.rb",
24
+ "ext/jsmin.cpp",
25
+ "ext/jsmin.h",
26
+ "ext/jsmin_wrap.c",
27
+ "jsmin-ffi.gemspec",
28
+ "lib/jsmin_ffi.rb"
29
+ ]
30
+ s.homepage = %q{http://github.com/romanbsd/jsmin-ffi}
31
+ s.require_paths = ["lib"]
32
+ s.rubygems_version = %q{1.3.7}
33
+ s.summary = %q{Very fast jsmin implementation using FFI}
34
+
35
+ if s.respond_to? :specification_version then
36
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
37
+ s.specification_version = 3
38
+
39
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
40
+ s.add_runtime_dependency(%q<ffi>, [">= 0.3.5"])
41
+ else
42
+ s.add_dependency(%q<ffi>, [">= 0.3.5"])
43
+ end
44
+ else
45
+ s.add_dependency(%q<ffi>, [">= 0.3.5"])
46
+ end
47
+ end
48
+
@@ -0,0 +1,23 @@
1
+ require 'ffi'
2
+
3
+ module JsminFFI
4
+ extend FFI::Library
5
+ ffi_lib File.dirname(__FILE__) + '/Jsmin.' + (RUBY_PLATFORM=~/darwin/ ? 'bundle' : 'so')
6
+ attach_function :minify, [:string], :string
7
+
8
+ class ParseError < RuntimeError; end
9
+
10
+ extend self
11
+
12
+ # Minify the provided javascript
13
+ # @param [String] buf Javascript text
14
+ # @return [String] minified Javascript text
15
+ # @raise [ParseError] when parsing fails
16
+ def minify!(buf)
17
+ res = minify(buf)
18
+ if res[0..0] == '!'
19
+ raise ParseError.new(res.tr('!', ''))
20
+ end
21
+ res
22
+ end
23
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jsmin-ffi
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 2
9
+ version: 0.2.2
10
+ platform: ruby
11
+ authors:
12
+ - Roman Shterenzon
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-03-28 00:00:00 +02:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: ffi
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ - 3
31
+ - 5
32
+ version: 0.3.5
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ description: Very fast jsmin implementation using FFI
36
+ email: romanbsd@yahoo.com
37
+ executables: []
38
+
39
+ extensions:
40
+ - ext/extconf.rb
41
+ extra_rdoc_files:
42
+ - README.textile
43
+ files:
44
+ - README.textile
45
+ - Rakefile
46
+ - VERSION
47
+ - ext/extconf.rb
48
+ - ext/jsmin.cpp
49
+ - ext/jsmin.h
50
+ - ext/jsmin_wrap.c
51
+ - jsmin-ffi.gemspec
52
+ - lib/jsmin_ffi.rb
53
+ has_rdoc: true
54
+ homepage: http://github.com/romanbsd/jsmin-ffi
55
+ licenses: []
56
+
57
+ post_install_message:
58
+ rdoc_options: []
59
+
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ segments:
68
+ - 0
69
+ version: "0"
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ requirements: []
79
+
80
+ rubyforge_project:
81
+ rubygems_version: 1.3.7
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: Very fast jsmin implementation using FFI
85
+ test_files: []
86
+