jeanny 0.8

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,8 @@
1
+ Jeanny GEM
2
+ ---------------------
3
+
4
+ Usage:
5
+
6
+ require 'jeanny'
7
+
8
+ # blah blah blah
@@ -0,0 +1,341 @@
1
+
2
+ module Jeanny
3
+
4
+ # Класс который выполнят всю основную работу.
5
+ # Парсит и заменяет классы, сохраняет и сравнивает их.
6
+ class Engine
7
+
8
+ attr_reader :classes
9
+
10
+ def initialize
11
+ @classes = Dictionary.new
12
+ end
13
+
14
+ # Метод ищет имена классов, в переданном ему тексте
15
+ def analyze file_meat
16
+
17
+ fail TypeError, "передан неверный аргумент (Jeanny::Engine.analyze)" if file_meat.empty?
18
+
19
+ # Удаляем все экспрешены и удаляем все что в простых и фигурных скобках
20
+ file_meat = file_meat.remove_expressions.gsub(/\{.*?\}/m , '{}').gsub(/\(.*?\)/, '()')
21
+
22
+ short_words = generate_short_words
23
+
24
+ # Находим имена классов
25
+ file_meat.gsub(/\.([^\.,\{\} :#\[\]\*\n\s\/]+)/) do |match|
26
+ # Если найденная строка соответствует маске и класс еще не был добавлен — добавляем его
27
+ @classes[$1] = short_words.shift if match =~ /^\.([a-z]-.+)$/ and not(@classes.include? $1 )
28
+ end
29
+
30
+ fail JeannyClassesNotFound, "похоже, что в анализируемом файле нет классов подходящих по условию" if @classes.empty?
31
+
32
+ # @classes.sort!
33
+ @classes
34
+
35
+ end
36
+
37
+ # Метод сравниваеи найденные классы с переданными в аргументе saved_classes
38
+ # и возвращает имена элементво которых нет в saved_classes
39
+ def compare_with saved_classes
40
+
41
+ return if saved_classes.nil? or saved_classes.empty?
42
+
43
+ saved_classes = Dictionary.new saved_classes
44
+
45
+ # находим новые классы
46
+ new_classes = ((saved_classes.keys | @classes.keys) - saved_classes.keys)
47
+
48
+ @classes = saved_classes
49
+
50
+ # генерируем короткие имена и удаляем из них уже используемые
51
+ short_words = generate_short_words - saved_classes.values
52
+ new_classes.each do |class_name|
53
+ @classes[class_name] = short_words.shift
54
+ end
55
+
56
+ # @classes.sort!
57
+
58
+ new_classes
59
+
60
+ end
61
+
62
+ # Метод для замены классов
63
+ def replace data, type
64
+
65
+ fail "Тип блока не понятный" unless [:js, :css, :html, :plain].include? type
66
+ fail "nil Ololo" if data.nil?
67
+
68
+ code = case type
69
+ when :js then JSCode
70
+ when :css then CSSCode
71
+ when :html then HTMLCode
72
+ when :plain then PlainCode
73
+ end
74
+
75
+ @classes.sort!
76
+
77
+ code.new(data).replace @classes
78
+
79
+ end
80
+
81
+ private
82
+
83
+ # Метод генерирует и возращает массив коротких имен.
84
+ # По умолчанию генерируется 38471 имя. Если надо больше, добавить — легко
85
+ def generate_short_words again = false
86
+
87
+ short_words = []
88
+
89
+ %w(a aa a0 a_ a- aaa a00 a0a aa0 aa_ a_a aa- a-a a0_ a0- a_0 a-0).each do |name|
90
+ max = name.length + 1
91
+ while name.length < max
92
+ short_words << name
93
+ name = name.next
94
+ end
95
+ end
96
+
97
+ short_words
98
+
99
+ end
100
+
101
+ end
102
+
103
+ # Класс-попытка реализовать что нибудь похожее на упорядоченный хэш
104
+ class Dictionary
105
+
106
+ include Enumerable
107
+
108
+ attr_reader :keys, :values
109
+
110
+ def initialize hash = { }
111
+
112
+ @keys = [ ]
113
+ @values = [ ]
114
+
115
+ hash.each_pair { |key, val| append key, val } if hash.kind_of? Hash
116
+ hash.each { |item| append item[0], item[1] } if hash.kind_of? Array
117
+
118
+ end
119
+
120
+ def [](key)
121
+ if include? key
122
+ @values[@keys.index(key)]
123
+ else
124
+ nil
125
+ end
126
+ end
127
+
128
+ def []=(key, value)
129
+ if include? key
130
+ @values[@keys.index(key)] = value
131
+ else
132
+ append key, value
133
+ end
134
+ end
135
+
136
+ def append key, value
137
+ @keys << key
138
+ @values << value
139
+ end
140
+
141
+ def include? class_name
142
+ @keys.include? class_name
143
+ end
144
+
145
+ alias :has_key? include?
146
+
147
+ def empty?
148
+ @keys.empty?
149
+ end
150
+
151
+ def each
152
+ @keys.length.times do |i|
153
+ yield @keys[i], @values[i]
154
+ end
155
+ end
156
+
157
+ def sort!
158
+ @keys.map { |x| [x, @values[@keys.index(x)]] }.sort_by { |x| x[0].length }.reverse.each_with_index do |x, i|
159
+ @keys[i] = x[0]
160
+ @values[i] = x[1]
161
+ end
162
+ end
163
+
164
+ def select_keys_if &block
165
+ array = []
166
+ @keys.length.times do |i|
167
+ need_append = yield @keys[i], @values[i]
168
+ array << @keys[i] if need_append
169
+ end
170
+ array
171
+ end
172
+
173
+ def length
174
+ @keys.length
175
+ end
176
+
177
+ def last
178
+ unless @keys.empty?
179
+ [@keys.last, @values.last]
180
+ end
181
+ end
182
+
183
+ def to_s
184
+ each do |key, val|
185
+ puts key.ljust(40) + val
186
+ end
187
+ end
188
+
189
+ def to_a
190
+ @keys.map { |x| [x, @values[@keys.index(x)]] }
191
+ end
192
+
193
+ end
194
+
195
+ class Code
196
+
197
+ attr_reader :code
198
+
199
+ def initialize code
200
+ @code = code
201
+ end
202
+
203
+ def replace classes
204
+
205
+ end
206
+
207
+ end
208
+
209
+ class JSCode < Code
210
+
211
+ def replace classes
212
+
213
+ # Находим все строки и регулярные выражения
214
+ @code.gsub(/(("|'|\/)((\\\2|.)*?)\2)/m) do |string|
215
+
216
+ string_before, string_after = $3, $3
217
+
218
+ # Проходимся по всем классам
219
+ classes.each do |full_name, short_name|
220
+
221
+ # И заменяем старый класс, на новый
222
+ string_after = string_after.gsub(full_name, short_name)
223
+ end
224
+
225
+ string.gsub(string_before, string_after.gsub(/(\\+)("|')/, "\\1\\1\\2"))
226
+
227
+ end
228
+
229
+ end
230
+
231
+ end
232
+
233
+ class CSSCode < Code
234
+
235
+ def replace classes
236
+
237
+ # Вырезаем экспрешены
238
+ expression_list = []
239
+ @code.replace_expressions! do |expression|
240
+ # и заменяем в них классы как в js
241
+ expression_list << JSCode.new(expression).replace(classes)
242
+ "_ololo_#{expression_list.length}_ololo_"
243
+ end
244
+
245
+ # Вставляем экспрешены с замененными классами обратно
246
+ expression_list.each_with_index do |expression, index|
247
+ @code.gsub! /_ololo_#{index + 1}_ololo_/, expression
248
+ end
249
+
250
+ @code.gsub!(/\[class\^=(.*?)\]/) do |class_name|
251
+ if classes.include? $1
252
+ class_name.gsub $1, classes[$1]
253
+ else
254
+ class_name
255
+ end
256
+ end
257
+
258
+ # Случайная строка
259
+ unique_string = Time.now.object_id.to_s
260
+
261
+ # Проходимся по классам
262
+ classes.each do |full_name, short_name|
263
+
264
+ # Заменяем старое имя класса на новое, плюс случайное число,
265
+ # чтобы знать что этот класс мы уже изменяли
266
+ # TODO: Может это нахрен не надо?
267
+ @code = @code.gsub(/\.#{full_name}(?=[^-\w])/, ".#{unique_string}#{short_name}")
268
+ end
269
+
270
+ # После замены имен классов, случайное число уже не нужно,
271
+ # так что удаляем его, и возвращаем css с замененными значениями
272
+ @code.gsub(unique_string, '')
273
+
274
+ end
275
+
276
+ end
277
+
278
+ class HTMLCode < Code
279
+
280
+ def replace classes
281
+
282
+ # Заменяем классы во встроенных стилях
283
+ @code.gsub!(/<style[^>]*?>(.*?)<\s*\/\s*style\s*>/mi) do |style|
284
+ style.gsub($1, CSSCode.new($1).replace(classes))
285
+ end
286
+
287
+ # Заменяем классы во встроенных скриптах
288
+ @code.gsub!(/<script[^>]*?>(.*?)<\s*\/\s*script\s*>/mi) do |script|
289
+ script.gsub($1, JSCode.new($1).replace(classes))
290
+ end
291
+
292
+ # Находим аттрибуты с именем "class"
293
+ # TODO: Надо находить не просто "class=blablabl", а искать
294
+ # именно теги с аттрибутом "class"
295
+ @code.gsub!(/class\s*=\s*('|")(.*?)\1/) do |match|
296
+
297
+ # берем то что в кавычках и разбиваем по пробелам
298
+ match = $2.split(' ')
299
+
300
+ # проходимся по получившемуся массиву
301
+ match.map! do |class_name|
302
+
303
+ # удаляем проблелы по бокам
304
+ class_name = class_name.strip
305
+
306
+ # и если в нашем списке замены есть такой класс заменяем на новое значение
307
+ if classes.has_key? class_name
308
+ classes[class_name]
309
+ else
310
+ class_name
311
+ end
312
+ # elsif class_name.eql? 'g-js'
313
+ # class_name
314
+ # end
315
+
316
+ end.delete_if { |class_name| class_name.nil? or class_name.empty? }
317
+
318
+ unless match.empty?
319
+ "class=\"#{match.join(' ')}\""
320
+ else
321
+ ''
322
+ # puts match
323
+ # match
324
+ end
325
+
326
+ end
327
+
328
+ # Находим тэги с аттрибутами в которых может быть js
329
+ @code.gsub(/<[^>]*?(onload|onunload|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onfocus|onblur|onkeypress|onkeydown|onkeyup|onsubmit|onreset|onselect|onchange)\s*=\s*("|')((\\\2|.)*?)\2[^>]*?>/mi) do |tag|
330
+ tag.gsub($3, JSCode.new($3.gsub(/\\-/ , '-')).replace(classes))
331
+ end
332
+
333
+ end
334
+
335
+ end
336
+
337
+ class PlainCode < Code
338
+
339
+ end
340
+
341
+ end
@@ -0,0 +1,179 @@
1
+ class Module
2
+
3
+ # Проверка наличия метода
4
+ def jeanny_extension(method)
5
+ if method_defined?(method)
6
+ $stderr.puts "WARNING: Possible conflict with jeanny extension: #{self}##{method} already exists"
7
+ else
8
+ yield
9
+ end
10
+ end
11
+
12
+ end
13
+
14
+ class File
15
+
16
+ jeanny_extension('open_file') do
17
+ # Метод для чтения файла
18
+ def self.open_file file
19
+ # Если файл существует
20
+ if exists?(expand_path(file))
21
+ # Открываем, читаем, объединяем, возвращаем содержимое
22
+ # open(expand_path(file), 'r').readlines.join
23
+ open(expand_path(file), 'r').read
24
+ else
25
+ # Возвращаем пустую строку
26
+ raise Jeanny::JeannyFileNotFound, "Файл не найден: #{expand_path(file)}"
27
+ end
28
+ end
29
+ end
30
+
31
+ jeanny_extension('open_file') do
32
+ # Метод для сохранения файла
33
+ def self.save_file file, data, prefix = ''
34
+ # Если префикс не пустой, добавляем его к имени файла
35
+ file = "#{dirname(expand_path(file))}/#{prefix}#{basename(file)}" unless prefix.empty?
36
+ # Открываем файл
37
+ open(file, 'w') do |file|
38
+ # Помещаем данные
39
+ file.puts data
40
+ end
41
+ end
42
+ end
43
+
44
+ jeanny_extension('list') do
45
+ def self.list path
46
+
47
+ file_list = []
48
+ file_path = [path].flatten.map do |item|
49
+ expand_path item
50
+ end
51
+
52
+ file_path.each do |file|
53
+
54
+ list_item = Dir[file]
55
+ file_list << list_item
56
+
57
+ if block_given?
58
+ unless list_item.empty?
59
+ list_item.each { |x| yield x, 0 }
60
+ else
61
+ yield file, 1
62
+ end
63
+ end
64
+
65
+ end
66
+
67
+ file_list.flatten
68
+
69
+ end
70
+ end
71
+
72
+ end
73
+
74
+ class String
75
+
76
+ jeanny_extension('colorize') do
77
+ # Функция для подсвечивания строки с помошью ANSI кодов...
78
+ def colorize(color_code)
79
+ unless PLATFORM =~ /win32/
80
+ "#{color_code}#{self}\e[0m"
81
+ else
82
+ self
83
+ end
84
+ end
85
+ end
86
+
87
+ jeanny_extension('red') do
88
+ # ... красным
89
+ def red; colorize("\e[31m"); end
90
+ end
91
+
92
+ jeanny_extension('green') do
93
+ # ... зеленым цветом
94
+ def green; colorize("\e[32m"); end
95
+ end
96
+
97
+ jeanny_extension('yellow') do
98
+ # ... и желтым
99
+ def yellow; colorize("\e[33m"); end
100
+ end
101
+
102
+ jeanny_extension('each_expression') do
103
+ def each_expression &block
104
+
105
+ expression_list = []
106
+ code, index, length = self.dup, 0, self.length
107
+
108
+ while code[index, length].include? 'expression(' do
109
+ brake = 0
110
+ start = code[index, length].index 'expression('
111
+ block = code[index + start, length]
112
+
113
+ block.length.times do |i|
114
+ char = block[i, 1]
115
+ if char =~ /\(|\)/
116
+ brake = brake + 1 if char.eql? '('
117
+ brake = brake - 1 if char.eql? ')'
118
+
119
+ if brake.zero?
120
+ brake = block[0, i + 1]
121
+ break
122
+ end
123
+ end
124
+ end
125
+
126
+ index = index + start + brake.length
127
+ expression_list << brake
128
+
129
+ yield brake if block_given?
130
+
131
+ end
132
+
133
+ expression_list
134
+
135
+ end
136
+ end
137
+
138
+ jeanny_extension 'replace_expressions' do
139
+ def replace_expressions replace_string = '', &block
140
+ code = self.dup
141
+ self.each_expression do |expression|
142
+ if block_given?
143
+ edoc = yield expression
144
+ code.gsub!(expression, edoc)
145
+ else
146
+ code.gsub!(expression, replace_string)
147
+ end
148
+ end
149
+
150
+ code
151
+ end
152
+ end
153
+
154
+ jeanny_extension 'replace_expressions!' do
155
+ def replace_expressions! replace_string = '', &block
156
+ replace replace_expressions(replace_string, &block)
157
+ end
158
+ end
159
+
160
+ jeanny_extension 'remove_expressions' do
161
+ def remove_expressions
162
+ replace_expressions ''
163
+ end
164
+ end
165
+
166
+ jeanny_extension 'remove_expressions!' do
167
+ def remove_expressions!
168
+ replace replace_expressions ''
169
+ end
170
+ end
171
+
172
+ end
173
+
174
+ module Jeanny
175
+
176
+ %w(FileNotFound CompareFileFormatError ClassesNotFound).each { |error| eval "class Jeanny#{error} < RuntimeError; end" }
177
+ # %w(CompareFileNotFound SaveError).each { |error| eval "class Jeanny#{error} < SystemCallError; end" }
178
+
179
+ end
@@ -0,0 +1,234 @@
1
+ require 'singleton'
2
+
3
+ module Jeanny
4
+
5
+ module Sugar
6
+
7
+ # Singleton класс который взаимодействует c Jeanny
8
+ class BridgeToJeanny
9
+
10
+ include Singleton
11
+
12
+ def initialize
13
+ # refresh
14
+ end
15
+
16
+ def analyze(path, args = {})
17
+
18
+ refresh
19
+
20
+ begin
21
+
22
+ file_list = File.list path
23
+
24
+ fail JeannyFileNotFound, "файлы для анализа не найдены (Jeanny::Engine.analyze)" if file_list.empty?
25
+
26
+ file_meat = ''
27
+ file_list.each do |file|
28
+ file_meat = file_meat + File.open_file(file)
29
+ end
30
+
31
+ if @engine.analyze file_meat
32
+ @canbe[:save], @canbe[:process] = true, true
33
+ @canbe[:analyze] = false
34
+ end
35
+
36
+ rescue StandardError => e
37
+ $stderr.puts "Ошибка: ".red + e.message
38
+ exit 1
39
+ end
40
+
41
+ @compare_file = (args[:compare_with] or '')
42
+
43
+ # Завершаем метод если сравнивать ни с чем не надо
44
+ return true if @compare_file.empty?
45
+
46
+ # Если файл не найден, спрашиваем у юзернейма, как быть дальше,
47
+ # продолжать без сравнения или прекратить работу.
48
+ unless File.exists? @compare_file
49
+ puts "Файл с сохраненными классами не найден. Продолжаем без сравнения.".yellow
50
+ return true
51
+ end
52
+
53
+ saved_classes = []
54
+
55
+ begin
56
+ # Открываем файл
57
+ raw_file = File.open_file @compare_file
58
+ raw_data = raw_file.split "\n"
59
+
60
+ # ... и читаем структиуру
61
+ raw_data.map do |line|
62
+ line.split(':').map do |hash|
63
+ hash.strip
64
+ end
65
+ end.each_with_index do |item, index|
66
+ if item.length != 2 or item[1].empty?
67
+ fail JeannyCompareFileFormatError, "Какая то ерунда с одим (а может больше) классом. Можно пропустить, но хрен его знает что дальше будет…\n" + "файл: #{file}, строка: #{index}\n#{raw_data[index]}".red
68
+ else
69
+ saved_classes << [item[0], item[1]]
70
+ end
71
+ end
72
+ rescue Exception => e
73
+ $stderr.puts e.message
74
+ exit 1
75
+ end
76
+
77
+ # Сравниваем
78
+ new_classes = @engine.compare_with saved_classes
79
+
80
+ unless new_classes.nil? or new_classes.empty?
81
+ puts 'Новые классы: '
82
+ new_classes.each do |class_name|
83
+ puts " #{class_name.ljust(40, '.')}#{@engine.classes[class_name].green}"
84
+ end
85
+ end
86
+
87
+ true
88
+
89
+ end
90
+
91
+ def save file = ''
92
+
93
+ fail RuntimeError, 'на данном этапе нельзя вызывать сохранение классов' unless canbe[:save]
94
+
95
+ file = file.empty? ? @compare_file : file
96
+
97
+ File.open(File.expand_path(file), 'w') do |f|
98
+ @engine.classes.each do |key, val|
99
+ f.puts "#{key}: #{val.rjust(40 - key.length)}"
100
+ end
101
+ end unless @compare_file.empty? and file.empty?
102
+
103
+ end
104
+
105
+ def save_to file
106
+ save file
107
+ end
108
+
109
+ def group type, args = {}, &block
110
+
111
+ begin
112
+
113
+ fail "We can`t process here..." unless @canbe[:process]
114
+ fail "Блоки process не должны быть рекурсивными" if @process_block_start
115
+ fail "Не передан блок" unless block_given?
116
+
117
+ @canbe[:process] = false
118
+ @canbe[:replace] = true
119
+
120
+ @process_block_start = true
121
+ @process_block = []
122
+
123
+ yield block
124
+
125
+ @process_block_start = false
126
+
127
+ @canbe[:replace] = false
128
+ @canbe[:process] = true
129
+
130
+ rescue Exception => e
131
+ $stderr.puts "Ошибка: ".red + e.message
132
+ exit 1
133
+ end
134
+
135
+ @process_block.each do |replace|
136
+ File.list replace[:in] do |file, status|
137
+
138
+ # next unless status.zero?
139
+ unless status.zero?
140
+ puts file.red
141
+ next
142
+ end
143
+
144
+ exclude = false
145
+ replace[:ex].each do |exclude_rule|
146
+ if exclude_rule.kind_of? Regexp
147
+ exclude = file =~ exclude_rule
148
+ else
149
+ exclude = file.include? exclude_rule
150
+ end
151
+ break if exclude
152
+ end
153
+
154
+ # next if exclude
155
+ if exclude
156
+ puts file.yellow
157
+ next
158
+ end
159
+
160
+ begin
161
+ data = File.open_file file
162
+ data = @engine.replace data, type
163
+
164
+ File.save_file file, data
165
+
166
+ puts file.green
167
+ rescue Exception => e
168
+ puts e.message + "\n#{$@}"
169
+ exit 1
170
+ end
171
+
172
+ end
173
+ end
174
+
175
+ end
176
+
177
+ def replace args = { }
178
+
179
+ fail "We can`t replace here..." unless @canbe[:replace]
180
+
181
+ struct = { :in => [], :ex => [], :prefix => '' }
182
+
183
+ struct[:in] = ([args[:in]] | [args[:include]]).flatten.delete_if { |item| item.nil? or item.empty? }
184
+ struct[:ex] = ([args[:ex]] | [args[:exclude]]).flatten.delete_if { |item| item.nil? or item.empty? }
185
+
186
+ struct[:prefix] = args[:prefix] unless args[:prefix].nil?
187
+
188
+ @process_block << struct if struct[:in].length > 0
189
+
190
+ end
191
+
192
+ private
193
+
194
+ attr_reader :analyze_file, :compare_file
195
+ attr_reader :engine
196
+ attr_reader :canbe
197
+
198
+ attr_reader :process_block_start
199
+ attr_reader :process_block
200
+
201
+ def refresh
202
+
203
+ @engine = Engine.new
204
+ @file_list, @compare_file = '', ''
205
+ @canbe = { :analyze => true, :save => false, :process => false, :replace => false, :stat => false, :make => false }
206
+
207
+ @process_block_start = false
208
+ @process_block = []
209
+
210
+ end
211
+
212
+ def answer question, yes, no
213
+
214
+ action, answers = '', [yes, no].flatten
215
+ until answers.include? action
216
+ print "#{question} "
217
+ action = gets.chomp!
218
+ end
219
+
220
+ [yes].flatten.include? action
221
+
222
+ end
223
+
224
+ end
225
+
226
+ # Тут перехватываем все несуществующие методы.
227
+ # И те которые используются в DSL отправляем куда надо.
228
+ def method_missing(method, *args, &block)
229
+ BridgeToJeanny.instance.send(method, *args, &block) if BridgeToJeanny.instance.respond_to?(method)
230
+ end
231
+
232
+ end
233
+
234
+ end
data/lib/jeanny.rb ADDED
@@ -0,0 +1,20 @@
1
+
2
+ #
3
+ # jeanny.rb
4
+ # jeanny
5
+ #
6
+ # Created by seriously drunken on 2009-09-16.
7
+ # Copyright 2009 Yandex. All rights reserved.
8
+ #
9
+
10
+ $:.unshift File.dirname(__FILE__) unless $:.include? File.dirname(__FILE__)
11
+
12
+ module Jeanny
13
+
14
+ JEANNY_VERSION = '0.8'
15
+
16
+ end
17
+
18
+ require 'jeanny/extend'
19
+ require 'jeanny/engine'
20
+ require 'jeanny/sugar'
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jeanny
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.8"
5
+ platform: ruby
6
+ authors:
7
+ - gfranco
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-28 00:00:00 +03:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: hello@gfranco.ru
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - README
26
+ - lib/jeanny.rb
27
+ - lib/jeanny/engine.rb
28
+ - lib/jeanny/sugar.rb
29
+ - lib/jeanny/extend.rb
30
+ has_rdoc: true
31
+ homepage: http://github.com/gfranco/jeanny
32
+ licenses: []
33
+
34
+ post_install_message:
35
+ rdoc_options:
36
+ - --inline-source
37
+ - --charset=UTF-8
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: "0"
45
+ version:
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ requirements: []
53
+
54
+ rubyforge_project:
55
+ rubygems_version: 1.3.5
56
+ signing_key:
57
+ specification_version: 3
58
+ summary: Lib for obfuscation css class names
59
+ test_files: []
60
+