archival 0.0.1 → 0.0.6

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: 2cb0c34388e5643a954509fca99931fc9b60d2705ba12733ea890eed817a549e
4
- data.tar.gz: 29c8b6c12cd039be464211cd658b76cde06d4e20a8f2dad5b8f1921c0d40e76b
3
+ metadata.gz: 823433cd1bdae7bc4e3a6179451a875e7fbe5de17c4ceaa8a0b246cdd9fa3b61
4
+ data.tar.gz: 778ef490bf23365d6e1fa1207d0f959ead5a59edbaebebeebe16837981a6c230
5
5
  SHA512:
6
- metadata.gz: 1a6015cc2dafc8335acd969a1616b1842e8fb5afa9a6d966920d5399792a9fc774313e67b2c8467767f408f32eddc33660f7389954a515af6dcd386392ee74ba
7
- data.tar.gz: 3b076507b17cde33ef678c8ced6a84efa4e69c927017dc411b6c4a9475eff5344cbeafd11396c5185fa8e3638854ae40e91ef9756a8c02303d2781839e7f886c
6
+ metadata.gz: cbbbd3c0ef92fabfcc08608adc376ce1272c36e30aa6f3b6d9e12d0ce5b9dd781b1fac4663262b144a85fe42ad951df396004f08518059820f6fc0c45d8a082c
7
+ data.tar.gz: fee79490a8d086eee8779e985483f0b80e75b1ec0df657eb19ed8c74a3a2e5590a8c2e4f74f20ce529e1536e204c3f62574cc5c6ac777bc38c07ebcd1723f3b4
data/.rubocop.yml CHANGED
@@ -1,14 +1,17 @@
1
+ inherit_mode:
2
+ merge:
3
+ - Exclude
4
+
1
5
  AllCops:
2
6
  NewCops: enable
3
7
  SuggestExtensions: false
4
8
  Exclude:
5
- - bin/bundle
9
+ - bin/*
6
10
 
7
11
  Layout/LineLength:
8
12
  Max: 80
9
13
  Exclude:
10
14
  - archival.gemspec
11
- - bin/*
12
15
  - spec/spec_helper.rb
13
16
 
14
17
  Layout/TrailingWhitespace:
@@ -29,6 +32,18 @@ Metrics/BlockLength:
29
32
  Metrics/ClassLength:
30
33
  Max: 150
31
34
 
35
+ Metrics/CyclomaticComplexity:
36
+ Max: 10
37
+ Exclude:
38
+ # This file does a lot of defaulting. It's easy to read.
39
+ - lib/archival/config.rb
40
+
41
+ Metrics/PerceivedComplexity:
42
+ Max: 10
43
+ Exclude:
44
+ # This file does a lot of defaulting. It's easy to read.
45
+ - lib/archival/config.rb
46
+
32
47
  Lint/ImplicitStringConcatenation:
33
48
  Exclude:
34
49
  - spec/tags/layout_spec.rb
data/Rakefile CHANGED
@@ -10,3 +10,4 @@ RuboCop::RakeTask.new
10
10
 
11
11
  task default: :spec
12
12
  task lint: :rubocop
13
+ task 'lint:fix': 'rubocop:auto_correct'
data/action.yml CHANGED
@@ -11,4 +11,5 @@ runs:
11
11
  with:
12
12
  ruby-version: 3.0
13
13
  bundler-cache: true
14
- - run: bin/archival build
14
+ - run: bin/archival build
15
+ shell: bash
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.1'
5
+ s.version = '0.0.6'
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']
@@ -24,5 +24,6 @@ Gem::Specification.new do |s|
24
24
 
25
25
  s.add_dependency 'liquid', '~> 5.1.0'
26
26
  s.add_dependency 'listen', '~> 3.7.0'
27
+ s.add_dependency 'redcarpet', '~> 3.5.1'
27
28
  s.add_dependency 'tomlrb', '~> 2.0.1'
28
29
  end
data/bin/archival CHANGED
@@ -8,16 +8,14 @@
8
8
  # this file is here to facilitate running it.
9
9
  #
10
10
 
11
- require 'pathname'
12
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
13
- Pathname.new(__FILE__).realpath)
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
14
 
15
- bundle_binstub = File.expand_path('bundle',
16
- __dir__)
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
17
16
 
18
17
  if File.file?(bundle_binstub)
19
- if File.read(bundle_binstub,
20
- 300) =~ /This file was generated by Bundler/
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
21
19
  load(bundle_binstub)
22
20
  else
23
21
  abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
@@ -25,7 +23,7 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this
25
23
  end
26
24
  end
27
25
 
28
- require 'rubygems'
29
- require 'bundler/setup'
26
+ require "rubygems"
27
+ require "bundler/setup"
30
28
 
31
- load Gem.bin_path('archival', 'archival')
29
+ load Gem.bin_path("archival", "archival")
data/bin/bundle CHANGED
@@ -8,46 +8,46 @@
8
8
  # this file is here to facilitate running it.
9
9
  #
10
10
 
11
- require 'rubygems'
11
+ require "rubygems"
12
12
 
13
13
  m = Module.new do
14
14
  module_function
15
15
 
16
16
  def invoked_as_script?
17
- File.expand_path($PROGRAM_NAME) == File.expand_path(__FILE__)
17
+ File.expand_path($0) == File.expand_path(__FILE__)
18
18
  end
19
19
 
20
20
  def env_var_version
21
- ENV['BUNDLER_VERSION']
21
+ ENV["BUNDLER_VERSION"]
22
22
  end
23
23
 
24
24
  def cli_arg_version
25
25
  return unless invoked_as_script? # don't want to hijack other binstubs
26
- return unless 'update'.start_with?(ARGV.first || ' ') # must be running `bundle update`
27
-
26
+ return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
28
27
  bundler_version = nil
29
28
  update_index = nil
30
29
  ARGV.each_with_index do |a, i|
31
- bundler_version = a if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
30
+ if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
31
+ bundler_version = a
32
+ end
32
33
  next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
33
-
34
- bundler_version = Regexp.last_match(1)
34
+ bundler_version = $1
35
35
  update_index = i
36
36
  end
37
37
  bundler_version
38
38
  end
39
39
 
40
40
  def gemfile
41
- gemfile = ENV['BUNDLE_GEMFILE']
41
+ gemfile = ENV["BUNDLE_GEMFILE"]
42
42
  return gemfile if gemfile && !gemfile.empty?
43
43
 
44
- File.expand_path('../Gemfile', __dir__)
44
+ File.expand_path("../../Gemfile", __FILE__)
45
45
  end
46
46
 
47
47
  def lockfile
48
48
  lockfile =
49
49
  case File.basename(gemfile)
50
- when 'gems.rb' then gemfile.sub(/\.rb$/, gemfile)
50
+ when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
51
51
  else "#{gemfile}.lock"
52
52
  end
53
53
  File.expand_path(lockfile)
@@ -55,17 +55,15 @@ m = Module.new do
55
55
 
56
56
  def lockfile_version
57
57
  return unless File.file?(lockfile)
58
-
59
58
  lockfile_contents = File.read(lockfile)
60
59
  return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
61
-
62
60
  Regexp.last_match(1)
63
61
  end
64
62
 
65
63
  def bundler_requirement
66
64
  @bundler_requirement ||=
67
65
  env_var_version || cli_arg_version ||
68
- bundler_requirement_for(lockfile_version)
66
+ bundler_requirement_for(lockfile_version)
69
67
  end
70
68
 
71
69
  def bundler_requirement_for(version)
@@ -75,32 +73,28 @@ m = Module.new do
75
73
 
76
74
  requirement = bundler_gem_version.approximate_recommendation
77
75
 
78
- return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new('2.7.0')
76
+ return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
79
77
 
80
- requirement += '.a' if bundler_gem_version.prerelease?
78
+ requirement += ".a" if bundler_gem_version.prerelease?
81
79
 
82
80
  requirement
83
81
  end
84
82
 
85
83
  def load_bundler!
86
- ENV['BUNDLE_GEMFILE'] ||= gemfile
84
+ ENV["BUNDLE_GEMFILE"] ||= gemfile
87
85
 
88
86
  activate_bundler
89
87
  end
90
88
 
91
89
  def activate_bundler
92
90
  gem_error = activation_error_handling do
93
- gem 'bundler', bundler_requirement
91
+ gem "bundler", bundler_requirement
94
92
  end
95
93
  return if gem_error.nil?
96
-
97
94
  require_error = activation_error_handling do
98
- require 'bundler/version'
99
- end
100
- if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
101
- return
95
+ require "bundler/version"
102
96
  end
103
-
97
+ return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
104
98
  warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
105
99
  exit 42
106
100
  end
@@ -115,4 +109,6 @@ end
115
109
 
116
110
  m.load_bundler!
117
111
 
118
- load Gem.bin_path('bundler', 'bundle') if m.invoked_as_script?
112
+ if m.invoked_as_script?
113
+ load Gem.bin_path("bundler", "bundle")
114
+ end
data/bin/htmldiff CHANGED
@@ -8,16 +8,14 @@
8
8
  # this file is here to facilitate running it.
9
9
  #
10
10
 
11
- require 'pathname'
12
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
13
- Pathname.new(__FILE__).realpath)
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
14
 
15
- bundle_binstub = File.expand_path('bundle',
16
- __dir__)
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
17
16
 
18
17
  if File.file?(bundle_binstub)
19
- if File.read(bundle_binstub,
20
- 300) =~ /This file was generated by Bundler/
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
21
19
  load(bundle_binstub)
22
20
  else
23
21
  abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
@@ -25,7 +23,7 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this
25
23
  end
26
24
  end
27
25
 
28
- require 'rubygems'
29
- require 'bundler/setup'
26
+ require "rubygems"
27
+ require "bundler/setup"
30
28
 
31
- load Gem.bin_path('diff-lcs', 'htmldiff')
29
+ load Gem.bin_path("diff-lcs", "htmldiff")
data/bin/ldiff CHANGED
@@ -8,16 +8,14 @@
8
8
  # this file is here to facilitate running it.
9
9
  #
10
10
 
11
- require 'pathname'
12
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
13
- Pathname.new(__FILE__).realpath)
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
14
 
15
- bundle_binstub = File.expand_path('bundle',
16
- __dir__)
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
17
16
 
18
17
  if File.file?(bundle_binstub)
19
- if File.read(bundle_binstub,
20
- 300) =~ /This file was generated by Bundler/
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
21
19
  load(bundle_binstub)
22
20
  else
23
21
  abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
@@ -25,7 +23,7 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this
25
23
  end
26
24
  end
27
25
 
28
- require 'rubygems'
29
- require 'bundler/setup'
26
+ require "rubygems"
27
+ require "bundler/setup"
30
28
 
31
- load Gem.bin_path('diff-lcs', 'ldiff')
29
+ load Gem.bin_path("diff-lcs", "ldiff")
data/bin/listen CHANGED
@@ -8,16 +8,14 @@
8
8
  # this file is here to facilitate running it.
9
9
  #
10
10
 
11
- require 'pathname'
12
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
13
- Pathname.new(__FILE__).realpath)
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
14
 
15
- bundle_binstub = File.expand_path('bundle',
16
- __dir__)
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
17
16
 
18
17
  if File.file?(bundle_binstub)
19
- if File.read(bundle_binstub,
20
- 300) =~ /This file was generated by Bundler/
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
21
19
  load(bundle_binstub)
22
20
  else
23
21
  abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
@@ -25,7 +23,7 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this
25
23
  end
26
24
  end
27
25
 
28
- require 'rubygems'
29
- require 'bundler/setup'
26
+ require "rubygems"
27
+ require "bundler/setup"
30
28
 
31
- load Gem.bin_path('listen', 'listen')
29
+ load Gem.bin_path("listen", "listen")
data/bin/rake CHANGED
@@ -8,16 +8,14 @@
8
8
  # this file is here to facilitate running it.
9
9
  #
10
10
 
11
- require 'pathname'
12
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
13
- Pathname.new(__FILE__).realpath)
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
14
 
15
- bundle_binstub = File.expand_path('bundle',
16
- __dir__)
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
17
16
 
18
17
  if File.file?(bundle_binstub)
19
- if File.read(bundle_binstub,
20
- 300) =~ /This file was generated by Bundler/
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
21
19
  load(bundle_binstub)
22
20
  else
23
21
  abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
@@ -25,7 +23,7 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this
25
23
  end
26
24
  end
27
25
 
28
- require 'rubygems'
29
- require 'bundler/setup'
26
+ require "rubygems"
27
+ require "bundler/setup"
30
28
 
31
- load Gem.bin_path('rake', 'rake')
29
+ load Gem.bin_path("rake", "rake")
data/bin/redcarpet ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'redcarpet' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("redcarpet", "redcarpet")
data/bin/rspec CHANGED
@@ -8,16 +8,14 @@
8
8
  # this file is here to facilitate running it.
9
9
  #
10
10
 
11
- require 'pathname'
12
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
13
- Pathname.new(__FILE__).realpath)
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
14
 
15
- bundle_binstub = File.expand_path('bundle',
16
- __dir__)
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
17
16
 
18
17
  if File.file?(bundle_binstub)
19
- if File.read(bundle_binstub,
20
- 300) =~ /This file was generated by Bundler/
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
21
19
  load(bundle_binstub)
22
20
  else
23
21
  abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
@@ -25,7 +23,7 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this
25
23
  end
26
24
  end
27
25
 
28
- require 'rubygems'
29
- require 'bundler/setup'
26
+ require "rubygems"
27
+ require "bundler/setup"
30
28
 
31
- load Gem.bin_path('rspec-core', 'rspec')
29
+ load Gem.bin_path("rspec-core", "rspec")
data/bin/rubocop CHANGED
@@ -8,16 +8,14 @@
8
8
  # this file is here to facilitate running it.
9
9
  #
10
10
 
11
- require 'pathname'
12
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
13
- Pathname.new(__FILE__).realpath)
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
14
 
15
- bundle_binstub = File.expand_path('bundle',
16
- __dir__)
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
17
16
 
18
17
  if File.file?(bundle_binstub)
19
- if File.read(bundle_binstub,
20
- 300) =~ /This file was generated by Bundler/
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
21
19
  load(bundle_binstub)
22
20
  else
23
21
  abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
@@ -25,7 +23,7 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this
25
23
  end
26
24
  end
27
25
 
28
- require 'rubygems'
29
- require 'bundler/setup'
26
+ require "rubygems"
27
+ require "bundler/setup"
30
28
 
31
- load Gem.bin_path('rubocop', 'rubocop')
29
+ load Gem.bin_path("rubocop", "rubocop")
data/bin/ruby-parse CHANGED
@@ -8,16 +8,14 @@
8
8
  # this file is here to facilitate running it.
9
9
  #
10
10
 
11
- require 'pathname'
12
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
13
- Pathname.new(__FILE__).realpath)
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
14
 
15
- bundle_binstub = File.expand_path('bundle',
16
- __dir__)
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
17
16
 
18
17
  if File.file?(bundle_binstub)
19
- if File.read(bundle_binstub,
20
- 300) =~ /This file was generated by Bundler/
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
21
19
  load(bundle_binstub)
22
20
  else
23
21
  abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
@@ -25,7 +23,7 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this
25
23
  end
26
24
  end
27
25
 
28
- require 'rubygems'
29
- require 'bundler/setup'
26
+ require "rubygems"
27
+ require "bundler/setup"
30
28
 
31
- load Gem.bin_path('parser', 'ruby-parse')
29
+ load Gem.bin_path("parser", "ruby-parse")
data/bin/ruby-rewrite CHANGED
@@ -8,16 +8,14 @@
8
8
  # this file is here to facilitate running it.
9
9
  #
10
10
 
11
- require 'pathname'
12
- ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile',
13
- Pathname.new(__FILE__).realpath)
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
14
 
15
- bundle_binstub = File.expand_path('bundle',
16
- __dir__)
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
17
16
 
18
17
  if File.file?(bundle_binstub)
19
- if File.read(bundle_binstub,
20
- 300) =~ /This file was generated by Bundler/
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
21
19
  load(bundle_binstub)
22
20
  else
23
21
  abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
@@ -25,7 +23,7 @@ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this
25
23
  end
26
24
  end
27
25
 
28
- require 'rubygems'
29
- require 'bundler/setup'
26
+ require "rubygems"
27
+ require "bundler/setup"
30
28
 
31
- load Gem.bin_path('parser', 'ruby-rewrite')
29
+ load Gem.bin_path("parser", "ruby-rewrite")
data/exe/archival CHANGED
@@ -19,17 +19,13 @@ build_dir = Dir.pwd
19
19
 
20
20
  case command
21
21
  when 'build'
22
- builder = Archival::Builder('root' => build_dir)
23
- builder.write_all
24
- when 'run'
25
- Archival.listen(build_dir)
26
- begin
27
- sleep
28
- rescue Interrupt
29
- # Don't print a stack when a user interrupts, as this is the right way to
30
- # stop the development server.
31
- puts ''
22
+ Archival::Logger.benchmark('built') do
23
+ config = Archival::Config.new('root' => build_dir)
24
+ builder = Archival::Builder.new(config)
25
+ builder.write_all
32
26
  end
27
+ when 'run'
28
+ Archival.listen('root' => build_dir)
33
29
  else
34
30
  # print help
35
31
  puts 'archival [command]'
@@ -0,0 +1,91 @@
1
+ /**
2
+ * When running archival in development mode (archival run), this file is
3
+ * injected into all pages, and is responsible for reloading the page when the
4
+ * source has changed.
5
+ */
6
+
7
+ (function () {
8
+ const remotePort = $PORT;
9
+ const CONNECTING_COLOR = "#bd270d";
10
+ const CONNECTED_COLOR = "#19bd0d";
11
+ const CHECK_INTERVAL = 500;
12
+ const DISCONNECTED_INTERVAL = 1000;
13
+ const connectionDot = document.createElement("div");
14
+ connectionDot.style = `position: absolute; z-index: 9999; bottom: 10px; right: 10px; background-color: ${CONNECTING_COLOR}; width: 15px; height: 15px; border-radius: 50%; opacity: 0.8;`;
15
+ connectionDot.setAttribute("title", "Archival Dev Server: Connecting");
16
+ connectionDot.addEventListener(
17
+ "mouseenter",
18
+ () => (connectionDot.style.opacity = 0.2)
19
+ );
20
+ connectionDot.addEventListener(
21
+ "mouseleave",
22
+ () => (connectionDot.style.opacity = 0.8)
23
+ );
24
+
25
+ let lastContact = -1;
26
+ let isConnecting = false;
27
+ let connection;
28
+
29
+ function connectionLoop() {
30
+ connection.send(`page:${window.location.pathname}`);
31
+ if (Date.now() - lastContact > DISCONNECTED_INTERVAL) {
32
+ setConnected(false);
33
+ connectSocket();
34
+ }
35
+ setTimeout(connectionLoop, CHECK_INTERVAL);
36
+ }
37
+
38
+ function setConnected(connected) {
39
+ connectionDot.style.backgroundColor = connected
40
+ ? CONNECTED_COLOR
41
+ : CONNECTING_COLOR;
42
+ connectionDot.setAttribute(
43
+ "title",
44
+ `Archival Dev Server: ${connected ? "Connected" : "Disconnected"}`
45
+ );
46
+ }
47
+
48
+ window.onload = () => {
49
+ connectSocket(true);
50
+ };
51
+
52
+ function connectSocket(init) {
53
+ if (isConnecting) {
54
+ return;
55
+ }
56
+ isConnecting = true;
57
+ console.log(
58
+ `${init ? "connecting" : "reconnecting"} to archival dev server...`
59
+ );
60
+ document.body.appendChild(connectionDot);
61
+ connection = new WebSocket(`ws://localhost:${remotePort}`);
62
+ connection.onerror = () => {
63
+ isConnecting = false;
64
+ };
65
+
66
+ connection.onopen = () => {
67
+ isConnecting = false;
68
+ connection.send("connected");
69
+ if (init) {
70
+ connectionLoop();
71
+ }
72
+ };
73
+ connection.onmessage = (event) => {
74
+ lastContact = Date.now();
75
+ switch (event.data) {
76
+ case "ready":
77
+ console.log("connected to archival dev server.");
78
+ break;
79
+ case "ok":
80
+ setConnected(true);
81
+ break;
82
+ case "refresh":
83
+ window.location.reload();
84
+ break;
85
+ default:
86
+ console.log(`receieved unexpected message ${event.data}`);
87
+ break;
88
+ }
89
+ };
90
+ }
91
+ })();
@@ -3,39 +3,58 @@
3
3
  require 'liquid'
4
4
  require 'tomlrb'
5
5
  require 'tags/layout'
6
+ require 'redcarpet'
6
7
 
7
8
  Liquid::Template.error_mode = :strict
8
9
  Liquid::Template.register_tag('layout', Layout)
9
10
 
10
11
  module Archival
12
+ class DuplicateKeyError < StandardError
13
+ end
14
+
11
15
  class Builder
12
16
  attr_reader :page_templates
13
17
 
14
18
  def initialize(config, *_args)
15
- @config = Config.new(config)
19
+ @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
+ )
16
28
  refresh_config
17
29
  end
18
30
 
19
31
  def refresh_config
20
32
  @file_system = Liquid::LocalFileSystem.new(
21
- @config.root, '%s.liquid'
33
+ File.join(@config.root, @config.pages_dir), '%s.liquid'
22
34
  )
23
35
  @variables = {}
24
36
  @object_types = {}
25
37
  @page_templates = {}
26
38
 
27
- Liquid::Template.file_system = @file_system
39
+ Liquid::Template.file_system = Liquid::LocalFileSystem.new(
40
+ File.join(@config.root, @config.pages_dir), '_%s.liquid'
41
+ )
28
42
 
29
43
  objects_definition_file = File.join(@config.root,
30
44
  'objects.toml')
31
45
  if File.file? objects_definition_file
32
- @object_types = read_toml(objects_definition_file)
46
+ @object_types = Tomlrb.load_file(objects_definition_file)
33
47
  end
34
48
 
35
49
  update_pages
36
50
  update_objects
37
51
  end
38
52
 
53
+ def full_rebuild
54
+ Layout.reset_cache
55
+ refresh_config
56
+ end
57
+
39
58
  def update_pages
40
59
  do_update_pages(File.join(@config.root, @config.pages_dir))
41
60
  end
@@ -56,11 +75,9 @@ module Archival
56
75
  if entry.end_with?('.liquid') && !(entry.start_with? '_')
57
76
  page_name = File.basename(entry,
58
77
  '.liquid')
59
- template_file = File.join(
60
- @config.pages_dir,
61
- add_prefix.call(page_name)
62
- )
78
+ template_file = add_prefix.call(page_name)
63
79
  content = @file_system.read_template_file(template_file)
80
+ content += dev_mode_content if @config.dev_mode
64
81
  @page_templates[add_prefix.call(page_name)] =
65
82
  Liquid::Template.parse(content)
66
83
  end
@@ -75,30 +92,50 @@ module Archival
75
92
 
76
93
  def do_update_objects(dir)
77
94
  objects = {}
78
- @object_types.each do |name, _definition|
79
- objects[name] = []
95
+ @object_types.each do |name, definition|
96
+ objects[name] = {}
80
97
  obj_dir = File.join(dir, name)
81
98
  if File.directory? obj_dir
82
99
  Dir.foreach(obj_dir) do |file|
83
100
  if file.end_with? '.toml'
84
- object = read_toml(File.join(
85
- obj_dir, file
86
- ))
101
+ object = Tomlrb.load_file(File.join(
102
+ obj_dir, file
103
+ ))
87
104
  object[:name] =
88
105
  File.basename(file, '.toml')
89
- objects[name].push object
106
+ objects[name][object[:name]] = parse_object(object, definition)
90
107
  end
91
108
  end
92
109
  end
93
- objects[name] = objects[name].sort do |a, b|
94
- (a['order'] || a[:name]).to_s <=> (b['order'] || b[:name]).to_s
95
- end
110
+ objects[name] = sort_objects(objects[name])
96
111
  end
97
112
  @variables['objects'] = objects
98
113
  end
99
114
 
100
- def read_toml(file_path)
101
- Tomlrb.load_file(file_path)
115
+ def sort_objects(objects)
116
+ # Sort by either 'order' key or object name, depending on what is
117
+ # available.
118
+ sorted_by_keys = objects.sort_by do |name, obj|
119
+ obj.key?('order') ? obj['order'].to_s : name
120
+ end
121
+ sorted_objects = Archival::TemplateArray.new
122
+ sorted_by_keys.each do |d|
123
+ raise DuplicateKeyError if sorted_objects.key?(d[0])
124
+
125
+ sorted_objects.push(d[1])
126
+ sorted_objects[d[0]] = d[1]
127
+ end
128
+ sorted_objects
129
+ end
130
+
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
102
139
  end
103
140
 
104
141
  def set_var(name, value)
@@ -122,6 +159,19 @@ module Archival
122
159
  file.write(render(template))
123
160
  end
124
161
  end
162
+ return if @config.dev_mode
163
+
164
+ # in production, also copy all assets to the dist folder.
165
+ @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)
168
+ end
169
+ end
170
+
171
+ private
172
+
173
+ def dev_mode_content
174
+ "<script src=\"http://localhost:#{@config.helper_port}/js/archival-helper.js\" type=\"application/javascript\"></script>" # rubocop:disable Layout/LineLength
125
175
  end
126
176
  end
127
177
  end
@@ -1,16 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'tomlrb'
4
+
3
5
  module Archival
4
6
  class Config
5
- attr_reader :pages_dir, :objects_dir, :root, :build_dir
7
+ attr_reader :pages_dir, :objects_dir, :assets_dirs, :root, :build_dir,
8
+ :helper_port, :dev_mode
6
9
 
7
- def initialize(config)
8
- @pages_dir = config['pages'] || 'pages'
9
- @objects_dir = config['objects'] || 'objects'
10
+ def initialize(config = {})
10
11
  @root = config['root'] || Dir.pwd
11
- @build_dir = config['build_dir'] || File.join(
12
+ manifest = load_manifest
13
+ @pages_dir = config['pages'] || manifest['pages'] || 'pages'
14
+ @objects_dir = config['objects'] || manifest['objects'] || 'objects'
15
+ @build_dir = config['build_dir'] || manifest['build_dir'] || File.join(
12
16
  @root, 'dist'
13
17
  )
18
+ @helper_port = config['helper_port'] || manifest['helper_port'] || 2701
19
+ @assets_dirs = config['assets_dirs'] || manifest['assets'] || []
20
+ @dev_mode = config[:dev_mode] || false
21
+ end
22
+
23
+ def load_manifest
24
+ manifest_file = File.join(@root, 'manifest.toml')
25
+ return Tomlrb.load_file(manifest_file) if File.file? manifest_file
26
+
27
+ {}
14
28
  end
15
29
  end
16
30
  end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'open-uri'
5
+
6
+ module Archival
7
+ class HelperServer
8
+ attr_reader :page
9
+
10
+ def initialize(port, build_dir)
11
+ @port = port
12
+ @build_dir = build_dir
13
+ @helper_dir = File.expand_path(File.join(File.dirname(__FILE__),
14
+ '../../helper'))
15
+ end
16
+
17
+ def start
18
+ server = TCPServer.new @port
19
+ loop do
20
+ Thread.start(server.accept) do |client|
21
+ req = ''
22
+ method = nil
23
+ path = nil
24
+ while (line = client.gets) && (line != "\r\n")
25
+ unless method
26
+ req_info = line.split
27
+ method = req_info[0]
28
+ path = req_info[1]
29
+ end
30
+ req += line
31
+ end
32
+ client.close unless req
33
+ handle_request(client, req, method, path)
34
+ end
35
+ end
36
+ end
37
+
38
+ def refresh_client
39
+ ws_sendmessage('refresh')
40
+ end
41
+
42
+ private
43
+
44
+ MAGIC_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
45
+
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.
49
+ http_response(client, type: 'application/javascript') do
50
+ serve_static(client, path)
51
+ end
52
+ client.close
53
+ elsif (matches = req.match(/^Sec-WebSocket-Key: (\S+)/))
54
+ websocket_key = matches[1]
55
+ # puts "Websocket handshake detected with key: #{websocket_key}"
56
+ connect_socket(client, websocket_key)
57
+ else
58
+ client.close
59
+ end
60
+ end
61
+
62
+ def connect_socket(client, websocket_key)
63
+ @socket = client
64
+ response_key = Digest::SHA1.base64digest([websocket_key,
65
+ MAGIC_GUID].join)
66
+ # puts "Responding to handshake with key: #{response_key}"
67
+
68
+ @socket.write "HTTP/1.1 101 Switching Protocols\r\n"
69
+ @socket.write "Upgrade: websocket\r\n"
70
+ @socket.write "Connection: Upgrade\r\n"
71
+ @socket.write "Sec-WebSocket-Accept: #{response_key}\r\n"
72
+ @socket.write "\r\n"
73
+
74
+ # puts 'Handshake completed.'
75
+ ws_loop
76
+ end
77
+
78
+ def ws_loop
79
+ loop do
80
+ msg = ws_getmessage
81
+ next unless msg
82
+
83
+ if msg == 'connected'
84
+ ws_sendmessage('ready')
85
+ elsif msg.start_with?('page:')
86
+ page_path = Pathname.new(msg.sub(/^page:/, ''))
87
+ @page = page_path.relative_path_from(@build_dir)
88
+ ws_sendmessage('ok')
89
+ end
90
+ end
91
+ end
92
+
93
+ def validate_ws_message
94
+ first_byte = @socket.getbyte
95
+ return unless first_byte
96
+
97
+ fin = first_byte & 0b10000000
98
+ opcode = first_byte & 0b00001111
99
+
100
+ # Our server only supports single-frame, text messages.
101
+ # 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
104
+
105
+ second_byte = @socket.getbyte
106
+ is_masked = second_byte & 0b10000000
107
+ payload_size = second_byte & 0b01111111
108
+
109
+ raise 'frame masked incorrectly' unless is_masked
110
+ raise 'payload must be < 126 bytes in length' unless payload_size < 126
111
+
112
+ payload_size
113
+ end
114
+
115
+ def ws_getmessage
116
+ payload_size = validate_ws_message
117
+ return unless payload_size
118
+
119
+ # warn "Payload size: #{payload_size} bytes"
120
+
121
+ mask = 4.times.map { @socket.getbyte }
122
+ # warn "Got mask: #{mask.inspect}"
123
+
124
+ data = payload_size.times.map { @socket.getbyte }
125
+ # warn "Got masked data: #{data.inspect}"
126
+
127
+ unmasked_data = data.each_with_index.map do |byte, i|
128
+ byte ^ mask[i % 4]
129
+ end
130
+ # warn "Unmasked the data: #{unmasked_data.inspect}"
131
+
132
+ unmasked_data.pack('C*').force_encoding('utf-8')
133
+ end
134
+
135
+ def ws_sendmessage(message)
136
+ return unless @socket
137
+
138
+ output = [0b10000001, message.size, message]
139
+ @socket.write output.pack("CCA#{message.size}")
140
+ end
141
+
142
+ def serve_static(client, path)
143
+ buffer = File.open(File.join(@helper_dir, path)).read
144
+ buffer.sub! '$PORT', @port.to_s
145
+ client.print buffer
146
+ end
147
+
148
+ def http_response(client, config)
149
+ status = config[:status] ||= 200
150
+ type = config[:type] ||= 'text/html'
151
+ client.print "HTTP/1.1 #{status}\r\n"
152
+ client.print "Content-Type: #{type}\r\n"
153
+ client.print "\r\n"
154
+ yield
155
+ end
156
+ end
157
+ end
@@ -4,40 +4,83 @@ require 'listen'
4
4
  require 'pathname'
5
5
 
6
6
  module Archival
7
- def self.child?(parent, child)
8
- path = Pathname.new(child)
9
- return true if path.fnmatch?(File.join(parent, '**'))
10
-
11
- false
7
+ def listen(config = {})
8
+ @config = Config.new(config.merge(dev_mode: true))
9
+ builder = Builder.new(@config)
10
+ Logger.benchmark('built') do
11
+ builder.write_all
12
+ end
13
+ ignore = %r{/dist/}
14
+ listener = Listen.to(@config.root,
15
+ 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
28
+ end
29
+ @server.refresh_client if rebuild?(builder, updated_objects,
30
+ updated_pages, updated_assets)
31
+ end
32
+ listener.start
33
+ serve_helpers
12
34
  end
13
35
 
14
- def self.process_change?(file, builder)
15
- if child?(File.join(@config.root, @config.pages_dir), file)
36
+ module_function :listen
37
+
38
+ class << self
39
+ private
40
+
41
+ def child?(parent, child)
42
+ path = Pathname.new(child)
43
+ return true if path.fnmatch?(File.join(parent, '**'))
44
+
45
+ false
46
+ end
47
+
48
+ def change_type(file)
16
49
  # a page was modified, rebuild the pages.
17
- builder.update_pages
18
- return true
19
- elsif child?(File.join(@config.root, @config.objects_dir), file)
50
+ return :pages if child?(File.join(@config.root, @config.pages_dir),
51
+ file)
20
52
  # an object was modified, rebuild the objects.
21
- builder.update_objects
22
- return true
53
+ return :objects if child?(File.join(@config.root, @config.objects_dir),
54
+ file)
55
+
56
+ # layout and other assets. For now, this is everything.
57
+ @config.assets_dirs.each do |dir|
58
+ return :assets if child?(File.join(@config.root, dir), file)
59
+ end
60
+ return :assets if child?(File.join(@config.root, 'layout'), file)
61
+ return :assets if ['manifest.toml',
62
+ 'objects.toml'].include? File.basename(file)
63
+
64
+ :none
23
65
  end
24
- false
25
- end
26
66
 
27
- def listen(config)
28
- @config = Config.new(config)
29
- builder = Builder.new(config)
30
- builder.write_all
31
- listener = Listen.to(@config.root) do |modified, added, removed|
32
- needs_update = false
33
- (modified + added + removed).each do |file|
34
- needs_update = true if process_change?(file, builder)
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
71
+
72
+ 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
76
+ builder.write_all
35
77
  end
36
- builder.write_all if needs_update
78
+ true
37
79
  end
38
- listener.start
39
- listener
40
- end
41
80
 
42
- module_function :listen
81
+ def serve_helpers
82
+ @server = HelperServer.new(@config.helper_port, @config.build_dir)
83
+ @server.start
84
+ end
85
+ end
43
86
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'benchmark'
4
+
5
+ module Archival
6
+ class Logger
7
+ def self.benchmark(message, &block)
8
+ Benchmark.bm do |bm|
9
+ bm.report(message, &block)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -27,19 +27,15 @@ class RakeTasks
27
27
  build_dir = Dir.pwd
28
28
 
29
29
  task 'build' do
30
- builder = Archival::Builder.new('root' => build_dir)
31
- builder.write_all
30
+ Archival::Logger.benchmark('built') do
31
+ config = Archival::Config.new('root' => build_dir)
32
+ builder = Archival::Builder.new(config)
33
+ builder.write_all
34
+ end
32
35
  end
33
36
 
34
37
  task 'run' do
35
- Archival.listen(build_dir)
36
- begin
37
- sleep
38
- rescue Interrupt
39
- # Don't print a stack when a user interrupts, as this is the right way
40
- # to stop the development server.
41
- puts ''
42
- end
38
+ Archival.listen('root' => build_dir)
43
39
  end
44
40
 
45
41
  RakeTasks.instance = self
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Archival
4
- VERSION = '0.0.1'
4
+ VERSION = '0.0.6'
5
5
  end
data/lib/archival.rb CHANGED
@@ -5,6 +5,9 @@ module Archival
5
5
  end
6
6
 
7
7
  require 'archival/version'
8
+ require 'archival/template_array'
9
+ require 'archival/logger'
8
10
  require 'archival/config'
11
+ require 'archival/helper_server'
9
12
  require 'archival/builder'
10
13
  require 'archival/listen'
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "archival",
3
- "version": "0.0.1",
3
+ "version": "0.0.6",
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.1
4
+ version: 0.0.6
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-05 00:00:00.000000000 Z
11
+ date: 2021-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: liquid
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: 3.7.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: redcarpet
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.5.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.5.1
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: tomlrb
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -76,17 +90,22 @@ files:
76
90
  - bin/ldiff
77
91
  - bin/listen
78
92
  - bin/rake
93
+ - bin/redcarpet
79
94
  - bin/rspec
80
95
  - bin/rubocop
81
96
  - bin/ruby-parse
82
97
  - bin/ruby-rewrite
83
98
  - bin/setup
84
99
  - exe/archival
100
+ - helper/js/archival-helper.js
85
101
  - lib/archival.rb
86
102
  - lib/archival/builder.rb
87
103
  - lib/archival/config.rb
104
+ - lib/archival/helper_server.rb
88
105
  - lib/archival/listen.rb
106
+ - lib/archival/logger.rb
89
107
  - lib/archival/rake_tasks.rb
108
+ - lib/archival/template_array.rb
90
109
  - lib/archival/version.rb
91
110
  - lib/tags/layout.rb
92
111
  - package.json