# frozen_string_literal: true
module Gladys
class Script
attr_reader :path
def self.all
Dir.glob(File.join(__dir__, "..", "..", "scripts", "*.rb")).map do |path|
load(path.split("/").last.split(".").first)
end
end
def self.load(path, option_set: nil)
code = if File.exist?(path)
File.read(path)
elsif File.exist?(File.join(__dir__, "..", "..", "scripts", "#{path}.rb"))
File.read(File.join(__dir__, "..", "..", "scripts", "#{path}.rb"))
else
raise Errors::ScriptNotFound,
"Script #{File.join(__dir__, '..', '..', 'scripts', "#{path}.rb")} not found."
end
new(path, code, option_set: option_set).tap(&:validate_provided_options!)
end
def initialize(path, code, option_set: nil)
@path = path
@option_set = option_set
@blocks = {
cleanup: proc { |*| },
prepare: proc { |*| },
benchmark: proc { |*| },
create_tables: proc { |*| },
create_indexes: proc { |*| },
helpers: proc { |*| },
before: Concurrent::Hash.new,
after: Concurrent::Hash.new,
inputs: Concurrent::Hash.new,
description: proc { |*| "TODO" }
}
@definitions = {
preload_scale: 200_000,
insert_batch_size: 5_000,
database_size: nil
}
@option_sets = {}
eval_code(code)
end
def describe
@blocks[:description].call
end
def option_sets
@option_sets.values
end
def validate_provided_options!
return unless @option_set && !@option_sets.key?(@option_set.to_sym)
raise Errors::InvalidOptionSet, "--option-set #{@option_set} is not valid."
end
def script_name
@path.split("/").last.split(".").first
end
def name
name = @path.split("/").last.split(".").first
name = @display_name if @display_name
name
end
def running_version
@version || "unknown"
end
def helpers_block
@blocks[:helpers]
end
def action_block(action)
@blocks[action]
end
def before_block(action)
@blocks[:before][action] || proc { |*| }
end
def after_block(action)
@blocks[:after][action] || proc { |*| }
end
def defined_inputs
@blocks[:inputs].keys
end
def preload_inputs?
defined_inputs.any? { |input| preload_input?(input) }
end
def preload_input?(name)
(@blocks[:inputs][name] || {}).fetch(:preload, false)
end
def inputs_block(name)
(@blocks[:inputs][name] || {}).fetch(:block, proc { |*| })
end
# Check for the provided option set, or default to "default" (if it exists)
# and run the block to get the options.
def defined_options
@defined_options ||= begin
block = @option_sets[@option_set.to_sym] if @option_set
block ||= @option_sets[:default]
block&.call
Struct.new(*@definitions.keys).new(*@definitions.values)
end
end
def option(name, value: nil)
@definitions[name] = value || yield
end
def option_set(name, description = "", &)
@option_sets[name] = OptionSet.new(name, description, &)
end
def define_input(name, preload: false, &block)
@blocks[:inputs][name] = {
preload: preload,
block: block
}
end
def cleanup(&block)
@blocks[:cleanup] = block
end
def prepare(&block)
@blocks[:prepare] = block
end
def benchmark(&block)
@blocks[:benchmark] = block
end
def create_tables(&block)
@blocks[:create_tables] = block
end
def create_indexes(&block)
@blocks[:create_indexes] = block
end
def helpers(&block)
@blocks[:helpers] = block
end
def before(action, &block)
@blocks[:before][action] = block
end
def after(action, &block)
@blocks[:after][action] = block
end
def display_name(name)
@display_name = name
end
def version(version)
@version = version
end
def description(&block)
@blocks[:description] = block
end
private
# Eval in as much isolation as possible.
# TODO: Improve this somewhat.
def eval_code(code)
eval(code, binding, @path)
end
end
end