# encoding: utf-8 require 'fedux_org_stdlib/require_files' require 'fedux_org_stdlib/file_template/exceptions' require 'fedux_org_stdlib/core_ext/array' 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/.tt # 1. $HOME/.config//templates/.tt # 2. $HOME/./templates/.tt # 3. /etc//templates/.tt # # Please keep in mind # # * application_name: Module of your class, e.g. "MyApplication" becomes # "my_application" # * template_file: Singular name of your class and "Template" strip # off, e.g "ClientTemplate" becomes "client.tt" # # 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_file # * template_name # * application_name # # If you want the class to look for your template file at a different place # overwrite the following method # # * allowed_template_file_paths # # Below you find some examples for the usage of the class: # # @example Create template with one writer and reader # module MyApplication # class ClientTemplate < FileTemplate # end # end # # @example Template yaml file for the classes above: clients.yaml # <%= hello %> # --- # option1: 'data2' class FileTemplate attr_reader :working_directory, :logger, :file, :content # Create a new instance of template # # It tries to find a suitable template file. If it doesn't find one # the template is empty # # @param [String] file # Path where template file is stored. # # @raise [Exceptions::TemplateFileNotReadable] # If an avaiable template file 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( file: nil, logger: FeduxOrgStdlib::Logging::Logger.new, working_directory: Dir.getwd ) @logger = logger @working_directory = working_directory @file ||= available_template_file unless @file logger.debug "No template file found at #{allowed_template_file_paths.to_list}, therefor I'm going to use an empty template object instead." @content = '' return end begin @content = File.read(@file).chomp rescue StandardError => e fail Exceptions::TemplateFileNotReadable, JSON.dump(message: e.message, file: @file) end end # Return the path to the preferred template file # @return [String] # The path to the preferred template file def preferred_template_file allowed_template_file_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_file "#{template_name}#{template_file_suffix}" end # The suffix of the template file # # @return [String] # The suffix of the template file def template_file_suffix '*.tt' 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_name unless (name = class_name.sub(/Template/, '').underscore.singularize).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_file_paths paths = [] paths << resolve_path(working_directory, 'templates', template_file) paths << resolve_path('~', '.config', application_name, 'templates', template_file) paths << resolve_path('~', format('.%s', application_name), 'templates', template_file) paths << resolve_path('/etc', application_name, 'templates', template_file) paths << resolve_path(fallback_template_directory, template_file) 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_file allowed_template_file_paths.map { |f| Dir.glob(f).first }.compact.find { |f| ::File.exists?(f) } end def self.reserved_key_words (methods | instance_methods | private_methods | private_instance_methods ) - (Class.methods | Class.private_methods ) | [:to_s] end def basename File.basename(file, '.tt') end def extname File.extname(basename) end def extname?(*ext) ext.any? { |e| e == extname } end public def proposed_extname ext = File.extname(basename) return '.erb' if ext.blank? ext end end end