bridgetown-core 0.16.0.beta1 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -0
- data/bridgetown-core.gemspec +3 -1
- data/lib/bridgetown-core.rb +45 -29
- data/lib/bridgetown-core/collection.rb +5 -1
- data/lib/bridgetown-core/commands/apply.rb +2 -2
- data/lib/bridgetown-core/commands/concerns/actions.rb +2 -1
- data/lib/bridgetown-core/commands/console.rb +4 -4
- data/lib/bridgetown-core/commands/new.rb +1 -1
- data/lib/bridgetown-core/concerns/layout_placeable.rb +1 -1
- data/lib/bridgetown-core/concerns/liquid_renderable.rb +10 -0
- data/lib/bridgetown-core/concerns/site/configurable.rb +24 -22
- data/lib/bridgetown-core/concerns/site/content.rb +46 -33
- data/lib/bridgetown-core/concerns/site/extensible.rb +14 -13
- data/lib/bridgetown-core/concerns/site/localizable.rb +24 -0
- data/lib/bridgetown-core/concerns/site/processable.rb +12 -11
- data/lib/bridgetown-core/concerns/site/renderable.rb +35 -28
- data/lib/bridgetown-core/concerns/site/writable.rb +7 -15
- data/lib/bridgetown-core/concerns/validatable.rb +2 -2
- data/lib/bridgetown-core/configuration.rb +14 -6
- data/lib/bridgetown-core/converter.rb +0 -42
- data/lib/bridgetown-core/converters/erb_templates.rb +93 -17
- data/lib/bridgetown-core/converters/liquid_templates.rb +96 -0
- data/lib/bridgetown-core/converters/markdown.rb +0 -3
- data/lib/bridgetown-core/document.rb +34 -21
- data/lib/bridgetown-core/drops/site_drop.rb +5 -1
- data/lib/bridgetown-core/drops/unified_payload_drop.rb +0 -1
- data/lib/bridgetown-core/drops/url_drop.rb +19 -3
- data/lib/bridgetown-core/excerpt.rb +1 -1
- data/lib/bridgetown-core/filters.rb +37 -55
- data/lib/bridgetown-core/filters/condition_helpers.rb +56 -0
- data/lib/bridgetown-core/frontmatter_defaults.rb +17 -0
- data/lib/bridgetown-core/generators/prototype_generator.rb +42 -25
- data/lib/bridgetown-core/helpers.rb +84 -0
- data/lib/bridgetown-core/liquid_renderer.rb +1 -1
- data/lib/bridgetown-core/log_writer.rb +2 -2
- data/lib/bridgetown-core/page.rb +8 -2
- data/lib/bridgetown-core/plugin_manager.rb +44 -3
- data/lib/bridgetown-core/reader.rb +2 -4
- data/lib/bridgetown-core/readers/collection_reader.rb +1 -0
- data/lib/bridgetown-core/readers/data_reader.rb +4 -3
- data/lib/bridgetown-core/readers/defaults_reader.rb +27 -0
- data/lib/bridgetown-core/readers/layout_reader.rb +1 -0
- data/lib/bridgetown-core/readers/page_reader.rb +1 -0
- data/lib/bridgetown-core/readers/post_reader.rb +29 -15
- data/lib/bridgetown-core/readers/static_file_reader.rb +1 -0
- data/lib/bridgetown-core/renderer.rb +42 -160
- data/lib/bridgetown-core/ruby_template_view.rb +26 -8
- data/lib/bridgetown-core/site.rb +14 -2
- data/lib/bridgetown-core/tags/find.rb +86 -0
- data/lib/bridgetown-core/tags/t.rb +14 -0
- data/lib/bridgetown-core/tags/webpack_path.rb +6 -41
- data/lib/bridgetown-core/utils.rb +69 -2
- data/lib/bridgetown-core/utils/ruby_exec.rb +1 -1
- data/lib/bridgetown-core/version.rb +2 -2
- data/lib/bridgetown-core/watcher.rb +1 -0
- data/lib/site_template/src/_layouts/{default.html → default.liquid} +0 -0
- data/lib/site_template/src/_layouts/{home.html → home.liquid} +0 -0
- data/lib/site_template/src/_layouts/{page.html → page.liquid} +0 -0
- data/lib/site_template/src/_layouts/{post.html → post.liquid} +0 -0
- data/lib/site_template/src/images/.keep +1 -0
- metadata +47 -10
@@ -54,6 +54,10 @@ module Bridgetown
|
|
54
54
|
#
|
55
55
|
# Returns the default value or nil if none was found
|
56
56
|
def find(path, type, setting)
|
57
|
+
merged_data = {}
|
58
|
+
merge_data_cascade_for_path(path, merged_data)
|
59
|
+
return merged_data[setting] if merged_data.key?(setting)
|
60
|
+
|
57
61
|
value = nil
|
58
62
|
old_scope = nil
|
59
63
|
|
@@ -74,6 +78,9 @@ module Bridgetown
|
|
74
78
|
# Returns a hash with all default values (an empty hash if there are none)
|
75
79
|
def all(path, type)
|
76
80
|
defaults = {}
|
81
|
+
|
82
|
+
merge_data_cascade_for_path(path, defaults)
|
83
|
+
|
77
84
|
old_scope = nil
|
78
85
|
matching_sets(path, type).each do |set|
|
79
86
|
if has_precedence?(old_scope, set["scope"])
|
@@ -88,6 +95,16 @@ module Bridgetown
|
|
88
95
|
|
89
96
|
private
|
90
97
|
|
98
|
+
def merge_data_cascade_for_path(path, merged_data)
|
99
|
+
absolute_path = @site.in_source_dir(path)
|
100
|
+
@site.defaults_reader.path_defaults
|
101
|
+
.select { |k, _v| absolute_path.include? k }
|
102
|
+
.sort_by { |k, _v| k.length }
|
103
|
+
.each do |defaults|
|
104
|
+
merged_data.merge!(defaults[1])
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
91
108
|
# Checks if a given default setting scope matches the given path and type
|
92
109
|
#
|
93
110
|
# scope - the hash indicating the scope, as defined in bridgetown.config.yml
|
@@ -10,6 +10,9 @@ module Bridgetown
|
|
10
10
|
class PrototypeGenerator < Generator
|
11
11
|
priority :low
|
12
12
|
|
13
|
+
# @return [Bridgetown::Site]
|
14
|
+
attr_reader :site
|
15
|
+
|
13
16
|
@matching_templates = []
|
14
17
|
|
15
18
|
def self.add_matching_template(template)
|
@@ -17,6 +20,7 @@ module Bridgetown
|
|
17
20
|
end
|
18
21
|
|
19
22
|
class << self
|
23
|
+
# @return [Array<Page>]
|
20
24
|
attr_reader :matching_templates
|
21
25
|
end
|
22
26
|
|
@@ -44,7 +48,13 @@ module Bridgetown
|
|
44
48
|
end
|
45
49
|
end
|
46
50
|
|
51
|
+
# Check incoming prototype configuration and normalize options.
|
52
|
+
#
|
53
|
+
# @param prototype_page [PrototypePage]
|
54
|
+
#
|
55
|
+
# @return [String, nil]
|
47
56
|
def validate_search_term(prototype_page)
|
57
|
+
# @type [String]
|
48
58
|
search_term = prototype_page.data["prototype"]["term"]
|
49
59
|
return nil unless search_term.is_a?(String)
|
50
60
|
|
@@ -52,6 +62,8 @@ module Bridgetown
|
|
52
62
|
@configured_collection = prototype_page.data["prototype"]["collection"]
|
53
63
|
end
|
54
64
|
|
65
|
+
return nil unless site.collections[@configured_collection]
|
66
|
+
|
55
67
|
# Categories and Tags are unique in that singular and plural front matter
|
56
68
|
# can be present for each
|
57
69
|
search_term.sub(%r!^category$!, "categories").sub(%r!^tag$!, "tags")
|
@@ -59,71 +71,75 @@ module Bridgetown
|
|
59
71
|
|
60
72
|
def generate_new_page_from_prototype(prototype_page, search_term, term)
|
61
73
|
new_page = PrototypePage.new(prototype_page, @configured_collection, search_term, term)
|
62
|
-
|
74
|
+
site.pages << new_page
|
63
75
|
new_page
|
64
76
|
end
|
65
77
|
|
66
|
-
#
|
67
|
-
#
|
78
|
+
# Provide a list of all relevent indexed values for the given term.
|
79
|
+
#
|
80
|
+
# @param search_term [String]
|
81
|
+
#
|
82
|
+
# @return [Array<String>]
|
68
83
|
def terms_matching_pages(search_term)
|
69
|
-
selected_docs = @site.documents.select do |document|
|
70
|
-
document.respond_to?(:collection) && document.collection.label == @configured_collection
|
71
|
-
end
|
72
|
-
|
73
84
|
Bridgetown::Paginate::PaginationIndexer.index_documents_by(
|
74
|
-
|
85
|
+
site.collections[@configured_collection].docs, search_term
|
75
86
|
).keys
|
76
87
|
end
|
77
88
|
end
|
78
89
|
|
79
90
|
class PrototypePage < Page
|
80
|
-
|
81
|
-
|
91
|
+
# @return [Page]
|
92
|
+
attr_reader :prototyped_page
|
93
|
+
|
94
|
+
def initialize(prototyped_page, collection, search_term, term)
|
95
|
+
@prototyped_page = prototyped_page
|
96
|
+
@site = prototyped_page.site
|
82
97
|
@url = ""
|
83
98
|
@name = "index.html"
|
84
|
-
@path =
|
99
|
+
@path = prototyped_page.path
|
85
100
|
|
86
101
|
process(@name)
|
87
102
|
|
88
|
-
self.data = Bridgetown::Utils.deep_merge_hashes
|
89
|
-
self.content =
|
103
|
+
self.data = Bridgetown::Utils.deep_merge_hashes prototyped_page.data, {}
|
104
|
+
self.content = prototyped_page.content
|
90
105
|
|
91
106
|
# Perform some validation that is also performed in Bridgetown::Page
|
92
|
-
validate_data!
|
93
|
-
validate_permalink!
|
107
|
+
validate_data! prototyped_page.path
|
108
|
+
validate_permalink! prototyped_page.path
|
94
109
|
|
95
|
-
@dir = Pathname.new(
|
110
|
+
@dir = Pathname.new(prototyped_page.relative_path).dirname.to_s
|
96
111
|
@path = site.in_source_dir(@dir, @name)
|
97
112
|
|
98
|
-
process_prototype_page_data(
|
113
|
+
process_prototype_page_data(collection, search_term, term)
|
99
114
|
|
100
115
|
Bridgetown::Hooks.trigger :pages, :post_init, self
|
101
116
|
end
|
102
117
|
|
103
|
-
def process_prototype_page_data(
|
118
|
+
def process_prototype_page_data(collection, search_term, term)
|
104
119
|
# Fill in pagination details to be handled later by Bridgetown::Paginate
|
105
120
|
data["pagination"] = Bridgetown::Utils.deep_merge_hashes(
|
106
|
-
|
121
|
+
prototyped_page.data["pagination"].to_h, {
|
107
122
|
"enabled" => true,
|
108
123
|
"collection" => collection,
|
109
124
|
"where_query" => [search_term, term],
|
110
125
|
}
|
111
126
|
)
|
112
127
|
# Use the original prototype page term so we get "tag" back, not "tags":
|
113
|
-
data[
|
128
|
+
data[prototyped_page.data["prototype"]["term"]] = term
|
114
129
|
# Process title and slugs/URLs:
|
115
|
-
process_title_data_placeholder(
|
130
|
+
process_title_data_placeholder(search_term, term)
|
116
131
|
process_title_simple_placeholders(term)
|
117
132
|
slugify_term(term)
|
118
133
|
end
|
119
134
|
|
120
|
-
|
121
|
-
|
135
|
+
# rubocop:disable Metrics/AbcSize
|
136
|
+
def process_title_data_placeholder(search_term, term)
|
137
|
+
if prototyped_page.data["prototype"]["data"]
|
122
138
|
if data["title"]&.include?(":prototype-data-label")
|
123
|
-
related_data =
|
139
|
+
related_data = site.data[prototyped_page.data["prototype"]["data"]].dig(term)
|
124
140
|
if related_data
|
125
141
|
data["#{search_term}_data"] = related_data
|
126
|
-
data_label = related_data[
|
142
|
+
data_label = related_data[prototyped_page.data["prototype"]["data_label"]]
|
127
143
|
data["title"] = data["title"].gsub(
|
128
144
|
":prototype-data-label", data_label
|
129
145
|
)
|
@@ -131,6 +147,7 @@ module Bridgetown
|
|
131
147
|
end
|
132
148
|
end
|
133
149
|
end
|
150
|
+
# rubocop:enable Metrics/AbcSize
|
134
151
|
|
135
152
|
def process_title_simple_placeholders(term)
|
136
153
|
if data["title"]&.include?(":prototype-term-titleize")
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bridgetown
|
4
|
+
class RubyTemplateView
|
5
|
+
class Helpers
|
6
|
+
include Bridgetown::Filters
|
7
|
+
|
8
|
+
attr_reader :view, :site
|
9
|
+
|
10
|
+
Context = Struct.new(:registers)
|
11
|
+
|
12
|
+
def initialize(view, site)
|
13
|
+
@view = view
|
14
|
+
@site = site
|
15
|
+
|
16
|
+
# duck typing for Liquid context
|
17
|
+
@context = Context.new({ site: site })
|
18
|
+
end
|
19
|
+
|
20
|
+
def webpack_path(asset_type)
|
21
|
+
Bridgetown::Utils.parse_webpack_manifest_file(site, asset_type.to_s)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param pairs [Hash] A hash of key/value pairs.
|
25
|
+
#
|
26
|
+
# @return [String] Space-separated keys where the values are truthy.
|
27
|
+
def class_map(pairs = {})
|
28
|
+
pairs.select { |_key, truthy| truthy }.keys.join(" ")
|
29
|
+
end
|
30
|
+
|
31
|
+
# This helper will generate the correct permalink URL for the file path.
|
32
|
+
#
|
33
|
+
# @param relative_path [String, Object] source file path, e.g.
|
34
|
+
# "_posts/2020-10-20-my-post.md", or object that responds to `url`
|
35
|
+
# @return [String] the permalink URL for the file
|
36
|
+
# @raise [ArgumentError] if the file cannot be found
|
37
|
+
def url_for(relative_path)
|
38
|
+
path_string = !relative_path.is_a?(String) ? relative_path.url : relative_path
|
39
|
+
|
40
|
+
return path_string if path_string.start_with?("/", "http")
|
41
|
+
|
42
|
+
site.each_site_file do |item|
|
43
|
+
if item.relative_path == path_string || item.relative_path == "/#{path_string}"
|
44
|
+
return relative_url(item)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
raise ArgumentError, <<~MSG
|
49
|
+
Could not find document '#{relative_path}' in 'url_for' helper.
|
50
|
+
|
51
|
+
Make sure the document exists and the path is correct.
|
52
|
+
MSG
|
53
|
+
end
|
54
|
+
alias_method :link, :url_for
|
55
|
+
|
56
|
+
# This helper will generate the correct permalink URL for the file path.
|
57
|
+
#
|
58
|
+
# @param text [String] the content inside the anchor tag
|
59
|
+
# @param relative_path [String, Object] source file path, e.g.
|
60
|
+
# "_posts/2020-10-20-my-post.md", or object that responds to `url`
|
61
|
+
# @param options [Hash] key-value pairs of HTML attributes to add to the tag
|
62
|
+
# @return [String] the anchor tag HTML
|
63
|
+
# @raise [ArgumentError] if the file cannot be found
|
64
|
+
def link_to(text, relative_path, options = {})
|
65
|
+
segments = []
|
66
|
+
segments << "a"
|
67
|
+
segments << "href=\"#{url_for(relative_path)}\""
|
68
|
+
options.each do |attr, option|
|
69
|
+
attr = attr.to_s.tr("_", "-")
|
70
|
+
segments << "#{attr}=\"#{Utils.xml_escape(option)}\""
|
71
|
+
end
|
72
|
+
"<#{segments.join(" ")}>#{text}</a>"
|
73
|
+
end
|
74
|
+
|
75
|
+
# Forward all arguments to I18n.t method
|
76
|
+
#
|
77
|
+
# @return [String] the translated string
|
78
|
+
# @see I18n
|
79
|
+
def t(*args)
|
80
|
+
I18n.send :t, *args
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -33,12 +33,12 @@ module Bridgetown
|
|
33
33
|
true
|
34
34
|
end
|
35
35
|
|
36
|
-
# Log a
|
36
|
+
# Log a `WARN` message
|
37
37
|
def warn(progname = nil, &block)
|
38
38
|
add(WARN, nil, progname.yellow, &block)
|
39
39
|
end
|
40
40
|
|
41
|
-
# Log an
|
41
|
+
# Log an `ERROR` message
|
42
42
|
def error(progname = nil, &block)
|
43
43
|
add(ERROR, nil, progname.red, &block)
|
44
44
|
end
|
data/lib/bridgetown-core/page.rb
CHANGED
@@ -9,7 +9,7 @@ module Bridgetown
|
|
9
9
|
include Validatable
|
10
10
|
|
11
11
|
attr_writer :dir
|
12
|
-
attr_accessor :site, :pager
|
12
|
+
attr_accessor :site, :paginator, :pager
|
13
13
|
attr_accessor :name, :ext, :basename
|
14
14
|
attr_accessor :data, :content, :output
|
15
15
|
|
@@ -121,12 +121,18 @@ module Bridgetown
|
|
121
121
|
# desired placeholder replacements. For details see "url.rb"
|
122
122
|
def url_placeholders
|
123
123
|
{
|
124
|
-
path:
|
124
|
+
path: qualified_pages_path_for_url,
|
125
125
|
basename: basename,
|
126
126
|
output_ext: output_ext,
|
127
127
|
}
|
128
128
|
end
|
129
129
|
|
130
|
+
# Strips _pages prefix off if needed for the url/destination generation
|
131
|
+
# @return [String]
|
132
|
+
def qualified_pages_path_for_url
|
133
|
+
@dir.sub(%r!^/_pages!, "")
|
134
|
+
end
|
135
|
+
|
130
136
|
# Extract information from the page filename.
|
131
137
|
#
|
132
138
|
# name - The String filename of the page file.
|
@@ -1,11 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "zeitwerk"
|
4
|
+
|
3
5
|
module Bridgetown
|
4
6
|
class PluginManager
|
5
7
|
PLUGINS_GROUP = :bridgetown_plugins
|
6
8
|
YARN_DEPENDENCY_REGEXP = %r!(.+)@([^@]*)$!.freeze
|
7
9
|
|
8
|
-
attr_reader :site
|
10
|
+
attr_reader :site, :component_loaders
|
9
11
|
|
10
12
|
@source_manifests = Set.new
|
11
13
|
@registered_plugins = Set.new
|
@@ -37,6 +39,7 @@ module Bridgetown
|
|
37
39
|
# Returns nothing
|
38
40
|
def initialize(site)
|
39
41
|
@site = site
|
42
|
+
@component_loaders = {}
|
40
43
|
end
|
41
44
|
|
42
45
|
def self.require_from_bundler
|
@@ -68,12 +71,20 @@ module Bridgetown
|
|
68
71
|
# If that exact package hasn't been installed, execute yarn add
|
69
72
|
#
|
70
73
|
# Returns nothing.
|
71
|
-
def self.install_yarn_dependencies(required_gems)
|
74
|
+
def self.install_yarn_dependencies(required_gems, single_gemname = nil)
|
72
75
|
return unless File.exist?("package.json")
|
73
76
|
|
74
77
|
package_json = JSON.parse(File.read("package.json"))
|
75
78
|
|
76
|
-
|
79
|
+
gems_to_search = if single_gemname
|
80
|
+
required_gems.select do |loaded_gem|
|
81
|
+
loaded_gem.to_spec&.name == single_gemname.to_s
|
82
|
+
end
|
83
|
+
else
|
84
|
+
required_gems
|
85
|
+
end
|
86
|
+
|
87
|
+
gems_to_search.each do |loaded_gem|
|
77
88
|
yarn_dependency = find_yarn_dependency(loaded_gem)
|
78
89
|
next unless add_yarn_dependency?(yarn_dependency, package_json)
|
79
90
|
|
@@ -150,5 +161,35 @@ module Bridgetown
|
|
150
161
|
Array(site.config["plugins_dir"]).map { |d| File.expand_path(d) }
|
151
162
|
end
|
152
163
|
end
|
164
|
+
|
165
|
+
# rubocop:disable Metrics/AbcSize
|
166
|
+
def setup_component_loaders
|
167
|
+
unless @component_loaders.keys.empty?
|
168
|
+
@component_loaders.each do |_path, loader|
|
169
|
+
loader.unload
|
170
|
+
end
|
171
|
+
@component_loaders = {}
|
172
|
+
end
|
173
|
+
|
174
|
+
# Because "first constant wins" in Zeitwerk, we need to load the local
|
175
|
+
# source components _before_ we load any from plugins
|
176
|
+
site.components_load_paths.reverse_each do |load_path|
|
177
|
+
next unless Dir.exist? load_path
|
178
|
+
next if Zeitwerk::Registry.loaders.find { |loader| loader.manages?(load_path) }
|
179
|
+
|
180
|
+
@component_loaders[load_path] = Zeitwerk::Loader.new
|
181
|
+
@component_loaders[load_path].push_dir(load_path)
|
182
|
+
@component_loaders[load_path].enable_reloading if load_path.start_with?(site.root_dir)
|
183
|
+
@component_loaders[load_path].ignore(File.join(load_path, "**", "*.js.rb"))
|
184
|
+
@component_loaders[load_path].setup
|
185
|
+
end
|
186
|
+
end
|
187
|
+
# rubocop:enable Metrics/AbcSize
|
188
|
+
|
189
|
+
def reload_component_loaders
|
190
|
+
@component_loaders.each do |path, loader|
|
191
|
+
loader.reload if path.start_with?(site.root_dir)
|
192
|
+
end
|
193
|
+
end
|
153
194
|
end
|
154
195
|
end
|
@@ -13,6 +13,7 @@ module Bridgetown
|
|
13
13
|
# Returns nothing.
|
14
14
|
# rubocop:disable Metrics/AbcSize
|
15
15
|
def read
|
16
|
+
@site.defaults_reader.read
|
16
17
|
@site.layouts = LayoutReader.new(site).read
|
17
18
|
read_directories
|
18
19
|
read_included_excludes
|
@@ -75,10 +76,7 @@ module Bridgetown
|
|
75
76
|
def retrieve_posts(dir)
|
76
77
|
return if outside_configured_directory?(dir)
|
77
78
|
|
78
|
-
post_reader.read_posts(dir)
|
79
|
-
site.posts.docs.concat(entries.select { |entry| entry.is_a?(Document) })
|
80
|
-
site.posts.files.concat(entries.select { |entry| entry.is_a?(StaticFile) })
|
81
|
-
end
|
79
|
+
post_reader.read_posts(dir)
|
82
80
|
end
|
83
81
|
|
84
82
|
# Recursively traverse directories with the read_directories function.
|
@@ -3,9 +3,10 @@
|
|
3
3
|
module Bridgetown
|
4
4
|
class DataReader
|
5
5
|
attr_reader :site, :content
|
6
|
+
|
6
7
|
def initialize(site)
|
7
8
|
@site = site
|
8
|
-
@content =
|
9
|
+
@content = {}
|
9
10
|
@entry_filter = EntryFilter.new(site)
|
10
11
|
end
|
11
12
|
|
@@ -19,7 +20,7 @@ module Bridgetown
|
|
19
20
|
base = site.in_source_dir(dir)
|
20
21
|
read_data_to(base, @content)
|
21
22
|
merge_environment_specific_metadata!
|
22
|
-
@content
|
23
|
+
@content = @content.with_dot_access
|
23
24
|
end
|
24
25
|
|
25
26
|
# Read and parse all .yaml, .yml, .json, .csv and .tsv
|
@@ -43,7 +44,7 @@ module Bridgetown
|
|
43
44
|
if File.directory?(path)
|
44
45
|
read_data_to(
|
45
46
|
path,
|
46
|
-
data[sanitize_filename(entry)] =
|
47
|
+
data[sanitize_filename(entry)] = {}
|
47
48
|
)
|
48
49
|
else
|
49
50
|
key = sanitize_filename(File.basename(entry, ".*"))
|