jekyll-minibundle 2.1.2 → 2.2.0

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +96 -42
  3. data/LICENSE.txt +1 -1
  4. data/README.md +232 -52
  5. data/Rakefile +16 -0
  6. data/jekyll-minibundle.gemspec +7 -6
  7. data/lib/jekyll/minibundle/asset_bundle.rb +3 -3
  8. data/lib/jekyll/minibundle/asset_file_drop.rb +45 -0
  9. data/lib/jekyll/minibundle/asset_file_properties.rb +12 -8
  10. data/lib/jekyll/minibundle/asset_file_registry.rb +4 -4
  11. data/lib/jekyll/minibundle/asset_tag_markup.rb +21 -20
  12. data/lib/jekyll/minibundle/bundle_file.rb +6 -1
  13. data/lib/jekyll/minibundle/development_file.rb +2 -1
  14. data/lib/jekyll/minibundle/development_file_collection.rb +2 -2
  15. data/lib/jekyll/minibundle/environment.rb +13 -15
  16. data/lib/jekyll/minibundle/files.rb +16 -18
  17. data/lib/jekyll/minibundle/hashes.rb +10 -12
  18. data/lib/jekyll/minibundle/log.rb +5 -7
  19. data/lib/jekyll/minibundle/mini_bundle_block.rb +35 -12
  20. data/lib/jekyll/minibundle/mini_stamp_tag.rb +108 -16
  21. data/lib/jekyll/minibundle/stamp_file.rb +1 -0
  22. data/lib/jekyll/minibundle/variable_template.rb +145 -0
  23. data/lib/jekyll/minibundle/variable_template_registry.rb +19 -0
  24. data/lib/jekyll/minibundle/version.rb +1 -1
  25. data/test/fixture/site/_bin/with_count +1 -1
  26. data/test/integration/minibundle_development_mode_test.rb +146 -61
  27. data/test/integration/minibundle_production_mode_test.rb +271 -143
  28. data/test/integration/ministamp_development_mode_test.rb +66 -25
  29. data/test/integration/ministamp_production_mode_test.rb +129 -37
  30. data/test/integration/static_files_as_asset_sources_test.rb +10 -10
  31. data/test/support/assertions.rb +1 -1
  32. data/test/support/{static_file_api_config.rb → static_file_config.rb} +6 -3
  33. data/test/support/test_case.rb +7 -4
  34. data/test/unit/asset_bundle_test.rb +6 -6
  35. data/test/unit/asset_file_drop_test.rb +65 -0
  36. data/test/unit/asset_file_registry_test.rb +136 -98
  37. data/test/unit/asset_tag_markup_test.rb +11 -5
  38. data/test/unit/bundle_file_properties_test.rb +44 -23
  39. data/test/unit/bundle_file_writing_test.rb +50 -32
  40. data/test/unit/development_file_properties_test.rb +95 -0
  41. data/test/unit/development_file_writing_test.rb +15 -6
  42. data/test/unit/environment_test.rb +3 -3
  43. data/test/unit/files_test.rb +7 -7
  44. data/test/unit/hashes_test.rb +12 -12
  45. data/test/unit/jekyll_static_file_api_test.rb +91 -20
  46. data/test/unit/mini_bundle_block_test.rb +59 -9
  47. data/test/unit/mini_stamp_tag_test.rb +37 -6
  48. data/test/unit/stamp_file_properties_test.rb +47 -24
  49. data/test/unit/stamp_file_writing_test.rb +33 -24
  50. data/test/unit/variable_template_test.rb +121 -0
  51. metadata +27 -6
  52. data/test/unit/development_file_collection_properties_test.rb +0 -106
data/Rakefile CHANGED
@@ -20,6 +20,22 @@ def run_jekyll_in_fixture_site(command)
20
20
  sh env, jekyll_cmd
21
21
  end
22
22
 
23
+ desc 'Run benchmarks; BM=<benchmark_suite_path>'
24
+ task :benchmark do
25
+ run_single_bm = ENV.key?('BM')
26
+
27
+ bm_sources =
28
+ if run_single_bm
29
+ [ENV['BM']]
30
+ else
31
+ Dir['benchmark/*_bm.rb']
32
+ end
33
+
34
+ bm_sources.each do |bm_source|
35
+ sh "ruby -I lib #{bm_source}"
36
+ end
37
+ end
38
+
23
39
  namespace :gem do
24
40
  gem_name = 'jekyll-minibundle'
25
41
 
@@ -27,12 +27,13 @@ There are no other runtime dependencies besides the minification tool
27
27
 
28
28
  s.test_files = `git ls-files -- test`.split("\n")
29
29
 
30
- s.add_development_dependency 'jekyll', '~> 3.0'
31
- s.add_development_dependency 'minitest', '~> 5.8'
32
- s.add_development_dependency 'nokogiri', '~> 1.6'
33
- s.add_development_dependency 'pry', '~> 0.10'
34
- s.add_development_dependency 'rake', '~> 12.0'
35
- s.add_development_dependency 'rubocop', '~> 0.47.0'
30
+ s.add_development_dependency 'benchmark-ips', '~> 2.7'
31
+ s.add_development_dependency 'jekyll', '~> 3.0'
32
+ s.add_development_dependency 'minitest', '~> 5.8'
33
+ s.add_development_dependency 'nokogiri', '~> 1.6'
34
+ s.add_development_dependency 'pry', '~> 0.10'
35
+ s.add_development_dependency 'rake', '~> 12.0'
36
+ s.add_development_dependency 'rubocop', '~> 0.47.0'
36
37
 
37
38
  s.required_ruby_version = '>= 2.0.0'
38
39
 
@@ -16,7 +16,7 @@ module Jekyll::Minibundle
16
16
 
17
17
  unless @minifier_cmd
18
18
  raise <<-END
19
- Missing minification command for bundling #{bundle_destination_path}. Specify it in
19
+ Missing minification command for bundling #{bundle_destination_path.inspect}. Specify it in
20
20
  1) minibundle.minifier_commands.#{@type} setting in _config.yml,
21
21
  2) $JEKYLL_MINIBUNDLE_CMD_#{@type.to_s.upcase} environment variable, or
22
22
  3) minifier_cmd setting inside minibundle block.
@@ -48,7 +48,7 @@ Missing minification command for bundling #{bundle_destination_path}. Specify it
48
48
  end
49
49
  end
50
50
  if exit_status != 0
51
- msg = "Bundling #{bundle_destination_path} failed with exit status #{exit_status}, command: '#{@minifier_cmd}'"
51
+ msg = "Bundling #{bundle_destination_path.inspect} failed with exit status #{exit_status}, command: #{@minifier_cmd.inspect}"
52
52
  log_minifier_error(msg)
53
53
  raise msg
54
54
  end
@@ -73,7 +73,7 @@ Missing minification command for bundling #{bundle_destination_path}. Specify it
73
73
  _, status = Process.waitpid2(pid)
74
74
  status.exitstatus
75
75
  rescue => e
76
- raise "Bundling #{bundle_destination_path} failed: #{e}"
76
+ raise "Bundling #{bundle_destination_path.inspect} failed: #{e}"
77
77
  ensure
78
78
  [rd, wr].each { |io| io.close unless io.closed? }
79
79
  end
@@ -0,0 +1,45 @@
1
+ require 'forwardable'
2
+
3
+ module Jekyll::Minibundle
4
+ class AssetFileDrop < ::Liquid::Drop
5
+ extend Forwardable
6
+
7
+ KEYS = %w{
8
+ name
9
+ extname
10
+ basename
11
+ modified_time
12
+ path
13
+ collection
14
+ }.freeze
15
+
16
+ def initialize(file)
17
+ @file = file
18
+ end
19
+
20
+ def key?(key)
21
+ respond_to?(key)
22
+ end
23
+
24
+ def keys
25
+ KEYS
26
+ end
27
+
28
+ def to_h
29
+ keys.each_with_object({}) do |key, acc|
30
+ acc[key] = self[key]
31
+ end
32
+ end
33
+
34
+ alias to_hash to_h
35
+
36
+ def inspect
37
+ require 'json'
38
+ JSON.pretty_generate(to_h)
39
+ end
40
+
41
+ def_delegators :@file, :name, :extname, :basename, :modified_time
42
+ def_delegator :@file, :relative_path, :path
43
+ def_delegator :@file, :type, :collection
44
+ end
45
+ end
@@ -1,3 +1,5 @@
1
+ require 'jekyll/minibundle/asset_file_drop'
2
+
1
3
  module Jekyll::Minibundle
2
4
  module AssetFileProperties
3
5
  def asset_destination_path
@@ -26,6 +28,10 @@ module Jekyll::Minibundle
26
28
  asset_destination_filename
27
29
  end
28
30
 
31
+ def basename
32
+ File.basename(name, extname)
33
+ end
34
+
29
35
  def modified_time
30
36
  File.stat(path).mtime
31
37
  end
@@ -35,17 +41,15 @@ module Jekyll::Minibundle
35
41
  end
36
42
 
37
43
  def destination_rel_dir
38
- asset_destination_dir
44
+ "/#{asset_destination_dir}"
39
45
  end
40
46
 
41
47
  def to_liquid
42
- {
43
- 'basename' => File.basename(name, extname),
44
- 'name' => name,
45
- 'extname' => extname,
46
- 'modified_time' => modified_time,
47
- 'path' => relative_path
48
- }
48
+ AssetFileDrop.new(self)
49
+ end
50
+
51
+ def data
52
+ {}
49
53
  end
50
54
 
51
55
  def write?
@@ -49,7 +49,7 @@ module Jekyll::Minibundle
49
49
 
50
50
  if cached
51
51
  if cached.fetch(:type) != :bundle
52
- raise "minibundle block has the same destination path as a ministamp tag: '#{asset_destination_path}'"
52
+ raise "minibundle block has the same destination path as a ministamp tag: #{asset_destination_path}"
53
53
  end
54
54
 
55
55
  cached_file = cached.fetch(:file)
@@ -67,7 +67,7 @@ module Jekyll::Minibundle
67
67
 
68
68
  if cached_is_used
69
69
  raise <<-END
70
- Two or more minibundle blocks with the same destination path '#{asset_destination_path}', but having different asset configuration: #{bundle_config.inspect} vs. #{cached_config.inspect}
70
+ Two or more minibundle blocks with the same destination path #{asset_destination_path.inspect}, but having different asset configuration: #{bundle_config.inspect} vs. #{cached_config.inspect}
71
71
  END
72
72
  end
73
73
 
@@ -90,7 +90,7 @@ Two or more minibundle blocks with the same destination path '#{asset_destinatio
90
90
 
91
91
  if cached
92
92
  if cached.fetch(:type) != :stamp
93
- raise "ministamp tag has the same destination path as a minibundle block: '#{asset_destination_path}'"
93
+ raise "ministamp tag has the same destination path as a minibundle block: #{asset_destination_path}"
94
94
  end
95
95
 
96
96
  cached_file = cached.fetch(:file)
@@ -108,7 +108,7 @@ Two or more minibundle blocks with the same destination path '#{asset_destinatio
108
108
 
109
109
  if cached_is_used
110
110
  raise <<-END
111
- Two or more ministamp tags with the same destination path '#{asset_destination_path}', but different asset source paths: '#{asset_source_path}' vs. '#{cached_config}'
111
+ Two or more ministamp tags with the same destination path #{asset_destination_path.inspect}, but different asset source paths: #{asset_source_path.inspect} vs. #{cached_config.inspect}
112
112
  END
113
113
  end
114
114
 
@@ -1,29 +1,30 @@
1
- require 'cgi'
1
+ require 'cgi/util'
2
2
 
3
3
  module Jekyll::Minibundle
4
4
  module AssetTagMarkup
5
- class << self
6
- def make_markup(type, path, attributes)
7
- case type
8
- when :js
9
- %{<script type="text/javascript" src="#{path}"#{make_attributes(attributes)}></script>}
10
- when :css
11
- %{<link rel="stylesheet" href="#{path}"#{make_attributes(attributes)}>}
12
- else
13
- raise ArgumentError, "Unknown type for generating bundle markup: #{type}, #{path}"
14
- end
15
- end
5
+ def self.make_markup(type, url, attributes)
6
+ url_str = CGI.escape_html(url)
7
+ attributes_str = make_attributes(attributes)
16
8
 
17
- def make_attributes(attributes)
18
- attributes.map { |name, value| make_attribute(name, value) }.join('')
9
+ case type
10
+ when :js
11
+ %{<script type="text/javascript" src="#{url_str}"#{attributes_str}></script>}
12
+ when :css
13
+ %{<link rel="stylesheet" href="#{url_str}"#{attributes_str}>}
14
+ else
15
+ raise ArgumentError, "Unknown type for generating bundle markup: #{type}, #{url}"
19
16
  end
17
+ end
18
+
19
+ def self.make_attributes(attributes)
20
+ attributes.map { |name, value| make_attribute(name, value) }.join('')
21
+ end
20
22
 
21
- def make_attribute(name, value)
22
- if value.nil?
23
- %{ #{name}}
24
- else
25
- %{ #{name}="#{CGI.escape_html(value.to_s)}"}
26
- end
23
+ def self.make_attribute(name, value)
24
+ if value.nil?
25
+ %{ #{name}}
26
+ else
27
+ %{ #{name}="#{CGI.escape_html(value.to_s)}"}
27
28
  end
28
29
  end
29
30
  end
@@ -14,7 +14,12 @@ module Jekyll::Minibundle
14
14
  @site = site
15
15
  @type = config.fetch('type')
16
16
  asset_source_dir = File.join(@site.source, config.fetch('source_dir'))
17
- @asset_paths = config.fetch('assets').map { |asset_path| File.join(asset_source_dir, "#{asset_path}.#{@type}") }
17
+ raise ArgumentError, "Bundle source directory does not exist: #{asset_source_dir}" unless File.directory?(asset_source_dir)
18
+ @asset_paths = config.fetch('assets').map do |asset_path|
19
+ path = File.join(asset_source_dir, "#{asset_path}.#{@type}")
20
+ raise ArgumentError, "Bundle asset source file does not exist: #{path}" unless File.file?(path)
21
+ path
22
+ end
18
23
  @destination_path = config.fetch('destination_path')
19
24
  @asset_destination_dir = File.dirname(@destination_path)
20
25
  @asset_destination_filename_prefix = File.basename(@destination_path)
@@ -12,7 +12,8 @@ module Jekyll::Minibundle
12
12
 
13
13
  def initialize(site, asset_source_path, asset_destination_path)
14
14
  @site = site
15
- @asset_source_path = asset_source_path
15
+ @asset_source_path = File.join(@site.source, asset_source_path)
16
+ raise ArgumentError, "Development source file does not exist: #{@asset_source_path}" unless File.file?(@asset_source_path)
16
17
  @asset_destination_dir = File.dirname(asset_destination_path)
17
18
  @asset_destination_filename = File.basename(asset_destination_path)
18
19
  @stamped_at = nil
@@ -7,12 +7,12 @@ module Jekyll::Minibundle
7
7
  def initialize(site, config)
8
8
  @type = config.fetch('type')
9
9
 
10
- asset_source_dir = File.join(site.source, config.fetch('source_dir'))
10
+ source_dir = config.fetch('source_dir')
11
11
  destination_path = config.fetch('destination_path')
12
12
 
13
13
  @files = config.fetch('assets').map do |asset_path|
14
14
  asset_basename = "#{asset_path}.#{@type}"
15
- asset_source = File.join(asset_source_dir, asset_basename)
15
+ asset_source = File.join(source_dir, asset_basename)
16
16
  asset_destination = File.join(destination_path, asset_basename)
17
17
  DevelopmentFile.new(site, asset_source, asset_destination)
18
18
  end
@@ -2,24 +2,22 @@ require 'jekyll/minibundle/hashes'
2
2
 
3
3
  module Jekyll::Minibundle
4
4
  module Environment
5
- class << self
6
- def minifier_command(site, type)
7
- type = type.to_s
8
- ENV["JEKYLL_MINIBUNDLE_CMD_#{type.upcase}"] || Environment.find_site_config(site, ['minibundle', 'minifier_commands', type], String)
9
- end
5
+ def self.minifier_command(site, type)
6
+ type = type.to_s
7
+ ENV["JEKYLL_MINIBUNDLE_CMD_#{type.upcase}"] || Environment.find_site_config(site, ['minibundle', 'minifier_commands', type], String)
8
+ end
10
9
 
11
- def development?(site)
12
- mode = ENV['JEKYLL_MINIBUNDLE_MODE'] || Environment.find_site_config(site, %w{minibundle mode}, String)
13
- mode == 'development'
14
- end
10
+ def self.development?(site)
11
+ mode = ENV['JEKYLL_MINIBUNDLE_MODE'] || Environment.find_site_config(site, %w{minibundle mode}, String)
12
+ mode == 'development'
13
+ end
15
14
 
16
- def find_site_config(site, keys, type)
17
- value = Hashes.dig(site.config, *keys)
18
- if value && !value.is_a?(type)
19
- raise "Invalid site configuration for key #{keys.join('.')}; expecting type #{type}"
20
- end
21
- value
15
+ def self.find_site_config(site, keys, type)
16
+ value = Hashes.dig(site.config, *keys)
17
+ if value && !value.is_a?(type)
18
+ raise "Invalid site configuration for key #{keys.join('.')}; expecting type #{type}"
22
19
  end
20
+ value
23
21
  end
24
22
  end
25
23
  end
@@ -2,30 +2,28 @@ require 'fileutils'
2
2
 
3
3
  module Jekyll::Minibundle
4
4
  module Files
5
- class << self
6
- def copy_p(src_path, dst_path)
7
- FileUtils.mkdir_p(File.dirname(dst_path))
8
- FileUtils.cp(src_path, dst_path)
9
- end
5
+ def self.copy_p(src_path, dst_path)
6
+ FileUtils.mkdir_p(File.dirname(dst_path))
7
+ FileUtils.cp(src_path, dst_path)
8
+ end
10
9
 
11
- def read_last(path, max_size)
12
- File.open(path, 'rb') do |file|
13
- return '' if max_size < 1
10
+ def self.read_last(path, max_size)
11
+ File.open(path, 'rb') do |file|
12
+ return '' if max_size < 1
14
13
 
15
- file_size = file.stat.size
14
+ file_size = file.stat.size
16
15
 
17
- if file_size < max_size
18
- file.read(file_size)
19
- else
20
- file.seek(file_size - max_size, ::IO::SEEK_SET)
21
- file.read(max_size)
22
- end
16
+ if file_size < max_size
17
+ file.read(file_size)
18
+ else
19
+ file.seek(file_size - max_size, ::IO::SEEK_SET)
20
+ file.read(max_size)
23
21
  end
24
22
  end
23
+ end
25
24
 
26
- def strip_dot_slash_from_path_start(path)
27
- path.sub(%r{\A\./+}, '')
28
- end
25
+ def self.strip_dot_slash_from_path_start(path)
26
+ path.sub(%r{\A\./+}, '')
29
27
  end
30
28
  end
31
29
  end
@@ -1,19 +1,17 @@
1
1
  module Jekyll::Minibundle
2
2
  module Hashes
3
- class << self
4
- def dig(obj, *keys)
5
- value = obj
6
- keys.each do |key|
7
- return nil unless value
8
- value = value[key]
9
- end
10
- value
3
+ def self.dig(obj, *keys)
4
+ value = obj
5
+ keys.each do |key|
6
+ return nil unless value
7
+ value = value[key]
11
8
  end
9
+ value
10
+ end
12
11
 
13
- def pick(hash, *keys)
14
- keys.each_with_object({}) do |key, acc|
15
- acc[key] = hash.fetch(key)
16
- end
12
+ def self.pick(hash, *keys)
13
+ keys.each_with_object({}) do |key, acc|
14
+ acc[key] = hash.fetch(key)
17
15
  end
18
16
  end
19
17
  end
@@ -2,14 +2,12 @@ module Jekyll::Minibundle
2
2
  module Log
3
3
  TOPIC = 'Minibundle:'.freeze
4
4
 
5
- class << self
6
- def error(msg)
7
- ::Jekyll.logger.error(TOPIC, msg)
8
- end
5
+ def self.error(msg)
6
+ ::Jekyll.logger.error(TOPIC, msg)
7
+ end
9
8
 
10
- def info(msg)
11
- ::Jekyll.logger.info(TOPIC, msg)
12
- end
9
+ def self.info(msg)
10
+ ::Jekyll.logger.info(TOPIC, msg)
13
11
  end
14
12
  end
15
13
  end
@@ -1,3 +1,4 @@
1
+ require 'pathname'
1
2
  require 'jekyll/minibundle/hashes'
2
3
  require 'jekyll/minibundle/files'
3
4
  require 'jekyll/minibundle/environment'
@@ -9,41 +10,63 @@ module Jekyll::Minibundle
9
10
  def initialize(tag_name, type, _tokens)
10
11
  super
11
12
  @type = type.strip.downcase.to_sym
12
- raise ArgumentError, "No asset type for minibundle block; pass value such as 'css' or 'js' as the argument" if @type.empty?
13
+ raise ArgumentError, "Missing asset type for minibundle block; pass value such as 'css' or 'js' as the argument" if @type.empty?
13
14
  end
14
15
 
15
16
  def render(context)
16
17
  site = context.registers.fetch(:site)
17
- bundle_config = get_current_bundle_config(::SafeYAML.load(super), site)
18
+
19
+ bundle_config = get_current_bundle_config(parse_contents(super), site)
18
20
  baseurl = bundle_config.fetch('baseurl')
21
+ destination_baseurl = bundle_config.fetch('destination_baseurl')
19
22
  attributes = bundle_config.fetch('attributes')
20
23
 
24
+ do_form_destination_baseurl = !destination_baseurl.empty?
25
+ destination_dir_path = Pathname.new(File.dirname(bundle_config.fetch('destination_path'))) if do_form_destination_baseurl
26
+
21
27
  register_asset_files(site, bundle_config).map do |file|
22
28
  dst_path = Files.strip_dot_slash_from_path_start(file.destination_path_for_markup)
23
29
 
24
- full_path =
25
- if baseurl.empty?
26
- dst_path
27
- else
30
+ url =
31
+ if do_form_destination_baseurl
32
+ destination_baseurl + Pathname.new(dst_path).relative_path_from(destination_dir_path).to_s
33
+ elsif !baseurl.empty?
28
34
  File.join(baseurl, dst_path)
35
+ else
36
+ dst_path
29
37
  end
30
38
 
31
- AssetTagMarkup.make_markup(@type, full_path, attributes)
39
+ AssetTagMarkup.make_markup(@type, url, attributes)
32
40
  end.join("\n")
33
41
  end
34
42
 
35
43
  def self.default_bundle_config
36
44
  {
37
- 'source_dir' => '_assets',
38
- 'destination_path' => 'assets/site',
39
- 'baseurl' => '',
40
- 'assets' => [],
41
- 'attributes' => {}
45
+ 'source_dir' => '_assets',
46
+ 'destination_path' => 'assets/site',
47
+ 'baseurl' => '',
48
+ 'destination_baseurl' => '',
49
+ 'assets' => [],
50
+ 'attributes' => {},
51
+ 'minifier_cmd' => nil
42
52
  }
43
53
  end
44
54
 
45
55
  private
46
56
 
57
+ def parse_contents(contents)
58
+ raise ArgumentError, 'Missing configuration for minibundle block; pass configuration in YAML syntax' if contents =~ /\A\s+\z/
59
+ structure = parse_structure(contents)
60
+ raise ArgumentError, "Unsupported minibundle block contents type (#{structure.class}), only Hash is supported: #{contents}" unless structure.is_a?(Hash)
61
+ structure
62
+ end
63
+
64
+ def parse_structure(contents)
65
+ ::SafeYAML.load(contents)
66
+ rescue => e
67
+ raise ArgumentError, "Failed parsing minibundle block contents syntax as YAML: #{contents.strip.inspect}. Cause: #{e}"
68
+ end
69
+
47
70
  def get_current_bundle_config(local_bundle_config, site)
48
71
  config =
49
72
  MiniBundleBlock