lib/nanoc/base/site.rb in nanoc-2.0.4 vs lib/nanoc/base/site.rb in nanoc-2.1

- old
+ new

@@ -1,141 +1,272 @@ module Nanoc + + # A Nanoc::Site is the in-memory representation of a nanoc site. It holds + # references to the following site data: + # + # * +pages+ is a list of Nanoc::Page instances representing pages + # * +assets+ is a list of Nanoc::Asset instances representing assets + # * +page_defaults+ is a Nanoc::PageDefaults instance representing page + # defaults + # * +asset_defaults+ is a Nanoc::AssetDefaults instance representing asset + # defaults + # * +layouts+ is a list of Nanoc::Layout instances representing layouts + # * +templates+ is a list of Nanoc::Template representing templates + # * +code+ is a Nanoc::Code instance representing custom site code + # + # In addition, each site has a +config+ hash which stores the site + # configuration. This configuration hash can have the following keys: + # + # +output_dir+:: The directory to which compiled pages and assets will be + # written. This path is relative to the current working + # directory, but can also be an absolute path. + # + # +data_source+:: The identifier of the data source that will be used for + # loading site data. + # + # +router+:: The identifier of the router that will be used for determining + # page and asset representation paths. + # + # +index_filenames+:: A list of filenames that will be stripped off full + # page and asset paths to create cleaner URLs (for + # example, '/about/' will be used instead of + # '/about/index.html'). The default value should be okay + # in most cases. + # + # A site also has several helper classes: + # + # * +router+ is a Nanoc::Router subclass instance used for determining page + # and asset paths. + # * +data_source+ is a Nanoc::DataSource subclass instance used for managing + # site data. + # * +compiler+ is a Nanoc::Compiler instance that compiles page and asset + # representations. + # + # The physical representation of a Nanoc::Site is usually a directory that + # contains a configuration file, site data, and some rake tasks. However, + # different frontends may store data differently. For example, a web-based + # frontend would probably store the configuration and the site content in a + # database, and would not have rake tasks at all. class Site + # The default configuration for a site. A site's configuration overrides + # these options: when a Nanoc::Site is created with a configuration that + # lacks some options, the default value will be taken from + # +DEFAULT_CONFIG+. DEFAULT_CONFIG = { - :output_dir => 'output', - :eruby_engine => 'erb', - :data_source => 'filesystem' + :output_dir => 'output', + :data_source => 'filesystem', + :router => 'default', + :index_filenames => [ 'index.html' ] } attr_reader :config - attr_reader :compiler, :data_source - attr_reader :code, :pages, :page_defaults, :layouts, :templates + attr_reader :compiler, :data_source, :router + attr_reader :page_defaults, :asset_defaults + attr_reader :pages, :assets, :layouts, :templates, :code - # Creating a Site object - - def self.from_cwd - File.file?('config.yaml') ? new : nil - end - - def initialize + # Returns a Nanoc::Site object for the site specified by the given + # configuration hash +config+. + # + # +config+:: A hash containing the site configuration. + def initialize(config) # Load configuration - @config = DEFAULT_CONFIG.merge((YAML.load_file('config.yaml') || {}).clean) + @config = DEFAULT_CONFIG.merge(config.clean) # Create data source - @data_source_class = PluginManager.data_source_named(@config[:data_source]) - error "Unrecognised data source: #{@config[:data_source]}" if @data_source_class.nil? + @data_source_class = Nanoc::DataSource.named(@config[:data_source]) + raise Nanoc::Errors::UnknownDataSourceError.new(@config[:data_source]) if @data_source_class.nil? @data_source = @data_source_class.new(self) # Create compiler - @compiler = Compiler.new(self) - @autocompiler = AutoCompiler.new(self) + @compiler = Compiler.new(self) - # Set not loaded - @data_loaded = false + # Load code (necessary for custom routers) + load_code + + # Create router + @router_class = Nanoc::Router.named(@config[:router]) + raise Nanoc::Errors::UnknownRouterError.new(@config[:router]) if @router_class.nil? + @router = @router_class.new(self) + + # Initialize data + @page_defaults = PageDefaults.new({}) + @page_defaults.site = self + @asset_defaults = AssetDefaults.new({}) + @asset_defaults.site = self + @pages = [] + @assets = [] + @layouts = [] + @templates = [] end + # Loads the site data. This will query the Nanoc::DataSource associated + # with the site and fetch all site data. The site data is cached, so + # calling this method will not have any effect the second time, unless + # +force+ is true. + # + # +force+:: If true, will force load the site data even if it has been + # loaded before, to circumvent caching issues. def load_data(force=false) + # Don't load data twice + @data_loaded ||= false return if @data_loaded and !force - # Load data + # Load all data @data_source.loading do - @code = @data_source.code - @pages = @data_source.pages.map { |p| Page.new(p, self) } - @page_defaults = @data_source.page_defaults - @layouts = @data_source.layouts - @templates = @data_source.templates + load_code(force) + load_page_defaults + load_pages + load_asset_defaults + load_assets + load_layouts + load_templates end - # Setup child-parent links - @pages.each do |page| - # Get parent - parent_path = page.path.sub(/[^\/]+\/$/, '') - parent = @pages.find { |p| p.path == parent_path } - next if parent.nil? or page.path == '/' + # Set loaded + @data_loaded = true + end - # Link - page.parent = parent - parent.children << page + private + + # Loads this site's code and executes it. + def load_code(force=false) + # Don't load code twice + @code_loaded ||= false + return if @code_loaded and !force + + # Get code + @code = @data_source.code + + # Fix code if outdated + if @code.is_a? String + warn_data_source('Code', 'code', false) + @code = Code.new(code) end + # Set site + @code.site = self + + # Execute code + # FIXME This could be dangerous when using nanoc as a framework + # (a separate ruby process should probably be forked, and the code + # should only be loaded in this forked process) + @code.load + # Set loaded - @data_loaded = true + @code_loaded = true end - # Creating a new site on disk + # Loads this site's page defaults. + def load_page_defaults + # Get page defaults + @page_defaults = @data_source.page_defaults - def self.create(sitename) - # Check whether site exists - error "A site named '#{sitename}' already exists." if File.exist?(sitename) + # Fix page defaults if outdated + if @page_defaults.is_a? Hash + warn_data_source('PageDefaults', 'page_defaults', false) + @page_defaults = PageDefaults.new(@page_defaults) + end - FileUtils.mkdir_p sitename - in_dir([sitename]) do - # Create output - FileManager.create_dir 'output' + # Set site + @page_defaults.site = self + end - # Create config - FileManager.create_file 'config.yaml' do - "output_dir: \"output\"\n" + - "data_source: \"filesystem\"\n" - end + # Loads this site's pages, sets up page child-parent relationships and + # builds each page's list of page representations. + def load_pages + # Get pages + @pages = @data_source.pages - # Create rakefile - FileManager.create_file 'Rakefile' do - "Dir['tasks/**/*.rake'].sort.each { |rakefile| load rakefile }\n" + - "\n" + - "task :default do\n" + - " puts 'This is an example rake task.'\n" + - "end\n" - end + # Fix pages if outdated + if @pages.any? { |p| p.is_a? Hash } + warn_data_source('Page', 'pages', true) + @pages.map! { |p| Page.new(p[:uncompiled_content], p, p[:path]) } + end - # Create tasks - FileManager.create_file 'tasks/default.rake' do - "task :example do\n" + - " puts 'This is an example rake task in tasks/default.rake.'\n" + - "end\n" - end + # Set site + @pages.each { |p| p.site = self } - # Setup site - Site.from_cwd.setup + # Setup child-parent links + @pages.each do |page| + # Get parent + parent_path = page.path.sub(/[^\/]+\/$/, '') + parent = @pages.find { |p| p.path == parent_path } + next if parent.nil? or page.path == '/' + + # Link + page.parent = parent + parent.children << page end + + # Build page representations + @pages.each { |p| p.build_reps } end - # Compiling + # Loads this site's asset defaults. + def load_asset_defaults + # Get page defaults + @asset_defaults = @data_source.asset_defaults - def compile - @compiler.run + # Set site + @asset_defaults.site = self + rescue NotImplementedError + @asset_defaults = AssetDefaults.new({}) + @asset_defaults.site = self end - def autocompile(port) - @autocompiler.start(port) - end + # Loads this site's assets and builds each asset's list of asset + # representations. + def load_assets + # Get assets + @assets = @data_source.assets - # Creating + # Set site + @assets.each { |a| a.site = self } - def setup - @data_source.loading { @data_source.setup } + # Build asset representations + @assets.each { |p| p.build_reps } + rescue NotImplementedError + @assets = [] end - def create_page(name, template_name='default') - load_data + # Loads this site's layouts. + def load_layouts + # Get layouts + @layouts = @data_source.layouts - template = @templates.find { |t| t[:name] == template_name } - error "A template named '#{template_name}' was not found; aborting." if template.nil? + # Fix layouts if outdated + if @layouts.any? { |l| l.is_a? Hash } + warn_data_source('Layout', 'layouts', true) + @layouts.map! { |l| Layout.new(l[:content], l, l[:path] || l[:name]) } + end - @data_source.loading { @data_source.create_page(name, template) } + # Set site + @layouts.each { |l| l.site = self } end - def create_template(name) - load_data + # Loads this site's templates. + def load_templates + # Get templates + @templates = @data_source.templates - @data_source.loading {@data_source.create_template(name) } + # Fix templates if outdated + if @templates.any? { |t| t.is_a? Hash } + warn_data_source('Template', 'templates', true) + @templates.map! { |t| Template.new(t[:content], t[:meta].is_a?(String) ? YAML.load(t[:meta]) : t[:meta], t[:name]) } + end + + # Set site + @templates.each { |t| t.site = self } end - def create_layout(name) - load_data - - @data_source.loading { @data_source.create_layout(name) } + # Raises a warning about an outdated data source method. + def warn_data_source(class_name, method_name, is_array) + warn( + "In nanoc 2.1, DataSource##{method_name} should return #{is_array ? 'an array of' : 'a' } Nanoc::#{class_name} object#{is_array ? 's' : ''}. Future versions will not support these outdated data sources.", + 'DEPRECATION WARNING' + ) end end + end