middleman-core 4.0.0 → 4.1.0.rc.1

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/features/asset_hash.feature +63 -0
  3. data/features/asset_host.feature +14 -0
  4. data/features/cache_buster.feature +15 -1
  5. data/features/clean_build.feature +16 -0
  6. data/features/cli/preview_server.feature +6 -4
  7. data/features/cli_extension.feature +1 -1
  8. data/features/directory_index.feature +3 -1
  9. data/features/encoding_option.feature +28 -0
  10. data/features/i18n_mixed_sources.feature +39 -0
  11. data/features/ignore.feature +33 -4
  12. data/features/relative_assets.feature +14 -0
  13. data/features/support/env.rb +1 -1
  14. data/fixtures/asset-hash-app/source/index.html.erb +3 -0
  15. data/fixtures/asset-hash-app/source/layout.erb +3 -3
  16. data/fixtures/asset-hash-app/source/other.html.erb +3 -0
  17. data/fixtures/asset-hash-app/source/stylesheets/fragment.css.scss +7 -0
  18. data/fixtures/asset-hash-host-app/source/index.html.erb +3 -0
  19. data/fixtures/asset-hash-host-app/source/layout.erb +3 -3
  20. data/fixtures/asset-hash-host-app/source/other.html.erb +3 -0
  21. data/fixtures/asset-hash-host-app/source/stylesheets/fragment.css.scss +3 -0
  22. data/fixtures/i-8859-1-app/config.rb +1 -0
  23. data/fixtures/i-8859-1-app/source/index.html.erb +1 -0
  24. data/fixtures/i18n-mixed-sources/config.rb +1 -0
  25. data/fixtures/i18n-mixed-sources/locales/en.yml +4 -0
  26. data/fixtures/i18n-mixed-sources/locales/es.yml +4 -0
  27. data/fixtures/i18n-mixed-sources/source/a/sub.html.erb +9 -0
  28. data/fixtures/i18n-mixed-sources/source/b/index.html.erb +9 -0
  29. data/fixtures/i18n-mixed-sources/source/index.html.erb +9 -0
  30. data/fixtures/i18n-mixed-sources/source/localizable/a/index.html.erb +9 -0
  31. data/fixtures/i18n-mixed-sources/source/localizable/b/sub.html.erb +9 -0
  32. data/fixtures/i18n-mixed-sources/source/localizable/index.html.erb +9 -0
  33. data/fixtures/import-app/config.rb +1 -1
  34. data/fixtures/indexable-app/config.rb +1 -0
  35. data/fixtures/indexable-app/source/regex_leave_me_alone2.html +1 -0
  36. data/lib/middleman-core/application.rb +6 -5
  37. data/lib/middleman-core/builder.rb +7 -3
  38. data/lib/middleman-core/core_extensions/collections.rb +3 -1
  39. data/lib/middleman-core/core_extensions/collections/step_context.rb +6 -7
  40. data/lib/middleman-core/core_extensions/default_helpers.rb +2 -2
  41. data/lib/middleman-core/core_extensions/front_matter.rb +1 -0
  42. data/lib/middleman-core/core_extensions/i18n.rb +16 -9
  43. data/lib/middleman-core/core_extensions/routing.rb +1 -1
  44. data/lib/middleman-core/extension.rb +34 -0
  45. data/lib/middleman-core/extensions/asset_hash.rb +2 -0
  46. data/lib/middleman-core/extensions/asset_host.rb +2 -0
  47. data/lib/middleman-core/extensions/cache_buster.rb +2 -0
  48. data/lib/middleman-core/extensions/external_pipeline.rb +0 -2
  49. data/lib/middleman-core/extensions/gzip.rb +1 -1
  50. data/lib/middleman-core/extensions/relative_assets.rb +2 -0
  51. data/lib/middleman-core/load_paths.rb +1 -1
  52. data/lib/middleman-core/meta_pages/sitemap_resource.rb +1 -1
  53. data/lib/middleman-core/middleware/inline_url_rewriter.rb +10 -2
  54. data/lib/middleman-core/sitemap/extensions/ignores.rb +37 -39
  55. data/lib/middleman-core/sitemap/extensions/import.rb +24 -66
  56. data/lib/middleman-core/sitemap/extensions/move_file.rb +9 -38
  57. data/lib/middleman-core/sitemap/extensions/proxies.rb +20 -52
  58. data/lib/middleman-core/sitemap/extensions/traversal.rb +13 -1
  59. data/lib/middleman-core/sitemap/resource.rb +4 -9
  60. data/lib/middleman-core/sitemap/store.rb +10 -9
  61. data/lib/middleman-core/step_definitions/server_steps.rb +4 -0
  62. data/lib/middleman-core/util.rb +22 -24
  63. data/lib/middleman-core/version.rb +1 -1
  64. data/spec/middleman-core/util_spec.rb +10 -1
  65. metadata +34 -4
@@ -10,7 +10,7 @@
10
10
  # to .html, .htm, .js and .css.
11
11
  #
12
12
  class Middleman::Extensions::Gzip < ::Middleman::Extension
13
- option :exts, %w(.js .css .html .htm), 'File extensions to Gzip when building.'
13
+ option :exts, %w(.js .css .html .htm .svg), 'File extensions to Gzip when building.'
14
14
  option :ignore, [], 'Patterns to avoid gzipping'
15
15
  option :overwrite, false, 'Overwrite original files instead of adding .gz extension.'
16
16
 
@@ -5,6 +5,7 @@ class Middleman::Extensions::RelativeAssets < ::Middleman::Extension
5
5
  option :exts, %w(.css .png .jpg .jpeg .webp .svg .svgz .js .gif .ttf .otf .woff .woff2 .eot), 'List of extensions that get cache busters strings appended to them.'
6
6
  option :sources, %w(.htm .html .css), 'List of extensions that are searched for relative assets.'
7
7
  option :ignore, [], 'Regexes of filenames to skip adding query strings to'
8
+ option :rewrite_ignore, [], 'Regexes of filenames to skip processing for path rewrites'
8
9
 
9
10
  def initialize(app, options_hash={}, &block)
10
11
  super
@@ -18,6 +19,7 @@ class Middleman::Extensions::RelativeAssets < ::Middleman::Extension
18
19
  url_extensions: options.exts,
19
20
  source_extensions: options.sources,
20
21
  ignore: options.ignore,
22
+ rewrite_ignore: options.rewrite_ignore,
21
23
  middleman_app: app,
22
24
  proc: method(:rewrite_url)
23
25
  end
@@ -22,7 +22,7 @@ module Middleman
22
22
 
23
23
  # Set BUNDLE_GEMFILE and run Bundler setup. Raises an exception if there is no Gemfile
24
24
  def setup_bundler
25
- ENV['BUNDLE_GEMFILE'] ||= findup('Gemfile', ENV['MM_ROOT'])
25
+ ENV['BUNDLE_GEMFILE'] ||= File.join(findup('Gemfile', ENV['MM_ROOT']), 'Gemfile')
26
26
 
27
27
  unless File.exist?(ENV['BUNDLE_GEMFILE'])
28
28
  ENV['BUNDLE_GEMFILE'] = File.expand_path('../../../../Gemfile', __FILE__)
@@ -42,7 +42,7 @@ module Middleman
42
42
  props['Source File'] = @resource.file_descriptor ? @resource.file_descriptor[:full_path].to_s.sub(/^#{Regexp.escape(ENV['MM_ROOT'] + '/')}/, '') : 'Dynamic'
43
43
 
44
44
  data = @resource.data
45
- props['Data'] = data.inspect unless data.empty?
45
+ props['Data'] = data.to_hash(symbolize_keys: true).inspect unless data.empty?
46
46
 
47
47
  options = @resource.options
48
48
  props['Options'] = options.inspect unless options.empty?
@@ -34,13 +34,16 @@ module Middleman
34
34
  @source_exts_regex_text = Regexp.union(@source_exts).to_s
35
35
 
36
36
  @ignore = options.fetch(:ignore)
37
+ @rewrite_ignore = Array(options.fetch(:rewrite_ignore, []))
37
38
  end
38
39
 
39
40
  def call(env)
40
41
  status, headers, response = @rack_app.call(env)
41
42
 
42
- # Allow upstream request to skip all rewriting
43
- return [status, headers, response] if env['bypass_inline_url_rewriter'] == 'true'
43
+ # Allow configuration or upstream request to skip all rewriting
44
+ if rewrite_ignore?(env['PATH_INFO']) || env['bypass_inline_url_rewriter'] == 'true'
45
+ return [status, headers, response]
46
+ end
44
47
 
45
48
  # Allow upstream request to skip this specific rewriting
46
49
  if @uid
@@ -96,6 +99,11 @@ module Middleman
96
99
  false
97
100
  end
98
101
  end
102
+
103
+ Contract String => Bool
104
+ def rewrite_ignore?(path)
105
+ @rewrite_ignore.any? { |ignore| Middleman::Util.path_match(ignore, path) }
106
+ end
99
107
  end
100
108
  end
101
109
  end
@@ -2,58 +2,56 @@ module Middleman
2
2
  module Sitemap
3
3
  module Extensions
4
4
  # Class to handle managing ignores
5
- class Ignores < Extension
5
+ class Ignores < ConfigExtension
6
6
  self.resource_list_manipulator_priority = 0
7
7
 
8
- # Expose `create_ignore` as `app.ignore`
9
- expose_to_application ignore: :create_ignore
10
-
11
- # Expose `create_ignore` to config as `ignore`
12
8
  expose_to_config ignore: :create_ignore
13
9
 
14
- def initialize(app, config={}, &block)
15
- super
16
-
17
- # Array of callbacks which can assign ignored
18
- @ignored_callbacks = Set.new
19
-
20
- @app.sitemap.define_singleton_method(:ignored?, &method(:ignored?))
21
- end
22
-
23
10
  # Ignore a path or add an ignore callback
24
11
  # @param [String, Regexp] path Path glob expression, or path regex
25
- # @return [void]
26
- Contract Maybe[Or[String, Regexp]], Maybe[Proc] => Any
12
+ # @return [IgnoreDescriptor]
13
+ Contract Maybe[Or[String, Regexp]], Maybe[Proc] => RespondTo[:execute_descriptor]
27
14
  def create_ignore(path=nil, &block)
28
- if path.is_a? Regexp
29
- @ignored_callbacks << proc { |p| p =~ path }
30
- elsif path.is_a? String
31
- path_clean = ::Middleman::Util.normalize_path(path)
32
- if path_clean.include?('*') # It's a glob
33
- if defined? File::FNM_EXTGLOB
34
- @ignored_callbacks << proc { |p| File.fnmatch(path_clean, p, File::FNM_EXTGLOB) }
15
+ @app.sitemap.invalidate_resources_not_ignored_cache!
16
+ IgnoreDescriptor.new(path, block)
17
+ end
18
+
19
+ IgnoreDescriptor = Struct.new(:path, :block) do
20
+ def execute_descriptor(_app, resources)
21
+ resources.map do |r|
22
+ # Ignore based on the source path (without template extensions)
23
+ if ignored?(r.path)
24
+ r.ignore!
35
25
  else
36
- @ignored_callbacks << proc { |p| File.fnmatch(path_clean, p) }
26
+ # This allows files to be ignored by their source file name (with template extensions)
27
+ r.ignore! if !r.is_a?(ProxyResource) && r.file_descriptor && ignored?(r.file_descriptor[:relative_path].to_s)
37
28
  end
38
- else
39
- # Add a specific-path ignore unless that path is already covered
40
- return if ignored?(path_clean)
41
- @ignored_callbacks << proc { |p| p == path_clean }
29
+
30
+ r
42
31
  end
43
- elsif block_given?
44
- @ignored_callbacks << block
45
32
  end
46
33
 
47
- @app.sitemap.invalidate_resources_not_ignored_cache!
48
- end
34
+ def ignored?(match_path)
35
+ match_path = ::Middleman::Util.normalize_path(match_path)
36
+
37
+ if path.is_a? Regexp
38
+ match_path =~ path
39
+ elsif path.is_a? String
40
+ path_clean = ::Middleman::Util.normalize_path(path)
49
41
 
50
- # Whether a path is ignored
51
- # @param [String] path
52
- # @return [Boolean]
53
- Contract String => Bool
54
- def ignored?(path)
55
- path_clean = ::Middleman::Util.normalize_path(path)
56
- @ignored_callbacks.any? { |b| b.call(path_clean) }
42
+ if path_clean.include?('*') # It's a glob
43
+ if defined?(::File::FNM_EXTGLOB)
44
+ ::File.fnmatch(path_clean, match_path, ::File::FNM_EXTGLOB)
45
+ else
46
+ ::File.fnmatch(path_clean, match_path)
47
+ end
48
+ else
49
+ match_path == path_clean
50
+ end
51
+ elsif block_given?
52
+ block.call(match_path)
53
+ end
54
+ end
57
55
  end
58
56
  end
59
57
  end
@@ -4,46 +4,45 @@ require 'middleman-core/contracts'
4
4
  module Middleman
5
5
  module Sitemap
6
6
  module Extensions
7
- class Import < Extension
7
+ class Import < ConfigExtension
8
8
  self.resource_list_manipulator_priority = 1
9
9
 
10
- ImportFileDescriptor = Struct.new(:from, :to)
11
- ImportPathDescriptor = Struct.new(:from, :renameProc)
12
-
13
10
  # Expose `create_import_file` to config as `import_file`
14
11
  expose_to_config import_file: :create_import_file
15
12
 
16
13
  # Expose `create_import_path` to config as `import_path`
17
14
  expose_to_config import_path: :create_import_path
18
15
 
19
- def initialize(app, config={}, &block)
20
- super
21
-
22
- @import_file_configs = Set.new
23
- @import_path_configs = Set.new
16
+ ImportFileDescriptor = Struct.new(:from, :to) do
17
+ def execute_descriptor(app, resources)
18
+ resources + [
19
+ ::Middleman::Sitemap::Resource.new(app.sitemap, to, from)
20
+ ]
21
+ end
24
22
  end
25
23
 
26
- def after_configuration
27
- ::Middleman::CoreExtensions::Collections::StepContext.add_to_context(:import_file, &method(:create_import_file))
28
- ::Middleman::CoreExtensions::Collections::StepContext.add_to_context(:import_path, &method(:create_import_path))
29
- end
24
+ ImportPathDescriptor = Struct.new(:from, :renameProc) do
25
+ def execute_descriptor(app, resources)
26
+ resources + ::Middleman::Util.glob_directory(File.join(from, '**/*'))
27
+ .reject { |path| File.directory?(path) }
28
+ .map do |path|
29
+ target_path = Pathname(path).relative_path_from(Pathname(from).parent).to_s
30
30
 
31
- # Import an external file into `source`
32
- # @param [String] from The original path.
33
- # @param [String] to The new path.
34
- # @return [void]
35
- Contract String, String => Any
36
- def create_import_file(from, to)
37
- @import_file_configs << create_anonymous_import_file(from, to)
38
- @app.sitemap.rebuild_resource_list!(:added_import_file)
31
+ ::Middleman::Sitemap::Resource.new(
32
+ app.sitemap,
33
+ renameProc.call(target_path, path),
34
+ path
35
+ )
36
+ end
37
+ end
39
38
  end
40
39
 
41
40
  # Import an external file into `source`
42
41
  # @param [String] from The original path.
43
42
  # @param [String] to The new path.
44
- # @return [ImportFileDescriptor]
43
+ # @return [void]
45
44
  Contract String, String => ImportFileDescriptor
46
- def create_anonymous_import_file(from, to)
45
+ def create_import_file(from, to)
47
46
  ImportFileDescriptor.new(
48
47
  File.expand_path(from, @app.root),
49
48
  ::Middleman::Util.normalize_path(to)
@@ -54,54 +53,13 @@ module Middleman
54
53
  # @param [String] from The original path.
55
54
  # @param [Proc] block Renaming method
56
55
  # @return [void]
57
- Contract String, Maybe[Proc] => Any
56
+ Contract String, Maybe[Proc] => ImportPathDescriptor
58
57
  def create_import_path(from, &block)
59
- rename_proc = block_given? ? block : proc { |path| path }
60
- @import_path_configs << create_anonymous_import_path(from, rename_proc)
61
- @app.sitemap.rebuild_resource_list!(:added_import_path)
62
- end
63
-
64
- # Import an external glob into `source`
65
- # @param [String] from The original path.
66
- # @param [Proc] block Renaming method
67
- # @return [ImportPathDescriptor]
68
- Contract String, Proc => ImportPathDescriptor
69
- def create_anonymous_import_path(from, block)
70
58
  ImportPathDescriptor.new(
71
59
  from,
72
- block
60
+ block_given? ? block : proc { |path| path }
73
61
  )
74
62
  end
75
-
76
- Contract IsA['Middleman::SourceFile'] => Bool
77
- def ignored?(file)
78
- @app.config[:ignored_sitemap_matchers].any? { |_, fn| fn.call(file, @app) }
79
- end
80
-
81
- # Update the main sitemap resource list
82
- # @return Array<Middleman::Sitemap::Resource>
83
- Contract ResourceList => ResourceList
84
- def manipulate_resource_list(resources)
85
- resources + @import_file_configs.map { |c|
86
- ::Middleman::Sitemap::Resource.new(
87
- @app.sitemap,
88
- c[:to],
89
- c[:from]
90
- )
91
- } + @import_path_configs.flat_map { |c|
92
- ::Middleman::Util.glob_directory(File.join(c[:from], '**/*'))
93
- .reject { |path| File.directory?(path) }
94
- .map do |path|
95
- target_path = Pathname(path).relative_path_from(Pathname(c[:from]).parent).to_s
96
-
97
- ::Middleman::Sitemap::Resource.new(
98
- @app.sitemap,
99
- c[:renameProc].call(target_path, path),
100
- path
101
- )
102
- end
103
- }
104
- end
105
63
  end
106
64
  end
107
65
  end
@@ -6,62 +6,33 @@ module Middleman
6
6
  module Extensions
7
7
  # Manages the list of proxy configurations and manipulates the sitemap
8
8
  # to include new resources based on those configurations
9
- class MoveFile < Extension
10
- MoveFileDescriptor = Struct.new(:from, :to)
11
-
9
+ class MoveFile < ConfigExtension
12
10
  self.resource_list_manipulator_priority = 101
13
11
 
14
12
  # Expose `create_move_file` to config as `move_file`
15
13
  expose_to_config move_file: :create_move_file
16
14
 
17
- def initialize(app, config={}, &block)
18
- super
19
-
20
- @move_configs = Set.new
21
- end
15
+ MoveFileDescriptor = Struct.new(:from, :to) do
16
+ def execute_descriptor(_app, resources)
17
+ resources.each do |r|
18
+ r.destination_path = to if from == r.path || from == r.destination_path
19
+ end
22
20
 
23
- def after_configuration
24
- ::Middleman::CoreExtensions::Collections::StepContext.add_to_context(:move_file, &method(:create_move_file))
21
+ resources
22
+ end
25
23
  end
26
24
 
27
25
  # Setup a move from one path to another
28
26
  # @param [String] from The original path.
29
27
  # @param [String] to The new path.
30
28
  # @return [void]
31
- Contract String, String => Any
32
- def create_move_file(from, to)
33
- @move_configs << create_anonymous_move(from, to)
34
- @app.sitemap.rebuild_resource_list!(:added_move_file)
35
- end
36
-
37
- # Setup a move from one path to another
38
- # @param [String] from The original path.
39
- # @param [String] to The new path.
40
- # @return [MoveFileDescriptor]
41
29
  Contract String, String => MoveFileDescriptor
42
- def create_anonymous_move(from, to)
30
+ def create_move_file(from, to)
43
31
  MoveFileDescriptor.new(
44
32
  ::Middleman::Util.normalize_path(from),
45
33
  ::Middleman::Util.normalize_path(to)
46
34
  )
47
35
  end
48
-
49
- # Update the main sitemap resource list
50
- # @return Array<Middleman::Sitemap::Resource>
51
- Contract ResourceList => ResourceList
52
- def manipulate_resource_list(resources)
53
- resources.each do |r|
54
- matches = @move_configs.select do |c|
55
- c.from == r.path || c.from == r.destination_path
56
- end
57
-
58
- if c = matches.last
59
- r.destination_path = c.to
60
- end
61
- end
62
-
63
- resources
64
- end
65
36
  end
66
37
  end
67
38
  end
@@ -6,28 +6,12 @@ module Middleman
6
6
  module Extensions
7
7
  # Manages the list of proxy configurations and manipulates the sitemap
8
8
  # to include new resources based on those configurations
9
- class Proxies < Extension
9
+ class Proxies < ConfigExtension
10
10
  self.resource_list_manipulator_priority = 0
11
11
 
12
- # Expose `create_proxy` as `app.proxy`
13
- expose_to_application proxy: :create_proxy
14
-
15
12
  # Expose `create_proxy` to config as `proxy`
16
13
  expose_to_config proxy: :create_proxy
17
14
 
18
- def initialize(app, config={}, &block)
19
- super
20
-
21
- @proxy_configs = Set.new
22
- @post_config = false
23
- end
24
-
25
- def after_configuration
26
- @post_config = true
27
-
28
- ::Middleman::CoreExtensions::Collections::StepContext.add_to_context(:proxy, &method(:create_anonymous_proxy))
29
- end
30
-
31
15
  # Setup a proxy from a path to a target
32
16
  # @param [String] path The new, proxied path to create
33
17
  # @param [String] target The existing path that should be proxied to. This must be a real resource, not another proxy.
@@ -36,51 +20,35 @@ module Middleman
36
20
  # @option opts [Boolean] directory_indexes Whether or not the `:directory_indexes` extension applies to these paths.
37
21
  # @option opts [Hash] locals Local variables for the template. These will be available when the template renders.
38
22
  # @option opts [Hash] data Extra metadata to add to the page. This is the same as frontmatter, though frontmatter will take precedence over metadata defined here. Available via {Resource#data}.
39
- # @return [void]
40
- Contract String, String, Maybe[Hash] => Any
23
+ # @return [ProxyDescriptor]
24
+ Contract String, String, Maybe[Hash] => RespondTo[:execute_descriptor]
41
25
  def create_proxy(path, target, opts={})
42
- options = opts.dup
43
- @app.ignore(target) if options.delete(:ignore)
44
-
45
- @proxy_configs << create_anonymous_proxy(path, target, options)
46
- @app.sitemap.rebuild_resource_list!(:added_proxy)
47
- end
48
-
49
- # Setup a proxy from a path to a target
50
- # @param [String] path The new, proxied path to create
51
- # @param [String] target The existing path that should be proxied to. This must be a real resource, not another proxy.
52
- # @option opts [Boolean] ignore Ignore the target from the sitemap (so only the new, proxy resource ends up in the output)
53
- # @option opts [Symbol, Boolean, String] layout The layout name to use (e.g. `:article`) or `false` to disable layout.
54
- # @option opts [Boolean] directory_indexes Whether or not the `:directory_indexes` extension applies to these paths.
55
- # @option opts [Hash] locals Local variables for the template. These will be available when the template renders.
56
- # @option opts [Hash] data Extra metadata to add to the page. This is the same as frontmatter, though frontmatter will take precedence over metadata defined here. Available via {Resource#data}.
57
- # @return [void]
58
- def create_anonymous_proxy(path, target, options={})
59
26
  ProxyDescriptor.new(
60
27
  ::Middleman::Util.normalize_path(path),
61
28
  ::Middleman::Util.normalize_path(target),
62
- options
29
+ opts.dup
63
30
  )
64
31
  end
65
-
66
- # Update the main sitemap resource list
67
- # @return Array<Middleman::Sitemap::Resource>
68
- Contract ResourceList => ResourceList
69
- def manipulate_resource_list(resources)
70
- resources + @proxy_configs.map { |c| c.to_resource(@app) }
71
- end
72
32
  end
73
33
 
74
34
  ProxyDescriptor = Struct.new(:path, :target, :metadata) do
75
- def to_resource(app)
76
- ProxyResource.new(app.sitemap, path, target).tap do |p|
77
- md = metadata.dup
78
- p.add_metadata(
79
- locals: md.delete(:locals) || {},
80
- page: md.delete(:data) || {},
81
- options: md
82
- )
35
+ def execute_descriptor(app, resources)
36
+ md = metadata.dup
37
+ should_ignore = md.delete(:ignore)
38
+
39
+ r = ProxyResource.new(app.sitemap, path, target)
40
+ r.add_metadata(
41
+ locals: md.delete(:locals) || {},
42
+ page: md.delete(:data) || {},
43
+ options: md
44
+ )
45
+
46
+ if should_ignore
47
+ d = ::Middleman::Sitemap::Extensions::Ignores::IgnoreDescriptor.new(target)
48
+ d.execute_descriptor(app, resources)
83
49
  end
50
+
51
+ resources + [r]
84
52
  end
85
53
  end
86
54
  end