# frozen_string_literal: true if defined?(ActiveRecord) # :nocov: # TODO: This is for compatibility with the Web application. A future refactoring might be able # to remove this dependency. class Coursemology::Polyglot::Language < ActiveRecord::Base; end # :nocov: else class Coursemology::Polyglot::Language; end end # An abstract language. This is a class which can represent a family of languages. Languages become # concrete and can be used when that language includes +Coursemology::Polyglot::ConcreteLanguage+. # # Languages define their own scripts and stylesheets needed to syntax highlight code. # # Each subclass represents a language ancestry, such as differing language versions (see the Python # language definition.) Derived languages can be defined at runtime to utilise the syntax # highlighting capabilities of the root language, while requiring a separate runtime environment to # run programs written in the derived language. # # Do *NOT* remove languages after they have been defined because the types specified in this # library can have references stored in code (e.g. in a database) class Coursemology::Polyglot::Language extend ActiveSupport::Autoload eager_autoload do autoload :Python autoload :JavaScript end # Marks the current class as a concrete language. # # Concrete languages can be instantiated and used. # # @param [String] display_name The display name for the language # @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) { display_name } define_method(:docker_image) { docker_image } if docker_image end extend concrete_class_methods end private_class_method :concrete_language # Determines the concrete language subclasses of this language. # # @return [Array] def self.concrete_languages descendants.select do |klass| klass.ancestors.include?(Coursemology::Polyglot::ConcreteLanguage) end end # The name of the lexer or mode to use with a given library. This is inherited by classes. # # @param [String|nil] default The default lexer/mode to use for each library. # @param [String] rouge The overridden lexer to use for Rouge. This is optional and will # default to +default+ # @param [String] ace The overridden mode to use for Ace. This is optional and will # default to +default+ # @raise [ArgumentError] When no default is specified and the Rouge lexer or Ace mode is not # specified. def self.syntax_highlighter(default = nil, rouge: default, ace: default) fail ArgumentError unless rouge && ace syntax_highlighter_class_methods = Module.new do define_method(:rouge_lexer) { rouge } define_method(:ace_mode) { ace } end extend syntax_highlighter_class_methods syntax_highlighter_instance_methods = Module.new do delegate :rouge_lexer, :ace_mode, to: :class end include syntax_highlighter_instance_methods end private_class_method :syntax_highlighter # Gets the display name of the language. # # @abstract # @return [String] def self.display_name fail NotImplementedError end # The Rouge lexer to use with this language. # # @abstract # @return [String] def self.rouge_lexer fail NotImplementedError end # The Ace mode to use with this language. # # @abstract # @return [String] def self.ace_mode fail NotImplementedError end end