From a6ca2086ce8ad5e0a47d205ba6eb7fc47f852510 Mon Sep 17 00:00:00 2001 From: Joel Low Date: Sat, 16 Jan 2016 14:37:11 +0800 Subject: [PATCH] Implement algorithmic generation of Docker image names given a programming language name. This is needed to map a language to a suitable Docker image to use to run programs in that language. --- .../concrete_language/class_methods.rb | 22 ++++++++++ lib/coursemology/polyglot/language.rb | 9 ++-- .../polyglot/concrete_language_spec.rb | 43 +++++++++++++++++++ spec/coursemology/polyglot/language_spec.rb | 19 ++++++++ 4 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 spec/coursemology/polyglot/concrete_language_spec.rb diff --git a/lib/coursemology/polyglot/concrete_language/class_methods.rb b/lib/coursemology/polyglot/concrete_language/class_methods.rb index 33898e3..9ad1702 100644 --- a/lib/coursemology/polyglot/concrete_language/class_methods.rb +++ b/lib/coursemology/polyglot/concrete_language/class_methods.rb @@ -4,4 +4,26 @@ # This is deliberately defined so that consumers of this library can inject methods into all # concrete languages' classes. module Coursemology::Polyglot::ConcreteLanguage::ClassMethods + # The default algorithm for translating a language to a corresponding Docker image name. + # + # This will: + # 1. Strip the +Coursemology::Polyglot::Language+ prefix. + # 2. Underscore the name. + # 3. Replace *n*Point*n* strings with dots (e.g. Python2Point7 to Python2.7). + # 4. Replace slashes with dashes. + def docker_image + # Remove the Coursemology::Polyglot::Language prefix + class_name = name + class_name = class_name.sub(/^Coursemology::Polyglot::Language::/, '') + + # Use the underscored name + class_name = class_name.underscore + + # Replace the "_point" string with a decimal + class_name.gsub!(/(?<=[\d])_point(?=[\d])/, '.') + + # Replace slashes with dashes + class_name.tr!('/', '-') + class_name + end end diff --git a/lib/coursemology/polyglot/language.rb b/lib/coursemology/polyglot/language.rb index ded5ad0..458c581 100644 --- a/lib/coursemology/polyglot/language.rb +++ b/lib/coursemology/polyglot/language.rb @@ -32,14 +32,15 @@ class Coursemology::Polyglot::Language # Concrete languages can be instantiated and used. # # @param [String] display_name The display name for the language - def self.concrete_language(display_name) + # @param [String] docker_image The Docker image to use for the given language. This defaults to + # the string generated by +Coursemology::Polyglot::ConcreteLanguage.docker_image+. + def self.concrete_language(display_name, docker_image: nil) include Coursemology::Polyglot::ConcreteLanguage extend Coursemology::Polyglot::ConcreteLanguage::ClassMethods concrete_class_methods = Module.new do - define_method(:display_name) do - display_name - end + define_method(:display_name) { display_name } + define_method(:docker_image) { docker_image } if docker_image end extend concrete_class_methods diff --git a/spec/coursemology/polyglot/concrete_language_spec.rb b/spec/coursemology/polyglot/concrete_language_spec.rb new file mode 100644 index 0000000..84bf0ce --- /dev/null +++ b/spec/coursemology/polyglot/concrete_language_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +RSpec.describe Coursemology::Polyglot::ConcreteLanguage do + class self::DummyLanguage < Coursemology::Polyglot::Language + concrete_language 'Dummy Concrete Language' + end + + class self::DummyOverriddenNameLanguage < self::DummyLanguage + concrete_language 'Dummy Overridden Concrete Language' + + def self.name + 'Coursemology::Polyglot::Language::Dummy::OverriddenNameLanguage'.freeze + end + end + + describe '.docker_image' do + it 'removes the Coursemology::Polyglot::Language prefix' do + expect(self.class::DummyOverriddenNameLanguage.docker_image).to \ + start_with('dummy') + end + + it 'preserves the nesting of namespaces' do + expect(self.class::DummyOverriddenNameLanguage.docker_image).to \ + eq('dummy-overridden_name_language') + end + + context 'when the name contains Point surrounded by numerals' do + class self::DummyLanguage1Point0 < self::DummyLanguage; end + + it 'converts "Point" to dots' do + expect(self.class::DummyLanguage1Point0.docker_image).to end_with('dummy_language1.0') + end + end + + context 'when the name contains Point but not surrounded by numerals' do + class self::DummyLanguagePoint < self::DummyLanguage; end + + it 'preserves "Point"' do + expect(self.class::DummyLanguagePoint.docker_image).to end_with('dummy_language_point') + end + end + end +end diff --git a/spec/coursemology/polyglot/language_spec.rb b/spec/coursemology/polyglot/language_spec.rb index 7a93e50..100e7b3 100644 --- a/spec/coursemology/polyglot/language_spec.rb +++ b/spec/coursemology/polyglot/language_spec.rb @@ -9,6 +9,11 @@ class self::DummyLanguage < self::AbstractLanguage concrete_language DISPLAY_NAME end + class self::DummyLanguageWithDockerImage < self::DummyLanguage + DOCKER_IMAGE = 'dummy-docker' + concrete_language 'Dummy Docker Image Language', docker_image: DOCKER_IMAGE + end + describe '.concrete_language' do it 'sets the correct display name' do expect(self.class::DummyLanguage.display_name).to eq(self.class::DummyLanguage::DISPLAY_NAME) @@ -23,6 +28,20 @@ class self::DummyLanguage < self::AbstractLanguage expect(self.class::DummyLanguage).to \ be_a_kind_of(Coursemology::Polyglot::ConcreteLanguage::ClassMethods) end + + context 'when an explicit Docker image name is specified' do + it 'returns the explicit image name' do + expect(self.class::DummyLanguageWithDockerImage.docker_image).to \ + eq(self.class::DummyLanguageWithDockerImage::DOCKER_IMAGE) + end + end + + context 'when no explicit Docker image is specified' do + it 'generates an image name' do + expect(self.class::DummyLanguage.docker_image).to \ + eq('r_spec-example_groups-coursemology_polyglot_language-dummy_language') + end + end end describe '.concrete_languages' do