bridgetown-core 0.19.3 → 0.20.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/bridgetown-core.gemspec +1 -1
  3. data/lib/bridgetown-core.rb +30 -11
  4. data/lib/bridgetown-core/cleaner.rb +7 -1
  5. data/lib/bridgetown-core/collection.rb +173 -77
  6. data/lib/bridgetown-core/commands/base.rb +9 -0
  7. data/lib/bridgetown-core/commands/configure.rb +4 -0
  8. data/lib/bridgetown-core/commands/console.rb +4 -0
  9. data/lib/bridgetown-core/concerns/data_accessible.rb +1 -0
  10. data/lib/bridgetown-core/concerns/site/configurable.rb +7 -3
  11. data/lib/bridgetown-core/concerns/site/content.rb +57 -15
  12. data/lib/bridgetown-core/concerns/site/processable.rb +1 -0
  13. data/lib/bridgetown-core/concerns/site/renderable.rb +26 -0
  14. data/lib/bridgetown-core/concerns/site/writable.rb +11 -1
  15. data/lib/bridgetown-core/concerns/validatable.rb +1 -0
  16. data/lib/bridgetown-core/configuration.rb +39 -19
  17. data/lib/bridgetown-core/converter.rb +14 -0
  18. data/lib/bridgetown-core/converters/identity.rb +0 -9
  19. data/lib/bridgetown-core/converters/markdown.rb +14 -4
  20. data/lib/bridgetown-core/converters/markdown/kramdown_parser.rb +3 -0
  21. data/lib/bridgetown-core/current.rb +10 -0
  22. data/lib/bridgetown-core/document.rb +6 -14
  23. data/lib/bridgetown-core/drops/collection_drop.rb +1 -1
  24. data/lib/bridgetown-core/drops/page_drop.rb +4 -0
  25. data/lib/bridgetown-core/drops/resource_drop.rb +81 -0
  26. data/lib/bridgetown-core/drops/site_drop.rb +33 -8
  27. data/lib/bridgetown-core/drops/unified_payload_drop.rb +4 -0
  28. data/lib/bridgetown-core/entry_filter.rb +10 -23
  29. data/lib/bridgetown-core/errors.rb +0 -2
  30. data/lib/bridgetown-core/filters.rb +2 -1
  31. data/lib/bridgetown-core/generators/prototype_generator.rb +37 -19
  32. data/lib/bridgetown-core/layout.rb +2 -2
  33. data/lib/bridgetown-core/liquid_renderer/file.rb +1 -0
  34. data/lib/bridgetown-core/liquid_renderer/table.rb +1 -0
  35. data/lib/bridgetown-core/model/base.rb +138 -0
  36. data/lib/bridgetown-core/model/builder_origin.rb +40 -0
  37. data/lib/bridgetown-core/model/file_origin.rb +119 -0
  38. data/lib/bridgetown-core/model/origin.rb +38 -0
  39. data/lib/bridgetown-core/page.rb +9 -1
  40. data/lib/bridgetown-core/plugin_manager.rb +0 -2
  41. data/lib/bridgetown-core/publisher.rb +7 -1
  42. data/lib/bridgetown-core/reader.rb +25 -12
  43. data/lib/bridgetown-core/readers/data_reader.rb +3 -4
  44. data/lib/bridgetown-core/readers/post_reader.rb +1 -1
  45. data/lib/bridgetown-core/regenerator.rb +8 -1
  46. data/lib/bridgetown-core/related_posts.rb +1 -1
  47. data/lib/bridgetown-core/renderer.rb +5 -12
  48. data/lib/bridgetown-core/resource/base.rb +275 -0
  49. data/lib/bridgetown-core/resource/destination.rb +49 -0
  50. data/lib/bridgetown-core/resource/permalink_processor.rb +179 -0
  51. data/lib/bridgetown-core/resource/taxonomy_term.rb +25 -0
  52. data/lib/bridgetown-core/resource/taxonomy_type.rb +47 -0
  53. data/lib/bridgetown-core/resource/transformer.rb +173 -0
  54. data/lib/bridgetown-core/ruby_template_view.rb +4 -0
  55. data/lib/bridgetown-core/site.rb +9 -1
  56. data/lib/bridgetown-core/static_file.rb +33 -10
  57. data/lib/bridgetown-core/url.rb +1 -0
  58. data/lib/bridgetown-core/utils.rb +40 -40
  59. data/lib/bridgetown-core/utils/platforms.rb +1 -0
  60. data/lib/bridgetown-core/version.rb +2 -2
  61. data/lib/site_template/webpack.config.js.erb +8 -6
  62. metadata +28 -21
  63. data/lib/bridgetown-core/page_without_a_file.rb +0 -17
  64. data/lib/bridgetown-core/readers/collection_reader.rb +0 -23
  65. data/lib/bridgetown-core/utils/exec.rb +0 -26
  66. data/lib/bridgetown-core/utils/internet.rb +0 -37
  67. data/lib/bridgetown-core/utils/win_tz.rb +0 -75
@@ -22,7 +22,9 @@ module Bridgetown
22
22
  raise Errors::FatalException, "Invalid Markdown processor given: #{@config["markdown"]}"
23
23
  end
24
24
 
25
- @cache = Bridgetown::Cache.new("Bridgetown::Converters::Markdown")
25
+ unless @config.cache_markdown == false || @config.kramdown.input == "GFMExtractions"
26
+ @cache = Bridgetown::Cache.new("Bridgetown::Converters::Markdown")
27
+ end
26
28
  @setup = true
27
29
  end
28
30
 
@@ -59,10 +61,18 @@ module Bridgetown
59
61
  # content - String content of file (without front matter).
60
62
  #
61
63
  # Returns a String of the converted content.
62
- def convert(content)
64
+ def convert(content, convertible = nil)
63
65
  setup
64
- @cache.getset(content) do
65
- @parser.convert(content)
66
+ if @cache
67
+ @cache.getset(content) do
68
+ @parser.convert(content)
69
+ end
70
+ else
71
+ output = @parser.convert(content)
72
+ if @parser.respond_to?(:extractions)
73
+ convertible.data.markdown_extractions = @parser.extractions
74
+ end
75
+ output
66
76
  end
67
77
  end
68
78
 
@@ -69,6 +69,8 @@ module Bridgetown
69
69
  module Converters
70
70
  class Markdown
71
71
  class KramdownParser
72
+ attr_reader :extractions
73
+
72
74
  def initialize(config)
73
75
  @main_fallback_highlighter = config["highlighter"] || "rouge"
74
76
  @config = config["kramdown"] || {}
@@ -95,6 +97,7 @@ module Bridgetown
95
97
  Bridgetown.logger.warn "Kramdown warning:", warning
96
98
  end
97
99
  end
100
+ @extractions = document.root.options[:extractions] # could be nil
98
101
  html_output
99
102
  end
100
103
 
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ class Current < ActiveSupport::CurrentAttributes
5
+ # @!method self.site
6
+ # @return [Bridgetown::Site]
7
+
8
+ attribute :site
9
+ end
10
+ end
@@ -52,7 +52,7 @@ module Bridgetown
52
52
  @has_yaml_header = nil
53
53
 
54
54
  data.default_proc = proc do |_, key|
55
- site.frontmatter_defaults.find(relative_path, type, key)
55
+ site.frontmatter_defaults.find(relative_path, type, key.to_s)
56
56
  end
57
57
 
58
58
  trigger_hooks(:post_init)
@@ -81,7 +81,7 @@ module Bridgetown
81
81
  #
82
82
  # Return document date string.
83
83
  def date
84
- data["date"] ||= site.time
84
+ data["date"] ||= site.time # TODO: this doesn't reflect documented behavior
85
85
  end
86
86
 
87
87
  # Return document file modification time in the form of a Time object.
@@ -402,21 +402,13 @@ module Bridgetown
402
402
  end
403
403
 
404
404
  def populate_categories
405
- categories = Array(data["categories"]) + Utils.pluralized_array_from_hash(
406
- data, "category", "categories"
407
- )
408
- categories.map!(&:to_s)
409
- categories.flatten!
410
- categories.uniq!
411
-
412
- merge_data!({ "categories" => categories })
405
+ data.categories = Utils.pluralized_array_from_hash(
406
+ data, :category, :categories
407
+ ).map(&:to_s)
413
408
  end
414
409
 
415
410
  def populate_tags
416
- tags = Utils.pluralized_array_from_hash(data, "tag", "tags")
417
- tags.flatten!
418
-
419
- merge_data!({ "tags" => tags })
411
+ data.tags = Utils.pluralized_array_from_hash(data, :tag, :tags)
420
412
  end
421
413
 
422
414
  def determine_locale
@@ -8,7 +8,7 @@ module Bridgetown
8
8
  mutable false
9
9
 
10
10
  def_delegator :@obj, :write?, :output
11
- def_delegators :@obj, :label, :docs, :files, :relative_path
11
+ def_delegators :@obj, :label, :docs, :files, :relative_path, :resources, :static_files
12
12
 
13
13
  private def_delegator :@obj, :metadata, :fallback_data
14
14
 
@@ -9,6 +9,10 @@ module Bridgetown
9
9
 
10
10
  def_delegators :@obj, :content, :dir, :name, :path, :url, :pager
11
11
  private def_delegator :@obj, :data, :fallback_data
12
+
13
+ def relative_url
14
+ @obj.relative_url
15
+ end
12
16
  end
13
17
  end
14
18
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Drops
5
+ class ResourceDrop < Drop
6
+ extend Forwardable
7
+
8
+ NESTED_OBJECT_FIELD_BLACKLIST = %w(
9
+ content output excerpt next previous
10
+ ).freeze
11
+
12
+ mutable false
13
+
14
+ def_delegator :@obj, :relative_path, :path
15
+ def_delegators :@obj,
16
+ :id,
17
+ :output,
18
+ :content,
19
+ :to_s,
20
+ :absolute_url,
21
+ :relative_path,
22
+ :relative_url,
23
+ :date,
24
+ :taxonomies
25
+
26
+ private def_delegator :@obj, :data, :fallback_data
27
+
28
+ def collection
29
+ @collection ||= @obj.collection.to_liquid
30
+ end
31
+
32
+ def excerpt
33
+ fallback_data["excerpt"].to_s
34
+ end
35
+
36
+ def <=>(other)
37
+ return nil unless other.is_a? ResourceDrop
38
+
39
+ cmp = self["date"] <=> other["date"]
40
+ cmp = self["path"] <=> other["path"] if cmp.nil? || cmp.zero?
41
+ cmp
42
+ end
43
+
44
+ def previous
45
+ @previous ||= @obj.previous_resource.to_liquid
46
+ end
47
+
48
+ def next
49
+ @next ||= @obj.previous_resource.to_liquid
50
+ end
51
+
52
+ # Generate a Hash for use in generating JSON.
53
+ # This is useful if fields need to be cleared before the JSON can generate.
54
+ #
55
+ # state - the JSON::State object which determines the state of current processing.
56
+ #
57
+ # Returns a Hash ready for JSON generation.
58
+ def hash_for_json(state = nil)
59
+ to_h.tap do |hash|
60
+ # use collection label in the hash
61
+ hash["collection"] = hash["collection"]["label"] if hash["collection"]
62
+
63
+ if state && state.depth >= 2
64
+ hash["previous"] = collapse_document(hash["previous"]) if hash["previous"]
65
+ hash["next"] = collapse_document(hash["next"]) if hash["next"]
66
+ end
67
+ end
68
+ end
69
+
70
+ # Generate a Hash which breaks the recursive chain.
71
+ # Certain fields which are normally available are omitted.
72
+ #
73
+ # Returns a Hash with only non-recursive fields present.
74
+ def collapse_document(doc)
75
+ doc.keys.each_with_object({}) do |(key, _), result|
76
+ result[key] = doc[key] unless NESTED_OBJECT_FIELD_BLACKLIST.include?(key)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -7,35 +7,53 @@ module Bridgetown
7
7
 
8
8
  mutable false
9
9
 
10
- def_delegator :@obj, :data
11
- def_delegators :@obj, :locale, :time, :pages, :static_files, :tags, :categories
10
+ def_delegators :@obj,
11
+ :data,
12
+ :locale,
13
+ :time,
14
+ :pages,
15
+ :generated_pages,
16
+ :static_files,
17
+ :tags,
18
+ :categories,
19
+ :taxonomies,
20
+ :taxonomy_types
12
21
 
13
22
  private def_delegator :@obj, :config, :fallback_data
14
23
 
15
24
  attr_writer :current_document
16
25
 
17
26
  def [](key)
18
- if key != "posts" && @obj.collections.key?(key)
19
- @obj.collections[key].docs
20
- else
21
- super(key)
27
+ if !@obj.uses_resource? && !%w(posts data).freeze.include?(key) &&
28
+ @obj.collections.key?(key)
29
+ return @obj.collections[key].docs
22
30
  end
31
+
32
+ super(key)
23
33
  end
24
34
 
25
35
  def key?(key)
26
- (key != "posts" && @obj.collections.key?(key)) || super
36
+ (!@obj.uses_resource? && key != "posts" && @obj.collections.key?(key)) || super
37
+ end
38
+
39
+ def uses_resource
40
+ @obj.uses_resource?
27
41
  end
28
42
 
29
43
  def posts
30
- @site_posts ||= @obj.posts.docs.sort { |a, b| b <=> a }
44
+ unless @obj.uses_resource?
45
+ @site_posts ||= @obj.collections.posts.docs.sort { |a, b| b <=> a }
46
+ end
31
47
  end
32
48
 
49
+ # TODO: deprecate before v1.0
33
50
  def html_pages
34
51
  @site_html_pages ||= @obj.pages.select do |page|
35
52
  page.html? || page.url.end_with?("/")
36
53
  end
37
54
  end
38
55
 
56
+ # TODO: deprecate before v1.0
39
57
  def collections
40
58
  @site_collections ||= @obj.collections.values.sort_by(&:label).map(&:to_liquid)
41
59
  end
@@ -50,6 +68,10 @@ module Bridgetown
50
68
  @documents ||= @obj.documents
51
69
  end
52
70
 
71
+ def resources
72
+ @resources ||= @obj.resources
73
+ end
74
+
53
75
  def contents
54
76
  @contents ||= @obj.contents
55
77
  end
@@ -58,6 +80,9 @@ module Bridgetown
58
80
  @site_metadata ||= @obj.data["site_metadata"]
59
81
  end
60
82
 
83
+ # TODO: change this so you *do* use site.config...aka site.config.timezone,
84
+ # not site.timezone
85
+ #
61
86
  # return nil for `{{ site.config }}` even if --config was passed via CLI
62
87
  def config; end
63
88
  end
@@ -15,6 +15,10 @@ module Bridgetown
15
15
  @site_drop ||= SiteDrop.new(@obj)
16
16
  end
17
17
 
18
+ def collections
19
+ @obj.collections
20
+ end
21
+
18
22
  private
19
23
 
20
24
  def fallback_data
@@ -4,12 +4,14 @@ module Bridgetown
4
4
  class EntryFilter
5
5
  attr_reader :site
6
6
  SPECIAL_LEADING_CHAR_REGEX = %r!\A#{Regexp.union([".", "_", "#", "~"])}!o.freeze
7
+ SPECIAL_LEADING_CHAR_NO_UNDERSCORES_REGEX = %r!\A#{Regexp.union([".", "#", "~"])}!o.freeze
7
8
 
8
- def initialize(site, base_directory = nil)
9
+ def initialize(site, base_directory: nil, include_underscores: false)
9
10
  @site = site
10
11
  @base_directory = derive_base_directory(
11
12
  @site, base_directory.to_s.dup
12
13
  )
14
+ @include_underscores = include_underscores
13
15
  end
14
16
 
15
17
  def base_directory
@@ -32,8 +34,6 @@ module Bridgetown
32
34
  # Reject this entry if it is just a "dot" representation.
33
35
  # e.g.: '.', '..', '_movies/.', 'music/..', etc
34
36
  next true if e.end_with?(".")
35
- # Reject this entry if it is a symlink.
36
- next true if symlink?(e)
37
37
  # Do not reject this entry if it is included.
38
38
  next false if included?(e)
39
39
 
@@ -48,8 +48,13 @@ module Bridgetown
48
48
  end
49
49
 
50
50
  def special?(entry)
51
- SPECIAL_LEADING_CHAR_REGEX.match?(entry) ||
52
- SPECIAL_LEADING_CHAR_REGEX.match?(File.basename(entry))
51
+ use_regex = if @include_underscores
52
+ SPECIAL_LEADING_CHAR_NO_UNDERSCORES_REGEX
53
+ else
54
+ SPECIAL_LEADING_CHAR_REGEX
55
+ end
56
+
57
+ use_regex.match?(entry) || use_regex.match?(File.basename(entry))
53
58
  end
54
59
 
55
60
  def backup?(entry)
@@ -67,24 +72,6 @@ module Bridgetown
67
72
  end
68
73
  end
69
74
 
70
- # --
71
- # TODO: this is for old Safe mode and can be removed.
72
- # --
73
- def symlink?(_entry)
74
- false
75
- end
76
-
77
- # --
78
- # NOTE: Pathutil#in_path? gets the realpath.
79
- # @param [<Anything>] entry the entry you want to validate.
80
- # Check if a path is outside of our given root.
81
- # --
82
- def symlink_outside_site_source?(entry)
83
- !Pathutil.new(entry).in_path?(
84
- site.in_source_dir
85
- )
86
- end
87
-
88
75
  # Check if an entry matches a specific pattern.
89
76
  # Returns true if path matches against any glob pattern, else false.
90
77
  def glob_include?(enumerator, entry)
@@ -4,8 +4,6 @@ module Bridgetown
4
4
  module Errors
5
5
  FatalException = Class.new(::RuntimeError)
6
6
 
7
- InvalidThemeName = Class.new(FatalException)
8
-
9
7
  DropMutationException = Class.new(FatalException)
10
8
  InvalidPermalinkError = Class.new(FatalException)
11
9
  InvalidYAMLFrontMatterError = Class.new(FatalException)
@@ -63,6 +63,7 @@ module Bridgetown
63
63
  # Returns the given filename or title as a lowercase URL String.
64
64
  # See Utils.slugify for more detail.
65
65
  def slugify(input, mode = nil)
66
+ mode = @context.registers[:site].config.slugify_mode if mode.nil?
66
67
  Utils.slugify(input, mode: mode)
67
68
  end
68
69
 
@@ -102,7 +103,7 @@ module Bridgetown
102
103
  #
103
104
  # Returns the escaped String.
104
105
  def cgi_escape(input)
105
- CGI.escape(input)
106
+ CGI.escape(input.to_s)
106
107
  end
107
108
 
108
109
  # URI escape a string.
@@ -1,11 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Handles Legacy Pages
3
4
  Bridgetown::Hooks.register :pages, :post_init, reloadable: false do |page|
4
5
  if page.class != Bridgetown::PrototypePage && page.data["prototype"].is_a?(Hash)
5
6
  Bridgetown::PrototypeGenerator.add_matching_template(page)
6
7
  end
7
8
  end
8
9
 
10
+ # Handles Resources
11
+ Bridgetown::Hooks.register :resources, :post_read, reloadable: false do |resource|
12
+ if resource.data["prototype"].is_a?(Hash)
13
+ Bridgetown::PrototypeGenerator.add_matching_template(resource)
14
+ end
15
+ end
16
+
9
17
  module Bridgetown
10
18
  class PrototypeGenerator < Generator
11
19
  priority :low
@@ -13,27 +21,27 @@ module Bridgetown
13
21
  # @return [Bridgetown::Site]
14
22
  attr_reader :site
15
23
 
16
- @matching_templates = []
17
-
18
- def self.add_matching_template(template)
19
- @matching_templates << template
24
+ # @return [Array<Bridgetown::Page, Bridgetown::Resource::Base>]
25
+ def self.matching_templates
26
+ @matching_templates ||= []
20
27
  end
21
28
 
22
- class << self
23
- # @return [Array<Page>]
24
- attr_reader :matching_templates
29
+ def self.add_matching_template(template)
30
+ matching_templates << template
25
31
  end
26
32
 
33
+ # @param site [Bridgetown::Site]
27
34
  def generate(site)
28
35
  @site = site
29
- @configured_collection = "posts"
36
+ @configured_collection = "posts" unless site.uses_resource?
37
+ page_list = site.uses_resource? ? site.collections.pages.resources : site.pages
30
38
 
31
39
  prototype_pages = self.class.matching_templates.select do |page|
32
- site.pages.include? page
40
+ page_list.include?(page)
33
41
  end
34
42
 
35
43
  if prototype_pages.length.positive?
36
- site.pages.reject! do |page|
44
+ page_list.reject! do |page|
37
45
  prototype_pages.include? page
38
46
  end
39
47
 
@@ -42,7 +50,7 @@ module Bridgetown
42
50
  next if search_term.nil?
43
51
 
44
52
  terms_matching_pages(search_term).each do |term|
45
- generate_new_page_from_prototype(prototype_page, search_term, term).data
53
+ generate_new_page_from_prototype(prototype_page, search_term, term)
46
54
  end
47
55
  end
48
56
  end
@@ -50,7 +58,7 @@ module Bridgetown
50
58
 
51
59
  # Check incoming prototype configuration and normalize options.
52
60
  #
53
- # @param prototype_page [PrototypePage]
61
+ # @param prototype_page [Bridgetown::Page, Bridgetown::Resource::Base]
54
62
  #
55
63
  # @return [String, nil]
56
64
  def validate_search_term(prototype_page)
@@ -71,7 +79,7 @@ module Bridgetown
71
79
 
72
80
  def generate_new_page_from_prototype(prototype_page, search_term, term)
73
81
  new_page = PrototypePage.new(prototype_page, @configured_collection, search_term, term)
74
- site.pages << new_page
82
+ site.add_generated_page new_page
75
83
  new_page
76
84
  end
77
85
 
@@ -81,16 +89,26 @@ module Bridgetown
81
89
  #
82
90
  # @return [Array<String>]
83
91
  def terms_matching_pages(search_term)
92
+ pages_list = if site.uses_resource?
93
+ site.collections[@configured_collection].resources
94
+ else
95
+ site.collections[@configured_collection].docs
96
+ end
97
+
84
98
  Bridgetown::Paginate::PaginationIndexer.index_documents_by(
85
- site.collections[@configured_collection].docs, search_term
99
+ pages_list, search_term
86
100
  ).keys
87
101
  end
88
102
  end
89
103
 
90
- class PrototypePage < Page
91
- # @return [Page]
104
+ class PrototypePage < GeneratedPage
105
+ # @return [Bridgetown::Page, Bridgetown::Resource::Base]
92
106
  attr_reader :prototyped_page
93
107
 
108
+ # @param prototyped_page [Bridgetown::Page, Bridgetown::Resource::Base]
109
+ # @param collection [Bridgetown::Collection]
110
+ # @param search_term [String]
111
+ # @param term [String]
94
112
  def initialize(prototyped_page, collection, search_term, term)
95
113
  @prototyped_page = prototyped_page
96
114
  @site = prototyped_page.site
@@ -107,7 +125,7 @@ module Bridgetown
107
125
  validate_data! prototyped_page.path
108
126
  validate_permalink! prototyped_page.path
109
127
 
110
- @dir = Pathname.new(prototyped_page.relative_path).dirname.to_s
128
+ @dir = Pathname.new(prototyped_page.relative_path).dirname.to_s.sub(%r{^_pages}, "")
111
129
  @path = site.in_source_dir(@dir, @name)
112
130
 
113
131
  process_prototype_page_data(collection, search_term, term)
@@ -132,7 +150,7 @@ module Bridgetown
132
150
  slugify_term(term)
133
151
  end
134
152
 
135
- # rubocop:disable Metrics/AbcSize
153
+ # rubocop:todo Metrics/AbcSize
136
154
  def process_title_data_placeholder(search_term, term)
137
155
  if prototyped_page.data["prototype"]["data"]
138
156
  if data["title"]&.include?(":prototype-data-label")
@@ -164,7 +182,7 @@ module Bridgetown
164
182
  end
165
183
 
166
184
  def slugify_term(term)
167
- term_slug = Bridgetown::Utils.slugify(term)
185
+ term_slug = Bridgetown::Utils.slugify(term, mode: site.config.slugify_mode)
168
186
  @url = if permalink.is_a?(String)
169
187
  data["permalink"] = data["permalink"].sub(":term", term_slug)
170
188
  else