lowtype 1.4.0 → 1.5.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/lib/cli/cli.rb +36 -0
- data/lib/cli/rbs/class_sig.rb +57 -0
- data/lib/cli/rbs/exporter.rb +67 -0
- data/lib/cli/rbs/expression_mapper.rb +34 -0
- data/lib/cli/rbs/method_sig.rb +52 -0
- data/lib/cli/rbs/type_mapper.rb +65 -0
- data/lib/cli/type_stripper.rb +9 -0
- data/lib/expressions/type_expression.rb +5 -3
- data/lib/version.rb +1 -1
- metadata +39 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e86b8beb0266fbdc8746e0b9db1be9e6bd1d5da302d3120da506f61ad91954d3
|
|
4
|
+
data.tar.gz: 03f88ef3894d97035083dfd24c84e570e457272662c50ece98ebb3611d68579f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bf55ecfffdf71788a602ede14078a87c1d216700d24317698b23840045e6fe546af705791db67b575ce4467df35c9e98d4cda8a588a27c404e5e604a30d7a677
|
|
7
|
+
data.tar.gz: 3235f927017e31e58b71a303639adaf0b721a354a1442d6f125d9fe270eae269bb6967bf08d1ab7687ea99f5bfc9ca0a27b74095ec50154fc8e95a0ad7198077
|
data/lib/cli/cli.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'trees'
|
|
4
|
+
|
|
5
|
+
require_relative 'rbs/exporter'
|
|
6
|
+
require_relative 'type_stripper'
|
|
7
|
+
|
|
8
|
+
module LowType
|
|
9
|
+
module CLI
|
|
10
|
+
extend Trees
|
|
11
|
+
|
|
12
|
+
line('rbs :src') do |src|
|
|
13
|
+
s { 'Exports files from the source to type signatures in the sig directory' }
|
|
14
|
+
e { 'lowtype rbs app' }
|
|
15
|
+
x { RBS::Exporter.export(path: src) }
|
|
16
|
+
|
|
17
|
+
line('--boot', '-b') do |src, boot|
|
|
18
|
+
s { 'Runs a file before export and could be used to load constants outside your source directory' }
|
|
19
|
+
e { 'lowtype rbs app --boot config/boot.rb' }
|
|
20
|
+
x { RBS::Exporter.export(path: src, boot:) }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
line('strip!') do
|
|
25
|
+
s { 'Strips files from the current directory of their types' }
|
|
26
|
+
e { 'lowtype strip' }
|
|
27
|
+
x { TypeStripper.export(src: '.', dest: '.') }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
line('strip :src :dest') do |src, dest|
|
|
31
|
+
s { 'Exports files from the source directory to a destination directory with types stripped' }
|
|
32
|
+
e { 'lowtype strip src/app dest/app' }
|
|
33
|
+
x { TypeStripper.export(src:, dest:) }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'lowkey'
|
|
4
|
+
|
|
5
|
+
require_relative 'method_sig'
|
|
6
|
+
|
|
7
|
+
module LowType
|
|
8
|
+
module CLI
|
|
9
|
+
module RBS
|
|
10
|
+
module ClassSig
|
|
11
|
+
class << self
|
|
12
|
+
def build(class_proxy:, file_proxy:)
|
|
13
|
+
body = method_lines(class_proxy)
|
|
14
|
+
wrap_namespace(namespace: class_proxy.namespace, name: class_proxy.name, body:, file_proxy:).join("\n")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def method_lines(class_proxy)
|
|
20
|
+
instance_lines = class_proxy.instance_methods.each_value.map do |method_proxy|
|
|
21
|
+
MethodSig.build(method_proxy:)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class_lines = class_proxy.class_methods.each_value.map do |method_proxy|
|
|
25
|
+
MethodSig.build(method_proxy:, class_method: true)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
instance_lines + class_lines
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def wrap_namespace(namespace:, name:, body:, file_proxy:)
|
|
32
|
+
lines = wrap("class #{name}", body)
|
|
33
|
+
|
|
34
|
+
segments(namespace).reverse_each do |segment|
|
|
35
|
+
lines = wrap("#{namespace_type(segment:, file_proxy:)} #{segment}", lines)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
lines
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def wrap(header, body_lines)
|
|
42
|
+
[header, *body_lines.map { |line| " #{line}" }, 'end']
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Lowkey's "namespace" includes the class's own name.
|
|
46
|
+
def segments(namespace)
|
|
47
|
+
namespace.split('::')[0..-2]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def namespace_type(segment:, file_proxy:)
|
|
51
|
+
file_proxy.definitions[segment].is_a?(::Lowkey::ClassProxy) ? 'class' : 'module'
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'lowkey'
|
|
5
|
+
require 'dirload'
|
|
6
|
+
|
|
7
|
+
require_relative 'class_sig'
|
|
8
|
+
|
|
9
|
+
module LowType
|
|
10
|
+
module CLI
|
|
11
|
+
module RBS
|
|
12
|
+
class MissingPathError < StandardError; end
|
|
13
|
+
|
|
14
|
+
# Exports .rbs files for every class in `path` (a file or directory) that includes LowType.
|
|
15
|
+
# Uses dirload() to autoload missing constants from files within the same directory.
|
|
16
|
+
module Exporter
|
|
17
|
+
class << self
|
|
18
|
+
def export(path:, boot: nil)
|
|
19
|
+
dirload(path).loaded_paths.each do |file_path, _adapter|
|
|
20
|
+
next unless file_path.end_with?('.rb') || file_path.end_with?('.rbx')
|
|
21
|
+
|
|
22
|
+
export_file(file_path:)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def export_file(file_path:)
|
|
29
|
+
return unless file_proxy = ::Lowkey[file_path]
|
|
30
|
+
|
|
31
|
+
class_proxies = typed_class_proxies(file_proxy)
|
|
32
|
+
return if class_proxies.empty?
|
|
33
|
+
|
|
34
|
+
content = class_proxies.map do |class_proxy|
|
|
35
|
+
ClassSig.build(class_proxy:, file_proxy:)
|
|
36
|
+
end.join("\n\n")
|
|
37
|
+
|
|
38
|
+
save_file(file_path:, content:)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def typed_class_proxies(file_proxy)
|
|
42
|
+
file_proxy.definitions.values.filter do |proxy|
|
|
43
|
+
includes_lowtype = proxy.lines[proxy.start_line..proxy.end_line].find do |line|
|
|
44
|
+
line.include?('include LowType')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
proxy.is_a?(::Lowkey::ClassProxy) && includes_lowtype
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def save_file(file_path:, content:)
|
|
52
|
+
sig_path = sig_path(file_path:)
|
|
53
|
+
FileUtils.mkdir_p(File.dirname(sig_path))
|
|
54
|
+
File.write(sig_path, "#{content}\n")
|
|
55
|
+
|
|
56
|
+
puts "Exported #{sig_path}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def sig_path(file_path:)
|
|
60
|
+
relative = file_path.delete_prefix("#{Dir.pwd}/").delete_prefix('lib/')
|
|
61
|
+
File.join('sig', relative.sub(/\.rb\z/, '.rbs'))
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'type_mapper'
|
|
4
|
+
|
|
5
|
+
module LowType
|
|
6
|
+
module CLI
|
|
7
|
+
module RBS
|
|
8
|
+
# Maps a TypeExpression to an RBS type.
|
|
9
|
+
module ExpressionMapper
|
|
10
|
+
class << self
|
|
11
|
+
def match(expression)
|
|
12
|
+
return 'untyped' if expression.nil?
|
|
13
|
+
|
|
14
|
+
types = expression.types.map { |type| TypeMapper.map(type) }.uniq
|
|
15
|
+
types = ['untyped'] if types.empty?
|
|
16
|
+
|
|
17
|
+
union = types.join(' | ')
|
|
18
|
+
nilable?(expression) ? nilable(union:, multiple: types.length > 1) : union
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def nilable?(expression)
|
|
24
|
+
expression.default_value.nil?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def nilable(union:, multiple:)
|
|
28
|
+
multiple ? "(#{union})?" : "#{union}?"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'expression_mapper'
|
|
4
|
+
|
|
5
|
+
module LowType
|
|
6
|
+
module CLI
|
|
7
|
+
module RBS
|
|
8
|
+
# Builds an RBS method signature line from a Lowkey::MethodProxy.
|
|
9
|
+
# TODO: Support positional args, keyword args and blocks.
|
|
10
|
+
module MethodSig
|
|
11
|
+
POSITIONAL_TYPES = %i[pos_req pos_opt].freeze
|
|
12
|
+
KEYWORD_TYPES = %i[key_req key_opt].freeze
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def build(method_proxy:, class_method: false)
|
|
16
|
+
scope = class_method ? 'self.' : ''
|
|
17
|
+
"def #{scope}#{method_proxy.name}: (#{params(method_proxy)}) -> #{return_type(method_proxy)}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def params(method_proxy)
|
|
23
|
+
positional = method_proxy.params.select { |param| POSITIONAL_TYPES.include?(param.type) }
|
|
24
|
+
keyword = method_proxy.params.select { |param| KEYWORD_TYPES.include?(param.type) }
|
|
25
|
+
|
|
26
|
+
(positional.map { |param| positional_fragment(param) } + keyword.map { |param| keyword_fragment(param) }).join(', ')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def positional_fragment(param_proxy)
|
|
30
|
+
type = ExpressionMapper.match(param_proxy.expression)
|
|
31
|
+
optional?(param_proxy) ? "?#{type} #{param_proxy.name}" : "#{type} #{param_proxy.name}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def keyword_fragment(param_proxy)
|
|
35
|
+
type = ExpressionMapper.match(param_proxy.expression)
|
|
36
|
+
optional?(param_proxy) ? "?#{param_proxy.name}: #{type}" : "#{param_proxy.name}: #{type}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def optional?(param_proxy)
|
|
40
|
+
return !param_proxy.expression.required? if param_proxy.expression
|
|
41
|
+
|
|
42
|
+
%i[pos_opt key_opt].include?(param_proxy.type)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def return_type(method_proxy)
|
|
46
|
+
ExpressionMapper.match(method_proxy.return_proxy&.expression)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../types/complex_types'
|
|
4
|
+
|
|
5
|
+
module LowType
|
|
6
|
+
module CLI
|
|
7
|
+
module RBS
|
|
8
|
+
module TypeMapper
|
|
9
|
+
COMPLEX_TYPE_MAP = {
|
|
10
|
+
Low::Types::Boolean => 'bool',
|
|
11
|
+
Low::Types::HTML => 'String',
|
|
12
|
+
Low::Types::JSON => 'String',
|
|
13
|
+
Low::Types::XML => 'String',
|
|
14
|
+
Low::Types::Status => 'Integer',
|
|
15
|
+
Low::Types::Headers => 'Hash[String, untyped]',
|
|
16
|
+
Low::Types::Tuple => 'Array[untyped]'
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
def map(type)
|
|
21
|
+
return 'nil' if type == NilClass
|
|
22
|
+
return 'bool' if [TrueClass, FalseClass].include?(type)
|
|
23
|
+
return COMPLEX_TYPE_MAP[type] if COMPLEX_TYPE_MAP.key?(type)
|
|
24
|
+
|
|
25
|
+
case type
|
|
26
|
+
when ::Low::TypeExpression then map_expression(type)
|
|
27
|
+
when Array then map_array(type)
|
|
28
|
+
when Hash then map_hash(type)
|
|
29
|
+
when Class then map_class(type)
|
|
30
|
+
else 'untyped'
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# A nested type expressio like "String | Integer" inside "Array[String | Integer]".
|
|
37
|
+
def map_expression(expression)
|
|
38
|
+
require_relative 'expression_mapper'
|
|
39
|
+
ExpressionMapper.match(expression)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# When [T] then every element is type T. When [X, Y, Z] then each element is that type at that position.
|
|
43
|
+
def map_array(type)
|
|
44
|
+
return 'Array[untyped]' if type.empty?
|
|
45
|
+
return "[#{type.map { |subtype| map(subtype) }.join(', ')}]" if type.length > 1
|
|
46
|
+
|
|
47
|
+
"Array[#{map(type.first)}]"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def map_hash(type)
|
|
51
|
+
return 'Hash[untyped, untyped]' if type.empty?
|
|
52
|
+
|
|
53
|
+
"Hash[#{map(type.keys.first)}, #{map(type.values.first)}]"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def map_class(type)
|
|
57
|
+
return 'untyped' if type.name.nil?
|
|
58
|
+
|
|
59
|
+
"::#{type.name}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -7,9 +7,11 @@ require_relative '../proxies/param_proxy'
|
|
|
7
7
|
require_relative '../queries/type_query'
|
|
8
8
|
|
|
9
9
|
module Low
|
|
10
|
-
|
|
11
|
-
adapter_paths = Dir.
|
|
12
|
-
module_paths = %w[expressions/
|
|
10
|
+
lib_path = File.expand_path('..', __dir__)
|
|
11
|
+
adapter_paths = Dir.glob('adapters/*', base: lib_path).map { |path| File.join(lib_path, path) }
|
|
12
|
+
module_paths = %w[expressions/expression_helpers definitions/type_accessors definitions/redefiner].map do |path|
|
|
13
|
+
File.join(lib_path, "#{path}.rb")
|
|
14
|
+
end
|
|
13
15
|
|
|
14
16
|
HIDDEN_PATHS = [File.expand_path(__FILE__), *adapter_paths, *module_paths].freeze
|
|
15
17
|
|
data/lib/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lowtype
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- maedi
|
|
8
|
-
bindir:
|
|
8
|
+
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: dirload
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
12
26
|
- !ruby/object:Gem::Dependency
|
|
13
27
|
name: expressions
|
|
14
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -29,14 +43,28 @@ dependencies:
|
|
|
29
43
|
requirements:
|
|
30
44
|
- - "~>"
|
|
31
45
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '0.
|
|
46
|
+
version: '0.5'
|
|
33
47
|
type: :runtime
|
|
34
48
|
prerelease: false
|
|
35
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
50
|
requirements:
|
|
37
51
|
- - "~>"
|
|
38
52
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '0.
|
|
53
|
+
version: '0.5'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: trees
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
40
68
|
description: "LowType introduces the concept of \"type expressions\" in method arguments.
|
|
41
69
|
\nWhen an argument's default value resolves to a type instead of a value then it's
|
|
42
70
|
treated as a type expression. \nNow you can have types in Ruby in the simplest syntax
|
|
@@ -49,6 +77,13 @@ extra_rdoc_files: []
|
|
|
49
77
|
files:
|
|
50
78
|
- lib/adapters/adapter_loader.rb
|
|
51
79
|
- lib/adapters/sinatra_adapter.rb
|
|
80
|
+
- lib/cli/cli.rb
|
|
81
|
+
- lib/cli/rbs/class_sig.rb
|
|
82
|
+
- lib/cli/rbs/exporter.rb
|
|
83
|
+
- lib/cli/rbs/expression_mapper.rb
|
|
84
|
+
- lib/cli/rbs/method_sig.rb
|
|
85
|
+
- lib/cli/rbs/type_mapper.rb
|
|
86
|
+
- lib/cli/type_stripper.rb
|
|
52
87
|
- lib/definitions/evaluator.rb
|
|
53
88
|
- lib/definitions/redefiner.rb
|
|
54
89
|
- lib/definitions/type_accessors.rb
|