From 572c532fb0465c5a588be8ee68c9ba5c265a18b7 Mon Sep 17 00:00:00 2001 From: Joshua Beam Date: Thu, 16 Apr 2015 19:05:31 -0500 Subject: [PATCH 1/2] updated api documentation in comments for use with rdoc, and added acceptinput and templatedir as virtual classes in utils --- lib/ngi.rb | 2 +- lib/ngi/configure.rb | 93 ++++++++++++++++++++------------- lib/ngi/generator.rb | 42 ++++++++++----- lib/ngi/parser.rb | 16 +++++- lib/ngi/utils/command_parser.rb | 11 ++-- lib/ngi/utils/current_dir.rb | 8 ++- lib/ngi/utils/jser.rb | 19 ++++--- lib/ngi/utils/user_input.rb | 6 ++- lib/ngi/utils/utils.rb | 4 ++ lib/ngi/version.rb | 6 ++- test/test_ngi.rb | 4 +- 11 files changed, 140 insertions(+), 71 deletions(-) diff --git a/lib/ngi.rb b/lib/ngi.rb index 1b422f1..d82bf5c 100644 --- a/lib/ngi.rb +++ b/lib/ngi.rb @@ -5,7 +5,7 @@ # This module wraps the basic classes # of the tool ngi (in other words, this -# module simply provides namespace) +# module simply provides namespace). module Ngi require_relative 'ngi/delegate' require_relative 'ngi/parser' diff --git a/lib/ngi/configure.rb b/lib/ngi/configure.rb index 14c493f..9dc39e8 100644 --- a/lib/ngi/configure.rb +++ b/lib/ngi/configure.rb @@ -9,17 +9,30 @@ require 'fileutils' require 'yaml' -# Run the user through an interactive -# session of configuring ngi +# This class handles configuring Ngi. +# It is divided into basically two parts. +# The first part collects user input in the +# command line in sort of an interactive +# Q&A session, and the second part is involved +# in manipulating the configuration files, +# which are stored in lib/config. THe configuration +# file that stores the user's config +# information is actually lib/config/config.yml. +# All the other files in lib/config are supporting +# files that hold default, unchangeable information. class Configure attr_accessor :file, :location 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 + # This class is used in place of just + # using $stdin.gets (or similar methods) + # to collect input. This is so that + # this class can be redefined in tests, + # basically making #gets testable because + # it can be replaced with whatever you want. + class AcceptInput < Utils::AcceptInput def self.str(type) case type when :stripped @@ -28,11 +41,14 @@ def self.str(type) 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 + # for user input in the format of + # "Choose from: ". The result of the + # user's input is returned so that it can be + # used later in the code. class AskLoop < Utils::AskLoop + # Starts the loop and waits for user + # input. def self.ask(args) puts "\nChoose from: #{args[:valid]}" answer = AcceptInput.str(:stripped) @@ -46,10 +62,10 @@ def self.ask(args) end end - # An extraction of the template file/directory for - # the generator... This way it can be separated, redefined, + # An extraction of the template file/directory. + # This way it can be separated, redefined, # and tested. - class TemplateDir + class TemplateDir < Utils::TemplateDir attr_reader :d def initialize(component, language, template) @@ -68,8 +84,12 @@ def read end # Holds all the configure functions for the properties - # that are configurable + # that are configurable. lib/config/config.configurable.yml + # holds a list of the properties that the user is allowed + # to configure. class Configurable + # Allows the user to define the language + # of the languages that they are allowed to configure. def self.language(config) v = JSer.new(config.lang_types).to_str type = AskLoop.ask(check: config.lang_types, valid: v) @@ -85,6 +105,10 @@ def self.language(config) answer end + # This method creates the actual template file + # by copying the user's custom template file + # into ngi's templates directory. + # This method is used inside of #self.templates def self.create_template_file(args) component = args[:component] language = args[:language] @@ -115,6 +139,8 @@ def self.create_template_file(args) end end + # Allows the user to configure the templates for the various + # components, like directives, controllers, etc. def self.templates(config) v_c = JSer.new(config.components).to_str component = AskLoop.ask(check: config.components, valid: v_c) @@ -210,10 +236,10 @@ def self.templates(config) 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 + # Make Questioner accesible as in: Configure::Questioner.run(). + # Questioner simply starts an interactive prompt to guide + # the user in configuring ngi, + # which is a file that holds all the global configurable # options (like language to use, templates, etc.) class Questioner attr_accessor :file @@ -265,6 +291,11 @@ def configure_property(property) end end + # Creates a new instance of Questioner and + # in a "functional programming" sort of way, + # runs through the various methods of choosing + # a configurable property based on the user's + # input and configuring that property. def self.run(file) questioner = Questioner.new(file) do |q| # First, the user chooses a property @@ -278,8 +309,8 @@ def self.run(file) # 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 + # Hash object that came from + # config/config.yml # and is inside of this instance of Questioner q.config[property] = result @@ -293,16 +324,13 @@ def self.run(file) 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.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 + # The only thing we do here is load the config file + # as just a string. + # It will be converted to a Ruby hash in #to_ruby def initialize(location = nil) unless location.nil? @location = location @@ -314,12 +342,7 @@ def initialize(location = nil) yield(self) if block_given? end - # Convert the file to a Ruby hash - # def from_json - # JSON.parse(@file) - # end - - # Convert the file to a Ruby hash + # Convert the file to a Ruby hash. # Usage: Configure.new('file/path').to_ruby(from: 'yaml') def to_ruby(args) case args[:from] @@ -342,8 +365,8 @@ def from_ruby(args) end end - # Here we actually write the new JSON config file - # to config/angular_init.config.json + # Here we actually write the new config file + # to config/config.yml def write(args) File.open(args[:destination], 'w') do |f| f.write(args[:file]) @@ -352,12 +375,12 @@ def write(args) end # All this method does is handle retrieving - # the file from config/angular_init.config.json + # the file from config/config.yml # so that it can pass it off to Questioner, # 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 + # to actually write the file + # to config/config.yml def self.run(args) Configure.new do |c| c.file = Configure::Questioner.run(args) diff --git a/lib/ngi/generator.rb b/lib/ngi/generator.rb index 7f9be4e..e41fbb8 100644 --- a/lib/ngi/generator.rb +++ b/lib/ngi/generator.rb @@ -7,9 +7,13 @@ # This class generates templates (hence the name "Generator") class Generator - # STDIN is separated into a class so that - # it can be extracted and tested - class AcceptInput + # This class is used in place of just + # using $stdin.gets (or similar methods) + # to collect input. This is so that + # this class can be redefined in tests, + # basically making #gets testable because + # it can be replaced with whatever you want. + class AcceptInput < Utils::AcceptInput def self.str(type) case type when :condensed @@ -22,8 +26,10 @@ def self.str(type) end end - # Here we just implement - # the virtual class Utils::AskLoop + # This implementation just creates a loop that asks + # for user input. The result of the + # user's input is returned so that it can be + # used later in the code. class AskLoop < Utils::AskLoop def self.ask(args) print "\n#{args[:prompt]}" @@ -50,9 +56,9 @@ def self.ask(args) # Generator.new (the initialization function) is called in self.run # An extraction of the template file/directory for - # the generator... This way it can be separated, redefined, + # the generator. This way it can be separated, redefined, # and tested. - class TemplateDir + class TemplateDir < Utils::TemplateDir attr_reader :dir def initialize(component) @@ -83,24 +89,33 @@ def initialize(args) ############################### + # Asks for the new file name + # of the component being generated. def new_file_name print '[?] New file name: ' @new_file = AcceptInput.str(:condensed) end + # Asks for the module name + # of the component being generated. def module_name print '[?] Module name: ' @module_name = AcceptInput.str(:condensed) end + # Asks for the name of the component + # being generated (e.g. "MyController") def name print "[?] #{@type.capitalize} name: " @name = AcceptInput.str(:condensed) end + # Asks for what additional services, etc., + # the user would like to inject into + # the component. def inject special = %w(routes controller).include?(@type) auto_injections = [ @@ -123,6 +138,8 @@ def inject @dependencies << injection unless injection.nil? end + # This method replaces all the tags inside the template. + # A tag is something like {{name}}. def replace # inject may or may not have run... # if it wasn't run, then @dependencies was never set @@ -155,17 +172,18 @@ def replace .gsub(cdv_regex, cdv_string) end + # If Liquid-style tags are used in a template that can be used + # for multiple components, remove those parts that don't + # belong to the type of component user wants to generate def tag - # If Liquid-style tags are used in a template that can be used - # for multiple components, remove those parts that don't - # belong to the type of component user wants to generate @template_file = @template_file .gsub(/\{\%\sif\s#{@type}\s\%\}(.*)\{\%\sendif\s#{@type}\s\%\}/m, '\1') .gsub(/\s\{\%\sif\s.*\s\%\}.*\{\%\sendif\s.*\s\%\}/m, '') end + # Here we create the new generated file based on the + # template for the component that the user chose. def write - # create the new file def overwrite? AskLoop.ask( check: 'y', prompt: 'File exists already, overwrite it? (y/n) ' @@ -185,7 +203,7 @@ def overwrite? # the executable file. # This function simply goes through all of the # methods in order to interactively - # prompt the user to generate a new template + # prompt the user to generate a new template. def self.run(args) Generator.new(args) do |g| g.new_file_name diff --git a/lib/ngi/parser.rb b/lib/ngi/parser.rb index 508c882..829afde 100644 --- a/lib/ngi/parser.rb +++ b/lib/ngi/parser.rb @@ -4,7 +4,11 @@ require_relative 'version' # This class is just a wrapper for the main process -# of ngi +# of ngi. It looks at the user input and passes +# arguments to either Configure or Generator. +# It also handles retrieving the configuration files +# from lib/config/config.yml so that they only have +# to be read in *one place*. class Parser CURRENT_DIR = File.dirname(__FILE__) @@ -24,7 +28,13 @@ class Parser LANGUAGES_FILE = "#{CURRENT_DIR}/../config/config.languages.yml" LANGUAGES_HASH = Delegate::Configure.new(LANGUAGES_FILE).to_ruby(from: 'yaml') - # Abstraction to choose the component (custom or default) + # Abstraction to choose the component (custom or default). + # If a custom component exists in config/config.yml, then + # this class will select the attributes from that and + # build a component to pass into Generator. If no custom + # component exists, then the default attributes from + # config/config.components.yml are used to build a component + # to pass into Generator. class ComponentChooser attr_reader :component @@ -55,6 +65,8 @@ def initialize(args) end end + # Here we implement CommandParser to use in the + # executable file. def self.parse(args) p = Utils::CommandParser.new do |parser| components = Utils::UserInput.new(valid_inputs: COMPONENTS) diff --git a/lib/ngi/utils/command_parser.rb b/lib/ngi/utils/command_parser.rb index 4a5e124..fa4648b 100644 --- a/lib/ngi/utils/command_parser.rb +++ b/lib/ngi/utils/command_parser.rb @@ -5,14 +5,14 @@ # Utilities module Utils - # Similar to Ruby's OptionParser - # However, this is customized for angular_init + # Similar to Ruby's OptionParser. + # However, this is customized for angular_init. class CommandParser attr_reader :args attr_accessor :banner, :version, :separator, :name - # Create an abstraction of puts - # so that we can test it + # Create an abstraction of $stdout.puts + # so that we can test it. class Output def initialize(str) @str = str @@ -46,7 +46,7 @@ def name=(name) @name = name end - # register the listeners + # Register the listeners. def on(options, description = '', &block) listener = {} @@ -91,6 +91,7 @@ def parse(args) end end + # Automatically register the "help" listener def register_help # automaticaly register this listener on(['-h', '--help'], 'Show the help menu') do diff --git a/lib/ngi/utils/current_dir.rb b/lib/ngi/utils/current_dir.rb index f863a9a..5a4717e 100644 --- a/lib/ngi/utils/current_dir.rb +++ b/lib/ngi/utils/current_dir.rb @@ -5,7 +5,13 @@ # Utilities module Utils - # Current directory + # Current directory. We use this + # in place of a constant. When testing, + # we can reset the CurrentDir to use + # the testing config files (which are + # separate from the normal config files) + # and suppress any CONSTANT REDEFINITION + # errors. class CurrentDir @@dir = '' diff --git a/lib/ngi/utils/jser.rb b/lib/ngi/utils/jser.rb index b4e4534..865b22b 100644 --- a/lib/ngi/utils/jser.rb +++ b/lib/ngi/utils/jser.rb @@ -12,18 +12,13 @@ module Utils # For example: # { "hello" => "world" } # becomes: - # { 'hello': 'world' } - # and ["some","array"] - # becomes: - # ['some','array'] + # { 'hello': 'world' }. module JSer def to_str to_s.gsub(/\"/, "'").gsub(/\=\>/, ': ') end - # A JSer class - # Usage: - # JSHash.new({"hello" => "world"}).to_str + # Usage: JSHash.new({"hello" => "world"}).to_str class JSHash < Hash def initialize(hash) super @@ -35,9 +30,7 @@ def initialize(hash) include JSer end - # Another JSer class - # Usage: - # JSArray.new(["some","array"]).to_str + # Usage: JSArray.new(["some","array"]).to_str class JSArray < Array def initialize(array) super @@ -48,6 +41,12 @@ def initialize(array) module_function + # This makes the module JSer "look like" + # a class that can be "newed", even though + # JSer is a module. When we "new" JSer, + # it delegates flow of control + # to either JSArray or JSHash, depending + # on what type of object is passed in. def new(obj) if obj.class == Array return JSArray.new(obj) diff --git a/lib/ngi/utils/user_input.rb b/lib/ngi/utils/user_input.rb index 274e3a3..105bb7f 100644 --- a/lib/ngi/utils/user_input.rb +++ b/lib/ngi/utils/user_input.rb @@ -7,14 +7,18 @@ module Utils # This class is just a way to meaningfully # collect an array of valid user inputs - # for use with OptionParser in bin/ngi + # for use with CommandParser. class UserInput attr_reader :valid_inputs + # Here we collect all the valid inputs. def initialize(args) @valid_inputs = args[:valid_inputs] end + # Here we compare an input against + # the array of valid inputs that was + # set whenever we created an instance of this. def valid?(input) @valid_inputs.include?(input) end diff --git a/lib/ngi/utils/utils.rb b/lib/ngi/utils/utils.rb index 6ad5d03..a5ac899 100644 --- a/lib/ngi/utils/utils.rb +++ b/lib/ngi/utils/utils.rb @@ -11,5 +11,9 @@ module Utils require_relative 'user_input' require_relative 'current_dir' + # These are just "virtual" classes + # that are later implemented. class AskLoop; end + class AcceptInput; end + class TemplateDir; end end diff --git a/lib/ngi/version.rb b/lib/ngi/version.rb index 8150036..05ba97e 100644 --- a/lib/ngi/version.rb +++ b/lib/ngi/version.rb @@ -3,9 +3,11 @@ # github.com/joshbeam/angular_init # MIT License -# The current version of Ngi. +# This module wraps the basic classes +# of the tool ngi (in other words, this +# module simply provides namespace). # This uses semantic versioning. -# See http://semver.org/ +# See http://semver.org module Ngi VERSION = '0.3.0' end diff --git a/test/test_ngi.rb b/test/test_ngi.rb index ce21894..6297720 100644 --- a/test/test_ngi.rb +++ b/test/test_ngi.rb @@ -70,7 +70,7 @@ class Generator Utils::CurrentDir.dir = @dir # Redefine the $stdin in Generator - class AcceptInput + class AcceptInput < Utils::AcceptInput def self.str(type) case type when :condensed @@ -89,7 +89,7 @@ class Configure Utils::CurrentDir.dir = @dir # Redefine the $stdin in Generator - class AcceptInput + class AcceptInput < Utils::AcceptInput def self.str(type) case type when :stripped From e48a63d6b746dc4b506eafdfdc04aeeccdadce75 Mon Sep 17 00:00:00 2001 From: Joshua Beam Date: Thu, 16 Apr 2015 19:06:29 -0500 Subject: [PATCH 2/2] updated to version 0.3.1 due to small refactoring --- README.md | 2 +- lib/ngi/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4672389..8469f7e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# ngi 0.3.0 +# ngi 0.3.1 Check out the [website][website]. diff --git a/lib/ngi/version.rb b/lib/ngi/version.rb index 05ba97e..ef3d84e 100644 --- a/lib/ngi/version.rb +++ b/lib/ngi/version.rb @@ -9,5 +9,5 @@ # This uses semantic versioning. # See http://semver.org module Ngi - VERSION = '0.3.0' + VERSION = '0.3.1' end