Class: Conductor::Condition

Inherits:
Object
  • Object
show all
Defined in:
lib/conductor/condition.rb

Overview

Condition class

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(condition) ⇒ Condition

Initializes the given condition.

Parameters:

  • condition

    The condition



14
15
16
17
# File 'lib/conductor/condition.rb', line 14

def initialize(condition)
  @condition = condition
  @env = Conductor::Env.env
end

Instance Attribute Details

#conditionObject

R/W condition



7
8
9
# File 'lib/conductor/condition.rb', line 7

def condition
  @condition
end

Instance Method Details

#operator_to_symbol(operator) ⇒ Symbol

Convert an operator string to a symbol

Parameters:

  • operator (String)

    The operator

Returns:

  • (Symbol)

    the operator symbol



456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
# File 'lib/conductor/condition.rb', line 456

def operator_to_symbol(operator)
  return operator if operator.nil?

  case operator
  when /(gt|greater( than)?|>|(?:is )?after)/i
    :gt
  when /(lt|less( than)?|<|(?:is )?before)/i
    :lt
  when /not (ha(?:s|ve)|contains?|includes?) +file/i
    :not_includes_file
  when /not (ha(?:s|ve)|contains?|includes?) +path/i
    :not_includes_path
  when /(ha(?:s|ve)|contains?|includes?|\*=) +file/i
    :includes_file
  when /(ha(?:s|ve)|contains?|includes?|\*=) +path/i
    :includes_path
  when /not (ha(?:s|ve)|contains|includes|match(es)?)/i
    :not_contains
  when /(ha(?:s|ve)|contains|includes|match(es)?|\*=)/i
    :contains
  when /not (suffix|ends? with)/i
    :not_ends_with
  when /not (prefix|(starts?|begins?) with)/i
    :not_starts_with
  when /(suffix|ends with|\$=)/i
    :ends_with
  when /(prefix|(starts?|begins?) with|\^=)/i
    :starts_with
  when /is not (an?|type( of)?)/i
    :not_type_of
  when /is (an?|type( of)?)/i
    :type_of
  when /((?:(?:is|does) )?not(?: equals?)?|!==?)/i
    :not_equal
  when /(is|==?|equals?)/i
    :equal
  end
end

#parse_conditionBoolean

Parse a condition, handling parens and booleans

Returns:

  • (Boolean)

    condition result



500
501
502
503
504
505
506
507
# File 'lib/conductor/condition.rb', line 500

def parse_condition
  cond = @condition.to_s.gsub(/\((.*?)\)/) do
    condition = Regexp.last_match(1)
    split_booleans(condition)
  end

  split_booleans(cond)
end

#split_booleans(condition) ⇒ Boolean

Splits booleans and tests components.

Parameters:

  • condition

    The condition to test

Returns:

  • (Boolean)

    test result



35
36
37
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
# File 'lib/conductor/condition.rb', line 35

def split_booleans(condition)
  split = condition.split(/ ((?:AND )?NOT|AND|OR) /)

  if split.count == 1
    test_condition(split[0])
  else
    res = nil
    bool = nil
    prev = false
    split.each do |cond|
      if /((?:AND )?NOT|AND|OR|&&|\|\||!!)/.match?(cond)
        bool = cond.bool_to_symbol
        next
      end

      r = split_booleans(cond)

      if bool == :and && (!r || !prev)
        res = false
      elsif bool == :or && (r || prev)
        return true
      elsif bool == :not && (r || prev)
        res = false
      else
        res = r
      end

      prev = res
    end
    res
  end
end

#split_condition(condition) ⇒ Array

Splits a natural language condition.

Parameters:

  • condition

    The condition

Returns:

  • (Array)

    Value, value to compare, operator



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/conductor/condition.rb', line 104

def split_condition(condition)
  if condition.match(/(?:((?:does )?not)?(?:ha(?:s|ve)|contains?|includes?) +)?(yaml|headers|frontmatter|mmd|meta(?:data)?|pandoc)(:[\S_ ]+)?/i)
    m = Regexp.last_match
    op = m[1].nil? ? :contains : :not_contains
    type = case m[2]
           when /^m/i
             "mmd"
           when /^p/i
             "pandoc"
           else
             "yaml"
           end
    return ["#{type}#{m[3]}", nil, op]
  end

  res = condition.match(/(?i)^(?<val1>.*?)(?:(?:\s+(?<bool>(?:is|do(?:es)?)?\s*(?:not)?\s*)(?<op>(?:an?|type(?:\sof)?|equals?(?:\sto))?|[!*$]?==?|[gl]t|(?:greater|less)(?:\sthan)?|<|>|(?:starts|ends) with|(?:ha(?:s|ve)\s)?(?:prefix|suffix)|(?:contains?|includes?)\s+(?:file|path)|has|contains?|includes?|match(?:es)?)\s+)(?<val2>.*?))?$/)
  operator = res["bool"] ? "#{res["bool"]}#{res["op"]}" : res["op"]
  [res["val1"], res["val2"], operator_to_symbol(operator)]
end

#test_condition(condition) ⇒ Object



406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
# File 'lib/conductor/condition.rb', line 406

def test_condition(condition)
  type, value, operator = split_condition(condition)

  if operator.nil?
    return case type
           when /^(true|any|all|else|\*+|catch(all)?)$/i
             true
           else
             false
           end
  end

  case type
  when /^env(?:ironment)?(?:[:\[\()](.*?)[\]\)]?)?$/i
    key = Regexp.last_match(1) || nil
    test_env(value, key, operator)
  when /^include/i
    test_includes(@env[:includes], value, operator) ? true : false
  when /^ext/i
    test_string(@env[:ext], value, operator) ? true : false
  when /^tree/i
    test_tree(@env[:origin], value, operator)
  when /^(path|dir)/i
    test_string(@env[:filepath], value, operator) ? true : false
  when /^(file)?name/i
    test_string(@env[:filename], value, operator) ? true : false
  when /^phase/i
    test_string(@env[:phase], value, :starts_with) ? true : false
  when /^text/i
    test_string(Conductor.stdin, value, operator) ? true : false
  when /^(?:yaml|headers|frontmatter)(?::(.*?))?$/i
    key = Regexp.last_match(1) || nil
    Conductor.stdin.yaml? ? test_yaml(Conductor.stdin, value, key, operator) : false
  when /^(?:mmd|meta(?:data)?)(?::(.*?))?$/i
    key = Regexp.last_match(1) || nil
    Conductor.stdin.meta? ? test_meta(Conductor.stdin, value, key, operator) : false
  when /^pandoc/
    test_pandoc(Conductor.stdin, operator)
  else
    false
  end
end

#test_env(value, key, operator) ⇒ Boolean

Test for environment variable

Parameters:

  • value (String)

    The value (test for existence of key if nil)

  • key (String)

    The key

  • operator (Symbol)

    The operator

Returns:

  • (Boolean)

    test result



294
295
296
297
298
299
300
301
302
# File 'lib/conductor/condition.rb', line 294

def test_env(value, key, operator)
  env = Env.env.merge(ENV)
  if key.nil?
    res = !env[value].nil?
    return operator == :not_contains ? !res : res
  end

  return test_string(env[key], value, operator)
end

#test_includes(includes, val, operator) ⇒ Object

Test for includes

Parameters:

  • includes (Array)

    array of included files

  • val

    value to test against

  • operator

    The operator



156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/conductor/condition.rb', line 156

def test_includes(includes, val, operator)
  case operator
  when :not_includes_file
    !includes.includes_file?(val)
  when :not_includes_path
    !includes.includes_frag?(val)
  when :includes_file
    includes.includes_file?(val)
  when :includes_path
    includes.includes_frag?(val)
  else
    false
  end
end

#test_meta(content, value, key, operator) ⇒ Boolean

Test for MultiMarkdown metadata,

optionally key and value

Parameters:

  • content (String)

    The text content

  • value (String)

    The value to test for

  • key (String)

    The key to test for

  • operator (Symbol)

    The operator

Returns:

  • (Boolean)

    test result



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'lib/conductor/condition.rb', line 363

def test_meta(content, value, key, operator)
  headers = []
  content.split("\n").each do |line|
    break if line == /^ *\n$/ || line !~ /\w+: *\S/

    headers << line
  end

  return operator == :not_equal if headers.empty?

  return operator != :not_equal if value.nil?

  meta = {}
  headers.each do |h|
    parts = h.split(/ *: */)
    k = parts[0].strip.downcase.gsub(/ +/, "")
    v = parts[1..].join(":").strip
    meta[k] = v
  end

  if key
    if %i[type_of not_type_of].include?(operator)
      return test_type(meta[key], value, operator)
    end

    test_string(meta[key], value, operator)
  else
    res = value ? meta.key?(value) : true
    operator == :not_equal ? !res : res
  end
end

#test_operator(value1, value2, operator) ⇒ Boolean

Test operators

Parameters:

  • value1

    Value

  • value2

    Value to test

  • operator

    The operator

Returns:

  • (Boolean)

    test result



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/conductor/condition.rb', line 77

def test_operator(value1, value2, operator)
  case operator
  when :gt
    value1.to_f > value2.to_f
  when :lt
    value1.to_f < value2.to_f
  when :not_contains
    value1.to_s !~ /#{value2}/i
  when :contains
    value1.to_s =~ /#{value2}/i
  when :starts_with
    value1.to_s =~ /^#{value2}/i
  when :ends_with
    value1.to_s =~ /#{value2}$/i
  when :not_equal
    value1 != value2
  when :equal
    value1 == value2
  end
end

#test_pandoc(content, operator) ⇒ Object

Test for Pandoc headers

Parameters:

  • content (String)

    The content to test

  • operator (Symbol)

    The operator



401
402
403
404
# File 'lib/conductor/condition.rb', line 401

def test_pandoc(content, operator)
  res = content.meta_type == :pandoc
  %i[not_contains not_equal].include?(operator) ? !res.nil? : res.nil?
end

#test_string(val1, val2, operator) ⇒ Boolean

Compare a string based on operator

Parameters:

  • val1

    The string to test against

  • val2

    The value to test

  • operator

    The operator

Returns:

  • (Boolean)

    test result



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/conductor/condition.rb', line 180

def test_string(val1, val2, operator)
  return operator == :not_equal ? val1.nil? : !val1.nil? if val2.nil?

  return operator == :not_equal if val1.nil?

  val2 = val2.to_s.dup.force_encoding("utf-8")

  if val2.bool?
    res = val2.to_bool == val1.to_bool
    return operator === :not_equal ? !res : res
  end

  if val1.date?
    if val2.time?
      date1 = val1.to_date
      date2 = val2.to_date
    else
      date1 = operator == :gt ? val1.to_day(:end) : val1.to_day
      date2 = operator == :gt ? val2.to_day(:end) : val2.to_day
    end

    res = case operator
          when :gt
            date1 > date2
          when :lt
            date1 < date2
          when :equal
            date1 == date2
          when :not_equal
            date1 != date2
          end
    return res unless res.nil?
  end

  val2 = if %r{^/.*?/$}.match?(val2.strip)
           val2.gsub(%r{(^/|/$)}, "")
         else
           Regexp.escape(val2)
         end
  val1 = val1.dup.to_s.force_encoding("utf-8")
  case operator
  when :contains
    val1 =~ /#{val2}/i ? true : false
  when :not_starts_with
    val1 !~ /^#{val2}/i ? true : false
  when :not_ends_with
    val1 !~ /#{val2}$/i ? true : false
  when :starts_with
    val1 =~ /^#{val2}/i ? true : false
  when :ends_with
    val1 =~ /#{val2}$/i ? true : false
  when :equal
    val1 =~ /^#{val2}$/i ? true : false
  when :not_equal
    val1 !~ /^#{val2}$/i ? true : false
  else
    false
  end
end

#test_tree(origin, value, operator) ⇒ Boolean

Test for the existince of a

file/directory in the parent tree

Parameters:

  • origin

    Starting directory

  • value

    The file/directory to search for

  • operator

    The operator

Returns:

  • (Boolean)

    test result



251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/conductor/condition.rb', line 251

def test_tree(origin, value, operator)
  return true if File.exist?(File.join(origin, value))

  dir = File.dirname(origin)

  if Dir.exist?(File.join(dir, value))
    true
  elsif [Dir.home, "/"].include?(dir)
    false
  else
    test_tree(dir, value, operator)
  end
end

#test_truthy(value1, value2, operator) ⇒ Boolean

Test “truthiness”

Parameters:

  • value1

    Value to test against

  • value2

    Value to test

  • operator

    The operator

Returns:

  • (Boolean)

    test result



274
275
276
277
278
279
280
281
282
# File 'lib/conductor/condition.rb', line 274

def test_truthy(value1, value2, operator)
  return false unless value2&.bool?

  value2 = value2.to_bool if value2.is_a?(String)

  res = value1 == value2

  operator == :not_equal ? !res : res
end

#test_type(val1, val2, operator) ⇒ Object

Test for type of value

Parameters:

  • val1

    value

  • val2

    value to test against

  • operator

    The operator



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/conductor/condition.rb', line 131

def test_type(val1, val2, operator)
  res = case val2
        when /number/
          val1.is_a?(Numeric)
        when /int(eger)?/
          val1.is_a?(Integer)
        when /(float|decimal)/
          val1.is_a?(Float)
        when /array/i
          val1.is_a?(Array)
        when /(string|text)/i
          val1.is_a?(String)
        when /date/i
          val1.date?
        end
  operator == :type_of ? res : !res
end

#test_yaml(content, value, key, operator) ⇒ Boolean

Test for presence of yaml, optionall for

a key, optionally for a key's value

Parameters:

  • content

    Text content containing YAML

  • value

    The value to test for

  • key

    The key to test for

  • operator

    The operator

Returns:

  • (Boolean)

    test result



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
# File 'lib/conductor/condition.rb', line 315

def test_yaml(content, value, key, operator)
  begin
    yaml = YAML.load(content.split(/^(?:---|\.\.\.)/)[1])
  rescue StandardError
    return false
  end

  return operator == :not_equal unless yaml

  if key
    value1 = yaml[key]
    return operator == :not_equal if value1.nil?

    if value.nil?
      has_key = !value1.nil?
      return operator == :not_equal ? !has_key : has_key
    end

    if %i[type_of not_type_of].include?(operator)
      return test_type(value1, value, operator)
    end

    value1 = value1.join(",") if value1.is_a?(Array)

    if value1.to_s.bool?
      test_truthy(value1, value, operator)
    elsif value1.to_s.number? && value.to_s.number? && %i[gt lt equal not_equal].include?(operator)
      test_operator(value1, value, operator)
    else
      test_string(value1, value, operator)
    end
  else
    res = value ? yaml.key?(value) : true
    (operator == :not_equal) ? !res : res
  end
end

#true?Boolean

Tests condition

Returns:

  • (Boolean)

    test result



24
25
26
# File 'lib/conductor/condition.rb', line 24

def true?
  parse_condition
end