romanbsd-jsmin-ffi 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.textile ADDED
@@ -0,0 +1,31 @@
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
+ input = IO.read('prototype.js')
16
+ begin
17
+ output = Jsmin.process!(input)
18
+ File.open('output.js', 'w') {|f| f.write(output)}
19
+ rescue Jsmin::ParseException => e
20
+ $stderr.puts "Cannot minify: #{e}"
21
+ end
22
+
23
+ h3. Bugs
24
+
25
+ The memory allocated in the C++ code might not be freed.
26
+ It depends on how FFI handles the returned char*.
27
+
28
+ h3. See also
29
+
30
+ For the original implementation, please see:
31
+ http://www.crockford.com/javascript/jsmin.html
data/Rakefile ADDED
@@ -0,0 +1,16 @@
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.extensions << 'ext/extconf.rb'
11
+ gemspec.add_dependency('ffi', '>=0.3.5')
12
+ gemspec.files = FileList["[A-Z]*", "{ext,lib}/**/*"]
13
+ end
14
+ rescue LoadError
15
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
16
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/ext/Makefile ADDED
@@ -0,0 +1,13 @@
1
+ CC=g++
2
+ CXXFLAGS=-fPIC -O3
3
+
4
+ libjsmin.so.0: jsmin.o
5
+ $(CC) -shared -Wl,-soname,libjsmin.so.0 -o libjsmin.so.0 jsmin.o
6
+
7
+ jsmin.o: jsmin.cpp jsmin.h
8
+
9
+ clean:
10
+ rm -f *.o *.so.0
11
+
12
+ install:
13
+ cp libjsmin.so.0 ../lib
data/ext/extconf.rb ADDED
@@ -0,0 +1 @@
1
+ puts 'Using provided Makefile'
data/ext/jsmin.cpp ADDED
@@ -0,0 +1,286 @@
1
+ /* jsmin.c
2
+ 2008-08-03
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 = 0;
37
+ output_buf = NULL;
38
+ input_buf = NULL;
39
+ }
40
+
41
+
42
+ /* isAlphanum -- return true if the character is a letter, digit, underscore,
43
+ dollar sign, or non-ASCII character.
44
+ */
45
+
46
+ int Jsmin::isAlphanum(int c)
47
+ {
48
+ return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
49
+ (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' ||
50
+ c > 126);
51
+ }
52
+
53
+
54
+ /* get -- return the next character from stdin. Watch out for lookahead. If
55
+ the character is a control character, translate it to a space or
56
+ linefeed.
57
+ */
58
+
59
+ int Jsmin::get()
60
+ {
61
+ int c = theLookahead;
62
+ theLookahead = 0;
63
+ if (c == 0) {
64
+ c = input_buf[index_in++];
65
+ }
66
+ if (c >= ' ' || c == '\n' || c == 0) {
67
+ return c;
68
+ }
69
+ if (c == '\r') {
70
+ return '\n';
71
+ }
72
+ return ' ';
73
+ }
74
+
75
+
76
+ /* peek -- get the next character without getting it.
77
+ */
78
+
79
+ int Jsmin::peek()
80
+ {
81
+ theLookahead = get();
82
+ return theLookahead;
83
+ }
84
+
85
+
86
+ /* next -- get the next character, excluding comments. peek() is used to see
87
+ if a '/' is followed by a '/' or '*'.
88
+ */
89
+
90
+ int Jsmin::next()
91
+ {
92
+ int c = get();
93
+ if (c == '/') {
94
+ switch (peek()) {
95
+ case '/':
96
+ for (;;) {
97
+ c = get();
98
+ if (c <= '\n') {
99
+ return c;
100
+ }
101
+ }
102
+ case '*':
103
+ get();
104
+ for (;;) {
105
+ switch (get()) {
106
+ case '*':
107
+ if (peek() == '/') {
108
+ get();
109
+ return ' ';
110
+ }
111
+ break;
112
+ case 0:
113
+ throw("!Unterminated comment");
114
+ }
115
+ }
116
+ default:
117
+ return c;
118
+ }
119
+ }
120
+ return c;
121
+ }
122
+
123
+
124
+ /* action -- do something! What you do is determined by the argument:
125
+ 1 Output A. Copy B to A. Get the next B.
126
+ 2 Copy B to A. Get the next B. (Delete A).
127
+ 3 Get the next B. (Delete B).
128
+ action treats a string as a single character. Wow!
129
+ action recognizes a regular expression if it is preceded by ( or , or =.
130
+ */
131
+
132
+ void Jsmin::action(int d)
133
+ {
134
+ switch (d) {
135
+ case 1:
136
+ output_buf[index_out++] = theA;
137
+ case 2:
138
+ theA = theB;
139
+ if (theA == '\'' || theA == '"') {
140
+ for (;;) {
141
+ output_buf[index_out++] = theA;
142
+ theA = get();
143
+ if (theA == theB) {
144
+ break;
145
+ }
146
+ if (theA == '\\') {
147
+ output_buf[index_out++] = theA;
148
+ theA = get();
149
+ }
150
+ if (theA == 0) {
151
+ throw("!Unterminated string literal");
152
+ }
153
+ }
154
+ }
155
+ case 3:
156
+ theB = next();
157
+ if (theB == '/' && (theA == '(' || theA == ',' || theA == '=' ||
158
+ theA == ':' || theA == '[' || theA == '!' ||
159
+ theA == '&' || theA == '|' || theA == '?' ||
160
+ theA == '{' || theA == '}' || theA == ';' ||
161
+ theA == '\n')) {
162
+ output_buf[index_out++] = theA;
163
+ output_buf[index_out++] = theB;
164
+ for (;;) {
165
+ theA = get();
166
+ if (theA == '/') {
167
+ break;
168
+ }
169
+ if (theA =='\\') {
170
+ output_buf[index_out++] = theA;
171
+ theA = get();
172
+ }
173
+ if (theA == 0) {
174
+ throw("!Unterminated Regular Expression literal");
175
+ }
176
+ output_buf[index_out++] = theA;
177
+ }
178
+ theB = next();
179
+ }
180
+ }
181
+ }
182
+
183
+
184
+ /* minify -- Copy the input to the output, deleting the characters which are
185
+ insignificant to JavaScript. Comments will be removed. Tabs will be
186
+ replaced with spaces. Carriage returns will be replaced with linefeeds.
187
+ Most spaces and linefeeds will be removed.
188
+ */
189
+
190
+ char* Jsmin::minify(char *original)
191
+ {
192
+ input_buf = original;
193
+ index_in = 0;
194
+ index_out = 0;
195
+
196
+ if (output_buf != NULL) {
197
+ free(output_buf);
198
+ output_buf = NULL;
199
+ }
200
+
201
+ output_buf = (char *)malloc(sizeof(char) * strlen(original));
202
+
203
+ theA = '\n';
204
+ action(3);
205
+ while (theA != 0) {
206
+ switch (theA) {
207
+ case ' ':
208
+ if (isAlphanum(theB)) {
209
+ action(1);
210
+ } else {
211
+ action(2);
212
+ }
213
+ break;
214
+ case '\n':
215
+ switch (theB) {
216
+ case '{':
217
+ case '[':
218
+ case '(':
219
+ case '+':
220
+ case '-':
221
+ action(1);
222
+ break;
223
+ case ' ':
224
+ action(3);
225
+ break;
226
+ default:
227
+ if (isAlphanum(theB)) {
228
+ action(1);
229
+ } else {
230
+ action(2);
231
+ }
232
+ }
233
+ break;
234
+ default:
235
+ switch (theB) {
236
+ case ' ':
237
+ if (isAlphanum(theA)) {
238
+ action(1);
239
+ break;
240
+ }
241
+ action(3);
242
+ break;
243
+ case '\n':
244
+ switch (theA) {
245
+ case '}':
246
+ case ']':
247
+ case ')':
248
+ case '+':
249
+ case '-':
250
+ case '"':
251
+ case '\'':
252
+ action(1);
253
+ break;
254
+ default:
255
+ if (isAlphanum(theA)) {
256
+ action(1);
257
+ } else {
258
+ action(3);
259
+ }
260
+ }
261
+ break;
262
+ default:
263
+ action(1);
264
+ break;
265
+ }
266
+ }
267
+ }
268
+ output_buf[index_out] = 0;
269
+ return output_buf;
270
+ }
271
+
272
+ extern "C" {
273
+ char* minify(char *in)
274
+ {
275
+ char *out;
276
+ Jsmin *m = new Jsmin();
277
+ try {
278
+ out = m->minify(in);
279
+ }
280
+ catch (char const *e) {
281
+ out = (char*) e;
282
+ }
283
+ delete(m);
284
+ return out;
285
+ }
286
+ }
data/ext/jsmin.h ADDED
@@ -0,0 +1,25 @@
1
+ #ifndef JSMIN_H
2
+ #define JSMIN_H
3
+
4
+ class Jsmin
5
+ {
6
+ public:
7
+ Jsmin();
8
+ char* minify(char *);
9
+
10
+ private:
11
+ int theA;
12
+ int theB;
13
+ int theLookahead;
14
+ int index_in;
15
+ int index_out;
16
+ char *input_buf;
17
+ char *output_buf;
18
+
19
+ int isAlphanum(int c);
20
+ int get();
21
+ int peek();
22
+ int next();
23
+ void action(int d);
24
+ };
25
+ #endif
data/lib/jsmin.rb ADDED
@@ -0,0 +1,22 @@
1
+ require 'ffi'
2
+
3
+ module Jsmin
4
+ extend FFI::Library
5
+ ffi_lib File.dirname(__FILE__) + '/libjsmin.so.0'
6
+ attach_function :minify, [:string], :string
7
+
8
+ class ParseException < 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
+ def process!(buf)
16
+ res = minify(buf)
17
+ if res[0..0] == '!'
18
+ raise ParseException.new(res.tr('!', ''))
19
+ end
20
+ res
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: romanbsd-jsmin-ffi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Roman Shterenzon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-20 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: ffi
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.3.5
24
+ version:
25
+ description: Very fast jsmin implementation using FFI
26
+ email: romanbsd@yahoo.com
27
+ executables: []
28
+
29
+ extensions:
30
+ - ext/extconf.rb
31
+ extra_rdoc_files:
32
+ - README.textile
33
+ files:
34
+ - README.textile
35
+ - Rakefile
36
+ - VERSION
37
+ - ext/Makefile
38
+ - ext/extconf.rb
39
+ - ext/jsmin.cpp
40
+ - ext/jsmin.h
41
+ - lib/jsmin.rb
42
+ has_rdoc: false
43
+ homepage: http://github.com/romanbsd/jsmin-ffi
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --charset=UTF-8
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements: []
62
+
63
+ rubyforge_project:
64
+ rubygems_version: 1.2.0
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: Very fast jsmin implementation using FFI
68
+ test_files: []
69
+