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' => false,
    'include_default_message' => true,
    'include_param_documentation' => 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' => ['spec']
    }
  },
  'rbs' => {
    'enabled' => false,
    'collection' => 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
# File 'lib/docscribe/config/template.rb', line 12

def self.default_yaml
  <<~YAML
    ---
    # Docscribe configuration file
    #
    # Docscribe works without this file — create it only for customization.
    #
    # Quick start:
    #   bundle exec docscribe lib          # check what would change
    #   bundle exec docscribe -a lib       # apply safe updates
    #   bundle exec docscribe -A lib       # rebuild all doc blocks

    emit:
      # What to include in generated documentation
      header: false                       # +MyClass#foo+ -> ReturnType
      param_tags: true                    # @param tags
      return_tag: true                    # @return tag
      visibility_tags: true               # @private / @protected
      raise_tags: true                    # @raise tags
      rescue_conditional_returns: true    # @return [Type] if Error
      attributes: false                   # @!attribute for attr_*

      # Placeholder text for generated docs
      include_default_message: true       # "Method documentation."
      include_param_documentation: true   # "Param documentation."

    doc:
      # Default text and formatting
      default_message: "Method documentation."
      param_documentation: "Param documentation."
      param_tag_style: "type_name"        # "type_name" or "name_type"
      sort_tags: true
      tag_order: ["todo", "note", "api", "private", "protected", "param", "option", "yieldparam", "raise", "return"]

    inference:
      # Type inference behavior
      fallback_type: "Object"             # when uncertain
      nil_as_optional: true               # String | nil => String?
      treat_options_keyword_as_hash: true # options: keyword => Hash

    filter:
      # Which methods and files to process
      # Method format: "Container#method" (instance) or "Container.method" (class)
      # Supports globs ("*#initialize") and regex ("/^MyApp::.*$/")
      include: []
      exclude: []
      visibilities: ["public", "protected", "private"]
      scopes: ["instance", "class"]

      files:
        # File paths relative to project root (globs or /regex/)
        include: []
        exclude: ["spec"]

    methods:
      # Override defaults per scope and visibility.
      # Empty {} means "use values from `doc` section".
      #
      # Example:
      #   instance:
      #     public:
      #       default_message: "Public API."
      #     private:
      #       return_tag: false
      instance:
        public: {}
        protected: {}
        private: {}
      class:
        public: {}
        protected: {}
        private: {}

    rbs:
      # Use RBS signatures for better types (requires `gem "rbs"`)
      enabled: false
      sig_dirs: ["sig"]
      collapse_generics: false            # Hash<Symbol, String> => Hash
      collection: false                   # auto-discover from rbs_collection.lock.yaml

    sorbet:
      # Use Sorbet inline sigs and RBI files for better types
      enabled: false
      rbi_dirs: ["sorbet/rbi", "rbi"]
      collapse_generics: false

    plugins:
      # Load custom plugins
      # Example:
      #   require:
      #     - ./docscribe_plugins
      #     - docscribe-rails-associations
      require: []
  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

#core_rbs_providerObject

Method documentation.

Returns:

  • (Object)

Raises:

  • (LoadError)


38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/docscribe/config/rbs.rb', line 38

def core_rbs_provider
  return nil unless ruby_supports_rbs?

  @core_rbs_provider ||= begin
    require 'docscribe/types/rbs/provider'
    Docscribe::Types::RBS::Provider.new(
      sig_dirs: [],
      collapse_generics: false
    )
  rescue LoadError
    nil
  end
end

#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_enabled?Boolean

Whether RBS integration is enabled.

Returns:

  • (Boolean)


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

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
25
# File 'lib/docscribe/config/rbs.rb', line 12

def rbs_provider
  return nil unless rbs_enabled?
  return nil unless ruby_supports_rbs?

  @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

#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