Skip to content

Commit 9558e20

Browse files
author
Flurin Egger
committed
Parse and pass unknown options to Roger's inner machinery
1 parent 9ffc76c commit 9558e20

File tree

3 files changed

+143
-3
lines changed

3 files changed

+143
-3
lines changed

doc/cli.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,15 @@ All commands accept these options.
5252
Options:
5353
-v, [--verbose] # Set's verbose output
5454
-h, [--help] # Help
55-
```
55+
```
56+
57+
## Custom options
58+
59+
All processors/middleware/etc. can use custom options. Roger will parse any leftover option that starts with `--` and sets it in the `project.options` hash. It even supports setting nested keys by using `:` as a separator. This means that `--release:rsync:disable=true` will become: `{release: { rsync: { disable: true}}}` in the options hash.
60+
61+
### Parsing
62+
The commandline parser will work with flags in the following formats:
63+
64+
* `--key` will just set the flag to `true`
65+
* `--key=value` and `--key value` will set the key `:key` to `"value"`
66+
* `--key=true` and `--key=false` will convert to boolean `true` and `false`

lib/roger/cli.rb

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,76 @@ def version
9696

9797
protected
9898

99-
# TODO: handle options
10099
def initialize_project
101100
if (Pathname.new(options[:path]) + "../partials").exist?
102101
puts "[ERROR]: Don't use the \"html\" path, use the project base path instead"
103102
exit(1)
104103
end
105104

106-
Project.new(options[:path], { shell: shell }.update(options))
105+
project_options = { shell: shell }
106+
project_options.update(parse_generic_options(args)[0])
107+
project_options.update(options)
108+
109+
Project.new(options[:path], project_options)
110+
end
111+
112+
# Very simplified method to parse CLI options
113+
# only works with options starting with --
114+
# Will also make nested options by using ":" so
115+
# --a:b:c=yes will become {a: {b: {c: "yes"}}}
116+
def parse_generic_options(args)
117+
a = args.dup
118+
arguments = []
119+
options = {}
120+
121+
until a.empty?
122+
arg = a.shift
123+
case arg
124+
when /\A--.+=/
125+
_, option, value = arg.match(/\A--(.+)=(.+)\Z/).to_a
126+
update_options(option, value, options)
127+
when /\A--.+/
128+
if a[0].nil? || a[0].to_s.start_with?("--")
129+
# Current option is a boolean
130+
update_options(arg, true, options)
131+
else
132+
# Take value from next
133+
update_options(arg, a.shift, options)
134+
end
135+
else
136+
arguments << arg
137+
end
138+
end
139+
140+
[options, arguments]
141+
end
142+
143+
# Will update the passed options array by splitting
144+
# the composite_key by ":" and applying the keys nested
145+
def update_options(composite_key, value, options)
146+
nesting = options
147+
keys = composite_key.sub(/\A--/, "").split(":")
148+
keys.each_with_index do |key, i|
149+
key = key.to_sym
150+
if i < keys.length - 1
151+
nesting[key] ||= {}
152+
nesting = nesting[key]
153+
else
154+
nesting[key] = parse_possible_boolean(value)
155+
end
156+
end
157+
options
158+
end
159+
160+
def parse_possible_boolean(value)
161+
case value
162+
when "true"
163+
true
164+
when "false"
165+
false
166+
else
167+
value
168+
end
107169
end
108170
end
109171
end

test/unit/cli/cli_base_test.rb

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,32 @@
11
require "test_helper"
22
require "./lib/roger/cli"
33

4+
module Roger
5+
# Just a noop command class for testing
6+
class CliNoop < Cli::Command
7+
end
8+
9+
# Open existing Cli::Base to add Noop method.
10+
class Cli::Base < Thor
11+
register(
12+
CliNoop,
13+
"noop",
14+
"noop",
15+
"noop"
16+
)
17+
tasks["noop"].options = Cli::Serve.class_options
18+
end
19+
end
20+
421
module Roger
522
# Test for roger base commands
623
class CliBaseTest < ::Test::Unit::TestCase
24+
include TestCli
25+
26+
def teardown
27+
Cli::Base.project = nil
28+
end
29+
730
def test_has_test_command
831
assert_includes Cli::Base.tasks.keys, "test"
932
end
@@ -23,5 +46,49 @@ def test_has_generate_command
2346
def test_has_version_command
2447
assert_includes Cli::Base.tasks.keys, "version"
2548
end
49+
50+
# Option passing
51+
52+
def test_pass_options_to_project
53+
run_command(%w(noop --path=henk))
54+
assert_equal "henk", Cli::Base.project.options[:path]
55+
end
56+
57+
def test_pass_roger_options_to_project
58+
run_command(%w(noop --some_option=henk))
59+
assert_equal "henk", Cli::Base.project.options[:some_option]
60+
end
61+
62+
def test_pass_multiple_roger_options_to_project
63+
run_command(%w(noop --option1=henk --option2=henk))
64+
assert_equal "henk", Cli::Base.project.options[:option1]
65+
assert_equal "henk", Cli::Base.project.options[:option2]
66+
end
67+
68+
def test_pass_roger_options_without_equal_to_project
69+
run_command(%w(noop --option1 henk --option2 pino))
70+
assert_equal "henk", Cli::Base.project.options[:option1]
71+
assert_equal "pino", Cli::Base.project.options[:option2]
72+
end
73+
74+
def test_pass_roger_nested_options_to_project
75+
run_command(%w(noop --level0:level1=henk))
76+
assert_equal "henk", Cli::Base.project.options[:level0][:level1]
77+
end
78+
79+
def test_pass_boolean_roger_options_to_project
80+
run_command(%w(noop --istrue))
81+
assert_equal true, Cli::Base.project.options[:istrue]
82+
end
83+
84+
def test_pass_boolean_literal_true_roger_options_to_project
85+
run_command(%w(noop --istrue=true))
86+
assert_equal true, Cli::Base.project.options[:istrue]
87+
end
88+
89+
def test_pass_boolean_literal_false_roger_options_to_project
90+
run_command(%w(noop --isfalse=false))
91+
assert_equal false, Cli::Base.project.options[:isfalse]
92+
end
2693
end
2794
end

0 commit comments

Comments
 (0)