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.
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