ruby-next-core 0.5.3 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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