Module: SiteMaps

Defined in:
lib/site_maps.rb,
lib/site_maps/cli.rb,
lib/site_maps/ping.rb,
lib/site_maps/runner.rb,
lib/site_maps/process.rb,
lib/site_maps/railtie.rb,
lib/site_maps/version.rb,
lib/site_maps/middleware.rb,
lib/site_maps/robots_txt.rb,
lib/site_maps/notification.rb,
lib/site_maps/configuration.rb,
lib/site_maps/sitemap_reader.rb,
lib/site_maps/sitemap_builder.rb,
lib/site_maps/notification/bus.rb,
lib/site_maps/primitive/output.rb,
lib/site_maps/atomic_repository.rb,
lib/site_maps/notification/event.rb,
lib/site_maps/incremental_location.rb,
lib/site_maps/runner/event_listener.rb

Defined Under Namespace

Modules: Adapters, Builder, Notification, Ping, Primitive, RobotsTxt Classes: AtomicRepository, CLI, Configuration, IncrementalLocation, Middleware, Railtie, Runner, SitemapBuilder, SitemapReader

Constant Summary collapse

MAX_LENGTH =
{
  links: 50_000,
  images: 1_000,
  news: 1_000
}
MAX_FILESIZE =

bytes

50_000_000
DEFAULT_LOGGER =
::Logger.new($stdout)
Error =
Class.new(StandardError)
AdapterNotFound =
Class.new(Error)
AdapterNotSetError =
Class.new(Error)
FileNotFoundError =
Class.new(Error)
FullSitemapError =
Class.new(Error)
ConfigurationError =
Class.new(Error)
SCOPE_KEY =
:__site_maps_scope__
Process =
Concurrent::ImmutableStruct.new(:name, :location_template, :kwargs_template, :block) do
  def id
    @id ||= SecureRandom.hex(4)
  end

  def location(**kwargs)
    return unless location_template

    location_template % keyword_arguments(kwargs)
  end

  def call(builder, **kwargs)
    return unless block

    block.call(builder, **keyword_arguments(kwargs))
  end

  def static?
    !dynamic?
  end

  def dynamic?
    kwargs_template.is_a?(Hash) && kwargs_template.any?
  end

  def keyword_arguments(given)
    (kwargs_template || {}).merge(given || {})
  end
end
VERSION =
"0.1.0"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.current_adapterObject (readonly)

Returns the value of attribute current_adapter.



52
53
54
# File 'lib/site_maps.rb', line 52

def current_adapter
  @current_adapter
end

.loggerObject



175
176
177
# File 'lib/site_maps.rb', line 175

def logger
  @mutex.synchronize { @logger ||= DEFAULT_LOGGER }
end

Class Method Details

.config {|@config| ... } ⇒ Object Also known as: configure

Yields:



106
107
108
109
110
# File 'lib/site_maps.rb', line 106

def config
  @mutex.synchronize { @config ||= Configuration.new }
  yield(@config) if block_given?
  @config
end

.define(&block) ⇒ Object

Register a context-aware sitemap definition. The block is stored and called when generate is invoked with a ‘context:` hash. The hash keys are passed as keyword arguments to the block.

Example:

# config/sitemap.rb
SiteMaps.define do |site:|
  use(:file_system) do
    config.url = "https://#{site.domain}/sitemap.xml"
    process { |s| site.pages.each { |p| s.add(p.path) } }
  end
end

# Usage:
SiteMaps.generate(config_file: "config/sitemap.rb", context: {site: site})
  .enqueue_all
  .run

Parameters:

  • block (Proc)

    Receives keyword arguments from the ‘context:` hash



98
99
100
101
102
103
104
# File 'lib/site_maps.rb', line 98

def define(&block)
  if (scope = Thread.current[SCOPE_KEY])
    scope[:definition] = block
  else
    @definition = block
  end
end

.generate(config_file: nil, context: nil, **options) ⇒ Runner

Load and prepare a runner with the current adapter Note that it won’t start running until you call ‘#run` on the runner

Example:

SiteMaps.generate(config_file: "config/site_maps.rb", max_threads: 10)
  .enqueue_all
  .run

You may also enqueue processes manually, specially those that are dynamic

Example:

SiteMaps.generate(config_file: "config/site_maps.rb", max_threads: 10)
  .enqueue(:monthly, year: 2020, month: 1)
  .enqueue(:monthly, year: 2020, month: 2)
  .enqueue_remaining # Enqueue all other non-enqueued processes
  .run

For multi-tenant / context-aware configurations, the config file can use define and pass runtime context as keyword arguments via the ‘context:` kwarg:

Example:

SiteMaps.generate(config_file: "config/sitemap.rb", context: {site: site})
  .enqueue_all
  .run

Parameters:

  • config_file (String) (defaults to: nil)

    The path to a configuration file

  • context (Hash) (defaults to: nil)

    Keyword arguments passed to the block registered via define. Must be a Hash (or nil for no context).

  • options (Hash)

    Options to pass to the runner

Returns:

  • (Runner)

    An instance of the runner

Raises:



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/site_maps.rb', line 144

def generate(config_file: nil, context: nil, **options)
  adapter = nil
  if config_file
    previous_scope = Thread.current[SCOPE_KEY]
    scope = {adapter: nil, definition: nil}
    Thread.current[SCOPE_KEY] = scope
    begin
      load(config_file)
      if scope[:definition]
        kwargs = context || {}
        raise ArgumentError, "context: must be a Hash, got #{context.class}" unless kwargs.is_a?(Hash)

        instance_exec(**kwargs, &scope[:definition])
      end
      adapter = scope[:adapter]
    ensure
      Thread.current[SCOPE_KEY] = previous_scope
    end
    # Preserve backward-compat: expose the generated adapter through
    # the `current_adapter` singleton for single-tenant callers. In
    # multi-tenant concurrent use, last-writer-wins — each Runner still
    # gets its own isolated adapter from the thread-local scope above.
    @current_adapter = adapter if adapter
  else
    adapter = current_adapter
  end
  raise AdapterNotSetError, "No adapter set. Use SiteMaps.use to set an adapter" unless adapter

  Runner.new(adapter, **options)
end

.use(adapter, **options, &block) ⇒ Object

Returns An instance of the adapter.

Parameters:

  • adapter (Class, String, Symbol)

    The name of the adapter to use

  • options (Hash)

    Options to pass to the adapter. Note that these are adapter-specific

  • block (Proc)

    A block to pass to the adapter

Returns:

  • (Object)

    An instance of the adapter



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/site_maps.rb', line 59

def use(adapter, **options, &block)
  adapter_class = if adapter.is_a?(Class) # && adapter < Adapters::Adapter
    adapter
  else
    const_name = Primitive::String.new(adapter.to_s).classify
    begin
      Adapters.const_get(const_name)
    rescue NameError
      raise AdapterNotFound, "Adapter #{adapter.inspect} not found"
    end
  end
  instance = adapter_class.new(**options, &block)
  if (scope = Thread.current[SCOPE_KEY])
    scope[:adapter] = instance
  else
    @current_adapter = instance
  end
  instance
end