jekyll-minibundle 1.5.1 → 1.6.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -6
  3. data/README.md +34 -24
  4. data/Rakefile +19 -14
  5. data/jekyll-minibundle.gemspec +5 -4
  6. data/lib/jekyll/minibundle/asset_bundle.rb +24 -4
  7. data/lib/jekyll/minibundle/asset_file_operations.rb +0 -8
  8. data/lib/jekyll/minibundle/asset_file_properties.rb +26 -8
  9. data/lib/jekyll/minibundle/asset_file_registry.rb +57 -21
  10. data/lib/jekyll/minibundle/asset_tag_markup.rb +13 -4
  11. data/lib/jekyll/minibundle/bundle_file.rb +9 -6
  12. data/lib/jekyll/minibundle/compatibility.rb +11 -1
  13. data/lib/jekyll/minibundle/development_file.rb +6 -0
  14. data/lib/jekyll/minibundle/development_file_collection.rb +3 -16
  15. data/lib/jekyll/minibundle/environment.rb +5 -12
  16. data/lib/jekyll/minibundle/files.rb +18 -0
  17. data/lib/jekyll/minibundle/hashes.rb +20 -0
  18. data/lib/jekyll/minibundle/mini_bundle_block.rb +52 -12
  19. data/lib/jekyll/minibundle/mini_stamp_tag.rb +27 -8
  20. data/lib/jekyll/minibundle/stamp_file.rb +9 -6
  21. data/lib/jekyll/minibundle/version.rb +1 -1
  22. data/test/fixture/site/_bin/with_count +6 -5
  23. data/test/integration/known_caveats_test.rb +89 -0
  24. data/test/integration/minibundle_development_mode_test.rb +171 -26
  25. data/test/integration/minibundle_production_mode_test.rb +234 -42
  26. data/test/integration/ministamp_development_mode_test.rb +145 -0
  27. data/test/integration/{ministamp_test.rb → ministamp_production_mode_test.rb} +72 -23
  28. data/test/integration/static_files_as_asset_sources_test.rb +3 -0
  29. data/test/support/assertions.rb +24 -0
  30. data/test/support/fixture_config.rb +13 -10
  31. data/test/support/static_file_api_config.rb +9 -3
  32. data/test/support/test_case.rb +7 -6
  33. data/test/unit/asset_bundle_test.rb +16 -2
  34. data/test/unit/asset_file_registry_test.rb +117 -14
  35. data/test/unit/asset_tag_markup_test.rb +17 -5
  36. data/test/unit/bundle_file_properties_test.rb +32 -8
  37. data/test/unit/bundle_file_writing_test.rb +10 -11
  38. data/test/unit/development_file_collection_properties_test.rb +47 -15
  39. data/test/unit/environment_test.rb +0 -13
  40. data/test/unit/files_test.rb +46 -0
  41. data/test/unit/hashes_test.rb +41 -0
  42. data/test/unit/mini_bundle_block_test.rb +56 -1
  43. data/test/unit/mini_stamp_tag_test.rb +8 -8
  44. data/test/unit/stamp_file_properties_test.rb +32 -13
  45. data/test/unit/stamp_file_writing_test.rb +1 -5
  46. metadata +38 -16
  47. data/test/unit/development_file_collection_writing_test.rb +0 -43
  48. data/test/unit/jekyll_payload_test.rb +0 -64
@@ -1,44 +1,80 @@
1
- require 'jekyll/minibundle/environment'
2
- require 'jekyll/minibundle/development_file_collection'
3
1
  require 'jekyll/minibundle/bundle_file'
2
+ require 'jekyll/minibundle/development_file'
3
+ require 'jekyll/minibundle/development_file_collection'
4
+ require 'jekyll/minibundle/environment'
4
5
  require 'jekyll/minibundle/stamp_file'
5
6
 
6
7
  module Jekyll::Minibundle
7
8
  module AssetFileRegistry
8
9
  class << self
9
10
  def clear
10
- @_instances = {}
11
+ @_files = {}
11
12
  end
12
13
 
13
- def bundle_file(site, bundle_config)
14
- asset_destination_path = "#{bundle_config.fetch('destination_path')}.#{bundle_config.fetch('type')}"
15
- @_instances[asset_destination_path] ||= register_bundle_file(site, bundle_config)
14
+ def register_bundle_file(site, bundle_config)
15
+ register_file_for_bundle_block(BundleFile, site, bundle_config) { |file| [file] }
16
16
  end
17
17
 
18
- def stamp_file(site, asset_source_path, asset_destination_path)
19
- @_instances[asset_destination_path] ||= register_stamp_file(site, asset_source_path, asset_destination_path)
18
+ def register_development_file_collection(site, bundle_config)
19
+ register_file_for_bundle_block(DevelopmentFileCollection, site, bundle_config, &:files)
20
+ end
21
+
22
+ def register_stamp_file(site, asset_source_path, asset_destination_path)
23
+ register_file_for_stamp_tag(StampFile, site, asset_source_path, asset_destination_path)
24
+ end
25
+
26
+ def register_development_file(site, asset_source_path, asset_destination_path)
27
+ register_file_for_stamp_tag(DevelopmentFile, site, asset_source_path, asset_destination_path)
20
28
  end
21
29
 
22
30
  private
23
31
 
24
- def register_bundle_file(site, bundle_config)
25
- if Environment.development?(site)
26
- DevelopmentFileCollection.new(site, bundle_config)
27
- else
28
- BundleFile.new(site, bundle_config)
32
+ def register_file_for_bundle_block(file_class, site, bundle_config, &get_files)
33
+ asset_destination_path = "#{bundle_config.fetch('destination_path')}.#{bundle_config.fetch('type')}"
34
+
35
+ cached = @_files[asset_destination_path]
36
+
37
+ if cached
38
+ raise "minibundle block has same destination path as a ministamp tag: #{asset_destination_path}" if cached.fetch(:type) != :bundle
39
+
40
+ cached_file = cached.fetch(:file)
41
+
42
+ if bundle_config == cached.fetch(:config)
43
+ get_files.call(cached_file).each do |file|
44
+ site.static_files << file unless site.static_files.include?(file)
45
+ end
46
+ return cached_file
47
+ else
48
+ get_files.call(cached_file).each { |file| site.static_files.delete(file) }
49
+ end
29
50
  end
30
- end
31
51
 
32
- def register_stamp_file(site, asset_source_path, asset_destination_path)
33
- StampFile.new(site, asset_source_path, asset_destination_path, &get_stamp_file_basenamer(site))
52
+ new_file = file_class.new(site, bundle_config)
53
+ @_files[asset_destination_path] = {type: :bundle, file: new_file, config: bundle_config}
54
+ get_files.call(new_file).each { |file| site.static_files << file }
55
+ new_file
34
56
  end
35
57
 
36
- def get_stamp_file_basenamer(site)
37
- if Environment.development?(site)
38
- ->(base, ext, _) { base + ext }
39
- else
40
- ->(base, ext, stamper) { "#{base}-#{stamper.call}#{ext}" }
58
+ def register_file_for_stamp_tag(file_class, site, asset_source_path, asset_destination_path)
59
+ cached = @_files[asset_destination_path]
60
+
61
+ if cached
62
+ raise "ministamp tag has same destination path as a minibundle block: #{asset_destination_path}" if cached.fetch(:type) != :stamp
63
+
64
+ cached_file = cached.fetch(:file)
65
+
66
+ if asset_source_path == cached.fetch(:config)
67
+ site.static_files << cached_file unless site.static_files.include?(cached_file)
68
+ return cached_file
69
+ else
70
+ site.static_files.delete(cached_file)
71
+ end
41
72
  end
73
+
74
+ new_file = file_class.new(site, asset_source_path, asset_destination_path)
75
+ @_files[asset_destination_path] = {type: :stamp, file: new_file, config: asset_source_path}
76
+ site.static_files << new_file
77
+ new_file
42
78
  end
43
79
  end
44
80
 
@@ -3,14 +3,15 @@ require 'cgi'
3
3
  module Jekyll::Minibundle
4
4
  module AssetTagMarkup
5
5
  class << self
6
- def make_markup(type, path, attributes)
6
+ def make_markup(type, baseurl, path, attributes)
7
+ url = make_url(baseurl, path)
7
8
  case type
8
9
  when :js
9
- %{<script type="text/javascript" src="#{path}"#{make_attributes(attributes)}></script>}
10
+ %{<script type="text/javascript" src="#{url}"#{make_attributes(attributes)}></script>}
10
11
  when :css
11
- %{<link rel="stylesheet" href="#{path}"#{make_attributes(attributes)}>}
12
+ %{<link rel="stylesheet" href="#{url}"#{make_attributes(attributes)}>}
12
13
  else
13
- fail ArgumentError, "Unknown type for generating bundle markup: #{type}, #{path}"
14
+ raise ArgumentError, "Unknown type for generating bundle markup: #{type}, #{path}"
14
15
  end
15
16
  end
16
17
 
@@ -21,6 +22,14 @@ module Jekyll::Minibundle
21
22
  def make_attribute(name, value)
22
23
  %{ #{name}="#{CGI.escape_html(value)}"}
23
24
  end
25
+
26
+ def make_url(baseurl, path)
27
+ if baseurl.empty?
28
+ path
29
+ else
30
+ File.join(baseurl, path)
31
+ end
32
+ end
24
33
  end
25
34
  end
26
35
  end
@@ -2,7 +2,6 @@ require 'jekyll/minibundle/asset_bundle'
2
2
  require 'jekyll/minibundle/asset_file_operations'
3
3
  require 'jekyll/minibundle/asset_file_properties'
4
4
  require 'jekyll/minibundle/asset_stamp'
5
- require 'jekyll/minibundle/asset_tag_markup'
6
5
 
7
6
  module Jekyll::Minibundle
8
7
  class BundleFile
@@ -17,7 +16,6 @@ module Jekyll::Minibundle
17
16
  asset_source_dir = File.join(@site.source, config.fetch('source_dir'))
18
17
  @asset_paths = config.fetch('assets').map { |asset_path| File.join(asset_source_dir, "#{asset_path}.#{@type}") }
19
18
  @destination_path = config.fetch('destination_path')
20
- @attributes = config.fetch('attributes')
21
19
  @minifier_cmd = config.fetch('minifier_cmd')
22
20
  @stamped_at = nil
23
21
  @is_modified = false
@@ -25,7 +23,8 @@ module Jekyll::Minibundle
25
23
 
26
24
  def destination_path_for_markup
27
25
  # we must rebundle here, if at all, in order to make sure the
28
- # markup and generated file have the same fingerprint
26
+ # markup destination and generated file paths have the same
27
+ # fingerprint
29
28
  if modified?
30
29
  @stamped_at = mtime
31
30
  @is_modified = true
@@ -33,7 +32,7 @@ module Jekyll::Minibundle
33
32
  asset_bundle.make_bundle
34
33
  end
35
34
 
36
- AssetTagMarkup.make_markup(@type, asset_destination_path, @attributes)
35
+ asset_destination_path
37
36
  end
38
37
 
39
38
  def path
@@ -48,8 +47,12 @@ module Jekyll::Minibundle
48
47
  "#{@destination_path}-#{asset_stamp}.#{@type}"
49
48
  end
50
49
 
51
- def mtime
52
- @asset_paths.map { |f| File.stat(f).mtime.to_i }.max
50
+ def extname
51
+ ".#{@type}"
52
+ end
53
+
54
+ def modified_time
55
+ @asset_paths.map { |f| File.stat(f).mtime }.max
53
56
  end
54
57
 
55
58
  # writes destination only after `destination_path_for_markup` has
@@ -1,5 +1,7 @@
1
1
  module Jekyll::Minibundle
2
2
  module Compatibility
3
+ LOG_TOPIC = 'Minibundle:'.freeze
4
+
3
5
  class << self
4
6
  # SafeYAML.load is introduced in Jekyll 2.0.0
5
7
  if defined?(::SafeYAML) && ::SafeYAML.respond_to?(:load)
@@ -14,10 +16,18 @@ module Jekyll::Minibundle
14
16
 
15
17
  # Jekyll.logger is introduced in Jekyll 1.0.3
16
18
  if ::Jekyll.respond_to?(:logger)
19
+ def log_error(msg)
20
+ ::Jekyll.logger.error(LOG_TOPIC, msg)
21
+ end
22
+
17
23
  def log_info(msg)
18
- ::Jekyll.logger.info('Minibundle:', msg)
24
+ ::Jekyll.logger.info(LOG_TOPIC, msg)
19
25
  end
20
26
  else
27
+ def log_error(msg)
28
+ $stderr.puts(msg)
29
+ end
30
+
21
31
  def log_info(msg)
22
32
  $stdout.puts(msg)
23
33
  end
@@ -16,6 +16,12 @@ module Jekyll::Minibundle
16
16
  @stamped_at = nil
17
17
  end
18
18
 
19
+ alias destination_path_for_markup asset_destination_path
20
+
21
+ def extname
22
+ File.extname(asset_destination_path)
23
+ end
24
+
19
25
  def write(site_destination_dir)
20
26
  if modified?
21
27
  @stamped_at = mtime
@@ -1,10 +1,12 @@
1
- require 'jekyll/minibundle/asset_tag_markup'
2
1
  require 'jekyll/minibundle/development_file'
3
2
 
4
3
  module Jekyll::Minibundle
5
4
  class DevelopmentFileCollection
5
+ attr_reader :files
6
+
6
7
  def initialize(site, config)
7
8
  @type = config.fetch('type')
9
+
8
10
  asset_source_dir = File.join(site.source, config.fetch('source_dir'))
9
11
  destination_path = config.fetch('destination_path')
10
12
 
@@ -14,21 +16,6 @@ module Jekyll::Minibundle
14
16
  asset_destination = File.join(destination_path, asset_basename)
15
17
  DevelopmentFile.new(site, asset_source, asset_destination)
16
18
  end
17
-
18
- @attributes = config.fetch('attributes')
19
- end
20
-
21
- def add_as_static_file_to(site)
22
- # NOTE: We could optimize here by iterating over site's static
23
- # files only once instead of per each of our file. Seems like a
24
- # premature optimization for now, however.
25
- @files.each { |f| f.add_as_static_file_to(site) }
26
- end
27
-
28
- def destination_path_for_markup
29
- @files.
30
- map { |f| AssetTagMarkup.make_markup(@type, f.asset_destination_path, @attributes) }.
31
- join("\n")
32
19
  end
33
20
  end
34
21
  end
@@ -1,3 +1,5 @@
1
+ require 'jekyll/minibundle/hashes'
2
+
1
3
  module Jekyll::Minibundle
2
4
  module Environment
3
5
  class << self
@@ -7,23 +9,14 @@ module Jekyll::Minibundle
7
9
  end
8
10
 
9
11
  def development?(site)
10
- mode = ENV['JEKYLL_MINIBUNDLE_MODE'] || Environment.find_site_config(site, ['minibundle', 'mode'], String)
12
+ mode = ENV['JEKYLL_MINIBUNDLE_MODE'] || Environment.find_site_config(site, %w{minibundle mode}, String)
11
13
  mode == 'development'
12
14
  end
13
15
 
14
16
  def find_site_config(site, keys, type)
15
- value = traverse_keys(site.config, keys)
17
+ value = Hashes.dig(site.config, *keys)
16
18
  if value && !value.is_a?(type)
17
- fail "Invalid site configuration for key #{keys.join('.')}; expecting type #{type}"
18
- end
19
- value
20
- end
21
-
22
- def traverse_keys(obj, keys)
23
- value = obj
24
- keys.each do |key|
25
- return nil unless value
26
- value = value[key]
19
+ raise "Invalid site configuration for key #{keys.join('.')}; expecting type #{type}"
27
20
  end
28
21
  value
29
22
  end
@@ -0,0 +1,18 @@
1
+ module Jekyll::Minibundle
2
+ module Files
3
+ def self.read_last(path, max_size)
4
+ File.open(path, 'rb') do |file|
5
+ return '' if max_size < 1
6
+
7
+ file_size = file.stat.size
8
+
9
+ if file_size < max_size
10
+ file.read(file_size)
11
+ else
12
+ file.seek(file_size - max_size, ::IO::SEEK_SET)
13
+ file.read(max_size)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ module Jekyll::Minibundle
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
11
+ end
12
+
13
+ def pick(hash, *keys)
14
+ keys.each_with_object({}) do |key, acc|
15
+ acc[key] = hash.fetch(key)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,29 +1,33 @@
1
- require 'jekyll/minibundle/asset_file_registry'
1
+ require 'jekyll/minibundle/hashes'
2
2
  require 'jekyll/minibundle/compatibility'
3
3
  require 'jekyll/minibundle/environment'
4
+ require 'jekyll/minibundle/asset_file_registry'
5
+ require 'jekyll/minibundle/asset_tag_markup'
4
6
 
5
7
  module Jekyll::Minibundle
6
8
  class MiniBundleBlock < Liquid::Block
7
9
  def initialize(tag_name, type, _tokens)
8
10
  super
9
11
  @type = type.strip.downcase.to_sym
10
- if @type.empty?
11
- fail ArgumentError, "No asset type for minibundle block; pass value such as 'css' or 'js' as the argument"
12
- end
12
+ raise ArgumentError, "No asset type for minibundle block; pass value such as 'css' or 'js' as the argument" if @type.empty?
13
13
  end
14
14
 
15
15
  def render(context)
16
16
  site = context.registers.fetch(:site)
17
17
  bundle_config = get_current_bundle_config(Compatibility.load_yaml(super), site)
18
- file = AssetFileRegistry.bundle_file(site, bundle_config)
19
- file.add_as_static_file_to(site)
20
- file.destination_path_for_markup
18
+ baseurl = bundle_config.fetch('baseurl')
19
+ attributes = bundle_config.fetch('attributes')
20
+
21
+ register_asset_files(site, bundle_config).map do |file|
22
+ AssetTagMarkup.make_markup(@type, baseurl, file.destination_path_for_markup, attributes)
23
+ end.join("\n")
21
24
  end
22
25
 
23
26
  def self.default_bundle_config
24
27
  {
25
28
  'source_dir' => '_assets',
26
29
  'destination_path' => 'assets/site',
30
+ 'baseurl' => '',
27
31
  'assets' => [],
28
32
  'attributes' => {}
29
33
  }
@@ -32,14 +36,50 @@ module Jekyll::Minibundle
32
36
  private
33
37
 
34
38
  def get_current_bundle_config(local_bundle_config, site)
35
- MiniBundleBlock.default_bundle_config.
36
- merge(environment_bundle_config(site)).
37
- merge(local_bundle_config).
38
- merge('type' => @type)
39
+ config =
40
+ MiniBundleBlock
41
+ .default_bundle_config
42
+ .merge(environment_bundle_config(site))
43
+ .merge(local_bundle_config)
44
+ .merge('type' => @type)
45
+
46
+ baseurl, destination_path = normalize_baseurl_and_destination_path(config.fetch('baseurl'), config.fetch('destination_path'))
47
+
48
+ config.merge('baseurl' => baseurl, 'destination_path' => destination_path)
39
49
  end
40
50
 
41
51
  def environment_bundle_config(site)
42
- { 'minifier_cmd' => Environment.minifier_command(site, @type) }
52
+ {'minifier_cmd' => Environment.minifier_command(site, @type)}
53
+ end
54
+
55
+ def normalize_baseurl_and_destination_path(baseurl, destination_path)
56
+ baseurl = '' if baseurl.nil?
57
+
58
+ unless destination_path.start_with?('/')
59
+ return [baseurl, destination_path]
60
+ end
61
+
62
+ normalized_baseurl = baseurl.empty? ? '/' : baseurl
63
+ normalized_destination_path = destination_path.sub(%r{\A/+}, '')
64
+
65
+ [normalized_baseurl, normalized_destination_path]
66
+ end
67
+
68
+ def register_asset_files(site, bundle_config)
69
+ registry_config = Hashes.pick(
70
+ bundle_config,
71
+ 'type',
72
+ 'source_dir',
73
+ 'destination_path',
74
+ 'assets',
75
+ 'minifier_cmd'
76
+ )
77
+
78
+ if Environment.development?(site)
79
+ AssetFileRegistry.register_development_file_collection(site, registry_config).files
80
+ else
81
+ [AssetFileRegistry.register_bundle_file(site, registry_config)]
82
+ end
43
83
  end
44
84
  end
45
85
  end
@@ -4,20 +4,39 @@ module Jekyll::Minibundle
4
4
  class MiniStampTag < Liquid::Tag
5
5
  def initialize(tag_name, text, _tokens)
6
6
  super
7
- @asset_source, @asset_destination = text.split(/\s+/, 3)[0, 2]
8
- if !@asset_source || @asset_source.empty?
9
- fail ArgumentError, "No asset source for ministamp tag; pass value such as '/_assets/site.css' as the first argument"
7
+
8
+ @source_path, destination_path = text.split(/\s+/, 3)[0, 2]
9
+
10
+ if !@source_path || @source_path.empty?
11
+ raise ArgumentError, "No asset source for ministamp tag; pass value such as '_assets/site.css' as the first argument"
10
12
  end
11
- if !@asset_destination || @asset_destination.empty?
12
- fail ArgumentError, "No asset destination for ministamp tag; pass value such as '/assets/site.css' as the second argument"
13
+
14
+ if !destination_path || destination_path.empty?
15
+ raise ArgumentError, "No asset destination for ministamp tag; pass value such as 'assets/site.css' as the second argument"
13
16
  end
17
+
18
+ @baseurl, @destination_path = normalize_destination_path(destination_path)
14
19
  end
15
20
 
16
21
  def render(context)
17
22
  site = context.registers.fetch(:site)
18
- file = AssetFileRegistry.stamp_file(site, @asset_source, @asset_destination)
19
- file.add_as_static_file_to(site)
20
- file.destination_path_for_markup
23
+ file =
24
+ if Environment.development?(site)
25
+ AssetFileRegistry.register_development_file(site, @source_path, @destination_path)
26
+ else
27
+ AssetFileRegistry.register_stamp_file(site, @source_path, @destination_path)
28
+ end
29
+ @baseurl + file.destination_path_for_markup
30
+ end
31
+
32
+ private
33
+
34
+ def normalize_destination_path(destination_path)
35
+ if destination_path.start_with?('/')
36
+ ['/', destination_path.sub(%r{\A/+}, '')]
37
+ else
38
+ ['', destination_path]
39
+ end
21
40
  end
22
41
  end
23
42
  end