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.



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

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.



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

def failure_mode
  @failure_mode
end

#input_sourceObject (readonly)

Returns the value of attribute input_source.



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

def input_source
  @input_source
end

#modeObject (readonly)

Returns the value of attribute mode.



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

def mode
  @mode
end

#prompt_answersObject (readonly)

Returns the value of attribute prompt_answers.



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

def prompt_answers
  @prompt_answers
end

#require_cleanObject (readonly)

Returns the value of attribute require_clean.



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

def require_clean
  @require_clean
end

Class Method Details

.falsey?(value) ⇒ Boolean

Returns:

  • (Boolean)


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

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

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



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

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



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

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



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

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

.truthy?(value) ⇒ Boolean

Returns:

  • (Boolean)


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

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

.value_to_boolean(value) ⇒ Object



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

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)


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

def accept?
  mode == :accept
end

#interactive?Boolean

Returns:

  • (Boolean)


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

def interactive?
  mode == :interactive
end

#non_interactive?Boolean

Returns:

  • (Boolean)


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

def non_interactive?
  !interactive?
end

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

Raises:



638
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
# File 'lib/kettle/jem.rb', line 638

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



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

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