Class: Canopus::Settings

Inherits:
Object
  • Object
show all
Defined in:
lib/canopus/settings.rb,
lib/canopus/settings/schema.rb,
sig/canopus.rbs

Defined Under Namespace

Modules: Schema

Constant Summary collapse

DEFAULT_AUTO_PAIRS =
Schema::DEFAULT_AUTO_PAIRS
DEFAULTS =
Schema::DEFAULTS
SCHEMA =
Schema::JSON_SCHEMA
SCHEMA_MODEL =
Schema::KOCHAB_SCHEMA
SCHEMA_METADATA =
SCHEMA_MODEL

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*layers) ⇒ Settings

Returns a new instance of Settings.

Parameters:



37
38
39
40
41
42
43
44
45
46
47
# File 'lib/canopus/settings.rb', line 37

def initialize(*layers)
  fallback = layers.pop.values if layers.last.is_a?(ReloadFallback)
  @values, @errors = DEFAULTS.dup, []
  @layers = layers.compact
  @reload_fallback = fallback
  @initializing = true
  layers.compact.each { |layer| merge!(layer.is_a?(String) ? parse_file(layer) : layer) }
  @initializing = false
  @reload_fallback = nil
  validate!
end

Instance Attribute Details

#errors ⇒ Array[String] (readonly)

Returns the value of attribute errors.

Returns:

  • (Array[String])


18
19
20
# File 'lib/canopus/settings.rb', line 18

def errors
  @errors
end

#layers ⇒ Array[String | values] (readonly)

Returns the value of attribute layers.

Returns:



18
19
20
# File 'lib/canopus/settings.rb', line 18

def layers
  @layers
end

#values ⇒ values (readonly)

Returns the value of attribute values.

Returns:



18
19
20
# File 'lib/canopus/settings.rb', line 18

def values
  @values
end

Class Method Details

.file_diagnostics(path) ⇒ [Kochab::Document, Array[Kochab::Error | Kochab::Diagnostic]]?

Parameters:

  • path (String)

Returns:

  • ([Kochab::Document, Array[Kochab::Error | Kochab::Diagnostic]], nil)


22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/canopus/settings.rb', line 22

def self.file_diagnostics(path)
  return nil unless File.file?(path)

  document = Kochab.parse(File.read(path))
  diagnostics = document.errors.dup
  if document.valid?
    diagnostics.concat(SCHEMA_MODEL.validate(document).reject { |diagnostic| diagnostic.message == "Required value is missing" })
  end
  [document, diagnostics.freeze].freeze
rescue Errno::ENOENT, Errno::EACCES, Errno::EISDIR
  nil
end

.schema ⇒ values

Returns:



19
# File 'lib/canopus/settings.rb', line 19

def self.schema = SCHEMA

.schema_metadata ⇒ Kochab::Schema

Returns:

  • (Kochab::Schema)


21
# File 'lib/canopus/settings.rb', line 21

def self. = SCHEMA_MODEL

.schema_model ⇒ Kochab::Schema

Returns:

  • (Kochab::Schema)


20
# File 'lib/canopus/settings.rb', line 20

def self.schema_model = SCHEMA_MODEL

.user_path ⇒ String

Returns:

  • (String)


34
35
36
# File 'lib/canopus/settings.rb', line 34

def self.user_path
  File.join(ENV["XDG_CONFIG_HOME"] || File.expand_path("~/.config"), "canopus", "settings.jsonc")
end

Instance Method Details

#[](key) ⇒ json

Parameters:

  • key (String, Symbol)

Returns:

  • (json)


48
# File 'lib/canopus/settings.rb', line 48

def [](key) = @values[key.to_s]

#for_language(language) ⇒ instance

Parameters:

  • language (String)

Returns:

  • (instance)


51
# File 'lib/canopus/settings.rb', line 51

def for_language(language) = Settings.new(@values, @values.fetch("languages", {}).fetch(language, {}))

#merge!(layer) ⇒ instance

Parameters:

Returns:

  • (instance)


52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/canopus/settings.rb', line 52

def merge!(layer)
  document = layer.is_a?(Kochab::Document)
  raise Error, "settings must be an object" unless document || layer.is_a?(Hash)
  previous = @values
  @values = document ? merge_document(@values, layer) : merge_hash(@values, layer)
  validate!
  @layers << snapshot_layer(layer) unless @initializing
  self
rescue StandardError
  @values = previous if previous
  raise
end

#paths ⇒ Array[String]

Returns:

  • (Array[String])


49
# File 'lib/canopus/settings.rb', line 49

def paths = @layers.grep(String)

#reload ⇒ instance

Returns:

  • (instance)


50
# File 'lib/canopus/settings.rb', line 50

def reload = self.class.new(*@layers, ReloadFallback.new(values: @values))

#set_file(path, key, value) ⇒ instance

Parameters:

  • path (String)
  • key (String, Array[String])
  • value (json)

Returns:

  • (instance)

Raises:



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/canopus/settings.rb', line 64

def set_file(path, key, value)
  source = File.file?(path) ? File.read(path) : "{}\n"
  doc = Kochab.parse(source)
  raise Error, "cannot edit invalid settings" unless doc.valid?
  key_path = key.is_a?(Array) ? key : [key]
  raise Error, "settings key path must contain strings" unless key_path.all? { |part| part.is_a?(String) && !part.empty? }
  editable_source = source
  (1...key_path.length).each do |length|
    current = Kochab.parse(editable_source)
    parent = key_path[0...length]
    next if current.range_of(parent)

    editable_source = Kochab.apply(editable_source, current.set(parent, {}))
  end
  doc = Kochab.parse(editable_source)
  replacement = Kochab.apply(editable_source, doc.set(key_path, value))
  candidate = Kochab.parse(replacement)
  schema_errors = SCHEMA_MODEL.validate(candidate).reject { |diagnostic| diagnostic.message == "Required value is missing" }
  raise Error, "invalid setting #{key_path.join(".")}: #{schema_errors.first.message}" unless schema_errors.empty?
  Settings.new(candidate)
  FileUtils.mkdir_p(File.dirname(path))
  Tempfile.create([".settings-", ".json"], File.dirname(path)) do |file|
    file.write(replacement)
    file.flush
    file.fsync
    file.close
    raise SaveConflict, "settings changed during save" if File.file?(path) && File.read(path) != source
    File.rename(file.path, path)
  end
end