parlour 3.0.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|