Class: Docscribe::Config

Inherits:
Object
  • Object
show all
Defined in:
lib/docscribe/config.rb,
lib/docscribe/config/rbs.rb,
lib/docscribe/config/emit.rb,
lib/docscribe/config/utils.rb,
lib/docscribe/config/loader.rb,
lib/docscribe/config/plugin.rb,
lib/docscribe/config/sorbet.rb,
lib/docscribe/config/sorting.rb,
lib/docscribe/config/defaults.rb,
lib/docscribe/config/template.rb,
lib/docscribe/config/filtering.rb

Constant Summary collapse

DEFAULT =

Default configuration values used when no ‘docscribe.yml` is present or when specific keys are missing from user config.

These defaults define:

  • which documentation tags are emitted

  • default generated text

  • type inference behavior

  • method / file filtering

  • optional RBS integration

  • optional Sorbet integration

{
  'emit' => {
    'header' => true,
    'param_tags' => true,
    'return_tag' => true,
    'visibility_tags' => true,
    'raise_tags' => true,
    'rescue_conditional_returns' => true,
    'attributes' => false
  },
  'doc' => {
    'default_message' => 'Method documentation.',
    'param_tag_style' => 'type_name',
    'param_documentation' => 'Param documentation.',
    'sort_tags' => true,
    'tag_order' => %w[todo note api private protected param option yieldparam raise return]
  },
  'methods' => {
    'instance' => {
      'public' => {},
      'protected' => {},
      'private' => {}
    },
    'class' => {
      'public' => {},
      'protected' => {},
      'private' => {}
    }
  },
  'inference' => {
    'fallback_type' => 'Object',
    'nil_as_optional' => true,
    'treat_options_keyword_as_hash' => true
  },
  'filter' => {
    'visibilities' => %w[public protected private],
    'scopes' => %w[instance class],
    'include' => [],
    'exclude' => [],
    'files' => {
      'include' => [],
      'exclude' => []
    }
  },
  'rbs' => {
    'enabled' => false,
    'sig_dirs' => ['sig'],
    'collapse_generics' => false
  },
  'sorbet' => {
    'enabled' => false,
    'rbi_dirs' => ['sorbet/rbi', 'rbi'],
    'collapse_generics' => false
  },
  'plugins' => {
    'require' => []
  }
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(raw = {}) ⇒ void

Create a configuration object from a raw config hash.

Missing keys are filled from DEFAULT via deep merge.

Parameters:

  • raw (Hash, nil) (defaults to: {})

    user-provided config hash



21
22
23
# File 'lib/docscribe/config.rb', line 21

def initialize(raw = {})
  @raw = deep_merge(DEFAULT, raw || {})
end

Instance Attribute Details

#rawObject (readonly)

Raw config hash after deep-merging user config with defaults.



13
14
15
# File 'lib/docscribe/config.rb', line 13

def raw
  @raw
end

Class Method Details

.default_yamlString

Return the default YAML template used by ‘docscribe init`.

The template documents the most common CLI workflows and all supported configuration sections with comments.

Returns:

  • (String)

See Also:



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
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
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/docscribe/config/template.rb', line 12

def self.default_yaml
  <<~YAML
    ---
    # Docscribe configuration file
    #
    # Inspect what safe doc updates would be applied:
    #   bundle exec docscribe lib
    #
    # Apply safe doc updates:
    #   bundle exec docscribe -a lib
    #
    # Apply aggressive doc updates (rebuild existing doc blocks):
    #   bundle exec docscribe -A lib
    #

    emit:
      # Emit the header line:
      #
      #   +MyClass#my_method+ -> ReturnType
      header: false

      # Whether to include the default placeholder line:
      #   # Method documentation.
      include_default_message: true

      # Whether to append placeholder text to generated @param tags:
      #   # @param [String] name Param documentation.
      include_param_documentation: true

      # Emit @param tags.
      param_tags: true

      # Emit @return tag (can be overridden per scope/visibility under methods:).
      return_tag: true

      # Emit @private / @protected tags based on Ruby visibility context.
      visibility_tags: true

      # Emit @raise tags inferred from rescue clauses / raise/fail calls.
      raise_tags: true

      # Emit conditional rescue return tags:
      #
      #   @return [String] if FooError, BarError
      rescue_conditional_returns: true

      # Generate @!attribute docs for attr_reader/attr_writer/attr_accessor.
      attributes: false

    doc:
      # Default text inserted into each generated doc block.
      default_message: "Method documentation."

      # Default text appended to generated @param tags.
      param_documentation: "Param documentation."

      # Style for generated @param tags:
      # - type_name => @param [Type] name
      # - name_type => @param name [Type]
      param_tag_style: "type_name"

      # Sort generated / merged tags in safe mode when possible.
      sort_tags: true

      # Tag order used when sorting contiguous tag runs.
      tag_order: ["todo", "note", "api", "private", "protected", "param", "option", "yieldparam", "raise", "return"]

    methods:
      # Per-scope / per-visibility overrides.
      #
      # Example:
      # methods:
      #   instance:
      #     public:
      #       default_message: "Public API."
      #       return_tag: true
      instance:
        public: {}
        protected: {}
        private: {}

      class:
        public: {}
        protected: {}
        private: {}

    inference:
      # Type used when inference is uncertain.
      fallback_type: "Object"

      # Whether nil unions become optional types (for example String | nil => String?).
      nil_as_optional: true

      # Special-case: treat keyword arg named options/options: as a Hash.
      treat_options_keyword_as_hash: true

    filter:
      # Filter which methods Docscribe touches.
      #
      # Method id format:
      #   instance: "MyModule::MyClass#instance_method"
      #   class:    "MyModule::MyClass.class_method"
      #
      # Patterns:
      # - glob: "*#initialize", "MyApp::*#*"
      # - regex: "/^MyApp::.*#(foo|bar)$/"
      #
      # Semantics:
      # - scopes / visibilities act as allow-lists
      # - exclude wins
      # - if include is empty => include everything (subject to allow-lists)
      visibilities: ["public", "protected", "private"]
      scopes: ["instance", "class"]
      include: []
      exclude: []

      files:
        # Filter which files Docscribe processes (paths are matched relative
        # to the project root).
        #
        # Tips:
        # - Use directory shorthand to exclude a whole directory:
        #     exclude: ["spec"]
        # - Or use globs:
        #     exclude: ["spec/**/*.rb", "vendor/**/*.rb"]
        include: []
        exclude: ["spec"]

    plugins:
      # Load custom plugins by path or gem name.
      #
      # Each entry is passed to `require`. Registration happens inside
      # the required file via Docscribe::Plugin::Registry.register.
      #
      # Example:
      #   require:
      #     - ./docscribe_plugins
      #     - docscribe-rails-associations
      require: []

    rbs:
      # Optional: use RBS signatures to improve @param / @return types.
      #
      # CLI equivalent:
      # bundle exec docscribe -a --rbs --sig-dir sig lib
      #
      # Under Bundler, you may need `gem "rbs"` in your Gemfile (or a
      # Gemfile that includes it), otherwise `require "rbs"` may fail and
      # Docscribe will fall back to inference.
      enabled: false

      # Signature directories (repeatable via --sig-dir).
      sig_dirs: ["sig"]

      # If true, simplify generic types:
      # - Hash<Symbol, String> => Hash
      # - Array<Integer>       => Array
      collapse_generics: false
      # Auto-discover RBS collection from rbs_collection.lock.yaml.
      # Equivalent to --rbs-collection CLI flag.
      # Requires `bundle exec rbs collection install` to have been run.
      #
      collection: false

    sorbet:
      # Optional: use Sorbet signatures from inline `sig` declarations and
      # RBI files to improve @param / @return types.
      #
      # CLI equivalent:
      # bundle exec docscribe -a --sorbet --rbi-dir sorbet/rbi lib
      #
      # Sorbet resolution order is:
      # 1. inline `sig` in the current source file
      # 2. RBI files
      # 3. RBS
      # 4. AST inference
      enabled: false

      # RBI directories scanned recursively for `.rbi` files
      # (repeatable via --rbi-dir).
      rbi_dirs: ["sorbet/rbi", "rbi"]

      # If true, simplify generic types:
      # - Hash<Symbol, String> => Hash
      # - Array<Integer>       => Array
      collapse_generics: false
  YAML
end

.load(path = nil) ⇒ Docscribe::Config

Load Docscribe configuration from YAML.

Resolution order:

  • explicit ‘path`, if it exists

  • ‘docscribe.yml` in the current directory, if present

  • otherwise defaults only

Parameters:

  • path (String, nil) (defaults to: nil)

    optional config path

Returns:



14
15
16
17
18
19
20
21
22
# File 'lib/docscribe/config/loader.rb', line 14

def self.load(path = nil)
  raw = {}
  if path && File.file?(path)
    raw = safe_load_file_compat(path)
  elsif File.file?('docscribe.yml')
    raw = safe_load_file_compat('docscribe.yml')
  end
  new(raw)
end

.safe_load_compat(yaml, filename: nil) ⇒ Hash

Safely load YAML from a string across Psych API versions.

Parameters:

  • yaml (String)

    YAML document

  • filename (String, nil) (defaults to: nil)

    optional filename for diagnostics

Returns:

  • (Hash)

Raises:

  • (ArgumentError)


46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/docscribe/config/loader.rb', line 46

def self.safe_load_compat(yaml, filename: nil)
  Psych.safe_load(
    yaml,
    permitted_classes: [],
    permitted_symbols: [],
    aliases: true,
    filename: filename
  )
rescue ArgumentError
  # Older Psych signature uses positional args
  Psych.safe_load(yaml, [], [], true, filename)
end

.safe_load_file_compat(path) ⇒ Hash

Safely load YAML from a file across Ruby/Psych versions.

Uses ‘YAML.safe_load_file` when available, otherwise falls back to reading the file and calling safe_load_compat.

Parameters:

  • path (String)

    file path

Returns:

  • (Hash)


31
32
33
34
35
36
37
38
# File 'lib/docscribe/config/loader.rb', line 31

def self.safe_load_file_compat(path)
  if YAML.respond_to?(:safe_load_file)
    YAML.safe_load_file(path, permitted_classes: [], permitted_symbols: [], aliases: true) || {}
  else
    yaml = File.open(path, 'r:bom|utf-8', &:read)
    safe_load_compat(yaml, filename: path) || {}
  end
end

Instance Method Details

#default_message(scope, visibility) ⇒ String

Default text inserted into generated doc blocks, taking per-scope and per-visibility overrides into account.

Parameters:

  • scope (Symbol)

    :instance or :class

  • visibility (Symbol)

    :public, :protected, or :private

Returns:

  • (String)


70
71
72
73
74
75
76
77
78
79
# File 'lib/docscribe/config/emit.rb', line 70

def default_message(scope, visibility)
  method_override_str(
    scope,
    visibility,
    'default_message',
    default: raw.dig('doc', 'default_message') ||
             DEFAULT.dig('doc', 'default_message') ||
             'Method documentation.'
  )
end

#emit_attributes?Boolean

Whether to emit YARD ‘@!attribute` docs for `attr_*` macros.

Returns:

  • (Boolean)


45
46
47
# File 'lib/docscribe/config/emit.rb', line 45

def emit_attributes?
  fetch_bool(%w[emit attributes], false)
end

#emit_header?Boolean

Whether to emit method header lines such as:

# +MyClass#foo+ -> Integer

Returns:

  • (Boolean)


9
10
11
# File 'lib/docscribe/config/emit.rb', line 9

def emit_header?
  fetch_bool(%w[emit header], true)
end

#emit_param_tags?Boolean

Whether to emit ‘@param` tags.

Returns:

  • (Boolean)


16
17
18
# File 'lib/docscribe/config/emit.rb', line 16

def emit_param_tags?
  fetch_bool(%w[emit param_tags], true)
end

#emit_raise_tags?Boolean

Whether to emit inferred ‘@raise` tags.

Returns:

  • (Boolean)


30
31
32
# File 'lib/docscribe/config/emit.rb', line 30

def emit_raise_tags?
  fetch_bool(%w[emit raise_tags], true)
end

#emit_rescue_conditional_returns?Boolean

Whether to emit conditional rescue-return tags like:

# @return [String] if FooError

Returns:

  • (Boolean)


38
39
40
# File 'lib/docscribe/config/emit.rb', line 38

def emit_rescue_conditional_returns?
  fetch_bool(%w[emit rescue_conditional_returns], true)
end

#emit_return_tag?(scope, visibility) ⇒ Boolean

Whether to emit the ‘@return` tag for a method, taking per-scope and per-visibility overrides into account.

Parameters:

  • scope (Symbol)

    :instance or :class

  • visibility (Symbol)

    :public, :protected, or :private

Returns:

  • (Boolean)


55
56
57
58
59
60
61
62
# File 'lib/docscribe/config/emit.rb', line 55

def emit_return_tag?(scope, visibility)
  method_override_bool(
    scope,
    visibility,
    'return_tag',
    default: fetch_bool(%w[emit return_tag], true)
  )
end

#emit_visibility_tags?Boolean

Whether to emit visibility tags such as ‘@private` and `@protected`.

Returns:

  • (Boolean)


23
24
25
# File 'lib/docscribe/config/emit.rb', line 23

def emit_visibility_tags?
  fetch_bool(%w[emit visibility_tags], true)
end

#fallback_typeString

Fallback type used when inference cannot determine a more specific type.

Returns:

  • (String)


84
85
86
87
88
# File 'lib/docscribe/config/emit.rb', line 84

def fallback_type
  raw.dig('inference', 'fallback_type') ||
    DEFAULT.dig('inference', 'fallback_type') ||
    'Object'
end

#include_default_message?Boolean

Whether to include the default placeholder line:

# Method documentation.

Returns:

  • (Boolean)


130
131
132
# File 'lib/docscribe/config/emit.rb', line 130

def include_default_message?
  fetch_bool(%w[emit include_default_message], true)
end

#include_param_documentation?Boolean

Whether to append placeholder text to generated @param tags:

# @param [String] name Param documentation.

Returns:

  • (Boolean)


138
139
140
# File 'lib/docscribe/config/emit.rb', line 138

def include_param_documentation?
  fetch_bool(%w[emit include_param_documentation], true)
end

#load_plugins!void

This method returns an undefined value.

Load and register plugins declared under ‘plugins.require` in config.

Each entry is expanded relative to the current working directory and passed to ‘require`. Registration is expected to happen inside the required file via Plugin::Registry.register.

Loading failures are non-fatal: a warning is printed and the run continues without the plugin.

Raises:

  • (LoadError)


16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/docscribe/config/plugin.rb', line 16

def load_plugins!
  paths = Array(raw.dig('plugins', 'require')).compact
  return if paths.empty?

  require 'docscribe/plugin'

  paths.each do |path|
    require File.expand_path(path)
  rescue LoadError => e
    warn "Docscribe: could not load plugin #{path.inspect}: #{e.message}"
  end
end

#nil_as_optional?Boolean

Whether unions involving nil should be rendered as optional types.

For example, ‘String, nil` may become `String?` depending on formatter behavior.

Returns:

  • (Boolean)


96
97
98
# File 'lib/docscribe/config/emit.rb', line 96

def nil_as_optional?
  fetch_bool(%w[inference nil_as_optional], true)
end

#param_documentationString

Default generated parameter description text.

Returns:

  • (String)


122
123
124
# File 'lib/docscribe/config/emit.rb', line 122

def param_documentation
  raw.dig('doc', 'param_documentation') || DEFAULT.dig('doc', 'param_documentation')
end

#param_tag_styleString

Param tag syntax style.

Supported values:

  • ‘“type_name”` => `@param [String] name`

  • ‘“name_type”` => `@param name [String]`

Returns:

  • (String)


115
116
117
# File 'lib/docscribe/config/emit.rb', line 115

def param_tag_style
  raw.dig('doc', 'param_tag_style') || DEFAULT.dig('doc', 'param_tag_style')
end

#process_file?(path) ⇒ Boolean

Decide whether a file path should be processed based on ‘filter.files`.

File paths are matched relative to the current working directory when possible. Exclude rules win. If no include rules are configured, files are included by default.

Parameters:

  • path (String)

    file path to test

Returns:

  • (Boolean)

Raises:

  • (StandardError)


13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/docscribe/config/filtering.rb', line 13

def process_file?(path)
  files = raw.dig('filter', 'files') || {}
  include_patterns = normalize_file_patterns(files['include'])
  exclude_patterns = normalize_file_patterns(files['exclude'])

  rel = begin
    Pathname.new(path).expand_path.relative_path_from(Pathname.pwd).cleanpath.to_s
  rescue StandardError
    path
  end

  return false if file_matches_any?(exclude_patterns, rel)
  return true if include_patterns.empty?

  file_matches_any?(include_patterns, rel)
end

#process_method?(container:, scope:, visibility:, name:) ⇒ Boolean

Decide whether a method should be processed based on configured method filters.

Method IDs are normalized as:

  • instance method => ‘MyModule::MyClass#foo`

  • class method => ‘MyModule::MyClass.foo`

Exclude rules win. If no include rules are configured, methods are included by default subject to scope and visibility allow-lists.

Parameters:

  • container (String)

    enclosing class/module name

  • scope (Symbol)

    :instance or :class

  • visibility (Symbol)

    :public, :protected, or :private

  • name (String, Symbol)

    method name

Returns:

  • (Boolean)


44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/docscribe/config/filtering.rb', line 44

def process_method?(container:, scope:, visibility:, name:)
  return false unless filter_scopes.include?(scope.to_s)
  return false unless filter_visibilities.include?(visibility.to_s)

  method_id = "#{container}#{scope == :instance ? '#' : '.'}#{name}"

  return false if matches_any?(filter_exclude_patterns, method_id)

  inc = filter_include_patterns
  return true if inc.empty?

  matches_any?(inc, method_id)
end

#rbs_collapse_generics?Boolean

Whether generic RBS types should be collapsed to simpler container names.

Examples:

  • ‘Hash<Symbol, String>` => `Hash`

  • ‘Array<Integer>` => `Array`

Returns:

  • (Boolean)


47
48
49
# File 'lib/docscribe/config/rbs.rb', line 47

def rbs_collapse_generics?
  fetch_bool(%w[rbs collapse_generics], false)
end

#rbs_enabled?Boolean

Whether RBS integration is enabled.

Returns:

  • (Boolean)


29
30
31
# File 'lib/docscribe/config/rbs.rb', line 29

def rbs_enabled?
  fetch_bool(%w[rbs enabled], false)
end

#rbs_providerDocscribe::Types::RBS::Provider?

Return a memoized RBS provider if RBS integration is enabled and available.

If RBS cannot be loaded, this returns nil and Docscribe falls back to inference.

Returns:

Raises:

  • (LoadError)


12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/docscribe/config/rbs.rb', line 12

def rbs_provider
  return nil unless rbs_enabled?

  @rbs_provider ||= begin
    require 'docscribe/types/rbs/provider'
    Docscribe::Types::RBS::Provider.new(
      sig_dirs: rbs_sig_dirs,
      collapse_generics: rbs_collapse_generics?
    )
  rescue LoadError
    nil
  end
end

#rbs_sig_dirsArray<String>

Signature directories used by the RBS provider.

Returns:

  • (Array<String>)


36
37
38
# File 'lib/docscribe/config/rbs.rb', line 36

def rbs_sig_dirs
  Array(raw.dig('rbs', 'sig_dirs') || DEFAULT.dig('rbs', 'sig_dirs')).map(&:to_s)
end

#signature_provider_for(source:, file:) ⇒ Docscribe::Types::ProviderChain?

Build the effective external signature provider chain for a given source.

Provider precedence is:

  1. inline Sorbet signatures from the current source

  2. Sorbet RBI files

  3. RBS files

Returns nil when no external type provider is enabled or available.

Parameters:

  • source (String)

    Ruby source being rewritten

  • file (String)

    source name for diagnostics

Returns:

Raises:

  • (LoadError)


18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/docscribe/config/sorbet.rb', line 18

def signature_provider_for(source:, file:)
  providers = []

  if sorbet_enabled?
    begin
      require 'docscribe/types/sorbet/source_provider'
      providers << Docscribe::Types::Sorbet::SourceProvider.new(
        source: source,
        file: file,
        collapse_generics: sorbet_collapse_generics?
      )
    rescue LoadError
      # Sorbet support is optional; fall back quietly.
    end

    providers << sorbet_rbi_provider
  end

  providers << rbs_provider if rbs_enabled?

  providers = providers.compact
  return nil if providers.empty?

  require 'docscribe/types/provider_chain'
  Docscribe::Types::ProviderChain.new(*providers)
end

#sorbet_collapse_generics?Boolean

Whether generic Sorbet/RBI container types should be simplified.

Falls back to the RBS ‘collapse_generics` setting when Sorbet-specific config is not present.

Returns:

  • (Boolean)


83
84
85
# File 'lib/docscribe/config/sorbet.rb', line 83

def sorbet_collapse_generics?
  fetch_bool(%w[sorbet collapse_generics], rbs_collapse_generics?)
end

#sorbet_enabled?Boolean

Whether Sorbet support is enabled in config.

Returns:

  • (Boolean)


66
67
68
# File 'lib/docscribe/config/sorbet.rb', line 66

def sorbet_enabled?
  fetch_bool(%w[sorbet enabled], false)
end

#sorbet_rbi_dirsArray<String>

RBI directories searched by the Sorbet provider.

Returns:

  • (Array<String>)


73
74
75
# File 'lib/docscribe/config/sorbet.rb', line 73

def sorbet_rbi_dirs
  Array(raw.dig('sorbet', 'rbi_dirs') || DEFAULT.dig('sorbet', 'rbi_dirs')).map(&:to_s)
end

#sorbet_rbi_providerDocscribe::Types::Sorbet::RBIProvider?

Return a memoized Sorbet RBI provider if Sorbet integration is enabled.

Returns:

Raises:

  • (LoadError)


49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/docscribe/config/sorbet.rb', line 49

def sorbet_rbi_provider
  return nil unless sorbet_enabled?

  @sorbet_rbi_provider ||= begin
    require 'docscribe/types/sorbet/rbi_provider'
    Docscribe::Types::Sorbet::RBIProvider.new(
      rbi_dirs: sorbet_rbi_dirs,
      collapse_generics: sorbet_collapse_generics?
    )
  rescue LoadError
    nil
  end
end

#sort_tags?Boolean

Whether sortable tag normalization is enabled for doc-like blocks.

Returns:

  • (Boolean)


8
9
10
# File 'lib/docscribe/config/sorting.rb', line 8

def sort_tags?
  raw.dig('doc', 'sort_tags') != false
end

#tag_orderArray<String>

Configured sortable tag order.

Tags are normalized without a leading ‘@`.

Returns:

  • (Array<String>)


17
18
19
20
21
# File 'lib/docscribe/config/sorting.rb', line 17

def tag_order
  Array(raw.dig('doc', 'tag_order') || DEFAULT.dig('doc', 'tag_order')).map do |t|
    t.to_s.sub(/\A@/, '')
  end
end

#treat_options_keyword_as_hash?Boolean

Whether keyword arguments named ‘options` / `options:` should be treated specially as Hash values during inference.

Returns:

  • (Boolean)


104
105
106
# File 'lib/docscribe/config/emit.rb', line 104

def treat_options_keyword_as_hash?
  fetch_bool(%w[inference treat_options_keyword_as_hash], true)
end