parlour 0.1.0 → 0.1.1
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/.gitignore +2 -0
- data/CHANGELOG.md +10 -0
- data/README.md +97 -17
- data/lib/parlour.rb +15 -5
- data/lib/parlour/conflict_resolver.rb +81 -0
- data/lib/parlour/rbi_generator.rb +23 -0
- data/lib/parlour/rbi_generator/class_namespace.rb +80 -0
- data/lib/parlour/rbi_generator/method.rb +142 -0
- data/lib/parlour/rbi_generator/module_namespace.rb +68 -0
- data/lib/parlour/rbi_generator/namespace.rb +144 -0
- data/lib/parlour/rbi_generator/options.rb +25 -0
- data/lib/parlour/rbi_generator/parameter.rb +81 -0
- data/lib/parlour/rbi_generator/rbi_object.rb +32 -0
- data/lib/parlour/version.rb +2 -1
- data/parlour.gemspec +6 -2
- data/sorbet/config +2 -0
- data/sorbet/rbi/gems/rake.rbi +636 -0
- data/sorbet/rbi/gems/rspec-core.rbi +1642 -0
- data/sorbet/rbi/gems/rspec-support.rbi +126 -0
- data/sorbet/rbi/gems/rspec.rbi +14 -0
- data/sorbet/rbi/hidden-definitions/errors.txt +4637 -0
- data/sorbet/rbi/hidden-definitions/hidden.rbi +10079 -0
- data/sorbet/rbi/sorbet-typed/lib/bundler/all/bundler.rbi +8547 -0
- data/sorbet/rbi/sorbet-typed/lib/ruby/all/open3.rbi +111 -0
- data/sorbet/rbi/sorbet-typed/lib/ruby/all/resolv.rbi +543 -0
- metadata +66 -8
- data/.travis.yml +0 -7
- data/Rakefile +0 -6
- data/bin/console +0 -14
- data/bin/setup +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c2e368fa34eb2073d6b20beda3455dbf51db175ec2f5ef6c085de62f62601888
|
4
|
+
data.tar.gz: c0c0b1e1f0e9df83e76cba72609083058149d7de169afcb07418a3117b00880a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6d87997cabce942aa838efc114071f08465de8fb7c442eb58a6bce45d1e95bb78369183fa11568da31b52038bbb38af9ddf78617f5328536bbd6f3fbd0976f7
|
7
|
+
data.tar.gz: 70b90d57fc92f67934816152c6ac40505e2305400cbadab4313d3e01b079a0637e6e056c1f58a93db455a6d5d715d58c6cdc8e4b43f9274fa23d718f225519fa
|
data/.gitignore
CHANGED
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
@@ -1,38 +1,118 @@
|
|
1
1
|
# Parlour
|
2
2
|
|
3
|
-
|
3
|
+
Parlour is an RBI generator and merger for Sorbet. Once done, it'll consist of
|
4
|
+
two parts:
|
4
5
|
|
5
|
-
|
6
|
+
- The generator, which outputs beautifully formatted RBI files, created using
|
7
|
+
an intuitive DSL.
|
6
8
|
|
7
|
-
|
9
|
+
- The plugin/build system, which allows multiple Parlour plugins to generate
|
10
|
+
RBIs for the same codebase. These are combined automatically as much as
|
11
|
+
possible, but any other conflicts can be resolved manually through prompts.
|
8
12
|
|
9
|
-
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
Here's a quick example of how you might generate an RBI currently, though this
|
16
|
+
API is very likely to change:
|
10
17
|
|
11
18
|
```ruby
|
12
|
-
|
13
|
-
```
|
19
|
+
require 'parlour'
|
14
20
|
|
15
|
-
|
21
|
+
generator = Parlour::RbiGenerator.new
|
22
|
+
generator.root.create_module('A') do |a|
|
23
|
+
a.create_class('Foo') do |foo|
|
24
|
+
foo.create_method('add_two_integers', [
|
25
|
+
Parlour::RbiGenerator::Parameter.new('a', type: 'Integer'),
|
26
|
+
Parlour::RbiGenerator::Parameter.new('b', type: 'Integer')
|
27
|
+
], 'Integer')
|
28
|
+
end
|
16
29
|
|
17
|
-
|
30
|
+
a.create_class('Bar', superclass: 'Foo')
|
31
|
+
end
|
18
32
|
|
19
|
-
|
33
|
+
generator.rbi # => Our RBI as a string
|
34
|
+
```
|
20
35
|
|
21
|
-
|
36
|
+
This will generate the following RBI:
|
22
37
|
|
23
|
-
|
38
|
+
```ruby
|
39
|
+
module A
|
40
|
+
class Foo
|
41
|
+
sig { params(a: Integer, b: Integer).returns(Integer) }
|
42
|
+
def add_two_integers(a, b); end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Bar < Foo
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
24
49
|
|
25
|
-
|
50
|
+
There aren't really any docs currently, so have a look around the code to find
|
51
|
+
any extra options you may need.
|
26
52
|
|
27
|
-
##
|
53
|
+
## Code Structure
|
28
54
|
|
29
|
-
|
55
|
+
### Overall Flow
|
56
|
+
```
|
57
|
+
STEP 1
|
58
|
+
All plugins mutate the
|
59
|
+
instance of RbiGenerator
|
60
|
+
They generate a tree
|
61
|
+
structure of RbiObjects
|
62
|
+
|
63
|
+
+--------+ +--------+
|
64
|
+
|Plugin 1| |Plugin 2|
|
65
|
+
+----+---+ +----+---+ STEP 2
|
66
|
+
^ ^ ConflictResolver
|
67
|
+
| | mutates the structure
|
68
|
+
+-------------------+ | | to fix conflicts
|
69
|
+
| | | |
|
70
|
+
| One instance of +-------------+ +----------------+
|
71
|
+
| RbiGenerator +--------------------->+ConflictResolver|
|
72
|
+
| | +----------------+
|
73
|
+
+---------+---------+
|
74
|
+
|
|
75
|
+
|
|
76
|
+
| +-------+ STEP 3
|
77
|
+
+--------->+ File | The final RBI is written
|
78
|
+
+-------+ to a file
|
79
|
+
```
|
30
80
|
|
31
|
-
|
81
|
+
### Generation
|
82
|
+
Everything that can generate lines of the RBI implements the
|
83
|
+
`RbiGenerator::RbiObject` interface. This defines one function, `generate_rbi`,
|
84
|
+
which accepts the current indentation level and a set of formatting options.
|
85
|
+
(Each object is responsible for generating its own indentation; that is, the
|
86
|
+
lines generated by a child object should not then be indented by its parent.)
|
87
|
+
|
88
|
+
I think generation is quite close to done, but it still needs features like
|
89
|
+
constants and type parameters.
|
90
|
+
|
91
|
+
### Conflict Resolution
|
92
|
+
This will be a key part of the plugin/build system. The `ConflictResolver` takes
|
93
|
+
a namespace from the `RbiGenerator` and merges duplicate items in it together.
|
94
|
+
This means that many plugins can generate their own signatures which are all
|
95
|
+
bundled into one, conflict-free output RBI.
|
96
|
+
|
97
|
+
It is able to do the following merges automatically:
|
98
|
+
|
99
|
+
- If many methods are identical, delete all but one.
|
100
|
+
- If many classes are defined with the same name, merge their methods,
|
101
|
+
includes and extends. (But only if they are all abstract or all not,
|
102
|
+
and only if they don't define more than one superclass together.)
|
103
|
+
- If many modules are defined with the same name, merge their methods,
|
104
|
+
includes and extends. (But only if they are all interfaces or all not.)
|
105
|
+
|
106
|
+
If a merge can't be performed automatically, then the `#resolve_conflicts`
|
107
|
+
method takes a block. This block is passed all the conflicting objects, and one
|
108
|
+
should be selected and returned - all others will be deleted. (Alternatively,
|
109
|
+
the block can return nil, and all will be deleted.) This will allow a CLI to
|
110
|
+
prompt the user asking them what they'd like to do, in the case of conflicts
|
111
|
+
between each plugin's signatures which can't automatically be resolved.
|
32
112
|
|
33
113
|
## Contributing
|
34
114
|
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
115
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/AaronC81/parlour. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
36
116
|
|
37
117
|
## License
|
38
118
|
|
@@ -40,4 +120,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
40
120
|
|
41
121
|
## Code of Conduct
|
42
122
|
|
43
|
-
Everyone interacting in the Parlour project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
123
|
+
Everyone interacting in the Parlour project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/AaronC81/parlour/blob/master/CODE_OF_CONDUCT.md).
|
data/lib/parlour.rb
CHANGED
@@ -1,6 +1,16 @@
|
|
1
|
-
|
1
|
+
# typed: strong
|
2
|
+
require 'sorbet-runtime'
|
3
|
+
|
4
|
+
require 'parlour/version'
|
5
|
+
|
6
|
+
require 'parlour/rbi_generator/parameter'
|
7
|
+
require 'parlour/rbi_generator/rbi_object'
|
8
|
+
require 'parlour/rbi_generator/method'
|
9
|
+
require 'parlour/rbi_generator/options'
|
10
|
+
require 'parlour/rbi_generator/namespace'
|
11
|
+
require 'parlour/rbi_generator/module_namespace'
|
12
|
+
require 'parlour/rbi_generator/class_namespace'
|
13
|
+
require 'parlour/rbi_generator'
|
14
|
+
|
15
|
+
require 'parlour/conflict_resolver'
|
2
16
|
|
3
|
-
module Parlour
|
4
|
-
class Error < StandardError; end
|
5
|
-
# Your code goes here...
|
6
|
-
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# typed: true
|
2
|
+
module Parlour
|
3
|
+
class ConflictResolver
|
4
|
+
extend T::Sig
|
5
|
+
|
6
|
+
sig do
|
7
|
+
params(
|
8
|
+
namespace: RbiGenerator::Namespace,
|
9
|
+
resolver: T.proc.params(
|
10
|
+
desc: String,
|
11
|
+
choices: T::Array[RbiGenerator::RbiObject]
|
12
|
+
).returns(RbiGenerator::RbiObject)
|
13
|
+
).void
|
14
|
+
end
|
15
|
+
def resolve_conflicts(namespace, &resolver)
|
16
|
+
# Check for multiple definitions with the same name
|
17
|
+
grouped_by_name_children = namespace.children.group_by do |rbi_obj|
|
18
|
+
if RbiGenerator::ModuleNamespace === rbi_obj \
|
19
|
+
|| RbiGenerator::ClassNamespace === rbi_obj \
|
20
|
+
|| RbiGenerator::Method === rbi_obj
|
21
|
+
rbi_obj.name
|
22
|
+
else
|
23
|
+
raise "unsupported child of type #{T.cast(rbi_obj, Object).class}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
grouped_by_name_children.each do |name, children|
|
28
|
+
if children.length > 1
|
29
|
+
# We found a conflict!
|
30
|
+
# Start by removing all the conflicting items
|
31
|
+
children.each do |c|
|
32
|
+
namespace.children.delete(c)
|
33
|
+
end
|
34
|
+
|
35
|
+
# We can only try to resolve automatically if they're all the same
|
36
|
+
# type of object, so check that first
|
37
|
+
children_type = single_type_of_array(children)
|
38
|
+
unless children_type
|
39
|
+
# The types aren't the same, so ask the resovler what to do, and
|
40
|
+
# insert that (if not nil)
|
41
|
+
choice = resolver.call("Different kinds of definition for the same name", children)
|
42
|
+
namespace.children << choice if choice
|
43
|
+
next
|
44
|
+
end
|
45
|
+
|
46
|
+
# Are all of the children equivalent? If so, just keep one of them
|
47
|
+
if all_eql?(children)
|
48
|
+
namespace.children << T.must(children.first)
|
49
|
+
next
|
50
|
+
end
|
51
|
+
|
52
|
+
# Can the children merge themselves automatically? If so, let them
|
53
|
+
first, *rest = children
|
54
|
+
first, rest = T.must(first), T.must(rest)
|
55
|
+
if T.must(first).mergeable?(T.must(rest))
|
56
|
+
first.merge_into_self(rest)
|
57
|
+
namespace.children << first
|
58
|
+
next
|
59
|
+
end
|
60
|
+
|
61
|
+
# I give up! Let it be resolved manually somehow
|
62
|
+
choice = resolver.call("Can't automatically resolve", children)
|
63
|
+
namespace.children << choice if choice
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# TODO: recurse to deeper namespaces
|
68
|
+
end
|
69
|
+
|
70
|
+
sig { params(arr: T::Array[T.untyped]).returns(T.nilable(Class)) }
|
71
|
+
def single_type_of_array(arr)
|
72
|
+
array_types = arr.map { |c| T.cast(c, Object).class }.uniq
|
73
|
+
array_types.length == 1 ? array_types.first : nil
|
74
|
+
end
|
75
|
+
|
76
|
+
sig { params(arr: T::Array[T.untyped]).returns(T::Boolean) }
|
77
|
+
def all_eql?(arr)
|
78
|
+
arr.each_cons(2).all? { |x, y| x == y }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# typed: true
|
2
|
+
module Parlour
|
3
|
+
class RbiGenerator
|
4
|
+
extend T::Sig
|
5
|
+
|
6
|
+
sig { params(break_params: Integer, tab_size: Integer).void }
|
7
|
+
def initialize(break_params: 4, tab_size: 2)
|
8
|
+
@options = Options.new(break_params: break_params, tab_size: tab_size)
|
9
|
+
@root = Namespace.new
|
10
|
+
end
|
11
|
+
|
12
|
+
sig { returns(Options) }
|
13
|
+
attr_reader :options
|
14
|
+
|
15
|
+
sig { returns(Namespace) }
|
16
|
+
attr_reader :root
|
17
|
+
|
18
|
+
sig { returns(String) }
|
19
|
+
def rbi
|
20
|
+
root.generate_rbi(0, options).join("\n")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# typed: true
|
2
|
+
module Parlour
|
3
|
+
class RbiGenerator
|
4
|
+
class ClassNamespace < Namespace
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
sig do
|
8
|
+
params(
|
9
|
+
name: String,
|
10
|
+
superclass: T.nilable(String),
|
11
|
+
abstract: T::Boolean,
|
12
|
+
block: T.nilable(T.proc.params(x: ClassNamespace).void)
|
13
|
+
).void
|
14
|
+
end
|
15
|
+
def initialize(name, superclass, abstract, &block)
|
16
|
+
super(&block)
|
17
|
+
@name = name
|
18
|
+
@superclass = superclass
|
19
|
+
@abstract = abstract
|
20
|
+
end
|
21
|
+
|
22
|
+
sig do
|
23
|
+
override.params(
|
24
|
+
indent_level: Integer,
|
25
|
+
options: Options
|
26
|
+
).returns(T::Array[String])
|
27
|
+
end
|
28
|
+
def generate_rbi(indent_level, options)
|
29
|
+
class_definition = superclass.nil? \
|
30
|
+
? "class #{name}"
|
31
|
+
: "class #{name} < #{superclass}"
|
32
|
+
|
33
|
+
lines = []
|
34
|
+
lines << options.indented(indent_level, class_definition)
|
35
|
+
lines += [options.indented(indent_level + 1, "abstract!"), ""] if abstract
|
36
|
+
lines += super(indent_level + 1, options)
|
37
|
+
lines << options.indented(indent_level, "end")
|
38
|
+
end
|
39
|
+
|
40
|
+
sig { returns(String) }
|
41
|
+
attr_reader :name
|
42
|
+
|
43
|
+
sig { returns(T.nilable(String)) }
|
44
|
+
attr_reader :superclass
|
45
|
+
|
46
|
+
sig { returns(T::Boolean) }
|
47
|
+
attr_reader :abstract
|
48
|
+
|
49
|
+
sig do
|
50
|
+
override.params(
|
51
|
+
others: T::Array[RbiGenerator::RbiObject]
|
52
|
+
).returns(T::Boolean)
|
53
|
+
end
|
54
|
+
def mergeable?(others)
|
55
|
+
others = T.cast(others, T::Array[ClassNamespace]) rescue (return false)
|
56
|
+
all = others + [self]
|
57
|
+
|
58
|
+
all.map(&:abstract).uniq.length == 1 &&
|
59
|
+
all.map(&:superclass).compact.uniq.length <= 1
|
60
|
+
end
|
61
|
+
|
62
|
+
sig do
|
63
|
+
override.params(
|
64
|
+
others: T::Array[RbiGenerator::RbiObject]
|
65
|
+
).void
|
66
|
+
end
|
67
|
+
def merge_into_self(others)
|
68
|
+
others.each do |other|
|
69
|
+
other = T.cast(other, ClassNamespace)
|
70
|
+
|
71
|
+
other.children.each { |c| children << c }
|
72
|
+
other.extends.each { |e| extends << e }
|
73
|
+
other.includes.each { |i| includes << i }
|
74
|
+
|
75
|
+
@superclass = other.superclass unless superclass
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# typed: true
|
2
|
+
module Parlour
|
3
|
+
class RbiGenerator
|
4
|
+
class Method
|
5
|
+
extend T::Sig
|
6
|
+
|
7
|
+
include RbiObject
|
8
|
+
|
9
|
+
sig do
|
10
|
+
params(
|
11
|
+
name: String,
|
12
|
+
parameters: T::Array[Parameter],
|
13
|
+
return_type: T.nilable(String),
|
14
|
+
abstract: T::Boolean,
|
15
|
+
implementation: T::Boolean,
|
16
|
+
override: T::Boolean,
|
17
|
+
overridable: T::Boolean,
|
18
|
+
class_method: T::Boolean
|
19
|
+
).void
|
20
|
+
end
|
21
|
+
def initialize(name, parameters, return_type = nil, abstract: false, implementation: false, override: false, overridable: false, class_method: false)
|
22
|
+
@name = name
|
23
|
+
@parameters = parameters
|
24
|
+
@return_type = return_type
|
25
|
+
@abstract = abstract
|
26
|
+
@implementation = implementation
|
27
|
+
@override = override
|
28
|
+
@overridable = overridable
|
29
|
+
@class_method = class_method
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { params(other: Object).returns(T::Boolean) }
|
33
|
+
def ==(other)
|
34
|
+
Method === other &&
|
35
|
+
name == other.name &&
|
36
|
+
parameters == other.parameters &&
|
37
|
+
return_type == other.return_type &&
|
38
|
+
abstract == other.abstract &&
|
39
|
+
implementation == other.implementation &&
|
40
|
+
override == other.override &&
|
41
|
+
overridable == other.overridable &&
|
42
|
+
class_method == other.class_method
|
43
|
+
end
|
44
|
+
|
45
|
+
sig { returns(String) }
|
46
|
+
attr_reader :name
|
47
|
+
|
48
|
+
sig { returns(T::Array[Parameter]) }
|
49
|
+
attr_reader :parameters
|
50
|
+
|
51
|
+
sig { returns(T.nilable(String)) }
|
52
|
+
attr_reader :return_type
|
53
|
+
|
54
|
+
sig { returns(T::Boolean) }
|
55
|
+
attr_reader :abstract
|
56
|
+
|
57
|
+
sig { returns(T::Boolean) }
|
58
|
+
attr_reader :implementation
|
59
|
+
|
60
|
+
sig { returns(T::Boolean) }
|
61
|
+
attr_reader :override
|
62
|
+
|
63
|
+
sig { returns(T::Boolean) }
|
64
|
+
attr_reader :overridable
|
65
|
+
|
66
|
+
sig { returns(T::Boolean) }
|
67
|
+
attr_reader :class_method
|
68
|
+
|
69
|
+
sig do
|
70
|
+
implementation.params(
|
71
|
+
indent_level: Integer,
|
72
|
+
options: Options
|
73
|
+
).returns(T::Array[String])
|
74
|
+
end
|
75
|
+
def generate_rbi(indent_level, options)
|
76
|
+
return_call = return_type ? "returns(#{return_type})" : 'void'
|
77
|
+
|
78
|
+
sig_params = parameters.map(&:to_sig_param)
|
79
|
+
sig_lines = parameters.length >= options.break_params \
|
80
|
+
? [
|
81
|
+
options.indented(indent_level, 'sig do'),
|
82
|
+
options.indented(indent_level + 1, "#{qualifiers}params("),
|
83
|
+
] +
|
84
|
+
(
|
85
|
+
parameters.empty? ? [] : sig_params.map do |x|
|
86
|
+
options.indented(indent_level + 2, "#{x},")
|
87
|
+
end
|
88
|
+
) +
|
89
|
+
[
|
90
|
+
options.indented(indent_level + 1, ").#{return_call}"),
|
91
|
+
options.indented(indent_level, 'end')
|
92
|
+
]
|
93
|
+
|
94
|
+
: [options.indented(
|
95
|
+
indent_level,
|
96
|
+
"sig { #{qualifiers}#{
|
97
|
+
parameters.empty? ? '' : "params(#{sig_params.join(', ')})"
|
98
|
+
}#{
|
99
|
+
qualifiers.empty? && parameters.empty? ? '' : '.'
|
100
|
+
}#{return_call} }"
|
101
|
+
)]
|
102
|
+
|
103
|
+
def_params = parameters.map(&:to_def_param)
|
104
|
+
name_prefix = class_method ? 'self.' : ''
|
105
|
+
def_line = options.indented(
|
106
|
+
indent_level,
|
107
|
+
"def #{name_prefix}#{name}(#{def_params.join(', ')}); end"
|
108
|
+
)
|
109
|
+
|
110
|
+
sig_lines + [def_line]
|
111
|
+
end
|
112
|
+
|
113
|
+
sig { returns(String) }
|
114
|
+
def qualifiers
|
115
|
+
result = ''
|
116
|
+
result += 'abstract.' if abstract
|
117
|
+
result += 'implementation.' if implementation
|
118
|
+
result += 'override.' if override
|
119
|
+
result += 'overridable.' if overridable
|
120
|
+
result
|
121
|
+
end
|
122
|
+
|
123
|
+
sig do
|
124
|
+
implementation.params(
|
125
|
+
others: T::Array[RbiGenerator::RbiObject]
|
126
|
+
).returns(T::Boolean)
|
127
|
+
end
|
128
|
+
def mergeable?(others)
|
129
|
+
false
|
130
|
+
end
|
131
|
+
|
132
|
+
sig do
|
133
|
+
implementation.params(
|
134
|
+
others: T::Array[RbiGenerator::RbiObject]
|
135
|
+
).void
|
136
|
+
end
|
137
|
+
def merge_into_self(others)
|
138
|
+
raise 'methods can never be merged like this'
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|