Module: RemindMe::Utils::HashASTManipulations

Included in:
Reminder::BaseReminder
Defined in:
lib/remind_me/utils/hash_ast_manipulations.rb

Instance Method Summary collapse

Instance Method Details

#apply_to_hash_with(key_values) ⇒ Object

Will look for string/symbol keys with specified names in the comment, and if all are found, will return true



10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/remind_me/utils/hash_ast_manipulations.rb', line 10

def apply_to_hash_with(key_values)
  # Method for determining if we should consider given reminder for this AST
  define_singleton_method('applicable_to_ast?') do |ast|
    ast.type == :hash && key_values.all? { |key_value| key_present?(ast, key_value) }
  end
  # Building a Reminder from AST, creating invalid one if any of validations returns any non-nil value
  define_singleton_method('build_from') do |reminder_comment_ast, source_location|
    create_reminder_from_ast(reminder_comment_ast, source_location)
  end
  key_values.each do |key|
    validate_hash_ast(key: key, value_types: %i[sym str])
  end
end

#ast_hash_pair(hash_ast, key_value) ⇒ Object



88
89
90
# File 'lib/remind_me/utils/hash_ast_manipulations.rb', line 88

def ast_hash_pair(hash_ast, key_value)
  children_of_type(hash_ast, :pair).find { |pair| valid_hash_ast_key?(pair, key_value) }
end

#children_of_type(reminder_comment_ast, type) ⇒ Object



96
97
98
# File 'lib/remind_me/utils/hash_ast_manipulations.rb', line 96

def children_of_type(reminder_comment_ast, type)
  reminder_comment_ast.children.select { |child| child.type.to_s == type.to_s }
end

#create_hash_value_accessor_method(key) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/remind_me/utils/hash_ast_manipulations.rb', line 69

def create_hash_value_accessor_method(key)
  return if method_defined?("hash_#{key}")

  send(:define_method, "hash_#{key}") do
    comment_ast = instance_variable_get('@reminder_comment_ast')
    value = self.class.hash_ast_pair_value(self.class.ast_hash_pair(comment_ast, key))
    default_values = self.class.instance_variable_get('@hash_ast_default_values')
    if (value.nil? || value == '') && default_values.key?(key)
      default_values[key]
    else
      value
    end
  end
end

#create_reminder_from_ast(reminder_comment_ast, source_location) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/remind_me/utils/hash_ast_manipulations.rb', line 50

def create_reminder_from_ast(reminder_comment_ast, source_location)
  # We first perform AST validation
  ast_validation_errors = singleton_methods.select { |method| method.to_s.start_with?('validate_hash_ast_') }
                                           .map { |method| send(method, reminder_comment_ast, source_location) }
                                           .compact
  if ast_validation_errors.empty?
    # AST looks good, now we perform reminder-specific validations
    reminder = new(reminder_comment_ast, source_location)
    validation_errors = reminder.validation_errors
    if validation_errors.empty?
      reminder
    else
      RemindMe::Reminder::InvalidReminder.new(source_location, validation_errors.join(';'))
    end
  else
    RemindMe::Reminder::InvalidReminder.new(source_location, ast_validation_errors.join(';'))
  end
end

#hash_ast_pair_value(ast_pair) ⇒ Object



115
116
117
118
119
# File 'lib/remind_me/utils/hash_ast_manipulations.rb', line 115

def hash_ast_pair_value(ast_pair)
  return nil if ast_pair.nil? || ast_pair.children.nil? || ast_pair.children.size != 2

  ast_pair.children[1].to_a.first
end

#hash_ast_pair_value_type(ast_pair) ⇒ Object



111
112
113
# File 'lib/remind_me/utils/hash_ast_manipulations.rb', line 111

def hash_ast_pair_value_type(ast_pair)
  ast_pair.children[1].type
end

#key_present?(reminder_comment_ast, key_value) ⇒ Boolean

Returns:

  • (Boolean)


92
93
94
# File 'lib/remind_me/utils/hash_ast_manipulations.rb', line 92

def key_present?(reminder_comment_ast, key_value)
  children_of_type(reminder_comment_ast, :pair).any? { |pair| valid_hash_ast_key?(pair, key_value) }
end

#singleton_method_defined?(method_name) ⇒ Boolean

Returns:

  • (Boolean)


84
85
86
# File 'lib/remind_me/utils/hash_ast_manipulations.rb', line 84

def singleton_method_defined?(method_name)
  singleton_methods.any? { |method| method.to_s == method_name }
end

#valid_hash_ast_key?(ast_pair, key_value) ⇒ Boolean

key is either a symbol or string

Returns:

  • (Boolean)


101
102
103
104
# File 'lib/remind_me/utils/hash_ast_manipulations.rb', line 101

def valid_hash_ast_key?(ast_pair, key_value)
  key = ast_pair.children.first
  %i[sym str].include?(key.type) && key_value.to_s == key.to_a.first.to_s
end

#valid_hash_ast_value?(ast_pair, allowed_types) ⇒ Boolean

Returns:

  • (Boolean)


106
107
108
109
# File 'lib/remind_me/utils/hash_ast_manipulations.rb', line 106

def valid_hash_ast_value?(ast_pair, allowed_types)
  value = ast_pair.children[1]
  allowed_types.include?(value.type)
end

#validate_hash_ast(key:, value_types:, **options) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/remind_me/utils/hash_ast_manipulations.rb', line 24

def validate_hash_ast(key:, value_types:, **options)
  @hash_ast_default_values ||= {}
  @hash_ast_default_values[key] = options[:default_value] if options.key?(:default_value)
  create_hash_value_accessor_method(key)
  define_singleton_method("validate_hash_ast_#{key}") do |ast, source_location|
    return "REMIND_ME comment in #{source_location} is not a Hash" unless ast.type == :hash

    pair = ast_hash_pair(ast, key)
    # Valid pair was not found...
    if pair.nil?
      # ... and we don't have default value set for it
      unless @hash_ast_default_values.key?(key)
        "REMIND_ME comment in #{source_location}: value for '#{key}' could not be found, key needs to be "\
        "either String or Symbol. If not set 'default_value' can be used, but that one was not given as well"
      end
      # Pair was found...
    else
      # ... but it does not have proper value type
      unless valid_hash_ast_value?(pair, value_types)
        "REMIND_ME comment in #{source_location}: value under specified key '#{key}' does not have allowed "\
        "type (it has '#{hash_ast_pair_value_type(pair)}'), allowed types are #{value_types}"
      end
    end
  end
end