module Backticks module CLI # Command-line parameter generator that relies on traditional *nix getopt # conventions. Getopt doesn't know about GNU conventions such as short and # long options; it doesn't know about abbreviations; it doesn't know about # conventions such as `--X` vs. `--no-X` or `-d` vs. `-D`. # # Although Getopt is simple, it has the tremendous advantage of being # compatible with a wide range of other schemes including GNU getopt-long, # golang flags, and most Java utilities. It's a great choice of default # CLI. module Getopt # Translate a series Ruby positional and keyword arguments into command- # parameters consisting of words and options. # # Each positional argument can be a Hash, an Array, or another object. # They are handled as follows: # - Hash is translated to a sequence of options; see #options # - Array is appended to the command line as a sequence of words # - other objects are turned into a string with #to_s and appended to the command line as a single word # # @return [Array] list of String words and options # # @example recursively find all text files # parameters('ls', l:true, R:true, '*.txt') => 'ls -l -R *.txt # # @example install your favorite gem # parameters('gem', 'install', no_document:true, 'backticks') def self.parameters(*sugar) argv = [] sugar.each do |item| case item when Array # list of words to append to argv argv.concat(item.map { |e| e.to_s }) when Hash # list of options to convert to CLI parameters argv.concat(options(item)) else # single word to append to argv argv << item.to_s end end argv end # Translate Ruby keyword arguments into command-line parameters using a # notation that is compatible with traditional Unix getopt. Command lines # generated by this method are also mostly compatible with the following: # - GNU getopt # - Ruby trollop gem # - Golang flags package # # This method accepts an unbounded set of keyword arguments (i.e. you can # pass it _any_ valid Ruby symbol as a kwarg). Each kwarg has a # value; the key/value pair is translated into a CLI option using the # following heuristic: # 1) Snake-case keys are hyphenated, e.g. :no_foo => "--no-foo" # 2) boolean values indicate a CLI flag; true includes the flag, false or nil omits it # 3) all other values indicate a CLI option that has a value. # 4) single character keys are passed as short options; {X: V} becomes "-X V" # 5) multi-character keys are passed as long options; {Xxx: V} becomes "--XXX=V" # # The generic translator doesn't know about short vs. long option names, # abbreviations, or the GNU "X vs. no-X" convention, so it does not # produce the most idiomatic or compact command line for a given program; # its output is, however, almost always valid for utilities that use # Unix-like parameters. # # @return [Array] list of String command-line options def self.options(kwargs={}) flags = [] # Transform opts into golang flags-style command line parameters; # append them to the command. kwargs.each do |kw, arg| if kw.length == 1 if arg == true # true: boolean flag flags << "-#{kw}" elsif arg # truthey: option that has a value flags << "-#{kw}" << arg.to_s else # falsey: omit boolean flag end else kw = kw.to_s.gsub('_','-') if arg == true # true: boolean flag flags << "--#{kw}" elsif arg # truthey: option that has a value flags << "--#{kw}=#{arg}" else # falsey: omit boolean flag end end end flags end end end end