ruby-next-core 0.15.3 → 1.0.0.rc.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/CHANGELOG.md +28 -0
- data/README.md +118 -48
- data/bin/mspec +11 -0
- data/lib/.rbnext/2.1/ruby-next/commands/nextify.rb +295 -0
- data/lib/.rbnext/2.1/ruby-next/core.rb +10 -2
- data/lib/.rbnext/2.1/ruby-next/language.rb +54 -10
- data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +82 -2
- data/lib/.rbnext/2.3/ruby-next/config.rb +79 -0
- data/lib/.rbnext/2.3/ruby-next/core/data.rb +159 -0
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/2.7/pattern_matching.rb +2 -2
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +6 -32
- data/lib/.rbnext/2.3/ruby-next/utils.rb +3 -22
- data/lib/.rbnext/2.6/ruby-next/core/data.rb +159 -0
- data/lib/.rbnext/2.7/ruby-next/core/data.rb +159 -0
- data/lib/.rbnext/2.7/ruby-next/core.rb +10 -2
- data/lib/.rbnext/2.7/ruby-next/language/paco_parsers/string_literals.rb +109 -0
- data/lib/.rbnext/2.7/ruby-next/language/rewriters/2.7/pattern_matching.rb +2 -2
- data/lib/.rbnext/2.7/ruby-next/language/rewriters/text.rb +132 -0
- data/lib/.rbnext/3.2/ruby-next/commands/base.rb +55 -0
- data/lib/.rbnext/3.2/ruby-next/language/rewriters/2.7/pattern_matching.rb +1095 -0
- data/lib/.rbnext/3.2/ruby-next/rubocop.rb +210 -0
- data/lib/ruby-next/commands/nextify.rb +84 -2
- data/lib/ruby-next/config.rb +27 -0
- data/lib/ruby-next/core/data.rb +159 -0
- data/lib/ruby-next/core/matchdata/deconstruct.rb +9 -0
- data/lib/ruby-next/core/matchdata/deconstruct_keys.rb +20 -0
- data/lib/ruby-next/core/matchdata/named_captures.rb +11 -0
- data/lib/ruby-next/core/refinement/import.rb +44 -36
- data/lib/ruby-next/core/time/deconstruct_keys.rb +30 -0
- data/lib/ruby-next/core.rb +10 -2
- data/lib/ruby-next/irb.rb +2 -2
- data/lib/ruby-next/language/bootsnap.rb +2 -25
- data/lib/ruby-next/language/paco_parser.rb +7 -0
- data/lib/ruby-next/language/paco_parsers/base.rb +47 -0
- data/lib/ruby-next/language/paco_parsers/comments.rb +26 -0
- data/lib/ruby-next/language/paco_parsers/string_literals.rb +109 -0
- data/lib/ruby-next/language/parser.rb +24 -2
- data/lib/ruby-next/language/rewriters/3.0/args_forward_leading.rb +2 -2
- data/lib/ruby-next/language/rewriters/3.1/oneline_pattern_parensless.rb +1 -1
- data/lib/ruby-next/language/rewriters/3.2/anonymous_restargs.rb +104 -0
- data/lib/ruby-next/language/rewriters/abstract.rb +57 -0
- data/lib/ruby-next/language/rewriters/base.rb +6 -32
- data/lib/ruby-next/language/rewriters/edge/it_param.rb +58 -0
- data/lib/ruby-next/language/rewriters/edge.rb +12 -0
- data/lib/ruby-next/language/rewriters/proposed/bind_vars_pattern.rb +3 -0
- data/lib/ruby-next/language/rewriters/proposed/method_reference.rb +9 -20
- data/lib/ruby-next/language/rewriters/text.rb +132 -0
- data/lib/ruby-next/language/runtime.rb +9 -86
- data/lib/ruby-next/language/setup.rb +5 -2
- data/lib/ruby-next/language/unparser.rb +5 -0
- data/lib/ruby-next/language.rb +54 -10
- data/lib/ruby-next/pry.rb +1 -1
- data/lib/ruby-next/rubocop.rb +2 -0
- data/lib/ruby-next/utils.rb +3 -22
- data/lib/ruby-next/version.rb +1 -1
- data/lib/uby-next.rb +2 -2
- metadata +65 -12
@@ -0,0 +1,295 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require "pathname"
|
5
|
+
|
6
|
+
require "ruby-next/language"
|
7
|
+
|
8
|
+
module RubyNext
|
9
|
+
module Commands
|
10
|
+
class Nextify < Base
|
11
|
+
using RubyNext
|
12
|
+
|
13
|
+
class Stats
|
14
|
+
def initialize
|
15
|
+
@started_at = ::Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
16
|
+
@files = 0
|
17
|
+
@scans = 0
|
18
|
+
@transpiled_files = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def file!
|
22
|
+
@files += 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def scan!
|
26
|
+
@scans += 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def transpiled!
|
30
|
+
@transpiled_files += 1
|
31
|
+
end
|
32
|
+
|
33
|
+
def report
|
34
|
+
<<-TXT
|
35
|
+
Files processed: #{@files}
|
36
|
+
Total scans: #{@scans}
|
37
|
+
Files transpiled: #{@transpiled_files}
|
38
|
+
Completed in #{::Process.clock_gettime(Process::CLOCK_MONOTONIC) - @started_at}s
|
39
|
+
TXT
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :lib_path, :paths, :out_path, :min_version, :single_version, :specified_rewriters, :overwrite
|
44
|
+
alias_method :overwrite?, :overwrite
|
45
|
+
attr_reader :stats
|
46
|
+
|
47
|
+
def run
|
48
|
+
@stats = Stats.new
|
49
|
+
|
50
|
+
log "RubyNext core strategy: #{RubyNext::Core.strategy}"
|
51
|
+
log "RubyNext transpile mode: #{RubyNext::Language.mode}"
|
52
|
+
|
53
|
+
remove_rbnext!
|
54
|
+
|
55
|
+
@min_version ||= MIN_SUPPORTED_VERSION
|
56
|
+
|
57
|
+
paths.each do |path|
|
58
|
+
stats.file!
|
59
|
+
|
60
|
+
contents = File.read(path)
|
61
|
+
transpile path, contents
|
62
|
+
end
|
63
|
+
|
64
|
+
ensure_rbnext!
|
65
|
+
|
66
|
+
log stats.report
|
67
|
+
end
|
68
|
+
|
69
|
+
def parse!(args)
|
70
|
+
print_help = false
|
71
|
+
print_rewriters = false
|
72
|
+
rewriter_names = []
|
73
|
+
custom_rewriters = []
|
74
|
+
@single_version = false
|
75
|
+
@overwrite = false
|
76
|
+
|
77
|
+
optparser = base_parser do |opts|
|
78
|
+
opts.banner = "Usage: ruby-next nextify DIRECTORY_OR_FILE [options]"
|
79
|
+
|
80
|
+
opts.on("-o", "--output=OUTPUT", "Specify output directory or file or stdout") do |val|
|
81
|
+
@out_path = val
|
82
|
+
end
|
83
|
+
|
84
|
+
opts.on("--min-version=VERSION", "Specify the minimum Ruby version to support") do |val|
|
85
|
+
@min_version = Gem::Version.new(val)
|
86
|
+
end
|
87
|
+
|
88
|
+
opts.on("--single-version", "Only create one version of a file (for the earliest Ruby version)") do
|
89
|
+
@single_version = true
|
90
|
+
end
|
91
|
+
|
92
|
+
opts.on("--overwrite", "Overwrite original file") do
|
93
|
+
@overwrite = true
|
94
|
+
end
|
95
|
+
|
96
|
+
opts.on("--edge", "Enable edge (master) Ruby features") do |val|
|
97
|
+
ENV["RUBY_NEXT_EDGE"] = val.to_s
|
98
|
+
require "ruby-next/language/rewriters/edge"
|
99
|
+
end
|
100
|
+
|
101
|
+
opts.on("--proposed", "Enable proposed/experimental Ruby features") do |val|
|
102
|
+
ENV["RUBY_NEXT_PROPOSED"] = val.to_s
|
103
|
+
require "ruby-next/language/rewriters/proposed"
|
104
|
+
end
|
105
|
+
|
106
|
+
opts.on(
|
107
|
+
"--transpile-mode=MODE",
|
108
|
+
"Transpiler mode (ast or rewrite). Default: rewrite"
|
109
|
+
) do |val|
|
110
|
+
Language.mode = val.to_sym
|
111
|
+
end
|
112
|
+
|
113
|
+
opts.on("--[no-]refine", "Do not inject `using RubyNext`") do |val|
|
114
|
+
Core.strategy = :core_ext unless val
|
115
|
+
end
|
116
|
+
|
117
|
+
opts.on("--list-rewriters", "List available rewriters") do |val|
|
118
|
+
print_rewriters = true
|
119
|
+
end
|
120
|
+
|
121
|
+
opts.on("--rewrite=REWRITERS...", "Specify particular Ruby features to rewrite") do |val|
|
122
|
+
rewriter_names << val
|
123
|
+
end
|
124
|
+
|
125
|
+
opts.on("--import-rewriter=REWRITERS...", "Specify paths to load custom rewritiers") do |val|
|
126
|
+
custom_rewriters << val
|
127
|
+
end
|
128
|
+
|
129
|
+
opts.on("-h", "--help", "Print help") do
|
130
|
+
print_help = true
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
optparser.parse!(args)
|
135
|
+
|
136
|
+
@lib_path = args[0]
|
137
|
+
|
138
|
+
if print_help
|
139
|
+
$stdout.puts optparser.help
|
140
|
+
exit 0
|
141
|
+
end
|
142
|
+
|
143
|
+
# Load custom rewriters
|
144
|
+
custom_rewriters.each do |path|
|
145
|
+
Kernel.load path
|
146
|
+
end
|
147
|
+
|
148
|
+
if print_rewriters
|
149
|
+
Language.rewriters.each do |rewriter|
|
150
|
+
$stdout.puts "#{rewriter::NAME} (\"#{rewriter::SYNTAX_PROBE}\")#{rewriter.unsupported_syntax? ? " (unsupported)" : ""}"
|
151
|
+
end
|
152
|
+
exit 0
|
153
|
+
end
|
154
|
+
|
155
|
+
unless ((((__safe_lvar__ = lib_path) || true) && (!__safe_lvar__.nil? || nil)) && __safe_lvar__.then(&File.method(:exist?)))
|
156
|
+
$stdout.puts "Path not found: #{lib_path}"
|
157
|
+
$stdout.puts optparser.help
|
158
|
+
exit 2
|
159
|
+
end
|
160
|
+
|
161
|
+
if rewriter_names.any? && min_version
|
162
|
+
$stdout.puts "--rewrite cannot be used with --min-version simultaneously"
|
163
|
+
exit 2
|
164
|
+
end
|
165
|
+
|
166
|
+
@specified_rewriters =
|
167
|
+
if rewriter_names.any?
|
168
|
+
begin
|
169
|
+
Language.select_rewriters(*rewriter_names)
|
170
|
+
rescue Language::RewriterNotFoundError => error
|
171
|
+
$stdout.puts error.message
|
172
|
+
$stdout.puts "Try --list-rewriters to see list of available rewriters"
|
173
|
+
exit 2
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
if overwrite? && !single_version?
|
178
|
+
$stdout.puts "--overwrite only works with --single-version or explcit rewritires specified (via --rewrite)"
|
179
|
+
exit 2
|
180
|
+
end
|
181
|
+
|
182
|
+
@paths =
|
183
|
+
if File.directory?(lib_path)
|
184
|
+
Dir[File.join(lib_path, "**/*.rb")]
|
185
|
+
elsif File.file?(lib_path)
|
186
|
+
[lib_path].tap do |_|
|
187
|
+
@lib_path = File.dirname(lib_path)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
|
194
|
+
def transpile(path, contents, version: min_version)
|
195
|
+
stats.scan!
|
196
|
+
|
197
|
+
rewriters = specified_rewriters || Language.rewriters.select { |rw| rw.unsupported_version?(version) }
|
198
|
+
|
199
|
+
context = Language::TransformContext.new
|
200
|
+
|
201
|
+
new_contents = Language.transform contents, context: context, rewriters: rewriters
|
202
|
+
|
203
|
+
return unless context.dirty?
|
204
|
+
|
205
|
+
versions = context.sorted_versions
|
206
|
+
version = versions.shift
|
207
|
+
|
208
|
+
# First, store already transpiled contents in the minimum required version dir
|
209
|
+
save new_contents, path, version
|
210
|
+
|
211
|
+
return if versions.empty? || single_version?
|
212
|
+
|
213
|
+
# Then, generate the source code for the next version
|
214
|
+
transpile path, contents, version: version
|
215
|
+
rescue SyntaxError, StandardError => e
|
216
|
+
warn "Failed to transpile #{path}: #{e.class} — #{e.message}"
|
217
|
+
warn e.backtrace.take(10).join("\n") if ENV["RUBY_NEXT_DEBUG"] == "1"
|
218
|
+
exit 1
|
219
|
+
end
|
220
|
+
|
221
|
+
def save(contents, path, version)
|
222
|
+
stats.transpiled!
|
223
|
+
|
224
|
+
return $stdout.puts(contents) if stdout?
|
225
|
+
|
226
|
+
paths = [Pathname.new(path).relative_path_from(Pathname.new(lib_path))]
|
227
|
+
|
228
|
+
paths.unshift(version.segments[0..1].join(".")) unless single_version?
|
229
|
+
|
230
|
+
if overwrite?
|
231
|
+
overwrite_file_content!(path: path, contents: contents)
|
232
|
+
|
233
|
+
return
|
234
|
+
end
|
235
|
+
|
236
|
+
next_path =
|
237
|
+
if next_dir_path.end_with?(".rb")
|
238
|
+
out_path
|
239
|
+
else
|
240
|
+
File.join(next_dir_path, *paths)
|
241
|
+
end
|
242
|
+
|
243
|
+
unless CLI.dry_run?
|
244
|
+
FileUtils.mkdir_p File.dirname(next_path)
|
245
|
+
|
246
|
+
File.write(next_path, contents)
|
247
|
+
end
|
248
|
+
|
249
|
+
log "Generated: #{next_path}"
|
250
|
+
end
|
251
|
+
|
252
|
+
def overwrite_file_content!(path: ::Kernel.raise(::ArgumentError, "missing keyword: path"), contents: ::Kernel.raise(::ArgumentError, "missing keyword: contents"))
|
253
|
+
unless CLI.dry_run?
|
254
|
+
File.write(path, contents)
|
255
|
+
end
|
256
|
+
|
257
|
+
log "Overwritten: #{path}"
|
258
|
+
end
|
259
|
+
|
260
|
+
def remove_rbnext!
|
261
|
+
return if CLI.dry_run? || stdout?
|
262
|
+
|
263
|
+
return unless File.directory?(next_dir_path)
|
264
|
+
|
265
|
+
log "Remove old files: #{next_dir_path}"
|
266
|
+
FileUtils.rm_r(next_dir_path)
|
267
|
+
end
|
268
|
+
|
269
|
+
def ensure_rbnext!
|
270
|
+
return if CLI.dry_run? || stdout?
|
271
|
+
|
272
|
+
return if File.directory?(next_dir_path)
|
273
|
+
|
274
|
+
return if next_dir_path.end_with?(".rb")
|
275
|
+
|
276
|
+
return if overwrite?
|
277
|
+
|
278
|
+
FileUtils.mkdir_p next_dir_path
|
279
|
+
File.write(File.join(next_dir_path, ".keep"), "")
|
280
|
+
end
|
281
|
+
|
282
|
+
def next_dir_path
|
283
|
+
@next_dir_path ||= (out_path || File.join(lib_path, RUBY_NEXT_DIR))
|
284
|
+
end
|
285
|
+
|
286
|
+
def stdout?
|
287
|
+
out_path == "stdout"
|
288
|
+
end
|
289
|
+
|
290
|
+
def single_version?
|
291
|
+
single_version || specified_rewriters
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "set"
|
3
|
+
require "set" # rubocop:disable Lint/RedundantRequireStatement
|
4
4
|
|
5
5
|
require "ruby-next/config"
|
6
6
|
require "ruby-next/utils"
|
@@ -78,7 +78,7 @@ module RubyNext
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def native_location?(location)
|
81
|
-
location.nil? || location.first.match?(/(<internal:|resource:\/truffleruby\/core)/)
|
81
|
+
location.nil? || location.first.match?(/(<internal:|resource:\/truffleruby\/core|uri:classloader:\/jruby)/)
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
@@ -197,6 +197,11 @@ require "ruby-next/core/matchdata/match"
|
|
197
197
|
require "ruby-next/core/enumerable/compact"
|
198
198
|
require "ruby-next/core/integer/try_convert"
|
199
199
|
|
200
|
+
require "ruby-next/core/matchdata/deconstruct"
|
201
|
+
require "ruby-next/core/matchdata/deconstruct_keys"
|
202
|
+
require "ruby-next/core/matchdata/named_captures"
|
203
|
+
require "ruby-next/core/time/deconstruct_keys"
|
204
|
+
|
200
205
|
# Generate refinements
|
201
206
|
RubyNext.module_eval do
|
202
207
|
RubyNext::Core.patches.refined.each do |mod, patches|
|
@@ -210,3 +215,6 @@ RubyNext.module_eval do
|
|
210
215
|
end
|
211
216
|
end
|
212
217
|
end
|
218
|
+
|
219
|
+
# Load backports
|
220
|
+
require "ruby-next/core/data" unless ENV["RUBY_NEXT_DISABLE_DATA"] == "true"
|
@@ -3,7 +3,7 @@
|
|
3
3
|
gem "ruby-next-parser", ">= 2.8.0.3"
|
4
4
|
gem "unparser", ">= 0.4.7"
|
5
5
|
|
6
|
-
require "set"
|
6
|
+
require "set" # rubocop:disable Lint/RedundantRequireStatement
|
7
7
|
|
8
8
|
require "ruby-next"
|
9
9
|
|
@@ -62,8 +62,15 @@ module RubyNext
|
|
62
62
|
end
|
63
63
|
|
64
64
|
class << self
|
65
|
+
attr_reader :include_patterns
|
66
|
+
attr_reader :exclude_patterns
|
67
|
+
|
68
|
+
def watch_dirs
|
69
|
+
warn "[DEPRECATED] Use `RubyNext::Language.include_patterns` instead of `RubyNext::Language.watch_dirs`"
|
70
|
+
@watch_dirs
|
71
|
+
end
|
72
|
+
|
65
73
|
attr_accessor :rewriters
|
66
|
-
attr_reader :watch_dirs
|
67
74
|
|
68
75
|
attr_accessor :strategy
|
69
76
|
|
@@ -95,14 +102,17 @@ module RubyNext
|
|
95
102
|
end
|
96
103
|
|
97
104
|
def transform(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
|
105
|
+
text_rewriters, ast_rewriters = rewriters.partition(&:text?)
|
106
|
+
|
98
107
|
retried = 0
|
99
|
-
new_source =
|
108
|
+
new_source = text_rewrite(source, rewriters: text_rewriters, using: using, context: context)
|
109
|
+
|
100
110
|
begin
|
101
111
|
new_source =
|
102
112
|
if mode == :rewrite
|
103
|
-
rewrite(
|
113
|
+
rewrite(new_source, rewriters: ast_rewriters, using: using, context: context)
|
104
114
|
else
|
105
|
-
regenerate(
|
115
|
+
regenerate(new_source, rewriters: ast_rewriters, using: using, context: context)
|
106
116
|
end
|
107
117
|
rescue Unparser::UnknownNodeError => err
|
108
118
|
RubyNext.warn "Ruby Next fallbacks to \"rewrite\" transpiling mode since the version of Unparser you use doesn't support some syntax yet: #{err.message}.\n" \
|
@@ -119,8 +129,17 @@ module RubyNext
|
|
119
129
|
Core.inject! new_source.dup
|
120
130
|
end
|
121
131
|
|
132
|
+
def target_dir?(dirname)
|
133
|
+
# fnmatch? requires a file name, not a folder
|
134
|
+
fname = File.join(dirname, "x.rb")
|
135
|
+
|
136
|
+
include_patterns.any? { |pattern| File.fnmatch?(pattern, fname) } &&
|
137
|
+
exclude_patterns.none? { |pattern| File.fnmatch?(pattern, fname) }
|
138
|
+
end
|
139
|
+
|
122
140
|
def transformable?(path)
|
123
|
-
|
141
|
+
include_patterns.any? { |pattern| File.fnmatch?(pattern, path) } &&
|
142
|
+
exclude_patterns.none? { |pattern| File.fnmatch?(pattern, path) }
|
124
143
|
end
|
125
144
|
|
126
145
|
# Rewriters required for the current version
|
@@ -165,14 +184,36 @@ module RubyNext
|
|
165
184
|
end
|
166
185
|
end
|
167
186
|
|
187
|
+
def text_rewrite(source, rewriters: ::Kernel.raise(::ArgumentError, "missing keyword: rewriters"), using: ::Kernel.raise(::ArgumentError, "missing keyword: using"), context: ::Kernel.raise(::ArgumentError, "missing keyword: context"))
|
188
|
+
rewriters.inject(source) do |src, rewriter|
|
189
|
+
rewriter.new(context).rewrite(src)
|
190
|
+
end.then do |new_source|
|
191
|
+
next source unless context.dirty?
|
192
|
+
|
193
|
+
new_source
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
168
197
|
attr_writer :watch_dirs
|
198
|
+
attr_writer :include_patterns, :exclude_patterns
|
169
199
|
end
|
170
200
|
|
171
201
|
self.rewriters = []
|
172
|
-
self.watch_dirs =
|
202
|
+
self.watch_dirs = [].tap do |dirs|
|
203
|
+
# For backward compatibility
|
204
|
+
dirs.define_singleton_method(:<<) do |dir|
|
205
|
+
super(dir)
|
206
|
+
RubyNext::Language.include_patterns << File.join(dir, "*.rb")
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
self.include_patterns = %w[app lib spec test].map { |path| File.join(Dir.pwd, path, "*.rb") }
|
211
|
+
self.exclude_patterns = %w[vendor/bundle].map { |path| File.join(Dir.pwd, path, "*") }
|
173
212
|
self.mode = ENV.fetch("RUBY_NEXT_TRANSPILE_MODE", "rewrite").to_sym
|
174
213
|
|
214
|
+
require "ruby-next/language/rewriters/abstract"
|
175
215
|
require "ruby-next/language/rewriters/base"
|
216
|
+
require "ruby-next/language/rewriters/text"
|
176
217
|
|
177
218
|
require "ruby-next/language/rewriters/2.1/numeric_literals"
|
178
219
|
rewriters << Rewriters::NumericLiterals
|
@@ -212,7 +253,7 @@ module RubyNext
|
|
212
253
|
rewriters << Rewriters::InPattern
|
213
254
|
|
214
255
|
require "ruby-next/language/rewriters/3.0/endless_method"
|
215
|
-
|
256
|
+
rewriters << RubyNext::Language::Rewriters::EndlessMethod
|
216
257
|
|
217
258
|
require "ruby-next/language/rewriters/3.1/oneline_pattern_parensless"
|
218
259
|
rewriters << Rewriters::OnelinePatternParensless
|
@@ -232,16 +273,19 @@ module RubyNext
|
|
232
273
|
require "ruby-next/language/rewriters/3.1/shorthand_hash"
|
233
274
|
rewriters << RubyNext::Language::Rewriters::ShorthandHash
|
234
275
|
|
276
|
+
require "ruby-next/language/rewriters/3.2/anonymous_restargs"
|
277
|
+
rewriters << RubyNext::Language::Rewriters::AnonymousRestArgs
|
278
|
+
|
235
279
|
# Put endless range in the end, 'cause Parser fails to parse it in
|
236
280
|
# pattern matching
|
237
281
|
require "ruby-next/language/rewriters/2.6/endless_range"
|
238
282
|
rewriters << Rewriters::EndlessRange
|
239
283
|
|
240
|
-
if
|
284
|
+
if RubyNext.edge_syntax?
|
241
285
|
require "ruby-next/language/rewriters/edge"
|
242
286
|
end
|
243
287
|
|
244
|
-
if
|
288
|
+
if RubyNext.proposed_syntax?
|
245
289
|
require "ruby-next/language/rewriters/proposed"
|
246
290
|
end
|
247
291
|
end
|
@@ -10,9 +10,43 @@ module RubyNext
|
|
10
10
|
class Nextify < Base
|
11
11
|
using RubyNext
|
12
12
|
|
13
|
-
|
13
|
+
class Stats
|
14
|
+
def initialize
|
15
|
+
@started_at = ::Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
16
|
+
@files = 0
|
17
|
+
@scans = 0
|
18
|
+
@transpiled_files = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def file!
|
22
|
+
@files += 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def scan!
|
26
|
+
@scans += 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def transpiled!
|
30
|
+
@transpiled_files += 1
|
31
|
+
end
|
32
|
+
|
33
|
+
def report
|
34
|
+
<<-TXT
|
35
|
+
Files processed: #{@files}
|
36
|
+
Total scans: #{@scans}
|
37
|
+
Files transpiled: #{@transpiled_files}
|
38
|
+
Completed in #{::Process.clock_gettime(Process::CLOCK_MONOTONIC) - @started_at}s
|
39
|
+
TXT
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :lib_path, :paths, :out_path, :min_version, :single_version, :specified_rewriters, :overwrite
|
44
|
+
alias_method :overwrite?, :overwrite
|
45
|
+
attr_reader :stats
|
14
46
|
|
15
47
|
def run
|
48
|
+
@stats = Stats.new
|
49
|
+
|
16
50
|
log "RubyNext core strategy: #{RubyNext::Core.strategy}"
|
17
51
|
log "RubyNext transpile mode: #{RubyNext::Language.mode}"
|
18
52
|
|
@@ -21,18 +55,24 @@ module RubyNext
|
|
21
55
|
@min_version ||= MIN_SUPPORTED_VERSION
|
22
56
|
|
23
57
|
paths.each do |path|
|
58
|
+
stats.file!
|
59
|
+
|
24
60
|
contents = File.read(path)
|
25
61
|
transpile path, contents
|
26
62
|
end
|
27
63
|
|
28
64
|
ensure_rbnext!
|
65
|
+
|
66
|
+
log stats.report
|
29
67
|
end
|
30
68
|
|
31
69
|
def parse!(args)
|
32
70
|
print_help = false
|
33
71
|
print_rewriters = false
|
34
72
|
rewriter_names = []
|
73
|
+
custom_rewriters = []
|
35
74
|
@single_version = false
|
75
|
+
@overwrite = false
|
36
76
|
|
37
77
|
optparser = base_parser do |opts|
|
38
78
|
opts.banner = "Usage: ruby-next nextify DIRECTORY_OR_FILE [options]"
|
@@ -49,11 +89,17 @@ module RubyNext
|
|
49
89
|
@single_version = true
|
50
90
|
end
|
51
91
|
|
92
|
+
opts.on("--overwrite", "Overwrite original file") do
|
93
|
+
@overwrite = true
|
94
|
+
end
|
95
|
+
|
52
96
|
opts.on("--edge", "Enable edge (master) Ruby features") do |val|
|
97
|
+
ENV["RUBY_NEXT_EDGE"] = val.to_s
|
53
98
|
require "ruby-next/language/rewriters/edge"
|
54
99
|
end
|
55
100
|
|
56
101
|
opts.on("--proposed", "Enable proposed/experimental Ruby features") do |val|
|
102
|
+
ENV["RUBY_NEXT_PROPOSED"] = val.to_s
|
57
103
|
require "ruby-next/language/rewriters/proposed"
|
58
104
|
end
|
59
105
|
|
@@ -76,6 +122,10 @@ module RubyNext
|
|
76
122
|
rewriter_names << val
|
77
123
|
end
|
78
124
|
|
125
|
+
opts.on("--import-rewriter=REWRITERS...", "Specify paths to load custom rewritiers") do |val|
|
126
|
+
custom_rewriters << val
|
127
|
+
end
|
128
|
+
|
79
129
|
opts.on("-h", "--help", "Print help") do
|
80
130
|
print_help = true
|
81
131
|
end
|
@@ -90,9 +140,14 @@ module RubyNext
|
|
90
140
|
exit 0
|
91
141
|
end
|
92
142
|
|
143
|
+
# Load custom rewriters
|
144
|
+
custom_rewriters.each do |path|
|
145
|
+
Kernel.load path
|
146
|
+
end
|
147
|
+
|
93
148
|
if print_rewriters
|
94
149
|
Language.rewriters.each do |rewriter|
|
95
|
-
$stdout.puts "#{rewriter::NAME} (\"#{rewriter::SYNTAX_PROBE}\")"
|
150
|
+
$stdout.puts "#{rewriter::NAME} (\"#{rewriter::SYNTAX_PROBE}\")#{rewriter.unsupported_syntax? ? " (unsupported)" : ""}"
|
96
151
|
end
|
97
152
|
exit 0
|
98
153
|
end
|
@@ -119,6 +174,11 @@ module RubyNext
|
|
119
174
|
end
|
120
175
|
end
|
121
176
|
|
177
|
+
if overwrite? && !single_version?
|
178
|
+
$stdout.puts "--overwrite only works with --single-version or explcit rewritires specified (via --rewrite)"
|
179
|
+
exit 2
|
180
|
+
end
|
181
|
+
|
122
182
|
@paths =
|
123
183
|
if File.directory?(lib_path)
|
124
184
|
Dir[File.join(lib_path, "**/*.rb")]
|
@@ -132,6 +192,8 @@ module RubyNext
|
|
132
192
|
private
|
133
193
|
|
134
194
|
def transpile(path, contents, version: min_version)
|
195
|
+
stats.scan!
|
196
|
+
|
135
197
|
rewriters = specified_rewriters || Language.rewriters.select { |rw| rw.unsupported_version?(version) }
|
136
198
|
|
137
199
|
context = Language::TransformContext.new
|
@@ -157,12 +219,20 @@ module RubyNext
|
|
157
219
|
end
|
158
220
|
|
159
221
|
def save(contents, path, version)
|
222
|
+
stats.transpiled!
|
223
|
+
|
160
224
|
return $stdout.puts(contents) if stdout?
|
161
225
|
|
162
226
|
paths = [Pathname.new(path).relative_path_from(Pathname.new(lib_path))]
|
163
227
|
|
164
228
|
paths.unshift(version.segments[0..1].join(".")) unless single_version?
|
165
229
|
|
230
|
+
if overwrite?
|
231
|
+
overwrite_file_content!(path: path, contents: contents)
|
232
|
+
|
233
|
+
return
|
234
|
+
end
|
235
|
+
|
166
236
|
next_path =
|
167
237
|
if next_dir_path.end_with?(".rb")
|
168
238
|
out_path
|
@@ -179,6 +249,14 @@ module RubyNext
|
|
179
249
|
log "Generated: #{next_path}"
|
180
250
|
end
|
181
251
|
|
252
|
+
def overwrite_file_content!(path:, contents:)
|
253
|
+
unless CLI.dry_run?
|
254
|
+
File.write(path, contents)
|
255
|
+
end
|
256
|
+
|
257
|
+
log "Overwritten: #{path}"
|
258
|
+
end
|
259
|
+
|
182
260
|
def remove_rbnext!
|
183
261
|
return if CLI.dry_run? || stdout?
|
184
262
|
|
@@ -195,6 +273,8 @@ module RubyNext
|
|
195
273
|
|
196
274
|
return if next_dir_path.end_with?(".rb")
|
197
275
|
|
276
|
+
return if overwrite?
|
277
|
+
|
198
278
|
FileUtils.mkdir_p next_dir_path
|
199
279
|
File.write(File.join(next_dir_path, ".keep"), "")
|
200
280
|
end
|