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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 823433cd1bdae7bc4e3a6179451a875e7fbe5de17c4ceaa8a0b246cdd9fa3b61
4
- data.tar.gz: 778ef490bf23365d6e1fa1207d0f959ead5a59edbaebebeebe16837981a6c230
3
+ metadata.gz: e0b66a3140671bb0e77bceb609bf3474cd69aa960f7c3de3a9d9931b5c14bb6f
4
+ data.tar.gz: 8451171849fc29a01f773861b5436dedcf411b91578ace7847a08d02cb53389b
5
5
  SHA512:
6
- metadata.gz: cbbbd3c0ef92fabfcc08608adc376ce1272c36e30aa6f3b6d9e12d0ce5b9dd781b1fac4663262b144a85fe42ad951df396004f08518059820f6fc0c45d8a082c
7
- data.tar.gz: fee79490a8d086eee8779e985483f0b80e75b1ec0df657eb19ed8c74a3a2e5590a8c2e4f74f20ce529e1536e204c3f62574cc5c6ac777bc38c07ebcd1723f3b4
6
+ metadata.gz: 5712c5e1f67f602b667bad310b265991942ad9d4ac3a6fa6d8f1e0ca74808a7aeb4625ccc11a69ca9bec87bfbdf9066430c30e04e9c64c8817ce8f159d4dd003
7
+ data.tar.gz: 612032276d7031ec758f6023e4c11f438e2e74d7882c4afe359f0e95024ca2be04080662a7dfd04b24b2eb62620343ee22beee87b06ffe03c206a9c06848dfef
data/.rubocop.yml CHANGED
@@ -30,7 +30,7 @@ Metrics/BlockLength:
30
30
  Max: 150
31
31
 
32
32
  Metrics/ClassLength:
33
- Max: 150
33
+ Max: 250
34
34
 
35
35
  Metrics/CyclomaticComplexity:
36
36
  Max: 10
data/Gemfile CHANGED
@@ -7,4 +7,4 @@ gemspec
7
7
 
8
8
  gem 'rake', '~> 13.0'
9
9
  gem 'rspec', '~> 3.0'
10
- gem 'rubocop', '~> 1.22', require: false
10
+ gem 'rubocop', '~> 1.23', require: false
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.6'
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::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
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
 
@@ -2,52 +2,51 @@
2
2
 
3
3
  require 'liquid'
4
4
  require 'tomlrb'
5
- require 'tags/layout'
6
5
  require 'redcarpet'
7
-
8
- Liquid::Template.error_mode = :strict
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
- File.join(@config.root, @config.pages_dir), '%s.liquid'
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
- File.join(@config.root, @config.pages_dir), '_%s.liquid'
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 update_pages
59
- do_update_pages(File.join(@config.root, @config.pages_dir))
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
- if entry.end_with?('.liquid') && !(entry.start_with? '_')
76
- page_name = File.basename(entry,
77
- '.liquid')
78
- template_file = add_prefix.call(page_name)
79
- content = @file_system.read_template_file(template_file)
80
- content += dev_mode_content if @config.dev_mode
81
- @page_templates[add_prefix.call(page_name)] =
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 update_objects
89
- do_update_objects(File.join(@config.root,
90
- @config.objects_dir))
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 do_update_objects(dir)
144
+ def objects_for_template(template_path)
94
145
  objects = {}
95
- @object_types.each do |name, definition|
96
- objects[name] = {}
97
- obj_dir = File.join(dir, name)
98
- if File.directory? obj_dir
99
- Dir.foreach(obj_dir) do |file|
100
- if file.end_with? '.toml'
101
- object = Tomlrb.load_file(File.join(
102
- obj_dir, file
103
- ))
104
- object[:name] =
105
- File.basename(file, '.toml')
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[name] = sort_objects(objects[name])
159
+ objects[type] = sort_objects(objects[type])
111
160
  end
112
- @variables['objects'] = objects
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
- template.render(@variables)
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(@config.build_dir,
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
- return if @config.dev_mode
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
- FileUtils.copy_entry File.join(@config.root, asset_dir),
167
- File.join(@config.build_dir, asset_dir)
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
@@ -4,8 +4,8 @@ require 'tomlrb'
4
4
 
5
5
  module Archival
6
6
  class Config
7
- attr_reader :pages_dir, :objects_dir, :assets_dirs, :root, :build_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 static paths, just serve the files they refer to.
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 "We don't support continuations" unless fin
103
- raise 'We only support opcode 1' unless opcode == 1
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(@helper_dir, path)).read
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
@@ -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
- updated_pages = []
17
- updated_objects = []
18
- updated_assets = []
19
- (modified + added + removed).each do |file|
20
- case change_type(file)
21
- when :pages
22
- updated_pages << file
23
- when :objects
24
- updated_objects << file
25
- when :assets
26
- updated_assets << file
27
- end
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
- @server.refresh_client if rebuild?(builder, updated_objects,
30
- updated_pages, updated_assets)
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
- # layout and other assets. For now, this is everything.
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
- return :assets if child?(File.join(@config.root, 'layout'), file)
61
- return :assets if ['manifest.toml',
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, updated_objects, updated_pages, updated_assets)
68
- if updated_pages.empty? && updated_objects.empty? && updated_assets.empty?
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
- builder.update_objects if updated_objects.length
74
- builder.update_pages if updated_pages.length
75
- builder.full_rebuild if updated_assets.length
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Archival
4
- VERSION = '0.0.6'
4
+ VERSION = '0.0.10'
5
5
  end
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
- raise "Layout dir #{layout_path} not found" unless File.exist? layout_path
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
- raise "More than one layout named #{layout_name} found." if found_layout
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
- raise "No layouts named #{layout_name} found." if found_layout.nil?
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archival",
3
- "version": "0.0.6",
3
+ "version": "0.0.10",
4
4
  "description": "An incredibly simple CMS for durable websites",
5
5
  "bin": "build.rb",
6
6
  "directories": {
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.6
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: 2021-11-13 00:00:00.000000000 Z
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.2.22
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