deadfire 0.1.0
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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +18 -0
- data/.gitignore +8 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +33 -0
- data/LICENSE.txt +21 -0
- data/README.md +159 -0
- data/Rakefile +10 -0
- data/benchmarks/basic_benchmark.rb +89 -0
- data/benchmarks/input.scss +40 -0
- data/benchmarks/output.css +42 -0
- data/benchmarks/output.css.map +1 -0
- data/benchmarks/profilng.rb +25 -0
- data/benchmarks/tailwind.css +295158 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/test +7 -0
- data/changelog.md +5 -0
- data/deadfire.gemspec +26 -0
- data/lib/deadfire/configuration.rb +24 -0
- data/lib/deadfire/css_buffer.rb +10 -0
- data/lib/deadfire/errors.rb +42 -0
- data/lib/deadfire/parser.rb +380 -0
- data/lib/deadfire/transformers/transformer.rb +17 -0
- data/lib/deadfire/version.rb +5 -0
- data/lib/deadfire.rb +28 -0
- metadata +72 -0
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "deadfire"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/bin/test
ADDED
data/changelog.md
ADDED
data/deadfire.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'lib/deadfire/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "deadfire"
|
5
|
+
spec.version = Deadfire::VERSION
|
6
|
+
spec.authors = ["Haroon Ahmed"]
|
7
|
+
spec.email = ["haroon.ahmed25@gmail.com"]
|
8
|
+
|
9
|
+
spec.summary = "Deadfire - lightweight css preprocessor"
|
10
|
+
spec.homepage = "https://github.com/hahmed/deadfire"
|
11
|
+
spec.license = "MIT"
|
12
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.7")
|
13
|
+
|
14
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
15
|
+
spec.metadata["source_code_uri"] = "https://github.com/hahmed/deadfire"
|
16
|
+
spec.metadata["changelog_uri"] = "https://github.com/hahmed/deadfire/changelog.md"
|
17
|
+
|
18
|
+
# Specify which files should be added to the gem when it is released.
|
19
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
20
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
21
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
22
|
+
end
|
23
|
+
spec.bindir = "exe"
|
24
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
|
+
spec.require_paths = ["lib"]
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Deadfire
|
4
|
+
class Configuration
|
5
|
+
attr_reader :directories, :root_path, :keep_comments
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@directories = []
|
9
|
+
@root_path = ""
|
10
|
+
@keep_comments = true
|
11
|
+
end
|
12
|
+
|
13
|
+
def root_path=(value)
|
14
|
+
unless Dir.exist?(value)
|
15
|
+
raise DirectoryNotFoundError.new("Root not found #{value}")
|
16
|
+
end
|
17
|
+
@root_path = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def keep_comments=(value)
|
21
|
+
@keep_comments = value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Deadfire
|
4
|
+
class DirectoryNotFoundError < StandardError; end
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
class DuplicateImportException < StandardError
|
8
|
+
def initialize(filename = "", lineno = "")
|
9
|
+
msg = if filename
|
10
|
+
"Duplicate import found: `#{filename}` line: #{lineno}"
|
11
|
+
else
|
12
|
+
"Duplicate import."
|
13
|
+
end
|
14
|
+
|
15
|
+
super(msg)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class ImportException < StandardError
|
20
|
+
def initialize(filename = "", lineno = "")
|
21
|
+
msg = if filename
|
22
|
+
"Error importing file: `#{filename}` line: #{lineno}"
|
23
|
+
else
|
24
|
+
"Error importing file."
|
25
|
+
end
|
26
|
+
|
27
|
+
super(msg)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class EarlyApplyException < StandardError
|
32
|
+
def initialize(input = "", lineno = "")
|
33
|
+
msg = if input
|
34
|
+
"Error with input: `#{input}` line: #{lineno}"
|
35
|
+
else
|
36
|
+
"Apply called too early in css. There are no mixins defined."
|
37
|
+
end
|
38
|
+
|
39
|
+
super(msg)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,380 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Deadfire
|
3
|
+
class Parser
|
4
|
+
singleton_class.attr_accessor :cached_mixins
|
5
|
+
self.cached_mixins = Hash.new { |h, k| h[k] = {} }
|
6
|
+
|
7
|
+
singleton_class.attr_accessor :import_path_cache
|
8
|
+
self.import_path_cache = []
|
9
|
+
|
10
|
+
ROOT_SELECTOR = ":root {"
|
11
|
+
OPENING_SELECTOR_PATTERN = /\s*\{/
|
12
|
+
CLOSING_SELECTOR_PATTERN = /\s*\}/
|
13
|
+
NEST_SELECTOR = "&"
|
14
|
+
START_BLOCK_CHAR = "{"
|
15
|
+
END_BLOCK_CHAR = "}"
|
16
|
+
OPENING_SELECTOR_PATTERN_OTHER = /\..*\{/
|
17
|
+
IMPORT_SELECTOR = "@import"
|
18
|
+
CSS_FILE_EXTENSION = ".css"
|
19
|
+
APPLY_SELECTOR = "@apply"
|
20
|
+
NEWLINE = "\n"
|
21
|
+
|
22
|
+
def self.parse(content, options = {})
|
23
|
+
new(content, options).parse
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :output
|
27
|
+
|
28
|
+
def initialize(content, options = {})
|
29
|
+
@content = content
|
30
|
+
@filename = options[:filename]
|
31
|
+
@output = []
|
32
|
+
@imports = []
|
33
|
+
end
|
34
|
+
|
35
|
+
def buffer
|
36
|
+
@buffer ||= CssBuffer.new(@content)
|
37
|
+
end
|
38
|
+
|
39
|
+
class Line
|
40
|
+
attr_accessor :content, :line_number
|
41
|
+
|
42
|
+
def initialize(content, line_number)
|
43
|
+
@content = content
|
44
|
+
@line_number = line_number
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
content
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Root < Line
|
53
|
+
def initialize(content, lineno, buffer)
|
54
|
+
super(content, lineno)
|
55
|
+
@end_tag = false
|
56
|
+
@output_current_line = true
|
57
|
+
@output = []
|
58
|
+
@buffer = buffer
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse
|
62
|
+
line = @content
|
63
|
+
if line.include? ROOT_SELECTOR
|
64
|
+
@output << Line.new(line, @line_number)
|
65
|
+
end
|
66
|
+
|
67
|
+
while !@end_tag && line = @buffer.gets
|
68
|
+
if line =~ OPENING_SELECTOR_PATTERN
|
69
|
+
@output_current_line = false
|
70
|
+
name = extract_mixin_name(line)
|
71
|
+
properties = extract_properties_from_mixin(@buffer, line)
|
72
|
+
Parser.cached_mixins[name] = properties
|
73
|
+
elsif line =~ CLOSING_SELECTOR_PATTERN
|
74
|
+
@end_tag = true
|
75
|
+
end
|
76
|
+
|
77
|
+
@output << Line.new(line, @buffer.lineno) if @output_current_line
|
78
|
+
@output_current_line = true
|
79
|
+
end
|
80
|
+
|
81
|
+
to_s
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_s
|
85
|
+
return "" if @output.size <= 1
|
86
|
+
|
87
|
+
@output.map(&:to_s)
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def extract_mixin_name(line)
|
93
|
+
line.tr("{", "").tr(".", "").tr(":", "").strip
|
94
|
+
end
|
95
|
+
|
96
|
+
def extract_properties_from_mixin(buffer, line)
|
97
|
+
properties = {}
|
98
|
+
line = buffer.gets # skip opening {
|
99
|
+
while line !~ CLOSING_SELECTOR_PATTERN && !buffer.eof?
|
100
|
+
name, value = extract_name_and_values(line)
|
101
|
+
properties[name] = value
|
102
|
+
line = buffer.gets
|
103
|
+
end
|
104
|
+
properties
|
105
|
+
end
|
106
|
+
|
107
|
+
def extract_name_and_values(line)
|
108
|
+
name, value = line.split(":")
|
109
|
+
value = value.gsub(";", "")
|
110
|
+
[name, value].map(&:strip)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class Import < Line
|
115
|
+
attr_accessor :import_path
|
116
|
+
|
117
|
+
def initialize(content, lineno)
|
118
|
+
super
|
119
|
+
@import_path = self.class.resolve_import_path(content, lineno)
|
120
|
+
end
|
121
|
+
|
122
|
+
def parse
|
123
|
+
Parser.new(File.read(import_path), filename: import_path).parse
|
124
|
+
end
|
125
|
+
|
126
|
+
class << self
|
127
|
+
def resolve_import_path(line, lineno = 0)
|
128
|
+
path = normalize_import_path(line)
|
129
|
+
unless path.end_with?(Parser::CSS_FILE_EXTENSION)
|
130
|
+
path += Parser::CSS_FILE_EXTENSION
|
131
|
+
end
|
132
|
+
import_path = File.join(Deadfire.configuration.root_path, path)
|
133
|
+
|
134
|
+
unless File.exist?(import_path)
|
135
|
+
raise Deadfire::ImportException.new(import_path, lineno)
|
136
|
+
end
|
137
|
+
|
138
|
+
import_path
|
139
|
+
end
|
140
|
+
|
141
|
+
def normalize_import_path(line)
|
142
|
+
path = line.split.last
|
143
|
+
path.gsub!("\"", "")
|
144
|
+
path.gsub!("\'", "")
|
145
|
+
path.gsub!(";", "")
|
146
|
+
path
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class Apply < Line
|
152
|
+
def initialize(...)
|
153
|
+
super
|
154
|
+
@current_line = @content.dup
|
155
|
+
@space = " "
|
156
|
+
@space_counter = 0
|
157
|
+
@import_start_tag = "@"
|
158
|
+
@output = []
|
159
|
+
end
|
160
|
+
|
161
|
+
def parse
|
162
|
+
raise Deadfire::EarlyApplyException.new(@content, @lineno) if Parser.cached_mixins.empty?
|
163
|
+
|
164
|
+
@current_line.each_char do |char|
|
165
|
+
break if char == @import_start_tag
|
166
|
+
@space_counter += 1
|
167
|
+
end
|
168
|
+
|
169
|
+
@current_line.split(" ").each do |css|
|
170
|
+
next if css.include?(APPLY_SELECTOR)
|
171
|
+
css.gsub!(";", "")
|
172
|
+
|
173
|
+
fetch_cached_mixin(css).each_pair do |key, value|
|
174
|
+
@output << "#{@space * @space_counter}#{key}: #{value};"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
@output
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
# find css class key/val from hash, otherwise throw because the mixin is not defined
|
183
|
+
def fetch_cached_mixin(key)
|
184
|
+
raise Deadfire::EarlyApplyException.new(key, @lineno) unless Parser.cached_mixins.include?(key)
|
185
|
+
|
186
|
+
Parser.cached_mixins[key]
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class Nesting < Line
|
191
|
+
attr_accessor :block_names
|
192
|
+
|
193
|
+
def initialize(content, lineno, buffer, output)
|
194
|
+
super(content, lineno)
|
195
|
+
@buffer = buffer
|
196
|
+
@output = output
|
197
|
+
@block_names = []
|
198
|
+
@nested_level = 0
|
199
|
+
end
|
200
|
+
|
201
|
+
def parse
|
202
|
+
line = content.dup.strip
|
203
|
+
@block_names << find_block_name(@output, @lineno)
|
204
|
+
tmp = []
|
205
|
+
|
206
|
+
add_end_block_when_no_end_block_on_prev_line(arr: tmp)
|
207
|
+
while @nested_level > 0 || !@buffer.eof?
|
208
|
+
spaces = calculate_spaces_to_add(line)
|
209
|
+
if line.start_with?(NEST_SELECTOR)
|
210
|
+
add_end_block_when_no_end_block_on_prev_line(arr: tmp) if @nested_level > 0
|
211
|
+
add_selector_to_block_name(line)
|
212
|
+
@nested_level += 1
|
213
|
+
tmp << rewrite_line(spaces, line, @block_names[0...-1].join(" "))
|
214
|
+
remove_last_block_name_entry if line.end_with?(END_BLOCK_CHAR)
|
215
|
+
else
|
216
|
+
remove_last_block_name_entry if line.end_with?(END_BLOCK_CHAR)
|
217
|
+
tmp << "#{spaces}#{line.lstrip}"
|
218
|
+
end
|
219
|
+
|
220
|
+
line = @buffer.gets
|
221
|
+
|
222
|
+
if line.nil? || @buffer.eof? || line.empty?
|
223
|
+
break
|
224
|
+
else
|
225
|
+
line.strip!
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
tmp.pop if tmp[-1] == END_BLOCK_CHAR
|
230
|
+
tmp.join("\n")
|
231
|
+
end
|
232
|
+
|
233
|
+
private
|
234
|
+
|
235
|
+
def remove_last_block_name_entry
|
236
|
+
@nested_level -= 1
|
237
|
+
@block_names.pop
|
238
|
+
end
|
239
|
+
|
240
|
+
def add_selector_to_block_name(line)
|
241
|
+
line = extract_selector(line)
|
242
|
+
line = line_without_nested_block(line)
|
243
|
+
@block_names << line unless @block_names.include?(line)
|
244
|
+
end
|
245
|
+
|
246
|
+
def add_end_block_when_no_end_block_on_prev_line(arr: @output)
|
247
|
+
unless arr[-1]&.strip&.end_with?("}")
|
248
|
+
arr << "}"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def calculate_spaces_to_add(line)
|
253
|
+
unless line =~ OPENING_SELECTOR_PATTERN || line =~ CLOSING_SELECTOR_PATTERN
|
254
|
+
" "
|
255
|
+
else
|
256
|
+
""
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def extract_selector(line)
|
261
|
+
line.split(START_BLOCK_CHAR).first.strip
|
262
|
+
end
|
263
|
+
|
264
|
+
def line_without_nested_block(line)
|
265
|
+
line.split(NEST_SELECTOR).last.strip
|
266
|
+
end
|
267
|
+
|
268
|
+
def rewrite_line(spaces, line, selector)
|
269
|
+
case number_of_selectors_in(line)
|
270
|
+
when 0
|
271
|
+
line
|
272
|
+
when 1
|
273
|
+
"#{spaces}#{line.lstrip.gsub("&", selector)}"
|
274
|
+
else
|
275
|
+
line.strip.each_char.map do |s|
|
276
|
+
if s == NEST_SELECTOR
|
277
|
+
selector
|
278
|
+
else
|
279
|
+
s
|
280
|
+
end
|
281
|
+
end.join
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def number_of_selectors_in(line)
|
286
|
+
line.split.count do |s|
|
287
|
+
# break if s == "{" # early exit, no need to read every char
|
288
|
+
s.start_with?(NEST_SELECTOR)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def find_block_name(output, lineno = nil)
|
293
|
+
lineno = output.size unless lineno
|
294
|
+
if lineno < 0
|
295
|
+
raise "Cannot find block name"
|
296
|
+
end
|
297
|
+
|
298
|
+
line = output[lineno]
|
299
|
+
|
300
|
+
if line.to_s =~ OPENING_SELECTOR_PATTERN
|
301
|
+
extract_selector(line)
|
302
|
+
else
|
303
|
+
find_block_name(output, lineno - 1)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def parse
|
309
|
+
while ! buffer.eof?
|
310
|
+
process_line(buffer.readline)
|
311
|
+
end
|
312
|
+
|
313
|
+
@output << NEWLINE
|
314
|
+
|
315
|
+
@output.join
|
316
|
+
end
|
317
|
+
|
318
|
+
private
|
319
|
+
|
320
|
+
# this method returns void, and modifies the output array directly
|
321
|
+
def process_line(line)
|
322
|
+
if line.strip.start_with?("/*")
|
323
|
+
handle_comment(line)
|
324
|
+
elsif line.strip.start_with?("@import")
|
325
|
+
handle_import(line)
|
326
|
+
elsif line.strip.start_with?(":root {")
|
327
|
+
handle_mixins(line)
|
328
|
+
elsif line.strip.start_with?("@apply") # or line.include?("@apply")
|
329
|
+
handle_apply(line)
|
330
|
+
elsif line.strip.start_with?("&")
|
331
|
+
handle_nestings(line)
|
332
|
+
else
|
333
|
+
@output << line
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
def keep_comments?
|
338
|
+
Deadfire.configuration.keep_comments
|
339
|
+
end
|
340
|
+
|
341
|
+
def handle_comment(line)
|
342
|
+
@output << Line.new(line, buffer.lineno) if keep_comments?
|
343
|
+
|
344
|
+
while ! line.include?("*/") && ! buffer.eof?
|
345
|
+
line = buffer.gets
|
346
|
+
@output << Line.new(line, buffer.lineno) if keep_comments?
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def handle_import(line)
|
351
|
+
import = Import.new(line, buffer.lineno)
|
352
|
+
|
353
|
+
if self.class.import_path_cache.include?(import.import_path)
|
354
|
+
raise DuplicateImportException.new(import.import_path, buffer.lineno)
|
355
|
+
end
|
356
|
+
|
357
|
+
self.class.import_path_cache << import.import_path
|
358
|
+
|
359
|
+
# TODO:
|
360
|
+
# - decide on how many levels of imports we want to allow
|
361
|
+
# - make async??
|
362
|
+
@output << import.parse
|
363
|
+
end
|
364
|
+
|
365
|
+
def handle_apply(line)
|
366
|
+
@apply = Apply.new(line, buffer.lineno)
|
367
|
+
@output << @apply.parse.join(NEWLINE)
|
368
|
+
end
|
369
|
+
|
370
|
+
def handle_mixins(line)
|
371
|
+
@root = Root.new(line, buffer.lineno, buffer)
|
372
|
+
@output << @root.parse
|
373
|
+
end
|
374
|
+
|
375
|
+
def handle_nestings(line)
|
376
|
+
nesting = Nesting.new(line, buffer.lineno, buffer, @output)
|
377
|
+
@output << nesting.parse
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
data/lib/deadfire.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "deadfire/css_buffer"
|
4
|
+
require_relative "deadfire/configuration"
|
5
|
+
require_relative "deadfire/errors"
|
6
|
+
require_relative "deadfire/parser"
|
7
|
+
require_relative "deadfire/transformers/transformer"
|
8
|
+
require_relative "deadfire/version"
|
9
|
+
|
10
|
+
module Deadfire
|
11
|
+
class << self
|
12
|
+
def configuration
|
13
|
+
@configuration ||= Configuration.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def reset
|
17
|
+
@configuration = Configuration.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def configure
|
21
|
+
yield(@configuration)
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse(content, options = {})
|
25
|
+
Parser.parse(content, options)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: deadfire
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Haroon Ahmed
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-10-17 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
- haroon.ahmed25@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".github/workflows/ci.yml"
|
21
|
+
- ".gitignore"
|
22
|
+
- CODE_OF_CONDUCT.md
|
23
|
+
- Gemfile
|
24
|
+
- Gemfile.lock
|
25
|
+
- LICENSE.txt
|
26
|
+
- README.md
|
27
|
+
- Rakefile
|
28
|
+
- benchmarks/basic_benchmark.rb
|
29
|
+
- benchmarks/input.scss
|
30
|
+
- benchmarks/output.css
|
31
|
+
- benchmarks/output.css.map
|
32
|
+
- benchmarks/profilng.rb
|
33
|
+
- benchmarks/tailwind.css
|
34
|
+
- bin/console
|
35
|
+
- bin/setup
|
36
|
+
- bin/test
|
37
|
+
- changelog.md
|
38
|
+
- deadfire.gemspec
|
39
|
+
- lib/deadfire.rb
|
40
|
+
- lib/deadfire/configuration.rb
|
41
|
+
- lib/deadfire/css_buffer.rb
|
42
|
+
- lib/deadfire/errors.rb
|
43
|
+
- lib/deadfire/parser.rb
|
44
|
+
- lib/deadfire/transformers/transformer.rb
|
45
|
+
- lib/deadfire/version.rb
|
46
|
+
homepage: https://github.com/hahmed/deadfire
|
47
|
+
licenses:
|
48
|
+
- MIT
|
49
|
+
metadata:
|
50
|
+
homepage_uri: https://github.com/hahmed/deadfire
|
51
|
+
source_code_uri: https://github.com/hahmed/deadfire
|
52
|
+
changelog_uri: https://github.com/hahmed/deadfire/changelog.md
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.7'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
requirements: []
|
68
|
+
rubygems_version: 3.3.7
|
69
|
+
signing_key:
|
70
|
+
specification_version: 4
|
71
|
+
summary: Deadfire - lightweight css preprocessor
|
72
|
+
test_files: []
|