Skip to content

Commit fac7e9d

Browse files
authored
Inline syntax for commands (#77)
1 parent 3504cb5 commit fac7e9d

File tree

3 files changed

+145
-0
lines changed

3 files changed

+145
-0
lines changed

lib/dry/cli/inline.rb

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# frozen_string_literal: true
2+
3+
require 'backports/2.5.0/module/define_method' if RUBY_VERSION < '2.5'
4+
5+
module Dry
6+
class CLI
7+
require 'dry/cli'
8+
# Inline Syntax (aka DSL) to implement one-file applications
9+
#
10+
# `dry/cli/inline` is not required by default
11+
# and explicit requirement of this file means that
12+
# it is expected of abusing global namespace with
13+
# methods below
14+
#
15+
# DSL consists of 5 methods:
16+
# `desc`, `example`, `argument`, `option` 
17+
# — are similar to methods from Command class
18+
#
19+
# `run` accepts a block to execute
20+
#
21+
# @example
22+
# require 'bundler/inline'
23+
# gemfile { gem 'dry/cli', require: 'dry/cli/inline' }
24+
#
25+
# desc 'List files in a directory'
26+
# argument :path, required: false, desc: '[DIR]'
27+
# option :all, aliases: ['a'], type: :boolean
28+
#
29+
# run do |path: '.', **options|
30+
# puts options.key?(:all) ? Dir.entries(path) : Dir.children(path)
31+
# end
32+
#
33+
# # $ ls -a
34+
# # $ ls somepath
35+
# # $ ls somepath --all
36+
# @since 0.6.x
37+
module Inline
38+
extend Forwardable
39+
40+
# AnonymousCommand
41+
#
42+
# @since 0.6.x
43+
AnonymousCommand = Class.new(Dry::CLI::Command)
44+
45+
# @since 0.6.x
46+
delegate %i[desc example argument option] => AnonymousCommand
47+
48+
# The rule of thumb for implementation of run block
49+
# is that for every argument from your CLI
50+
# you need to specify that as an mandatory argument for a block.
51+
# Optional arguments have to have default value as ruby syntax expect them
52+
#
53+
# @example
54+
# argument :one
55+
# argument :two, required: false
56+
# option :three
57+
#
58+
# run do |one:, two: 'default', **options|
59+
# puts one, two, options.inspect
60+
# end
61+
#
62+
# @since 0.6.x
63+
def run(arguments: ARGV, out: $stdout)
64+
command = AnonymousCommand
65+
command.define_method(:call) do |*args|
66+
yield(*args)
67+
end
68+
69+
Dry.CLI(command).call(arguments: arguments, out: out)
70+
end
71+
end
72+
end
73+
end
74+
75+
include Dry::CLI::Inline # rubocop:disable Style/MixinUsage

spec/integration/inline_spec.rb

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe 'Inline' do
4+
context 'with command' do
5+
let(:cmd) { 'inline' }
6+
7+
it 'shows help' do
8+
output = `inline -h`
9+
expected_output = <<~OUTPUT
10+
Command:
11+
inline
12+
13+
Usage:
14+
inline MANDATORY_ARG [OPTIONAL_ARG]
15+
16+
Description:
17+
Baz command line interface
18+
19+
Arguments:
20+
MANDATORY_ARG # REQUIRED Mandatory argument
21+
OPTIONAL_ARG # Optional argument (has to have default value in call method)
22+
23+
Options:
24+
--option-one=VALUE, -1 VALUE # Option one
25+
--[no-]boolean-option, -b # Option boolean
26+
--option-with-default=VALUE, -d VALUE # Option default, default: "test"
27+
--help, -h # Print this help
28+
OUTPUT
29+
expect(output).to eq(expected_output)
30+
end
31+
32+
it 'with option_one', if: RUBY_VERSION < '2.4' do
33+
output = `inline first_arg --option-one=test2 -bd test3`
34+
expect(output).to eq(
35+
'mandatory_arg: first_arg. optional_arg: optional_arg. ' \
36+
'Options: {:option_with_default=>"test3", :option_one=>"test2", :boolean_option=>true}' \
37+
"\n"
38+
)
39+
end
40+
41+
it 'with underscored option_one', if: RUBY_VERSION >= '2.4' do
42+
output = `inline first_arg -1 test2 -bd test3`
43+
expect(output).to eq(
44+
'mandatory_arg: first_arg. optional_arg: optional_arg. ' \
45+
'Options: {:option_with_default=>"test3", :option_one=>"test2", :boolean_option=>true}' \
46+
"\n"
47+
)
48+
end
49+
end
50+
end

spec/support/fixtures/inline

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
$LOAD_PATH.unshift __dir__ + '/../../../lib'
5+
require 'dry/cli'
6+
require_relative '../../../lib/dry/cli/inline'
7+
8+
desc 'Baz command line interface'
9+
argument :mandatory_arg, required: true, aliases: %w[m], desc: 'Mandatory argument'
10+
argument :optional_arg, aliases: %w[o],
11+
desc: 'Optional argument (has to have default value in call method)'
12+
option :option_one, aliases: %w[1], desc: 'Option one'
13+
option :boolean_option, aliases: %w[b], desc: 'Option boolean', type: :boolean
14+
option :option_with_default, aliases: %w[d], desc: 'Option default', default: 'test'
15+
16+
run do |mandatory_arg:, optional_arg: 'optional_arg', **options|
17+
puts "mandatory_arg: #{mandatory_arg}. " \
18+
"optional_arg: #{optional_arg}. " \
19+
"Options: #{options.inspect}"
20+
end

0 commit comments

Comments
 (0)