grizzled.string.template.template.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of grizzled-scala_2.9.1-1 Show documentation
Show all versions of grizzled-scala_2.9.1-1 Show documentation
A general-purpose Scala utility library
The newest version!
/*
---------------------------------------------------------------------------
This software is released under a BSD license, adapted from
http://opensource.org/licenses/bsd-license.php
Copyright (c) 2009, Brian M. Clapper
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the names "clapper.org", "Grizzled Scala Library", nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
package grizzled.string.template
import scala.util.matching.Regex
import scala.util.matching.Regex.Match
import scala.annotation.tailrec
/** Base class for all `StringTemplate` exceptions.
*/
class StringTemplateException(val message: String) extends Exception(message)
/** Thrown for non-safe templates when a variable is not found.
*/
class VariableNotFoundException(val variableName: String)
extends Exception("Variable \"" + variableName + "\" not found.")
/** Information about a parsed variable name.
*/
class Variable(val start: Int,
val end: Int,
val name: String,
val default: Option[String])
/** A simple, configurable string template that substitutes variable references
* within a string.
*
* @param resolveVar A function that takes a variable name as a parameter and
* returns an `Option[String]` value for the variable,
* or `None` if there is no value
* (`Map[String, String].get()`, for instance).
* @param safe `true` for a "safe" template that just substitutes
* a blank string for an unknown variable, `false`
* for one that throws an exception.
*/
abstract class StringTemplate(val resolveVar: (String) => Option[String],
val safe: Boolean) {
/** Replace all variable references in the given string. Variable references
* are recognized per the regular expression passed to the constructor. If
* a referenced variable is not found in the resolver, this method either:
*
* - throws a `VariableNotFoundException` (if `safe` is `false`), or
* - substitutes an empty string (if `safe` is `true`)
*
* Recursive references are supported (but beware of infinite recursion).
*
* @param s the string in which to replace variable references
*
* @return the result
*
* @throws VariableNotFoundException a referenced variable could not be
* found, and `safe` is
* `false`
*/
def substitute(s: String): String = {
def doSub(s2: String): String = {
def subVariable(variable: Variable): Option[String] = {
var endString = if (variable.end == s2.length) ""
else s2.substring(variable.end)
val transformed = s2.substring(0, variable.start) +
getVar(variable.name, variable.default) +
endString
Some(doSub(transformed))
}
// Note to self:
//
// - findVariableReferences() returns an Option[Variable].
// - flatMap() on an option invokes the supplied function on the
// option's value, if it's not None; otherwise, it returns None.
// - The getOrElse() is invoked on the result.
findVariableReference(s2) flatMap(v => subVariable(v)) getOrElse s2
}
doSub(s)
}
/** Parse the location of the first variable in string.
*
* @param s the string
*
* @return an `Option[Variable]`, specifying the variable's
* location; or `None` if not found
*/
protected def findVariableReference(s: String): Option[Variable]
/** Get a variable's value, returning an empty string or throwing an
* exception, depending on the setting of `safe`.
*
* @param name the variable name
* @param default default value, or None if there isn't one
*
* @return the value of the variable
*
* @throws VariableNotFoundException the variable could not be found
* and `safe` is `false`
*/
private def getVar(name: String, default: Option[String]): String = {
def handleDefault: String = {
if (default != None)
default.get
else if (safe)
""
else
throw new VariableNotFoundException(name)
}
resolveVar(name) getOrElse handleDefault
}
}
/** A string template that uses the Unix shell-like syntax `\${varname}`
* (or `\$varname`) for variable references. A variable's name may consist
* of alphanumerics and underscores. To include a literal "$" in a string,
* escape it with a backslash.
*
* For this class, the general form of a variable reference is:
*
* {{{
* \${varname?default}
* }}}}
*
* The `?default` suffix is optional and specifies a default value
* to be used if the variable has no value.
*
* A shorthand form of a variable reference is:
*
* {{{
* \$varname
* }}}
*
* The ''default'' capability is not available in the shorthand form.
*
* @param resolveVar A function that takes a variable name as a parameter
* and returns an `Option[String]` value for the
* variable, or `None` if there is no value
* (`Map[String, String].get()`, for instance).
* @param namePattern Regular expression pattern to match a variable name, as
* a string (not a Regex). For example: "[a-zA-Z0-9_]+"
* @param safe `true` for a "safe" template that just substitutes
* a blank string for an unknown variable, `false`
* for one that throws an exception.
*/
class UnixShellStringTemplate(resolveVar: (String) => Option[String],
namePattern: String,
safe: Boolean)
extends StringTemplate(resolveVar, safe) {
// ${foo} or ${foo?default}
private var LongFormVariable = ("""\$\{(""" +
namePattern +
""")(\?[^}]*)?\}""").r
// $foo
private var ShortFormVariable = ("""\$(""" + namePattern + ")").r
private val EscapedDollar = """(\\*)(\\\$)""".r
private val RealEscapeToken = "\u0001"
private val NonEscapeToken = "\u0002"
/** Alternate constructor that uses a variable name pattern that permits
* variable names with alphanumerics and underscore.
*
* @param resolveVar A function that takes a variable name as a parameter
* and returns an `Option[String]` value for the
* variable, or `None` if there is no value
* (`Map[String, String].get()`, for instance).
* @param safe `true` for a "safe" template that just
* substitutes a blank string for an unknown variable,
* `false` for one that throws an exception.
*/
def this(resolveVar: (String) => Option[String], safe: Boolean) = {
this(resolveVar, "[a-zA-Z0-9_]+", safe)
}
/** Replace all variable references in the given string. Variable references
* are recognized per the regular expression passed to the constructor. If
* a referenced variable is not found in the resolver, this method either:
*
* - throws a `VariableNotFoundException` (if `safe` is `false`), or
* - substitutes an empty string (if `safe` is `true`)
*
* Recursive references are supported (but beware of infinite recursion).
*
* @param s the string in which to replace variable references
*
* @return the result
*
* @throws VariableNotFoundException a referenced variable could not be
* found, and `safe` is
* `false`
*/
override def substitute(s: String): String = {
// Kludge to handle escaped "$". Temporarily replace it with something
// highly unlikely to be in the string. Then, put a single "$" in its
// place, after the substitution. Must be sure to handle even versus
// odd number of backslash characters.
def preSub(s: String): List[String] = {
def handleMatch(m: Match): List[String] = {
if ((m.group(1).length % 2) == 0) {
// Odd number of backslashes before "$", including
// the one with the dollar token (group 2). Valid escape.
List(s.substring(0, m.start(2)), RealEscapeToken) :::
preSub(s.substring(m.end(2)))
}
else {
// Even number of backslashes before "$", including
// the one with the dollar token (group 2). Not an escape.
List(s.substring(0, m.start(2)), NonEscapeToken) :::
preSub(s.substring(m.end(2)))
}
}
// findFirstMatchIn() returns an Option[Match]. Use map() to
// invoke handleMatch on the result.
EscapedDollar.findFirstMatchIn(s).
map(m => handleMatch(m)).
getOrElse(List(s))
}
val s2 = super.substitute(preSub(s) mkString "")
s2.replaceAll(RealEscapeToken, """\$""").
replaceAll(NonEscapeToken, """\\\$""")
}
/** Parse the location of the first variable in string.
*
* @param s the string
*
* @return an `Option[Variable]`, specifying the variable's
* location; or `None` if not found
*/
protected def findVariableReference(s: String): Option[Variable] = {
def handleMatch(m: Match): Option[Variable] = {
val name = m.group(1)
val default = m.group(2) match {
case null =>
None
case s: String => {
// Pull off the "?". Can't do Some(s drop 1),
// because that yields a StringOps, not a String.
// Casting doesn't work, either. But assigning to a
// temporary string does.
val realDefault: String = s drop 1
Some(realDefault)
}
}
Some(new Variable(m.start, m.end, name, default))
}
def handleNoMatch: Option[Variable] = {
ShortFormVariable.findFirstMatchIn(s).
map(m => new Variable(m.start,
m.end,
m.group(1),
None))
}
LongFormVariable.findFirstMatchIn(s).
flatMap(m => handleMatch(m)).
orElse(handleNoMatch)
}
}
/** A string template that uses the cmd Windows.exe syntax `%varname%` for
* variable references. A variable's name may consist of alphanumerics and
* underscores. To include a literal "%" in a string, use two in a row
* ("%%").
*
* @param resolveVar A function that takes a variable name as a parameter
* and returns an `Option[String]` value for the
* variable, or `None` if there is no value
* (`Map[String, String].get()`, for instance).
* @param namePattern Regular expression pattern to match a variable name, as
* a string (not a Regex). For example: "[a-zA-Z0-9_]+"
* @param safe `true` for a "safe" template that just substitutes
* a blank string for an unknown variable, `false`
* for one that throws an exception.
*/
class WindowsCmdStringTemplate(resolveVar: (String) => Option[String],
namePattern: String,
safe: Boolean)
extends StringTemplate(resolveVar, safe) {
private val Variable = ("""%(""" + namePattern + """)%""").r
private val EscapedPercent = """%%""" // regexp string, for replaceAll
private val Placeholder = "\u0001" // temporarily replaces $$
/** Alternate constructor that uses a variable name pattern that permits
* variable names with alphanumerics and underscore.
*
* @param resolveVar A function that takes a variable name as a parameter
* and returns an `Option[String]` value for the
* variable, or `None` if there is no value
* (`Map[String, String].get()`, for instance).
* @param safe `true` for a "safe" template that just
* substitutes a blank string for an unknown variable,
* `false` for one that throws an exception.
*/
def this(resolveVar: (String) => Option[String], safe: Boolean) = {
this(resolveVar, "[a-zA-Z0-9_]+", safe)
}
/** Replace all variable references in the given string. Variable references
* are recognized per the regular expression passed to the constructor. If
* a referenced variable is not found in the resolver, this method either:
*
*
* - throws a `VariableNotFoundException` (if `safe` is
* `false`), or
* - substitutes an empty string (if `safe` is `true`)
*
*
* Recursive references are supported (but beware of infinite recursion).
*
* @param s the string in which to replace variable references
*
* @return the result
*
* @throws VariableNotFoundException a referenced variable could not be
* found, and `safe` is
* `false`
*/
override def substitute(s: String): String = {
// Kludge to handle escaped "%%". Temporarily replace it with something
// highly unlikely to be in the string. Then, put a single "%" in its
// place, after the substitution.
super.substitute(s.replaceAll(EscapedPercent, Placeholder)).
replaceAll(Placeholder, "%");
}
/** Parse the location of the first variable in string.
*
* @param s the string
*
* @return an `Option[Variable]`, specifying the variable's
* location; or `None` if not found
*/
protected def findVariableReference(s: String): Option[Variable] = {
def handleMatch(m: Match): Option[Variable] = {
val name = m.group(1)
Some(new Variable(m.start, m.end, name, None))
}
Variable.findFirstMatchIn(s).flatMap(m => handleMatch(m))
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy