Featured image

If you need to build a command-line tool with flag parsing and can’t decide on the right ecosystem, give Ruby a look. You don’t even need to pull in third-party gems to get started - OptionParser is built right into the standard library. All it takes is defining your help message and declaring your flags with its expressive block syntax. Take a look at the snippet above - it’s remarkable how clean and readable CLI flag declarations look using OptionParser’s DSL.

How do you usually build CLI tools? Which languages and libraries do you reach for in your projects?

require 'optparse'

module Arithmetic
  class CLI
    OPS = { 'add' => :+, 'sub' => :-, 'mul' => :*, 'div' => :/ }.freeze
    def initialize(args)
      @args = args.dup
      @options = {}
    end
    def run
      parser = OptionParser.new do |opts|
        opts.banner = 'Usage: ruby arithmetic.rb -n <numbers> -o <operation>'
        opts.separator ''
        opts.separator 'Example:'
        opts.separator '  ruby arithmetic.rb -n 1,2,3 -o add'
        opts.separator '  1+2+3=6'
        opts.separator ''
        opts.on(
          '-n', '--numbers <1,2,3,...>', Array,
          'Comma-separated list of integers'
        ) do |nums|
          raise OptionParser::InvalidArgument,
            'must not be empty' if nums.empty?
          @options[:nums] = nums.map(&:to_i)
        end
        opts.on(
          '-o', '--operation <add|sub|mul|div>', String,
          "Operation: #{OPS.keys.join(', ')}"
        ) do |op|
          raise OptionParser::InvalidArgument,
            "must be one of: #{OPS.keys.join(', ')}" unless OPS.key?(op)
          @options[:op] = op
        end
      end
      parser.parse!(@args)
      return puts(parser.help) unless @options[:nums] && @options[:op]
      op = OPS[@options[:op]]
      nums = @options[:nums]
      puts "#{nums.join(op.to_s)}=#{nums.reduce(op)}"
    end
  end
end
Arithmetic::CLI.new(ARGV).run

# Save to arithmetic.rb and run:
# $ ruby arithmetic.rb -h
# $ ruby arithmetic.rb -n 7,8,9 -o add