archival 0.0.6 → 0.0.10
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 +162 -71
- 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/version.rb +1 -1
- data/lib/archival.rb +10 -0
- data/lib/tags/asset.rb +68 -0
- data/lib/tags/layout.rb +14 -5
- data/package.json +1 -1
- metadata +8 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e0b66a3140671bb0e77bceb609bf3474cd69aa960f7c3de3a9d9931b5c14bb6f
|
|
4
|
+
data.tar.gz: 8451171849fc29a01f773861b5436dedcf411b91578ace7847a08d02cb53389b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5712c5e1f67f602b667bad310b265991942ad9d4ac3a6fa6d8f1e0ca74808a7aeb4625ccc11a69ca9bec87bfbdf9066430c30e04e9c64c8817ce8f159d4dd003
|
|
7
|
+
data.tar.gz: 612032276d7031ec758f6023e4c11f438e2e74d7882c4afe359f0e95024ca2be04080662a7dfd04b24b2eb62620343ee22beee87b06ffe03c206a9c06848dfef
|
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.10'
|
|
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,52 +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
|
|
12
10
|
class DuplicateKeyError < StandardError
|
|
13
11
|
end
|
|
14
12
|
|
|
13
|
+
class DuplicateStaticFileError < StandardError
|
|
14
|
+
end
|
|
15
|
+
|
|
15
16
|
class Builder
|
|
16
17
|
attr_reader :page_templates
|
|
17
18
|
|
|
18
19
|
def initialize(config, *_args)
|
|
19
20
|
@config = config
|
|
20
|
-
@markdown = Redcarpet::Markdown.new(
|
|
21
|
-
Redcarpet::Render::HTML.new(prettify: true,
|
|
22
|
-
hard_wrap: true), no_intra_emphasis: true,
|
|
23
|
-
fenced_code_blocks: true,
|
|
24
|
-
autolink: true,
|
|
25
|
-
strikethrough: true,
|
|
26
|
-
underline: true
|
|
27
|
-
)
|
|
28
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)
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
def refresh_config
|
|
32
34
|
@file_system = Liquid::LocalFileSystem.new(
|
|
33
|
-
|
|
35
|
+
pages_dir, '%s.liquid'
|
|
34
36
|
)
|
|
35
|
-
@variables = {}
|
|
36
37
|
@object_types = {}
|
|
37
38
|
@page_templates = {}
|
|
39
|
+
@dynamic_types = Set.new
|
|
40
|
+
@dynamic_templates = {}
|
|
41
|
+
@parser = Archival::Parser.new(pages_dir)
|
|
38
42
|
|
|
39
43
|
Liquid::Template.file_system = Liquid::LocalFileSystem.new(
|
|
40
|
-
|
|
44
|
+
pages_dir, '_%s.liquid'
|
|
41
45
|
)
|
|
42
46
|
|
|
43
|
-
objects_definition_file = File.join(@config.root,
|
|
44
|
-
'objects.toml')
|
|
45
|
-
if File.file? objects_definition_file
|
|
46
|
-
@object_types = Tomlrb.load_file(objects_definition_file)
|
|
47
|
-
end
|
|
47
|
+
@objects_definition_file = File.join(@config.root, 'objects.toml')
|
|
48
48
|
|
|
49
49
|
update_pages
|
|
50
|
-
update_objects
|
|
51
50
|
end
|
|
52
51
|
|
|
53
52
|
def full_rebuild
|
|
@@ -55,8 +54,46 @@ module Archival
|
|
|
55
54
|
refresh_config
|
|
56
55
|
end
|
|
57
56
|
|
|
58
|
-
def
|
|
59
|
-
|
|
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)
|
|
60
97
|
end
|
|
61
98
|
|
|
62
99
|
def do_update_pages(dir, prefix = nil)
|
|
@@ -72,44 +109,56 @@ module Archival
|
|
|
72
109
|
add_prefix(entry))
|
|
73
110
|
end
|
|
74
111
|
elsif File.file? File.join(dir, entry)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
template_file =
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
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)
|
|
83
119
|
end
|
|
84
120
|
end
|
|
85
121
|
end
|
|
86
122
|
end
|
|
87
123
|
|
|
88
|
-
def
|
|
89
|
-
|
|
90
|
-
|
|
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"))
|
|
91
142
|
end
|
|
92
143
|
|
|
93
|
-
def
|
|
144
|
+
def objects_for_template(template_path)
|
|
94
145
|
objects = {}
|
|
95
|
-
@object_types.each do |
|
|
96
|
-
objects[
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
objects[name][object[:name]] = parse_object(object, definition)
|
|
107
|
-
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
|
|
108
157
|
end
|
|
109
158
|
end
|
|
110
|
-
objects[
|
|
159
|
+
objects[type] = sort_objects(objects[type])
|
|
111
160
|
end
|
|
112
|
-
|
|
161
|
+
objects
|
|
113
162
|
end
|
|
114
163
|
|
|
115
164
|
def sort_objects(objects)
|
|
@@ -128,23 +177,28 @@ module Archival
|
|
|
128
177
|
sorted_objects
|
|
129
178
|
end
|
|
130
179
|
|
|
131
|
-
def parse_object(object, definition)
|
|
132
|
-
definition.each do |name, type|
|
|
133
|
-
case type
|
|
134
|
-
when 'markdown'
|
|
135
|
-
object[name] = @markdown.render(object[name]) if object[name]
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
object
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def set_var(name, value)
|
|
142
|
-
@variables[name] = value
|
|
143
|
-
end
|
|
144
|
-
|
|
145
180
|
def render(page)
|
|
181
|
+
dir = File.join(pages_dir, File.dirname(page))
|
|
146
182
|
template = @page_templates[page]
|
|
147
|
-
|
|
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)
|
|
148
202
|
end
|
|
149
203
|
|
|
150
204
|
def write_all
|
|
@@ -153,23 +207,60 @@ module Archival
|
|
|
153
207
|
out_dir = File.join(@config.build_dir,
|
|
154
208
|
File.dirname(template))
|
|
155
209
|
Dir.mkdir(out_dir) unless File.exist? out_dir
|
|
156
|
-
out_path = File.join(
|
|
157
|
-
"#{template}.html")
|
|
210
|
+
out_path = File.join(out_dir, "#{template}.html")
|
|
158
211
|
File.open(out_path, 'w+') do |file|
|
|
159
212
|
file.write(render(template))
|
|
160
213
|
end
|
|
161
214
|
end
|
|
162
|
-
|
|
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
|
|
163
225
|
|
|
164
|
-
# 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.
|
|
165
228
|
@config.assets_dirs.each do |asset_dir|
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
168
236
|
end
|
|
237
|
+
|
|
238
|
+
copy_static
|
|
169
239
|
end
|
|
170
240
|
|
|
171
241
|
private
|
|
172
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
|
+
|
|
173
264
|
def dev_mode_content
|
|
174
265
|
"<script src=\"http://localhost:#{@config.helper_port}/js/archival-helper.js\" type=\"application/javascript\"></script>" # rubocop:disable Layout/LineLength
|
|
175
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
|
data/lib/archival/version.rb
CHANGED
data/lib/archival.rb
CHANGED
|
@@ -1,13 +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'
|
|
8
16
|
require 'archival/template_array'
|
|
9
17
|
require 'archival/logger'
|
|
10
18
|
require 'archival/config'
|
|
19
|
+
require 'archival/markdown_renderer'
|
|
11
20
|
require 'archival/helper_server'
|
|
21
|
+
require 'archival/parser'
|
|
12
22
|
require 'archival/builder'
|
|
13
23
|
require 'archival/listen'
|
data/lib/tags/asset.rb
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
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}+|\w+)/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
|
+
path = @path
|
|
45
|
+
path = path.evaluate(context) if path.is_a? Liquid::VariableLookup
|
|
46
|
+
unless @@root_dir
|
|
47
|
+
raise AssetError,
|
|
48
|
+
'root_dir must be set on Archival::Asset'
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
unless context.key? 'template_path'
|
|
52
|
+
raise AssetError,
|
|
53
|
+
'template_path must be provided to parse when using assets'
|
|
54
|
+
end
|
|
55
|
+
template_path = File.dirname(context['template_path'])
|
|
56
|
+
abs_asset_path = Pathname.new(File.join(@@root_dir, path))
|
|
57
|
+
asset_path = abs_asset_path.relative_path_from(template_path).cleanpath.to_s
|
|
58
|
+
output << if @attributes['serve'] == true
|
|
59
|
+
"http://localhost:#{@@helper_port}/#{asset_path}"
|
|
60
|
+
else
|
|
61
|
+
asset_path
|
|
62
|
+
end
|
|
63
|
+
output
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class AssetError < Liquid::Error
|
|
68
|
+
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.10
|
|
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-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: liquid
|
|
@@ -104,15 +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
|
|
108
110
|
- lib/archival/template_array.rb
|
|
109
111
|
- lib/archival/version.rb
|
|
112
|
+
- lib/tags/asset.rb
|
|
110
113
|
- lib/tags/layout.rb
|
|
111
114
|
- package.json
|
|
112
115
|
homepage: https://archival.dev
|
|
113
116
|
licenses:
|
|
114
117
|
- Unlicense
|
|
115
|
-
metadata:
|
|
118
|
+
metadata:
|
|
119
|
+
rubygems_mfa_required: 'true'
|
|
116
120
|
post_install_message:
|
|
117
121
|
rdoc_options: []
|
|
118
122
|
require_paths:
|
|
@@ -128,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
128
132
|
- !ruby/object:Gem::Version
|
|
129
133
|
version: '0'
|
|
130
134
|
requirements: []
|
|
131
|
-
rubygems_version: 3.
|
|
135
|
+
rubygems_version: 3.3.3
|
|
132
136
|
signing_key:
|
|
133
137
|
specification_version: 4
|
|
134
138
|
summary: An incredibly simple CMS for durable websites
|