glim 0.1.1
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/bin/glim +1538 -0
- data/lib/cache.rb +66 -0
- data/lib/commands.rb +261 -0
- data/lib/exception.rb +18 -0
- data/lib/liquid_ext.rb +249 -0
- data/lib/local_server.rb +375 -0
- data/lib/log_and_profile.rb +115 -0
- data/lib/version.rb +3 -0
- metadata +178 -0
data/lib/cache.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Glim
|
4
|
+
class Cache
|
5
|
+
CACHE_PATH = '.cache/glim/data.bin'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def load
|
9
|
+
cache
|
10
|
+
end
|
11
|
+
|
12
|
+
def save
|
13
|
+
unless @cache.nil?
|
14
|
+
FileUtils.mkdir_p(File.dirname(CACHE_PATH))
|
15
|
+
open(CACHE_PATH, 'w') do |io|
|
16
|
+
Marshal.dump(cache, io)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def track_updates=(flag)
|
22
|
+
@updates = flag ? {} : nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def updates
|
26
|
+
@updates
|
27
|
+
end
|
28
|
+
|
29
|
+
def merge!(updates)
|
30
|
+
updates.each do |group, paths|
|
31
|
+
(cache[group] ||= {}).merge!(paths)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def getset(path, group = :default)
|
36
|
+
begin
|
37
|
+
mtime = File.stat(path).mtime
|
38
|
+
if record = cache.dig(group, path)
|
39
|
+
if mtime == record['modified']
|
40
|
+
return record['data']
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
record = {
|
45
|
+
'modified' => mtime,
|
46
|
+
'data' => yield,
|
47
|
+
}
|
48
|
+
|
49
|
+
(cache[group] ||= {})[path] = record
|
50
|
+
(@updates[group] ||= {})[path] = record if @updates
|
51
|
+
|
52
|
+
record['data']
|
53
|
+
rescue Errno::ENOENT
|
54
|
+
$log.warn("File does not exist: #{path}")
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def cache
|
62
|
+
@cache ||= open(CACHE_PATH) { |io| Marshal.load(io) } rescue {}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/commands.rb
ADDED
@@ -0,0 +1,261 @@
|
|
1
|
+
module Glim
|
2
|
+
module Commands
|
3
|
+
def self.build(config)
|
4
|
+
output_dir = File.expand_path(config['destination'])
|
5
|
+
files = config.site.files_and_documents.select { |file| file.write? }
|
6
|
+
symlinks = (config.site.symlinks || []).map { |link| [ File.expand_path(File.join(link[:data]['domain'] || '.', link[:name]), output_dir), link[:realpath] ] }.to_h
|
7
|
+
|
8
|
+
output_paths = files.map { |file| file.output_path(output_dir) }
|
9
|
+
output_paths.concat(symlinks.keys)
|
10
|
+
|
11
|
+
delete_files, delete_dirs = items_in_directory(output_dir, skip: config['keep_files'])
|
12
|
+
deleted = delete_items(delete_files, delete_dirs, keep: output_paths)
|
13
|
+
created, updated, warnings, errors = *generate(output_dir, config['jobs'] || 7, files)
|
14
|
+
|
15
|
+
symlinks.each do |dest, path|
|
16
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
17
|
+
begin
|
18
|
+
File.symlink(path, dest)
|
19
|
+
created << dest
|
20
|
+
rescue Errno::EEXIST
|
21
|
+
if File.readlink(dest) != path
|
22
|
+
File.unlink(dest)
|
23
|
+
File.symlink(path, dest)
|
24
|
+
updated << dest
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
[ [ 'Created', created ], [ 'Deleted', deleted ], [ 'Updated', updated ] ].each do |label, files|
|
30
|
+
unless files.empty?
|
31
|
+
STDERR.puts "==> #{label} #{files.count} #{files.count == 1 ? 'File' : 'Files'}"
|
32
|
+
STDERR.puts files.map { |path| Util.relative_path(path, output_dir) }.sort.join(', ')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
unless warnings.empty?
|
37
|
+
STDERR.puts "==> #{warnings.count} #{warnings.count == 1 ? 'Warnings' : 'Warning'}"
|
38
|
+
warnings.each do |message|
|
39
|
+
STDERR.puts message
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
unless errors.empty?
|
44
|
+
STDERR.puts "==> Stopped After #{errors.count} #{errors.count == 1 ? 'Error' : 'Errors'}"
|
45
|
+
errors.each do |arr|
|
46
|
+
arr.each_with_index do |err, i|
|
47
|
+
STDERR.puts err.gsub(/^/, ' '*i)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.clean(config)
|
54
|
+
files, dirs = items_in_directory(File.expand_path(config['destination']), skip: config['keep_files'])
|
55
|
+
|
56
|
+
if config['dry_run']
|
57
|
+
if files.empty?
|
58
|
+
STDOUT.puts "No files to delete"
|
59
|
+
else
|
60
|
+
files.each do |file|
|
61
|
+
STDOUT.puts "Delete #{Util.relative_path(file, File.expand_path(config['source']))}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
else
|
65
|
+
deleted = delete_items(files, dirs)
|
66
|
+
STDOUT.puts "Deleted #{deleted.count} #{deleted.count == 1 ? 'File' : 'Files'}."
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.profile(config)
|
71
|
+
Profiler.enabled = true
|
72
|
+
|
73
|
+
site = Profiler.run("Setting up site") do
|
74
|
+
config.site
|
75
|
+
end
|
76
|
+
|
77
|
+
Profiler.run("Loading cache") do
|
78
|
+
Glim::Cache.load
|
79
|
+
end
|
80
|
+
|
81
|
+
files = []
|
82
|
+
|
83
|
+
Profiler.run("Loading pages") do
|
84
|
+
files.concat(site.files)
|
85
|
+
end
|
86
|
+
|
87
|
+
Profiler.run("Loading collections") do
|
88
|
+
files.concat(site.documents)
|
89
|
+
end
|
90
|
+
|
91
|
+
Profiler.run("Generating virtual pages") do
|
92
|
+
files.concat(site.generated_files)
|
93
|
+
end
|
94
|
+
|
95
|
+
files = files.select { |file| file.frontmatter? }
|
96
|
+
|
97
|
+
Profiler.run("Expanding liquid tags") do
|
98
|
+
files.each { |file| file.content('post-liquid') }
|
99
|
+
end
|
100
|
+
|
101
|
+
Profiler.run("Transforming pages") do
|
102
|
+
files.each { |file| file.content('pre-output') }
|
103
|
+
end
|
104
|
+
|
105
|
+
Profiler.run("Creating final output (layout)") do
|
106
|
+
files.each { |file| file.output }
|
107
|
+
end
|
108
|
+
|
109
|
+
Profiler.enabled = false
|
110
|
+
end
|
111
|
+
|
112
|
+
# ===========
|
113
|
+
# = Private =
|
114
|
+
# ===========
|
115
|
+
|
116
|
+
def self.items_in_directory(dir, skip: [])
|
117
|
+
files, dirs = [], []
|
118
|
+
|
119
|
+
begin
|
120
|
+
Find.find(dir) do |path|
|
121
|
+
next if path == dir
|
122
|
+
Find.prune if skip.include?(File.basename(path))
|
123
|
+
|
124
|
+
if File.file?(path) || File.symlink?(path)
|
125
|
+
files << path
|
126
|
+
elsif File.directory?(path)
|
127
|
+
dirs << path
|
128
|
+
else
|
129
|
+
$log.warn("Unknown entry: #{path}")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
rescue Errno::ENOENT
|
133
|
+
end
|
134
|
+
|
135
|
+
[ files, dirs ]
|
136
|
+
end
|
137
|
+
|
138
|
+
def self.delete_items(files, dirs, keep: [])
|
139
|
+
res = []
|
140
|
+
|
141
|
+
keep_files = Set.new(keep)
|
142
|
+
files.each do |path|
|
143
|
+
unless keep_files.include?(path)
|
144
|
+
begin
|
145
|
+
File.unlink(path)
|
146
|
+
res << path
|
147
|
+
rescue => e
|
148
|
+
$log.error("Error unlinking ‘#{path}’: #{e}\n")
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
dirs.sort.reverse.each do |path|
|
154
|
+
begin
|
155
|
+
Dir.rmdir(path)
|
156
|
+
rescue Errno::ENOTEMPTY => e
|
157
|
+
# Ignore
|
158
|
+
rescue => e
|
159
|
+
$log.error("Error removing directory ‘#{path}’: #{e}\n")
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
res
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.generate(output_dir, number_of_jobs, files)
|
167
|
+
Profiler.run("Creating pages") do
|
168
|
+
if number_of_jobs == 1
|
169
|
+
generate_subset(output_dir, files)
|
170
|
+
else
|
171
|
+
generate_async(output_dir, files.shuffle, number_of_jobs)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.generate_async(output_dir, files, number_of_jobs)
|
177
|
+
total = files.size
|
178
|
+
slices = number_of_jobs.times.map do |i|
|
179
|
+
first = (total * i / number_of_jobs).ceil
|
180
|
+
last = (total * (i+1) / number_of_jobs).ceil
|
181
|
+
files.shift(last-first)
|
182
|
+
end
|
183
|
+
|
184
|
+
Glim::Cache.track_updates = true
|
185
|
+
semaphore = Mutex.new
|
186
|
+
created, updated, warnings, errors = [], [], [], []
|
187
|
+
|
188
|
+
threads = slices.each_with_index.map do |files_slice, i|
|
189
|
+
pipe_rd, pipe_wr = IO.pipe
|
190
|
+
pid = fork do
|
191
|
+
start = Time.now
|
192
|
+
pipe_rd.close
|
193
|
+
created, updated, warnings, errors = *generate_subset(output_dir, files_slice)
|
194
|
+
pipe_wr << Marshal.dump({
|
195
|
+
'cache_updates' => Glim::Cache.updates,
|
196
|
+
'created' => created,
|
197
|
+
'updated' => updated,
|
198
|
+
'warnings' => warnings,
|
199
|
+
'errors' => errors,
|
200
|
+
'duration' => Time.now - start,
|
201
|
+
'id' => i,
|
202
|
+
})
|
203
|
+
pipe_wr.close
|
204
|
+
end
|
205
|
+
|
206
|
+
Process.detach(pid)
|
207
|
+
|
208
|
+
Thread.new do
|
209
|
+
pipe_wr.close
|
210
|
+
res = Marshal.load(pipe_rd)
|
211
|
+
semaphore.synchronize do
|
212
|
+
Glim::Cache.merge!(res['cache_updates'])
|
213
|
+
created += res['created']
|
214
|
+
updated += res['updated']
|
215
|
+
warnings += res['warnings']
|
216
|
+
errors += res['errors']
|
217
|
+
$log.debug("Wrote #{files_slice.size} pages in #{res['duration']} seconds (thread #{res['id']})") if Profiler.enabled
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
threads.each { |thread| thread.join }
|
223
|
+
|
224
|
+
[ created, updated, warnings, errors ]
|
225
|
+
end
|
226
|
+
|
227
|
+
def self.generate_subset(output_dir, files)
|
228
|
+
created, updated, warnings, errors = [], [], [], []
|
229
|
+
|
230
|
+
for file in files do
|
231
|
+
dest = file.output_path(output_dir)
|
232
|
+
file_exists = File.exists?(dest)
|
233
|
+
|
234
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
235
|
+
if file.frontmatter?
|
236
|
+
begin
|
237
|
+
if !file_exists || File.read(dest) != file.output
|
238
|
+
(file_exists ? updated : created) << dest
|
239
|
+
File.unlink(dest) if file_exists
|
240
|
+
File.write(dest, file.output)
|
241
|
+
end
|
242
|
+
warnings.concat(file.warnings.map { |warning| "#{file}: #{warning}" }) unless file.warnings.nil?
|
243
|
+
rescue Glim::Error => e
|
244
|
+
errors << [ "Unable to create output for: #{file}", *e.messages ]
|
245
|
+
break
|
246
|
+
rescue => e
|
247
|
+
errors << [ "Unable to create output for: #{file}", e.to_s, e.backtrace.join("\n") ]
|
248
|
+
break
|
249
|
+
end
|
250
|
+
else
|
251
|
+
unless File.file?(dest) && File.file?(file.path) && File.stat(dest).ino == File.stat(file.path).ino
|
252
|
+
File.unlink(dest) if file_exists
|
253
|
+
File.link(file.path, dest)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
[ created, updated, warnings, errors ]
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
data/lib/exception.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Glim
|
2
|
+
class Error < ::RuntimeError
|
3
|
+
attr_reader :message, :previous
|
4
|
+
|
5
|
+
def initialize(message, previous = nil)
|
6
|
+
@message, @previous = message, previous
|
7
|
+
end
|
8
|
+
|
9
|
+
def messages
|
10
|
+
res = [ @message ]
|
11
|
+
e = self
|
12
|
+
while e.respond_to?(:previous) && (e = e.previous)
|
13
|
+
res << e.message
|
14
|
+
end
|
15
|
+
res
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/liquid_ext.rb
ADDED
@@ -0,0 +1,249 @@
|
|
1
|
+
require 'kramdown'
|
2
|
+
require 'liquid'
|
3
|
+
|
4
|
+
module Glim
|
5
|
+
module LiquidFilters
|
6
|
+
def markdownify(input)
|
7
|
+
Profiler.group('markdownify') do
|
8
|
+
if defined?(MultiMarkdown)
|
9
|
+
MultiMarkdown.new("\n" + input, 'snippet', 'no_metadata').to_html
|
10
|
+
else
|
11
|
+
options = @context['site']['kramdown'].map { |key, value| [ key.to_sym, value ] }.to_h
|
12
|
+
document = Kramdown::Document.new(input, options)
|
13
|
+
@context['warnings'].concat(document.warnings) if options[:show_warnings] && @context['warnings']
|
14
|
+
document.to_html
|
15
|
+
end unless input.nil?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def slugify(input)
|
20
|
+
Util.slugify(input) unless input.nil?
|
21
|
+
end
|
22
|
+
|
23
|
+
def xml_escape(input)
|
24
|
+
input.encode(:xml => :attr).gsub(/\A"|"\z/, '') unless input.nil?
|
25
|
+
end
|
26
|
+
|
27
|
+
def cgi_escape(input)
|
28
|
+
CGI.escape(input) unless input.nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
def absolute_url(path)
|
32
|
+
unless path.nil?
|
33
|
+
site, page = URI(@context['site']['url']), URI(@context['page']['url'])
|
34
|
+
host, port = @context['site']['host'], @context['site']['port']
|
35
|
+
if page.relative? || (site.host == host && site.port == port)
|
36
|
+
site.merge(URI(path)).to_s
|
37
|
+
else
|
38
|
+
page.merge(URI(path)).to_s
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def relative_url(other)
|
44
|
+
helper = lambda do |base, other|
|
45
|
+
base_url, other_url = URI(base), URI(other)
|
46
|
+
if other_url.absolute? && base_url.host == other_url.host
|
47
|
+
other_url.path
|
48
|
+
else
|
49
|
+
other
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
unless other.nil?
|
54
|
+
site, page = URI(@context['site']['url']), URI(@context['page']['url'])
|
55
|
+
host, port = @context['site']['host'], @context['site']['port']
|
56
|
+
if page.relative? || (site.host == host && site.port == port)
|
57
|
+
helper.call(@context['site']['url'], other)
|
58
|
+
else
|
59
|
+
helper.call(@context['page']['url'], other)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def path_to_url(input)
|
65
|
+
if file = Jekyll.sites.last.links[input]
|
66
|
+
file.url
|
67
|
+
else
|
68
|
+
raise Glim::Error.new("path_to_url: No file found for: #{input}")
|
69
|
+
end unless input.nil?
|
70
|
+
end
|
71
|
+
|
72
|
+
def date_to_xmlschema(input)
|
73
|
+
Liquid::Utils.to_date(input).localtime.xmlschema unless input.nil?
|
74
|
+
end
|
75
|
+
|
76
|
+
def date_to_rfc822(input)
|
77
|
+
Liquid::Utils.to_date(input).localtime.rfc822 unless input.nil?
|
78
|
+
end
|
79
|
+
|
80
|
+
def date_to_string(input)
|
81
|
+
Liquid::Utils.to_date(input).localtime.strftime("%d %b %Y") unless input.nil?
|
82
|
+
end
|
83
|
+
|
84
|
+
def date_to_long_string(input)
|
85
|
+
Liquid::Utils.to_date(input).localtime.strftime("%d %B %Y") unless input.nil?
|
86
|
+
end
|
87
|
+
|
88
|
+
def where(input, property, value)
|
89
|
+
if input.respond_to?(:select) && property && value
|
90
|
+
input = input.values if input.is_a?(Hash)
|
91
|
+
input.select { |item| get_property(item, property) == value }
|
92
|
+
else
|
93
|
+
input
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def group_by(input, property)
|
98
|
+
if input.respond_to?(:group_by) && property
|
99
|
+
groups = input.group_by { |item| get_property(item, property) }
|
100
|
+
groups.map { |key, value| { "name" => key, "items" => value, "size" => value.size } }
|
101
|
+
else
|
102
|
+
input
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def group_by_exp(input, variable, expression)
|
107
|
+
if input.respond_to?(:group_by)
|
108
|
+
parsed_expr = Liquid::Variable.new(expression, Liquid::ParseContext.new)
|
109
|
+
@context.stack do
|
110
|
+
groups = input.group_by do |item|
|
111
|
+
@context[variable] = item
|
112
|
+
parsed_expr.render(@context)
|
113
|
+
end
|
114
|
+
groups.map { |key, value| { "name" => key, "items" => value, "size" => value.size } }
|
115
|
+
end
|
116
|
+
else
|
117
|
+
input
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def get_property(obj, property)
|
124
|
+
if obj.respond_to?(:to_liquid)
|
125
|
+
property.to_s.split('.').reduce(obj.to_liquid) do |mem, key|
|
126
|
+
mem[key]
|
127
|
+
end
|
128
|
+
elsif obj.respond_to?(:data)
|
129
|
+
obj.data[property.to_s]
|
130
|
+
else
|
131
|
+
obj[property.to_s]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
module LiquidTags
|
137
|
+
class PostURL < Liquid::Tag
|
138
|
+
def initialize(tag_name, markup, options)
|
139
|
+
super
|
140
|
+
@post_name = markup.strip
|
141
|
+
end
|
142
|
+
|
143
|
+
def render(context)
|
144
|
+
if file = Jekyll.sites.last.post_links[@post_name]
|
145
|
+
file.url
|
146
|
+
else
|
147
|
+
raise Glim::Error.new("post_url: No post found for: #{@post_name}")
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class Link < Liquid::Tag
|
153
|
+
def initialize(tag_name, markup, options)
|
154
|
+
super
|
155
|
+
@relative_path = markup.strip
|
156
|
+
end
|
157
|
+
|
158
|
+
def render(context)
|
159
|
+
if file = Jekyll.sites.last.links[@relative_path]
|
160
|
+
file.url
|
161
|
+
else
|
162
|
+
raise Glim::Error.new("link: No file found for: #{@relative_path}")
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class HighlightBlock < Liquid::Block
|
168
|
+
def initialize(tag_name, markup, tokens)
|
169
|
+
super
|
170
|
+
|
171
|
+
if markup =~ /^([a-zA-Z0-9.+#_-]+)((\s+\w+(=(\w+|"[^"]*"))?)*)\s*$/
|
172
|
+
@language, @options = $1, $2.scan(/(\w+)(?:=(?:(\w+)|"([^"]*)"))?/).map do |key, value, list|
|
173
|
+
[ key.to_sym, list ? list.split : (value || true) ]
|
174
|
+
end.to_h
|
175
|
+
else
|
176
|
+
@language, @options = nil, {}
|
177
|
+
$log.error("Unable to parse highlight tag: #{markup}") unless markup.strip.empty?
|
178
|
+
end
|
179
|
+
|
180
|
+
begin
|
181
|
+
require 'rouge'
|
182
|
+
rescue LoadError => e
|
183
|
+
$log.warn("Unable to load the rouge gem required by the highlight tag: #{e}")
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def render(context)
|
188
|
+
source = super.to_s.gsub(/\A[\r\n]+|[\r\n]+\z/, '')
|
189
|
+
|
190
|
+
if defined?(Rouge)
|
191
|
+
rouge_options = {
|
192
|
+
:line_numbers => @options[:linenos] == true ? 'inline' : @options[:linenos],
|
193
|
+
:wrap => false,
|
194
|
+
:css_class => 'highlight',
|
195
|
+
:gutter_class => 'gutter',
|
196
|
+
:code_class => 'code'
|
197
|
+
}.merge(@options)
|
198
|
+
|
199
|
+
lexer = Rouge::Lexer.find_fancy(@language, source) || Rouge::Lexers::PlainText
|
200
|
+
formatter = Rouge::Formatters::HTMLLegacy.new(rouge_options)
|
201
|
+
source = formatter.format(lexer.lex(source))
|
202
|
+
|
203
|
+
$log.warn("No language specified in highlight tag. Will use #{lexer.class.name} to parse the code.") if @language.nil?
|
204
|
+
end
|
205
|
+
|
206
|
+
code_attributes = @language ? " class=\"language-#{@language.tr('+', '-')}\" data-lang=\"#{@language}\"" : ""
|
207
|
+
"<figure class=\"highlight\"><pre><code#{code_attributes}>#{source.chomp}</code></pre></figure>"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def self.preprocess_template(source)
|
213
|
+
source = source.gsub(/({%-? include )([\w.\/-]+)(.*?)(-?%})/) do
|
214
|
+
prefix, include, variables, suffix = $1, $2, $3, $4
|
215
|
+
unless variables.strip.empty?
|
216
|
+
variables = ', ' + variables.scan(/(\w+)=(.*?)(?=\s)/).map { |key, value| "include_#{key}: #{value}" }.join(', ') + ' '
|
217
|
+
end
|
218
|
+
|
219
|
+
"#{prefix}\"#{include}\"#{variables}#{suffix}"
|
220
|
+
end
|
221
|
+
|
222
|
+
source.gsub!(/({{-? include)\.(.*?}})/) { "#$1_#$2" }
|
223
|
+
source.gsub!(/({%-? .+? include)\.(.*?%})/) { "#$1_#$2" }
|
224
|
+
|
225
|
+
source
|
226
|
+
end
|
227
|
+
|
228
|
+
class LocalFileSystem
|
229
|
+
def initialize(*paths)
|
230
|
+
@paths = paths.reject { |path| path.nil? }
|
231
|
+
end
|
232
|
+
|
233
|
+
def read_template_file(name)
|
234
|
+
@cache ||= {}
|
235
|
+
unless @cache[name]
|
236
|
+
paths = @paths.map { |path| File.join(path, name) }
|
237
|
+
if file = paths.find { |path| File.exist?(path) }
|
238
|
+
@cache[name] = Glim.preprocess_template(File.read(file))
|
239
|
+
end
|
240
|
+
end
|
241
|
+
@cache[name]
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
Liquid::Template.register_filter(Glim::LiquidFilters)
|
247
|
+
Liquid::Template.register_tag('post_url', Glim::LiquidTags::PostURL)
|
248
|
+
Liquid::Template.register_tag('link', Glim::LiquidTags::Link)
|
249
|
+
Liquid::Template.register_tag("highlight", Glim::LiquidTags::HighlightBlock)
|