Skip to content
4 changes: 2 additions & 2 deletions lib/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def initialize(track:, metadata:)
# Doesn't update the version information.
class GenerateTests < ImplementationDelegator
def call
create_tests_file
build_tests
end
end

Expand All @@ -24,7 +24,7 @@ class UpdateVersionAndGenerateTests < ImplementationDelegator
def call
update_tests_version
update_example_solution
create_tests_file
build_tests
end
end
end
16 changes: 13 additions & 3 deletions lib/generator/command_line.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def parse(args)
attr_reader :paths

def generators
implementations.map { |slug| generator(implementation(slug)) }
implementations.map { |slug| generator(implementation(exercise(slug))) }
end

def implementations
Expand All @@ -35,13 +35,23 @@ def freeze?
@options[:freeze] || @options[:all]
end

def implementation(slug)
def exercise(slug)
Exercise.new(slug: slug)
end

def implementation(exercise)
LoggingImplementation.new(
implementation: Implementation.new(paths: paths, slug: slug),
implementation: Implementation.new(exercise: exercise, repository: repository),
logger: logger
)
end

# do we need one per implementation, or could they all use the same one?
def repository
Repository.new(paths: paths)
end

# do we need one per implementation, or could they all use the same one?
def logger
logger = Logger.new($stdout)
logger.formatter = proc { |_severity, _datetime, _progname, msg| "#{msg}\n" }
Expand Down
19 changes: 19 additions & 0 deletions lib/generator/exercise.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module Generator
class Exercise
using Generator::Underscore

attr_reader :slug

def initialize(slug:)
@slug = slug
end

def name
@name ||= slug.underscore
end

def case_class
slug.camel_case + 'Case'
end
end
end
31 changes: 5 additions & 26 deletions lib/generator/files/generator_cases.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,15 @@ module Files
module GeneratorCases
class << self
def available(track_path)
cases_filepaths(track_path).map { |filepath| slugify(filepath) }.sort
end

def class_name(exercise_name_or_slug)
filename(exercise_name_or_slug).split('_').map(&:capitalize).join
end

def source_filepath(track_path, slug)
path = meta_generator_path(track_path, slug)
filename = filename(slug) + '.rb'
File.join(path, filename)
filepaths(track_path).map do |filepath|
%r{#{track_path}/exercises/([-a-z]+)/}.match(filepath)[1]
end.sort
end

private

def cases_filepaths(track_path)
generator_glob = File.join(meta_generator_path(track_path, '*'), '*_case.rb')
Dir.glob(generator_glob, File::FNM_DOTMATCH)
end

def slugify(filepath)
File.basename(filepath, '_case.rb').tr('_', '-')
end

def filename(exercise_name_or_slug)
"#{exercise_name_or_slug.tr('-', '_')}_case"
end

def meta_generator_path(track_path, slug)
File.join(track_path, 'exercises', slug, '.meta', 'generator')
def filepaths(track_path)
Dir.glob(File.join(track_path, 'exercises', '*', '.meta', 'generator'))
end
end
end
Expand Down
8 changes: 4 additions & 4 deletions lib/generator/files/metadata_files.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
module Generator
module Files
module MetadataFiles
def canonical_data
def canonical_data(exercise)
CanonicalDataFile.new(
filename: File.join(exercise_metadata_path, 'canonical-data.json'),
filename: File.join(exercise_metadata_path(exercise), 'canonical-data.json'),
repository_root: paths.metadata)
end

private

def exercise_metadata_path
File.join(paths.metadata, 'exercises', slug)
def exercise_metadata_path(exercise)
File.join(paths.metadata, 'exercises', exercise.slug)
end
end

Expand Down
59 changes: 37 additions & 22 deletions lib/generator/files/track_files.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,67 @@
module Generator
module Files
module TrackFiles
def minitest_tests
MinitestTestsFile.new(filename: File.join(exercise_path, minitest_tests_filename))
def minitest_tests(exercise)
MinitestTestsFile.new(filename: File.join(exercise_path(exercise), minitest_tests_filename(exercise)))
end

def tests_version
TestsVersionFile.new(filename: File.join(meta_path, version_filename))
def tests_version(exercise)
TestsVersionFile.new(filename: File.join(meta_path(exercise), version_filename))
end

def example_solution
ExampleSolutionFile.new(filename: File.join(solutions_path, example_filename))
def example_solution(exercise)
ExampleSolutionFile.new(filename: File.join(solutions_path(exercise), example_filename(exercise)))
end

def tests_template
TestsTemplateFile.new(filename: tests_template_absolute_filename)
def tests_template(exercise)
TestsTemplateFile.new(filename: tests_template_absolute_filename(exercise))
end

def test_case(exercise)
CaseFile.new(filename: File.join(generator_path(exercise), test_case_filename(exercise)))
end

private

def exercise_path
File.join(paths.track, 'exercises', slug)
def exercise_path(exercise)
File.join(paths.track, 'exercises', exercise.slug)
end

def meta_path(exercise)
File.join(exercise_path(exercise), '.meta')
end

def meta_path
File.join(exercise_path, '.meta')
def solutions_path(exercise)
File.join(meta_path(exercise), 'solutions')
end

def solutions_path
File.join(meta_path, 'solutions')
def generator_path(exercise)
File.join(meta_path(exercise), 'generator')
end

def minitest_tests_filename
"#{exercise_name}_test.rb"
def minitest_tests_filename(exercise)
"#{exercise.name}_test.rb"
end

def version_filename
'.version'
end

def example_filename
"#{exercise_name}.rb"
def example_filename(exercise)
"#{exercise.name}.rb"
end

def tests_template_absolute_filename
File.exist?(track_tests_template_filename) ? track_tests_template_filename :
def test_case_filename(exercise)
"#{exercise.name}_case.rb"
end

def tests_template_absolute_filename(exercise)
File.exist?(track_tests_template_filename(exercise)) ? track_tests_template_filename(exercise) :
default_tests_template_filename
end

def track_tests_template_filename
File.join(meta_path, 'generator', tests_template_filename)
def track_tests_template_filename(exercise)
File.join(meta_path(exercise), 'generator', tests_template_filename)
end

def default_tests_template_filename
Expand Down Expand Up @@ -89,5 +101,8 @@ def generate(template:, values:)

class TestsTemplateFile < Readable
end

class CaseFile < Readable
end
end
end
36 changes: 17 additions & 19 deletions lib/generator/implementation.rb
Original file line number Diff line number Diff line change
@@ -1,40 +1,38 @@
require 'delegate'
require 'forwardable'

module Generator
class Implementation
include Files::TrackFiles
include Files::MetadataFiles
include TemplateValuesFactory
extend Forwardable

def initialize(paths:, slug:)
@paths = paths
@slug = slug
def initialize(exercise:, repository:)
@exercise = exercise
@repository = repository
end

attr_reader :paths, :slug
attr_reader :exercise, :repository
def_delegators :@repository, :tests_version, :example_solution, :minitest_tests,
:tests_template, :canonical_data, :test_case

def version
tests_version.to_i
tests_version(exercise).to_i
end

def update_tests_version
tests_version.increment
tests_version(exercise).increment
end

def update_example_solution
example_solution.update_version(version)
example_solution(exercise).update_version(version)
end

def create_tests_file
minitest_tests.generate(
template: tests_template.to_s,
def build_tests
minitest_tests(exercise).generate(
template: tests_template(exercise).to_s,
values: template_values
)
end

def exercise_name
@exercise_name ||= slug.tr('-', '_')
end
end

# This exists to give us a clue as to what we are delegating to.
Expand All @@ -58,9 +56,9 @@ def update_example_solution
@logger.debug "Updated version in example solution to #{version}"
end

def create_tests_file
@implementation.create_tests_file
@logger.info "Generated #{slug} tests version #{version}"
def build_tests
@implementation.build_tests
@logger.info "Generated #{exercise.slug} tests version #{version}"
end
end
end
11 changes: 11 additions & 0 deletions lib/generator/repository.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Generator
class Repository
include Files::TrackFiles
include Files::MetadataFiles

def initialize(paths:)
@paths = paths
end
attr_reader :paths
end
end
48 changes: 28 additions & 20 deletions lib/generator/template_values.rb
Original file line number Diff line number Diff line change
@@ -1,55 +1,63 @@
module Generator
# Contains methods accessible to the ERB template
class TemplateValues
attr_reader :abbreviated_commit_hash, :version, :exercise_name, :test_cases, :canonical_data_version
using Underscore

def initialize(abbreviated_commit_hash:, version:, exercise_name:, test_cases:, canonical_data_version: nil)
@abbreviated_commit_hash = abbreviated_commit_hash
attr_reader :exercise, :version, :canonical_data, :test_cases

def initialize(exercise:, version:, canonical_data:, test_cases:)
@exercise = exercise
@version = version
@exercise_name = exercise_name
@canonical_data = canonical_data
@test_cases = test_cases
@canonical_data_version = canonical_data_version
end

def get_binding
binding
end

def abbreviated_commit_hash
canonical_data.abbreviated_commit_hash
end

def canonical_data_version
canonical_data.version
end

def exercise_name
exercise.name
end

def exercise_name_camel
exercise_name.split('_').map(&:capitalize).join
exercise.name.camel_case
end
end

module TemplateValuesFactory
def template_values
TemplateValues.new(
abbreviated_commit_hash: canonical_data.abbreviated_commit_hash,
canonical_data_version: canonical_data.version,
exercise: exercise,
version: version,
exercise_name: slug_underscore,
canonical_data: canonical_data(exercise),
test_cases: extract
)
end

private

def slug_underscore
slug ? slug.tr('-_', '_') : ''
end

def extract
load cases_load_name
extractor.cases(canonical_data.to_s)
load case_load_path(exercise)
extractor.cases(canonical_data(exercise).to_s)
end

def extractor
CaseValues::Extractor.new(
case_class: Object.const_get(Files::GeneratorCases.class_name(slug))
)
CaseValues::Extractor.new(
case_class: Object.const_get(exercise.case_class)
)
end

def cases_load_name
Files::GeneratorCases.source_filepath(paths.track, slug)
def case_load_path(exercise)
test_case(exercise).filename
end
end
end
Loading