jsmin-ffi 0.2.2

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