require "optparse" require "shellwords" module RBS class CLI class LibraryOptions attr_reader :libs attr_reader :dirs attr_accessor :no_stdlib def initialize() @libs = [] @dirs = [] @no_stdlib = false end def setup(loader) libs.each do |lib| loader.add(library: lib) end dirs.each do |dir| loader.add(path: Pathname(dir)) end loader.no_builtin! if no_stdlib loader 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 library_parse(opts, options:) opts.on("-r LIBRARY", "Load RBS files of the library") do |lib| options.libs << lib end opts.on("-I DIR", "Load RBS files from the directory") do |dir| options.dirs << dir end opts.on("--no-stdlib", "Skip loading standard library signatures") do options.no_stdlib = true end opts end 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| RBS.logger_output = File.open(output, "a") 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 library_parse(opts, options: options) 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) { case when path.file? "file" when path.directory? "dir" when !path.exist? "absent" else "unknown" end } if loader.stdlib_root path = loader.stdlib_root stdout.puts "#{path}/builtin (#{kind_of[path]}, stdlib)" end loader.paths.each do |path| case path when Pathname stdout.puts "#{path} (#{kind_of[path]})" when EnvironmentLoader::GemPath stdout.puts "#{path.path} (#{kind_of[path.path]}, gem, name=#{path.name}, version=#{path.version})" when EnvironmentLoader::LibraryPath stdout.puts "#{path.path} (#{kind_of[path.path]}, library, name=#{path.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 "#{sig_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 "#{sig_path}:#{loc.start_line}:#{loc.start_column}: #{ex.original_message}" syntax_error = true end end exit 1 if syntax_error end def parse_type_name(string) Namespace.parse(string).yield_self do |namespace| last = namespace.path.last TypeName.new(name: last, namespace: namespace.parent) end end def test_opt options opt_string = options.dirs.map { |dir| "-I #{Shellwords.escape(dir)}"}.concat(options.libs.map { |lib| "-r#{Shellwords.escape(lib)}"}).join(' ') opt_string.empty? ? nil : opt_string end def run_test(args, options) unchecked_classes = [] targets = [] sample_size = nil 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?) } system(env_hash, *args) $? end end end