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