# encoding: utf-8 require 'fedux_org_stdlib/require_files' require 'fedux_org_stdlib/template_directory/exceptions' require 'fedux_org_stdlib/core_ext/array/list' require 'fedux_org_stdlib/logging/logger' require_library %w{ json active_support/core_ext/string/inflections } module FeduxOrgStdlib # This class makes a template file available as an object. You can use # whatever template language you prefer. It's up to you to compile the # template with a suitable template parser. # # By default it will look for a suitable template file in the given order: # # 1. /templates/ # 2. $HOME/.config//templates/ # 3. $HOME/./templates/ # 4. /etc//templates/ # # Please keep in mind # # * application_name: Module of your class, e.g. "MyApplication" becomes # "my_application" # * template_directory: Plural name of your class and "TemplateDirectory" strip # off, e.g "ClientTemplateDirectory" becomes "clients.d" # # Most conventions defined by me are implemented as separate methods. If one convention # is not suitable for your use case, just overwrite the method. # # If you prefer to use a different path to the template file or name of the # template file one of the following methods needs to be overwritten: # # * template_directory # * template_directory_basename # * application_name # # If you want the class to look for your template file at a different place # overwrite the following method # # * allowed_template_directory_paths # # Below you find some examples for the usage of the class: # # @example Create template with one writer and reader # module MyApplication # class ClientTemplateDirectory < FileTemplate # end # end class TemplateDirectory attr_reader :working_directory, :logger, :directory, :template_files # Create a new instance of template # # It tries to find a suitable template directory. If it doesn't find one # the template is empty # # @param [String] directory # Path where template directory is stored. # # @raise [Exceptions::TemplateFileNotReadable] # If an avaiable template directory could not be read by the template engine # # @return [AppTemplate] # The template instance. If the resulting data structure created by the # template_engine does not respond to `:[]` an empty template object will be # created. def initialize( directory: nil, logger: FeduxOrgStdlib::Logging::Logger.new, working_directory: Dir.getwd, output_directory: nil ) @logger = logger @working_directory = working_directory @directory ||= available_template_directory fail Exceptions::NoTemplateDirectoryFound, "No template directory found at #{allowed_template_directory_paths.to_list}, therefor I'm stop working as there are methods which depend on an available template directory path." unless @directory begin @template_files = Dir.glob(File.join(@directory, '**', '*')).keep_if { |o| FileTest.file? o } rescue StandardError => e fail Exceptions::TemplateDirectoryNotReadable, JSON.dump(message: e.message, file: @directory) end end # Return the path to the preferred template file # @return [String] # The path to the preferred template file def preferred_template_directory allowed_template_directory_paths[1] end private # The name of the template file # # @return [String] # The name of the template file. It defaults to `.yaml`. If # you want to use a different file name you need to overwrite this # method. def template_directory "#{template_directory_basename}#{template_directory_suffix}" end # The suffix of the template file # # @return [String] # The suffix of the template file def template_directory_suffix '.d' end # The base name of the template # # @return [String] # This one returns the base name of the template file (without the file # extension). It uses the class name of the template class # # @example Determine the base name of the template # # class ClientTemplate; end # # This will result in `client` as base name for the template file. def template_directory_basename unless (name = class_name.sub(/TemplateDirectory/, '').underscore.pluralize).blank? return name end fail Exceptions::ClassNameIsMissing, JSON.dump(klass: class_name) end # The name of your application # # @return [String] # This will strip of the class part of fully qualified class name and # converted it to a path. # # @example Determine application name # # class MyApplication::MyTemplate; end # # This will be converted to # # my_application def application_name module_name.underscore end # The paths where to look for the template file # # @return [Array] # A list of paths where the template object should look for its template # file. def allowed_template_directory_paths paths = [] paths << resolve_path(working_directory, 'templates', template_directory) paths << resolve_path('~', '.config', application_name, 'templates', template_directory) paths << resolve_path('~', format('.%s', application_name), 'templates', template_directory) paths << resolve_path('/etc', application_name, 'templates', template_directory) paths << resolve_path(fallback_template_directory, template_directory) if fallback_template_directory paths end def resolve_path(*path) ::File.expand_path(::File.join(*path)) end # Use this path as fall back path def fallback_template_directory; end def class_name self.class.name.to_s.demodulize end def module_name self.class.to_s.deconstantize end def available_template_directory allowed_template_directory_paths.each do |p| object = Dir.glob("#{p}/**/*").keep_if { |o| ::FileTest.file?(o) }.first next if object.blank? return p if object end end def self.reserved_key_words (methods | instance_methods | private_methods | private_instance_methods ) - (Class.methods | Class.private_methods ) | [:to_s] end end end