Class: Kettle::Jem::DecisionPolicy

Inherits:
Object
  • Object
show all
Defined in:
lib/kettle/jem.rb

Constant Summary collapse

TRUE_VALUES =
%w[1 true y yes on].freeze
FALSE_VALUES =
%w[0 false n no off].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(mode: :accept, failure_mode: "error", require_clean: nil, input_source: "default", prompt_answers: {}) ⇒ DecisionPolicy

Returns a new instance of DecisionPolicy.



619
620
621
622
623
624
625
# File 'lib/kettle/jem.rb', line 619

def initialize(mode: :accept, failure_mode: "error", require_clean: nil, input_source: "default", prompt_answers: {})
  @mode = normalize_mode(mode)
  @failure_mode = failure_mode.to_s.empty? ? "error" : failure_mode.to_s
  @require_clean = require_clean
  @input_source = input_source.to_s
  @prompt_answers = normalize_prompt_answers(prompt_answers)
end

Instance Attribute Details

#failure_modeObject (readonly)

Returns the value of attribute failure_mode.



568
569
570
# File 'lib/kettle/jem.rb', line 568

def failure_mode
  @failure_mode
end

#input_sourceObject (readonly)

Returns the value of attribute input_source.



568
569
570
# File 'lib/kettle/jem.rb', line 568

def input_source
  @input_source
end

#modeObject (readonly)

Returns the value of attribute mode.



568
569
570
# File 'lib/kettle/jem.rb', line 568

def mode
  @mode
end

#prompt_answersObject (readonly)

Returns the value of attribute prompt_answers.



568
569
570
# File 'lib/kettle/jem.rb', line 568

def prompt_answers
  @prompt_answers
end

#require_cleanObject (readonly)

Returns the value of attribute require_clean.



568
569
570
# File 'lib/kettle/jem.rb', line 568

def require_clean
  @require_clean
end

Class Method Details

.falsey?(value) ⇒ Boolean

Returns:

  • (Boolean)


595
596
597
# File 'lib/kettle/jem.rb', line 595

def self.falsey?(value)
  FALSE_VALUES.include?(value.to_s.strip.downcase)
end

.from_env(env = {}, **options) ⇒ Object



570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
# File 'lib/kettle/jem.rb', line 570

def self.from_env(env = {}, **options)
  env_hash = env || {}
  option_hash = symbolize_keys(options)
  interactive = option_hash.key?(:interactive) ? option_hash[:interactive] : truthy?(env_hash["interactive"])
  force = option_hash.key?(:force) ? option_hash[:force] : value_to_boolean(env_hash["force"])
  accept = option_hash.key?(:accept) ? option_hash[:accept] : value_to_boolean(env_hash["accept"])
  interactive = false if accept == true || force == true
  interactive = true if force == false && accept != true && !option_hash.key?(:interactive)
  new(
    mode: interactive ? :interactive : :accept,
    failure_mode: option_hash.fetch(:failure_mode, env_hash["FAILURE_MODE"] || env_hash["failure_mode"] || "error"),
    require_clean: option_hash.fetch(:require_clean, value_to_boolean(env_hash["KETTLE_JEM_REQUIRE_CLEAN"])),
    input_source: option_hash.fetch(:input_source, "default"),
    prompt_answers: option_hash.fetch(:prompt_answers, parse_prompt_answers(env_hash["KETTLE_JEM_PROMPT_ANSWERS"]))
  )
end

.parse_prompt_answers(value) ⇒ Object



608
609
610
611
612
613
614
615
616
617
# File 'lib/kettle/jem.rb', line 608

def self.parse_prompt_answers(value)
  return {} if value.nil? || value.to_s.strip.empty?

  parsed = JSON.parse(value.to_s)
  raise ArgumentError, "KETTLE_JEM_PROMPT_ANSWERS must be a JSON object" unless parsed.is_a?(Hash)

  parsed
rescue JSON::ParserError => error
  raise ArgumentError, "KETTLE_JEM_PROMPT_ANSWERS must be valid JSON: #{error.message}"
end

.symbolize_keys(hash) ⇒ Object



587
588
589
# File 'lib/kettle/jem.rb', line 587

def self.symbolize_keys(hash)
  hash.each_with_object({}) { |(key, value), acc| acc[key.to_sym] = value }
end

.truthy?(value) ⇒ Boolean

Returns:

  • (Boolean)


591
592
593
# File 'lib/kettle/jem.rb', line 591

def self.truthy?(value)
  TRUE_VALUES.include?(value.to_s.strip.downcase)
end

.value_to_boolean(value) ⇒ Object



599
600
601
602
603
604
605
606
# File 'lib/kettle/jem.rb', line 599

def self.value_to_boolean(value)
  return true if value == true
  return false if value == false
  return true if truthy?(value)
  return false if falsey?(value)

  nil
end

Instance Method Details

#accept?Boolean

Returns:

  • (Boolean)


627
628
629
# File 'lib/kettle/jem.rb', line 627

def accept?
  mode == :accept
end

#interactive?Boolean

Returns:

  • (Boolean)


631
632
633
# File 'lib/kettle/jem.rb', line 631

def interactive?
  mode == :interactive
end

#non_interactive?Boolean

Returns:

  • (Boolean)


635
636
637
# File 'lib/kettle/jem.rb', line 635

def non_interactive?
  !interactive?
end

#resolve(id:, category:, default_action:, file: nil, severity: :advisory, diagnostics: []) ⇒ Object

Raises:



639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
# File 'lib/kettle/jem.rb', line 639

def resolve(id:, category:, default_action:, file: nil, severity: :advisory, diagnostics: [])
  action = normalize_action(default_action)
  severity_value = normalize_severity(severity)
  raise Error, "No safe default decision for #{id}" if action.nil? && severity_value == "fatal"
  promptable = interactive? && !action.nil? && severity_value != "fatal"
  answer = promptable ? prompt_answers[id.to_s] : nil
  selected_action = answer || action
  selected_source = if answer
    "interactive_answer"
  elsif promptable
    "interactive_default"
  else
    "default"
  end
  decision_diagnostics = Array(diagnostics).compact.map(&:to_s)
  prompt = nil
  if promptable
    prompt = {
      id: id.to_s,
      category: category.to_s,
      file: file&.to_s,
      message: "Apply #{category.to_s.tr("_", " ")} for #{file || id}?",
      default_action: action,
      choices: DECISION_ACTIONS
    }.compact
    decision_diagnostics << if answer
      "Interactive prompt answer supplied through the shared decision policy input contract."
    else
      "Interactive prompt transport is active; selected the configured default pending an external response."
    end
  end

  DecisionEvaluation.new(
    id: id.to_s,
    category: category.to_s,
    file: file&.to_s,
    default_action: action,
    selected_action: selected_action,
    source: selected_source,
    severity: severity_value,
    blocking: severity_value == "fatal",
    diagnostics: decision_diagnostics,
    prompt_required: promptable,
    prompt: prompt
  )
end

#to_hObject



686
687
688
689
690
691
692
693
694
695
696
697
# File 'lib/kettle/jem.rb', line 686

def to_h
  {
    mode: mode.to_s,
    non_interactive: non_interactive?,
    accept: accept?,
    interactive: interactive?,
    failure_mode: failure_mode,
    require_clean: require_clean,
    input_source: input_source,
    prompt_answers: prompt_answers.empty? ? nil : prompt_answers
  }.compact
end