require "open3" require "optparse" require "shellwords" module RBS class CLI class LibraryOptions attr_accessor :core_root attr_reader :repos attr_reader :libs attr_reader :dirs def initialize() @core_root = EnvironmentLoader::DEFAULT_CORE_ROOT @repos = [] @libs = [] @dirs = [] end def loader repository = Repository.new(no_stdlib: core_root.nil?) repos.each do |repo| repository.add(Pathname(repo)) end loader = EnvironmentLoader.new(core_root: core_root, repository: repository) dirs.each do |dir| loader.add(path: Pathname(dir)) end libs.each do |lib| name, version = lib.split(/:/, 2) next unless name loader.add(library: name, version: version) end loader end def setup_library_options(opts) opts.on("-r LIBRARY", "Load RBS files of the library") do |lib| libs << lib end opts.on("-I DIR", "Load RBS files from the directory") do |dir| dirs << dir end opts.on("--no-stdlib", "Skip loading standard library signatures") do self.core_root = nil end opts.on("--repo DIR", "Add RBS repository") do |dir| repos << dir end opts end end attr_reader :stdout attr_reader :stderr def initialize(stdout:, stderr:) @stdout = stdout @stderr = stderr end COMMANDS = [:ast, :list, :ancestors, :methods, :method, :validate, :constant, :paths, :prototype, :vendor, :parse, :test] def parse_logging_options(opts) opts.on("--log-level LEVEL", "Specify log level (defaults to `warn`)") do |level| RBS.logger_level = level end opts.on("--log-output OUTPUT", "Specify the file to output log (defaults to stderr)") do |output| io = File.open(output, "a") or raise RBS.logger_output = io end opts end def has_parser? defined?(RubyVM::AbstractSyntaxTree) end def run(args) options = LibraryOptions.new opts = OptionParser.new opts.banner = <<~USAGE Usage: rbs [options...] [command...] Available commands: #{COMMANDS.join(", ")}, version, help. Options: USAGE options.setup_library_options(opts) parse_logging_options(opts) opts.version = RBS::VERSION opts.order!(args) command = args.shift&.to_sym case command when :version stdout.puts opts.ver when *COMMANDS __send__ :"run_#{command}", args, options else stdout.puts opts.help end end def run_ast(args, options) OptionParser.new do |opts| opts.banner = < #{constant.name}: #{constant.type}" else stdout.puts " => [no constant]" end end def run_paths(args, options) OptionParser.new do |opts| opts.banner = < (path) { # @type var path: Pathname case when path.file? "file" when path.directory? "dir" when !path.exist? "absent" else "unknown" end } loader.each_dir do |source, dir| case source when :core stdout.puts "#{dir} (#{kind_of[dir]}, core)" when Pathname stdout.puts "#{dir} (#{kind_of[dir]})" when EnvironmentLoader::Library stdout.puts "#{dir} (#{kind_of[dir]}, library, name=#{source.name})" end end end def run_prototype(args, options) format = args.shift case format when "rbi", "rb" decls = run_prototype_file(format, args) when "runtime" require_libs = [] relative_libs = [] merge = false owners_included = [] OptionParser.new do |opts| opts.banner = < ex loc = ex.error_value.location stdout.puts "#{file_path}:#{loc.start_line}:#{loc.start_column}: parse error on value: (#{ex.token_str})" syntax_error = true rescue RBS::Parser::SemanticsError => ex loc = ex.location stdout.puts "#{file_path}:#{loc.start_line}:#{loc.start_column}: #{ex.original_message}" syntax_error = true end end exit 1 if syntax_error end def test_opt options opts = [] opts.push(*options.repos.map {|dir| "--repo #{Shellwords.escape(dir)}"}) opts.push(*options.dirs.map {|dir| "-I #{Shellwords.escape(dir)}"}) opts.push(*options.libs.map {|lib| "-r#{Shellwords.escape(lib)}"}) opts.empty? ? nil : opts.join(" ") end def run_test(args, options) # @type var unchecked_classes: Array[String] unchecked_classes = [] # @type var targets: Array[String] targets = [] # @type var sample_size: String? sample_size = nil # @type var double_suite: String? double_suite = nil (opts = OptionParser.new do |opts| opts.banner = < "#{ENV['RUBYOPT']} -rrbs/test/setup", 'RBS_TEST_OPT' => test_opt(options), 'RBS_TEST_LOGLEVEL' => RBS.logger_level, 'RBS_TEST_SAMPLE_SIZE' => sample_size, 'RBS_TEST_DOUBLE_SUITE' => double_suite, 'RBS_TEST_UNCHECKED_CLASSES' => (unchecked_classes.join(',') unless unchecked_classes.empty?), 'RBS_TEST_TARGET' => (targets.join(',') unless targets.empty?) } out, err, status = Open3.capture3(env_hash, *args) stdout.print(out) stderr.print(err) status end end end