ruby-next-core 0.15.3 → 1.0.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 +34 -0
- data/README.md +127 -54
- 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 +59 -12
- data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +83 -3
- data/lib/.rbnext/2.3/ruby-next/config.rb +79 -0
- data/lib/.rbnext/2.3/ruby-next/core/data.rb +163 -0
- data/lib/.rbnext/2.3/ruby-next/language/eval.rb +4 -4
- 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 +163 -0
- data/lib/.rbnext/2.7/ruby-next/core/data.rb +163 -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 +85 -3
- data/lib/ruby-next/config.rb +29 -2
- data/lib/ruby-next/core/data.rb +163 -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/eval.rb +4 -4
- 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 +31 -6
- 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.1/shorthand_hash.rb +2 -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 +59 -12
- 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 +63 -10
@@ -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"
|
@@ -1,9 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
gem
|
4
|
-
|
3
|
+
# Checking gem specs doesn't work in ruby.wasm
|
4
|
+
unless RUBY_PLATFORM.match?(/wasm/)
|
5
|
+
gem "ruby-next-parser", ">= 2.8.0.3"
|
6
|
+
gem "unparser", ">= 0.4.7"
|
7
|
+
end
|
5
8
|
|
6
|
-
require "set"
|
9
|
+
require "set" # rubocop:disable Lint/RedundantRequireStatement
|
7
10
|
|
8
11
|
require "ruby-next"
|
9
12
|
|
@@ -62,8 +65,15 @@ module RubyNext
|
|
62
65
|
end
|
63
66
|
|
64
67
|
class << self
|
68
|
+
attr_reader :include_patterns
|
69
|
+
attr_reader :exclude_patterns
|
70
|
+
|
71
|
+
def watch_dirs
|
72
|
+
warn "[DEPRECATED] Use `RubyNext::Language.include_patterns` instead of `RubyNext::Language.watch_dirs`"
|
73
|
+
@watch_dirs
|
74
|
+
end
|
75
|
+
|
65
76
|
attr_accessor :rewriters
|
66
|
-
attr_reader :watch_dirs
|
67
77
|
|
68
78
|
attr_accessor :strategy
|
69
79
|
|
@@ -95,14 +105,17 @@ module RubyNext
|
|
95
105
|
end
|
96
106
|
|
97
107
|
def transform(source, rewriters: self.rewriters, using: RubyNext::Core.refine?, context: TransformContext.new)
|
108
|
+
text_rewriters, ast_rewriters = rewriters.partition(&:text?)
|
109
|
+
|
98
110
|
retried = 0
|
99
|
-
new_source =
|
111
|
+
new_source = text_rewrite(source, rewriters: text_rewriters, using: using, context: context)
|
112
|
+
|
100
113
|
begin
|
101
114
|
new_source =
|
102
115
|
if mode == :rewrite
|
103
|
-
rewrite(
|
116
|
+
rewrite(new_source, rewriters: ast_rewriters, using: using, context: context)
|
104
117
|
else
|
105
|
-
regenerate(
|
118
|
+
regenerate(new_source, rewriters: ast_rewriters, using: using, context: context)
|
106
119
|
end
|
107
120
|
rescue Unparser::UnknownNodeError => err
|
108
121
|
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 +132,17 @@ module RubyNext
|
|
119
132
|
Core.inject! new_source.dup
|
120
133
|
end
|
121
134
|
|
135
|
+
def target_dir?(dirname)
|
136
|
+
# fnmatch? requires a file name, not a folder
|
137
|
+
fname = File.join(dirname, "x.rb")
|
138
|
+
|
139
|
+
include_patterns.any? { |pattern| File.fnmatch?(pattern, fname) } &&
|
140
|
+
exclude_patterns.none? { |pattern| File.fnmatch?(pattern, fname) }
|
141
|
+
end
|
142
|
+
|
122
143
|
def transformable?(path)
|
123
|
-
|
144
|
+
include_patterns.any? { |pattern| File.fnmatch?(pattern, path) } &&
|
145
|
+
exclude_patterns.none? { |pattern| File.fnmatch?(pattern, path) }
|
124
146
|
end
|
125
147
|
|
126
148
|
# Rewriters required for the current version
|
@@ -165,14 +187,36 @@ module RubyNext
|
|
165
187
|
end
|
166
188
|
end
|
167
189
|
|
190
|
+
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"))
|
191
|
+
rewriters.inject(source) do |src, rewriter|
|
192
|
+
rewriter.new(context).rewrite(src)
|
193
|
+
end.then do |new_source|
|
194
|
+
next source unless context.dirty?
|
195
|
+
|
196
|
+
new_source
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
168
200
|
attr_writer :watch_dirs
|
201
|
+
attr_writer :include_patterns, :exclude_patterns
|
169
202
|
end
|
170
203
|
|
171
204
|
self.rewriters = []
|
172
|
-
self.watch_dirs =
|
205
|
+
self.watch_dirs = [].tap do |dirs|
|
206
|
+
# For backward compatibility
|
207
|
+
dirs.define_singleton_method(:<<) do |dir|
|
208
|
+
super(dir)
|
209
|
+
RubyNext::Language.include_patterns << File.join(dir, "*.rb")
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
self.include_patterns = %w[app lib spec test].map { |path| File.join(Dir.pwd, path, "*.rb") }
|
214
|
+
self.exclude_patterns = %w[vendor/bundle].map { |path| File.join(Dir.pwd, path, "*") }
|
173
215
|
self.mode = ENV.fetch("RUBY_NEXT_TRANSPILE_MODE", "rewrite").to_sym
|
174
216
|
|
217
|
+
require "ruby-next/language/rewriters/abstract"
|
175
218
|
require "ruby-next/language/rewriters/base"
|
219
|
+
require "ruby-next/language/rewriters/text"
|
176
220
|
|
177
221
|
require "ruby-next/language/rewriters/2.1/numeric_literals"
|
178
222
|
rewriters << Rewriters::NumericLiterals
|
@@ -212,7 +256,7 @@ module RubyNext
|
|
212
256
|
rewriters << Rewriters::InPattern
|
213
257
|
|
214
258
|
require "ruby-next/language/rewriters/3.0/endless_method"
|
215
|
-
|
259
|
+
rewriters << RubyNext::Language::Rewriters::EndlessMethod
|
216
260
|
|
217
261
|
require "ruby-next/language/rewriters/3.1/oneline_pattern_parensless"
|
218
262
|
rewriters << Rewriters::OnelinePatternParensless
|
@@ -232,16 +276,19 @@ module RubyNext
|
|
232
276
|
require "ruby-next/language/rewriters/3.1/shorthand_hash"
|
233
277
|
rewriters << RubyNext::Language::Rewriters::ShorthandHash
|
234
278
|
|
279
|
+
require "ruby-next/language/rewriters/3.2/anonymous_restargs"
|
280
|
+
rewriters << RubyNext::Language::Rewriters::AnonymousRestArgs
|
281
|
+
|
235
282
|
# Put endless range in the end, 'cause Parser fails to parse it in
|
236
283
|
# pattern matching
|
237
284
|
require "ruby-next/language/rewriters/2.6/endless_range"
|
238
285
|
rewriters << Rewriters::EndlessRange
|
239
286
|
|
240
|
-
if
|
287
|
+
if RubyNext.edge_syntax?
|
241
288
|
require "ruby-next/language/rewriters/edge"
|
242
289
|
end
|
243
290
|
|
244
|
-
if
|
291
|
+
if RubyNext.proposed_syntax?
|
245
292
|
require "ruby-next/language/rewriters/proposed"
|
246
293
|
end
|
247
294
|
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,12 +273,14 @@ 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
|
201
281
|
|
202
282
|
def next_dir_path
|
203
|
-
@next_dir_path ||=
|
283
|
+
@next_dir_path ||= out_path || File.join(lib_path, RUBY_NEXT_DIR)
|
204
284
|
end
|
205
285
|
|
206
286
|
def stdout?
|