require_relative '../tailor' require_relative 'runtime_error' require_relative 'logger' class Tailor # Pulls in any configuration from the places configuration can be set: # 1. ~/.tailorrc # 2. CLI options # 3. Default options # # It then basically represents a list of "file sets" and the rulers that # should be applied against each file set. class Configuration include Tailor::Logger::Mixin DEFAULT_GLOB = 'lib/**/*.rb' DEFAULT_RC_FILE = Dir.home + '/.tailorrc' DEFAULT_PROJECT_CONFIG = Dir.pwd + '/.tailor' DEFAULT_STYLE = { allow_camel_case_methods: false, allow_hard_tabs: false, allow_screaming_snake_case_classes: false, allow_trailing_line_spaces: false, indentation_spaces: 2, max_code_lines_in_class: 300, max_code_lines_in_method: 30, max_line_length: 80, spaces_after_comma: 1, spaces_before_comma: 0, spaces_before_lbrace: 1, spaces_after_lbrace: 1, spaces_before_rbrace: 1, spaces_in_empty_braces: 0, spaces_after_lbracket: 0, spaces_before_rbracket: 0, spaces_after_lparen: 0, spaces_before_rparen: 0, trailing_newlines: 1 } # @return [Hash] def self.default new end attr_reader :file_sets attr_reader :formatters # @param [Array] runtime_file_list # @param [OpenStruct] options # @option options [String] config_file # @option options [Array] formatters # @option options [Hash] style def initialize(runtime_file_list=nil, options=nil) @formatters = ['text'] @file_sets = { default: { file_list: file_list(DEFAULT_GLOB), style: DEFAULT_STYLE } } @runtime_file_list = runtime_file_list log "Got runtime file list: #{@runtime_file_list}" @options = options log "Got options: #{@options}" end # Call this to load settings from the config file and from CLI options. def load! # Get config file settings @config_file = @options.config_file unless @options.config_file.empty? load_from_config_file(config_file) if config_file if @config_file if @rc_file_config # Get formatters from config file unless @rc_file_config.formatters.empty? @formatters = @rc_file_config.formatters log "@formatters is now #{@formatters}" end # Get file sets from config file unless @rc_file_config.file_sets.empty? @rc_file_config.file_sets.each do |label, file_set| unless @file_sets[label] @file_sets[label] = { style: DEFAULT_STYLE } end @file_sets[label][:file_list].concat file_set[:file_list] @file_sets[label][:file_list].uniq! @file_sets[label][:style].merge! file_set[:style] end end end end # Get formatters from CLI options unless @options.formatters.empty? || @options.formatters.nil? @formatters = @options.formatters log "@formatters is now #{@formatters}" end # Get file sets from CLI options unless @runtime_file_list.nil? || @runtime_file_list.empty? # Only use options set for the :default file set because the user gave # a different set of files to measure. @file_sets.delete_if { |k, v| k != :default } @file_sets[:default][:file_list] = file_list(@runtime_file_list) end # Get style overrides from CLI options @file_sets[:default][:style].merge!(@options.style) if @file_sets[:default][:file_list].empty? @file_sets[:default][:file_list] = file_list(DEFAULT_GLOB) end end # @return [String] Name of the config file to use. def config_file return @config_file if @config_file if File.exists?(DEFAULT_PROJECT_CONFIG) return @config_file = DEFAULT_PROJECT_CONFIG end if File.exists?(DEFAULT_RC_FILE) return @config_file = DEFAULT_RC_FILE end end # @return [Array] The list of formatters. def formatters(*new_formatters) @formatters = new_formatters unless new_formatters.empty? @formatters end # @param [String] file_glob The String that represents the file set. This # can be a file, directory, or a glob. # @param [Symbol] label The label that represents the file set. def file_set(file_glob=DEFAULT_GLOB, label=:default, &block) log "file set label #{label}" @temp_style = {} instance_eval(&block) if block_given? if @file_sets[label] @file_sets[label][:file_list].concat file_list(file_glob) log "file set: #{@file_sets}" @file_sets[label][:file_list].uniq! @file_sets[label][:style].merge @temp_style else @file_sets[label] = { file_list: file_list(file_glob), style: DEFAULT_STYLE.merge(@temp_style) } end @temp_style = {} end # Implemented for {file_set}, this converts the config file lines that look # like methods into a Hash. # # @return [Hash] The new style as defined by the config file. def method_missing(meth, *args, &blk) ok_methods = DEFAULT_STYLE.keys if ok_methods.include? meth @temp_style[meth] = args.first else super(meth, args, blk) end end # Tries to open the file at the path given at +config_file+ and read in # the configuration given there. # # @param [String] config_file Path to the config file to use. def load_from_config_file(config_file) user_config_file = File.expand_path(config_file) if File.exists? user_config_file log "Loading config from file: #{user_config_file}" begin config = instance_eval File.read(user_config_file) rescue LoadError => ex raise Tailor::RuntimeError, "Couldn't load config file: #{user_config_file}" end else log "No config file found at #{user_config_file}." end if config log "Got new config from file: #{config}" @rc_file_config = config end end # Gets a list of only files that are in +base_dir+. # # @param [String] base_dir The directory to get the file list for. # @return [Array] The List of files. def all_files_in_dir(base_dir) files = Dir.glob(File.join(base_dir, '**', '*')).find_all do |file| file if File.file?(file) end files end # The list of the files in the project to check. # # @param [String] glob Path to the file, directory or glob to check. # @return [Array] The list of files to check. def file_list(glob) files_in_project = if glob.is_a? Array log "Configured glob is an Array: #{glob}" glob.map do |e| if File.directory?(e) all_files_in_dir(e) else e end end.flatten.uniq elsif File.directory? glob log "Configured glob is an directory: #{glob}" all_files_in_dir(glob) else log "Configured glob is a glob/single-file: #{glob}" Dir.glob glob end list_with_absolute_paths = [] files_in_project.each do |file| list_with_absolute_paths << File.expand_path(file) end log "All files: #{list_with_absolute_paths}" list_with_absolute_paths.sort end def show table = Text::Table.new(horizontal_padding: 4) table.head = [{ value: 'Configuration', colspan: 2, align: :center }] table.rows << :separator table.rows << ['Style', @file_sets.inspect] table.rows << :separator table.rows << ['Formatters', @formatters] puts table end end end