ruby-next-core 0.15.3 → 1.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -0
  3. data/README.md +118 -48
  4. data/bin/mspec +11 -0
  5. data/lib/.rbnext/2.1/ruby-next/commands/nextify.rb +295 -0
  6. data/lib/.rbnext/2.1/ruby-next/core.rb +10 -2
  7. data/lib/.rbnext/2.1/ruby-next/language.rb +54 -10
  8. data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +82 -2
  9. data/lib/.rbnext/2.3/ruby-next/config.rb +79 -0
  10. data/lib/.rbnext/2.3/ruby-next/core/data.rb +159 -0
  11. data/lib/.rbnext/2.3/ruby-next/language/rewriters/2.7/pattern_matching.rb +2 -2
  12. data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +6 -32
  13. data/lib/.rbnext/2.3/ruby-next/utils.rb +3 -22
  14. data/lib/.rbnext/2.6/ruby-next/core/data.rb +159 -0
  15. data/lib/.rbnext/2.7/ruby-next/core/data.rb +159 -0
  16. data/lib/.rbnext/2.7/ruby-next/core.rb +10 -2
  17. data/lib/.rbnext/2.7/ruby-next/language/paco_parsers/string_literals.rb +109 -0
  18. data/lib/.rbnext/2.7/ruby-next/language/rewriters/2.7/pattern_matching.rb +2 -2
  19. data/lib/.rbnext/2.7/ruby-next/language/rewriters/text.rb +132 -0
  20. data/lib/.rbnext/3.2/ruby-next/commands/base.rb +55 -0
  21. data/lib/.rbnext/3.2/ruby-next/language/rewriters/2.7/pattern_matching.rb +1095 -0
  22. data/lib/.rbnext/3.2/ruby-next/rubocop.rb +210 -0
  23. data/lib/ruby-next/commands/nextify.rb +84 -2
  24. data/lib/ruby-next/config.rb +27 -0
  25. data/lib/ruby-next/core/data.rb +159 -0
  26. data/lib/ruby-next/core/matchdata/deconstruct.rb +9 -0
  27. data/lib/ruby-next/core/matchdata/deconstruct_keys.rb +20 -0
  28. data/lib/ruby-next/core/matchdata/named_captures.rb +11 -0
  29. data/lib/ruby-next/core/refinement/import.rb +44 -36
  30. data/lib/ruby-next/core/time/deconstruct_keys.rb +30 -0
  31. data/lib/ruby-next/core.rb +10 -2
  32. data/lib/ruby-next/irb.rb +2 -2
  33. data/lib/ruby-next/language/bootsnap.rb +2 -25
  34. data/lib/ruby-next/language/paco_parser.rb +7 -0
  35. data/lib/ruby-next/language/paco_parsers/base.rb +47 -0
  36. data/lib/ruby-next/language/paco_parsers/comments.rb +26 -0
  37. data/lib/ruby-next/language/paco_parsers/string_literals.rb +109 -0
  38. data/lib/ruby-next/language/parser.rb +24 -2
  39. data/lib/ruby-next/language/rewriters/3.0/args_forward_leading.rb +2 -2
  40. data/lib/ruby-next/language/rewriters/3.1/oneline_pattern_parensless.rb +1 -1
  41. data/lib/ruby-next/language/rewriters/3.2/anonymous_restargs.rb +104 -0
  42. data/lib/ruby-next/language/rewriters/abstract.rb +57 -0
  43. data/lib/ruby-next/language/rewriters/base.rb +6 -32
  44. data/lib/ruby-next/language/rewriters/edge/it_param.rb +58 -0
  45. data/lib/ruby-next/language/rewriters/edge.rb +12 -0
  46. data/lib/ruby-next/language/rewriters/proposed/bind_vars_pattern.rb +3 -0
  47. data/lib/ruby-next/language/rewriters/proposed/method_reference.rb +9 -20
  48. data/lib/ruby-next/language/rewriters/text.rb +132 -0
  49. data/lib/ruby-next/language/runtime.rb +9 -86
  50. data/lib/ruby-next/language/setup.rb +5 -2
  51. data/lib/ruby-next/language/unparser.rb +5 -0
  52. data/lib/ruby-next/language.rb +54 -10
  53. data/lib/ruby-next/pry.rb +1 -1
  54. data/lib/ruby-next/rubocop.rb +2 -0
  55. data/lib/ruby-next/utils.rb +3 -22
  56. data/lib/ruby-next/version.rb +1 -1
  57. data/lib/uby-next.rb +2 -2
  58. 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 = nil
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(source, rewriters: rewriters, using: using, context: context)
113
+ rewrite(new_source, rewriters: ast_rewriters, using: using, context: context)
104
114
  else
105
- regenerate(source, rewriters: rewriters, using: using, context: context)
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
- watch_dirs.any? { |dir| path.start_with?(dir) }
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 = %w[app lib spec test].map { |path| File.join(Dir.pwd, path) }
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
- RubyNext::Language.rewriters << RubyNext::Language::Rewriters::EndlessMethod
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 ENV["RUBY_NEXT_EDGE"] == "1"
284
+ if RubyNext.edge_syntax?
241
285
  require "ruby-next/language/rewriters/edge"
242
286
  end
243
287
 
244
- if ENV["RUBY_NEXT_PROPOSED"] == "1"
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
- attr_reader :lib_path, :paths, :out_path, :min_version, :single_version, :specified_rewriters
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