optparse.coffee

- Jump To … +
  browser.coffee cake.coffee coffeescript.coffee command.coffee grammar.coffee helpers.coffee index.coffee lexer.coffee nodes.coffee optparse.coffee register.coffee repl.coffee rewriter.coffee scope.litcoffee sourcemap.litcoffee

- # optparse.coffee

- §

      {repeat} = require './helpers'

- §

  A simple OptionParser class to parse option flags from the command-line. Use it like so:

      parser  = new OptionParser switches, helpBanner
      options = parser.parse process.argv

  The first non-option is considered to be the start of the file (and file option) list, and all subsequent arguments are left unparsed.

  The `coffee` command uses an instance of OptionParser to parse its command-line arguments in `src/command.coffee`.

      exports.OptionParser = class OptionParser

- §

  Initialize with a list of valid options, in the form:

      [short-flag, long-flag, description]

  Along with an optional banner for the usage help.

        constructor: (ruleDeclarations, @banner) ->
          @rules = buildRules ruleDeclarations

- §

  Parse the list of arguments, populating an `options` object with all of the specified options, and return it. Options after the first non-option argument are treated as arguments. `options.arguments` will be an array containing the remaining arguments. This is a simpler API than many option parsers that allow you to attach callback actions for every flag. Instead, you’re responsible for interpreting the options object.

        parse: (args) ->

- §

  The CoffeeScript option parser is a little odd; options after the first non-option argument are treated as non-option arguments themselves. Optional arguments are normalized by expanding merged flags into multiple flags. This allows you to have `-wl` be the same as `--watch --lint`. Note that executable scripts with a shebang (`#!`) line should use the line `#!/usr/bin/env coffee`, or `#!/absolute/path/to/coffee`, without a `--` argument after, because that will fail on Linux (see #3946).

          {rules, positional} = normalizeArguments args, @rules.flagDict
          options = {}

- §

  The `argument` field is added to the rule instance non-destructively by `normalizeArguments`.

          for {hasArgument, argument, isList, name} in rules
            if hasArgument
              if isList
                options[name] ?= []
                options[name].push argument
              else
                options[name] = argument
            else
              options[name] = true

          if positional[0] is '--'
            options.doubleDashed = yes
            positional = positional[1..]

          options.arguments = positional
          options

- §

  Return the help text for this OptionParser, listing and describing all of the valid options, for `--help` and such.

        help: ->
          lines = []
          lines.unshift "#{@banner}\n" if @banner
          for rule in @rules.ruleList
            spaces  = 15 - rule.longFlag.length
            spaces  = if spaces > 0 then repeat ' ', spaces else ''
            letPart = if rule.shortFlag then rule.shortFlag + ', ' else '    '
            lines.push '  ' + letPart + rule.longFlag + spaces + rule.description
          "\n#{ lines.join('\n') }\n"

- §

  ## Helpers

- §
- §

  Regex matchers for option flags on the command line and their rules.

      LONG_FLAG  = /^(--\w[\w\-]*)/
      SHORT_FLAG = /^(-\w)$/
      MULTI_FLAG = /^-(\w{2,})/

- §

  Matches the long flag part of a rule for an option with an argument. Not applied to anything in process.argv.

      OPTIONAL   = /\[(\w+(\*?))\]/

- §

  Build and return the list of option rules. If the optional short-flag is unspecified, leave it out by padding with `null`.

      buildRules = (ruleDeclarations) ->
        ruleList = for tuple in ruleDeclarations
          tuple.unshift null if tuple.length < 3
          buildRule tuple...
        flagDict = {}
        for rule in ruleList

- §

  `shortFlag` is null if not provided in the rule.

          for flag in [rule.shortFlag, rule.longFlag] when flag?
            if flagDict[flag]?
              throw new Error "flag #{flag} for switch #{rule.name}
                was already declared for switch #{flagDict[flag].name}"
            flagDict[flag] = rule

        {ruleList, flagDict}

- §

  Build a rule from a `-o` short flag, a `--output [DIR]` long flag, and the description of what the option does.

      buildRule = (shortFlag, longFlag, description) ->
        match     = longFlag.match(OPTIONAL)
        shortFlag = shortFlag?.match(SHORT_FLAG)[1]
        longFlag  = longFlag.match(LONG_FLAG)[1]
        {
          name:         longFlag.replace /^--/, ''
          shortFlag:    shortFlag
          longFlag:     longFlag
          description:  description
          hasArgument:  !!(match and match[1])
          isList:       !!(match and match[2])
        }

      normalizeArguments = (args, flagDict) ->
        rules = []
        positional = []
        needsArgOpt = null
        for arg, argIndex in args

- §

  If the previous argument given to the script was an option that uses the next command-line argument as its argument, create copy of the option’s rule with an `argument` field.

          if needsArgOpt?
            withArg = Object.assign {}, needsArgOpt.rule, {argument: arg}
            rules.push withArg
            needsArgOpt = null
            continue

          multiFlags = arg.match(MULTI_FLAG)?[1]
            .split('')
            .map (flagName) -> "-#{flagName}"
          if multiFlags?
            multiOpts = multiFlags.map (flag) ->
              rule = flagDict[flag]
              unless rule?
                throw new Error "unrecognized option #{flag} in multi-flag #{arg}"
              {rule, flag}

- §

  Only the last flag in a multi-flag may have an argument.

            [innerOpts..., lastOpt] = multiOpts
            for {rule, flag} in innerOpts
              if rule.hasArgument
                throw new Error "cannot use option #{flag} in multi-flag #{arg} except
                as the last option, because it needs an argument"
              rules.push rule
            if lastOpt.rule.hasArgument
              needsArgOpt = lastOpt
            else
              rules.push lastOpt.rule
          else if ([LONG_FLAG, SHORT_FLAG].some (pat) -> arg.match(pat)?)
            singleRule = flagDict[arg]
            unless singleRule?
              throw new Error "unrecognized option #{arg}"
            if singleRule.hasArgument
              needsArgOpt = {rule: singleRule, flag: arg}
            else
              rules.push singleRule
          else

- §

  This is a positional argument.

            positional = args[argIndex..]
            break

        if needsArgOpt?
          throw new Error "value required for #{needsArgOpt.flag}, but it was the last
          argument provided"
        {rules, positional}