#!/usr/bin/env ruby require 'parlour' require 'commander/import' require 'bundler' require 'rainbow' require 'yaml' program :name, 'parlour' program :version, Parlour::VERSION program :description, 'An RBI generator and plugin system' default_command :run command :run do |c| # TODO: re-add support for flags and figure out how to merge them with .parlour c.syntax = 'parlour run [options]' c.description = 'Generates an RBI file from your .parlour file' c.action do |args, options| configuration = keys_to_symbols(YAML.load_file(File.join(Dir.pwd, '.parlour'))) raise 'you must specify output_file in your .parlour file' unless configuration[:output_file] # Style defaults configuration[:style] ||= {} configuration[:style][:tab_size] ||= 2 configuration[:style][:break_params] ||= 4 # Require defaults configuration[:requires] ||= [] configuration[:relative_requires] ||= [] # Plugin defaults configuration[:plugins] ||= [] plugin_instances = [] configuration[:requires].each { |source| require(source) } configuration[:relative_requires].each do |source| require_relative(File.join(Dir.pwd, source)) end # Collect the instances of each plugin into an array configuration[:plugins].each do |name, options| plugin = Parlour::Plugin.registered_plugins[name.to_s]&.new(options) raise "missing plugin #{name}" unless plugin plugin_instances << plugin end # Create a generator instance and run all plugins on it gen = Parlour::RbiGenerator.new( break_params: configuration[:style][:break_params], tab_size: configuration[:style][:tab_size] ) Parlour::Plugin.run_plugins(plugin_instances, gen) # Run a pass of the conflict resolver Parlour::ConflictResolver.new.resolve_conflicts(gen.root) do |msg, candidates| puts Rainbow('Conflict! ').red.bright.bold + Rainbow(msg).blue.bright puts 'Multiple different definitions have been produced for the same object.' puts 'They could not be merged automatically.' puts Rainbow('What would you like to do?').bold + ' Type a choice and press Enter.' puts puts Rainbow(' [0] ').yellow + 'Remove ALL definitions' puts puts "Or select one definition to keep:" puts candidates.each.with_index do |candidate, i| puts Rainbow(" [#{i + 1}] ").yellow + candidate.describe end puts choice = ask("? ", Integer) { |q| q.in = 0..candidates.length } choice == 0 ? nil : candidates[choice - 1] end # Figure out strictness levels requested_strictness_levels = plugin_instances.map do |plugin| s = plugin.strictness&.to_s puts "WARNING: Plugin #{plugin.class.name} requested an invalid strictness #{s}" \ unless s && %w[ignore false true strict strong].include?(s) s end.compact unique_strictness_levels = requested_strictness_levels.uniq if unique_strictness_levels.empty? # If no requests were made, just use the default strictness = 'strong' else # Sort the strictnesses into "strictness order" and pick the weakest strictness = unique_strictness_levels.min_by do |level| %w[ignore false true strict strong].index(level) || Float::INFINITY end if unique_strictness_levels.one? puts Rainbow('Note: ').yellow.bold + "All plugins specified the same strictness level, using it (#{strictness})" else puts Rainbow('Note: ').yellow.bold + "Plugins specified multiple strictness levels, chose the weakest (#{strictness})" end end # Write the final RBI File.write(configuration[:output_file], gen.rbi(strictness)) end end private # Given a hash, converts its keys and any keys of child hashes to symbols. # @param [Hash] hash # @return [void] def keys_to_symbols(hash) hash.map do |k, v| [ k.to_sym, case v when Hash keys_to_symbols(v) when Array v.map { |x| x.is_a?(Hash) ? keys_to_symbols(x) : x } else v end ] end.to_h end