lib/ngi/configure.rb in ngi-0.2.3 vs lib/ngi/configure.rb in ngi-0.3.0

- old
+ new

@@ -1,118 +1,269 @@ # angular_init (short-name: ngi) # Copyright 2015 Joshua Beam # github.com/joshbeam/angular_init # MIT License -# CURRENT_DIR is defined in angualr_init.rb +# TODO: Method to clean out unused custom template files -require_relative '../dep/json' require_relative 'utils/utils' +require 'fileutils' +require 'yaml' # Run the user through an interactive # session of configuring ngi class Configure attr_accessor :file, :location - Utils = ::Utils - JSArray = Utils::JSArray - JSHash = Utils::JSHash + JSer = Utils::JSer + Utils::CurrentDir.dir = File.dirname(__FILE__) + # STDIN is separated into a class so that + # it can be extracted and tested + class AcceptInput + def self.str(type) + case type + when :stripped + $stdin.gets.strip + end + end + end + # Here, we implement the virtual class "Utils::AskLoop" # (see src/ruby/utils/utils.rb) # This implementation just creates a loop that asks # for user input class AskLoop < Utils::AskLoop def self.ask(args) puts "\nChoose from: #{args[:valid]}" - answer = $stdin.gets.strip + answer = AcceptInput.str(:stripped) loop do break if args[:check].include?(answer) puts "Choose from: #{args[:valid]}\n(or press ctrl+c to exit)" - answer = $stdin.gets.strip + answer = AcceptInput.str(:stripped) end answer end end + # An extraction of the template file/directory for + # the generator... This way it can be separated, redefined, + # and tested. + class TemplateDir + attr_reader :d + + def initialize(component, language, template) + @d = "#{Utils::CurrentDir.dir}/templates/" + @d << "#{component['type']}/#{language}" + @d << "/user/#{template}" + end + + def read + f = File.open(@d) + content = f.read + f.close + + content + end + end + # Holds all the configure functions for the properties # that are configurable class Configurable def self.language(config) - v = JSArray.new(config.lang_types).to_str + v = JSer.new(config.lang_types).to_str type = AskLoop.ask(check: config.lang_types, valid: v) - - curr_lang = config.global['language'][type] + curr_lang = config.config['language'][type] lang_opts = config.languages[type].reject { |l| l if curr_lang == l } - v = JSArray.new(lang_opts).to_str + v = JSer.new(lang_opts).to_str language = AskLoop.ask(check: lang_opts, valid: v) - answer = config.global['language'] + answer = config.config['language'] answer[type] = language answer end + + def self.create_template_file(args) + component = args[:component] + language = args[:language] + template = args[:template] + + template_dir = TemplateDir.new(component, language, template).d + + destination = File.dirname(template_dir) + + # Create the directory "user" unless it already exists + FileUtils.mkdir_p(destination) unless File.directory?(destination) + + # The actual custom file + custom_file = template + + # Copy the custom file into the new or existing "user" directory + if File.exist? custom_file + # TODO: Add a 'safety net' by checking if the user + # has already added the custom template before + # so that they don't overwrite it + FileUtils.cp(custom_file, destination) + else + puts "Cannot find custom template file: '#{custom_file}' +Check to make sure the custom template file exists, +and that you're in the correct directory of the custom template." + + exit + end + end + + def self.templates(config) + v_c = JSer.new(config.components).to_str + component = AskLoop.ask(check: config.components, valid: v_c) + chosen_component = -> (c) { c['name'] == component } + + print '[?] Use the following template file: ' + file_name = AcceptInput.str(:stripped) + + print '[?] What language is this template for?' + type = config.components_hash.find(&chosen_component)['type'] + v_l = JSer.new(config.languages[type]).to_str + language = AskLoop.ask(check: config.languages[type], valid: v_l) + chosen_language = -> (t) { t['language'] == language } + + # Our answer will be an empty array first, + # because Tempaltes: might not exist the + # config.yml file + answer = [] + + # Set the answer to Templates: from the config.yml + # file if it exists + answer = config.config['templates'] if config.config.key? 'templates' + + # Then we want to see if the component already exists in + # the config file + exists = false + existing_component = answer.find(&chosen_component) + exists = true if !existing_component == false + + if file_name != 'default' + if exists == true + # Remove the current existing component (it's already saved above) + answer = answer + .reject(&chosen_component) + + # Remove the existing template object + existing_component['templates'] = existing_component['templates'] + .reject(&chosen_language) + + # Put in the updated template object + existing_component['templates'] << { + 'language' => language, + 'template' => file_name + } + + # Push the existing component back into the templates array + answer << existing_component + + elsif exists == false + + # If it isn't already there, + # go ahead and add it to the templates array + answer << { + 'name' => component, + 'type' => type, + 'templates' => [ + { + 'language' => language, + 'template' => file_name + } + ] + } + end + + create_template_file( + config: config.config, + component: answer.last, + language: language, + template: file_name + ) + else + # If default is chosen as the template, + # then delete the template from the templates + # array for that component + answer + .find(&chosen_component)['templates'] + .delete_if(&chosen_language) + + # If the templates array of the component + # is empty, then delete the entire component + # from the answer + answer + .delete_if(&chosen_component) if answer + .find(&chosen_component)['templates'] + .size == 0 + end + + if answer.size == 0 + nil + else + answer + end + end end # Make Questioner accesible as in: Configure::Questioner.run() # "Questioner" simply starts an interactive prompt to guide # the user in configuring src/config/agular_init.config.json, # which is a JSON file that holds all the global configurable # options (like language to use, templates, etc.) class Questioner attr_accessor :file - attr_reader :configurable_properties, - :languages, :global, - :configurable, :lang_types + attr_reader :configurable, + :languages, + :config, + :components, + :components_hash, + :lang_types - def initialize(file) - @file = file - - @global = @file['global'] - # TODO: extend array with this inject function? - - # The options for languages to use - @languages = @global['languages'] - + def initialize(args) + @languages = args[:languages] language_types = @languages.select do |type, languages| type if languages.size > 1 end - # For example, ['script','markup'] @lang_types = language_types.collect { |type, _| type }.flatten + @components_hash = args[:components_hash] + @components = args[:components] + @config = args[:config] + @configurable = args[:configurable] - # The properties in key => value format - # of the properties the user can configure - @configurable = @global['configurable'] - - # An array of the properties that the user is allowed to configure, - # according to src/config/angular_init.config.json - @configurable_properties = @configurable.collect { |_k, v| v } - yield(self) if block_given? end def choose_configurable_property - @configurable_properties.each_with_index do |p, i| - puts "#{i + 1}) #{p.capitalize}: #{JSHash.new(@global[p]).to_str}" + puts "Current settings\n================" + @configurable.each_with_index do |p, i| + json_string = 'Currently using default settings' + json_string = JSer.new(@config[p]).to_str if @config.key? p + puts "#{i + 1}) #{p.capitalize}: #{json_string}" end - valid = JSArray.new(@configurable_properties).to_str + valid = JSer.new(@configurable).to_str # return - AskLoop.ask(check: @configurable_properties, valid: valid) + AskLoop.ask(check: @configurable, valid: valid) end # This method delegates to the appropriate # Configurable#<method> based on the property # that the user has chosen to configure # Returns: a Hash of the object, based on the # from_json config object from config/angular_init.config.json def configure_property(property) case property - when @configurable['language'] + when 'language' return Configurable.language(self) + when 'templates' + return Configurable.templates(self) end end def self.run(file) questioner = Questioner.new(file) do |q| @@ -128,58 +279,76 @@ # The hash that was spit out as the # result is "merged" into the original # Hash from_json object that came from # config/angular_init.config.json # and is inside of this instance of Questioner - q.file['global'][property] = result + q.config[property] = result + # delete any properties that are nil + q.config.delete_if { |_, v| v.nil? } + # This just tells the user that we were # successful - result_string_hash = JSHash.new(result).to_str + result_string_hash = JSer.new(result).to_str rescue 'null' puts "#{property.capitalize} set to: #{result_string_hash}" end # Returns the file so that it can be used # (For example, Configure might write this # new hash as a JSON file to # config/angular_init.config.json) - questioner.file + questioner.config end end # The only thing we do here is load the JSON config file basically # as just a string in JSON format. # It will be converted to a Ruby hash in from_json below - def initialize(location) - @location = location - @file = IO.read(@location) + def initialize(location = nil) + unless location.nil? + @location = location + f = File.open(@location, 'r') + @file = f.read + f.close + end yield(self) if block_given? end # Convert the file to a Ruby hash - def from_json - JSON.parse(@file) + # def from_json + # JSON.parse(@file) + # end + + # Convert the file to a Ruby hash + # Usage: Configure.new('file/path').to_ruby(from: 'yaml') + def to_ruby(args) + case args[:from] + when 'json' + JSON.parse(@file) + when 'yaml' + YAML.load(@file) + end end - # Generate a "prettified" JSON version of our - # newly updated Ruby hash with the user's options - def to_json - if @file.is_a? Hash + # Convert the file from a Ruby hash + # into whatever language is specified + # Usage: <Configure instance>.from_ruby(to: 'yaml') + def from_ruby(args) + case args[:to] + when 'json' JSON.pretty_generate(@file) - else - @file + when 'yaml' + @file.to_yaml end end # Here we actually write the new JSON config file # to config/angular_init.config.json - def write - new_file = to_json - - File.open(@location, 'w') do |f| - f.write(new_file) + def write(args) + File.open(args[:destination], 'w') do |f| + f.write(args[:file]) f.close end end # All this method does is handle retrieving @@ -188,25 +357,17 @@ # which can in turn change the Hash and pass it # *back* to Configure, which can then choose # to actually write the file in JSON format # to config/angular_init.config.json def self.run(args) - Configure.new(args[:file_path]) do |c| - # Get the JSON file as a Ruby Hash - json_file = c.from_json + Configure.new do |c| + c.file = Configure::Questioner.run(args) - # Pass it off to Questioner so that - # we can interact with the user and - # have the user configure the Hash. - # Then, we set the Configure file - # to the output of however the user - # configured it with Questioner - c.file = Configure::Questioner.run(json_file) - - # We'll write the hash to - # config/angular_init.config.json. - # Configure#write converts the Hash - # to JSON and then uses File.write - c.write if args[:write] == true + if args[:write] == true + c.write( + destination: args[:destination], + file: c.from_ruby(to: args[:to]) + ) + end end end end