sitepress/sitepress

View on GitHub
sitepress-core/lib/sitepress/path.rb

Summary

Maintainability
A
25 mins
Test Coverage
require "pathname"
require "mime-types"

module Sitepress
  class Path
    # Default handler extensions. Handlers are anything that render or
    # manipulate the contents of the file into a different output, like
    # ERB or HAML.
    HANDLER_EXTENSIONS = %i[haml erb md markdown]

    # The root node name is a blank string.
    ROOT_NODE_NAME = "".freeze

    # The name of the root path
    ROOT_PATH = "/".freeze

    attr_reader :handler, :format, :path, :node_name, :dirname, :basename

    # When Rails boots, it sets the handler extensions so that paths
    # can be properly parsed.
    class << self
      attr_writer :handler_extensions

      def handler_extensions
        @handler_extensions ||= HANDLER_EXTENSIONS
      end
    end

    def initialize(path, path_seperator: File::SEPARATOR, handler_extensions: self.class.handler_extensions)
      @path = path.to_s
      @path_seperator = Regexp.new(path_seperator)
      @handler_extensions = handler_extensions
      parse
    end

    def node_names
      @node_names ||= node_name_ancestors.push(node_name)
    end

    # Necessary for operations like `File.read path` where `path` is an instance
    # of this object.
    def to_str
      @path
    end
    alias :to_s :to_str

    def ==(path)
      to_s == path.to_s
    end

    def exists?
      File.exists? path
    end

    def expand_path
      File.expand_path path
    end

    def format
      (handler_is_format? ? handler : @format)&.to_sym
    end

    private
      # TODO: I don't want to look this up everytime I try to figure out the
      # extension. I'll have to create an extension registry .

      # Rails has handlers, like `:html` and `:raw` that are both
      # handlers and formats. If we don't account for this, then the object
      # would return a `nil` for a file named `blah.html`.
      def handler_is_format?
        return false if @handler.nil?
        @format.nil? and MIME::Types.type_for(@handler.to_s).any?
      end

      def parse
        @dirname, @basename = File.split(path)
        parse_basename
      end

      # Given a filename, this will work out the extensions, formats, and node_name.
      def parse_basename
        base = basename
        filename, extname = split_filename(base)

        # This is a root path, so we have to treat it a little differently
        # so that the node mapper and node names work properly.
        if filename == ROOT_PATH and extname.nil?
          @node_name = ROOT_NODE_NAME
        elsif extname
          extname = extname.to_sym

          # We have an extension! Let's figure out if its a handler or not.
          if @handler_extensions.include? extname
            # Yup, its a handler. Set those variables accordingly.
            @handler = extname
            base = filename
          end

          # Now let's get the format (e.g. :html, :xml, :json) for the path and
          # the key, which is just the basename without the format extension.
          @node_name, format = split_filename(base)
          @format = format
        else
          @node_name = filename
        end
      end

      # If given a path `/a/b/c`, thsi would return `["a", "b", "c"].
      def node_name_ancestors
        strip_leading_prefix(File.dirname(path)).split(@path_seperator)
      end

      # Make it easier to split the last extension off a filename.
      # For example, if you run `split_filename("c.taco.html")`
      # it would return `["c.taco", "html"]`. If you ran it against
      # something like `split_filename("c")`, it would return `["c"]`
      def split_filename(string)
        base, _, extension = string.rpartition(".")
        base.empty? ? [extension] : [base, extension]
      end

      # Strips leading `/` or leading `.` if the path is relative.
      def strip_leading_prefix(dirname)
        dirname.to_s.gsub(/^#{@path_seperator}|\./, "")
      end
  end
end