From 9a7f7410197a83a6e79bb47b2adc8e8e64b9f356 Mon Sep 17 00:00:00 2001 From: Rob Yurkowski Date: Wed, 14 Jan 2015 14:56:46 -0500 Subject: [PATCH] Add ApplicationName class, fixing dasherized application names and prohibiting use of reserved words. (Fixes #129) --- lib/lotus/application_name.rb | 99 +++++++++++++++++++ lib/lotus/commands/new.rb | 13 +-- .../container/config/.env.development.tt | 2 +- .../application/container/config/.env.test.tt | 2 +- .../application/container/lib/app_name.rb.tt | 2 +- lib/lotus/generators/slice.rb | 6 +- test/application_name_test.rb | 45 +++++++++ test/commands/new_test.rb | 28 ++++++ 8 files changed, 186 insertions(+), 11 deletions(-) create mode 100644 lib/lotus/application_name.rb create mode 100644 test/application_name_test.rb diff --git a/lib/lotus/application_name.rb b/lib/lotus/application_name.rb new file mode 100644 index 000000000..dc26c4ddd --- /dev/null +++ b/lib/lotus/application_name.rb @@ -0,0 +1,99 @@ +module Lotus + # An application name. + # + # @since x.x.x + class ApplicationName + + # A list of words that are prohibited from forming the application name + # + # @since x.x.x + RESERVED_WORDS = %w(lotus).freeze + + + # Initialize and check against reserved words + # + # An application name needs to be translated in quite a few ways: + # First, it must be checked against a list of reserved words and rejected + # if it is invalid. Secondly, assuming it is not invalid, it must be able + # to be output roughly as given, but with the following changes: + # + # 1. downcased, + # 2. with surrounding spaces removed, + # 3. with internal whitespace rendered as underscores + # 4. with underscores de-duplicated + # + # which is the default output. It must also be transformable into an + # environment variable. + # + # @return [Lotus::ApplicationName] a new instance of the application name + # + # @since x.x.x + def initialize(name) + @name = sanitize(name) + ensure_validity! + end + + + # Returns the cleaned application name. + # + # @example + # ApplicationName.new("my-App ").to_s # => "my-app" + # + # @since x.x.x + def to_s + @name + end + + + # Returns the application name uppercased with non-alphanumeric characters + # as underscores. + # + # @example + # ApplicationName.new("my-app").to_env_s => "MY_APP" + # + # @since x.x.x + def to_env_s + @name.upcase.gsub(/\W/, '_') + end + + + # Returns true if a potential application name matches one of the reserved + # words. + # + # @example + # Lotus::ApplicationName.invalid?("lotus") # => true + # + # @since x.x.x + def self.invalid?(name) + RESERVED_WORDS.include?(name) + end + + + private + + # Raises RuntimeError with explanation if the provided name is invalid. + # + # @api private + # @since x.x.x + def ensure_validity! + if self.class.invalid?(@name) + raise RuntimeError, + "application name must not be any one of the following: " + + RESERVED_WORDS.join(", ") + end + end + + + # Cleans a string to be a functioning application name. + # + # @api private + # @since x.x.x + def sanitize(name) + name + .downcase + .strip + .gsub(/\s/, '_') + .gsub(/_{2,}/, '_') + end + end +end diff --git a/lib/lotus/commands/new.rb b/lib/lotus/commands/new.rb index 9642c4ca0..43c2fd5b7 100644 --- a/lib/lotus/commands/new.rb +++ b/lib/lotus/commands/new.rb @@ -1,4 +1,5 @@ require 'pathname' +require 'lotus/application_name' require 'lotus/utils/string' require 'lotus/utils/class' @@ -9,12 +10,12 @@ class New attr_reader :app_name, :source, :target, :cli, :options - def initialize(app_name, environment, cli) - @app_name = _get_real_app_name(app_name) + def initialize(app_name_or_path, environment, cli) + @app_name = ApplicationName.new(_get_real_app_name(app_name_or_path)) @options = environment.to_options @arch = @options.fetch(:architecture) - @target = Pathname.pwd.join(@options.fetch(:path, app_name)) + @target = Pathname.pwd.join(@options.fetch(:path, app_name_or_path)) @source = Pathname.new(@options.fetch(:source) { ::File.dirname(__FILE__) + '/../generators/application/' }).join(@arch) @cli = cli @@ -29,12 +30,12 @@ def start end private - def _get_real_app_name(app_name) - if app_name.include?(::File::SEPARATOR) + def _get_real_app_name(app_name_or_path) + if app_name_or_path.include?(::File::SEPARATOR) raise ArgumentError.new("Invalid application name. If you want to set application path, please use --path option") end - app_name == '.' ? ::File.basename(Dir.getwd) : app_name + app_name_or_path == '.' ? ::File.basename(Dir.getwd) : app_name_or_path end end end diff --git a/lib/lotus/generators/application/container/config/.env.development.tt b/lib/lotus/generators/application/container/config/.env.development.tt index bb9de7926..f802645a8 100644 --- a/lib/lotus/generators/application/container/config/.env.development.tt +++ b/lib/lotus/generators/application/container/config/.env.development.tt @@ -1,2 +1,2 @@ # Define ENV variables for development environment -<%= config[:app_name].upcase %>_DATABASE_URL="file:///db/<%= config[:app_name] %>_development" +<%= config[:app_name].to_env_s %>_DATABASE_URL="file:///db/<%= config[:app_name] %>_development" diff --git a/lib/lotus/generators/application/container/config/.env.test.tt b/lib/lotus/generators/application/container/config/.env.test.tt index d4915c3ac..5d84ddd7a 100644 --- a/lib/lotus/generators/application/container/config/.env.test.tt +++ b/lib/lotus/generators/application/container/config/.env.test.tt @@ -1,2 +1,2 @@ # Define ENV variables for test environment -<%= config[:app_name].upcase %>_DATABASE_URL="file:///db/<%= config[:app_name] %>_test" +<%= config[:app_name].to_env_s %>_DATABASE_URL="file:///db/<%= config[:app_name] %>_test" diff --git a/lib/lotus/generators/application/container/lib/app_name.rb.tt b/lib/lotus/generators/application/container/lib/app_name.rb.tt index d76ce9086..6d7a9281b 100644 --- a/lib/lotus/generators/application/container/lib/app_name.rb.tt +++ b/lib/lotus/generators/application/container/lib/app_name.rb.tt @@ -14,7 +14,7 @@ Lotus::Model.configure do # adapter type: :sql, uri: 'postgres://localhost/<%= config[:app_name] %>_development' # adapter type: :sql, uri: 'mysql://localhost/<%= config[:app_name] %>_development' # - adapter type: :file_system, uri: ENV['<%= config[:app_name].upcase %>_DATABASE_URL'] + adapter type: :file_system, uri: ENV['<%= config[:app_name].to_env_s %>_DATABASE_URL'] ## # Database mapping diff --git a/lib/lotus/generators/slice.rb b/lib/lotus/generators/slice.rb index 562178900..294656fd3 100644 --- a/lib/lotus/generators/slice.rb +++ b/lib/lotus/generators/slice.rb @@ -1,6 +1,7 @@ require 'securerandom' require 'lotus/generators/abstract' require 'lotus/utils/string' +require 'lotus/application_name' module Lotus module Generators @@ -8,8 +9,9 @@ class Slice < Abstract def initialize(command) super - @slice_name = options.fetch(:application) - @upcase_slice_name = @slice_name.upcase + application_name = ApplicationName.new(options.fetch(:application)) + @slice_name = application_name.to_s + @upcase_slice_name = application_name.to_env_s @classified_slice_name = Utils::String.new(@slice_name).classify @source = Pathname.new(::File.dirname(__FILE__) + '/../generators/slice') diff --git a/test/application_name_test.rb b/test/application_name_test.rb new file mode 100644 index 000000000..6921a7d3d --- /dev/null +++ b/test/application_name_test.rb @@ -0,0 +1,45 @@ +require 'test_helper' +require 'lotus/application_name' + +describe Lotus::ApplicationName do + + describe "formats" do + + describe "#to_s" do + it "renders downcased" do + application_name = Lotus::ApplicationName.new("MY-APP") + application_name.to_s.must_equal "my-app" + end + + it "renders trimmed" do + application_name = Lotus::ApplicationName.new(" my-app ") + application_name.to_s.must_equal "my-app" + end + + it "renders internal spaces as underscores" do + application_name = Lotus::ApplicationName.new("my app") + application_name.to_s.must_equal "my_app" + end + + it "renders with underscores de-duplicated" do + application_name = Lotus::ApplicationName.new("my _app") + application_name.to_s.must_equal "my_app" + end + end + + + describe "to_env_s" do + it "renders uppercased with non-alphanumeric characters as underscores" do + application_name = Lotus::ApplicationName.new("my-app") + application_name.to_env_s.must_equal "MY_APP" + end + end + end + + + describe "reserved words" do + it "prohibits 'lotus'" do + -> { Lotus::ApplicationName.new("lotus") }.must_raise RuntimeError + end + end +end diff --git a/test/commands/new_test.rb b/test/commands/new_test.rb index 01a46f68d..2527919ee 100644 --- a/test/commands/new_test.rb +++ b/test/commands/new_test.rb @@ -149,6 +149,15 @@ def container_options content.must_match %(# Define ENV variables for development environment) content.must_match %(CHIRP_DATABASE_URL="file:///db/chirp_development") end + + describe "with non-simple application name" do + let(:app_name) { "chirp-two" } + + it "sanitizes application names for env variables" do + content = @root.join('config/.env.development').read + content.must_match %(CHIRP_TWO_DATABASE_URL="file:///db/chirp-two_development") + end + end end describe 'config/.env.test' do @@ -157,6 +166,15 @@ def container_options content.must_match %(# Define ENV variables for test environment) content.must_match %(CHIRP_DATABASE_URL="file:///db/chirp_test") end + + describe "with non-simple application name" do + let(:app_name) { "chirp-two" } + + it "sanitizes application names for env variables" do + content = @root.join('config/.env.test').read + content.must_match %(CHIRP_TWO_DATABASE_URL="file:///db/chirp-two_test") + end + end end describe 'lib/chirp.rb' do @@ -169,6 +187,16 @@ def container_options content.must_match %(mapping do) content.must_match %(mapping "\#{__dir__}/config/mapping") end + + describe "with non-simple application name" do + let(:app_name) { "chirp-two" } + + it "sanitizes application names for env variables" do + content = @root.join('lib/chirp-two.rb').read + content.must_match 'Dir["#{ __dir__ }/chirp-two/**/*.rb"].each { |file| require_relative file }' + content.must_match %(adapter type: :file_system, uri: ENV['CHIRP_TWO_DATABASE_URL']) + end + end end describe 'lib/config/mapping.rb' do