/
introspection.rb
152 lines (136 loc) · 5.51 KB
/
introspection.rb
1
2
3
4
5
6
7
8
9
10
11
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
# frozen_string_literal: true
# rubocop:todo all
module Mongoid
module Config
# This module provides a way to inspect not only the defined configuration
# settings and their defaults (which are available via
# `Mongoid::Config.settings`), but also the documentation about them. It
# does this by scraping the `mongoid/config.rb` file with a regular
# expression to match comments with options.
#
# @api private
module Introspection
extend self
# A helper class to represent an individual option, its name, its
# default value, and the comment that documents it.
class Option
# The name of this option.
#
# @return [ String ] The name of the option
attr_reader :name
# The default value of this option.
#
# @return [ Object ] The default value of the option, typically a
# String, Symbol, nil, true, or false.
attr_reader :default
# The comment that describes this option, as scraped from
# mongoid/config.rb.
#
# @return [ String ] The (possibly multi-line) comment. Each line is
# prefixed with the Ruby comment character ("#").
attr_reader :comment
# Instantiate an option from an array of Regex captures.
#
# @param [ Array<String> ] captures The array with the Regex captures
# to use to instantiate the option. The element at index 1 must be
# the comment, at index 2 must be the name, and at index 3 must be
# the default value.
#
# @return [ Option ] The newly instantiated Option object.
def self.from_captures(captures)
new(captures[2], captures[3], captures[1])
end
# Create a new Option instance with the given name, default value,
# and comment.
#
# @param [ String ] name The option's name.
# @param [ String ] default The option's default value, as a String
# representing the actual Ruby value.
# @param [ String ] comment The multi-line comment describing the
# option.
def initialize(name, default, comment)
@name, @default, @comment = name, default, unindent(comment)
end
# Indent the comment by the requested amount, optionally indenting the
# first line, as well.
#
# param [ Integer ] indent The number of spaces to indent each line
# (Default: 2)
# param [ true | false ] indent_first_line Whether or not to indent
# the first line of the comment (Default: false)
#
# @return [ String ] the reformatted comment
def indented_comment(indent: 2, indent_first_line: false)
comment.gsub(/^/, " " * indent).tap do |result|
result.strip! unless indent_first_line
end
end
# Reports whether or not the text "(Deprecated)" is present in the
# option's comment.
#
# @return [ true | false ] whether the option is deprecated or not.
def deprecated?
comment.include?("(Deprecated)")
end
# Compare self with the given option.
#
# @return [ true | false ] If name, default, and comment are all the
# same, return true. Otherwise, false.
def ==(option)
name == option.name &&
default == option.default &&
comment == option.comment
end
private
# Removes any existing whitespace from the beginning of each line in
# the text.
#
# @param [ String ] text The text to unindent.
#
# @return [ String ] the unindented text.
def unindent(text)
text.strip.gsub(/^\s+/, "")
end
end
# A regular expression that looks for option declarations of the format:
#
# # one or more lines of comments,
# # followed immediately by an option
# # declaration with a default value:
# option :option_name, default: "something"
#
# The regex produces three captures:
#
# 1: the (potentially multiline) comment
# 2: the option's name
# 3: the option's default value
OPTION_PATTERN = %r{
(
((?:^\s*\#.*\n)+) # match one or more lines of comments
^\s+option\s+ # followed immediately by a line declaring an option
:(\w+),\s+ # match the option's name, followed by a comma
default:\s+(.*?) # match the default value for the option
(?:,.*?)? # skip any other configuration
\n) # end with a newline
}x
# The full path to the source file of the Mongoid::Config module.
CONFIG_RB_PATH = File.absolute_path(File.join(
File.dirname(__FILE__), "../config.rb"))
# Extracts the available configuration options from the Mongoid::Config
# source file, and returns them as an array of hashes.
#
# @param [ true | false ] include_deprecated Whether deprecated options
# should be included in the list. (Default: false)
#
# @return [ Array<Introspection::Option>> ] the array of option objects
# representing each defined option, in alphabetical order by name.
def options(include_deprecated: false)
src = File.read(CONFIG_RB_PATH)
src.scan(OPTION_PATTERN)
.map { |opt| Option.from_captures(opt) }
.reject { |opt| !include_deprecated && opt.deprecated? }
.sort_by { |opt| opt.name }
end
end
end
end