 			appendRegexp:
 				value: getDirectiveRegExp('append')
 
-	findConcats: (callback) ->
-		[prepends, appends] = [[], []]
+	findConcats: (options, callback) ->
+		[{relative = false}, callback] = discernOptions(options, callback)
+		{prepend, append} = concats = {prepend: [], append: []}
 		lineReader = Readline.createInterface
 			input: createReadStream(@source, encoding: @encoding)
 
 		lineReader.on 'line', (line) =>
 			if match = line.match(@prependRegexp)?[2]
-				prepends.push(match)
+				prepend.push(match)
 			else if match = line.match(@appendRegexp)?[2]
-				appends.push(match)
+				append.push(match)
 
-		lineReader.on 'close', ->
-			callback?(prepend: prepends, append: appends)
+		lineReader.on 'close', =>
+			concats = mapConcats(concats, @getRelativePath, this) if relative
+			callback?(concats)
 
-	concat: (options = {}, callback) ->
-		[options, callback] = [{}, options] if options instanceof Function
-		{newline = EOL} = options
-		@findConcats (concats) =>
-			concatted = ''
-			concats?.prepend?.map (prepend) => @getRelativePath(prepend)
-				.forEach (prependPath) ->
-					concatted += readFileSync(prependPath, encoding: @encoding)
-					concatted += newline
-			concatted += readFileSync(@source, encoding: @encoding)
-			concats?.append?.map (append) => @getRelativePath(append)
-				.forEach (appendPath) ->
-					concatted += newline
-					concatted += readFileSync(appendPath, encoding: @encoding)
-			callback?(concatted)
+	concat: (options, callback) ->
+		[options, callback] = discernOptions(options, callback)
+		concatted = ''
+		write = (chunk, encoding, cb) ->
+			concatted += chunk
+			cb?(null)
+		stream = new Writable(write: write)
+		@concatTo stream, options, (error) -> callback?(concatted)
 
-	concatTo: (file, options, callback) ->
-		[options, callback] = [{}, options] if options instanceof Function
-		{newline = EOL} = options
-		@findConcats (concats) =>
+	concatTo: (dest, options, callback) ->
+		[{separator = EOL}, callback] = discernOptions(options, callback)
+		@findConcats relative: true, (concats) =>
 			error = null
-			stream = createWriteStream(file, defaultEncoding: @encoding)
-			stream.once 'open', =>
-				concats?.prepend?.map (prepend) => @getRelativePath(prepend)
-					.forEach (prependPath) ->
-						write = readFileSync(prependPath, encoding: @encoding)
-						write += newline
-						stream.write(write)
-				write = readFileSync(@source, encoding: @encoding)
-				stream.write(write)
-				concats?.append?.map (append) => @getRelativePath(append)
-					.forEach (appendPath) ->
-						write = newline
-						write += readFileSync(appendPath, encoding: @encoding)
-						stream.write(write)
-				stream.end ->
-					callback?(error)
+			paths = [concats.prepend..., @source, concats.append...]
+			ensureStream dest, defaultEncoding: @encoding, (stream) ->
+				joinFiles(paths, @encoding, separator, stream)
+				stream.end -> callback?(error)
 
 	getRelativePath: (file) ->
 		sourceDir = Path.dirname(@source)

		js: '//'
		coffee: '#'

	getSourceCommenter = (source) ->
		ext = Path.extname(source).slice(1)
		commenter = CodeCat.Commenters[ext]
		if commenter? then regexpEscape(commenter) else ''

	regexpEscape = (str) ->
		str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&')

{EOL} = require 'os'
Readline = require 'readline'
{readFileSync, createReadStream, createWriteStream} = require 'fs'
{Writable} = require 'stream'
Path = require 'path'

module.exports = class CodeCat
