# frozen_string_literal: true module Opera module Operation class Base include Opera::Operation::Builder attr_accessor :context attr_reader :params, :dependencies, :result def initialize(params: {}, dependencies: {}) @context = {} @finished = false @result = Result.new @params = params.freeze @dependencies = dependencies.freeze config end def config self.class.config end def finish! @finished = true end def finished? @finished end class << self def call(args = {}) operation = new(params: args.fetch(:params, {}), dependencies: args.fetch(:dependencies, {})) executor = Executor.new(operation) executor.evaluate_instructions(instructions) executor.result end def config @config ||= Config.new end def configure yield config end def reporter config.reporter end def check_method_availability!(method) return if instance_methods(false).none?(method) raise(ArgumentError, "Method #{method} is already defined") end %i[context params dependencies].each do |method| define_method("#{method}_reader") do |*attributes, **options| attributes.map(&:to_sym).each do |attribute| check_method_availability!(attribute) define_method(attribute) do value = if send(method).key?(attribute) send(method)[attribute] elsif options[:default] instance_exec(&options[:default]) end if send(method).frozen? send(method)[attribute] || value else send(method)[attribute] ||= value end end end end end %i[context].each do |method| define_method("#{method}_writer") do |*attributes| attributes.map(&:to_sym).each do |attribute| check_method_availability!("#{attribute}=") define_method("#{attribute}=") do |value| send(method)[attribute] = value end end end define_method("#{method}_accessor") do |*attributes, **options| send("#{method}_reader", *attributes, **options) send("#{method}_writer", *attributes) end end end end end end