parlour 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.parlour +5 -0
- data/CHANGELOG.md +10 -0
- data/README.md +28 -2
- data/exe/parlour +68 -2
- data/lib/parlour/detached_rbi_generator.rb +1 -6
- data/lib/parlour/plugin.rb +1 -1
- data/lib/parlour/rbi_generator/constant.rb +11 -2
- data/lib/parlour/rbi_generator/namespace.rb +29 -6
- data/lib/parlour/rbi_generator/parameter.rb +4 -4
- data/lib/parlour/type_loader.rb +25 -12
- data/lib/parlour/type_parser.rb +22 -17
- data/lib/parlour/version.rb +1 -1
- data/rbi/parlour.rbi +893 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f37a6dd2dffd0b59594b4c0ed664cdd27c4ea5bb02d1ca0f944ddff2d6ccda83
|
4
|
+
data.tar.gz: 38b776b79c349f48e672fbb81534d4ab1b751af13ecdab9e60d3f08562f03332
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8da56118b8e784707a054ae7dbdddda63dade826ec19d87a06f468229e8e53ef1669f7b05c4c5be6cc9e75377846e729748b0f31d3400768041079050fb72776
|
7
|
+
data.tar.gz: c7a39a7a91aee7298fbdf3e5d7ebdebc278001317cb365bc7f3970d62fa49c2d9e3f5d7e1eb4c70a386ef0964f14bde2647871ab8c0b6a3a5adad8c774bc08f5
|
data/.parlour
ADDED
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,16 @@ All notable changes to this project will be documented in this file.
|
|
3
3
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
5
5
|
|
6
|
+
## [4.0.0] - 2020-05-23
|
7
|
+
### Added
|
8
|
+
- Parlour now defaults to loading the current project when running its command
|
9
|
+
line tool, allowing it to be used as a "`sig` extractor" when run without
|
10
|
+
plugins! **Breaking if you invoke Parlour from its command line tool** - to
|
11
|
+
revert to the old behaviour of having nothing loaded into the root namespace
|
12
|
+
initially, add `parser: false` to your `.parlour` file.
|
13
|
+
- Generating constants in an eigenclass context (`class << self`) is now
|
14
|
+
supported.
|
15
|
+
|
6
16
|
## [3.0.0] - 2020-05-15
|
7
17
|
### Added
|
8
18
|
- `T::Struct` classes can now be generated and parsed.
|
data/README.md
CHANGED
@@ -13,7 +13,7 @@ key parts:
|
|
13
13
|
RBIs for the same codebase. These are combined automatically as much as
|
14
14
|
possible, but any other conflicts can be resolved manually through prompts.
|
15
15
|
|
16
|
-
- The parser, which can read an RBI and convert it back into a tree of
|
16
|
+
- The parser, which can read an RBI and convert it back into a tree of
|
17
17
|
generator objects.
|
18
18
|
|
19
19
|
## Why should I use this?
|
@@ -26,7 +26,10 @@ key parts:
|
|
26
26
|
RBI output file.
|
27
27
|
|
28
28
|
- You can **effortlessly build tools which need to access types within an RBI**;
|
29
|
-
no need to write your own parser!
|
29
|
+
no need to write your own parser!
|
30
|
+
|
31
|
+
- You can **generate RBI to ship with your gem** for consuming projects to use
|
32
|
+
([see "RBIs within gems" in Sorbet's docs](https://sorbet.org/docs/rbi#rbis-within-gems)).
|
30
33
|
|
31
34
|
Please [**read the wiki**](https://github.com/AaronC81/parlour/wiki) to get
|
32
35
|
started!
|
@@ -174,6 +177,29 @@ Parlour::TypeLoader.load_project('root/of/the/project')
|
|
174
177
|
The structure of the returned object trees is identical to those you would
|
175
178
|
create when generating an RBI, built of instances of `RbiObject` subclasses.
|
176
179
|
|
180
|
+
## Generating RBI for a Gem
|
181
|
+
|
182
|
+
Include `parlour` as a development_dependency in your `.gemspec`:
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
spec.add_development_dependency 'parlour'
|
186
|
+
```
|
187
|
+
|
188
|
+
Run Parlour from the command line:
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
bundle exec parlour
|
192
|
+
```
|
193
|
+
|
194
|
+
Parlour is configured to use sane defaults assuming a standard gem structure
|
195
|
+
to generate an RBI that Sorbet will automatically find when your gem is included
|
196
|
+
as a dependency. If you require more advanced configuration you can add a
|
197
|
+
`.parlour` YAML file in the root of your project (see this project's `.parlour`
|
198
|
+
file as an example).
|
199
|
+
|
200
|
+
To disable the parsing step entire and just run plugins you can set `parser: false`
|
201
|
+
in your `.parlour` file.
|
202
|
+
|
177
203
|
## Parlour Plugins
|
178
204
|
|
179
205
|
_Have you written an awesome Parlour plugin? Please submit a PR to add it to this list!_
|
data/exe/parlour
CHANGED
@@ -16,15 +16,42 @@ command :run do |c|
|
|
16
16
|
c.description = 'Generates an RBI file from your .parlour file'
|
17
17
|
|
18
18
|
c.action do |args, options|
|
19
|
-
|
19
|
+
working_dir = Dir.pwd
|
20
|
+
config_filename = File.join(working_dir, '.parlour')
|
20
21
|
|
21
|
-
|
22
|
+
if File.exists?(config_filename)
|
23
|
+
configuration = keys_to_symbols(YAML.load_file(config_filename))
|
24
|
+
else
|
25
|
+
configuration = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Output default
|
29
|
+
configuration[:output_file] ||= "rbi/#{File.basename(working_dir)}.rbi"
|
22
30
|
|
23
31
|
# Style defaults
|
24
32
|
configuration[:style] ||= {}
|
25
33
|
configuration[:style][:tab_size] ||= 2
|
26
34
|
configuration[:style][:break_params] ||= 4
|
27
35
|
|
36
|
+
# Parser defaults, set explicitly to false to not run parser
|
37
|
+
if configuration[:parser] != false
|
38
|
+
configuration[:parser] ||= {}
|
39
|
+
|
40
|
+
# Input/Output defaults
|
41
|
+
configuration[:parser][:root] ||= '.'
|
42
|
+
|
43
|
+
# Included/Excluded path defaults
|
44
|
+
configuration[:parser][:included_paths] ||= ['lib']
|
45
|
+
configuration[:parser][:excluded_paths] ||= ['sorbet', 'spec']
|
46
|
+
|
47
|
+
# Defaults can be overridden but we always want to exclude the output file
|
48
|
+
configuration[:parser][:excluded_paths] << configuration[:output_file]
|
49
|
+
end
|
50
|
+
|
51
|
+
# Included/Excluded module defaults
|
52
|
+
configuration[:included_modules] ||= []
|
53
|
+
configuration[:excluded_modules] ||= []
|
54
|
+
|
28
55
|
# Require defaults
|
29
56
|
configuration[:requires] ||= []
|
30
57
|
configuration[:relative_requires] ||= []
|
@@ -53,6 +80,15 @@ command :run do |c|
|
|
53
80
|
break_params: configuration[:style][:break_params],
|
54
81
|
tab_size: configuration[:style][:tab_size]
|
55
82
|
)
|
83
|
+
|
84
|
+
if configuration[:parser]
|
85
|
+
Parlour::TypeLoader.load_project(
|
86
|
+
configuration[:parser][:root],
|
87
|
+
inclusions: configuration[:parser][:included_paths],
|
88
|
+
exclusions: configuration[:parser][:excluded_paths],
|
89
|
+
generator: gen,
|
90
|
+
)
|
91
|
+
end
|
56
92
|
Parlour::Plugin.run_plugins(plugin_instances, gen)
|
57
93
|
|
58
94
|
# Run a pass of the conflict resolver
|
@@ -74,6 +110,14 @@ command :run do |c|
|
|
74
110
|
choice == 0 ? nil : candidates[choice - 1]
|
75
111
|
end
|
76
112
|
|
113
|
+
if !configuration[:included_modules].empty? || !configuration[:excluded_modules].empty?
|
114
|
+
remove_unwanted_modules(
|
115
|
+
gen.root,
|
116
|
+
included_modules: configuration[:included_modules],
|
117
|
+
excluded_modules: configuration[:excluded_modules],
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
77
121
|
# Figure out strictness levels
|
78
122
|
requested_strictness_levels = plugin_instances.map do |plugin|
|
79
123
|
s = plugin.strictness&.to_s
|
@@ -98,6 +142,7 @@ command :run do |c|
|
|
98
142
|
end
|
99
143
|
|
100
144
|
# Write the final RBI
|
145
|
+
FileUtils.mkdir_p(File.dirname(configuration[:output_file]))
|
101
146
|
File.write(configuration[:output_file], gen.rbi(strictness))
|
102
147
|
end
|
103
148
|
end
|
@@ -122,3 +167,24 @@ def keys_to_symbols(hash)
|
|
122
167
|
]
|
123
168
|
end.to_h
|
124
169
|
end
|
170
|
+
|
171
|
+
def remove_unwanted_modules(root, included_modules:, excluded_modules:, prefix: nil)
|
172
|
+
root.children.select! do |child|
|
173
|
+
module_name = "#{prefix}#{child.name}"
|
174
|
+
|
175
|
+
if child.respond_to?(:children)
|
176
|
+
remove_unwanted_modules(
|
177
|
+
child,
|
178
|
+
included_modules: included_modules,
|
179
|
+
excluded_modules: excluded_modules,
|
180
|
+
prefix: "#{module_name}::",
|
181
|
+
)
|
182
|
+
has_included_children = !child.children.empty?
|
183
|
+
end
|
184
|
+
|
185
|
+
included = included_modules.empty? ? true : included_modules.any? { |m| module_name.start_with?(m) }
|
186
|
+
excluded = excluded_modules.empty? ? false : excluded_modules.any? { |m| module_name.start_with?(m) }
|
187
|
+
|
188
|
+
(included || has_included_children) && !excluded
|
189
|
+
end
|
190
|
+
end
|
@@ -6,17 +6,12 @@ module Parlour
|
|
6
6
|
def detached!
|
7
7
|
raise "cannot call methods on a detached RBI generator"
|
8
8
|
end
|
9
|
-
|
9
|
+
|
10
10
|
sig { override.returns(Options) }
|
11
11
|
def options
|
12
12
|
detached!
|
13
13
|
end
|
14
14
|
|
15
|
-
sig { override.returns(Namespace) }
|
16
|
-
def root
|
17
|
-
detached!
|
18
|
-
end
|
19
|
-
|
20
15
|
sig { override.returns(T.nilable(Plugin)) }
|
21
16
|
def current_plugin
|
22
17
|
nil
|
data/lib/parlour/plugin.rb
CHANGED
@@ -8,6 +8,7 @@ module Parlour
|
|
8
8
|
generator: RbiGenerator,
|
9
9
|
name: String,
|
10
10
|
value: String,
|
11
|
+
eigen_constant: T::Boolean,
|
11
12
|
block: T.nilable(T.proc.params(x: Constant).void)
|
12
13
|
).void
|
13
14
|
end
|
@@ -15,9 +16,12 @@ module Parlour
|
|
15
16
|
#
|
16
17
|
# @param name [String] The name of the constant.
|
17
18
|
# @param value [String] The value of the constant, as a Ruby code string.
|
18
|
-
|
19
|
+
# @param eigen_constant [Boolean] Whether this constant is defined on the
|
20
|
+
# eigenclass of the current namespace.
|
21
|
+
def initialize(generator, name: '', value: '', eigen_constant: false, &block)
|
19
22
|
super(generator, name)
|
20
23
|
@value = value
|
24
|
+
@eigen_constant = eigen_constant
|
21
25
|
yield_self(&block) if block
|
22
26
|
end
|
23
27
|
|
@@ -25,6 +29,10 @@ module Parlour
|
|
25
29
|
sig { returns(String) }
|
26
30
|
attr_reader :value
|
27
31
|
|
32
|
+
# @return [Boolean] Whether this constant is defined on the eigenclass
|
33
|
+
# of the current namespace.
|
34
|
+
attr_reader :eigen_constant
|
35
|
+
|
28
36
|
sig { params(other: Object).returns(T::Boolean) }
|
29
37
|
# Returns true if this instance is equal to another extend.
|
30
38
|
#
|
@@ -32,7 +40,8 @@ module Parlour
|
|
32
40
|
# subclass of it), this will always return false.
|
33
41
|
# @return [Boolean]
|
34
42
|
def ==(other)
|
35
|
-
Constant === other && name == other.name && value == other.value
|
43
|
+
Constant === other && name == other.name && value == other.value \
|
44
|
+
&& eigen_constant == other.eigen_constant
|
36
45
|
end
|
37
46
|
|
38
47
|
sig do
|
@@ -527,7 +527,7 @@ module Parlour
|
|
527
527
|
returned_includables
|
528
528
|
end
|
529
529
|
|
530
|
-
sig { params(name: String, value: String, block: T.nilable(T.proc.params(x: Constant).void)).returns(Constant) }
|
530
|
+
sig { params(name: String, value: String, eigen_constant: T::Boolean, block: T.nilable(T.proc.params(x: Constant).void)).returns(Constant) }
|
531
531
|
# Adds a new constant definition to this namespace.
|
532
532
|
#
|
533
533
|
# @example Add an +Elem+ constant to the class.
|
@@ -535,13 +535,16 @@ module Parlour
|
|
535
535
|
#
|
536
536
|
# @param name [String] The name of the constant.
|
537
537
|
# @param value [String] The value of the constant, as a Ruby code string.
|
538
|
+
# @param eigen_constant [Boolean] Whether this constant is defined on the
|
539
|
+
# eigenclass of the current namespace.
|
538
540
|
# @param block A block which the new instance yields itself to.
|
539
541
|
# @return [RbiGenerator::Constant]
|
540
|
-
def create_constant(name, value:, &block)
|
542
|
+
def create_constant(name, value:, eigen_constant: false, &block)
|
541
543
|
new_constant = RbiGenerator::Constant.new(
|
542
544
|
generator,
|
543
545
|
name: name,
|
544
546
|
value: value,
|
547
|
+
eigen_constant: eigen_constant,
|
545
548
|
&block
|
546
549
|
)
|
547
550
|
move_next_comments(new_constant)
|
@@ -631,14 +634,19 @@ module Parlour
|
|
631
634
|
|
632
635
|
result += [options.indented(indent_level, 'final!'), ''] if final
|
633
636
|
|
634
|
-
|
637
|
+
# Split away the eigen constants; these need to be put in a
|
638
|
+
# "class << self" block later
|
639
|
+
eigen_constants, non_eigen_constants = constants.partition(&:eigen_constant)
|
640
|
+
eigen_constants.sort_by!(&:name) if options.sort_namespaces
|
641
|
+
|
642
|
+
if includes.any? || extends.any? || non_eigen_constants.any?
|
635
643
|
result += (options.sort_namespaces ? includes.sort_by(&:name) : includes)
|
636
644
|
.flat_map { |x| x.generate_rbi(indent_level, options) }
|
637
645
|
.reject { |x| x.strip == '' }
|
638
646
|
result += (options.sort_namespaces ? extends.sort_by(&:name) : extends)
|
639
647
|
.flat_map { |x| x.generate_rbi(indent_level, options) }
|
640
648
|
.reject { |x| x.strip == '' }
|
641
|
-
result += (options.sort_namespaces ?
|
649
|
+
result += (options.sort_namespaces ? non_eigen_constants.sort_by(&:name) : non_eigen_constants)
|
642
650
|
.flat_map { |x| x.generate_rbi(indent_level, options) }
|
643
651
|
.reject { |x| x.strip == '' }
|
644
652
|
result << ""
|
@@ -658,14 +666,29 @@ module Parlour
|
|
658
666
|
child.is_a?(Attribute) && child.class_attribute
|
659
667
|
end
|
660
668
|
|
661
|
-
|
662
|
-
|
669
|
+
# Handle the "class << self block"
|
670
|
+
result << options.indented(indent_level, 'class << self') \
|
671
|
+
if class_attributes.any? || eigen_constants.any?
|
672
|
+
|
673
|
+
if eigen_constants.any?
|
674
|
+
first, *rest = eigen_constants
|
675
|
+
result += T.must(first).generate_rbi(indent_level + 1, options) + T.must(rest)
|
676
|
+
.map { |obj| obj.generate_rbi(indent_level + 1, options) }
|
677
|
+
.map { |lines| [""] + lines }
|
678
|
+
.flatten
|
679
|
+
end
|
680
|
+
|
681
|
+
result << '' if eigen_constants.any? && class_attributes.any?
|
663
682
|
|
683
|
+
if class_attributes.any?
|
664
684
|
first, *rest = class_attributes
|
665
685
|
result += T.must(first).generate_rbi(indent_level + 1, options) + T.must(rest)
|
666
686
|
.map { |obj| obj.generate_rbi(indent_level + 1, options) }
|
667
687
|
.map { |lines| [""] + lines }
|
668
688
|
.flatten
|
689
|
+
end
|
690
|
+
|
691
|
+
if class_attributes.any? || eigen_constants.any?
|
669
692
|
result << options.indented(indent_level, 'end')
|
670
693
|
result << ''
|
671
694
|
end
|
@@ -42,7 +42,7 @@ module Parlour
|
|
42
42
|
|
43
43
|
@kind = :keyword if kind == :normal && name.end_with?(':')
|
44
44
|
|
45
|
-
@type = type
|
45
|
+
@type = type || 'T.untyped'
|
46
46
|
@default = default
|
47
47
|
end
|
48
48
|
|
@@ -80,10 +80,10 @@ module Parlour
|
|
80
80
|
T.must(name[prefix.length..-1])
|
81
81
|
end
|
82
82
|
|
83
|
-
sig { returns(
|
83
|
+
sig { returns(String) }
|
84
84
|
# A Sorbet string of this parameter's type, such as +"String"+ or
|
85
85
|
# +"T.untyped"+.
|
86
|
-
# @return [String
|
86
|
+
# @return [String]
|
87
87
|
attr_reader :type
|
88
88
|
|
89
89
|
sig { returns(T.nilable(String)) }
|
@@ -118,7 +118,7 @@ module Parlour
|
|
118
118
|
#
|
119
119
|
# @return [String]
|
120
120
|
def to_sig_param
|
121
|
-
"#{name_without_kind}: #{type
|
121
|
+
"#{name_without_kind}: #{type}"
|
122
122
|
end#
|
123
123
|
|
124
124
|
# A mapping of {kind} values to the characteristic prefixes each kind has.
|
data/lib/parlour/type_loader.rb
CHANGED
@@ -10,27 +10,34 @@ module Parlour
|
|
10
10
|
# TODO: make this into a class which stores configuration and passes it to
|
11
11
|
# all typeparsers
|
12
12
|
|
13
|
-
sig { params(source: String, filename: T.nilable(String)).returns(RbiGenerator::Namespace) }
|
13
|
+
sig { params(source: String, filename: T.nilable(String), generator: T.nilable(RbiGenerator)).returns(RbiGenerator::Namespace) }
|
14
14
|
# Converts Ruby source code into a tree of objects.
|
15
15
|
#
|
16
16
|
# @param [String] source The Ruby source code.
|
17
17
|
# @param [String, nil] filename The filename to use when parsing this code.
|
18
18
|
# This may be used in error messages, but is optional.
|
19
19
|
# @return [RbiGenerator::Namespace] The root of the object tree.
|
20
|
-
def self.load_source(source, filename = nil)
|
21
|
-
TypeParser.from_source(filename || '(source)', source).parse_all
|
20
|
+
def self.load_source(source, filename = nil, generator: nil)
|
21
|
+
TypeParser.from_source(filename || '(source)', source, generator: generator).parse_all
|
22
22
|
end
|
23
23
|
|
24
|
-
sig { params(filename: String).returns(RbiGenerator::Namespace) }
|
24
|
+
sig { params(filename: String, generator: T.nilable(RbiGenerator)).returns(RbiGenerator::Namespace) }
|
25
25
|
# Converts Ruby source code into a tree of objects from a file.
|
26
26
|
#
|
27
27
|
# @param [String] filename The name of the file to load code from.
|
28
28
|
# @return [RbiGenerator::Namespace] The root of the object tree.
|
29
|
-
def self.load_file(filename)
|
30
|
-
load_source(File.read(filename), filename)
|
29
|
+
def self.load_file(filename, generator: nil)
|
30
|
+
load_source(File.read(filename), filename, generator: generator)
|
31
|
+
end
|
32
|
+
|
33
|
+
sig do
|
34
|
+
params(
|
35
|
+
root: String,
|
36
|
+
inclusions: T::Array[String],
|
37
|
+
exclusions: T::Array[String],
|
38
|
+
generator: T.nilable(RbiGenerator),
|
39
|
+
).returns(RbiGenerator::Namespace)
|
31
40
|
end
|
32
|
-
|
33
|
-
sig { params(root: String, exclusions: T::Array[String]).returns(RbiGenerator::Namespace) }
|
34
41
|
# Loads an entire Sorbet project using Sorbet's file table, obeying any
|
35
42
|
# "typed: ignore" sigils, into a tree of objects.
|
36
43
|
#
|
@@ -39,12 +46,15 @@ module Parlour
|
|
39
46
|
#
|
40
47
|
# @param [String] root The root of the project; where the "sorbet" directory
|
41
48
|
# and "Gemfile" are located.
|
49
|
+
# @param [Array<String>] inclusions A list of files to include when loading
|
50
|
+
# the project, relative to the given root.
|
42
51
|
# @param [Array<String>] exclusions A list of files to exclude when loading
|
43
52
|
# the project, relative to the given root.
|
44
53
|
# @return [RbiGenerator::Namespace] The root of the object tree.
|
45
|
-
def self.load_project(root, exclusions: [])
|
54
|
+
def self.load_project(root, inclusions: ['.'], exclusions: [], generator: nil)
|
55
|
+
expanded_inclusions = inclusions.map { |i| File.expand_path(i, root) }
|
46
56
|
expanded_exclusions = exclusions.map { |e| File.expand_path(e, root) }
|
47
|
-
|
57
|
+
|
48
58
|
stdin, stdout, stderr, wait_thr = T.unsafe(Open3).popen3(
|
49
59
|
'bundle exec srb tc -p file-table-json',
|
50
60
|
chdir: root
|
@@ -63,14 +73,17 @@ module Parlour
|
|
63
73
|
path = File.expand_path(rel_path, root)
|
64
74
|
|
65
75
|
# Skip this file if it was excluded
|
66
|
-
next if
|
76
|
+
next if !expanded_inclusions.any? { |i| path.start_with?(i) } \
|
77
|
+
|| expanded_exclusions.any? { |e| path.start_with?(e) }
|
67
78
|
|
68
79
|
# There are some entries which are URLs to stdlib
|
69
80
|
next unless File.exist?(path)
|
70
81
|
|
71
|
-
namespaces << load_file(path)
|
82
|
+
namespaces << load_file(path, generator: generator)
|
72
83
|
end
|
73
84
|
|
85
|
+
namespaces.uniq!
|
86
|
+
|
74
87
|
raise 'project is empty' if namespaces.empty?
|
75
88
|
|
76
89
|
first_namespace, *other_namespaces = namespaces
|