module Nanoc::Int # Contains methods that will be executed by the site’s `Rules` file. # # @api private class CompilerDSL # The current rules filename. # # @return [String] The current rules filename. # # @api private attr_accessor :rules_filename # Creates a new compiler DSL for the given collection of rules. # # @api private # # @param [Nanoc::Int::RulesCollection] rules_collection The collection of # rules to modify when loading this DSL # # @param [Hash] config The site configuration def initialize(rules_collection, config) @rules_collection = rules_collection @config = config end # Creates a preprocessor block that will be executed after all data is # loaded, but before the site is compiled. # # @yield The block that will be executed before site compilation starts # # @return [void] def preprocess(&block) if @rules_collection.preprocessors[rules_filename] warn 'WARNING: A preprocess block is already defined. Defining ' \ 'another preprocess block overrides the previously one.' end @rules_collection.preprocessors[rules_filename] = block end # Creates a compilation rule for all items whose identifier match the # given identifier, which may either be a string containing the * # wildcard, or a regular expression. # # This rule will be applicable to reps with a name equal to `:default`; # this can be changed by giving an explicit `:rep` parameter. # # An item rep will be compiled by calling the given block and passing the # rep as a block argument. # # @param [String] identifier A pattern matching identifiers of items that # should be compiled using this rule # # @option params [Symbol] :rep (:default) The name of the representation # that should be compiled using this rule # # @yield The block that will be executed when an item matching this # compilation rule needs to be compiled # # @return [void] # # @example Compiling the default rep of the `/foo/` item # # compile '/foo/' do # rep.filter :erb # end # # @example Compiling the `:raw` rep of the `/bar/` item # # compile '/bar/', :rep => :raw do # # do nothing # end def compile(identifier, params = {}, &block) # Require block raise ArgumentError.new('#compile requires a block') unless block_given? # Get rep name rep_name = params[:rep] || :default # Create rule rule = Nanoc::Int::Rule.new(create_pattern(identifier), rep_name, block) @rules_collection.add_item_compilation_rule(rule) end # Creates a routing rule for all items whose identifier match the # given identifier, which may either be a string containing the `*` # wildcard, or a regular expression. # # This rule will be applicable to reps with a name equal to `:default`; # this can be changed by giving an explicit `:rep` parameter. # # The path of an item rep will be determined by calling the given block # and passing the rep as a block argument. # # @param [String] identifier A pattern matching identifiers of items that # should be routed using this rule # # @option params [Symbol] :rep (:default) The name of the representation # that should be routed using this rule # # @yield The block that will be executed when an item matching this # compilation rule needs to be routed # # @return [void] # # @example Routing the default rep of the `/foo/` item # # route '/foo/' do # item.identifier + 'index.html' # end # # @example Routing the `:raw` rep of the `/bar/` item # # route '/bar/', :rep => :raw do # '/raw' + item.identifier + 'index.txt' # end def route(identifier, params = {}, &block) # Require block raise ArgumentError.new('#route requires a block') unless block_given? # Get rep name rep_name = params[:rep] || :default snapshot_name = params[:snapshot] || :last # Create rule rule = Nanoc::Int::Rule.new(create_pattern(identifier), rep_name, block, snapshot_name: snapshot_name) @rules_collection.add_item_routing_rule(rule) end # Creates a layout rule for all layouts whose identifier match the given # identifier, which may either be a string containing the * wildcard, or a # regular expression. The layouts matching the identifier will be filtered # using the filter specified in the second argument. The params hash # contains filter arguments that will be passed to the filter. # # @param [String] identifier A pattern matching identifiers of layouts # that should be filtered using this rule # # @param [Symbol] filter_name The name of the filter that should be run # when processing the layout # # @param [Hash] params Extra filter arguments that should be passed to the # filter when processing the layout (see {Nanoc::Filter#run}) # # @return [void] # # @example Specifying the filter to use for a layout # # layout '/default/', :erb # # @example Using custom filter arguments for a layout # # layout '/custom/', :haml, :format => :html5 def layout(identifier, filter_name, params = {}) pattern = Nanoc::Int::Pattern.from(create_pattern(identifier)) @rules_collection.layout_filter_mapping[pattern] = [filter_name, params] end # Creates a pair of compilation and routing rules that indicate that the # specified item(s) should be copied to the output folder as-is. The items # are selected using an identifier, which may either be a string # containing the `*` wildcard, or a regular expression. # # This meta-rule will be applicable to reps with a name equal to # `:default`; this can be changed by giving an explicit `:rep` parameter. # # @param [String] identifier A pattern matching identifiers of items that # should be processed using this meta-rule # # @option params [Symbol] :rep (:default) The name of the representation # that should be routed using this rule # # @return [void] # # @since 3.2.0 # # @example Copying the `/foo/` item as-is # # passthrough '/foo/' # # @example Copying the `:raw` rep of the `/bar/` item as-is # # passthrough '/bar/', :rep => :raw def passthrough(identifier, params = {}) # Require no block raise ArgumentError.new('#passthrough does not require a block') if block_given? # Get rep name rep_name = params[:rep] || :default # Create compilation rule compilation_block = proc {} compilation_rule = Nanoc::Int::Rule.new(create_pattern(identifier), rep_name, compilation_block) @rules_collection.add_item_compilation_rule(compilation_rule) # Create routing rule routing_block = proc do if item.identifier.full? item.identifier.to_s else # This is a temporary solution until an item can map back to its data # source. # ATM item[:content_filename] is nil for items coming from the static # data source. item[:extension].nil? || (item[:content_filename].nil? && item.identifier =~ %r{#{item[:extension]}/$}) ? item.identifier.chop : item.identifier.chop + '.' + item[:extension] end end routing_rule = Nanoc::Int::Rule.new(create_pattern(identifier), rep_name, routing_block, snapshot_name: :last) @rules_collection.add_item_routing_rule(routing_rule) end # Creates a pair of compilation and routing rules that indicate that the # specified item(s) should be ignored, e.g. compiled and routed with an # empty rule. The items are selected using an identifier, which may either # be a string containing the `*` wildcard, or a regular expression. # # This meta-rule will be applicable to reps with a name equal to # `:default`; this can be changed by giving an explicit `:rep` parameter. # # @param [String] identifier A pattern matching identifiers of items that # should be processed using this meta-rule # # @option params [Symbol] :rep (:default) The name of the representation # that should be routed using this rule # # @return [void] # # @example Suppressing compilation and output for all all `/foo/*` items. # # ignore '/foo/*' def ignore(identifier, params = {}) raise ArgumentError.new('#ignore does not require a block') if block_given? rep_name = params[:rep] || :default compilation_rule = Nanoc::Int::Rule.new(create_pattern(identifier), rep_name, proc {}) @rules_collection.add_item_compilation_rule(compilation_rule) routing_rule = Nanoc::Int::Rule.new(create_pattern(identifier), rep_name, proc {}, snapshot_name: :last) @rules_collection.add_item_routing_rule(routing_rule) end # Includes an additional rules file in the current rules collection. # # @param [String] name The name of the rules file — an ".rb" extension is # implied if not explicitly given # # @return [void] # # @example Including two additional rules files, 'rules/assets.rb' and # 'rules/content.rb' # # include_rules 'rules/assets' # include_rules 'rules/content' def include_rules(name) filename = ["#{name}", "#{name}.rb", "./#{name}", "./#{name}.rb"].find { |f| File.file?(f) } raise Nanoc::Int::Errors::NoRulesFileFound.new if filename.nil? @rules_collection.parse(filename) end # @api private def create_pattern(arg) case @config[:string_pattern_type] when 'glob' Nanoc::Int::Pattern.from(arg) when 'legacy' Nanoc::Int::Pattern.from(identifier_to_regex(arg)) else raise Nanoc::Int::Errors::GenericTrivial, "Invalid string_pattern_type: #{@config[:string_pattern_type]}" end end private # Converts the given identifier, which can contain the '*' or '+' # wildcard characters, matching zero or more resp. one or more # characters, to a regex. For example, 'foo/*/bar' is transformed # into /^foo\/(.*?)\/bar$/ and 'foo+' is transformed into /^foo(.+?)/. def identifier_to_regex(identifier) if identifier.is_a? String # Add leading/trailing slashes if necessary new_identifier = identifier.dup new_identifier[/^/] = '/' if identifier[0, 1] != '/' new_identifier[/$/] = '/' unless ['*', '/'].include?(identifier[-1, 1]) /^#{new_identifier.gsub('*', '(.*?)').gsub('+', '(.+?)')}$/ else identifier end end end end