dry-rb / dry-cli

General purpose Command Line Interface (CLI) framework for Ruby
https://dry-rb.org/gems/dry-cli
MIT License
327 stars 41 forks source link

Add ability to use extensions with dry-cli #104

Closed Morozzzko closed 3 years ago

Morozzzko commented 3 years ago

Dry::Core enables us to add extensions to our libraries, which is a nice feature to have. Other than being nice, it enables us to make our own local patches to dry-rb libraries in a controlled fashion.

I'd love to be able use extensions with dry-cli too.

Here's my use-case: I'm writing a CLI which will prompt users for input. Naturally, I use tty-prompt for that, and want a better integration with dry-cli: I don't want to write too much boilerplate across the whole project.

When looking at other CLI libraries, I found that Thor has a collection of similar methods, but they don't use an external dependency. Their interface is seamless: they just use say and ask on self, with no intermediate object.

Right now, I've managed to devise a simple extension:

# lib/dry/cli/extensions/tty_prompt.rb
# frozen_string_literal: true

require 'dry/cli'
require 'tty-prompt'

module Dry
  class CLI
    class Command
      def prompt
        @prompt ||= TTY::Prompt.new
      end
    end
  end
end

module Dry
  class CLI
    extend Dry::Core::Extensions

    register_extension(:tty_prompt) do
      require 'dry/cli/extensions/tty_prompt'
    end

    load_extensions(:tty_prompt)
  end
end

Usage:

      class Version < Dry::CLI::Command
        def call(**)
          prompt.say "Gem version #{MyGEM::VERSION}"
        end
      end

However, there's two major issues:

So my proposal is to add dry-core as a dependency and enable extensions as an out-of-the-box feature.

Would be even nicer to add a tty-prompt integration, but it'll take a while

Morozzzko commented 3 years ago

Also there's no way to create an issue which is not a bug, so sorry about the tags

Re feature & CONTRIBUTING guide: we discussed it with @IvanShamatov, but via Saint P Telegram chat instead of Discourse. So I guess it's alright to post it as an issue

jodosha commented 3 years ago

@Morozzzko I don't believe it's a good idea for a few reasons:

How to achieve the same with existing features:

# frozen_string_literal: true
# lib/foo/cli/command.rb

require "dry/cli"
require "tty-prompt"

module Foo
  module CLI
    class Command < Dry::CLI::Command
      def initialize(prompt: TTY::Prompt.new)
        super()
        @prompt = prompt
      end

      private

      attr_reader :prompt
    end
  end
end
# frozen_string_literal: true
# lib/foo/cli/version.rb

require_relative "./command"

module Foo
  module CLI
    class Version < Command
      def call(**)
        prompt.say "Gem version #{Foo::VERSION}"
      end
    end
  end
end