ruby-next-core 0.5.3 → 0.9.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/CHANGELOG.md +65 -0
- data/README.md +153 -40
- data/bin/transform +12 -5
- data/lib/ruby-next.rb +3 -2
- data/lib/ruby-next/cli.rb +17 -2
- data/lib/ruby-next/commands/base.rb +15 -2
- data/lib/ruby-next/commands/core_ext.rb +5 -3
- data/lib/ruby-next/commands/nextify.rb +47 -20
- data/lib/ruby-next/core.rb +13 -2
- data/lib/ruby-next/core/time/ceil.rb +2 -1
- data/lib/ruby-next/core_ext.rb +1 -1
- data/lib/ruby-next/language.rb +15 -4
- data/lib/ruby-next/language/edge.rb +9 -0
- data/lib/ruby-next/language/eval.rb +10 -8
- data/lib/ruby-next/language/parser.rb +6 -2
- data/lib/ruby-next/language/proposed.rb +6 -0
- data/lib/ruby-next/language/rewriters/args_forward.rb +10 -8
- data/lib/ruby-next/language/rewriters/base.rb +1 -5
- data/lib/ruby-next/language/rewriters/endless_method.rb +40 -0
- data/lib/ruby-next/language/rewriters/method_reference.rb +0 -6
- data/lib/ruby-next/language/rewriters/pattern_matching.rb +2 -1
- data/lib/ruby-next/language/rewriters/right_hand_assignment.rb +44 -0
- data/lib/ruby-next/language/rewriters/runtime.rb +6 -0
- data/lib/ruby-next/language/rewriters/runtime/dir.rb +32 -0
- data/lib/ruby-next/language/runtime.rb +3 -2
- data/lib/ruby-next/language/setup.rb +21 -2
- data/lib/ruby-next/logging.rb +1 -1
- data/lib/ruby-next/rubocop.rb +70 -1
- data/lib/ruby-next/utils.rb +30 -0
- data/lib/ruby-next/version.rb +1 -1
- data/lib/uby-next.rb +4 -0
- metadata +12 -6
data/lib/ruby-next/cli.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "ruby-next"
|
4
|
-
require "ruby-next/language"
|
5
4
|
|
6
5
|
require "ruby-next/commands/base"
|
7
6
|
require "ruby-next/commands/nextify"
|
@@ -11,10 +10,14 @@ module RubyNext
|
|
11
10
|
# Command line interface for RubyNext
|
12
11
|
class CLI
|
13
12
|
class << self
|
14
|
-
attr_accessor :verbose
|
13
|
+
attr_accessor :verbose, :dry_run
|
14
|
+
|
15
|
+
alias verbose? verbose
|
16
|
+
alias dry_run? dry_run
|
15
17
|
end
|
16
18
|
|
17
19
|
self.verbose = false
|
20
|
+
self.dry_run = false
|
18
21
|
|
19
22
|
COMMANDS = {
|
20
23
|
"nextify" => Commands::Nextify,
|
@@ -37,6 +40,8 @@ module RubyNext
|
|
37
40
|
|
38
41
|
args.delete(command)
|
39
42
|
|
43
|
+
args.unshift(*load_args_from_rc(command))
|
44
|
+
|
40
45
|
COMMANDS.fetch(command) do
|
41
46
|
raise "Unknown command: #{command}. Available commands: #{COMMANDS.keys.join(",")}"
|
42
47
|
end.run(args)
|
@@ -90,5 +95,15 @@ module RubyNext
|
|
90
95
|
end
|
91
96
|
end
|
92
97
|
end
|
98
|
+
|
99
|
+
def load_args_from_rc(command)
|
100
|
+
return [] unless File.file?(".rbnextrc")
|
101
|
+
|
102
|
+
require "yaml"
|
103
|
+
command_args = YAML.load_file(".rbnextrc")[command]
|
104
|
+
return [] unless command_args
|
105
|
+
|
106
|
+
command_args.lines.flat_map { |line| line.chomp.split(/\s+/) }
|
107
|
+
end
|
93
108
|
end
|
94
109
|
end
|
@@ -11,6 +11,9 @@ module RubyNext
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
attr_reader :dry_run
|
15
|
+
alias dry_run? dry_run
|
16
|
+
|
14
17
|
def initialize(args)
|
15
18
|
parse! args
|
16
19
|
end
|
@@ -24,8 +27,13 @@ module RubyNext
|
|
24
27
|
end
|
25
28
|
|
26
29
|
def log(msg)
|
27
|
-
return unless CLI.verbose
|
28
|
-
|
30
|
+
return unless CLI.verbose?
|
31
|
+
|
32
|
+
if CLI.dry_run?
|
33
|
+
$stdout.puts "[DRY RUN] #{msg}"
|
34
|
+
else
|
35
|
+
$stdout.puts msg
|
36
|
+
end
|
29
37
|
end
|
30
38
|
|
31
39
|
def base_parser
|
@@ -35,6 +43,11 @@ module RubyNext
|
|
35
43
|
opts.on("-V", "Turn on verbose mode") do
|
36
44
|
CLI.verbose = true
|
37
45
|
end
|
46
|
+
|
47
|
+
opts.on("--dry-run", "Print verbose output without generating files") do
|
48
|
+
CLI.dry_run = true
|
49
|
+
CLI.verbose = true
|
50
|
+
end
|
38
51
|
end
|
39
52
|
end
|
40
53
|
end
|
@@ -69,7 +69,7 @@ module RubyNext
|
|
69
69
|
RubyNext::Core.patches.extensions
|
70
70
|
.values
|
71
71
|
.flatten
|
72
|
-
.
|
72
|
+
.select do |patch|
|
73
73
|
next if min_version && Gem::Version.new(patch.version) <= min_version
|
74
74
|
next if filter && !filter.match?(patch.name)
|
75
75
|
true
|
@@ -136,8 +136,10 @@ module RubyNext
|
|
136
136
|
|
137
137
|
return $stdout.puts(contents) if out_path == "stdout"
|
138
138
|
|
139
|
-
|
140
|
-
|
139
|
+
unless CLI.dry_run?
|
140
|
+
FileUtils.mkdir_p File.dirname(out_path)
|
141
|
+
File.write(out_path, contents)
|
142
|
+
end
|
141
143
|
|
142
144
|
log "Generated: #{out_path}"
|
143
145
|
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require "fileutils"
|
4
4
|
require "pathname"
|
5
5
|
|
6
|
+
require "ruby-next/language"
|
7
|
+
|
6
8
|
module RubyNext
|
7
9
|
module Commands
|
8
10
|
class Nextify < Base
|
@@ -12,6 +14,10 @@ module RubyNext
|
|
12
14
|
|
13
15
|
def run
|
14
16
|
log "RubyNext core strategy: #{RubyNext::Core.strategy}"
|
17
|
+
log "RubyNext transpile mode: #{RubyNext::Language.mode}"
|
18
|
+
|
19
|
+
remove_rbnext!
|
20
|
+
|
15
21
|
paths.each do |path|
|
16
22
|
contents = File.read(path)
|
17
23
|
transpile path, contents
|
@@ -38,9 +44,12 @@ module RubyNext
|
|
38
44
|
@single_version = true
|
39
45
|
end
|
40
46
|
|
41
|
-
opts.on("--
|
42
|
-
require "ruby-next/language/
|
43
|
-
|
47
|
+
opts.on("--edge", "Enable edge (master) Ruby features") do |val|
|
48
|
+
require "ruby-next/language/edge"
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on("--proposed", "Enable proposed/experimental Ruby features") do |val|
|
52
|
+
require "ruby-next/language/proposed"
|
44
53
|
end
|
45
54
|
|
46
55
|
opts.on(
|
@@ -59,6 +68,8 @@ module RubyNext
|
|
59
68
|
end
|
60
69
|
end
|
61
70
|
|
71
|
+
optparser.parse!(args)
|
72
|
+
|
62
73
|
@lib_path = args[0]
|
63
74
|
|
64
75
|
if print_help
|
@@ -67,12 +78,11 @@ module RubyNext
|
|
67
78
|
end
|
68
79
|
|
69
80
|
unless lib_path&.then(&File.method(:exist?))
|
81
|
+
$stdout.puts "Path not found: #{lib_path}"
|
70
82
|
$stdout.puts optparser.help
|
71
83
|
exit 2
|
72
84
|
end
|
73
85
|
|
74
|
-
optparser.parse!(args)
|
75
|
-
|
76
86
|
@paths =
|
77
87
|
if File.directory?(lib_path)
|
78
88
|
Dir[File.join(lib_path, "**/*.rb")]
|
@@ -89,7 +99,13 @@ module RubyNext
|
|
89
99
|
rewriters = Language.rewriters.select { |rw| rw.unsupported_version?(version) }
|
90
100
|
|
91
101
|
context = Language::TransformContext.new
|
92
|
-
|
102
|
+
|
103
|
+
new_contents =
|
104
|
+
if Gem::Version.new(version) >= Gem::Version.new("2.7.0") && !defined?(Unparser::Emitter::CaseMatch)
|
105
|
+
Language.rewrite contents, context: context, rewriters: rewriters
|
106
|
+
else
|
107
|
+
Language.transform contents, context: context, rewriters: rewriters
|
108
|
+
end
|
93
109
|
|
94
110
|
return unless context.dirty?
|
95
111
|
|
@@ -106,34 +122,45 @@ module RubyNext
|
|
106
122
|
end
|
107
123
|
|
108
124
|
def save(contents, path, version)
|
109
|
-
return $stdout.puts(contents) if
|
125
|
+
return $stdout.puts(contents) if stdout?
|
110
126
|
|
111
127
|
paths = [Pathname.new(path).relative_path_from(Pathname.new(lib_path))]
|
112
128
|
|
113
129
|
paths.unshift(version.segments[0..1].join(".")) unless single_version?
|
114
130
|
|
115
131
|
next_path =
|
116
|
-
if
|
117
|
-
|
118
|
-
out_path
|
119
|
-
else
|
120
|
-
File.join(out_path, *paths)
|
121
|
-
end
|
132
|
+
if next_dir_path.end_with?(".rb")
|
133
|
+
out_path
|
122
134
|
else
|
123
|
-
File.join(
|
124
|
-
lib_path,
|
125
|
-
RUBY_NEXT_DIR,
|
126
|
-
*paths
|
127
|
-
)
|
135
|
+
File.join(next_dir_path, *paths)
|
128
136
|
end
|
129
137
|
|
130
|
-
|
138
|
+
unless CLI.dry_run?
|
139
|
+
FileUtils.mkdir_p File.dirname(next_path)
|
131
140
|
|
132
|
-
|
141
|
+
File.write(next_path, contents)
|
142
|
+
end
|
133
143
|
|
134
144
|
log "Generated: #{next_path}"
|
135
145
|
end
|
136
146
|
|
147
|
+
def remove_rbnext!
|
148
|
+
return if CLI.dry_run? || stdout?
|
149
|
+
|
150
|
+
return unless File.directory?(next_dir_path)
|
151
|
+
|
152
|
+
log "Remove old files: #{next_dir_path}"
|
153
|
+
FileUtils.rm_r(next_dir_path)
|
154
|
+
end
|
155
|
+
|
156
|
+
def next_dir_path
|
157
|
+
@next_dir_path ||= (out_path || File.join(lib_path, RUBY_NEXT_DIR))
|
158
|
+
end
|
159
|
+
|
160
|
+
def stdout?
|
161
|
+
out_path == "stdout"
|
162
|
+
end
|
163
|
+
|
137
164
|
alias single_version? single_version
|
138
165
|
end
|
139
166
|
end
|
data/lib/ruby-next/core.rb
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require "set"
|
4
4
|
|
5
|
+
require_relative "utils"
|
6
|
+
|
5
7
|
module RubyNext
|
6
8
|
module Core
|
7
9
|
# Patch contains the extension implementation
|
@@ -95,7 +97,7 @@ module RubyNext
|
|
95
97
|
end
|
96
98
|
|
97
99
|
class << self
|
98
|
-
STRATEGIES = %i[refine core_ext].freeze
|
100
|
+
STRATEGIES = %i[refine core_ext backports].freeze
|
99
101
|
|
100
102
|
attr_reader :strategy
|
101
103
|
|
@@ -109,7 +111,11 @@ module RubyNext
|
|
109
111
|
end
|
110
112
|
|
111
113
|
def core_ext?
|
112
|
-
strategy == :core_ext
|
114
|
+
strategy == :core_ext || strategy == :backports
|
115
|
+
end
|
116
|
+
|
117
|
+
def backports?
|
118
|
+
strategy == :backports
|
113
119
|
end
|
114
120
|
|
115
121
|
def patch(*args, **kwargs, &block)
|
@@ -136,6 +142,8 @@ module RubyNext
|
|
136
142
|
end
|
137
143
|
end
|
138
144
|
|
145
|
+
require "backports/2.5" if RubyNext::Core.backports?
|
146
|
+
|
139
147
|
require_relative "core/kernel/then"
|
140
148
|
|
141
149
|
require_relative "core/proc/compose"
|
@@ -176,6 +184,9 @@ require_relative "core/struct/deconstruct_keys"
|
|
176
184
|
# Generate refinements
|
177
185
|
RubyNext.module_eval do
|
178
186
|
RubyNext::Core.patches.refined.each do |mod, patches|
|
187
|
+
# Only refine modules when supported
|
188
|
+
next unless mod.is_a?(Class) || RubyNext::Utils.refine_modules?
|
189
|
+
|
179
190
|
refine mod do
|
180
191
|
patches.each do |patch|
|
181
192
|
module_eval(patch.body, *patch.location)
|
data/lib/ruby-next/core_ext.rb
CHANGED
data/lib/ruby-next/language.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
gem "parser", ">= 2.
|
3
|
+
gem "ruby-next-parser", ">= 2.8.0.3"
|
4
4
|
gem "unparser", ">= 0.4.7"
|
5
5
|
|
6
6
|
require "set"
|
@@ -83,6 +83,14 @@ module RubyNext
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def runtime!
|
86
|
+
require "ruby-next/language/rewriters/runtime"
|
87
|
+
|
88
|
+
if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.7.0") && !defined?(Unparser::Emitter::CaseMatch)
|
89
|
+
RubyNext.warn "Ruby Next fallbacks to \"rewrite\" transpiling mode since Unparser doesn't support 2.7 AST yet.\n" \
|
90
|
+
"See https://github.com/mbj/unparser/pull/142"
|
91
|
+
self.mode = :rewrite
|
92
|
+
end
|
93
|
+
|
86
94
|
@runtime = true
|
87
95
|
end
|
88
96
|
|
@@ -166,9 +174,12 @@ module RubyNext
|
|
166
174
|
require "ruby-next/language/rewriters/endless_range"
|
167
175
|
rewriters << Rewriters::EndlessRange
|
168
176
|
|
169
|
-
if ENV["
|
170
|
-
require "ruby-next/language/
|
171
|
-
|
177
|
+
if ENV["RUBY_NEXT_EDGE"] == "1"
|
178
|
+
require "ruby-next/language/edge"
|
179
|
+
end
|
180
|
+
|
181
|
+
if ENV["RUBY_NEXT_PROPOSED"] == "1"
|
182
|
+
require "ruby-next/language/proposed"
|
172
183
|
end
|
173
184
|
end
|
174
185
|
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Load edge Ruby features
|
4
|
+
|
5
|
+
require "ruby-next/language/rewriters/endless_method"
|
6
|
+
RubyNext::Language.rewriters << RubyNext::Language::Rewriters::EndlessMethod
|
7
|
+
|
8
|
+
require "ruby-next/language/rewriters/right_hand_assignment"
|
9
|
+
RubyNext::Language.rewriters << RubyNext::Language::Rewriters::RightHandAssignment
|
@@ -3,14 +3,16 @@
|
|
3
3
|
module RubyNext
|
4
4
|
module Language
|
5
5
|
module KernelEval
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
if Utils.refine_modules?
|
7
|
+
refine Kernel do
|
8
|
+
def eval(source, bind = nil, *args)
|
9
|
+
new_source = ::RubyNext::Language::Runtime.transform(
|
10
|
+
source,
|
11
|
+
using: bind&.receiver == TOPLEVEL_BINDING.receiver || bind&.receiver&.is_a?(Module)
|
12
|
+
)
|
13
|
+
RubyNext.debug_source(new_source, "(#{caller_locations(1, 1).first})")
|
14
|
+
super new_source, bind, *args
|
15
|
+
end
|
14
16
|
end
|
15
17
|
end
|
16
18
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "parser/
|
3
|
+
require "parser/rubynext"
|
4
4
|
|
5
5
|
module RubyNext
|
6
6
|
module Language
|
@@ -10,7 +10,7 @@ module RubyNext
|
|
10
10
|
|
11
11
|
class << self
|
12
12
|
def parser
|
13
|
-
::Parser::
|
13
|
+
::Parser::RubyNext.new(Builder.new).tap do |prs|
|
14
14
|
prs.diagnostics.tap do |diagnostics|
|
15
15
|
diagnostics.all_errors_are_fatal = true
|
16
16
|
end
|
@@ -23,6 +23,8 @@ module RubyNext
|
|
23
23
|
end
|
24
24
|
|
25
25
|
parser.parse(buffer)
|
26
|
+
rescue ::Parser::SyntaxError => e
|
27
|
+
raise ::SyntaxError, e.message
|
26
28
|
end
|
27
29
|
|
28
30
|
def parse_with_comments(source, file = "(string)")
|
@@ -31,6 +33,8 @@ module RubyNext
|
|
31
33
|
end
|
32
34
|
|
33
35
|
parser.parse_with_comments(buffer)
|
36
|
+
rescue ::Parser::SyntaxError => e
|
37
|
+
raise ::SyntaxError, e.message
|
34
38
|
end
|
35
39
|
end
|
36
40
|
end
|
@@ -25,21 +25,23 @@ module RubyNext
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def on_send(node)
|
28
|
-
return unless node.children[2]&.type == :forwarded_args
|
28
|
+
return super(node) unless node.children[2]&.type == :forwarded_args
|
29
29
|
|
30
30
|
replace(node.children[2].loc.expression, "*#{REST}, &#{BLOCK}")
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
32
|
+
process(
|
33
|
+
node.updated(
|
34
|
+
nil,
|
35
|
+
[
|
36
|
+
*node.children[0..1],
|
37
|
+
*forwarded_args
|
38
|
+
]
|
39
|
+
)
|
38
40
|
)
|
39
41
|
end
|
40
42
|
|
41
43
|
def on_super(node)
|
42
|
-
return unless node.children[0]&.type == :forwarded_args
|
44
|
+
return super(node) unless node.children[0]&.type == :forwarded_args
|
43
45
|
|
44
46
|
replace(node.children[0].loc.expression, "*#{REST}, &#{BLOCK}")
|
45
47
|
|