 
 cloneSettings = (settings, addArrays) ->
   settings = {...settings}
+  if settings.formats?
+    settings.formats = [...settings.formats]
+  else if addArrays
+    settings.formats = []
   if settings.mappings?
     settings.mappings = new Mappings settings.mappings
   else if addArrays

      console.log error
  if Array.isArray filenames
    for filename in filenames
      processor.convertTo filename, formats
  else
    processor.convertTo filenames, formats

inputRequire = (filename, settings = getSettings() ? defaultSettings, dirname) ->
  filename = path.join dirname, filename if dirname?
  Input.recognize filename, undefined, settings


filter = (filenames, pattern, options) ->
  require('minimatch').match filenames, pattern, options

dashArgRegExp = /^-([a-zA-Z]{2,})$/

inputCache = new Map  # maps filenames to previously loaded `Input`s

class Driver extends HasSettings
  constructor: (@parent = getDriver()) ->
    super()
    @resetSettings()
  resetSettings: ->
    @settings = cloneSettings @parent?.settings ? defaultSettings, true
  loadMaketile: ->
    return @maketile unless @maketile == undefined
    filenames = glob @settings.maketile, nodir: true
    @maketile =
      if filenames.length
        inputRequire filenames.sort().pop(), @settings
      else
        null
  main: (args = process.argv[2..]) -> runWithDriver @, =>
    showHelp = 'No filename arguments and no Maketile to run; showing --help'
    ranMaketile = false
    numFileArgs = 0
    inits = []  # array of objects to call doInit() on
    stack = []  # {settings, inits} objects for implementing parens
    # `for arg, i in args` but allowing i to advance and args to get appended to
    i = 0
    loop
      ## Automatically run Maketile if we're out of arguments
      ## and haven't processed any files yet.
      if i >= args.length and not numFileArgs and not ranMaketile and
         @loadMaketile()?
        ranMaketile = true
        if @maketile instanceof Args
          showHelp = "No filename arguments on command line or in '#{@maketile.filename}'"
          args.push ...@maketile.args
        else if @maketile instanceof Mapping
          showHelp = false
          @maketile.doInit()
          if @maketile.isFunction()
            @maketile.map()
          else
            console.log "Maketile '#{@maketile.filename}' did not `export default` a function"
        else
          showHelp = false
          console.log "Unrecognized Maketile '#{@maketile.filename}' of type '#{@maketile.constructor?.name}'"
      break if i >= args.length

      ## Split up single-dash arguments with multiple options like -pP
      if (match = args[i].match dashArgRegExp)?
        args[i..i] =
          for char in match[1]
            "-#{char}"

      ## Main argument handling
      switch (arg = args[i])
        when '-h', '--help'
          help()
          return
        when '-f', '--force'
          @settings.force = true
        when '-m', '--margin'
          @settings.keepMargins = true
        when '--uneven'
          @settings.keepUneven = true
        when '--hidden'
          @settings.keepHidden = true
        when '--tw', '--tile-width'
          i++
          arg = parseFloat args[i]
          if arg?
            @settings.forceWidth = arg
          else
            console.warn "Invalid argument to --tile-width: #{args[i]}"
        when '--th', '--tile-height'
          i++
          arg = parseFloat args[i]
          if arg?
            @settings.forceHeight = arg
          else
            console.warn "Invalid argument to --tile-height: #{args[i]}"
        when '--bg', '--background'
          i++
          @settings.background = args[i]
        when '-s', '--share'
          i++
          [key, ...value] = args[i].split '='
          inits.push setter =
            new ShareSetter key, value.join '='  # ignore later =s
          setter.doInit()
        when '-o', '--output'
          i++
          @settings.outputDir = args[i]
        when '-O', '--output-stem'
          i++
          @settings.outputStem = args[i]
        when '--os', '--output-svg'
          i++
          @settings.outputDirExt['.svg'] = args[i]
        when '--op', '--output-pdf'
          i++
          @settings.outputDirExt['.pdf'] = args[i]
        when '--oP', '--output-png'
          i++
          @settings.outputDirExt['.png'] = args[i]
        when '--ot', '--output-tex'
          i++
          @settings.outputDirExt['.svg_tex'] = args[i]
        when '-i', '--inkscape'
          i++
          @settings.inkscape = args[i]
        when '-p', '--pdf'
          @settings.formats.push 'pdf'
        when '-P', '--png'
          @settings.formats.push 'png'
        when '-t', '--tex'
          @settings.texText = true
        when '--no-sanitize'
          @settings.sanitize = false
        when '--no-overflow'
          @settings.overflowDefault = null  # no default
        when '--no-inline'
          @settings.inlineImages = false
        when '-j', '--jobs'
          i++
          arg = parseInt args[i], 10
          if arg
            @settings.jobs = arg
          else
            console.warn "Invalid argument to --jobs: #{args[i]}"
        when '('
          stack.push
            settings: cloneSettings @settings
            inits: [...inits]
        when ')'
          oldInits = inits
          if stack.length
            {settings: @settings, inits} = stack.pop()
          else
            console.warn "Unmatched ')'"
            @resetSettings()
            inits = []
          ## Check whether inits list changed.  If so,
          ## reset globalShare without changing top-level object identity,
          ## and re-initialize all still-active mappings to restore side-effects.
          unless inits.length == oldInits.length and
                inits.every (init, i) -> init == oldInits[i] 
            delete globalShare[key] for key of globalShare
            init.doInit() for init in inits
        else
          showHelp = false
          numFileArgs++
          ## If argument is not a string (e.g. Mapping or Style),
          ## or is a string that corresponds to an existing filename.
          ## process it directly.
          exists = typeof arg != 'string' or inputCache.has arg
          unless exists
            try
              exists = fs.statSync arg
          if exists
            files = [arg]
          ## Otherwise, try matching as a glob pattern.
          else if (files = glob arg).length
          ## Otherwise, check for a matching rule in the Maketile.
          else
            if @loadMaketile()?.module?.hasOwnProperty arg
              @maketile.doInit()
              @maketile.module[arg]()
            else
              console.warn "No files or Maketile rules match '#{arg}'"
          append = i+1  # where to append Args
          for file in files
            if typeof file == 'string'
              cached = inputCache.has file
              console.log '*', file, if cached then '(cached)' else ''
              if cached
                input = inputCache.get file
                input.settings = @settings
              else
                input = Input.recognize file, undefined, @settings
                ## Cache Mapping files, as we only capture `onInit` etc. callbacks on
                ## the first load (top-level code run only during first `require`).
                ## Don't cache drawing files, as we might have changed options like
                ## `keepMargins` and `keepUneven` which affect parsing.
                inputCache.set file, input if input instanceof Mapping
            else if file? and typeof file == 'object'
              console.log '*', file.constructor?.name, file.filename
            else
              input = file
              continue  # skip boolean, number, null, undefined
            if input instanceof Mapping
              @settings.mappings.push input
              inits.push input
              input.doInit()
            else if input instanceof Style
              @settings.styles.push input
            else if input instanceof Drawing or input instanceof Drawings
              svgs = input.render @settings
              ## Convert to any additional formats.  Even if SVG files didn't
              ## change, we may not have done these conversions before or in the
              ## last run of SVG Tiler, so let svgink compare mod times and decide.
              convert svgs, @settings
              ## Reset -O output filename stem override unless it uses `*`.
              if @settings.outputStem? and not @settings.outputStem.includes '*'
                @settings.outputStem = null
            else if input instanceof SVGFile
              convert input.filename, @settings
            else if input instanceof Args
              args[append...append] = input.args
              append += input.args.length
            else
              console.log "Unrecognized file '#{file}' of type '#{input?.constructor?.name}'"
      i++
    if showHelp
      console.log showHelp
      help()

  run: (...protoArgs) ->
    args = []
    for arg in protoArgs
      if typeof arg == 'string'
        args.push ...(parseIntoArgs arg)
      else if Array.isArray arg
        args.push ...(arg.flat Infinity)
      else  # Mapping, Style, etc.
        args.push arg
    console.log '> svgtiler', (
      for arg in args
        if typeof arg == 'string'
          arg
        else if arg?.constructor?.name?
          if arg.filename?
            "[#{arg.constructor.name} #{arg.filename}]"
          else
            "[#{arg.constructor.name}]"
        else
          "[#{typeof arg} #{arg}]"
    ).join ' '
    @main args

main = (args...) -> new Driver().main args...
run = (args...) -> new Driver().run args...

svgtiler = Object.assign run, {
  SVGContent, SVGWrapped, SVGSymbol, unrecognizedSymbol,
