jeanny 0.8

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