Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at NCDevCon 2011 (Raleigh, NC) with:

How To UN-Unformat Your Code (Like A Pro)

By Ben Nadel on
Tags: ColdFusion

After my previous post on how to quickly and easily take my style of formatting and strip it out of ColdFusion code, some people suggested that I make a function that goes back the other way (adding my style of formatting into ColdFusion code). While I love this concept, the problem is actually quite a bear! Removing formatting doesn't really require much understanding of the code - you simply have to match patterns; adding formatting back in, however, now that requires really understanding what the code is doing at a detailed level. As such, there is no way that I would be able to do that, at least not with the attention span I was willing to give it. But, I did come up with a quick little something that might at least get you in the right direction.

Now, the Obsessive.cfc ColdFusion component has two methods:

  • Obsessive::format( code )
  • Obsessive::unformat( code )

I have only briefly tested this, so this was more for fun than anything else (don't take it as finished). It seems to work OK - it tries to put in appropriate tag-based line breaks and spacing. There's so much more that would need to be done; for example, normalizing all of the tag attributes (to use double quotes). Anyway, if you are curious, here is what I have:

Obsessive.cfc

  • <cfcomponent
  • output="false"
  • hint="I am a component written by Ben Nadel to help people remove Ben's own meticulous formatting from his code - some kids just aren't cool enough ;)">
  •  
  •  
  • <cffunction
  • name="init"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I initialize this component.">
  •  
  • <!--- I am the code that being unformatted. --->
  • <cfset this.setCode( "" ) />
  •  
  • <!--- Set up some constants. --->
  • <cfset this.tab = chr( 9 ) />
  • <cfset this.newLine = (chr( 13 ) & chr( 10 )) />
  • <cfset this.newLine2 = (this.newLine & this.newLine) />
  •  
  • <!--- Return this component reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="createMatcher"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I create a pattern matcher based on the given regular expression and store it as the internal matcher.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="pattern"
  • type="string"
  • required="true"
  • hint="I am the regular expression pattern."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!--- Compile the regular expression for the tag. --->
  • <cfset local.pattern = createObject(
  • "java",
  • "java.util.regex.Pattern"
  • ).compile(
  • javaCast( "string", arguments.pattern )
  • ) />
  •  
  • <!---
  • Create a pattern matcher for the given pattern and
  • the current code.
  • --->
  • <cfset local.matcher = local.pattern.matcher(
  • this.getCode()
  • ) />
  •  
  • <!--- Store the current matcher. --->
  • <cfset this.setMatcher( local.matcher ) />
  •  
  • <!--- Return this component reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="findMatch"
  • access="public"
  • returntype="boolean"
  • output="false"
  • hint="I call find on the current matcher.">
  •  
  • <!--- Check to see if the next match was found. --->
  • <cfif this.getMatcher().find()>
  •  
  • <!--- The next match was found. --->
  • <cfreturn true />
  •  
  • <cfelse>
  •  
  • <!---
  • The next match was not found. At this point, we
  • want to append any existing code into the buffer
  • and then comit the buffer back into the code.
  • --->
  •  
  • <!--- Append the tail. --->
  • <cfset this.getMatcher().appendTail( this.getBuffer() ) />
  •  
  • <!--- Commit the buffer into the code. --->
  • <cfset this.setCode( this.getBuffer().toString() ) />
  •  
  • <!--- Return false. --->
  • <cfreturn false />
  •  
  • </cfif>
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="format"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="I format the given code (adding Ben's code meticulous code formatting - as much as possible).">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="code"
  • type="string"
  • required="true"
  • hint="I am the code being formatted."
  • />
  •  
  • <!--- Set the code, format it, and return it. --->
  • <cfreturn this
  • .setCode( arguments.code )
  • .formatCFTag( "CFArgument", true )
  • .formatCFTag( "CFCookie", true )
  • .formatCFTag( "CFComponent" )
  • .formatCFTag( "CFContent", true )
  • .formatCFTag( "CFDirectory", true )
  • .formatCFTag( "CFFunction" )
  • .formatCFTag( "CFHeader", true )
  • .formatCFTag( "CFHttp" )
  • .formatCFTag( "CFHttpParam", true )
  • .formatCFTag( "CFImage", true )
  • .formatCFTag( "CFLocation", true )
  • .formatCFTag( "CFLock" )
  • .formatCFTag( "CFLoop" )
  • .formatCFTag( "CFMail" )
  • .formatCFTag( "CFMailParam", true )
  • .formatCFTag( "CFParam", true )
  • .formatCFTag( "CFSetting", true )
  • .formatCFTag( "CFZip" )
  • .formatCFTag( "CFZipParam", true )
  • .formatSpacing()
  • .formatComments()
  • .getCode()
  • />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="formatCFTag"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I format the given ColdFusoin tag with the given name.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="tagName"
  • type="string"
  • required="true"
  • hint="I am the name of the tag being formatted."
  • />
  •  
  • <cfargument
  • name="isSelfClosing"
  • type="boolean"
  • required="false"
  • default="false"
  • hint="I flag whether or not this type of flag should be self-closing."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!--- Create the pattern for the tag. --->
  • <cfsavecontent variable="local.regex">(?ixm)
  • <cfoutput>
  •  
  • ^(\p{Blank}*)<#arguments.tagName#
  • (
  • [^>"']
  • |
  • "([^"]|"")*"
  • |
  • '([^']|'')*'
  • )*
  • >
  •  
  • </cfoutput>
  • </cfsavecontent>
  •  
  • <!--- Create a new matcher. --->
  • <cfset this.createMatcher( local.regex ) />
  •  
  • <!--- Loop over each of the match. --->
  • <cfloop condition="this.findMatch()">
  •  
  • <!--- Get the tag code. --->
  • <cfset local.tagCode = trim( this.getMatch() ) />
  •  
  • <!--- Parse the tag code into a data set. --->
  • <cfset local.tagData = this.parseTag(
  • trim( this.getMatch() )
  • ) />
  •  
  • <!--- Get the leading space. --->
  • <cfset local.leadingSpace = this.getMatch( 1 ) />
  •  
  • <!---
  • Normalize the spaces (replacing 4 spaces with a
  • tab). There's simply no hope for code that was
  • using two-space indents.
  • --->
  • <cfset local.leadingSpace = reReplace(
  • local.leadingSpace,
  • " {4}",
  • this.tab,
  • "all"
  • ) />
  •  
  • <!---
  • Now that we have replaced-in tabs, remove any left
  • over spaces from the leading space.
  • --->
  • <cfset local.leadingSpace = reReplace(
  • local.leadingSpace,
  • " +",
  • "",
  • "all"
  • ) />
  •  
  •  
  • <!---
  • Create a constant for the indent of the sub-lines.
  • This will be the leading space plus an additional
  • tab character.
  • --->
  • <cfset local.subIndent = (local.leadingSpace & this.tab) />
  •  
  •  
  • <!---
  • Now that have the tag data parsed and the leading
  • space (indent), we can now construct the new tag
  • output.
  • --->
  • <cfset local.newCode = (
  • local.leadingSpace &
  • "<" &
  • lCase( local.tagData.name )
  • ) />
  •  
  • <!--- Loop over each attribute to append to code. --->
  • <cfloop
  • index="local.attribute"
  • array="#local.tagData.attributes#">
  •  
  • <!--- Add the attribute on its own line. --->
  • <cfset local.newCode &= (
  • this.newLine &
  • local.subIndent &
  • local.attribute.name &
  • "=" &
  • local.attribute.value
  • ) />
  •  
  • </cfloop>
  •  
  • <!---
  • When closing the tag, check to see if this tag is
  • self closing.
  • --->
  • <cfif arguments.isSelfClosing>
  •  
  • <!--- Add the self-closing cap on the next line. --->
  • <cfset local.newCode &= (
  • this.newLine &
  • local.subIndent &
  • "/>"
  • ) />
  •  
  • <cfelse>
  •  
  • <!--- Add the cap on the same line. --->
  • <cfset local.newCode &= ">" />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • If there was only ONE attribute in the tag,
  • override the spacing so that it is all on one line.
  • --->
  • <cfif (arrayLen( local.tagData.attributes ) eq 1)>
  •  
  • <!--- Remove line breaks. --->
  • <cfset local.newCode = reReplace(
  • local.newCode,
  • "[ \t]*[\r\n]+[ \t]*",
  • " ",
  • "all"
  • ) />
  •  
  • </cfif>
  •  
  •  
  • <!--- Replace with the new tag code. --->
  • <cfset this.replaceMatch( local.newCode ) />
  •  
  • </cfloop>
  •  
  • <!--- Return this component reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="formatComments"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I format the comments.">
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!--- Get all multiline comments. --->
  • <cfset this.createMatcher(
  • "(?ms)^([ \t]*)<!---(((?!--->).)+)--->"
  • ) />
  •  
  • <!--- Loop over each of the match. --->
  • <cfloop condition="this.findMatch()">
  •  
  • <!--- Get the leading space. --->
  • <cfset local.leadingSpace = this.getMatch( 1 ) />
  •  
  • <!---
  • Normalize the spaces (replacing 4 spaces with a
  • tab). There's simply no hope for code that was
  • using two-space indents.
  • --->
  • <cfset local.leadingSpace = reReplace(
  • local.leadingSpace,
  • " {4}",
  • this.tab,
  • "all"
  • ) />
  •  
  • <!---
  • Now that we have replaced-in tabs, remove any left
  • over spaces from the leading space.
  • --->
  • <cfset local.leadingSpace = reReplace(
  • local.leadingSpace,
  • " +",
  • "",
  • "all"
  • ) />
  •  
  •  
  • <!---
  • Create a constant for the indent of the sub-lines.
  • This will be the leading space plus an additional
  • tab character.
  • --->
  • <cfset local.subIndent = (local.leadingSpace & this.tab) />
  •  
  •  
  • <!--- Get the comment itself. --->
  • <cfset local.comment = trim( this.getMatch( 2 ) ) />
  •  
  • <!--- Remove any line breaks from the comment. --->
  • <cfset local.comment = reReplace(
  • local.comment,
  • "\s+",
  • " ",
  • "all"
  • ) />
  •  
  •  
  • <!---
  • Check to see how long the comment is. If it is
  • longer than 70 characters, we will break it up
  • into several lines. Otherwise, we will leave it
  • as a single line.
  • --->
  • <cfif (len( local.comment ) gt 70)>
  •  
  • <!--- Build up the new comment. --->
  • <cfset local.newComment = (
  • local.leadingSpace &
  • "<!---" &
  • this.newLine
  • ) />
  •  
  • <!---
  • Break the comment into words of length no
  • greater than 70.
  • --->
  • <cfset local.lines = reMatch(
  • ".{0,70}\b(?!\s)",
  • local.comment
  • ) />
  •  
  • <!--- Add the lines to the new comment. --->
  • <cfset local.newComment &= (
  • local.subIndent &
  • arrayToList(
  • local.lines,
  • (this.newLine & local.subIndent)
  • ) &
  • this.newLine
  • ) />
  •  
  • <!--- Close the comment. --->
  • <cfset local.newComment &= (
  • local.leadingSpace &
  • "--->"
  • ) />
  •  
  • <!--- Repalce in the new comment. --->
  • <cfset this.replaceMatch( local.newComment ) />
  •  
  • <cfelse>
  •  
  • <!--- Leave as a single-line comment. --->
  • <cfset this.replaceMatch(
  • local.leadingSpace &
  • "<!--- " &
  • local.comment &
  • " --->"
  • ) />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  • <!--- --------------------------------------------- --->
  •  
  • <!--- Create the pattern for the tag. --->
  • <cfsavecontent variable="local.regex">(?ixm)
  • <cfoutput>
  •  
  • (
  • <(cffunction)
  • ([^>"]|"([^"]|"")*")*
  • >.*
  • (\r\n?|\n)
  •  
  • (^\p{Blank}*$(\r\n?|\n))*
  • )
  •  
  • ^(\p{Blank}*)<(cfargument)
  •  
  • </cfoutput>
  • </cfsavecontent>
  •  
  • <!--- Create a new matcher. --->
  • <cfset this.createMatcher( local.regex ) />
  •  
  • <!--- Loop over each of the match. --->
  • <cfloop condition="this.findMatch()">
  •  
  • <!--- Add the pre-argument comment. --->
  • <cfset this.replaceMatch(
  • this.getMatch( 1 ) &
  • this.getMatch( 8 ) &
  • "<!--- Define arguments. --->" &
  • this.newLine &
  • this.getMatch( 8 ) &
  • "<" &
  • this.getMatch( 9 )
  • ) />
  •  
  • </cfloop>
  •  
  • <!--- --------------------------------------------- --->
  •  
  • <!--- Create the pattern for the local scope. --->
  • <cfsavecontent variable="local.regex">(?ixm)
  •  
  • ^(\p{Blank}*)<(cfset)\s+var\s+local
  •  
  • </cfsavecontent>
  •  
  • <!--- Create a new matcher. --->
  • <cfset this.createMatcher( local.regex ) />
  •  
  • <!--- Loop over each of the match. --->
  • <cfloop condition="this.findMatch()">
  •  
  • <!--- Add the local scope comment. --->
  • <cfset this.replaceMatch(
  • this.getMatch( 1 ) &
  • "<!--- Define the local scope. --->" &
  • this.newLine &
  • this.getMatch( 1 ) &
  • "<cfset var local"
  • ) />
  •  
  • </cfloop>
  •  
  • <!--- Return this component reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="formatSpacing"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I add certain spacing to the code.">
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!--- Create the pattern for the spacing. --->
  • <cfsavecontent variable="local.regex">(?ixm)
  •  
  • (
  • <(cfcomponent)
  • ([^>"]|"([^"]|"")*")*
  • >
  • .*
  • (\r\n?|\n)
  • )
  •  
  • (^(\p{Blank})*$(\r\n?|\n))*
  •  
  • </cfsavecontent>
  •  
  • <!--- Create a new matcher. --->
  • <cfset this.createMatcher( local.regex ) />
  •  
  • <!--- Loop over each of the match. --->
  • <cfloop condition="this.findMatch()">
  •  
  • <!--- Replace the post-tag empty lines. --->
  • <cfset this.replaceMatch(
  • this.getMatch( 1 ) &
  • this.newLine2
  • ) />
  •  
  • </cfloop>
  •  
  • <!--- --------------------------------------------- --->
  •  
  • <!--- Create the pattern for the spacing. --->
  • <cfsavecontent variable="local.regex">(?ixm)
  •  
  • (
  • <(cf(function|argument))
  • ([^>"]|"([^"]|"")*")*
  • >
  • .*
  • (\r\n?|\n)
  • )
  •  
  • (^(\p{Blank})*$(\r\n?|\n))*
  •  
  • </cfsavecontent>
  •  
  • <!--- Create a new matcher. --->
  • <cfset this.createMatcher( local.regex ) />
  •  
  • <!--- Loop over each of the match. --->
  • <cfloop condition="this.findMatch()">
  •  
  • <!--- Replace the post-tag empty lines. --->
  • <cfset this.replaceMatch(
  • this.getMatch( 1 ) &
  • this.newLine
  • ) />
  •  
  • </cfloop>
  •  
  • <!--- --------------------------------------------- --->
  •  
  • <!--- Create the pattern for the spacing. --->
  • <cfsavecontent variable="local.regex">(?ixm)
  •  
  • (
  • </(cffunction)>
  • .*
  • (\r\n?|\n)
  • )
  •  
  • (^(\p{Blank})*$(\r\n?|\n))*
  •  
  • </cfsavecontent>
  •  
  • <!--- Create a new matcher. --->
  • <cfset this.createMatcher( local.regex ) />
  •  
  • <!--- Loop over each of the match. --->
  • <cfloop condition="this.findMatch()">
  •  
  • <!--- Replace the post-tag empty lines. --->
  • <cfset this.replaceMatch(
  • this.getMatch( 1 ) &
  • this.newLine2
  • ) />
  •  
  • </cfloop>
  •  
  • <!--- Return this component reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="getBuffer"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I return the current string buffer being used in conjunction with the current regular expression pattern matcher.">
  •  
  • <!--- Return the buffer. --->
  • <cfreturn variables.buffer />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="getCode"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="I return the code.">
  •  
  • <!--- Return this current code state. --->
  • <cfreturn variables.code />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="getMatch"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I get the current match.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="group"
  • type="string"
  • required="false"
  • default="0"
  • hint="I am the group being returned - default to zero, which is the entire match."
  • />
  •  
  • <!---
  • Return the current match from the active
  • pattern matcher.
  • --->
  • <cfreturn this.getMatcher().group(
  • javaCast( "int", arguments.group )
  • ) />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="getMatcher"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I return the current regular expression pattern matcher.">
  •  
  • <!--- Return the current matcher. --->
  • <cfreturn variables.matcher />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="initBuffer"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I create a new string buffer to be used with the current regular expression pattern matcher.">
  •  
  • <!--- Store a new buffer. --->
  • <cfset variables.buffer = createObject(
  • "java",
  • "java.lang.StringBuffer"
  • ).init()
  • />
  •  
  • <!--- Return this component reference. --->
  • <cfreturn />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="parseTag"
  • access="public"
  • returntype="struct"
  • output="false"
  • hint="I parse the given tag code.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="code"
  • type="string"
  • required="true"
  • hint="I am the flat tag code being parsed."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!--- Create the tag structure. --->
  • <cfset local.tag = {
  • name = "",
  • attributes = []
  • } />
  •  
  • <!---
  • Create a new pattern for matching the tokens of
  • the tag, starting with the tag name and then the
  • attribute pairs.
  • --->
  • <cfsavecontent variable="local.regex">(?x)
  •  
  • ^<[^\s]+
  •  
  • |
  •  
  • \s+
  • [\w:]+
  • \s*=\s*
  • (
  • "[^"]*(""[^"]*)*"
  • |
  • '[^']*(''[^']*)*'
  • |
  • [^\s]+
  • )
  •  
  • </cfsavecontent>
  •  
  • <!--- Extract the tag tokens. --->
  • <cfset local.tokens = reMatch(
  • local.regex,
  • arguments.code
  • ) />
  •  
  • <!--- Set the name of the tag as the first token. --->
  • <cfset local.tag.name = reReplace(
  • lCase( local.tokens[ 1 ] ),
  • "^<",
  • "",
  • "one"
  • ) />
  •  
  • <!---
  • Delete the tag name token so we don't process it as
  • one of the tag attributs.
  • --->
  • <cfset arrayDeleteAt( local.tokens, 1 ) />
  •  
  • <!---
  • Now, let's look over the rest of the tokens and parse
  • the name-value pairs.
  • --->
  • <cfloop
  • index="local.token"
  • array="#local.tokens#">
  •  
  • <!--- Create a new attribute as dicated by the equals. --->
  • <cfset local.attribute = {
  • name = lcase( trim( listFirst( local.token, "=" ) ) ),
  • value = trim( listRest( local.token, "=" ) )
  • } />
  •  
  • <!---
  • Further clean the value to make sure that value is
  • only double quoted.
  • --->
  • <cfset local.attribute.value = (
  • """" &
  • reReplace(
  • reReplace(
  • local.attribute.value,
  • "^[""']|[""']$",
  • "",
  • "all"
  • ),
  • """""|""(?!"")",
  • """""",
  • "all"
  • ) &
  • """"
  • ) />
  •  
  • <!--- Add the parsed attribute to the collection. --->
  • <cfset arrayAppend(
  • local.tag.attributes,
  • local.attribute
  • ) />
  •  
  • </cfloop>
  •  
  • <!--- Return the parsed tag data. --->
  • <cfreturn local.tag />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="removeMatches"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I remove all of the matches.">
  •  
  • <!--- Loop over all the matches. --->
  • <cfloop condition="this.findMatch()">
  •  
  • <!---
  • Replace the current match with the empty string.
  • This will remove it from the code.
  • --->
  • <cfset this.replaceMatch( "" ) />
  •  
  • </cfloop>
  •  
  • <!--- Return this component reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="replaceMatch"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I replace the given string in leu of the current match.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="value"
  • type="string"
  • required="true"
  • hint="I am the value being replaced in."
  • />
  •  
  • <!--- Replace the current string. --->
  • <cfset this.getMatcher().appendReplacement(
  • this.getBuffer(),
  • reReplace(
  • arguments.value,
  • "([\\\$])",
  • "\\\1",
  • "all"
  • )
  • )/>
  •  
  • <!--- Return this component reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="setCode"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I store the given code as part of the object state.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="code"
  • type="string"
  • required="true"
  • hint="I am the code being stored."
  • />
  •  
  • <!--- Store the code. --->
  • <cfset variables.code = arguments.code />
  •  
  • <!--- Return this component reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="setMatcher"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I store the given matcher as the internal matcher.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="matcher"
  • type="any"
  • required="true"
  • hint="I am the matcher being stored."
  • />
  •  
  • <!--- Store the matcher. --->
  • <cfset variables.matcher = arguments.matcher />
  •  
  • <!---
  • Whenever we store a new matcher, we want to re-init
  • the buffer since this matcher will be used with a
  • replacement mechanism.
  • --->
  • <cfset this.initBuffer() />
  •  
  • <!--- Return this component reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="unformat"
  • access="public"
  • returntype="string"
  • output="false"
  • hint="I unformat the given code (removing Ben's code meticulous code formatting).">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="code"
  • type="string"
  • required="true"
  • hint="I am the code being unformatted."
  • />
  •  
  • <!--- Set the code, unformat it, and return it. --->
  • <cfreturn this
  • .setCode( arguments.code )
  • .unformatCFTag( "CFArgument" )
  • .unformatCFTag( "CFCookie" )
  • .unformatCFTag( "CFComponent" )
  • .unformatCFTag( "CFContent" )
  • .unformatCFTag( "CFDirectory" )
  • .unformatCFTag( "CFFunction" )
  • .unformatCFTag( "CFHeader" )
  • .unformatCFTag( "CFHttp" )
  • .unformatCFTag( "CFHttpParam" )
  • .unformatCFTag( "CFIF" )
  • .unformatCFTag( "CFImage" )
  • .unformatCFTag( "CFLocation" )
  • .unformatCFTag( "CFLock" )
  • .unformatCFTag( "CFLoop" )
  • .unformatCFTag( "CFMail" )
  • .unformatCFTag( "CFMailParam" )
  • .unformatCFTag( "CFParam" )
  • .unformatCFTag( "CFReturn" )
  • .unformatCFTag( "CFSet" )
  • .unformatCFTag( "CFSetting" )
  • .unformatCFTag( "CFZip" )
  • .unformatCFTag( "CFZipParam" )
  • .unformatComments()
  • .unformatSpacing()
  • .getCode()
  • />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="unformatCFTag"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I unformat the ColdFusion tag with the given name.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="tagName"
  • type="string"
  • required="true"
  • hint="I am the name of the tag being unformatted."
  • />
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!--- Create the pattern for the tag. --->
  • <cfsavecontent variable="local.regex">(?ix)
  • <cfoutput>
  •  
  • <#arguments.tagName#
  • ([^>"]|"([^"]|"")*")*
  • >
  •  
  • </cfoutput>
  • </cfsavecontent>
  •  
  • <!--- Create a new matcher. --->
  • <cfset this.createMatcher( local.regex ) />
  •  
  • <!--- Loop over each of the match. --->
  • <cfloop condition="this.findMatch()">
  •  
  • <!--- Remove the line breaks. --->
  • <cfset local.replace = reReplace(
  • this.getMatch(),
  • "[\s\t]*[\r\n]+[\s\t]*",
  • " ",
  • "all"
  • ) />
  •  
  • <!--- Remove any spaces before periods. --->
  • <cfset local.replace = reReplace(
  • local.replace,
  • " \.",
  • ".",
  • "all"
  • ) />
  •  
  • <!--- Replace match.. --->
  • <cfset this.replaceMatch( local.replace ) />
  •  
  • </cfloop>
  •  
  • <!--- Return this component reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="unformatComments"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I unformat the comments.">
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!--- Get all multiline comments. --->
  • <cfset this.createMatcher(
  • "(?ms)^([ \t]*)<!---(((?!--->).)+)--->"
  • ) />
  •  
  • <!--- Loop over each of the match. --->
  • <cfloop condition="this.findMatch()">
  •  
  • <!--- Unwrap the comments. --->
  • <cfset this.replaceMatch(
  • this.getMatch( 1 ) &
  • "<!---" &
  • reReplace(
  • this.getMatch( 2 ),
  • "[ \t]*(\r\n?|\n)[ \t]*",
  • " ",
  • "all"
  • ) &
  • "--->"
  • ) />
  •  
  • </cfloop>
  •  
  • <!--- --------------------------------------------- --->
  •  
  • <!--- We want to remove any "Define" comments. --->
  • <cfset this.createMatcher(
  • "(?ms)^[ \t]*(<!---) Define(((?!--->).)+)(--->)"
  • ) />
  •  
  • <!--- Remove matches. --->
  • <cfset this.removeMatches() />
  •  
  • <!--- Return this component reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  •  
  • <cffunction
  • name="unformatSpacing"
  • access="public"
  • returntype="any"
  • output="false"
  • hint="I remove certain spacing from the code.">
  •  
  • <!--- Define the local scope. --->
  • <cfset var local = {} />
  •  
  • <!--- Create the pattern for the spacing. --->
  • <cfsavecontent variable="local.regex">(?ixm)
  •  
  • (
  • <(cfcomponent)
  • ([^>"]|"([^"]|"")*")*
  • >
  • .*
  • (\r\n?|\n)
  • )
  •  
  • (^(\p{Blank})*$(\r\n?|\n))+
  •  
  • </cfsavecontent>
  •  
  • <!--- Create a new matcher. --->
  • <cfset this.createMatcher( local.regex ) />
  •  
  • <!--- Loop over each of the match. --->
  • <cfloop condition="this.findMatch()">
  •  
  • <!--- Replace the post-tag empty lines. --->
  • <cfset this.replaceMatch(
  • this.getMatch( 1 ) &
  • this.newLine
  • ) />
  •  
  • </cfloop>
  •  
  • <!--- --------------------------------------------- --->
  •  
  • <!--- Create the pattern for the spacing. --->
  • <cfsavecontent variable="local.regex">(?ixm)
  •  
  • (
  • <(cf(function|argument))
  • ([^>"]|"([^"]|"")*")*
  • >
  • .*
  • (\r\n?|\n)
  • )
  •  
  • (^(\p{Blank})*$(\r\n?|\n))+
  •  
  • </cfsavecontent>
  •  
  • <!--- Create a new matcher. --->
  • <cfset this.createMatcher( local.regex ) />
  •  
  • <!--- Loop over each of the match. --->
  • <cfloop condition="this.findMatch()">
  •  
  • <!--- Replace the post-tag empty lines. --->
  • <cfset this.replaceMatch(
  • this.getMatch( 1 )
  • ) />
  •  
  • </cfloop>
  •  
  • <!--- --------------------------------------------- --->
  •  
  • <!--- Create the pattern for the spacing. --->
  • <cfsavecontent variable="local.regex">(?ixm)
  •  
  • (
  • </(cffunction)>
  • .*
  • (\r\n?|\n)
  • )
  •  
  • (^(\p{Blank})*$(\r\n?|\n))+
  •  
  • </cfsavecontent>
  •  
  • <!--- Create a new matcher. --->
  • <cfset this.createMatcher( local.regex ) />
  •  
  • <!--- Loop over each of the match. --->
  • <cfloop condition="this.findMatch()">
  •  
  • <!--- Replace the post-tag empty lines. --->
  • <cfset this.replaceMatch(
  • this.getMatch( 1 ) &
  • this.newLine
  • ) />
  •  
  • </cfloop>
  •  
  • <!--- Return this component reference. --->
  • <cfreturn this />
  • </cffunction>
  •  
  • </cfcomponent>

I would show a demo of the code, but I can't get it to re-format code enough to present it nicely in a blog post.



Looking For A New Job?

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

Looking over your cfc I notice you set all function to access publicly. Since only two(Plus Init) of them should ever get called from outside of the cfc, why don't you make the rest private?

Reply to this Comment

@Daniel,

Three reasons for that:

1. Pre-ColdFusion 9, you can't execute variables-scoped methods using named arguments, only ordered arguments. As such, if I were to make my methods private, I could only call them as such:

variables.method( arg, arg )

... and NOT:

variables.method( name = arg, name = arg )

Now, I'm not saying I call all my methods with named arguments, but I like the option, especially if I ever need to have variable length argument lists.

In CF9, however, this bug will be removed.

2. Private methods simply don't add value to the way I program.

3. I am pretty sure, although now I am beginning to doubt myself (and this related back to #1), but I think that the THIS scope of a CFC only contains the public methods, even when called internally. And, I happen to prefer writing THIS.method() rather than using the variables scope. This is a shallow, aesthetic reason, but one that I think about.

Reply to this Comment

@Ben,

I am glad they fixed that in CF9 as that doesn't make sense why you wouldn't be able to if one was private and one wasn't.

In general I can see that they don't add value to the way most people program. Though if CFBuilder continues to get better I hope their insight would be able to detect public/private methods and only show it if it is able to be used in that situation. Also in my opinion it does help make code more readable so you know which functions are suppose to be used outside of the cfc.

I guess from a java standpoint I am already use to using variableName.method()

For me in general I prefer providing access to the function only where it is meant to be more for logic and design reasons. Overall I don't know if there is anything better about either way.

Reply to this Comment

"2. Private methods simply don't add value to the way I program. "

No offense but this has a smell to it.

That aside, thank you so much for this one. It reminds me of awhile ago looking for a good code beautifier? Does anyone know of one out there? A couple years ago I didn't find anything that struck me as easy to use. I really think it's needed for modern programming though.

Just as we allow each developer to set their preferences in their IDE for how big a tab is or what color to show javascript functions, we should allow developers to use their own syntax for their code. Let them double space functions but don't indent them in a component if they want and yet when some other developer pops that cfc open, they'll see it the way they want, functions are indented and, even better, all in cfscript.

Seems like pie in the sky but it's nearly 2010, surely someone's done something to address this issue by now.

Reply to this Comment

@Allen,

I know this isn't a Microsoft blog, sorry Ben, but they have done some really slick things in Visual Studio as far as formatting goes. They offer a lot of options for your particular style of programming and the neat thing is all you have to do is go to the end of the code page, remove the closing } and re-enter it,(C# anyway), and the whole page gets formatted the way you have it configured. The beauty of it is, each programmer can follow those steps and have the code formatted to their liking in seconds. Opening { on the same like or separate, space between arguments, tab spacing, etc.... Being CFBuilder is in the hands of Adobe, a company with $$$ to spend on developing a slick IDE, it would be nice if they could build something like that into the product.

Reply to this Comment

@Daniel,

If ColdFusion was updated to allow both public and private methods to be accessed internally via the "THIS" scope, I would probably start using more intentful access settings.

@Allen,

It may have a smell, but to me, it honestly doesn't add value. What's the worst case scenario:

Someone blindly grabs a CFC and starts using methods without any understanding of what they do... they set something that they shouldn't... something blows up (either immediately, or eventually).

... OK. I guess I should have read up on how this CFC should be used ;)

... and that's the "worst" case scenario. The best case scenario is that I can now easily refer to all of my methods via the THIS scope and use named-parameters without ColdFusion throwing an error.

Reply to this Comment

@Ben: The THIS scope by definition shows only public variables and functions (and in this is different from Java and other languages!) - so if you really want to prefix all your function calls with THIS then indeed you have to declare them as public. But why do you want to do that?

Reply to this Comment

@Wouter,

With CF9 fixing the named-arguments-scope-invocation issue, it might not matter any more. Going forward, I should be able to use named args on either public or private scopes:

this.do( action = "something" );

.... and:

variables.do( action = "something" );

As such, I can now use named-args on private methods.

The question then becomes, do I *want* to type out "varaibles"? Personally, I just like the way "this" looks and feels (and is much short to type).

Reply to this Comment

@Ben,

You should know I'm still working in MX 6.1 (and trying to get a two year-old licence for CF8 activated!) ;-) If I find a bit of time, I'll try some CF9 programming at home.

But I'm not concerned about what I have to type, I'm probably thinking too much like a Java-developer: I care about what parts (methods, variables) I make public when I build components. A variable that is internal to the component shouldn't be visible when you do a CFDUMP. I don't want to give other code (possibly not my own, since I work in a large company) acces to parts of my components that must remain internal.

And since discovering that even CF components can actually implement inheritance I've stopped putting THIS in front of methods - you can get some weird results if you don't pay attention (like declaring a method "private" and having a THIS.methodcall() fail...

Reply to this Comment

@Wouter,

"you can get some weird results if you don't pay attention"

... I suppose this would be true in all aspects of programming :)

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.