<cfscript>

	// Define tag attributes and defaults.
	param name="attributes.filePath" type="string";
	param name="attributes.dedent" type="string" default=true;
	param name="attributes.indentation" type="string" default="tab";
	param name="attributes.trim" type="boolean" default=true;
	param name="attributes.append" type="boolean" default=false;
	param name="attributes.charset" type="string" default="utf-8";

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	// Since this ColdFusion custom tag deals with generated output, we only care about
	// the tag in its "end" mode once we have generated content to consume.
	if ( thistag.executionMode == "start" ) {

		exit
			method = "exitTemplate"
		;

	}

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	fileContent = thistag.generatedContent;

	if ( attributes.dedent ) {

		fileContent = dedentContent( fileContent, attributes.indentation );

	}

	if ( attributes.trim ) {

		fileContent = trim( fileContent );

	}

	targetFile = ( attributes.append )
		? fileOpen( attributes.filePath, "append", attributes.charset )
		: fileOpen( attributes.filePath, "write", attributes.charset )
	;

	try {

		fileWrite( targetFile, fileContent, attributes.charset )

	} finally {

		fileClose( targetFile );

	}

	// We don't want this tag to generate content - all content was written to the file.
	thistag.generatedContent = "";

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I remove indentation from the given input but leave the input such that the relative
	* indentation across lines remains constant.
	*/
	private string function dedentContent(
		required string input,
		required string indentation
		) {

		var indentationCharacter = ( indentation == "tab" )
			? chr( 9 )  // Tab.
			: chr( 32 ) // Space.
		;

		// In order to figure out how much we can dedent the text, we must first locate
		// the smallest amount of indentation that currently exists across all lines of
		// text. However, we only care about lines of text that have non-indentation
		// characters on them (ie, we want to skip over empty lines). As such, we're going
		// to create a pattern that must end on non-indentation (or line-break) character.
		var minWidth = input
			.reMatch( "(?m)^#indentationCharacter#*[^#indentationCharacter#\r\n]" )
			.map(
				( linePrefix ) => {

					return ( linePrefix.len() - 1 );

				}
			)
			// NOTE: ArrayMin() returns zero if array is empty.
			.min()
		;

		if ( ! minWidth ) {

			return input;

		}

		// Now that we've found the smallest amount of indentation across all lines of
		// text, we can remove exactly that amount of indentation from each line of text.
		var result = input
			.reReplace( "(?m)^#indentationCharacter#{#minWidth#}", "", "all" )
		;

		return result;

	}

</cfscript>