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.



637
638
639
640
641
642
643
# File 'lib/kettle/jem.rb', line 637

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.



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

def failure_mode
  @failure_mode
end

#input_sourceObject (readonly)

Returns the value of attribute input_source.



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

def input_source
  @input_source
end

#modeObject (readonly)

Returns the value of attribute mode.



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

def mode
  @mode
end

#prompt_answersObject (readonly)

Returns the value of attribute prompt_answers.



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

def prompt_answers
  @prompt_answers
end

#require_cleanObject (readonly)

Returns the value of attribute require_clean.



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

def require_clean
  @require_clean
end

Class Method Details

.falsey?(value) ⇒ Boolean

Returns:

  • (Boolean)


613
614
615
# File 'lib/kettle/jem.rb', line 613

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

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



588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
# File 'lib/kettle/jem.rb', line 588

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



626
627
628
629
630
631
632
633
634
635
# File 'lib/kettle/jem.rb', line 626

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



605
606
607
# File 'lib/kettle/jem.rb', line 605

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

.truthy?(value) ⇒ Boolean

Returns:

  • (Boolean)


609
610
611
# File 'lib/kettle/jem.rb', line 609

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

.value_to_boolean(value) ⇒ Object



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

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)


645
646
647
# File 'lib/kettle/jem.rb', line 645

def accept?
  mode == :accept
end

#interactive?Boolean

Returns:

  • (Boolean)


649
650
651
# File 'lib/kettle/jem.rb', line 649

def interactive?
  mode == :interactive
end

#non_interactive?Boolean

Returns:

  • (Boolean)


653
654
655
# File 'lib/kettle/jem.rb', line 653

def non_interactive?
  !interactive?
end

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

Raises:



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
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
# File 'lib/kettle/jem.rb', line 657

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



704
705
706
707
708
709
710
711
712
713
714
715
# File 'lib/kettle/jem.rb', line 704

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