# Awful experience when add sub-command specified options use OptionParser

**URL:** https://forum.crystal-lang.org/t/awful-experience-when-add-sub-command-specified-options-use-optionparser/8784
**Category:** Help & Support
**Created:** [March 15, 2026, 6:44pm UTC](https://forum.crystal-lang.org/t/awful-experience-when-add-sub-command-specified-options-use-optionparser/8784 "2026-03-15T18:44:28Z")
**Posts on this page:** 7
**Page:** 1

<div class="post-metadata">

### Author: ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)
#### Post date: [March 15, 2026, 6:44pm UTC](https://forum.crystal-lang.org/t/awful-experience-when-add-sub-command-specified-options-use-optionparser/8784/1 "2026-03-15T18:44:28Z")

</div>

Following is the real usage in my project.

> <https://github.com/crystal-china/procodile/blob/3898229950377a60d775af780747c625d62c2cf4/src/procodile.cr#L29-L91>

I have to run `OptionParser.parse` twice.

- the first time run on line 36, only for get the correct sub-command in line 49
- the second time run on line 52, line 54-56, add logic for parse opts of sub-commands

Maybe this is the only expected approach? I feel like it could be simpler,any idea?

---

<div class="post-metadata">

### Author: ![kojix2](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/kojix2/32/1583_2.png) [@kojix2](https://forum.crystal-lang.org/u/kojix2)
#### Post date: [March 16, 2026, 2:11am UTC](https://forum.crystal-lang.org/t/awful-experience-when-add-sub-command-specified-options-use-optionparser/8784/2 "2026-03-16T02:11:28Z")

</div>

It’s common to want to build a command-line tool where subcommands share common options.

My approach is to define a macro and expand the shared options inside each subcommand. After macro expansion the code essentially becomes boilerplate.

The approach is simple enough that I imagine you’ve probably already thought of it. You can easily handle cases where the number of subcommands grows or where the shared parts start to diverge.

The unfortunate part is that `OptionParser` in Crystal’s standard library is difficult to modify due to historical constraints. Just last month, [a proposal to add short option bundling](https://github.com/crystal-lang/crystal/pull/16563) — a basic feature — was [reverted](https://github.com/crystal-lang/crystal/pull/16670) because it breaks backward compatibility.

Building command-line tools is my main use case for Crystal, so I hope there’s a way to work around this.

---

<div class="post-metadata">

### Author: ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)
#### Post date: [March 16, 2026, 5:34am UTC](https://forum.crystal-lang.org/t/awful-experience-when-add-sub-command-specified-options-use-optionparser/8784/3 "2026-03-16T05:34:13Z")

</div>

My solution is to maintain a hash mapping, `{} of Symbol => Proc(OptionParser, CLI, Nil)`, symbol is the sub-command string, add extra opt rule for sub-command in the Proc, if user input this sub-command, then call the Proc with opts object as arg.

> The approach is simple enough that I imagine you’ve probably already thought of it

Actually, I didn’t come up with the idea of doing it this way，😄, I thought if follow your instructions, we still need a separate OptionParser to process it and find the correct sub-command, right?

---

<div class="post-metadata">

### Author: ![kojix2](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/kojix2/32/1583_2.png) [@kojix2](https://forum.crystal-lang.org/u/kojix2)
#### Post date: [March 16, 2026, 7:49am UTC](https://forum.crystal-lang.org/t/awful-experience-when-add-sub-command-specified-options-use-optionparser/8784/4 "2026-03-16T07:49:15Z")

</div>

My approach is a naive extension of the official reference, requiring only a single `parse` call:

```crystal
macro on_common
  opt.on("-a", "--aa", "Common option A") do
  end

  opt.on("-b", "--bb", "Common option B") do
  end
end

OptionParser.parse do |opt|
  opt.on("sub1", "This is subcommand 1") do
    opt.on("-x", "--xx", "Extra option for sub1") do
    end
    on_common
  end

  opt.on("sub2", "This is subcommand 2") do
    opt.on("-y", "--yy", "Extra option for sub2") do
    end
    on_common
  end
end

```

This is just one example. I prefer to define all command-line options in a single file, so this approach works well for me. Note that this naive approach has its own drawbacks, and I am not claiming it is always the best solution.

---

<div class="post-metadata">

### Author: ![straight-shoota](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/straight-shoota/32/36_2.png) [@straight-shoota](https://forum.crystal-lang.org/u/straight-shoota)
#### Post date: [March 18, 2026, 10:58pm UTC](https://forum.crystal-lang.org/t/awful-experience-when-add-sub-command-specified-options-use-optionparser/8784/5 "2026-03-18T22:58:07Z")

</div>

What about encoding sub commands and global options directly in the option parser instance?

```cr
OptionParser.parse do |opt|
  opt.on("-r", "--root PATH", "The path to the root of your application") do |root|
    # global option
  end
  
  opt.on("subcommand", "Run subcommand") do
    # handle subcommand
    opt.on("-f", "--force", "Force the subcommand") do
       # subcommand option
    end
  end
end

```

---

<div class="post-metadata">

### Author: ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)
#### Post date: [March 28, 2026, 2:34pm UTC](https://forum.crystal-lang.org/t/awful-experience-when-add-sub-command-specified-options-use-optionparser/8784/6 "2026-03-28T14:34:00Z")

</div>

It’s probably work, I will try it when I free. thanks

---

<div class="post-metadata">

### Author: ![zw963](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/zw963/32/1623_2.png) [@zw963](https://forum.crystal-lang.org/u/zw963)
#### Post date: [April 29, 2026, 4:24am UTC](https://forum.crystal-lang.org/t/awful-experience-when-add-sub-command-specified-options-use-optionparser/8784/7 "2026-04-29T04:24:24Z")

</div>

Done, switched to use sub command instead.

If anyone’s interested, [here](https://github.com/crystal-china/procodile/blob/4f10807e54f705807fb0bd49c88c627bd18b5d12/src/procodile/cli_parser.cr)’s the source code. All the subcommands are still defined in separate files.
