   # options.writer: the options to pass to writer()
   # options.events: the event/listener pairs to register with the reader
   pipe: (pathOrStream, options) ->
+
+    # get a reader for *this* path
     reader = @reader options?.reader
-    reader.on event, listener for event,listener of options?.events?.reader
-    pathOrStream = pathOrStream.writer options?.writer if pathOrStream instanceof Path
-    pathOrStream.on event, listener for event,listener of options?.events?.writer
-    return reader.pipe pathOrStream
-
-  list: (options, done) ->
-    @_mustExist 'to list directory'
-    {options, done} = @_optionsAndDone options, done
-    done ?= options?.all
-    # TODO: allow a `returnType` option which has us store result in an object
-    #       instead of an array?
-    if done?
-      fs.readdir @path, (error, array) =>
-        if error? then return done error
-        done? undefined, @_processList options, done, array
-    else
-      array = fs.readdirSync @path
-      @_processList options, done, array
-
-  _processList: (options, done, array) ->
+
+    if reader.error? then return reader
+
+    if options?.events?.reader?
+      reader.on event, listener for event, listener of options.events.reader
+
+    # if target is a path then get a writer for it
+    writer =
+      if pathOrStream instanceof Path then pathOrStream.writer options?.writer
+      else pathOrStream
+
+    if writer.error? then return writer
+
+    if options?.events?.writer?
+      writer.on event, listener for event, listener of options.events.writer
+
+    # finally pipe together and return result for chaining further
+    return reader.pipe writer
+
+
+  list: (arg) ->
+
+    options = if typeof arg is 'function' then done:arg else arg
+
+    unless @isDir()
+      error = error: 'list() requires directory'
+      if options?.done? then return options.done error
+      else return error
+
+
+    if options?.done?
+      done = options.done
+      path = this
+      fs.readdir @path, (error, array) ->
+        ### istanbul ignore if ###
+        if error? then done error
+        else done null, path._processList options, array
+
+    else @_processList options, fs.readdirSync @path
+
+
+  _processList: (options, array) ->
+
     acceptString = options?.acceptString
     acceptPath   = options?.acceptPath
-    each   = options?.each
+    each         = options?.each
+
+    # store all the accepted paths
     paths = []
+
+    # count the rejected strings/paths
     rejectedStrings = rejectedPaths = 0
 
+    # check array with filters, report each, count rejects
     for string in array
+
+      # if there's no filter, or if the filter accepts
       if not acceptString? or acceptString string
+
         path = @to string
+
+        # if there's no filter, or if the filter accepts
         if not acceptPath? or acceptPath path
-          paths.push path
-          each? path:path
-        else
-          rejectedPaths++
-      else
-        rejectedStrings++
+
+          paths[paths.length] = path
+          # call each if it exists
+          each? {path}
+
+        else rejectedPaths++
+
+      else rejectedStrings++
 
     return paths:paths, rejected:{strings:rejectedStrings, paths:rejectedPaths}
 
-  files: (options, done) ->
-    {options, done} = @_optionsAndDone options, done
-    options ?= {}
-    options.acceptPath ?= (path) -> path.isFile()
-    @list options, done
-
-  dirs: (options, done) ->
-    {options, done} = @_optionsAndDone options, done
-    options ?= {}
-    options.acceptPath ?= (path) -> path.isDir()
-    @list options, done
-
-  # TODO: expect this path to be a file and reference file by name
-  #file: (path) ->
-  # read stats
-  # ensure isFile() is true
-
-  # TODO: expect this path to be a directory and reference directory by name
-  #dir: (path) ->
-  # read stats
-  # ensure isDir() is true
+
+  files: (arg) ->
+
+    options = if typeof arg is 'function' then done:arg else arg
+
+    if options?
+
+      # clone their options
+      options = Object.assign {}, options
+
+      # wrap their filter so we can use `isFile` first
+      if options.acceptPath?
+        theirFilter = options.acceptPath
+        options.acceptPath = (path) -> path.isFile() and theirFilter path
+
+      # they aren't filtering, so, use ours directly
+      else options.acceptPath = isFile
+
+    # supply our files filter
+    else options = acceptPath: isFile
+
+    @list options
+
+
+  dirs: (arg) ->
+
+    options = if typeof arg is 'function' then done:arg else arg
+
+    if options?
+
+      # clone their options
+      options = Object.assign {}, options
+
+      # wrap their filter so we can use `isDir` first
+      if options.acceptPath?
+        theirFilter = options.acceptPath
+        options.acceptPath = (path) -> path.isDir() and theirFilter path
+
+      # they aren't filtering, so, use ours directly
+      else options.acceptPath = isDir
+
+    # supply our files filter
+    else options = acceptPath: isDir
+
+    @list options
+
 
   to: (path) ->
-    unless path? then throw new Error 'must specify path to to()'
+
     if 'string' is typeof path
       newPath = corepath.join @path, path
-      if newPath is @path then return this else return new Path newPath
-    else if Array.isArray path
-      new Path @parts.slice().concat path
-    else throw new Error 'to(path) requires either a string or array of strings'
+      if newPath is @path then this else new Path newPath
+
+    else if Array.isArray path then new Path @parts.concat path
+
+    else error: 'to(path) requires either a string or array of strings'
+
 
   resolve: (path) ->
     path = corepath.resolve @path, path
     if path is @path then this else new Path path
 
-  relativeTo: (to) ->
-    path = corepath.relative @path, to
-    if path is @path then this else new Path path
+
+  relativeTo: (path) ->
+    path = corepath.relative @path, path
+    if path.length < 1 then return this else new Path path
+
 
   normalize: () ->
     normalized = corepath.normalize @path
     if normalized is @path then this else new Path normalized
 
+
   subpath: (start, end) -> new Path (if @path is '/' then '' else @parts[start...end])
 
+
   part: (index) -> @parts[index]
+
+
+# alias:
+Path::extension = Path::extname
+
 
 # Use these ways:
 #  1a. {Path} = require 'fspath'

fs       = require 'fs'
corepath = require 'path'

hasRoot = /// ^(
  \/           # *nix style root
  | [a-zA-Z]\: # windows drive spec
  | \\         # no drive spec, but, is absolute from current drive
) ///

class Path
  constructor: (path) ->
    @_the = {}
    if path?
      if 'string' is typeof path
        @path = path
        @parts = path.split corepath.sep # split into array
      else if Array.isArray path
        @path = path.join corepath.sep
        @parts = path.slice() # copy array
      else
        throw new Error 'Path requires either string or array as `path` to constructor'
    else
      @path = '.'
      @parts = [ '.' ]

    @isAbsolute = hasRoot.test @path
    @isRelative = not @isAbsolute

  reset: ->
    delete @_the
    delete @_stats
    @_the = {}

  stats: (done) ->
    if done?
      fs.stat @path, (error, stats) =>
        @_stats = stats
        # get rid of error if it's a 'path doesnt exist' error
        if error?.code is 'ENOENT' then error = null
        done error, stats
    else
      try
        delete @_stats
        @_stats = fs.statSync @path
      catch error
        # errno is 34 in node 0.10.*
        # errno is -2 in node 0.12.* and 4.* ... so, use 'code'
        if error.code is 'ENOENT' then return
        else throw error

  refresh: (done) ->
    @reset()
    @stats done

  toString: -> @path

  _fromStats: (done, getter) ->
    if done? then @stats (error, stats) ->
      if error?
        if error.errno is 34 then done undefined, getter()
        else done error
      else
        done undefined, getter stats

    else getter @stats()


  filename: -> @_the.filename  ? (@_the.filename  = corepath.basename @basename(), @extname())
  extname : -> @_the.extension ? (@_the.extension = corepath.extname @basename())
  dirname : -> @_the.dirname   ? (@_the.dirname   = corepath.basename corepath.dirname @path)

  parent: -> @_the.parent ? (@_the.parent = @to '..')


  up: (count = 1) ->
    count = Math.max 1, Math.min 20, count
