ruby-next 0.0.1.26 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +201 -11
  5. data/bin/parse +19 -0
  6. data/bin/ruby-next +16 -0
  7. data/bin/transform +18 -0
  8. data/lib/ruby-next/cli.rb +55 -0
  9. data/lib/ruby-next/commands/base.rb +42 -0
  10. data/lib/ruby-next/commands/nextify.rb +113 -0
  11. data/lib/ruby-next/core/array/difference_union_intersection.rb +31 -0
  12. data/lib/ruby-next/core/enumerable/filter.rb +23 -0
  13. data/lib/ruby-next/core/enumerable/filter_map.rb +50 -0
  14. data/lib/ruby-next/core/enumerable/tally.rb +28 -0
  15. data/lib/ruby-next/core/hash/merge.rb +16 -0
  16. data/lib/ruby-next/core/kernel/then.rb +12 -0
  17. data/lib/ruby-next/core/pattern_matching.rb +37 -0
  18. data/lib/ruby-next/core/proc/compose.rb +21 -0
  19. data/lib/ruby-next/core/runtime.rb +10 -0
  20. data/lib/ruby-next/core.rb +28 -0
  21. data/lib/ruby-next/language/parser.rb +23 -0
  22. data/lib/ruby-next/language/rewriters/args_forward.rb +57 -0
  23. data/lib/ruby-next/language/rewriters/base.rb +87 -0
  24. data/lib/ruby-next/language/rewriters/endless_range.rb +60 -0
  25. data/lib/ruby-next/language/rewriters/method_reference.rb +27 -0
  26. data/lib/ruby-next/language/rewriters/numbered_params.rb +41 -0
  27. data/lib/ruby-next/language/rewriters/pattern_matching.rb +464 -0
  28. data/lib/ruby-next/language/runtime.rb +149 -0
  29. data/lib/ruby-next/language/setup.rb +43 -0
  30. data/lib/ruby-next/language/unparser.rb +23 -0
  31. data/lib/ruby-next/language.rb +100 -0
  32. data/lib/ruby-next/utils.rb +39 -0
  33. data/lib/ruby-next/version.rb +5 -0
  34. data/lib/ruby-next.rb +37 -0
  35. data/lib/uby-next.rb +66 -0
  36. metadata +69 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75e0c4e0c3075a5f795dc502f0c5ded97f81655f2fa44a6dcc1097f4121cf6cc
4
- data.tar.gz: 4ab75f77f5ac5bd8669709d9af390dbeddcd685973fba556167ab37c67b3d052
3
+ metadata.gz: a874763234ae5833fd2b6617be24fb2e600acc49c62fd39707d8da73d5810656
4
+ data.tar.gz: c6e73faa43b329ac1c3e0d2daff8689d677d19ed28ffb2ac8d09dea526ac1a1c
5
5
  SHA512:
6
- metadata.gz: c4db7480850615e3a6fdfcdf59790d11fa5c1b4aa53a6f6f40e5c9741447d5286a35a1dfbc180640fc760264dabbe1ffc7d069b8e3fd94749bc5f92dfcd48783
7
- data.tar.gz: fb790e86e46637ad819cb4b9c9b01bb2007c1a5d25bbe06edde791f6e02c6f50bc442619aa5ed9e237224354ca45d436879a8fb3751da96f855b2ac368f656e6
6
+ metadata.gz: a9f7d9dff8b79bcc2121b7dbaba38cd8ac5a578b249b463fa6c7a5ef25845fb12537b82e3ee240463d8156512c2039245b0465fb56249f2a6812ce1ce3ff971b
7
+ data.tar.gz: e909ab9742e7e1d7a32f00465bfe84c7ba3ad197b126842131a8b4601ab015cb7bf29b8c8aef7d0b5fab6b0bef5a45d1ea5a6c8f2b83d6f63be243cb19c20b8a
data/CHANGELOG.md ADDED
@@ -0,0 +1,38 @@
1
+ # Change log
2
+
3
+ ## master
4
+
5
+ ## 0.1.0 (2019-11-16)
6
+
7
+ - Add pattern matching. ([@palkan][])
8
+
9
+ - Add numbered parameters. ([@palkan][])
10
+
11
+ - Add arguments forwarding. ([@palkan][])
12
+
13
+ - Add `Enumerable#filter_map`. ([@palkan][])
14
+
15
+ - Add `Enumerable#filter/filter!`. ([@palkan][])
16
+
17
+ - Add multiple arguments support to `Hash#merge`. ([@palkan][])
18
+
19
+ - Add `Array#intersection`, `Array#union`, `Array#difference`. ([@palkan][])
20
+
21
+ - Add `Enumerable#tally`. ([@palkan][])
22
+
23
+ - Implement gem integration flow. ([@palkan][])
24
+
25
+ - Transpile code via `ruby-next nextify`.
26
+ - Setup load path via `RubyNext::Language.setup_gem_load_Path`.
27
+
28
+ - Add `ruby-next nextify` command. ([@palkan][])
29
+
30
+ - Add endless Range support. ([@palkan][])
31
+
32
+ - Add method reference syntax support. ([@palkan][])
33
+
34
+ - Add `Proc#<<` and `Proc#>>`. ([@palkan][])
35
+
36
+ - Add `Kernel#then`. ([@palkan][])
37
+
38
+ [@palkan]: https://github.com/palkan
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Vladimir Dementyev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,14 +1,204 @@
1
- # Ruby Next Playground
1
+ [![Gem Version](https://badge.fury.io/rb/ruby-next.svg)](https://rubygems.org/gems/ruby-next) [![Build](https://github.com/ruby-next/ruby-next/workflows/Build/badge.svg)](https://github.com/ruby-next/ruby-next/actions)
2
+ [![JRuby Build](https://github.com/ruby-next/ruby-next/workflows/JRuby%20Build/badge.svg)](https://github.com/ruby-next/ruby-next/actions)
2
3
 
3
- > Make older Rubies quack like edge Ruby!
4
+ # Ruby Next
4
5
 
5
- ## Resources
6
+ > Make all Rubies quack like edge Ruby!
6
7
 
7
- - [Pattern Matching, RubyKaigi 2019](https://speakerdeck.com/k_tsj/pattern-matching-new-feature-in-ruby-2-dot-7?slide=15)
8
- - [parser + pattern matching](https://github.com/whitequark/parser/pull/574)
9
- - [vernacular-ast](https://github.com/kddeisz/vernacular-ast)
10
- - [iseq_builder](https://github.com/youchan/iseq_builder)
11
- - [unparser](https://github.com/mbj/unparser)
12
- - [TOPLEVEL_BINDING.eval + require_relative](https://bugs.ruby-lang.org/issues/4487)
13
- - [malloc_trim](https://github.com/tessi/malloc_trim)
14
- - [Ruby changes](https://rubyreferences.github.io/rubychanges)
8
+ Ruby Next is a tool for supporting modern/edge CRuby features (APIs and syntax) in older versions and alternative implementations. For example, you can use pattern matching and `Kernel#then` in Ruby 2.5 or [mruby][].
9
+
10
+ Who might be interested in Ruby Next?
11
+
12
+ - **Ruby gems maintainers** who want to write code using the latest Ruby version but still support older ones.
13
+ - **Application developers** who want to give new features a try without waiting for the final release (or, more often, for the first patch).
14
+ - **Users of non-MRI implementations** such as [mruby][], [JRuby][], [TruffleRuby][], [Opal][], [Artichoke][], [Prism][].
15
+
16
+ Ruby Next also aims to help the community to assess new, _experimental_, MRI features by making it easier to play with them.
17
+ That's why Ruby Next implements the `trunk` features as fast as possible.
18
+
19
+ _⚡️ The project is in early alpha stage and looking for the first adopters. Main functionality has been implemented (see [the list][features]) but APIs could change in the future._
20
+
21
+ ## Overview
22
+
23
+ Ruby Next consists of two parts: **core** and **language**.
24
+
25
+ Core provides **polyfills** for Ruby core classes APIs via Refinements.
26
+ Thus, polyfills are only available in compatible runtimes (MRI, JRuby, TruffleRuby).
27
+
28
+ Language is responsible for **transpiling** edge Ruby syntax into older versions. It could be done
29
+ programmatically or via CLI. It also could be done in runtime.
30
+
31
+ ## Using only polyfills
32
+
33
+ First, install a gem:
34
+
35
+ ```ruby
36
+ # Gemfile
37
+ gem "ruby-next"
38
+
39
+ # gemspec
40
+ spec.add_dependency "ruby-next"
41
+ ```
42
+
43
+ Then, all you need is to load the Ruby Next:
44
+
45
+ ```ruby
46
+ require "ruby-next"
47
+ ```
48
+
49
+ And activate the refinement in every file where you want to use it\*:
50
+
51
+ ```ruby
52
+ using RubyNext
53
+ ```
54
+
55
+ Ruby Next only refines core classes if necessary; thus, this line wouldn't have any effect in the edge Ruby.
56
+
57
+ [**The list of supported APIs.**][features_core]
58
+
59
+ ## Transpiling, or using edge Ruby syntax features
60
+
61
+ Ruby Next relies on its own version of the [parser][] gem hosted on Github Package Registry. That makes the installation process a bit more complicated than usual.
62
+
63
+ [**The list of supported syntax features.**][features_syntax]
64
+
65
+ ### Installing with Bundler
66
+
67
+ First, configure your bundler to access GPR:
68
+
69
+ ```sh
70
+ bundle config --local https://rubygems.pkg.github.com/ruby-next USERNAME:ACCESS_TOKEN
71
+ ```
72
+
73
+ Then, add to your Gemfile:
74
+
75
+ ```ruby
76
+ source "https://rubygems.pkg.github.com/ruby-next" do
77
+ gem "parser", "2.6.3.102"
78
+ end
79
+
80
+ gem "unparser", "~> 0.4.5"
81
+ gem "ruby-next"
82
+ ```
83
+
84
+ **NOTE:** we don't add `parser` and `unparser` to the gem's runtime deps, 'cause they're not necessary if you only need polyfills.
85
+
86
+ ### Installing globally via `gem`
87
+
88
+ Currently unavailable due to the limitations of Github Package Registry. See [issue](https://github.community/t5/GitHub-Actions/Can-t-install-Ruby-gem-after-publishing-to-Github-Package/m-p/35192/thread-id/2126).
89
+
90
+ ### Integrating into a gem development
91
+
92
+ We recommend _pre-transpiling_ source code to work with older versions before releasing it.
93
+
94
+ This is how you can do that with Ruby Next:
95
+
96
+ - Write source code using the modern/edge Ruby syntax.
97
+
98
+ - Generate transpiled code by calling `ruby-next nextify ./lib` (e.g., before releasing or pushing to VCS).
99
+
100
+ This will produce `lib/.rbnext` folder containing the transpiled files, `lib/.rbnext/2.6`, `lib/.rbnext/2.7`. The version in the path indicates which Ruby version is required for the original functionality. Only the source files containing new syntax are added to this folder.
101
+
102
+ **NOTE:** Do not edit these files manually, either run linters/type checkers/whatever against these files.
103
+
104
+ - Add the following code to your gem's _entrypoint_ (the file that is required first and contains other `require`-s):
105
+
106
+ ```ruby
107
+ require "ruby-next/language/setup"
108
+
109
+ RubyNext::Language.setup_gem_load_path
110
+ ```
111
+
112
+ The `setup_gem_load_path` does the following:
113
+
114
+ - Resolves the current ruby version.
115
+ - Checks whether there are directories corresponding to the current and earlier\* Ruby versions within the `.rbnext` folder.
116
+ - Add the path to this directory to the `$LOAD_PATH` before the path to the gem's directory.
117
+
118
+ That's why need an _entrypoint_: all the subsequent `require` calls will load the transpiled files instead of the original ones
119
+ due to the way feature resolving works in Ruby (scanning the `$LOAD_PATH` and halting as soon as the matching file is found).
120
+
121
+ **NOTE:** `require_relative` should be avoided due to the way we _hijack_ the features loading mechanism.
122
+
123
+ \* Ruby Next avoids storing duplicates; instead, only the code for the earlier version is created and is assumed to be used with other versions. For example, if the transpiled code is the same for Ruby 2.5 and Ruby 2.6, only the `.rbnext/2.7/path/to/file.rb` is kept. That's why multiple entries are added to the `$LOAD_PATH` (`.rbnext/2.6` and `.rbnext/2.7` in the specified order for Ruby 2.5 and only `.rbnext/2.7` for Ruby 2.6).
124
+
125
+ ## CLI
126
+
127
+ Ruby Next ships with the command-line interface (`ruby-next`) which provides the following functionality:
128
+
129
+ - `ruby-next nextify` — transpile file or directory into older Rubies (see, for example, the "Integrating into a gem development" section above).
130
+
131
+ It has the following interface:
132
+
133
+ ```sh
134
+ $ ruby-next nextify
135
+ Usage: ruby-next nextify DIRECTORY_OR_FILE [options]
136
+ -o, --output=OUTPUT Specify output directory or file
137
+ --min-version=VERSION Specify the minimum Ruby version to support
138
+ --single-version Only create one version of a file (for the earliest Ruby version)
139
+ -V Turn on verbose mode
140
+ ```
141
+
142
+ The behaviour depends on whether you transpile a single file or a directory:
143
+
144
+ - When transpiling a directory, the `.rbnext` subfolder is created within the target folder with subfolders for each supported Ruby versions (e.g., `.rbnext/2.6`, `.rbnext/2.7`). If you want to create only a single version (the smallest), you can also pass `--single-version` flag. In that case, no version directory is created (i.e., transpiled files go into `.rbnext`).
145
+
146
+ - When transpiling a file and providing the output path as a _file_ path, only a single version is created. For example:
147
+
148
+ ```sh
149
+ $ ruby-next nextify my_ruby.rb -o my_ruby_next.rb -V
150
+ Generated: my_ruby_next.rb
151
+ ```
152
+
153
+ ## Runtime mode
154
+
155
+ It is also possible to transpile Ruby source code in run-time via Ruby Next.
156
+
157
+ All you need is to `require "ruby-next/language/runtime"` as early as possible to hijack `Kernel#require` and friends.
158
+ You can also automatically inject `using RubyNext` to every\* loaded file by also adding `require "ruby-next/core/runtime"`.
159
+
160
+ Since the runtime mode requires Kernel monkey-patching, it should be used carefully. For example, we use it in Ruby Next tests—works perfectly. But think twice before enabling it in production.
161
+
162
+ We plan to add [Bootsnap][] integration in the future, which would allow us to avoid monkey-patching (by relying on the bullet-proofed Bootsnap's one 😉).
163
+
164
+ \* Ruby Next doesn't hijack every required file but _watches_ only the configured directories: `./app/`, `./lib/`, `./spec/`, `./test/` (relative to the `pwd`). You can configure the watch dirs:
165
+
166
+ ```ruby
167
+ RubyNext::Language::Runtime.watch_dirs << "path/to/other/dir"
168
+ ```
169
+
170
+ ## `uby-next`
171
+
172
+ You can also enable runtime mode by requiring `uby-next` while running a Ruby executable:
173
+
174
+ ```sh
175
+ ruby -ruby-next my_ruby_script.rb
176
+
177
+ # or
178
+ RUBYOPT="-ruby-next" ruby my_ruby_script.rb
179
+
180
+ # or
181
+ ruby -ruby-next -e "puts [2, 4, 5].tally"
182
+ ```
183
+
184
+ ## Contributing
185
+
186
+ Bug reports and pull requests are welcome on GitHub at [https://github.com/ruby-next/ruby-next](ttps://github.com/ruby-next/ruby-next).
187
+
188
+ See also the [development guide](./DEVELOPMENT.md).
189
+
190
+ ## License
191
+
192
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
193
+
194
+ [features]: ./SUPPORTED_FEATURES.md
195
+ [features_core]: ./SUPPORTED_FEATURES.md#Core
196
+ [features_syntax]: ./SUPPORTED_FEATURES.md#Syntax
197
+ [mruby]: https://mruby.org
198
+ [JRuby]: https://www.jruby.org
199
+ [TruffleRuby]: https://github.com/oracle/truffleruby
200
+ [Opal]: https://opalrb.com
201
+ [Artichoke]: https://github.com/artichoke/artichoke
202
+ [Prism]: https://github.com/prism-rb/prism
203
+ [parser]: https://github.com/whitequark/parser
204
+ [Bootsnap]: https://github.com/Shopify/bootsnap
data/bin/parse ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path("../../lib", __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ $VERBOSE = nil
6
+
7
+ require "bundler/setup"
8
+
9
+ require "ruby-next/language"
10
+
11
+ contents =
12
+ if File.exist?(ARGV[0])
13
+ File.read(ARGV[0])
14
+ else
15
+ ARGV[0]
16
+ end
17
+
18
+ ast = RubyNext::Language::Parser.parse(contents)
19
+ puts ast
data/bin/ruby-next ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib_path = File.expand_path("../lib", __dir__)
4
+ $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
5
+
6
+ require "ruby-next/cli"
7
+
8
+ begin
9
+ cli = RubyNext::CLI.new
10
+ cli.run(ARGV)
11
+ rescue => e
12
+ raise e if $DEBUG
13
+ STDERR.puts e.message
14
+ STDERR.puts e.backtrace.join("\n")
15
+ exit 1
16
+ end
data/bin/transform ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path("../../lib", __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ $VERBOSE = nil
6
+
7
+ require "bundler/setup"
8
+
9
+ require "ruby-next/language"
10
+
11
+ contents =
12
+ if File.exist?(ARGV[0])
13
+ File.read(ARGV[0])
14
+ else
15
+ ARGV[0]
16
+ end
17
+
18
+ puts RubyNext::Language.transform(contents)
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby-next"
4
+ require "ruby-next/language"
5
+
6
+ require "ruby-next/commands/base"
7
+ require "ruby-next/commands/nextify"
8
+
9
+ module RubyNext
10
+ # Command line interface for RubyNext
11
+ class CLI
12
+ class << self
13
+ attr_accessor :verbose
14
+ end
15
+
16
+ self.verbose = false
17
+
18
+ COMMANDS = {
19
+ "nextify" => Commands::Nextify
20
+ }.freeze
21
+
22
+ def initialize
23
+ end
24
+
25
+ def run(args = ARGV)
26
+ maybe_print_version(args)
27
+
28
+ command = args.shift
29
+
30
+ raise "Command must be specified!" unless command
31
+
32
+ COMMANDS.fetch(command) do
33
+ raise "Unknown command: #{command}. Available commands: #{COMMANDS.keys.join(",")}"
34
+ end.run(args)
35
+ end
36
+
37
+ private
38
+
39
+ def maybe_print_version(args)
40
+ args = args.dup
41
+ begin
42
+ OptionParser.new do |opts|
43
+ opts.banner = "Usage: ruby-next COMMAND [options]"
44
+
45
+ opts.on("-v", "--version", "Print version") do
46
+ STDOUT.puts RubyNext::VERSION
47
+ exit 0
48
+ end
49
+ end.parse!(args)
50
+ rescue OptionParser::InvalidOption
51
+ # skip and pass all args to the command's parser
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+
5
+ module RubyNext
6
+ module Commands
7
+ class Base
8
+ class << self
9
+ def run(args)
10
+ new(args).run
11
+ end
12
+ end
13
+
14
+ def initialize(args)
15
+ parse! args
16
+ end
17
+
18
+ def parse!(*)
19
+ raise NotImplementedError
20
+ end
21
+
22
+ def run
23
+ raise NotImplementedError
24
+ end
25
+
26
+ def log(msg)
27
+ return unless CLI.verbose
28
+ $stdout.puts msg
29
+ end
30
+
31
+ def base_parser
32
+ OptionParser.new do |opts|
33
+ yield opts
34
+
35
+ opts.on("-V", "Turn on verbose mode") do
36
+ CLI.verbose = true
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "pathname"
5
+
6
+ using RubyNext
7
+
8
+ module RubyNext
9
+ module Commands
10
+ class Nextify < Base
11
+ attr_reader :lib_path, :paths, :out_path, :min_version, :single_version
12
+
13
+ def run
14
+ paths.each do |path|
15
+ contents = File.read(path)
16
+ transpile path, contents
17
+ end
18
+ end
19
+
20
+ def parse!(args)
21
+ @min_version = MIN_SUPPORTED_VERSION
22
+ @single_version = false
23
+
24
+ optparser = base_parser do |opts|
25
+ opts.banner = "Usage: ruby-next nextify DIRECTORY_OR_FILE [options]"
26
+
27
+ opts.on("-o", "--output=OUTPUT", "Specify output directory or file or stdout") do |val|
28
+ @out_path = val
29
+ end
30
+
31
+ opts.on("--min-version=VERSION", "Specify the minimum Ruby version to support") do |val|
32
+ @min_version = Gem::Version.new(val)
33
+ end
34
+
35
+ opts.on("--single-version", "Only create one version of a file (for the earliest Ruby version)") do
36
+ @single_version = true
37
+ end
38
+ end
39
+
40
+ @lib_path = args[0]
41
+
42
+ unless lib_path&.then(&File.method(:exist?))
43
+ $stdout.puts optparser.help
44
+ exit 0
45
+ end
46
+
47
+ optparser.parse!(args)
48
+
49
+ @paths =
50
+ if File.directory?(lib_path)
51
+ Dir[File.join(lib_path, "**/*.rb")]
52
+ elsif File.file?(lib_path)
53
+ [lib_path].tap do |_|
54
+ @lib_path = File.dirname(lib_path)
55
+ end
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def transpile(path, contents, version: min_version)
62
+ rewriters = Language.rewriters.select { |rw| rw.unsupported_version?(version) }
63
+
64
+ context = Language::TransformContext.new
65
+ new_contents = Language.transform contents, context: context, rewriters: rewriters
66
+
67
+ return unless context.dirty?
68
+
69
+ versions = context.sorted_versions
70
+ version = versions.shift
71
+
72
+ # First, store already transpiled contents in the minimum required version dir
73
+ save new_contents, path, version
74
+
75
+ return if versions.empty? || single_version?
76
+
77
+ # Then, generate the source code for the next version
78
+ transpile path, contents, version: version
79
+ end
80
+
81
+ def save(contents, path, version)
82
+ return $stdout.puts(contents) if out_path == "stdout"
83
+
84
+ paths = [Pathname.new(path).relative_path_from(Pathname.new(lib_path))]
85
+
86
+ paths.unshift(version.segments[0..1].join(".")) unless single_version?
87
+
88
+ next_path =
89
+ if out_path
90
+ if out_path.end_with?(".rb")
91
+ out_path
92
+ else
93
+ File.join(out_path, *paths)
94
+ end
95
+ else
96
+ File.join(
97
+ lib_path,
98
+ RUBY_NEXT_DIR,
99
+ *paths
100
+ )
101
+ end
102
+
103
+ FileUtils.mkdir_p File.dirname(next_path)
104
+
105
+ File.write(next_path, contents)
106
+
107
+ log "Generated: #{next_path}"
108
+ end
109
+
110
+ alias single_version? single_version
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ unless [].respond_to?(:union)
4
+ RubyNext.module_eval do
5
+ refine Array do
6
+ def union(*others)
7
+ others.reduce(Array.new(self).uniq) { |acc, arr| acc | arr }
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ unless [].respond_to?(:difference)
14
+ RubyNext.module_eval do
15
+ refine Array do
16
+ def difference(*others)
17
+ others.reduce(Array.new(self)) { |acc, arr| acc - arr }
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ unless [].respond_to?(:intersection)
24
+ RubyNext.module_eval do
25
+ refine Array do
26
+ def intersection(*others)
27
+ others.reduce(Array.new(self)) { |acc, arr| acc & arr }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ unless [].respond_to?(:filter)
4
+ RubyNext.module_eval do
5
+ refine Enumerable do
6
+ alias filter select
7
+ end
8
+
9
+ # Refine Array seprately, 'cause refining modules is vulnerable to prepend:
10
+ # - https://bugs.ruby-lang.org/issues/13446
11
+ #
12
+ # Also, Array also have `filter!`
13
+ refine Array do
14
+ alias filter select
15
+ alias filter! select!
16
+ end
17
+
18
+ refine Hash do
19
+ alias filter select
20
+ alias filter! select!
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ unless [].respond_to?(:filter_map)
4
+ module RubyNext
5
+ module Core
6
+ module EnumerableFilterMap
7
+ def filter_map
8
+ if block_given?
9
+ result = []
10
+ each do |element|
11
+ res = yield element
12
+ result << res if res
13
+ end
14
+ result
15
+ else
16
+ Enumerator.new do |yielder|
17
+ result = []
18
+ each do |element|
19
+ res = yielder.yield element
20
+ result << res if res
21
+ end
22
+ result
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ RubyNext.module_eval do
31
+ refine Enumerable do
32
+ include RubyNext::Core::EnumerableFilterMap
33
+ end
34
+
35
+ refine Enumerator::Lazy do
36
+ def filter_map
37
+ Enumerator::Lazy.new(self) do |yielder, *values|
38
+ result = yield(*values)
39
+ yielder << result if result
40
+ end
41
+ end
42
+ end
43
+
44
+ # Refine Array seprately, 'cause refining modules is vulnerable to prepend:
45
+ # - https://bugs.ruby-lang.org/issues/13446
46
+ refine Array do
47
+ include RubyNext::Core::EnumerableFilterMap
48
+ end
49
+ end
50
+ end