class RubyNext::Commands::Nextify

Attributes

lib_path[R]
min_version[R]
out_path[R]
paths[R]
single_version[R]
specified_rewriters[R]

Public Instance Methods

parse!(args) click to toggle source
# File lib/ruby-next/commands/nextify.rb, line 29
def parse!(args)
  print_help = false
  print_rewriters = false
  rewriter_names = []
  @single_version = false

  optparser = base_parser do |opts|
    opts.banner = "Usage: ruby-next nextify DIRECTORY_OR_FILE [options]"

    opts.on("-o", "--output=OUTPUT", "Specify output directory or file or stdout") do |val|
      @out_path = val
    end

    opts.on("--min-version=VERSION", "Specify the minimum Ruby version to support") do |val|
      @min_version = Gem::Version.new(val)
    end

    opts.on("--single-version", "Only create one version of a file (for the earliest Ruby version)") do
      @single_version = true
    end

    opts.on("--edge", "Enable edge (master) Ruby features") do |val|
      require "ruby-next/language/edge"
    end

    opts.on("--proposed", "Enable proposed/experimental Ruby features") do |val|
      require "ruby-next/language/proposed"
    end

    opts.on(
      "--transpile-mode=MODE",
      "Transpiler mode (ast or rewrite). Default: rewrite"
    ) do |val|
      Language.mode = val.to_sym
    end

    opts.on("--[no-]refine", "Do not inject `using RubyNext`") do |val|
      Core.strategy = :core_ext unless val
    end

    opts.on("--list-rewriters", "List available rewriters") do |val|
      print_rewriters = true
    end

    opts.on("--rewrite=REWRITERS...", "Specify particular Ruby features to rewrite") do |val|
      rewriter_names << val
    end

    opts.on("-h", "--help", "Print help") do
      print_help = true
    end
  end

  optparser.parse!(args)

  @lib_path = args[0]

  if print_help
    $stdout.puts optparser.help
    exit 0
  end

  if print_rewriters
    Language.rewriters.each do |rewriter|
      $stdout.puts "#{rewriter::NAME} (\"#{rewriter::SYNTAX_PROBE}\")"
    end
    exit 0
  end

  unless lib_path&.then(&File.method(:exist?))
    $stdout.puts "Path not found: #{lib_path}"
    $stdout.puts optparser.help
    exit 2
  end

  if rewriter_names.any? && min_version
    $stdout.puts "--rewrite cannot be used with --min-version simultaneously"
    exit 2
  end

  @specified_rewriters =
    if rewriter_names.any?
      begin
        Language.select_rewriters(*rewriter_names)
      rescue Language::RewriterNotFoundError => error
        $stdout.puts error.message
        $stdout.puts "Try --list-rewriters to see list of available rewriters"
        exit 2
      end
    end

  @paths =
    if File.directory?(lib_path)
      Dir[File.join(lib_path, "**/*.rb")]
    elsif File.file?(lib_path)
      [lib_path].tap do |_|
        @lib_path = File.dirname(lib_path)
      end
    end
end
run() click to toggle source
# File lib/ruby-next/commands/nextify.rb, line 15
def run
  log "RubyNext core strategy: #{RubyNext::Core.strategy}"
  log "RubyNext transpile mode: #{RubyNext::Language.mode}"

  remove_rbnext!

  @min_version ||= MIN_SUPPORTED_VERSION

  paths.each do |path|
    contents = File.read(path)
    transpile path, contents
  end
end

Private Instance Methods

next_dir_path() click to toggle source
# File lib/ruby-next/commands/nextify.rb, line 188
def next_dir_path
  @next_dir_path ||= (out_path || File.join(lib_path, RUBY_NEXT_DIR))
end
remove_rbnext!() click to toggle source
# File lib/ruby-next/commands/nextify.rb, line 179
def remove_rbnext!
  return if CLI.dry_run? || stdout?

  return unless File.directory?(next_dir_path)

  log "Remove old files: #{next_dir_path}"
  FileUtils.rm_r(next_dir_path)
end
save(contents, path, version) click to toggle source
# File lib/ruby-next/commands/nextify.rb, line 156
def save(contents, path, version)
  return $stdout.puts(contents) if stdout?

  paths = [Pathname.new(path).relative_path_from(Pathname.new(lib_path))]

  paths.unshift(version.segments[0..1].join(".")) unless single_version?

  next_path =
    if next_dir_path.end_with?(".rb")
      out_path
    else
      File.join(next_dir_path, *paths)
    end

  unless CLI.dry_run?
    FileUtils.mkdir_p File.dirname(next_path)

    File.write(next_path, contents)
  end

  log "Generated: #{next_path}"
end
single_version?() click to toggle source
# File lib/ruby-next/commands/nextify.rb, line 196
def single_version?
  single_version || specified_rewriters
end
stdout?() click to toggle source
# File lib/ruby-next/commands/nextify.rb, line 192
def stdout?
  out_path == "stdout"
end
transpile(path, contents, version: min_version) click to toggle source
# File lib/ruby-next/commands/nextify.rb, line 132
def transpile(path, contents, version: min_version)
  rewriters = specified_rewriters || Language.rewriters.select { |rw| rw.unsupported_version?(version) }

  context = Language::TransformContext.new

  new_contents = Language.transform contents, context: context, rewriters: rewriters

  return unless context.dirty?

  versions = context.sorted_versions
  version = versions.shift

  # First, store already transpiled contents in the minimum required version dir
  save new_contents, path, version

  return if versions.empty? || single_version?

  # Then, generate the source code for the next version
  transpile path, contents, version: version
rescue SyntaxError, StandardError => e
  warn "Failed to transpile #{path}: #{e.class} — #{e.message}"
  exit 1
end