archival 0.0.6 → 0.0.10

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