archival 0.0.5 → 0.0.9
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 +4 -4
- data/.rubocop.yml +1 -1
- data/Gemfile +1 -1
- data/archival.gemspec +5 -1
- data/bin/bundle +1 -1
- data/lib/archival/builder.rb +174 -78
- data/lib/archival/config.rb +3 -2
- data/lib/archival/helper_server.rb +16 -7
- data/lib/archival/listen.rb +49 -24
- data/lib/archival/markdown_renderer.rb +33 -0
- data/lib/archival/parser.rb +33 -0
- data/lib/archival/template_array.rb +34 -0
- data/lib/archival/version.rb +1 -1
- data/lib/archival.rb +11 -0
- data/lib/tags/asset.rb +66 -0
- data/lib/tags/layout.rb +14 -5
- data/package.json +1 -1
- metadata +9 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd9c755847bfaabb7c6dee0882426534c44c7130a8cbfe84a45b755bca91088a
|
4
|
+
data.tar.gz: 3bbd4cb0b322db5eff11b85d9afc9f76e4cdbe1bc166c530f015583369c7f141
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: db20449d88ba3394580bd5efebd259a7479bd423fea6983a4790e21d124d831490f4e9f4bea3fe97411cc2b72898e93534be2b34d122e6c93e47290381cd74b3
|
7
|
+
data.tar.gz: f25b29c4fdb0b676dcee6156ab571877764ead475cac70944c6e81a1f5c445b3ebaf07b7266087b3163209184cf2c9dc50f33797105b8abb3a8e2564a19861c2
|
data/.rubocop.yml
CHANGED
data/Gemfile
CHANGED
data/archival.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = 'archival'
|
5
|
-
s.version = '0.0.
|
5
|
+
s.version = '0.0.9'
|
6
6
|
s.summary = 'An incredibly simple CMS for durable websites'
|
7
7
|
s.description = 'https://jesseditson.com/the-simplest-cms-part-1'
|
8
8
|
s.authors = ['Jesse Ditson']
|
@@ -26,4 +26,8 @@ Gem::Specification.new do |s|
|
|
26
26
|
s.add_dependency 'listen', '~> 3.7.0'
|
27
27
|
s.add_dependency 'redcarpet', '~> 3.5.1'
|
28
28
|
s.add_dependency 'tomlrb', '~> 2.0.1'
|
29
|
+
|
30
|
+
s.metadata = {
|
31
|
+
'rubygems_mfa_required' => 'true'
|
32
|
+
}
|
29
33
|
end
|
data/bin/bundle
CHANGED
@@ -73,7 +73,7 @@ m = Module.new do
|
|
73
73
|
|
74
74
|
requirement = bundler_gem_version.approximate_recommendation
|
75
75
|
|
76
|
-
return requirement unless Gem
|
76
|
+
return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0")
|
77
77
|
|
78
78
|
requirement += ".a" if bundler_gem_version.prerelease?
|
79
79
|
|
data/lib/archival/builder.rb
CHANGED
@@ -2,49 +2,51 @@
|
|
2
2
|
|
3
3
|
require 'liquid'
|
4
4
|
require 'tomlrb'
|
5
|
-
require 'tags/layout'
|
6
5
|
require 'redcarpet'
|
7
|
-
|
8
|
-
|
9
|
-
Liquid::Template.register_tag('layout', Layout)
|
6
|
+
require 'fileutils'
|
7
|
+
require 'tags/asset'
|
10
8
|
|
11
9
|
module Archival
|
10
|
+
class DuplicateKeyError < StandardError
|
11
|
+
end
|
12
|
+
|
13
|
+
class DuplicateStaticFileError < StandardError
|
14
|
+
end
|
15
|
+
|
12
16
|
class Builder
|
13
17
|
attr_reader :page_templates
|
14
18
|
|
15
19
|
def initialize(config, *_args)
|
16
20
|
@config = config
|
17
|
-
@markdown = Redcarpet::Markdown.new(
|
18
|
-
Redcarpet::Render::HTML.new(prettify: true,
|
19
|
-
hard_wrap: true), no_intra_emphasis: true,
|
20
|
-
fenced_code_blocks: true,
|
21
|
-
autolink: true,
|
22
|
-
strikethrough: true,
|
23
|
-
underline: true
|
24
|
-
)
|
25
21
|
refresh_config
|
22
|
+
Asset.helper_port = @config.helper_port
|
23
|
+
end
|
24
|
+
|
25
|
+
def pages_dir
|
26
|
+
File.join(@config.root, @config.pages_dir)
|
27
|
+
end
|
28
|
+
|
29
|
+
def objects_dir
|
30
|
+
File.join(@config.root, @config.objects_dir)
|
26
31
|
end
|
27
32
|
|
28
33
|
def refresh_config
|
29
34
|
@file_system = Liquid::LocalFileSystem.new(
|
30
|
-
|
35
|
+
pages_dir, '%s.liquid'
|
31
36
|
)
|
32
|
-
@variables = {}
|
33
37
|
@object_types = {}
|
34
38
|
@page_templates = {}
|
39
|
+
@dynamic_types = Set.new
|
40
|
+
@dynamic_templates = {}
|
41
|
+
@parser = Archival::Parser.new(pages_dir)
|
35
42
|
|
36
43
|
Liquid::Template.file_system = Liquid::LocalFileSystem.new(
|
37
|
-
|
44
|
+
pages_dir, '_%s.liquid'
|
38
45
|
)
|
39
46
|
|
40
|
-
objects_definition_file = File.join(@config.root,
|
41
|
-
'objects.toml')
|
42
|
-
if File.file? objects_definition_file
|
43
|
-
@object_types = Tomlrb.load_file(objects_definition_file)
|
44
|
-
end
|
47
|
+
@objects_definition_file = File.join(@config.root, 'objects.toml')
|
45
48
|
|
46
49
|
update_pages
|
47
|
-
update_objects
|
48
50
|
end
|
49
51
|
|
50
52
|
def full_rebuild
|
@@ -52,8 +54,46 @@ module Archival
|
|
52
54
|
refresh_config
|
53
55
|
end
|
54
56
|
|
55
|
-
def
|
56
|
-
|
57
|
+
def update_objects(_updated_objects = nil)
|
58
|
+
@object_types = {}
|
59
|
+
if File.file? @objects_definition_file
|
60
|
+
@object_types = Tomlrb.load_file(@objects_definition_file)
|
61
|
+
end
|
62
|
+
@dynamic_types = Set.new
|
63
|
+
@object_types.each do |_name, definition|
|
64
|
+
is_template = definition.key? 'template'
|
65
|
+
@dynamic_types << definition['template'] if is_template
|
66
|
+
end
|
67
|
+
# TODO: remove deleted dynamic pages
|
68
|
+
end
|
69
|
+
|
70
|
+
def update_pages(_updated_pages = nil, _updated_objects = nil)
|
71
|
+
update_objects
|
72
|
+
# TODO: remove deleted pages
|
73
|
+
do_update_pages(pages_dir)
|
74
|
+
end
|
75
|
+
|
76
|
+
def update_assets(changes)
|
77
|
+
changes.each do |change|
|
78
|
+
asset_path = File.join(@config.build_dir, change.path)
|
79
|
+
case change.type
|
80
|
+
when :removed
|
81
|
+
FileUtils.rm_rf asset_path
|
82
|
+
else
|
83
|
+
puts change.path
|
84
|
+
FileUtils.copy_entry File.join(@config.root, change.path), asset_path
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def dynamic?(file)
|
90
|
+
@dynamic_types.include? File.basename(file, '.liquid')
|
91
|
+
end
|
92
|
+
|
93
|
+
def template_for_page(template_file)
|
94
|
+
content = @file_system.read_template_file(template_file)
|
95
|
+
content += dev_mode_content if @config.dev_mode
|
96
|
+
Liquid::Template.parse(content)
|
57
97
|
end
|
58
98
|
|
59
99
|
def do_update_pages(dir, prefix = nil)
|
@@ -69,77 +109,96 @@ module Archival
|
|
69
109
|
add_prefix(entry))
|
70
110
|
end
|
71
111
|
elsif File.file? File.join(dir, entry)
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
template_file =
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
Liquid::Template.parse(content)
|
112
|
+
page_name = File.basename(entry, '.liquid')
|
113
|
+
template_file = add_prefix.call(page_name)
|
114
|
+
if dynamic? entry
|
115
|
+
@dynamic_templates[template_file] = template_for_page(template_file)
|
116
|
+
elsif entry.end_with?('.liquid') && !(entry.start_with? '_')
|
117
|
+
@page_templates[template_file] =
|
118
|
+
template_for_page(template_file)
|
80
119
|
end
|
81
120
|
end
|
82
121
|
end
|
83
122
|
end
|
84
123
|
|
85
|
-
def
|
86
|
-
|
87
|
-
|
124
|
+
def read_objects(type)
|
125
|
+
obj_dir = File.join(objects_dir, type)
|
126
|
+
return unless File.directory? obj_dir
|
127
|
+
|
128
|
+
Dir.foreach(obj_dir) do |file|
|
129
|
+
if file.end_with? '.toml'
|
130
|
+
object = Tomlrb.load_file(File.join(
|
131
|
+
obj_dir, file
|
132
|
+
))
|
133
|
+
object[:name] =
|
134
|
+
File.basename(file, '.toml')
|
135
|
+
yield object[:name], object
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def path_for_template(name, type)
|
141
|
+
Pathname.new(File.join(pages_dir, type, "#{name}.html"))
|
88
142
|
end
|
89
143
|
|
90
|
-
def
|
144
|
+
def objects_for_template(template_path)
|
91
145
|
objects = {}
|
92
|
-
@object_types.each do |
|
93
|
-
objects[
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
objects[name][object[:name]] = parse_object(object, definition)
|
104
|
-
end
|
146
|
+
@object_types.each do |type, definition|
|
147
|
+
objects[type] = {}
|
148
|
+
is_dynamic = @dynamic_types.include? type
|
149
|
+
read_objects type do |name, object|
|
150
|
+
objects[type][name] = @parser.parse_object(
|
151
|
+
object, definition, template_path
|
152
|
+
)
|
153
|
+
if is_dynamic
|
154
|
+
path = path_for_template(name, type)
|
155
|
+
objects[type][name]['path'] =
|
156
|
+
path.relative_path_from(File.dirname(template_path)).to_s
|
105
157
|
end
|
106
158
|
end
|
107
|
-
objects[
|
159
|
+
objects[type] = sort_objects(objects[type])
|
108
160
|
end
|
109
|
-
|
161
|
+
objects
|
110
162
|
end
|
111
163
|
|
112
164
|
def sort_objects(objects)
|
113
|
-
#
|
114
|
-
#
|
115
|
-
|
116
|
-
|
117
|
-
obj['order'].to_s || name
|
165
|
+
# Sort by either 'order' key or object name, depending on what is
|
166
|
+
# available.
|
167
|
+
sorted_by_keys = objects.sort_by do |name, obj|
|
168
|
+
obj.key?('order') ? obj['order'].to_s : name
|
118
169
|
end
|
119
|
-
sorted_objects =
|
120
|
-
|
170
|
+
sorted_objects = Archival::TemplateArray.new
|
171
|
+
sorted_by_keys.each do |d|
|
172
|
+
raise DuplicateKeyError if sorted_objects.key?(d[0])
|
173
|
+
|
174
|
+
sorted_objects.push(d[1])
|
121
175
|
sorted_objects[d[0]] = d[1]
|
122
176
|
end
|
123
177
|
sorted_objects
|
124
178
|
end
|
125
179
|
|
126
|
-
def parse_object(object, definition)
|
127
|
-
definition.each do |name, type|
|
128
|
-
case type
|
129
|
-
when 'markdown'
|
130
|
-
object[name] = @markdown.render(object[name]) if object[name]
|
131
|
-
end
|
132
|
-
end
|
133
|
-
object
|
134
|
-
end
|
135
|
-
|
136
|
-
def set_var(name, value)
|
137
|
-
@variables[name] = value
|
138
|
-
end
|
139
|
-
|
140
180
|
def render(page)
|
181
|
+
dir = File.join(pages_dir, File.dirname(page))
|
141
182
|
template = @page_templates[page]
|
142
|
-
|
183
|
+
template_path = File.join(dir, page)
|
184
|
+
parsed_objects = objects_for_template(template_path)
|
185
|
+
template.render('objects' => parsed_objects,
|
186
|
+
'template_path' => template_path)
|
187
|
+
end
|
188
|
+
|
189
|
+
def render_dynamic(type, name)
|
190
|
+
dir = File.join(pages_dir, type)
|
191
|
+
template = @dynamic_templates[type]
|
192
|
+
template_path = File.join(dir, name)
|
193
|
+
parsed_objects = objects_for_template(template_path)
|
194
|
+
obj = parsed_objects[type][name]
|
195
|
+
vars = {}
|
196
|
+
.merge(
|
197
|
+
'objects' => parsed_objects,
|
198
|
+
'template_path' => template_path
|
199
|
+
)
|
200
|
+
.merge({ type => obj })
|
201
|
+
template.render(vars)
|
143
202
|
end
|
144
203
|
|
145
204
|
def write_all
|
@@ -148,23 +207,60 @@ module Archival
|
|
148
207
|
out_dir = File.join(@config.build_dir,
|
149
208
|
File.dirname(template))
|
150
209
|
Dir.mkdir(out_dir) unless File.exist? out_dir
|
151
|
-
out_path = File.join(
|
152
|
-
"#{template}.html")
|
210
|
+
out_path = File.join(out_dir, "#{template}.html")
|
153
211
|
File.open(out_path, 'w+') do |file|
|
154
212
|
file.write(render(template))
|
155
213
|
end
|
156
214
|
end
|
157
|
-
|
215
|
+
@dynamic_types.each do |type|
|
216
|
+
out_dir = File.join(@config.build_dir, type)
|
217
|
+
Dir.mkdir(out_dir) unless File.exist? out_dir
|
218
|
+
read_objects(type) do |name|
|
219
|
+
out_path = File.join(out_dir, "#{name}.html")
|
220
|
+
File.open(out_path, 'w+') do |file|
|
221
|
+
file.write(render_dynamic(type, name))
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
158
225
|
|
159
|
-
# in production, also copy all assets to the dist folder.
|
226
|
+
# in production (or init), also copy all assets to the dist folder.
|
227
|
+
# in dev, they will be copied as they change.
|
160
228
|
@config.assets_dirs.each do |asset_dir|
|
161
|
-
|
162
|
-
|
229
|
+
asset_path = File.join(@config.build_dir, asset_dir)
|
230
|
+
next if @config.dev_mode && File.exist?(asset_path)
|
231
|
+
|
232
|
+
source_path = File.join(@config.root, asset_dir)
|
233
|
+
next unless File.exist?(source_path)
|
234
|
+
|
235
|
+
FileUtils.copy_entry source_path, asset_path
|
163
236
|
end
|
237
|
+
|
238
|
+
copy_static
|
164
239
|
end
|
165
240
|
|
166
241
|
private
|
167
242
|
|
243
|
+
def copy_static
|
244
|
+
static_dir = File.join(@config.root, @config.static_dir)
|
245
|
+
|
246
|
+
# same for the static dir, but just the content.
|
247
|
+
return unless File.exist?(static_dir)
|
248
|
+
|
249
|
+
copied_static_files = Set[Dir.children(@config.build_dir)]
|
250
|
+
Dir.children(static_dir).each do |child|
|
251
|
+
raise DuplicateStaticFileError if copied_static_files.include?(child)
|
252
|
+
|
253
|
+
copied_static_files << child
|
254
|
+
asset_path = File.join(@config.build_dir, child)
|
255
|
+
next if @config.dev_mode && File.exist?(asset_path)
|
256
|
+
|
257
|
+
source_path = File.join(static_dir, child)
|
258
|
+
next unless File.exist?(source_path)
|
259
|
+
|
260
|
+
FileUtils.copy_entry source_path, asset_path
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
168
264
|
def dev_mode_content
|
169
265
|
"<script src=\"http://localhost:#{@config.helper_port}/js/archival-helper.js\" type=\"application/javascript\"></script>" # rubocop:disable Layout/LineLength
|
170
266
|
end
|
data/lib/archival/config.rb
CHANGED
@@ -4,8 +4,8 @@ require 'tomlrb'
|
|
4
4
|
|
5
5
|
module Archival
|
6
6
|
class Config
|
7
|
-
attr_reader :pages_dir, :objects_dir, :
|
8
|
-
:helper_port, :dev_mode
|
7
|
+
attr_reader :pages_dir, :objects_dir, :static_dir, :assets_dirs, :root,
|
8
|
+
:build_dir, :helper_port, :dev_mode
|
9
9
|
|
10
10
|
def initialize(config = {})
|
11
11
|
@root = config['root'] || Dir.pwd
|
@@ -17,6 +17,7 @@ module Archival
|
|
17
17
|
)
|
18
18
|
@helper_port = config['helper_port'] || manifest['helper_port'] || 2701
|
19
19
|
@assets_dirs = config['assets_dirs'] || manifest['assets'] || []
|
20
|
+
@static_dir = config['static_dir'] || manifest['static'] || 'static'
|
20
21
|
@dev_mode = config[:dev_mode] || false
|
21
22
|
end
|
22
23
|
|
@@ -44,16 +44,23 @@ module Archival
|
|
44
44
|
MAGIC_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
|
45
45
|
|
46
46
|
def handle_request(client, req, method, path)
|
47
|
-
if method == 'GET' && path.start_with?('/js/')
|
48
|
-
# For
|
47
|
+
if method == 'GET' && path.start_with?('/js/archival-helper.js')
|
48
|
+
# For this special file, serve it from the helper dir
|
49
49
|
http_response(client, type: 'application/javascript') do
|
50
|
-
serve_static(client, path)
|
50
|
+
serve_static(client, path, @helper_dir)
|
51
51
|
end
|
52
52
|
client.close
|
53
53
|
elsif (matches = req.match(/^Sec-WebSocket-Key: (\S+)/))
|
54
54
|
websocket_key = matches[1]
|
55
55
|
# puts "Websocket handshake detected with key: #{websocket_key}"
|
56
56
|
connect_socket(client, websocket_key)
|
57
|
+
elsif method == 'GET'
|
58
|
+
# For static paths, just serve the files they refer to.
|
59
|
+
# TODO: mime type should be inferred from file type
|
60
|
+
http_response(client, type: 'application/javascript') do
|
61
|
+
serve_static(client, path)
|
62
|
+
end
|
63
|
+
client.close
|
57
64
|
else
|
58
65
|
client.close
|
59
66
|
end
|
@@ -99,8 +106,9 @@ module Archival
|
|
99
106
|
|
100
107
|
# Our server only supports single-frame, text messages.
|
101
108
|
# Raise an exception if the client tries to send anything else.
|
102
|
-
raise
|
103
|
-
|
109
|
+
raise 'Archival dev server does not support continuations' unless fin
|
110
|
+
# Some browsers send this regardless, so ignore it to keep the noise down.
|
111
|
+
return unless opcode == 1
|
104
112
|
|
105
113
|
second_byte = @socket.getbyte
|
106
114
|
is_masked = second_byte & 0b10000000
|
@@ -139,8 +147,8 @@ module Archival
|
|
139
147
|
@socket.write output.pack("CCA#{message.size}")
|
140
148
|
end
|
141
149
|
|
142
|
-
def serve_static(client, path)
|
143
|
-
buffer = File.open(File.join(
|
150
|
+
def serve_static(client, path, base = @build_dir)
|
151
|
+
buffer = File.open(File.join(base, path)).read
|
144
152
|
buffer.sub! '$PORT', @port.to_s
|
145
153
|
client.print buffer
|
146
154
|
end
|
@@ -150,6 +158,7 @@ module Archival
|
|
150
158
|
type = config[:type] ||= 'text/html'
|
151
159
|
client.print "HTTP/1.1 #{status}\r\n"
|
152
160
|
client.print "Content-Type: #{type}\r\n"
|
161
|
+
client.print "Access-Control-Allow-Origin: *\r\n"
|
153
162
|
client.print "\r\n"
|
154
163
|
yield
|
155
164
|
end
|
data/lib/archival/listen.rb
CHANGED
@@ -4,6 +4,8 @@ require 'listen'
|
|
4
4
|
require 'pathname'
|
5
5
|
|
6
6
|
module Archival
|
7
|
+
Change = Struct.new(:path, :type)
|
8
|
+
|
7
9
|
def listen(config = {})
|
8
10
|
@config = Config.new(config.merge(dev_mode: true))
|
9
11
|
builder = Builder.new(@config)
|
@@ -13,21 +15,27 @@ module Archival
|
|
13
15
|
ignore = %r{/dist/}
|
14
16
|
listener = Listen.to(@config.root,
|
15
17
|
ignore: ignore) do |modified, added, removed|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
18
|
+
changes = {
|
19
|
+
pages: [],
|
20
|
+
objects: [],
|
21
|
+
assets: [],
|
22
|
+
layout: [],
|
23
|
+
config: []
|
24
|
+
}
|
25
|
+
add_change = lambda { |file, type|
|
26
|
+
c_type = change_type(file)
|
27
|
+
changes[c_type] << change(file, type) unless c_type == :none
|
28
|
+
}
|
29
|
+
added.each do |file|
|
30
|
+
add_change.call(file, :added)
|
31
|
+
end
|
32
|
+
modified.each do |file|
|
33
|
+
add_change.call(file, :modified)
|
28
34
|
end
|
29
|
-
|
30
|
-
|
35
|
+
removed.each do |file|
|
36
|
+
add_change.call(file, :removed)
|
37
|
+
end
|
38
|
+
@server.refresh_client if rebuild?(builder, changes)
|
31
39
|
end
|
32
40
|
listener.start
|
33
41
|
serve_helpers
|
@@ -45,6 +53,13 @@ module Archival
|
|
45
53
|
false
|
46
54
|
end
|
47
55
|
|
56
|
+
def change(file, type)
|
57
|
+
c = Change.new
|
58
|
+
c.path = Pathname.new(file).relative_path_from(@config.root)
|
59
|
+
c.type = type
|
60
|
+
c
|
61
|
+
end
|
62
|
+
|
48
63
|
def change_type(file)
|
49
64
|
# a page was modified, rebuild the pages.
|
50
65
|
return :pages if child?(File.join(@config.root, @config.pages_dir),
|
@@ -53,26 +68,36 @@ module Archival
|
|
53
68
|
return :objects if child?(File.join(@config.root, @config.objects_dir),
|
54
69
|
file)
|
55
70
|
|
56
|
-
#
|
71
|
+
# an asset was changed, which just means to copy or delete it
|
57
72
|
@config.assets_dirs.each do |dir|
|
58
73
|
return :assets if child?(File.join(@config.root, dir), file)
|
59
74
|
end
|
60
|
-
|
61
|
-
|
75
|
+
|
76
|
+
# a static file was changed - copy or delete those too.
|
77
|
+
return :assets if child?(File.join(@config.root, @config.static_dir),
|
78
|
+
file)
|
79
|
+
|
80
|
+
# other special files
|
81
|
+
return :layout if child?(File.join(@config.root, 'layout'), file)
|
82
|
+
return :config if ['manifest.toml',
|
62
83
|
'objects.toml'].include? File.basename(file)
|
63
84
|
|
64
85
|
:none
|
65
86
|
end
|
66
87
|
|
67
|
-
def rebuild?(builder,
|
68
|
-
|
69
|
-
return false
|
70
|
-
end
|
88
|
+
def rebuild?(builder, changes)
|
89
|
+
return false if changes.values.all?(&:empty?)
|
71
90
|
|
72
91
|
Logger.benchmark('rebuilt') do
|
73
|
-
|
74
|
-
|
75
|
-
|
92
|
+
if changes[:pages].length || changes[:objects].length
|
93
|
+
builder.update_pages(changes[:pages], changes[:objects])
|
94
|
+
end
|
95
|
+
builder.update_assets(changes[:assets]) if changes[:assets].length
|
96
|
+
if changes[:assets].length || changes[:layouts] || changes[:config]
|
97
|
+
# TODO: optimization: this could operate on the known subset of
|
98
|
+
# changes instead...
|
99
|
+
builder.full_rebuild
|
100
|
+
end
|
76
101
|
builder.write_all
|
77
102
|
end
|
78
103
|
true
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redcarpet'
|
4
|
+
|
5
|
+
module Archival
|
6
|
+
class MarkdownRenderer < Redcarpet::Render::HTML
|
7
|
+
def autolink(link, _link_type, _opts)
|
8
|
+
# TODO: handle link_type?
|
9
|
+
"<a href=\"#{rewrite_link(link)}\">#{rewrite_link(link)}</a>"
|
10
|
+
end
|
11
|
+
|
12
|
+
def link(link, title, content)
|
13
|
+
"<a href=\"#{rewrite_link(link)}\" title=\"#{title}\">#{content}</a>"
|
14
|
+
end
|
15
|
+
|
16
|
+
def image(link, title, alt_text)
|
17
|
+
"<img src=\"#{rewrite_link(link)}\" \
|
18
|
+
title=\"#{title}\" alt=\"#{alt_text}\"/>"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def rewrite_link(link)
|
24
|
+
if link.start_with?('http') || link.start_with?('mailto') ||
|
25
|
+
link.start_with?('www') || link.start_with?('/')
|
26
|
+
link
|
27
|
+
end
|
28
|
+
template_dir = File.dirname(@options[:template_file])
|
29
|
+
resolved_link = Pathname.new(File.join(@options[:pages_root], link))
|
30
|
+
resolved_link.relative_path_from(template_dir)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'liquid'
|
4
|
+
require 'redcarpet'
|
5
|
+
|
6
|
+
module Archival
|
7
|
+
class Parser
|
8
|
+
def initialize(pages_root)
|
9
|
+
@pages_root = pages_root
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse_object(object, definition, template_file)
|
13
|
+
markdown = Redcarpet::Markdown.new(
|
14
|
+
Archival::MarkdownRenderer.new(prettify: true,
|
15
|
+
hard_wrap: true),
|
16
|
+
no_intra_emphasis: true,
|
17
|
+
fenced_code_blocks: true,
|
18
|
+
autolink: true,
|
19
|
+
strikethrough: true,
|
20
|
+
underline: true,
|
21
|
+
template_file: template_file,
|
22
|
+
pages_root: @pages_root
|
23
|
+
)
|
24
|
+
definition.each do |name, type|
|
25
|
+
case type
|
26
|
+
when 'markdown'
|
27
|
+
object[name] = markdown.render(object[name]) if object.key? name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
object
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Archival
|
4
|
+
class TemplateArray < Array
|
5
|
+
alias subscript_access []
|
6
|
+
alias subscript_write []=
|
7
|
+
|
8
|
+
def initialize(*args)
|
9
|
+
super(*args)
|
10
|
+
@data = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def [](*args)
|
14
|
+
key = args[0]
|
15
|
+
return @data[key] if key.is_a? String
|
16
|
+
return @data[key] if key.is_a? Symbol
|
17
|
+
|
18
|
+
subscript_access(*args)
|
19
|
+
end
|
20
|
+
|
21
|
+
def []=(*args)
|
22
|
+
key = args[0]
|
23
|
+
if key.is_a?(String) || key.is_a?(Symbol)
|
24
|
+
@data[key] = args[1]
|
25
|
+
return
|
26
|
+
end
|
27
|
+
subscript_write(*args)
|
28
|
+
end
|
29
|
+
|
30
|
+
def key?(key)
|
31
|
+
@data.key?(key)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/archival/version.rb
CHANGED
data/lib/archival.rb
CHANGED
@@ -1,12 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'liquid'
|
4
|
+
require 'tags/layout'
|
5
|
+
require 'tags/asset'
|
6
|
+
|
3
7
|
module Archival
|
4
8
|
# Main Archival module. See https://archival.dev for docs.
|
5
9
|
end
|
6
10
|
|
11
|
+
Liquid::Template.error_mode = :strict
|
12
|
+
Liquid::Template.register_tag('layout', Layout)
|
13
|
+
Liquid::Template.register_tag('asset', Asset)
|
14
|
+
|
7
15
|
require 'archival/version'
|
16
|
+
require 'archival/template_array'
|
8
17
|
require 'archival/logger'
|
9
18
|
require 'archival/config'
|
19
|
+
require 'archival/markdown_renderer'
|
10
20
|
require 'archival/helper_server'
|
21
|
+
require 'archival/parser'
|
11
22
|
require 'archival/builder'
|
12
23
|
require 'archival/listen'
|
data/lib/tags/asset.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'liquid'
|
4
|
+
|
5
|
+
class Asset < Liquid::Tag
|
6
|
+
# Adds an `asset` tag to liquid. Usage:
|
7
|
+
#
|
8
|
+
# {% asset "path/to/asset.png" %}
|
9
|
+
#
|
10
|
+
# This will replace the tag with a relative path to the asset from the
|
11
|
+
# current template. Using normal tags will work from the root, but when
|
12
|
+
# building dynamic pages or reusing layouts, asset paths are dynamic and
|
13
|
+
# will need to be rewritten.
|
14
|
+
|
15
|
+
prepend Liquid::Tag::Disableable
|
16
|
+
|
17
|
+
SYNTAX = /(#{Liquid::QuotedFragment}+)/o.freeze
|
18
|
+
|
19
|
+
def initialize(tag_name, markup, tokens)
|
20
|
+
super
|
21
|
+
raise AssetError, 'Invalid layout syntax' unless markup =~ SYNTAX
|
22
|
+
|
23
|
+
@path = parse_expression(Regexp.last_match(1))
|
24
|
+
# This is defaulted to the pages dir, because it represents the structure
|
25
|
+
# of our website. Asset directories are copied as siblings at runtime.
|
26
|
+
@@root_dir ||= File.join(Dir.pwd, 'pages')
|
27
|
+
|
28
|
+
@attributes = {}
|
29
|
+
|
30
|
+
markup.scan(Liquid::TagAttributes) do |key, value|
|
31
|
+
@attributes[key] = parse_expression(value)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.root_dir=(dir)
|
36
|
+
@@root_dir = dir
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.helper_port=(port)
|
40
|
+
@@helper_port = port
|
41
|
+
end
|
42
|
+
|
43
|
+
def render_to_output_buffer(context, output)
|
44
|
+
unless @@root_dir
|
45
|
+
raise AssetError,
|
46
|
+
'root_dir must be set on Archival::Asset'
|
47
|
+
end
|
48
|
+
|
49
|
+
unless context.key? 'template_path'
|
50
|
+
raise AssetError,
|
51
|
+
'template_path must be provided to parse when using assets'
|
52
|
+
end
|
53
|
+
template_path = File.dirname(context['template_path'])
|
54
|
+
abs_asset_path = Pathname.new(File.join(@@root_dir, @path))
|
55
|
+
asset_path = abs_asset_path.relative_path_from(template_path).cleanpath.to_s
|
56
|
+
output << if @attributes['serve'] == true
|
57
|
+
"http://localhost:#{@@helper_port}/#{asset_path}"
|
58
|
+
else
|
59
|
+
asset_path
|
60
|
+
end
|
61
|
+
output
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class AssetError < Liquid::Error
|
66
|
+
end
|
data/lib/tags/layout.rb
CHANGED
@@ -25,7 +25,7 @@ class Layout < Liquid::Tag
|
|
25
25
|
super
|
26
26
|
|
27
27
|
@page_content = []
|
28
|
-
raise 'Invalid layout syntax' unless markup =~ SYNTAX
|
28
|
+
raise LayoutError, 'Invalid layout syntax' unless markup =~ SYNTAX
|
29
29
|
|
30
30
|
layout_name = Regexp.last_match(1)
|
31
31
|
@layout_name_expr = parse_expression(layout_name)
|
@@ -40,7 +40,9 @@ class Layout < Liquid::Tag
|
|
40
40
|
base_path = Dir.pwd
|
41
41
|
layout_dir = 'layout'
|
42
42
|
layout_path = File.join(base_path, layout_dir)
|
43
|
-
|
43
|
+
unless File.exist? layout_path
|
44
|
+
raise LayoutError, "Layout dir #{layout_path} not found"
|
45
|
+
end
|
44
46
|
|
45
47
|
layout_path
|
46
48
|
end
|
@@ -55,11 +57,15 @@ class Layout < Liquid::Tag
|
|
55
57
|
)
|
56
58
|
|
57
59
|
next unless File.basename(f, '.*') == layout_name
|
58
|
-
|
60
|
+
if found_layout
|
61
|
+
raise LayoutError, "More than one layout named #{layout_name} found."
|
62
|
+
end
|
59
63
|
|
60
64
|
found_layout = File.join(layout_path, f)
|
61
65
|
end
|
62
|
-
|
66
|
+
if found_layout.nil?
|
67
|
+
raise LayoutError, "No layouts named #{layout_name} found."
|
68
|
+
end
|
63
69
|
|
64
70
|
layout = File.read(found_layout)
|
65
71
|
@@layout_cache[layout_name] =
|
@@ -77,7 +83,7 @@ class Layout < Liquid::Tag
|
|
77
83
|
|
78
84
|
def render_to_output_buffer(context, output)
|
79
85
|
layout_name = context.evaluate(@layout_name_expr)
|
80
|
-
raise 'Bad layout name argument' unless layout_name
|
86
|
+
raise LayoutError, 'Bad layout name argument' unless layout_name
|
81
87
|
|
82
88
|
layout = load_layout(layout_name)
|
83
89
|
|
@@ -104,3 +110,6 @@ class Layout < Liquid::Tag
|
|
104
110
|
output
|
105
111
|
end
|
106
112
|
end
|
113
|
+
|
114
|
+
class LayoutError < Liquid::Error
|
115
|
+
end
|
data/package.json
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: archival
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jesse Ditson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: liquid
|
@@ -104,14 +104,19 @@ files:
|
|
104
104
|
- lib/archival/helper_server.rb
|
105
105
|
- lib/archival/listen.rb
|
106
106
|
- lib/archival/logger.rb
|
107
|
+
- lib/archival/markdown_renderer.rb
|
108
|
+
- lib/archival/parser.rb
|
107
109
|
- lib/archival/rake_tasks.rb
|
110
|
+
- lib/archival/template_array.rb
|
108
111
|
- lib/archival/version.rb
|
112
|
+
- lib/tags/asset.rb
|
109
113
|
- lib/tags/layout.rb
|
110
114
|
- package.json
|
111
115
|
homepage: https://archival.dev
|
112
116
|
licenses:
|
113
117
|
- Unlicense
|
114
|
-
metadata:
|
118
|
+
metadata:
|
119
|
+
rubygems_mfa_required: 'true'
|
115
120
|
post_install_message:
|
116
121
|
rdoc_options: []
|
117
122
|
require_paths:
|
@@ -127,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
132
|
- !ruby/object:Gem::Version
|
128
133
|
version: '0'
|
129
134
|
requirements: []
|
130
|
-
rubygems_version: 3.
|
135
|
+
rubygems_version: 3.3.3
|
131
136
|
signing_key:
|
132
137
|
specification_version: 4
|
133
138
|
summary: An incredibly simple CMS for durable websites
|