glim 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|