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.



657
658
659
660
661
662
663
# File 'lib/kettle/jem.rb', line 657

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_mode ⇒ Object (readonly)

Returns the value of attribute failure_mode.



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

def failure_mode
  @failure_mode
end

#input_source ⇒ Object (readonly)

Returns the value of attribute input_source.



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

def input_source
  @input_source
end

#mode ⇒ Object (readonly)

Returns the value of attribute mode.



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

def mode
  @mode
end

#prompt_answers ⇒ Object (readonly)

Returns the value of attribute prompt_answers.



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

def prompt_answers
  @prompt_answers
end

#require_clean ⇒ Object (readonly)

Returns the value of attribute require_clean.



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

def require_clean
  @require_clean
end

Class Method Details

.falsey?(value) ⇒ Boolean

Returns:

  • (Boolean)


633
634
635
# File 'lib/kettle/jem.rb', line 633

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

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



608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
# File 'lib/kettle/jem.rb', line 608

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



646
647
648
649
650
651
652
653
654
655
# File 'lib/kettle/jem.rb', line 646

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



625
626
627
# File 'lib/kettle/jem.rb', line 625

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

.truthy?(value) ⇒ Boolean

Returns:

  • (Boolean)


629
630
631
# File 'lib/kettle/jem.rb', line 629

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

.value_to_boolean(value) ⇒ Object



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

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)


665
666
667
# File 'lib/kettle/jem.rb', line 665

def accept?
  mode == :accept
end

#interactive? ⇒ Boolean

Returns:

  • (Boolean)


669
670
671
# File 'lib/kettle/jem.rb', line 669

def interactive?
  mode == :interactive
end

#non_interactive? ⇒ Boolean

Returns:

  • (Boolean)


673
674
675
# File 'lib/kettle/jem.rb', line 673

def non_interactive?
  !interactive?
end

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

Raises:



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
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
# File 'lib/kettle/jem.rb', line 677

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_h ⇒ Object



724
725
726
727
728
729
730
731
732
733
734
735
# File 'lib/kettle/jem.rb', line 724

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