<commit_before>require 'itamae'
require 'shellwords'
require 'hashie'

module Itamae
  module Resource
    class Base
      class EvalContext
        attr_reader :attributes
        attr_reader :notifications
        attr_reader :subscriptions
        attr_reader :verify_commands
        attr_reader :only_if_command
        attr_reader :not_if_command

        def initialize(resource)
          @resource = resource

          @attributes = Hashie::Mash.new
          @notifications = []
          @subscriptions = []
          @verify_commands = []
        end

        def respond_to_missing?(method, include_private = false)
          @resource.class.defined_attributes.has_key?(method) || super
        end

        def method_missing(method, *args, &block)
          if @resource.class.defined_attributes[method]
            if args.size == 1
              return @attributes[method] = args.first
            elsif args.size == 0 && block_given?
              return @attributes[method] = block
            elsif args.size == 0
              return @attributes[method]
            end
          end

          super
        end

        def notifies(action, resource_desc, timing = :delay)
          @notifications << Notification.create(@resource, action, resource_desc, timing)
        end

        def subscribes(action, resource_desc, timing = :delay)
          @subscriptions << Subscription.create(@resource, action, resource_desc, timing)
        end

        def only_if(command)
          @only_if_command = command
        end

        def not_if(command)
          @not_if_command = command
        end

        def node
          @resource.recipe.runner.node
        end

        def run_command(*args)
          @resource.recipe.runner.backend.run_command(*args)
        end

        # Experimental
        def verify(command)
          @verify_commands << command
        end
      end

      @defined_attributes ||= {}

      class << self
        attr_reader :defined_attributes
        attr_reader :supported_oses

        def inherited(subclass)
          subclass.instance_variable_set(
            :@defined_attributes,
            self.defined_attributes.dup
          )
        end

        def define_attribute(name, options)
          current = @defined_attributes[name.to_sym] || {}
          @defined_attributes[name.to_sym] = current.merge(options)
        end
      end

      define_attribute :action, type: [Symbol, Array], required: true
      define_attribute :user, type: String
      define_attribute :cwd, type: String

      attr_reader :recipe
      attr_reader :resource_name
      attr_reader :attributes
      attr_reader :current_attributes
      attr_reader :subscriptions
      attr_reader :notifications
      attr_reader :updated

      def initialize(recipe, resource_name, &block)
        clear_current_attributes
        @recipe = recipe
        @resource_name = resource_name
        @updated = false

        EvalContext.new(self).tap do |context|
          context.instance_eval(&block) if block
          @attributes = context.attributes
          @notifications = context.notifications
          @subscriptions = context.subscriptions
          @only_if_command = context.only_if_command
          @not_if_command = context.not_if_command
          @verify_commands = context.verify_commands
        end

        process_attributes
      end

      def run(specific_action = nil)
        runner.report_with_block(:resource, resource_type: resource_type, resource_name: resource_name) do
          Itamae.logger.debug "#{resource_type}[#{resource_name}]"

          Itamae.logger.with_indent_if(Itamae.logger.debug?) do
            if do_not_run_because_of_only_if?
              Itamae.logger.debug "#{resource_type}[#{resource_name}] Execution skipped because of only_if attribute"
              return
            elsif do_not_run_because_of_not_if?
              Itamae.logger.debug "#{resource_type}[#{resource_name}] Execution skipped because of not_if attribute"
              return
            end

            [specific_action || attributes.action].flatten.each do |action|
              run_action(action)
            end

            verify unless runner.dry_run?
            notify if updated?
          end

          @updated = false
        end
      rescue Backend::CommandExecutionError
        Itamae.logger.error "#{resource_type}[#{resource_name}] Failed."
        exit 2
      end

      def action_nothing
        # do nothing
      end

      def resource_type
        self.class.name.split("::").last.scan(/[A-Z][^A-Z]+/).map(&:downcase).join('_')
      end

      private

      alias_method :current, :current_attributes

      def run_action(action)
        runner.report_with_block(:action, action: action) do
          original_attributes = @attributes # preserve and restore later
          @current_action = action

          clear_current_attributes

          Itamae.logger.debug "#{resource_type}[#{resource_name}] action: #{action}"

          return if action == :nothing

          Itamae.logger.with_indent_if(Itamae.logger.debug?) do
            Itamae.logger.debug "(in pre_action)"
            pre_action

            Itamae.logger.debug "(in set_current_attributes)"
            set_current_attributes

            Itamae.logger.debug "(in show_differences)"
            show_differences

            method_name = "action_#{action}"
            if runner.dry_run?
              unless respond_to?(method_name)
                Itamae.logger.error "action #{action.inspect} is unavailable"
              end
            else
              args = [method_name]
              if method(method_name).arity == 1
                # for plugin compatibility
                args << runner.options
              end

              public_send(*args)
            end

            updated! if different?<commit_msg>attribute_changed event.<commit_after>require 'itamae'
require 'shellwords'
require 'hashie'

module Itamae
  module Resource
    class Base
      class EvalContext
        attr_reader :attributes
        attr_reader :notifications
        attr_reader :subscriptions
        attr_reader :verify_commands
        attr_reader :only_if_command
        attr_reader :not_if_command

        def initialize(resource)
          @resource = resource

          @attributes = Hashie::Mash.new
          @notifications = []
          @subscriptions = []
          @verify_commands = []
        end

        def respond_to_missing?(method, include_private = false)
          @resource.class.defined_attributes.has_key?(method) || super
        end

        def method_missing(method, *args, &block)
          if @resource.class.defined_attributes[method]
            if args.size == 1
              return @attributes[method] = args.first
            elsif args.size == 0 && block_given?
              return @attributes[method] = block
            elsif args.size == 0
              return @attributes[method]
            end
          end

          super
        end

        def notifies(action, resource_desc, timing = :delay)
          @notifications << Notification.create(@resource, action, resource_desc, timing)
        end

        def subscribes(action, resource_desc, timing = :delay)
          @subscriptions << Subscription.create(@resource, action, resource_desc, timing)
        end

        def only_if(command)
          @only_if_command = command
        end

        def not_if(command)
          @not_if_command = command
        end

        def node
          @resource.recipe.runner.node
        end

        def run_command(*args)
          @resource.recipe.runner.backend.run_command(*args)
        end

        # Experimental
        def verify(command)
          @verify_commands << command
        end
      end

      @defined_attributes ||= {}

      class << self
        attr_reader :defined_attributes
        attr_reader :supported_oses

        def inherited(subclass)
          subclass.instance_variable_set(
            :@defined_attributes,
            self.defined_attributes.dup
          )
        end

        def define_attribute(name, options)
          current = @defined_attributes[name.to_sym] || {}
          @defined_attributes[name.to_sym] = current.merge(options)
        end
      end

      define_attribute :action, type: [Symbol, Array], required: true
      define_attribute :user, type: String
      define_attribute :cwd, type: String

      attr_reader :recipe
      attr_reader :resource_name
      attr_reader :attributes
      attr_reader :current_attributes
      attr_reader :subscriptions
      attr_reader :notifications
      attr_reader :updated

      def initialize(recipe, resource_name, &block)
        clear_current_attributes
        @recipe = recipe
        @resource_name = resource_name
        @updated = false

        EvalContext.new(self).tap do |context|
          context.instance_eval(&block) if block
          @attributes = context.attributes
          @notifications = context.notifications
          @subscriptions = context.subscriptions
          @only_if_command = context.only_if_command
          @not_if_command = context.not_if_command
          @verify_commands = context.verify_commands
        end

        process_attributes
      end

      def run(specific_action = nil)
        runner.report_with_block(:resource, resource_type: resource_type, resource_name: resource_name) do
          Itamae.logger.debug "#{resource_type}[#{resource_name}]"

          Itamae.logger.with_indent_if(Itamae.logger.debug?) do
            if do_not_run_because_of_only_if?
              Itamae.logger.debug "#{resource_type}[#{resource_name}] Execution skipped because of only_if attribute"
              return
            elsif do_not_run_because_of_not_if?
              Itamae.logger.debug "#{resource_type}[#{resource_name}] Execution skipped because of not_if attribute"
              return
            end

            [specific_action || attributes.action].flatten.each do |action|
              run_action(action)
            end

            verify unless runner.dry_run?
            notify if updated?
          end

          @updated = false
        end
      rescue Backend::CommandExecutionError
        Itamae.logger.error "#{resource_type}[#{resource_name}] Failed."
        exit 2
      end

      def action_nothing
        # do nothing
      end

      def resource_type
        self.class.name.split("::").last.scan(/[A-Z][^A-Z]+/).map(&:downcase).join('_')
      end

      private

      alias_method :current, :current_attributes

      def run_action(action)
        runner.report_with_block(:action, action: action) do
          original_attributes = @attributes # preserve and restore later
          @current_action = action

          clear_current_attributes

          Itamae.logger.debug "#{resource_type}[#{resource_name}] action: #{action}"

          return if action == :nothing

          Itamae.logger.with_indent_if(Itamae.logger.debug?) do
            Itamae.logger.debug "(in pre_action)"
            pre_action

            Itamae.logger.debug "(in set_current_attributes)"
            set_current_attributes

            Itamae.logger.debug "(in show_differences)"
            show_differences

            method_name = "action_#{action}"
            if runner.dry_run?
              unless respond_to?(method_name)
                Itamae.logger.error "action #{action.inspect} is unavailable"
              end
            else
              args = [method_name]
              if method(method_name).arity == 1
                # for plugin compatibility
                args << runner.options
              end

              public_send(*args)
            end

            if different?
              updated!
              runner.report(:attribute_changed, from: @current_attributes, to: @attributes)
            end
{'fraction_non_alphanumeric': 0.07842973131330042, 'fraction_numerical': 0.0008370302167908262, 'mean_word_length': 2.0065425264217414, 'pattern_counts': {'":': 2, '<': 23, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2331961', 'n_tokens_mistral': 3189, 'n_tokens_neox': 3082, 'n_words': 686}