Module: CurlyBracketParser

Defined in:
lib/curly_bracket_parser.rb,
lib/curly_bracket_parser/version.rb

Overview

CurlyBracketParser

Parse variables with curly brackets within templates/strings or files

Use filters for special cases

Constant Summary collapse

VARIABLE_DECODER_REGEX =

{variable_name|optional_filter}

/{{([^{}\|]*)\|?([^{}\|]*)}}/
VARIABLE_REGEX =
/{{[^{}]*}}/
VALID_DEFAULT_FILTERS =
[
  LuckyCase::CASES.keys.map(&:to_s)
].flatten
VERSION =
'1.1.6'.freeze

Class Method Summary collapse

Class Method Details

.any_variable_included?(string) ⇒ Boolean

Check if any variable is included in the given string

Parameters:

  • string (String)

    name of variable to check for

Returns:

  • (Boolean)

    true if any variable is included in the given string, otherwise false



307
308
309
# File 'lib/curly_bracket_parser.rb', line 307

def self.any_variable_included?(string)
  string.match(VARIABLE_REGEX) != nil
end

.decode_variable(variable) ⇒ Hash<String => String>

Return a hash containing separated name and filter of a variable

Examples:

'{{var_name|filter_name}}' => { name: 'var_name', filter: 'filter_name' }

Parameters:

  • variable (String)

Returns:

  • (Hash<String => String>)

    name, filter



272
273
274
# File 'lib/curly_bracket_parser.rb', line 272

def self.decode_variable(variable)
  decoded_variables(variable).first
end

.decoded_variables(string) ⇒ Array<Hash<Symbol => String>>

Scans the given url for variables with pattern ‘{var|optional_filter}’

Examples:

'The variable {{my_var|my_filter}} is inside this string' => [{ name: "my_var", filter: "my_filter"}]

Parameters:

  • string (String)

    to scan

Returns:

  • (Array<Hash<Symbol => String>>)

    array of variable names and its filters



285
286
287
288
289
# File 'lib/curly_bracket_parser.rb', line 285

def self.decoded_variables(string)
  var_name_index = 0
  var_filter_index = 1
  string.scan(VARIABLE_DECODER_REGEX).map { |e| { name: "#{e[var_name_index].strip}", filter: e[var_filter_index].strip != '' ? e[var_filter_index].strip : nil } }.flatten
end

.includes_one_variable_of(variable_names, string) ⇒ Boolean

Check if one of the given variable names is included in the given string

Parameters:

  • variable_names (Array<String>)
  • string (String)

    name of variable to check for

Returns:

  • (Boolean)

    true if one given variable name is included in given the string, otherwise false



318
319
320
321
322
323
# File 'lib/curly_bracket_parser.rb', line 318

def self.includes_one_variable_of(variable_names, string)
  decoded_variables(string).each do |dvar|
    return true if variable_names.map(&:to_sym).include?(dvar[:name].to_sym)
  end
  false
end

.number_string?(name) ⇒ Boolean

Check if given variable is a valid number inside a string that evaluates to a number in Ruby.

Examples:

# valid number strings
'200'
'25.75'
'500_000'
'0x1fF'

Parameters:

  • name (String)

Returns:

  • (Boolean)


339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/curly_bracket_parser.rb', line 339

def self.number_string?(name)
  number_regex = /(^[0-9]+[0-9_]*([.][0-9_]*[0-9]+)?$|^0[xX][0-9A-Fa-f]+$)/
  if (name =~ number_regex) != nil
    begin
      eval name
      true
    rescue StandardError, SyntaxError => e
      false
    end
  else
    false
  end
end

.parse(string, variables = {}, options = { unresolved_vars: :raise, replace_pattern: "##\\1##" }) ⇒ String, UnresolvedVariablesError

Parse given string and replace the included variables by the given variables

Parameters:

  • string (String)

    to parse

  • variables (Hash<Symbol => String>) (defaults to: {})

    <key: ‘value’>

  • options (Hash) (defaults to: { unresolved_vars: :raise, replace_pattern: "##\\1##" })
  • options.unresolved_vars (Symbol)

    :raise, :keep, :replace => define how to act when unresolved variables within the string are found.

  • options.replace_pattern (String)

    pattern used when param unresolved_vars is set to :replace. You can include the var name \1 and filter \2. Empty string to remove unresolved variables.

Returns:



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/curly_bracket_parser.rb', line 38

def self.parse(string, variables = {}, options = { unresolved_vars: :raise, replace_pattern: "##\\1##" })
  variables ||= {}
  options ||= {}
  options[:unresolved_vars] = :raise unless options[:unresolved_vars]
  options[:replace_pattern] = "##\\1##" unless options[:replace_pattern]
  result_string = string.clone
  # symbolize keys
  variables = variables.map { |key, value| [key.to_sym, value] }.to_h
  if CurlyBracketParser.any_variable_included? string
    loop do
      variables(result_string).each do |string_var|
        dec = decode_variable(string_var)
        name = !dec[:name] || dec[:name] == '' ? "''" : dec[:name]
        filter = dec[:filter]
        value = nil
        is_single_quoted = name.start_with?("'") && name.end_with?("'")
        is_double_quoted = name.start_with?('"') && name.end_with?('"')
        # When the name itself is quoted as string or is a number, we use it as a value itself
        if is_single_quoted || is_double_quoted
          value = name[1...-1]
        elsif CurlyBracketParser.number_string? name
          value = eval(name)
        elsif variables[name.to_sym]
          value = variables[name.to_sym]
        elsif registered_default_var?(name.to_s)
          value = process_default_var(name)
        end
        if value
          value = process_filter(filter, value) if filter
          result_string.gsub!(string_var, value.to_s)
        end
      end
      # break if no more given variable is available
      unless any_variable_included?(result_string) && includes_one_variable_of(variables.keys, result_string)
        break
      end
    end
    case options[:unresolved_vars]
    when :raise
      if any_variable_included? result_string
        raise UnresolvedVariablesError, "There are unresolved variables in the given string: #{variables(result_string)}"
      end
    when :replace
      result_string.gsub!(VARIABLE_DECODER_REGEX, options[:replace_pattern])
    end
  end
  result_string
end

.parse_file(path, variables, unresolved_vars: :raise, replace_pattern: "##\\1##") ⇒ String, UnresolvedVariablesError

Parse the content of the file of the given path with #parse and return it. The original file keeps unmodified.

Parameters:

  • string (String)

    to parse

  • variables (Hash<Symbol => String>)

    <key: ‘value’>

  • unresolved_vars (Symbol) (defaults to: :raise)

    :raise, :keep, :replace => define how to act when unresolved variables within the string are found.

  • replace_pattern (String) (defaults to: "##\\1##")

    pattern used when param unresolved_vars is set to :replace. You can include the var name \1 and filter \1. Empty string to remove unresolved variables.

Returns:



97
98
99
100
# File 'lib/curly_bracket_parser.rb', line 97

def self.parse_file(path, variables, unresolved_vars: :raise, replace_pattern: "##\\1##")
  file_content = File.read path
  parse(file_content, variables, unresolved_vars: unresolved_vars, replace_pattern: replace_pattern)
end

.parse_file!(path, variables, unresolved_vars: :raise, replace_pattern: "##\\1##") ⇒ String, UnresolvedVariablesError

Parse the content of the file of the given path with #parse and return it. The original file will be overwritten by the parsed content.

Parameters:

  • string (String)

    to parse

  • variables (Hash<Symbol => String>)

    <key: ‘value’>

  • unresolved_vars (Symbol) (defaults to: :raise)

    :raise, :keep, :replace => define how to act when unresolved variables within the string are found.

  • replace_pattern (String) (defaults to: "##\\1##")

    pattern used when param unresolved_vars is set to :replace. You can include the var name \1 and filter \1. Empty string to remove unresolved variables.

Returns:



110
111
112
113
114
# File 'lib/curly_bracket_parser.rb', line 110

def self.parse_file!(path, variables, unresolved_vars: :raise, replace_pattern: "##\\1##")
  parsed_file = parse_file path, variables, unresolved_vars: unresolved_vars, replace_pattern: replace_pattern
  File.write path, parsed_file
  parsed_file
end

.process_default_var(name) ⇒ String

Return the given default variable by returning the result of its block/proc

Parameters:

  • name (String)

    of the variable to return

Returns:

  • (String)

    value of the variable



200
201
202
203
204
205
206
207
208
209
# File 'lib/curly_bracket_parser.rb', line 200

def self.process_default_var(name)
  @@registered_default_vars ||= {}
  name = name.to_s
  if @@registered_default_vars[name]
    @@registered_default_vars[name].call()
  else
    message = "Invalid default variable '#{name}'. Valid registered default variables are: #{self.registered_default_vars.keys.join(' ')}"
    raise InvalidVariableError, message
  end
end

.process_filter(filter, value) ⇒ String

Process the given value with the given filter

Parameters:

  • filter (String)

    name of the filter, also used then in your strings, e.g. {var_name|my_filter_name}

  • value (String)

    string to apply the specified filter on

Returns:

  • (String)

    converted string with applied filter



141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/curly_bracket_parser.rb', line 141

def self.process_filter(filter, value)
  @@registered_filters ||= {}
  filter = filter.to_s
  if @@registered_filters[filter]
    @@registered_filters[filter].call(value)
  elsif VALID_DEFAULT_FILTERS.include?(filter) && LuckyCase.valid_case_type?(filter)
    LuckyCase.convert_case(value, filter)
  else
    message = "Invalid filter '#{filter}'. Valid filters are: #{self.valid_filters.join(' ')}"
    raise InvalidFilterError, message
  end
end

.register_default_var(name, &block) ⇒ Proc

Register a default variable to be replaced automatically by the given block value in future If the variable exists already, it will raise an VariableAlreadyRegisteredError

Parameters:

  • name (String)

    of the default var

  • block (Proc)

Returns:

  • (Proc)

    given block

Raises:



184
185
186
187
188
189
190
191
192
# File 'lib/curly_bracket_parser.rb', line 184

def self.register_default_var(name, &block)
  @@registered_default_vars ||= {}
  name = name.to_s
  if registered_default_var?(name)
    raise VariableAlreadyRegisteredError, "The given variable name '#{name}' is already registered. If you want to override that variable explicitly, call #register_default_var! instead!"
  else
    @@registered_default_vars[name] = block
  end
end

.register_default_var!(name, &block) ⇒ Proc

Register a default variable to be replaced automatically by the given block value in future If the variable exists already, it will be overwritten

Parameters:

  • name (String)

    of the default var

  • block (Proc)

Returns:

  • (Proc)

    given block

Raises:



220
221
222
223
224
# File 'lib/curly_bracket_parser.rb', line 220

def self.register_default_var!(name, &block)
  @@registered_default_vars ||= {}
  name = name.to_s
  @@registered_default_vars[name] = block
end

.register_filter(filter, &block) ⇒ Proc

Register your custom filter to the filter list

Parameters:

  • filter (String)

    name of the filter, also used then in your strings, e.g. {var_name|my_filter_name}

  • function (Lambda)

    of the filter to run the variable against

Returns:

  • (Proc)

    given block

Raises:



124
125
126
127
128
129
130
131
132
# File 'lib/curly_bracket_parser.rb', line 124

def self.register_filter(filter, &block)
  @@registered_filters ||= {}
  filter = filter.to_s
  if valid_filter?(filter)
    raise FilterAlreadyRegisteredError, "The given filter name '#{filter}' is already registered"
  else
    @@registered_filters[filter] = block
  end
end

.registered_default_var?(name) ⇒ Boolean

Check if the given variable is a registered default variable

Parameters:

  • name (String)

    of the variable

Returns:

  • (Boolean)

    true if variable is registered, otherwise false



259
260
261
# File 'lib/curly_bracket_parser.rb', line 259

def self.registered_default_var?(name)
  self.registered_default_vars.include? name
end

.registered_default_varsArray<String>

Return an array of registered default variables

Returns:

  • (Array<String>)


248
249
250
251
# File 'lib/curly_bracket_parser.rb', line 248

def self.registered_default_vars
  @@registered_default_vars ||= {}
  @@registered_default_vars.keys.map(&:to_s)
end

.unregister_default_var(name) ⇒ Boolean

Unregister / remove an existing default variable

Parameters:

  • name (String)

    of the variable

Returns:

  • (Boolean)

    true if variable existed and was unregistered, false if it didn’t exist



232
233
234
235
236
237
238
239
240
241
# File 'lib/curly_bracket_parser.rb', line 232

def self.unregister_default_var(name)
  @@registered_default_vars ||= {}
  name = name.to_s
  if @@registered_default_vars[name]
    @@registered_default_vars.delete(name)
    true
  else
    false
  end
end

.valid_filter?(name) ⇒ Boolean

Check if a given filter is valid

Parameters:

  • name (String)

Returns:

  • (Boolean)

    true if filter exists, otherwise false



171
172
173
# File 'lib/curly_bracket_parser.rb', line 171

def self.valid_filter?(name)
  self.valid_filters.include? name
end

.valid_filtersArray<String>

Retrieve Array with valid filters

Returns:

  • (Array<String>)

    of valid filters



159
160
161
162
163
# File 'lib/curly_bracket_parser.rb', line 159

def self.valid_filters
  all_filters = VALID_DEFAULT_FILTERS
  @@registered_filters ||= {}
  all_filters + @@registered_filters.keys.map(&:to_s)
end

.variables(string) ⇒ Array<String>

Scans the given url for variables with pattern ‘{var|optional_filter}’

Parameters:

  • string (String)

    to scan

Returns:

  • (Array<String>)

    array of variable names and its filters



297
298
299
# File 'lib/curly_bracket_parser.rb', line 297

def self.variables(string)
  string.scan(VARIABLE_REGEX).flatten
end