Class: Bridgetown::URL

Inherits:
Object
  • Object
show all
Defined in:
lib/bridgetown-core/url.rb

Overview

TODO: remove this class in favor of the new Resource permalink processor

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ URL

options - One of :permalink or :template must be supplied. :template - The String used as template for URL generation, for example "/:path/:basename:output_ext", where a placeholder is prefixed with a colon. :placeholders - A hash containing the placeholders which will be replaced when used inside the template. E.g. { "year" => Time.now.strftime("%Y") } would replace the placeholder ":year" with the current year. :permalink - If supplied, no URL will be generated from the template. Instead, the given permalink will be used as URL.

Raises:

  • (ArgumentError)


26
27
28
29
30
31
32
33
34
# File 'lib/bridgetown-core/url.rb', line 26

def initialize(options)
  @template     = options[:template]
  @placeholders = options[:placeholders] || {}
  @permalink    = options[:permalink]

  return unless (@template || @permalink).nil?

  raise ArgumentError, "One of :template or :permalink must be supplied."
end

Class Method Details

.escape_path(path) ⇒ Object

Escapes a path to be a valid URL path segment

path - The path to be escaped.

Examples:

URL.escape_path("/a b") # => "/a%20b"

Returns the escaped path.



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/bridgetown-core/url.rb', line 132

def self.escape_path(path)
  path = path.to_s
  return path if path.empty? || %r!^[a-zA-Z0-9./-]+$!.match?(path)

  # Because URI.escape doesn't escape "?", "[" and "]" by default,
  # specify unsafe string (except unreserved, sub-delims, ":", "@" and "/").
  #
  # URI path segment is defined in RFC 3986 as follows:
  #   segment       = *pchar
  #   pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
  #   unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
  #   pct-encoded   = "%" HEXDIG HEXDIG
  #   sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
  #                 / "*" / "+" / "," / ";" / "="
  Addressable::URI.encode(path).encode("utf-8").sub("#", "%23")
end

.unescape_path(path) ⇒ Object

Unescapes a URL path segment

path - The path to be unescaped.

Examples:

URL.unescape_path("/a%20b") # => "/a b"

Returns the unescaped path.



159
160
161
162
163
164
# File 'lib/bridgetown-core/url.rb', line 159

def self.unescape_path(path)
  path = path.encode("utf-8")
  return path unless path.include?("%")

  Addressable::URI.unencode(path)
end

Instance Method Details

#generate_url(template) ⇒ Object

Internal: Generate the URL by replacing all placeholders with their respective values in the given template

Returns the unsanitized String URL



61
62
63
64
65
66
67
# File 'lib/bridgetown-core/url.rb', line 61

def generate_url(template)
  if @placeholders.is_a? Drops::UrlDrop
    generate_url_from_drop(template)
  else
    generate_url_from_hash(template)
  end
end

#generate_url_from_drop(template) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/bridgetown-core/url.rb', line 96

def generate_url_from_drop(template)
  template.gsub(%r!:([a-z_]+)!) do |match|
    pool = possible_keys(match.sub(":", ""))

    winner = pool.find { |key| @placeholders.key?(key) }
    if winner.nil?
      raise NoMethodError,
            "The URL template doesn't have #{pool.join(" or ")} keys. " \
            "Check your permalink template!"
    end

    value = @placeholders[winner]
    value = "" if value.nil?
    replacement = self.class.escape_path(value)

    match.sub(":#{winner}", replacement)
  end.squeeze("/")
end

#generate_url_from_hash(template) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/bridgetown-core/url.rb', line 69

def generate_url_from_hash(template)
  @placeholders.inject(template) do |result, token|
    break result if result.index(":").nil?

    if token.last.nil?
      # Remove leading "/" to avoid generating urls with `//`
      result.gsub("/:#{token.first}", "")
    else
      result.gsub(":#{token.first}", self.class.escape_path(token.last))
    end
  end
end

Generates a URL from the permalink

Returns the _unsanitized String URL



46
47
48
# File 'lib/bridgetown-core/url.rb', line 46

def generated_permalink
  (@generated_permalink ||= generate_url(@permalink)) if @permalink
end

#generated_urlObject

Generates a URL from the template

Returns the unsanitized String URL



53
54
55
# File 'lib/bridgetown-core/url.rb', line 53

def generated_url
  @generated_url ||= generate_url(@template)
end

#possible_keys(key) ⇒ Object

We include underscores in keys to allow for 'i_month' and so forth. This poses a problem for keys which are followed by an underscore but the underscore is not part of the key, e.g. '/:month_:day'. That should be :month and :day, but our key extraction regexp isn't smart enough to know that so we have to make it an explicit possibility.



88
89
90
91
92
93
94
# File 'lib/bridgetown-core/url.rb', line 88

def possible_keys(key)
  if key.end_with?("_")
    [key, key.chomp("_")]
  else
    [key]
  end
end

#sanitize_url(str) ⇒ Object

Returns a sanitized String URL, stripping "../../" and multiples of "/", as well as the beginning "/" so we can enforce and ensure it.



118
119
120
# File 'lib/bridgetown-core/url.rb', line 118

def sanitize_url(str)
  "/#{str}".gsub("..", "/").gsub("./", "").squeeze("/")
end

#to_sObject

The generated relative URL of the resource

Returns the String URL



39
40
41
# File 'lib/bridgetown-core/url.rb', line 39

def to_s
  sanitize_url(generated_permalink || generated_url)
end