ruby-next-core 0.5.3 → 0.9.0

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