class Jekyll::Site
def categories
def categories post_attr_hash("categories") end
def cleanup
Remove orphaned files and empty directories in destination.
def cleanup site_cleaner.cleanup! nil end
def collection_names
Returns an array of collection names from the configuration,
The list of collection names.
def collection_names case config["collections"] when Hash config["collections"].keys when Array config["collections"] when nil [] else raise ArgumentError, "Your `collections` key must be a hash or an array." end end
def collections
for each item in the collection, a new hash is returned otherwise.
If config['collections'] is set, a new instance is created
The list of collections and their corresponding Jekyll::Collection instances.
def collections @collections ||= collection_names.each_with_object({}) do |name, hsh| hsh[name] = Jekyll::Collection.new(self, name) end end
def collections_path
with the current site.
Public: The full path to the directory that houses all the collections registered
def collections_path dir_str = config["collections_dir"] @collections_path ||= dir_str.empty? ? source : in_source_dir(dir_str) end
def config=(config)
config - a Jekyll::Configuration, containing the new configuration.
changing values in the configuration.
Public: Set the site's configuration. This handles side-effects caused by
def config=(config) @config = config.clone %w(safe lsi highlighter baseurl exclude include future unpublished show_drafts limit_posts keep_files).each do |opt| send("#{opt}=", config[opt]) end # keep using `gems` to avoid breaking change self.gems = config["plugins"] configure_cache configure_plugins configure_theme configure_include_paths configure_file_read_opts self.permalink_style = config["permalink"].to_sym # Read in a _config.yml from the current theme-gem at the very end. @config = load_theme_configuration(config) if theme @config end
def configure_cache
def configure_cache Jekyll::Cache.cache_dir = in_source_dir(config["cache_dir"], "Jekyll/Cache") if safe || config["disable_disk_cache"] Jekyll::Cache.disable_disk_cache! else hide_cache_dir_from_git end end
def configure_file_read_opts
def configure_file_read_opts self.file_read_opts = {} file_read_opts[:encoding] = config["encoding"] if config["encoding"] self.file_read_opts = Jekyll::Utils.merged_file_read_opts(self, {}) end
def configure_include_paths
def configure_include_paths @includes_load_paths = Array(in_source_dir(config["includes_dir"].to_s)) @includes_load_paths << theme.includes_path if theme&.includes_path end
def configure_plugins
def configure_plugins self.plugin_manager = Jekyll::PluginManager.new(self) self.plugins = plugin_manager.plugins_path end
def configure_theme
def configure_theme self.theme = nil return if config["theme"].nil? self.theme = if config["theme"].is_a?(String) Jekyll::Theme.new(config["theme"]) else Jekyll.logger.warn "Theme:", "value of 'theme' in config should be String to use " \ "gem-based themes, but got #{config["theme"].class}" nil end end
def docs_to_write
Get the to be written documents
def docs_to_write documents.select(&:write?) end
def documents
Get all the documents
def documents collections.each_with_object(Set.new) do |(_, collection), set| set.merge(collection.docs).merge(collection.files) end.to_a end
def each_site_file
def each_site_file pages.each { |page| yield page } static_files.each { |file| yield(file) if file.write? } collections.each_value { |coll| coll.docs.each { |doc| yield(doc) if doc.write? } } end
def ensure_not_in_dest
Check that the destination dir isn't the source dir or a directory
def ensure_not_in_dest dest_pathname = Pathname.new(dest) Pathname.new(source).ascend do |path| if path == dest_pathname raise Errors::FatalException, "Destination directory cannot be or contain the Source directory." end end end
def find_converter_instance(klass)
Returns the Converter instance implementing the given Converter.
Get the implementation class for the given Converter.
def find_converter_instance(klass) @find_converter_instance ||= {} @find_converter_instance[klass] ||= converters.find do |converter| converter.instance_of?(klass) end || \ raise("No Converters found for #{klass}") end
def frontmatter_defaults
if it doesn't already exist.
Returns the FrontmatterDefaults or creates a new FrontmatterDefaults
def frontmatter_defaults @frontmatter_defaults ||= FrontmatterDefaults.new(self) end
def generate
Run each of the Generators.
def generate generators.each do |generator| start = Time.now generator.generate(self) Jekyll.logger.debug "Generating:", "#{generator.class} finished in #{Time.now - start} seconds." end nil end
def hide_cache_dir_from_git
def hide_cache_dir_from_git @cache_gitignore_path ||= in_source_dir(config["cache_dir"], ".gitignore") return if File.exist?(@cache_gitignore_path) cache_dir_path = in_source_dir(config["cache_dir"]) FileUtils.mkdir_p(cache_dir_path) unless File.directory?(cache_dir_path) File.open(@cache_gitignore_path, "wb") do |file| file.puts("# ignore everything in this directory\n*") end end
def in_cache_dir(*paths)
cache directory
paths - (optional) path elements to a file or directory within the
Public: Prefix a given path with the cache directory.
def in_cache_dir(*paths) paths.reduce(cache_dir) do |base, path| Jekyll.sanitized_path(base, path) end end
def in_dest_dir(*paths)
destination directory
paths - (optional) path elements to a file or directory within the
Public: Prefix a given path with the destination directory.
def in_dest_dir(*paths) paths.reduce(dest) do |base, path| Jekyll.sanitized_path(base, path) end end
def in_source_dir(*paths)
source directory
paths - (optional) path elements to a file or directory within the
Public: Prefix a given path with the source directory.
def in_source_dir(*paths) paths.reduce(source) do |base, path| Jekyll.sanitized_path(base, path) end end
def in_theme_dir(*paths)
theme directory
paths - (optional) path elements to a file or directory within the
Public: Prefix a given path with the theme directory.
def in_theme_dir(*paths) return nil unless theme paths.reduce(theme.root) do |base, path| Jekyll.sanitized_path(base, path) end end
def incremental?(override = {})
Whether to perform a full rebuild without incremental regeneration
def incremental?(override = {}) override["incremental"] || config["incremental"] end
def initialize(config)
Public: Initialize a new Site.
def initialize(config) # Source and destination may not be changed after the site has been created. @source = File.expand_path(config["source"]).freeze @dest = File.expand_path(config["destination"]).freeze self.config = config @cache_dir = in_source_dir(config["cache_dir"]) @filter_cache = {} @reader = Reader.new(self) @profiler = Profiler.new(self) @regenerator = Regenerator.new(self) @liquid_renderer = LiquidRenderer.new(self) Jekyll.sites << self reset setup Jekyll::Hooks.trigger :site, :after_init, self end
def inspect
Public
def inspect "#<#{self.class} @source=#{@source}>" end
def instantiate_subclasses(klass)
def instantiate_subclasses(klass) klass.descendants.select { |c| !safe || c.safe }.tap do |result| result.sort! result.map! { |c| c.new(config) } end end
def limit_posts!
Limits the current posts; removes the posts which exceed the limit_posts
def limit_posts! if limit_posts.positive? limit = posts.docs.length < limit_posts ? posts.docs.length : limit_posts posts.docs = posts.docs[-limit, limit] end end
def load_theme_configuration(config)
def load_theme_configuration(config) return config if config["ignore_theme_config"] == true theme_config_file = in_theme_dir("_config.yml") return config unless File.exist?(theme_config_file) # Bail out if the theme_config_file is a symlink file irrespective of safe mode return config if File.symlink?(theme_config_file) theme_config = SafeYAML.load_file(theme_config_file) return config unless theme_config.is_a?(Hash) Jekyll.logger.info "Theme Config file:", theme_config_file # theme_config should not be overriding Jekyll's defaults theme_config.delete_if { |key, _| Configuration::DEFAULTS.key?(key) } # Override theme_config with existing config and return the result. # Additionally ensure we return a `Jekyll::Configuration` instance instead of a Hash. Utils.deep_merge_hashes(theme_config, config) .each_with_object(Jekyll::Configuration.new) do |(key, value), conf| conf[key] = value end end
def post_attr_hash(post_attr)
attr - One of the values for the requested attribute.
Returns the Hash: { attr => posts } where
# 'ruby' => [
# => { 'tech' => [
post_attr_hash('categories')
Examples
post_attr - The String name of the Post attribute.
Construct a Hash of Posts indexed by the specified Post attribute.
def post_attr_hash(post_attr) # Build a hash map based on the specified post attribute ( post attr => # array of posts ) then sort each array in reverse order. @post_attr_hash[post_attr] ||= begin hash = Hash.new { |h, key| h[key] = [] } posts.docs.each do |p| p.data[post_attr]&.each { |t| hash[t] << p } end hash.each_value { |posts| posts.sort!.reverse! } hash end end
def posts
def posts collections["posts"] ||= Collection.new(self, "posts") end
def print_stats
def print_stats Jekyll.logger.info @liquid_renderer.stats_table end
def process
Public: Read, process, and write this Site to output.
def process return profiler.profile_process if config["profile"] reset read generate render cleanup write end
def publisher
already exist.
Returns the publisher or creates a new publisher if it doesn't
def publisher @publisher ||= Publisher.new(self) end
def read
Read Site data from disk and load it into internal data structures.
def read reader.read limit_posts! Jekyll::Hooks.trigger :site, :post_read, self nil end
def relative_permalinks_are_deprecated
directory. As this is a deprecated function of Jekyll.
Warns the user if permanent links are relative to the parent
def relative_permalinks_are_deprecated if config["relative_permalinks"] Jekyll.logger.abort_with "Since v3.0, permalinks for pages " \ "in subfolders must be relative to the " \ "site source directory, not the parent " \ "directory. Check https://jekyllrb.com/docs/upgrading/ " \ "for more info." end end
def render
Render the site to the destination.
def render relative_permalinks_are_deprecated payload = site_payload Jekyll::Hooks.trigger :site, :pre_render, self, payload render_docs(payload) render_pages(payload) Jekyll::Hooks.trigger :site, :post_render, self, payload nil end
def render_docs(payload)
def render_docs(payload) collections.each_value do |collection| collection.docs.each do |document| render_regenerated(document, payload) end end end
def render_pages(payload)
def render_pages(payload) pages.each do |page| render_regenerated(page, payload) end end
def render_regenerated(document, payload)
def render_regenerated(document, payload) return unless regenerator.regenerate?(document) document.renderer.payload = payload document.output = document.renderer.run document.trigger_hooks(:post_render) end
def reset
Reset Site details.
rubocop:disable Metrics/MethodLength
rubocop:disable Metrics/AbcSize
def reset self.time = if config["time"] Utils.parse_date(config["time"].to_s, "Invalid time in _config.yml.") else Time.now end self.layouts = {} self.inclusions = {} self.pages = [] self.static_files = [] self.data = {} @post_attr_hash = {} @site_data = nil @collections = nil @documents = nil @docs_to_write = nil @regenerator.clear_cache @liquid_renderer.reset @site_cleaner = nil frontmatter_defaults.reset raise ArgumentError, "limit_posts must be a non-negative number" if limit_posts.negative? Jekyll::Cache.clear_if_config_changed config Jekyll::Hooks.trigger :site, :after_reset, self nil end
def setup
Load necessary libraries, plugins, converters, and generators.
def setup ensure_not_in_dest plugin_manager.conscientious_require self.converters = instantiate_subclasses(Jekyll::Converter) self.generators = instantiate_subclasses(Jekyll::Generator) end
def site_cleaner
already exist.
Returns the Cleaner or creates a new Cleaner if it doesn't
def site_cleaner @site_cleaner ||= Cleaner.new(self) end
def site_data
if the key 'data' is already used in _config.yml.
Prepare site data for site payload. The method maintains backward compatibility
def site_data @site_data ||= (config["data"] || data) end
def site_payload
"tags" - The Hash of tag values and Posts.
See Site#post_attr_hash for type info.
"categories" - The Hash of category values and Posts.
"html_pages" - The Array of HTML Pages.
"pages" - The Array of all Pages.
and then title.
"posts" - The Array of Posts, sorted chronologically by post date
current time if none was specified.
"time" - The Time as specified in the configuration or the
Returns the Hash: { "site" => data } where data is a Hash with keys:
The Hash payload containing site-wide data.
def site_payload Drops::UnifiedPayloadDrop.new self end
def static_files_to_write
Get the to be written static files
def static_files_to_write static_files.select(&:write?) end
def tags
def tags post_attr_hash("tags") end
def write
Write static files, pages, and posts.
def write Jekyll::Commands::Doctor.conflicting_urls(self) each_site_file do |item| item.write(dest) if regenerator.regenerate?(item) end regenerator.write_metadata Jekyll::Hooks.trigger :site, :post_write, self nil end