complete redesign: use nodejs on server instead of php - documentation to be updated
This commit is contained in:
13
ChangeLog
13
ChangeLog
@@ -1,3 +1,16 @@
|
||||
2016-01-08 11:40 marc
|
||||
|
||||
* ChangeLog, cordova/platforms/android/AndroidManifest.xml,
|
||||
cordova/platforms/android/assets/www/cordova_plugins.js,
|
||||
cordova/platforms/android/res/xml/config.xml, html/login.php,
|
||||
html/opendb.php, html/safechat.js, html/send.php,
|
||||
test/makefile.am, test/runtests.sh, test/settings.wt: non working
|
||||
experimental status
|
||||
|
||||
2015-12-18 16:07 marc
|
||||
|
||||
* build-in-docker.conf, html/schema.sql: fix build in docker
|
||||
|
||||
2015-12-04 08:36 marc
|
||||
|
||||
* COPYING, ChangeLog, INSTALL, build-in-docker.conf,
|
||||
|
@@ -113,7 +113,6 @@ function traperror() {
|
||||
fi
|
||||
echo
|
||||
fi
|
||||
echo "**** Entering docker container ${DOCKER_ID}, exit with Ctrl-D"
|
||||
echo -n " ... cleanup docker: "
|
||||
docker rm -f "${DOCKER_ID}"
|
||||
echo "returning status: $e"
|
||||
@@ -163,7 +162,7 @@ for repo in "${repos[@]}"; do
|
||||
ifthenelse "${repo}" "apt-add-repository ARG"
|
||||
done
|
||||
for key in "${keys[@]}"; do
|
||||
wget -O- \
|
||||
wget -O- "$key" \
|
||||
| docker exec -i ${DOCKER_ID} apt-key add -
|
||||
done
|
||||
docker exec ${DOCKER_ID} apt-get update
|
||||
|
@@ -8,7 +8,8 @@
|
||||
|
||||
m4_define(x_package_name, safechat) # project's name
|
||||
m4_define(x_major, 0) # project's major version
|
||||
m4_define(x_minor, 3) # project's minor version
|
||||
m4_define(x_minor, 5) # project's minor version
|
||||
m4_define(x_least_diff, 63)
|
||||
m4_include(ax_init_standard_project.m4)
|
||||
AC_INIT(x_package_name, x_version, x_package_name)
|
||||
AM_INIT_AUTOMAKE([1.9 tar-pax])
|
||||
@@ -20,7 +21,7 @@ AX_INIT_STANDARD_PROJECT
|
||||
AX_USE_SCRIPTS
|
||||
AX_USE_DOXYGEN
|
||||
AX_USE_DEBIAN_PACKAGING
|
||||
AX_BUILD_HTML
|
||||
#AX_BUILD_HTML
|
||||
AX_USE_RPM_PACKAGING
|
||||
#AX_USE_CPPUNIT
|
||||
AX_BUILD_TEST
|
||||
@@ -43,7 +44,8 @@ fi
|
||||
AM_CONDITIONAL(HAVE_CORDOVA, [test ${CORDOVA} != 0 -a ${ANDROID} != 0])
|
||||
AX_SUBST(CORDOVA)
|
||||
|
||||
AC_CONFIG_FILES([html/index.html])
|
||||
AC_CONFIG_FILES([nodejs/package.json])
|
||||
AC_CONFIG_FILES([nodejs/makefile])
|
||||
AC_CONFIG_FILES([cordova/makefile])
|
||||
AC_CONFIG_FILES([cordova/config.xml])
|
||||
|
||||
|
@@ -20,7 +20,7 @@ ${ANDROID_SRC}:
|
||||
chmod -R u+w "$${file}" ); \
|
||||
done
|
||||
if [ "$$(whoami 2> /dev/null)" != "root" ]; then \
|
||||
${CORDOVA} plugin add https://github.com/katzer/cordova-plugin-background-mode.git; \
|
||||
#${CORDOVA} plugin add https://github.com/katzer/cordova-plugin-background-mode.git; \
|
||||
${CORDOVA} build --debug || ${CORDOVA} build --debug; \
|
||||
fi
|
||||
|
||||
|
@@ -748,7 +748,7 @@ WARN_LOGFILE = doxygen.errors
|
||||
# spaces.
|
||||
# Note: If this tag is empty the current directory is searched.
|
||||
|
||||
INPUT = @top_srcdir@/html
|
||||
INPUT = @top_srcdir@/nodejs
|
||||
INPUT += @top_srcdir@/scripts
|
||||
INPUT += @top_srcdir@/test
|
||||
|
||||
|
@@ -1,40 +0,0 @@
|
||||
<div id="message">
|
||||
<form id="chat" autocomplete="off" onsubmit="sendmessage(this.elements['recv'].value, this.elements['msg'].value)">
|
||||
<input placeholder="receiver" autocomplete="off" type="text" id="recv" oninput="checkpartner(this.value)" />
|
||||
<input placeholder="message" autocomplete="off" type="text" id="msg"/>
|
||||
<div class="buttongroup">
|
||||
<span class="toolbutton">
|
||||
<label for="photo"><img src="photo.svg"/></label>
|
||||
<input autocomplete="off" type="file" accept="image/*" id="photo" multiple />
|
||||
</span>
|
||||
<!--
|
||||
<span class="toolbutton">
|
||||
<label for="video"><img src="video.svg"/></label>
|
||||
<input autocomplete="off" type="file" accept="video/*" id="video"/>
|
||||
</span>
|
||||
<span class="toolbutton">
|
||||
<label for="audio"><img src="audio.svg"/></label>
|
||||
<input autocomplete="off" type="file" accept="audio/*" id="audio"/>
|
||||
</span>
|
||||
<span class="toolbutton">
|
||||
<label for="file"><img src="attachment.svg"/></label>
|
||||
<input autocomplete="off" type="file" id="file"/>
|
||||
</span>
|
||||
-->
|
||||
<span class="toolbutton">
|
||||
<label for="send"><img src="send.svg"/></label>
|
||||
<input type="submit" id="send" disabled/>
|
||||
</span>
|
||||
<span class="toolbutton">
|
||||
<label for="reset"><img src="abort.svg" /></label>
|
||||
<input type="reset" id="reset" onclick="clearmessage();" />
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<div id="preview"></div>
|
||||
</div>
|
||||
<div id="msgs"></div>
|
||||
<script>
|
||||
$("#file,#photo,#audio,#video").change(function(evt){fileupload(evt)});
|
||||
if (!window.FileReader) $("#file,#photo,#audio,#video").hide(); // not supported by browser
|
||||
</script>
|
@@ -1,34 +0,0 @@
|
||||
<?php
|
||||
/*! @file
|
||||
|
||||
@id $Id$
|
||||
*/
|
||||
// 1 2 3 4 5 6 7 8
|
||||
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
|
||||
/// Check if a user exists
|
||||
/** Check if a user exists in the server's user table.
|
||||
|
||||
@param $user user name to check
|
||||
@return json encoded value:
|
||||
- 'user name as string', if user does exist
|
||||
- null, if user does not exist or in case of error
|
||||
|
||||
@api Check If User Exists
|
||||
*/
|
||||
function checknewuser($user) {
|
||||
try {
|
||||
require_once("opendb.php");
|
||||
$dbuser = $db->real_escape_string($user);
|
||||
$q = $db->query("select * from user where name='$dbuser';");
|
||||
if ($q->num_rows==0) {
|
||||
echo json_encode($user);
|
||||
} else {
|
||||
echo json_encode(null);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(null);
|
||||
}
|
||||
}
|
||||
checknewuser($_REQUEST['user']);
|
||||
?>
|
@@ -1,86 +0,0 @@
|
||||
<?php
|
||||
|
||||
/// Send Error To Client
|
||||
/** @return error message from server to client
|
||||
|
||||
Function calls exit to terminate.
|
||||
|
||||
Message format is json:
|
||||
@code
|
||||
{
|
||||
success: false,
|
||||
txt: 'error message string';
|
||||
}
|
||||
@endcode */
|
||||
function error($txt) {
|
||||
echo json_encode(array('success' => false, 'txt' => $txt));
|
||||
exit;
|
||||
}
|
||||
|
||||
/// Send Success To Client
|
||||
/** @return success message from server to client
|
||||
|
||||
Function calls exit to terminate.
|
||||
|
||||
Message format is json:
|
||||
@code
|
||||
{
|
||||
success: true,
|
||||
txt: 'success message string';
|
||||
}
|
||||
@endcode */
|
||||
function success($txt) {
|
||||
echo json_encode(array('success' => true, 'txt' => $txt));
|
||||
exit;
|
||||
}
|
||||
|
||||
function getoption($name, $default) {
|
||||
if (!isset($OPTION[$name])) {
|
||||
$q = $db->query("select value from options where name='$name';");
|
||||
if ($q->num_rows==1) {
|
||||
$OPTION[$name]=$q->fetch_row()[0];
|
||||
} else {
|
||||
if (isset($_SERVER[$name]))
|
||||
$OPTION[$name]=$_SERVER[$name];
|
||||
else
|
||||
$OPTION[$name]=$default;
|
||||
$q = $db->query("insert into options (name, value) values ('$name', '$value');");
|
||||
}
|
||||
}
|
||||
return $OPTION[$name];
|
||||
}
|
||||
|
||||
/// Create user safechat as server's identity
|
||||
/** Server has reserved username @c safechat */
|
||||
function createSafechatUser() {
|
||||
$q = $db->query("select pubkey from user where name='safechat';");
|
||||
if ($q->num_rows!=1 && $user=="safechat") {
|
||||
$KEY_LENGTH=getoption("KEYLEN", "4096");
|
||||
$SAFECHAT_NAME=getoption("NAME", "Safe Chat");
|
||||
$SAFECHAT_COMMENT=getoption("COMMENT", "https://safechat.ch");
|
||||
$SAFECHAT_EMAIL=getoption("EMAIL", "server@safechat.ch");
|
||||
$PASSWORD=getoption("PASSWORD", "s3Cr37");
|
||||
$create_key_cmd=<<<EOT
|
||||
gpg -v -v --gen-key --batch <<EOF
|
||||
Key-Type: RSA
|
||||
Key-Length: 4096
|
||||
Subkey-Type: RSA
|
||||
Subkey-Length: 4096
|
||||
Name-Real: ${SAFECHAT_NAME}
|
||||
Name-Comment: ${SAFECHAT_COMMENT}
|
||||
Name-Email: ${SAFECHAT_EMAIL}
|
||||
Expire-Date: 0
|
||||
Passphrase: ${PASSWORD}
|
||||
%echo generating key for ${SAFECHAT_NAME} ...
|
||||
%commit
|
||||
%echo done.
|
||||
EOF
|
||||
EOT;
|
||||
system($create_key_cmd);
|
||||
gnupg_setarmor($pgp, 1);
|
||||
$export = gnupg_export($pgp, ${SAFECHAT_NAME});
|
||||
$q = $db->query("insert into user (name, pubkey) values ('safechat', '$export');");
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
40
html/get.php
40
html/get.php
@@ -1,40 +0,0 @@
|
||||
<?php
|
||||
/*! @file
|
||||
|
||||
@id $Id$
|
||||
*/
|
||||
// 1 2 3 4 5 6 7 8
|
||||
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
|
||||
|
||||
/// Get new messages
|
||||
/** Get all messages that are newer than @c $start.
|
||||
|
||||
@param $start Number of message to start with.
|
||||
@return json encoded array of messages:
|
||||
@code
|
||||
[
|
||||
{
|
||||
id: message-id,
|
||||
time: unix-time-stamp,
|
||||
user: 'sender's user name',
|
||||
msg: 'armored and encrypted message as string'
|
||||
}, ...
|
||||
]
|
||||
@endcode
|
||||
|
||||
@api Get New Messages
|
||||
*/
|
||||
function get($start) {
|
||||
try {
|
||||
require_once("opendb.php");
|
||||
$start = $db->real_escape_string($start);
|
||||
$q = $db->query("select id, UNIX_TIMESTAMP(time) as time, user, msg from message where id>$start;");
|
||||
if ($q) echo json_encode($q->fetch_all(MYSQLI_ASSOC));
|
||||
else echo json_encode(null);
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(null);
|
||||
}
|
||||
}
|
||||
get($_REQUEST['start']);
|
||||
?>
|
@@ -1,56 +0,0 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width initial-scale=1" />
|
||||
<link href="safechat.css" rel="stylesheet" type="text/css" />
|
||||
<script type="text/javascript" src="jquery.js"></script>
|
||||
<script type="text/javascript" src="openpgp.js"></script>
|
||||
<script type="text/javascript" src="safechat.js"></script>
|
||||
<link href="jquery.cssemoticons.css" media="screen" rel="stylesheet" type="text/css" />
|
||||
<script src="jquery.cssemoticons.js" type="text/javascript"></script>
|
||||
<title>Safe Chat</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="header" class="header">
|
||||
<h1>Safe Chat @PACKAGE_VERSION@</h1>
|
||||
<div id="togglemenu">
|
||||
<span id="username">[unknown]</span>
|
||||
<img id="menuicon" onclick="togglemenu()" src="menu.svg" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul id="menu" style="display: none">
|
||||
<li onclick="backup()">Download Backup</li>
|
||||
<li class="toolbutton"><label for="restore">Restore Backup</label><input autocomplete="off" type="file" accept="*.bak" id="restore" /></li>
|
||||
<li id="groups" onclick="groups()">Edit Groups</li>
|
||||
<li id="removeKey" style="display: none" onclick="removeKey()">Password Forgotten</li>
|
||||
<li id="android-download" href="safechat.apk"><a href="safechat.apk">Download Android-App</a></li>
|
||||
<li href="https://dev.marc.waeckerlin.org/redmine/projects/safechat/embedded/index.html" target="_blank"><a href="https://dev.marc.waeckerlin.org/redmine/projects/safechat/embedded/index.html" target="_blank">About Safe Chat</a></li>
|
||||
</ul>
|
||||
<script type="text/javascript">
|
||||
$(function() { // on load: without cordova, remove andoid-download
|
||||
if ("@CORDOVA@" == "0") $("#android-download").hide();
|
||||
if (!window.FileReader) $("#restore").hide(); // not supported by browser
|
||||
$("#groups").hide();
|
||||
})
|
||||
$("#restore").change(function(evt){restore(evt)});
|
||||
</script>
|
||||
|
||||
<div id="main">
|
||||
|
||||
<p>start up engine, please wait ...</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="status">
|
||||
|
||||
<noscript>This is a secure and encryptet chat application, that runs
|
||||
in your browser and does not send any credentials to the
|
||||
server. Your password and your secret key is fully under your
|
||||
control. That's why you must enable javascript and local storage for
|
||||
this application.</noscript>
|
||||
|
||||
</div>
|
@@ -1,49 +0,0 @@
|
||||
<?php
|
||||
/*! @file
|
||||
|
||||
@id $Id$
|
||||
*/
|
||||
// 1 2 3 4 5 6 7 8
|
||||
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
|
||||
|
||||
/// Verify a user
|
||||
/** Check if a user is consistent to the data in the server's database
|
||||
or create a user, if he does not yet exist in the usertable
|
||||
(and the user name is available).
|
||||
|
||||
@param $user user's name
|
||||
@param $pubkey user's public key
|
||||
|
||||
@return json encoded status with text:
|
||||
- success() in case of success (user exists or has been created)
|
||||
- error() in case of mismatch
|
||||
|
||||
@api Verify a User
|
||||
*/
|
||||
function login($user, $pubkey) {
|
||||
try {
|
||||
require_once("opendb.php");
|
||||
if (!$db) error("database access failed");
|
||||
if ($user=="safechat") error("username safechat is reserved for server");
|
||||
//$verify = gnupg_import($pgp, $pubkey);
|
||||
//if (!$verify) error("wrong identity");
|
||||
$user = $db->real_escape_string($user);
|
||||
$pubkey = $db->real_escape_string($pubkey);
|
||||
$q = $db->query("select * from user where name='$user' and pubkey='$pubkey';");
|
||||
if (!$q) error("database query failed");
|
||||
if ($q->num_rows==1) {
|
||||
success("user $user found on server");
|
||||
} elseif ($q->num_rows==0) {
|
||||
$q = $db->query("insert into user (name, pubkey) values ('$user', '$pubkey');");
|
||||
if (!$q) error("creation of user failed");
|
||||
success("user $user created on server");
|
||||
} else {
|
||||
error("server database defect");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error("login failed");
|
||||
}
|
||||
}
|
||||
login($_REQUEST['user'], $_REQUEST['pubkey']);
|
||||
?>
|
@@ -1,21 +0,0 @@
|
||||
## @id $Id$
|
||||
|
||||
## 1 2 3 4 5 6 7 8
|
||||
## 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
|
||||
wwwdir = ${pkgdatadir}/html
|
||||
dist_www_DATA = index.html chat.html newuser.html safechat.js \
|
||||
jquery.js openpgp.js jquery.cssemoticons.js \
|
||||
safechat.css jquery.cssemoticons.css checknewuser.php \
|
||||
get.php login.php pubkey.php send.php abort.svg \
|
||||
A-Tone-His_Self-1266414414.mp3 attachment.svg \
|
||||
audio.svg chat-rodrigo-angleton.svg \
|
||||
Checkout-Scanner-Beep-SoundBible.com-593325210-by-Mike-Koenig.mp3 \
|
||||
envelope.svg functions.php menu.svg pfeil.svg \
|
||||
photo.png photo.svg safechat-rodrigo-angleton.svg \
|
||||
safe-mimooh.svg send.svg update-messages.js video.png \
|
||||
video.svg opendb.php schema.sql
|
||||
|
||||
EXTRA_DIST = documentation.dox
|
||||
|
||||
MAINTAINERCLEANFILES = makefile.in
|
@@ -1,15 +0,0 @@
|
||||
<h2>Register User</h2>
|
||||
<p>All you need to start is a username and a password:</p>
|
||||
<form id="register" onsubmit="createkeypair(this.elements['user'].value, this.elements['pwd'].value)">
|
||||
<input placeholder="username" type="text" id="user" oninput="checkuser(this.value)"/>
|
||||
<input placeholder="password" type="password" id="pwd" oninput="checkpwd(this.value, document.getElementById('pwd2').value)"/>
|
||||
<input placeholder="repeat password" type="password" id="pwd2" oninput="checkpwd(document.getElementById('pwd').value, this.value)"/>
|
||||
<input id="createuser" type="submit" disabled/>
|
||||
</form>
|
||||
<p>Please chose any username, e.g. a pseudonym, your e-mail, your phone number, your real name, and chose a safe password.</p>
|
||||
<h2>What is Safe Chat?</h2>
|
||||
<p>Safe Chat is a chat program to protect your privacy. It is designed to be extremely easy to use, with all cool features, but with highest security through strong encryption. For more information, open «About Safe Chat» in the menu (<img style="height: 1em; border: 2px solid green; vertical-align: text-bottom; background-color: green" src="menu.svg" />) above.</p>
|
||||
<h2>Never forget your Password!</h2>
|
||||
<p>This messenger is absolutely secure. But on the other hand, that means, no one except you knows your password. No one can read your messages exept you, not even our administrator. It is technically impossible to restore a password. You would have to delete your account and create a new one. In that case, all messages are lost.</p>
|
||||
<h3>Keys and Password</h3>
|
||||
<p>Safe Chat internally uses OpenPGP for public/private-key encryption. Your password is not transfered to the server and not stored, it is used only to create and encrypt your private key. There are two keys, a secret private key, that is stored in the browser (or app) on your computer, encrypted with your password and not sent to the server. There is also public key, which is sent to the server and used by other users to encrypt messages that only you can read. You can backup and restore your keys and setings to and from a file. This is the only way to transfer your account to another browser or to computer. Do not delete your browser's local storage unless you have backed up your keys.</p>
|
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
/*! @file
|
||||
|
||||
@id $Id$
|
||||
|
||||
@see @ref database for the database schema
|
||||
|
||||
*/
|
||||
// 1 2 3 4 5 6 7 8
|
||||
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
|
||||
mysqli_report(MYSQLI_REPORT_STRICT);
|
||||
require_once("functions.php");
|
||||
/*
|
||||
try {
|
||||
if (!isset($pgp)) {
|
||||
$pgp = gnupg_init();
|
||||
if (!$pgp) error("pgp on server failed");
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error('cannot start pgp on server');
|
||||
}
|
||||
*/
|
||||
try {
|
||||
$db = new mysqli("mysql", "root", $_SERVER["MYSQL_ENV_MYSQL_ROOT_PASSWORD"]);
|
||||
if (!$db) error("database connection failed on server");
|
||||
$db->query("create database if not exists safechat;");
|
||||
if (!$db) error("cannot create database");
|
||||
$db->select_db("safechat");
|
||||
if (!$db) error("cannot select database");
|
||||
$query = file_get_contents("schema.sql");
|
||||
if (!$query) error("cannot load database schema");
|
||||
foreach (split(";\n", $query) as $create) {
|
||||
if ($create) {
|
||||
$db->query($create);
|
||||
if (!$db) error("cannot create database tables");
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
error('database error on server: '+$e->getMessage());
|
||||
}
|
||||
?>
|
BIN
html/photo.png
BIN
html/photo.png
Binary file not shown.
Before Width: | Height: | Size: 18 KiB |
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
/*! @file
|
||||
|
||||
@id $Id$
|
||||
*/
|
||||
// 1 2 3 4 5 6 7 8
|
||||
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
|
||||
/// Get a user's public key
|
||||
/** Get the public key of a user.
|
||||
|
||||
@param $user Name of the user to ge public key from.
|
||||
|
||||
@return json encoded value:
|
||||
- @c null in case of error (user does not exist)
|
||||
- @code
|
||||
{
|
||||
pubkey: 'armored public key string'
|
||||
}
|
||||
@endcode
|
||||
|
||||
@api Get A User's Public Key
|
||||
*/
|
||||
function pubkey($user) {
|
||||
try {
|
||||
require_once("opendb.php");
|
||||
$user = $db->real_escape_string($user);
|
||||
$q = $db->query("select pubkey from user where name='$user';");
|
||||
/* if ($q->num_rows!=1 && $user=="safechat") { */
|
||||
/* require_once("optionstable.php"); */
|
||||
/* createSafechatUser(); */
|
||||
/* $q = $db->query("select pubkey from user where name='$user';"); */
|
||||
/* } */
|
||||
if ($q->num_rows==1) {
|
||||
echo json_encode($q->fetch_row()[0]);
|
||||
} else {
|
||||
echo json_encode(null);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo json_encode(null);
|
||||
}
|
||||
}
|
||||
pubkey($_REQUEST['user']);
|
||||
?>
|
722
html/safechat.js
722
html/safechat.js
@@ -1,722 +0,0 @@
|
||||
/*! @file
|
||||
|
||||
@id $Id$
|
||||
|
||||
This is the main application as it is fully run in the user's browser.
|
||||
|
||||
@dot
|
||||
digraph X {
|
||||
|
||||
start [URL="\ref start()"];
|
||||
newuser [URL="\ref newuser()"];
|
||||
login [URL="\ref login()"];
|
||||
createkeypair [URL="\ref createkeypair()"];
|
||||
chat [URL="\ref chat()"];
|
||||
getpwd [URL="\ref getpwd()"];
|
||||
setpw [URL="\ref setpw()"];
|
||||
get [URL="\ref get()"];
|
||||
sendmessage [URL="\ref sendmessage()"];
|
||||
|
||||
start -> newuser [label="if no keys exist"];
|
||||
start -> login [label="if keys exist"];
|
||||
newuser -> createkeypair [label="on submit"];
|
||||
createkeypair -> "openpgp.generateKeyPair";
|
||||
"openpgp.generateKeyPair" -> login [label="keys generated in local store"];
|
||||
login -> chat [label="user is valid on server"];
|
||||
chat -> getpwd [label="password not yet entered"];
|
||||
getpwd -> setpw [label="on input"];
|
||||
setpw -> chat [label="password is valid"];
|
||||
chat -> chat [label="remain in chat"];
|
||||
chat -> get [label="start timer"];
|
||||
get -> get [label="restart timer"];
|
||||
chat -> sendmessage [label="on submit"];
|
||||
sendmessage -> chat [label="remain in chat"];
|
||||
}
|
||||
@enddot
|
||||
*/
|
||||
// 1 2 3 4 5 6 7 8
|
||||
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
|
||||
var password = null; ///< password, only stored temporary, until reload
|
||||
var username = null; ///< username, only used during registration
|
||||
var filecontent = new Array(); ///< temporary storage for attachments
|
||||
|
||||
/// Show error messsage
|
||||
/** Fades in an error message and logs to console.
|
||||
@param data (optional) The error can be a string or any structure.
|
||||
Strings are shown to the user, structures are logged only.
|
||||
@param stay (optional) If not given as @c true, reloads page after 5s. */
|
||||
function error(data, stay) {
|
||||
$("#status").fadeOut("slow", function() {
|
||||
$("#status").addClass("error")
|
||||
$("#status").removeClass("notice")
|
||||
$("#status").removeClass("success")
|
||||
if (data) {
|
||||
if (typeof data == 'string') {
|
||||
$("#status").html(data);
|
||||
console.log("error: "+data);
|
||||
} else {
|
||||
$("#status").html('unknown error: '+JSON.stringify(data));
|
||||
console.log("error: "+JSON.stringify(data));
|
||||
}
|
||||
} else {
|
||||
$("#status").html('error');
|
||||
console.log("error");
|
||||
}
|
||||
$("#status").fadeIn("slow");
|
||||
if (!stay) setTimeout(start, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
/// Show notice messsage
|
||||
/** Fades in an notice message and logs to console.
|
||||
@param text (optional) The data is a string. */
|
||||
function notice(text) {
|
||||
$("#status").fadeOut("slow", function() {
|
||||
$("#status").addClass("notice")
|
||||
$("#status").removeClass("error")
|
||||
$("#status").removeClass("success")
|
||||
if (text) {
|
||||
$("#status").html(text);
|
||||
console.log("notice: "+text);
|
||||
} else {
|
||||
$("#status").html('');
|
||||
console.log("notice");
|
||||
}
|
||||
$("#status").fadeIn("slow");
|
||||
});
|
||||
}
|
||||
|
||||
/// Show notice messsage
|
||||
/** Fades in an success message and logs to console.
|
||||
@param text (optional) The data is a string. */
|
||||
function success(text) {
|
||||
$("#status").fadeOut("slow", function() {
|
||||
$("#status").addClass("success")
|
||||
$("#status").removeClass("error")
|
||||
$("#status").removeClass("notice")
|
||||
if (text) {
|
||||
$("#status").html(text);
|
||||
console.log("success: "+text);
|
||||
} else {
|
||||
$("#status").html('');
|
||||
console.log("success");
|
||||
}
|
||||
$("#status").fadeIn("slow");
|
||||
});
|
||||
}
|
||||
|
||||
/// Show status message in the main screen area
|
||||
/** @param text Text is a message or some complex HTML from the server.
|
||||
@param msg The success message text */
|
||||
function status(text, msg) {
|
||||
$("#main").fadeOut("slow", function() {
|
||||
$("#main").html(text);
|
||||
if (msg) success(msg);
|
||||
else setTimeout("$('#status').fadeOut('slow')", 5000);
|
||||
$("#main").fadeIn("slow", function() {
|
||||
$("form input:first-child").focus();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
var getLoopTimeout = null; ///< store get timeout to make sure only one is running
|
||||
/// Set timeout for next get request
|
||||
/** @param time timeout time in ms, defaults to 10000 */
|
||||
function getLoop(time) {
|
||||
if (!time) time = 10000;
|
||||
getLoopStop();
|
||||
getLoopTimeout = setTimeout(get, time);
|
||||
}
|
||||
/// Stop get loop if it is running
|
||||
function getLoopStop() {
|
||||
if (getLoopTimeout) clearTimeout(getLoopTimeout);
|
||||
getLoopTimeout = null;
|
||||
}
|
||||
|
||||
/// Alert user
|
||||
/** Alert user, e.g. that a new message has arrived. */
|
||||
function alert() {
|
||||
navigator.vibrate =
|
||||
navigator.vibrate || navigator.webkitVibrate || navigator.mozVibrate || navigator.msVibrate;
|
||||
if (navigator.vibrate) {
|
||||
// vibration API supported
|
||||
navigator.vibrate(1000);
|
||||
}
|
||||
(new Audio("A-Tone-His_Self-1266414414.mp3")).play();
|
||||
}
|
||||
|
||||
/// Toggle Menu Display
|
||||
function togglemenu() {
|
||||
$("#menu").toggle();
|
||||
}
|
||||
|
||||
/// Download Profile Backup
|
||||
function backup() {
|
||||
getLoopStop();
|
||||
status("<p>Starting backup download ...</p>", "");
|
||||
var download = document.createElement('a');
|
||||
download.href = 'data:attachment/text,'+encodeURI(JSON.stringify(localStorage));
|
||||
download.target = '_blank';
|
||||
function pad(n) {return n<10 ? '0'+n : n}
|
||||
var now = new Date();
|
||||
download.download =
|
||||
pad(now.getFullYear())+pad(now.getMonth()+1)+pad(now.getDate())+
|
||||
"-"+userid()+"@"+window.location.hostname+".bak";
|
||||
var clickEvent = new MouseEvent("click", {
|
||||
"view": window,
|
||||
"bubbles": true,
|
||||
"cancelable": false
|
||||
});
|
||||
download.dispatchEvent(clickEvent);
|
||||
togglemenu();
|
||||
setTimeout(start, 2000);
|
||||
}
|
||||
|
||||
/// Upload Profile Backup
|
||||
function restore(evt) {
|
||||
getLoopStop();
|
||||
status("<p>Starting backup restore ...</p>", "");
|
||||
if (!window.FileReader)
|
||||
return error("your browser dows not support file upload", true);
|
||||
for (var i=0, f; f=evt.target.files[i]; ++i) {
|
||||
var file = f;
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(evt) {
|
||||
if (evt.target.error) return error("error reading file", true);
|
||||
if (evt.target.readyState==0) return notice("waiting for data ...");
|
||||
if (evt.target.readyState==1) return notice("loading data ...");
|
||||
var parsed=JSON.parse(evt.target.result);
|
||||
togglemenu();
|
||||
localStorage.pubKey = parsed.pubKey;
|
||||
localStorage.privKey = parsed.privKey;
|
||||
setTimeout(start, 2000);
|
||||
}
|
||||
reader.readAsText(file);
|
||||
}
|
||||
}
|
||||
|
||||
/// Configure local groups
|
||||
/** ... */
|
||||
function groups() {
|
||||
}
|
||||
|
||||
/// Check if user name is available
|
||||
/** Calls checknewuser.php on server and displays an error, if the
|
||||
user name is already in use. This function is used when creating a
|
||||
new user. It immediately gives the user a feedback, whether the
|
||||
chosen user name is available or not.
|
||||
|
||||
Called when user edits the user name fields.
|
||||
|
||||
Sets @ref username and checks @ref password - if both are well
|
||||
defined, enables the submit button.
|
||||
|
||||
@param user User name to check. */
|
||||
function checkuser(user) {
|
||||
$("#register").submit(function(event) {
|
||||
return false;
|
||||
});
|
||||
$.post("checknewuser.php", {user: user})
|
||||
.done(function(res) {
|
||||
username=JSON.parse(res);
|
||||
if (!username||username.length<1) username=null;
|
||||
$("#createuser").prop("disabled", !(username && password));
|
||||
if (username) {
|
||||
if (password) success("user is ready to be created");
|
||||
else notice("user name is available, please set password");
|
||||
} else notice("user name is not available");
|
||||
}).fail(function(res) {
|
||||
username=null;
|
||||
$("#createuser").prop("disabled", !(username && password));
|
||||
error("offline");
|
||||
});
|
||||
}
|
||||
|
||||
/// Check if password is set and matches the repeated password
|
||||
/** Checks if both passwords are identical and valid and gives
|
||||
feedback to the user.
|
||||
|
||||
Called when user edits the password fields.
|
||||
|
||||
Sets @ref username and checks @ref password - if both are well
|
||||
defined, enables the submit button.
|
||||
|
||||
@param pwd The password.
|
||||
@param pwd2 The repeated password. */
|
||||
function checkpwd(pwd, pwd2) {
|
||||
$("#register").submit(function(event) {
|
||||
return false;
|
||||
});
|
||||
if (pwd==pwd2) password=pwd;
|
||||
else password=null;
|
||||
if (!password||password.length<1) password=null;
|
||||
$("#createuser").prop("disabled", !(username && password));
|
||||
if (password) {
|
||||
if (username) success("user is ready to be created");
|
||||
else notice("password matches, please chose a valid user name");
|
||||
} else notice("passwords don't match");
|
||||
}
|
||||
|
||||
/// Checks if the receiver of a message exists on server.
|
||||
/** Calls checknewuser.php on server and enables the message submit
|
||||
button if the receiver of the message exists on the server. */
|
||||
function checkpartner(user) {
|
||||
$("#chat").submit(function(event) {
|
||||
return false;
|
||||
});
|
||||
$.post("checknewuser.php", {user: user})
|
||||
.done(function(res) {
|
||||
if (JSON.parse(res)) {
|
||||
notice("receiver does not exist");
|
||||
$("#send").prop("disabled", true);
|
||||
return;
|
||||
}
|
||||
$("#send").prop("disabled", false);
|
||||
success("receiver exists");
|
||||
}).fail(function(res) {
|
||||
error("offline", true);
|
||||
$("#send").prop("disabled", true);
|
||||
});
|
||||
}
|
||||
|
||||
/// Create Local Public-/Private-Key Pair
|
||||
/** Called if user has not yet his keys, just generates a new key pair. */
|
||||
function createkeypair(user, pwd) {
|
||||
status("generate keys");
|
||||
openpgp.generateKeyPair({
|
||||
numBits: 4096,
|
||||
userId: user,
|
||||
passphrase: pwd
|
||||
}).then(function(keyPair) {
|
||||
success("keys generated");
|
||||
localStorage["pubKey"] = keyPair.publicKeyArmored;
|
||||
localStorage["privKey"] = keyPair.privateKeyArmored;
|
||||
login();
|
||||
}).catch(function(e) {
|
||||
console.log(e.stack);
|
||||
error("generating key pairs failed");
|
||||
});
|
||||
}
|
||||
|
||||
/// Get Own Public Key
|
||||
/** @return public key object */
|
||||
function publicKey() {
|
||||
if (typeof localStorage["pubKey"] == 'undefined') return null;
|
||||
return openpgp.key.readArmored(localStorage["pubKey"]);
|
||||
}
|
||||
|
||||
/// Get Own Private Key
|
||||
/** @return private key object */
|
||||
function privateKey() {
|
||||
if (typeof localStorage["privKey"] == 'undefined') return null;
|
||||
return openpgp.key.readArmored(localStorage["privKey"]);
|
||||
}
|
||||
|
||||
/// Get Own User Name
|
||||
/** Get user name as user id of first public key */
|
||||
function userid() {
|
||||
if (!publicKey() ||
|
||||
publicKey().keys.length < 1 ||
|
||||
publicKey().keys[0].getUserIds().length < 1) return null
|
||||
return publicKey().keys[0].getUserIds()[0];
|
||||
}
|
||||
|
||||
/// Clear Message Text And Attachments
|
||||
/** Does not remove the receiver's name */
|
||||
function clearmessage() {
|
||||
filecontent = new Array();
|
||||
$('#preview').empty();
|
||||
$("#msg").val("");
|
||||
notice("message cleared");
|
||||
}
|
||||
|
||||
/// Display Image Attachments
|
||||
function attachments(files, id) {
|
||||
if (files) files.forEach(function(file) {
|
||||
if (file.content.length<100000) {
|
||||
var img = document.createElement('img');
|
||||
img.src = file.content;
|
||||
$(id).append(img);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Upload Attachment
|
||||
/** Prepares attachment to be sent in a message. If the attachment is
|
||||
an image, it resizes the image to 400px on the lager side.
|
||||
|
||||
By now, only images are supported.
|
||||
|
||||
Stores data in global variable @ref filecontent. */
|
||||
function fileupload(evt) {
|
||||
if (!window.FileReader)
|
||||
return error("your browser dows not support file upload", true);
|
||||
for (var i=0, f; f=evt.target.files[i]; ++i) {
|
||||
var file = f;
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(evt) {
|
||||
if (evt.target.error) return error("error reading file", true);
|
||||
if (evt.target.readyState==0) return notice("waiting for data ...");
|
||||
if (evt.target.readyState==1) return notice("loading data ...");
|
||||
if (!file.type.match('^image/'))
|
||||
return error(file.name+": not an image", true);
|
||||
var img = document.createElement("img");
|
||||
img.onload = function() {
|
||||
var MAX = 400;
|
||||
var width = img.width;
|
||||
var height = img.height;
|
||||
if (width > MAX) {
|
||||
height *= MAX / width;
|
||||
width = MAX;
|
||||
}
|
||||
if (height > MAX) {
|
||||
width *= MAX / height;
|
||||
height = MAX;
|
||||
}
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
var ctx = canvas.getContext("2d");
|
||||
ctx.drawImage(img, 0, 0, width, height);
|
||||
img.onload = function() {
|
||||
filecontent.push({type: file.type, content: img.src});
|
||||
$("#preview").append(img);
|
||||
success('image of type '+file.type+' is ready to be sent');
|
||||
}
|
||||
img.src = canvas.toDataURL(file.type);
|
||||
}
|
||||
img.src=evt.target.result;
|
||||
}
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets Receiver's Name
|
||||
/** Called when clicked on a receiver's name. Sets focus to the
|
||||
message text field.
|
||||
|
||||
@param name The receiver's name. */
|
||||
function setreceiver(name) {
|
||||
$("#recv").val(name);
|
||||
checkpartner(name);
|
||||
$("#msg").focus();
|
||||
}
|
||||
|
||||
var userMap = null;
|
||||
/// Get a user's public key.
|
||||
/** The first time, gets it from the server, later from the cache. */
|
||||
function getPublicKey(user) {
|
||||
var deferredObject = $.Deferred();
|
||||
if (userMap == null) {
|
||||
if (localStorage.userMap) {
|
||||
userMap = JSON.parse(localStorage.userMap);
|
||||
console.log("got userMap from localStorage");
|
||||
} else {
|
||||
userMap = new Array();
|
||||
}
|
||||
}
|
||||
if (userMap[user]) {
|
||||
console.log("user "+user+" is in cache");
|
||||
deferredObject.resolve(userMap[user]);
|
||||
} else {
|
||||
$.post("pubkey.php", {user: user}) // get sender's key
|
||||
.done(function(pk) {
|
||||
console.log("got user "+user+" from server");
|
||||
userMap[user] = pk;
|
||||
localStorage.userMap = JSON.stringify(userMap);
|
||||
deferredObject.resolve(pk);
|
||||
}).fail(function(e) {
|
||||
error("offline");
|
||||
deferredObject.reject(e);
|
||||
});
|
||||
}
|
||||
return deferredObject.promise();
|
||||
}
|
||||
|
||||
var startmsg = 0; ///< number of last downloaded message
|
||||
/// Poll For New Messages, Get And Show Them
|
||||
/** The global variable @ref startmsg stores the id of the last
|
||||
downloaded message. This function is called by timer in regulary
|
||||
periods. It calls get on server and passes @ref startmsg. The
|
||||
server returns all newer messages. They are then decrypted. If
|
||||
decryption is successful, then the message is shown, including
|
||||
attachments. If decryption fails, the message is sent to someone
|
||||
else, so failure is simply ignored. Beeps a sound once, if new
|
||||
messages have been displayed. */
|
||||
function get() {
|
||||
var beeped = false; // beep only once
|
||||
$.post("get.php", {start: startmsg})
|
||||
.done(function(res) { // new messages from server received
|
||||
var msgs = JSON.parse(res);
|
||||
if (msgs) {
|
||||
msgs.forEach(function(e) { // one single message
|
||||
if (startmsg<Number(e.id)) startmsg = Number(e.id);
|
||||
getPublicKey(e.user) // get sender's key
|
||||
.done(function(pk) {
|
||||
var res=JSON.parse(pk);
|
||||
var key=openpgp.key.readArmored(res);
|
||||
if (!res||key.err) {
|
||||
getLoop();
|
||||
return error("key of receiver not found", true);
|
||||
}
|
||||
var message = openpgp.message.readArmored(e.msg);
|
||||
var privkey = privateKey().keys[0];
|
||||
if (privkey.decrypt(password)) // prepare own key
|
||||
openpgp.decryptAndVerifyMessage(privkey, key.keys, message)
|
||||
.then(function(msg) { // decryption succeded
|
||||
// prepend message to list of messages
|
||||
var message = JSON.parse(msg.text);
|
||||
$("#msgs") // todo: check msg.signatures[0].valid
|
||||
.prepend('<div id="id'+(e.id)+'" class="msg '+
|
||||
(e.user==userid()?"me":"other")+
|
||||
'"><div class="header">'+
|
||||
'<span class="date">'+
|
||||
(new Date(1000*Number(e.time))).toLocaleString()+
|
||||
'</span><span class="sender">'+
|
||||
'<a href="javascript:void(0)" '+
|
||||
'onclick="setreceiver(this.innerHTML)">'+
|
||||
e.user+
|
||||
'</a>'+(message.receiver?" → "+message.receiver:"")+
|
||||
'</span></div>'+
|
||||
'<div class="text">'+
|
||||
message.text+
|
||||
'</div></div><div class="clear"/>');
|
||||
// show attachments
|
||||
attachments(message.files, '#id'+e.id+' .text');
|
||||
// calculate and show emoticons
|
||||
$('#id'+e.id).emoticonize();
|
||||
// beep for the first new message
|
||||
if (!beeped) alert()
|
||||
beeped = true;
|
||||
success();
|
||||
})
|
||||
.catch(function(e) {
|
||||
// not for me
|
||||
success();
|
||||
});
|
||||
}).fail(function(e) {
|
||||
error("offline");
|
||||
});
|
||||
});
|
||||
}
|
||||
}).fail(function(e) {
|
||||
error("offline")
|
||||
});
|
||||
getLoop(); // repeat every 10 seconds
|
||||
}
|
||||
|
||||
/// Send Message To Server
|
||||
/** User wants to send a message. Encrypt message with own private and
|
||||
the receiver's public key, then send it to the server. */
|
||||
function sendmessage(recv, txt) {
|
||||
notice("1/3 preparing message ...");
|
||||
$("#message").fadeOut("slow");
|
||||
getPublicKey(recv) // get receiver's public key
|
||||
.done(function(pk) {
|
||||
var res=JSON.parse(pk);
|
||||
var key=openpgp.key.readArmored(res);
|
||||
if (!res||key.err) {
|
||||
$("#message").fadeIn("slow");
|
||||
error("receiver's key not found", true);
|
||||
return;
|
||||
}
|
||||
var privkey = privateKey().keys[0];
|
||||
privkey.decrypt(password); // get own private key ready
|
||||
var message = JSON.stringify({receiver: recv, text: txt, files: filecontent});
|
||||
notice("2/3 encrypting message ...");
|
||||
openpgp.signAndEncryptMessage(key.keys.concat(publicKey().keys), privkey, message)
|
||||
.then(function(msg) { // message is encrypted
|
||||
notice("3/3 sending message ...");
|
||||
$.post("send.php", {user: userid(), msg: msg})
|
||||
.done(function(res) { // message has been sent to server
|
||||
var st = JSON.parse(res);
|
||||
if (st.success) {
|
||||
$("#message").fadeIn("slow");
|
||||
clearmessage();
|
||||
success(st.txt);
|
||||
} else {
|
||||
$("#message").fadeIn("slow");
|
||||
error(st.txt, true);
|
||||
}
|
||||
})
|
||||
.fail(function() {
|
||||
error("offline", true);
|
||||
});
|
||||
})
|
||||
.catch(function(e) {
|
||||
$("#message").fadeIn("slow");
|
||||
error("encryption of message failed", true);
|
||||
});
|
||||
})
|
||||
.fail(function(e) {
|
||||
$("#message").fadeIn("slow");
|
||||
error("offline", true);
|
||||
});
|
||||
$("#message").fadeIn("slow");
|
||||
}
|
||||
|
||||
/// Check And Set Password
|
||||
/** Check if given password matches to decrypt the private key. If so,
|
||||
store it in global temporary variable @ref password and start the
|
||||
chat. The password matches, when the private key can be decrypted.
|
||||
|
||||
@param pwd The password to check. */
|
||||
function setpw(pwd) {
|
||||
if (privateKey().keys[0].decrypt(pwd)) {
|
||||
success("password matches");
|
||||
$("#removeKey").hide();
|
||||
password = pwd;
|
||||
chat();
|
||||
} else {
|
||||
notice("password does not match");
|
||||
}
|
||||
}
|
||||
|
||||
/// Create Password Entry Field
|
||||
/** Asks user for password. When user starts to enter it, it is
|
||||
permanentely checked in setpw(). As soon as the password matches,
|
||||
setpw() continues automatically. No submit is required by the
|
||||
user. */
|
||||
function getpwd() {
|
||||
$("#removeKey").show();
|
||||
status('<form>'+
|
||||
' <input placeholder="please enter password for user '+userid()+
|
||||
'" id="pwd" oninput="setpw(this.value)" type="password" />'+
|
||||
'</form>');
|
||||
}
|
||||
|
||||
function deleteUser() {
|
||||
var uid = userid();
|
||||
localStorage.pubKey = null;
|
||||
localStorage.privKey = null;
|
||||
error("user "+uid+" permanentely lost");
|
||||
status("Deleted User: "+uid);
|
||||
}
|
||||
|
||||
function removeKey() {
|
||||
togglemenu();
|
||||
$("#removeKey").hide();
|
||||
status('<h2>Password Forgotten</h2>'+
|
||||
'<div class="warning"><strong>Warning!</strong>'+
|
||||
'<ul><li>You loose all messages.</li>'+
|
||||
'<li>You loose your account name <em>«'+userid()+'»</em>.</li>'+
|
||||
'<li>You should backup now, before you continue!</li></ul></div>'+
|
||||
'<p>You can only remove your local data. '+
|
||||
'You will have to create a new account with a new name on the server. '+
|
||||
'This means, you loose all your messages and you loose your account '+
|
||||
'name <em>«'+userid()+'»</em> forever. '+
|
||||
'This chat program is secure, nobody can restore your password. '+
|
||||
'Without password, you can\'t prove, that you are <em>«'+userid()+'»</em>.</p>'+
|
||||
'<div class="buttongroup"><p class="toolbutton bad" onclick="deleteUser()">'+
|
||||
'Yes, I really forgot my password.<br/>I want to loose my data to get a new account.</p>'+
|
||||
'<p class="toolbutton good" onclick="start()">'+
|
||||
'No, bring me back!.<br/>I\'ll try to remember my password.</p></div>', "");
|
||||
}
|
||||
|
||||
/// Main Chat Window
|
||||
/** Gets chat widgets from server and displays them. Starts timer for
|
||||
get() which polls for new messages. */
|
||||
function chat() {
|
||||
$("#username").html(userid()+"@"+window.location.hostname);
|
||||
if (!password) return getpwd();
|
||||
$.ajax({url: "chat.html", success: function(res) {
|
||||
status(res);
|
||||
getLoop(2000);
|
||||
}}).fail(function() {
|
||||
error("offline")
|
||||
});
|
||||
}
|
||||
|
||||
/// Login User
|
||||
/** This is not really a login, it is just some kind of validation.
|
||||
The server does not care if a user is online or not, it is only
|
||||
interesting to the client to make sure, everything is fine. User
|
||||
is logged in the following way: User name and public key are sent
|
||||
to the server. If the user name exists on the server and the
|
||||
public key is the same, the user is considered logged in, his
|
||||
credentials seem to be valid. If user does not yet exits on
|
||||
server, it is created now. If user exists, but public key is
|
||||
different, then this is a complete failure, something went
|
||||
terribly wrong. */
|
||||
function login() {
|
||||
status("login ...");
|
||||
$.post("login.php", {user: userid(),
|
||||
pubkey: localStorage.pubKey},
|
||||
function(res) {
|
||||
var st = JSON.parse(res);
|
||||
if (st.success) {
|
||||
status("logged in ...", st.txt);
|
||||
chat();
|
||||
} else {
|
||||
error(st.txt);
|
||||
}
|
||||
})
|
||||
.fail(function(e) {
|
||||
error("offline");
|
||||
});
|
||||
}
|
||||
|
||||
/// Get And Display Form To Create New User
|
||||
/** Shows user creation form. On submit, a private key is generated in
|
||||
createkeypair(), then login() creates the user. */
|
||||
function newuser() {
|
||||
status("new user ...");
|
||||
$.ajax({url: "newuser.html", success: function(res) {
|
||||
status(res);
|
||||
}}).fail(function() {
|
||||
error("offline");
|
||||
});
|
||||
}
|
||||
|
||||
/// Check if local storage is available
|
||||
function checkLocalStorage() {
|
||||
var test = 'test';
|
||||
try {
|
||||
localStorage.setItem(test, test);
|
||||
localStorage.removeItem(test);
|
||||
return true;
|
||||
} catch(e) {
|
||||
status("<p>No access to local storage. Please allow "+window.location.hostname
|
||||
+" to access localhost, i.e. do not block cookies.<p>");
|
||||
error("local storage not available");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Initial Function: Startup
|
||||
/** Decide whether to login or to create a new user */
|
||||
function start() {
|
||||
$("#menu").hide();
|
||||
if (checkLocalStorage())
|
||||
try {
|
||||
status("Starting up ...");
|
||||
if (!userid()) {
|
||||
newuser();
|
||||
} else {
|
||||
login();
|
||||
}
|
||||
} catch (m) {
|
||||
console.log(m.stack);
|
||||
error(m);
|
||||
}
|
||||
}
|
||||
|
||||
/// On Load, Call @ref start
|
||||
$(window.onbeforeunload = function() {
|
||||
return "Are you sure you want to navigate away?";
|
||||
});
|
||||
$(window.onunload = function() { // you probably don't want to leave now...
|
||||
alert('You are trying to leave.');
|
||||
return false;
|
||||
});
|
||||
/// Allow Running in Background on Android
|
||||
document.addEventListener('deviceready', function () {
|
||||
if (cordova && cordova.plugins.backgroundMode) {
|
||||
cordova.plugins.backgroundMode.enable();
|
||||
}
|
||||
}, false);
|
||||
|
||||
/// Start Main Loop
|
||||
$(start);
|
||||
|
@@ -1,48 +0,0 @@
|
||||
<?php
|
||||
/*! @file
|
||||
|
||||
@id $Id$
|
||||
*/
|
||||
// 1 2 3 4 5 6 7 8
|
||||
// 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
|
||||
/// Send a message to the server
|
||||
/** Server checks if user exists and has
|
||||
a valid public key. More test could be added later.
|
||||
|
||||
@param $user The name of the user that send the message.
|
||||
|
||||
@param $msg The armored signed and encrypted message. There is a
|
||||
limit of 100000 bytes for the message.
|
||||
|
||||
@return
|
||||
- success() if the message has been stored successfully
|
||||
- error() in case of any error
|
||||
|
||||
@api Send Message to Server
|
||||
*/
|
||||
function send($user, $msg) {
|
||||
try {
|
||||
require_once("opendb.php");
|
||||
$user = $db->real_escape_string($user);
|
||||
$msg = $db->real_escape_string($msg);
|
||||
if (strlen($_REQUEST['msg'])>100000) error("message is too long");
|
||||
$q = $db->query("select pubkey from user where name='$user';");
|
||||
if (!$q || $q->num_rows!=1) error("user not found on server");
|
||||
/*
|
||||
$pubkey = gnupg_import($pgp, $q->fetch_row()[0]);
|
||||
if (!$pubkey) error("wrong identity");
|
||||
*/
|
||||
$q = $db->query("insert into message (user, msg) values ('$user', '$msg');");
|
||||
if (!$q) {
|
||||
error_log("Error storing message: ".$db->error);
|
||||
error("storing message failed");
|
||||
}
|
||||
success("message stored");
|
||||
} catch (Exception $e) {
|
||||
error_log("Error storing message: ".$e->message);
|
||||
error("storing message failed");
|
||||
}
|
||||
}
|
||||
send($_REQUEST['user'], $_REQUEST['msg']);
|
||||
?>
|
@@ -1,40 +0,0 @@
|
||||
importScripts('jquery.js');
|
||||
importScripts('openpgp.js');
|
||||
|
||||
function attachments(files) {
|
||||
var res = '';
|
||||
if (files) files.forEach(function(file) {
|
||||
if (file.type.match("^image/"))
|
||||
res += '<img src="'+'data:'+file.type+';base64,' + btoa(file.content)+'"/>';
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
addEventListener('message', function(data) {
|
||||
var e = data.e;
|
||||
var key = data.key;
|
||||
var message = openpgp.message.readArmored(e["msg"]);
|
||||
var privkey = privateKey().keys[0];
|
||||
if (privkey.decrypt(password))
|
||||
openpgp.decryptAndVerifyMessage(privkey, key.keys, message)
|
||||
.then(function(msg) {
|
||||
var message = JSON.parse(msg.text);
|
||||
// todo: check msg.signatures[0].valid
|
||||
postMessage('<div id="id'+(e["id"])+'" class="msg '+
|
||||
(e["user"]==userid()?"me":"other")+
|
||||
'"><div class="header">'+
|
||||
'<span class="date">'+
|
||||
(new Date(1000*Number(e["time"]))).toLocaleString()+
|
||||
'</span><span class="sender">'+
|
||||
'<a href="javascript:void(0)" onclick="setreceiver(this.innerHTML)">'+
|
||||
e["user"]+
|
||||
'</a></span></div>'+
|
||||
attachments(message.files)+
|
||||
'<div class="text">'+
|
||||
message.text+
|
||||
'</div></div><div class="clear"/>');
|
||||
})
|
||||
.catch(function(e) {
|
||||
// not for me
|
||||
});
|
||||
}, false);
|
BIN
html/video.png
BIN
html/video.png
Binary file not shown.
Before Width: | Height: | Size: 19 KiB |
@@ -6,7 +6,7 @@
|
||||
## 1 2 3 4 5 6 7 8
|
||||
## 45678901234567890123456789012345678901234567890123456789012345678901234567890
|
||||
|
||||
SUBDIRS = scripts doc html cordova test
|
||||
SUBDIRS = scripts doc nodejs cordova test
|
||||
|
||||
EXTRA_DIST = build-in-docker.conf
|
||||
|
||||
|
7
nodejs/database/config.json
Normal file
7
nodejs/database/config.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"host" : "localhost",
|
||||
"user" : "root",
|
||||
"port" : 8654,
|
||||
"password" : "ert456",
|
||||
"database" : "safechat"
|
||||
}
|
11
nodejs/database/index.js
Normal file
11
nodejs/database/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
module.exports = function() {
|
||||
var mysql = require('mysql');
|
||||
var fs = require('fs');
|
||||
var config = require(__dirname+'/config.json');
|
||||
config.multipleStatements = true;
|
||||
var pool = mysql.createPool(config);
|
||||
|
||||
pool.query(fs.readFileSync(__dirname+'/schema.sql').toString());
|
||||
|
||||
return pool;
|
||||
};
|
@@ -3,7 +3,6 @@ CREATE TABLE IF NOT EXISTS `user` (
|
||||
`pubkey` text NOT NULL COMMENT 'armored gnupg public key of the user',
|
||||
PRIMARY KEY (`name`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='list of all registered users and their public keys';
|
||||
|
||||
create table if not exists `message` (
|
||||
`id`
|
||||
int not null auto_increment
|
||||
@@ -24,8 +23,6 @@ create table if not exists `message` (
|
||||
on update cascade
|
||||
) character set utf8 engine=innodb
|
||||
comment="table to hold all messages for later download by the receiver";
|
||||
|
||||
/* table to sore arbitrary options */
|
||||
create table if not exists options (
|
||||
name varchar(50) not null unique comment "option name",
|
||||
value text not null comment "option value",
|
1
nodejs/node_modules/.bin/express
generated
vendored
Symbolic link
1
nodejs/node_modules/.bin/express
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../express/bin/express
|
1
nodejs/node_modules/.bin/stylus
generated
vendored
Symbolic link
1
nodejs/node_modules/.bin/stylus
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../stylus/bin/stylus
|
45
nodejs/node_modules/ejs/Jakefile
generated
vendored
Normal file
45
nodejs/node_modules/ejs/Jakefile
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
var fs = require('fs')
|
||||
, buildOpts = {
|
||||
printStdout: true
|
||||
, printStderr: true
|
||||
};
|
||||
|
||||
task('build', ['browserify', 'minify'], function () {
|
||||
console.log('Build completed.');
|
||||
});
|
||||
|
||||
desc('Cleans browerified/minified files and package files');
|
||||
task('clean', ['clobber'], function () {
|
||||
jake.rmRf('./ejs.js');
|
||||
jake.rmRf('./ejs.min.js');
|
||||
});
|
||||
|
||||
task('browserify', {async: true}, function () {
|
||||
jake.exec('./node_modules/browserify/bin/cmd.js lib/ejs.js > ejs.js',
|
||||
buildOpts, function () {
|
||||
console.log('Browserification completed.');
|
||||
setTimeout(complete, 0);
|
||||
});
|
||||
});
|
||||
|
||||
task('minify', {async: true}, function () {
|
||||
jake.exec('./node_modules/uglify-js/bin/uglifyjs ejs.js > ejs.min.js',
|
||||
buildOpts, function () {
|
||||
console.log('Minification completed.');
|
||||
setTimeout(complete, 0);
|
||||
});
|
||||
});
|
||||
|
||||
publishTask('ejs', ['build'], function () {
|
||||
this.packageFiles.include([
|
||||
'Jakefile'
|
||||
, 'README.md'
|
||||
, 'package.json'
|
||||
, 'ejs.js'
|
||||
, 'ejs.min.js'
|
||||
, 'lib/**'
|
||||
, 'test/**'
|
||||
]);
|
||||
});
|
||||
|
||||
|
178
nodejs/node_modules/ejs/README.md
generated
vendored
Normal file
178
nodejs/node_modules/ejs/README.md
generated
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
# EJS
|
||||
|
||||
Embedded JavaScript templates
|
||||
|
||||
[](https://travis-ci.org/mde/ejs)
|
||||
[](https://david-dm.org/mde/ejs#info=devDependencies)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ npm install ejs
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
* Control flow with `<% %>`
|
||||
* Escaped output with `<%= %>`
|
||||
* Unescaped raw output with `<%- %>`
|
||||
* Trim-mode ('newline slurping') with `-%>` ending tag
|
||||
* Custom delimiters (e.g., use '<? ?>' instead of '<% %>')
|
||||
* Includes
|
||||
* Client-side support
|
||||
* Static caching of intermediate JavaScript
|
||||
* Static caching of templates
|
||||
* Complies with the [Express](http://expressjs.com) view system
|
||||
|
||||
## Example
|
||||
|
||||
```html
|
||||
<% if (user) { %>
|
||||
<h2><%= user.name %></h2>
|
||||
<% } %>
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```javascript
|
||||
var template = ejs.compile(str, options);
|
||||
template(data);
|
||||
// => Rendered HTML string
|
||||
|
||||
ejs.render(str, data, options);
|
||||
// => Rendered HTML string
|
||||
```
|
||||
|
||||
You can also use the shortcut `ejs.render(dataAndOptions);` where you pass
|
||||
everything in a single object. In that case, you'll end up with local variables
|
||||
for all the passed options.
|
||||
|
||||
## Options
|
||||
|
||||
- `cache` Compiled functions are cached, requires `filename`
|
||||
- `filename` Used by `cache` to key caches, and for includes
|
||||
- `context` Function execution context
|
||||
- `compileDebug` When `false` no debug instrumentation is compiled
|
||||
- `client` Returns standalone compiled function
|
||||
- `delimiter` Character to use with angle brackets for open/close
|
||||
- `debug` Output generated function body
|
||||
- `_with` Whether or not to use `with() {}` constructs. If `false` then the locals will be stored in the `locals` object.
|
||||
- `rmWhitespace` Remove all safe-to-remove whitespace, including leading
|
||||
and trailing whitespace. It also enables a safer version of `-%>` line
|
||||
slurping for all scriptlet tags (it does not strip new lines of tags in
|
||||
the middle of a line).
|
||||
|
||||
## Tags
|
||||
|
||||
- `<%` 'Scriptlet' tag, for control-flow, no output
|
||||
- `<%=` Outputs the value into the template (HTML escaped)
|
||||
- `<%-` Outputs the unescaped value into the template
|
||||
- `<%#` Comment tag, no execution, no output
|
||||
- `<%%` Outputs a literal '<%'
|
||||
- `%>` Plain ending tag
|
||||
- `-%>` Trim-mode ('newline slurp') tag, trims following newline
|
||||
|
||||
## Includes
|
||||
|
||||
Includes either have to be an absolute path, or, if not, are assumed as
|
||||
relative to the template with the `include` call. (This requires the
|
||||
`filename` option.) For example if you are including `./views/user/show.ejs`
|
||||
from `./views/users.ejs` you would use `<%- include('user/show') %>`.
|
||||
|
||||
You'll likely want to use the raw output tag (`<%-`) with your include to avoid
|
||||
double-escaping the HTML output.
|
||||
|
||||
```html
|
||||
<ul>
|
||||
<% users.forEach(function(user){ %>
|
||||
<%- include('user/show', {user: user}) %>
|
||||
<% }); %>
|
||||
</ul>
|
||||
```
|
||||
|
||||
Includes are inserted at runtime, so you can use variables for the path in the
|
||||
`include` call (for example `<%- include(somePath) %>`). Variables in your
|
||||
top-level data object are available to all your includes, but local variables
|
||||
need to be passed down.
|
||||
|
||||
NOTE: Include preprocessor directives (`<% include user/show %>`) are
|
||||
still supported.
|
||||
|
||||
## Custom delimiters
|
||||
|
||||
Custom delimiters can be applied on a per-template basis, or globally:
|
||||
|
||||
```javascript
|
||||
var ejs = require('ejs'),
|
||||
users = ['geddy', 'neil', 'alex'];
|
||||
|
||||
// Just one template
|
||||
ejs.render('<?= users.join(" | "); ?>', {users: users}, {delimiter: '?'});
|
||||
// => 'geddy | neil | alex'
|
||||
|
||||
// Or globally
|
||||
ejs.delimiter = '$';
|
||||
ejs.render('<$= users.join(" | "); $>', {users: users});
|
||||
// => 'geddy | neil | alex'
|
||||
```
|
||||
|
||||
## Caching
|
||||
|
||||
EJS ships with a basic in-process cache for caching the intermediate JavaScript
|
||||
functions used to render templates. It's easy to plug in LRU caching using
|
||||
Node's `lru-cache` library:
|
||||
|
||||
```javascript
|
||||
var ejs = require('ejs')
|
||||
, LRU = require('lru-cache');
|
||||
ejs.cache = LRU(100); // LRU cache with 100-item limit
|
||||
```
|
||||
|
||||
If you want to clear the EJS cache, call `ejs.clearCache`. If you're using the
|
||||
LRU cache and need a different limit, simple reset `ejs.cache` to a new instance
|
||||
of the LRU.
|
||||
|
||||
## Layouts
|
||||
|
||||
EJS does not specifically support blocks, but layouts can be implemented by
|
||||
including headers and footers, like so:
|
||||
|
||||
|
||||
```html
|
||||
<%- include('header') -%>
|
||||
<h1>
|
||||
Title
|
||||
</h1>
|
||||
<p>
|
||||
My page
|
||||
</p>
|
||||
<%- include('footer') -%>
|
||||
```
|
||||
|
||||
## Client-side support
|
||||
|
||||
Go to the [Latest Release](https://github.com/mde/ejs/releases/latest), download
|
||||
`./ejs.js` or `./ejs.min.js`.
|
||||
|
||||
Include one of these on your page, and `ejs.render(str)`.
|
||||
|
||||
## Related projects
|
||||
|
||||
There are a number of implementations of EJS:
|
||||
|
||||
* TJ's implementation, the v1 of this library: https://github.com/tj/ejs
|
||||
* Jupiter Consulting's EJS: http://www.embeddedjs.com/
|
||||
* EJS Embedded JavaScript Framework on Google Code: https://code.google.com/p/embeddedjavascript/
|
||||
* Sam Stephenson's Ruby implementation: https://rubygems.org/gems/ejs
|
||||
* Erubis, an ERB implementation which also runs JavaScript: http://www.kuwata-lab.com/erubis/users-guide.04.html#lang-javascript
|
||||
|
||||
## License
|
||||
|
||||
Licensed under the Apache License, Version 2.0
|
||||
(<http://www.apache.org/licenses/LICENSE-2.0>)
|
||||
|
||||
- - -
|
||||
EJS Embedded JavaScript templates copyright 2112
|
||||
mde@fleegix.org.
|
||||
|
||||
|
1201
nodejs/node_modules/ejs/ejs.js
generated
vendored
Normal file
1201
nodejs/node_modules/ejs/ejs.js
generated
vendored
Normal file
@@ -0,0 +1,1201 @@
|
||||
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||
/*
|
||||
* EJS Embedded JavaScript templates
|
||||
* Copyright 2112 Matthew Eernisse (mde@fleegix.org)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @file Embedded JavaScript templating engine.
|
||||
* @author Matthew Eernisse <mde@fleegix.org>
|
||||
* @author Tiancheng "Timothy" Gu <timothygu99@gmail.com>
|
||||
* @project EJS
|
||||
* @license {@link http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0}
|
||||
*/
|
||||
|
||||
/**
|
||||
* EJS internal functions.
|
||||
*
|
||||
* Technically this "module" lies in the same file as {@link module:ejs}, for
|
||||
* the sake of organization all the private functions re grouped into this
|
||||
* module.
|
||||
*
|
||||
* @module ejs-internal
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Embedded JavaScript templating engine.
|
||||
*
|
||||
* @module ejs
|
||||
* @public
|
||||
*/
|
||||
|
||||
var fs = require('fs')
|
||||
, utils = require('./utils')
|
||||
, scopeOptionWarned = false
|
||||
, _VERSION_STRING = require('../package.json').version
|
||||
, _DEFAULT_DELIMITER = '%'
|
||||
, _DEFAULT_LOCALS_NAME = 'locals'
|
||||
, _REGEX_STRING = '(<%%|<%=|<%-|<%_|<%#|<%|%>|-%>|_%>)'
|
||||
, _OPTS = [ 'cache', 'filename', 'delimiter', 'scope', 'context'
|
||||
, 'debug', 'compileDebug', 'client', '_with', 'rmWhitespace'
|
||||
]
|
||||
, _TRAILING_SEMCOL = /;\s*$/
|
||||
, _BOM = /^\uFEFF/;
|
||||
|
||||
/**
|
||||
* EJS template function cache. This can be a LRU object from lru-cache NPM
|
||||
* module. By default, it is {@link module:utils.cache}, a simple in-process
|
||||
* cache that grows continuously.
|
||||
*
|
||||
* @type {Cache}
|
||||
*/
|
||||
|
||||
exports.cache = utils.cache;
|
||||
|
||||
/**
|
||||
* Name of the object containing the locals.
|
||||
*
|
||||
* This variable is overriden by {@link Options}`.localsName` if it is not
|
||||
* `undefined`.
|
||||
*
|
||||
* @type {String}
|
||||
* @public
|
||||
*/
|
||||
|
||||
exports.localsName = _DEFAULT_LOCALS_NAME;
|
||||
|
||||
/**
|
||||
* Get the path to the included file from the parent file path and the
|
||||
* specified path.
|
||||
*
|
||||
* @param {String} name specified path
|
||||
* @param {String} filename parent file path
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
exports.resolveInclude = function(name, filename) {
|
||||
var path = require('path')
|
||||
, dirname = path.dirname
|
||||
, extname = path.extname
|
||||
, resolve = path.resolve
|
||||
, includePath = resolve(dirname(filename), name)
|
||||
, ext = extname(name);
|
||||
if (!ext) {
|
||||
includePath += '.ejs';
|
||||
}
|
||||
return includePath;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the template from a string or a file, either compiled on-the-fly or
|
||||
* read from cache (if enabled), and cache the template if needed.
|
||||
*
|
||||
* If `template` is not set, the file specified in `options.filename` will be
|
||||
* read.
|
||||
*
|
||||
* If `options.cache` is true, this function reads the file from
|
||||
* `options.filename` so it must be set prior to calling this function.
|
||||
*
|
||||
* @memberof module:ejs-internal
|
||||
* @param {Options} options compilation options
|
||||
* @param {String} [template] template source
|
||||
* @return {(TemplateFunction|ClientFunction)}
|
||||
* Depending on the value of `options.client`, either type might be returned.
|
||||
* @static
|
||||
*/
|
||||
|
||||
function handleCache(options, template) {
|
||||
var fn
|
||||
, path = options.filename
|
||||
, hasTemplate = arguments.length > 1;
|
||||
|
||||
if (options.cache) {
|
||||
if (!path) {
|
||||
throw new Error('cache option requires a filename');
|
||||
}
|
||||
fn = exports.cache.get(path);
|
||||
if (fn) {
|
||||
return fn;
|
||||
}
|
||||
if (!hasTemplate) {
|
||||
template = fs.readFileSync(path).toString().replace(_BOM, '');
|
||||
}
|
||||
}
|
||||
else if (!hasTemplate) {
|
||||
// istanbul ignore if: should not happen at all
|
||||
if (!path) {
|
||||
throw new Error('Internal EJS error: no file name or template '
|
||||
+ 'provided');
|
||||
}
|
||||
template = fs.readFileSync(path).toString().replace(_BOM, '');
|
||||
}
|
||||
fn = exports.compile(template, options);
|
||||
if (options.cache) {
|
||||
exports.cache.set(path, fn);
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template function.
|
||||
*
|
||||
* If `options.cache` is `true`, then the template is cached.
|
||||
*
|
||||
* @memberof module:ejs-internal
|
||||
* @param {String} path path for the specified file
|
||||
* @param {Options} options compilation options
|
||||
* @return {(TemplateFunction|ClientFunction)}
|
||||
* Depending on the value of `options.client`, either type might be returned
|
||||
* @static
|
||||
*/
|
||||
|
||||
function includeFile(path, options) {
|
||||
var opts = utils.shallowCopy({}, options);
|
||||
if (!opts.filename) {
|
||||
throw new Error('`include` requires the \'filename\' option.');
|
||||
}
|
||||
opts.filename = exports.resolveInclude(path, opts.filename);
|
||||
return handleCache(opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JavaScript source of an included file.
|
||||
*
|
||||
* @memberof module:ejs-internal
|
||||
* @param {String} path path for the specified file
|
||||
* @param {Options} options compilation options
|
||||
* @return {String}
|
||||
* @static
|
||||
*/
|
||||
|
||||
function includeSource(path, options) {
|
||||
var opts = utils.shallowCopy({}, options)
|
||||
, includePath
|
||||
, template;
|
||||
if (!opts.filename) {
|
||||
throw new Error('`include` requires the \'filename\' option.');
|
||||
}
|
||||
includePath = exports.resolveInclude(path, opts.filename);
|
||||
template = fs.readFileSync(includePath).toString().replace(_BOM, '');
|
||||
|
||||
opts.filename = includePath;
|
||||
var templ = new Template(template, opts);
|
||||
templ.generateSource();
|
||||
return templ.source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-throw the given `err` in context to the `str` of ejs, `filename`, and
|
||||
* `lineno`.
|
||||
*
|
||||
* @implements RethrowCallback
|
||||
* @memberof module:ejs-internal
|
||||
* @param {Error} err Error object
|
||||
* @param {String} str EJS source
|
||||
* @param {String} filename file name of the EJS file
|
||||
* @param {String} lineno line number of the error
|
||||
* @static
|
||||
*/
|
||||
|
||||
function rethrow(err, str, filename, lineno){
|
||||
var lines = str.split('\n')
|
||||
, start = Math.max(lineno - 3, 0)
|
||||
, end = Math.min(lines.length, lineno + 3);
|
||||
|
||||
// Error context
|
||||
var context = lines.slice(start, end).map(function (line, i){
|
||||
var curr = i + start + 1;
|
||||
return (curr == lineno ? ' >> ' : ' ')
|
||||
+ curr
|
||||
+ '| '
|
||||
+ line;
|
||||
}).join('\n');
|
||||
|
||||
// Alter exception message
|
||||
err.path = filename;
|
||||
err.message = (filename || 'ejs') + ':'
|
||||
+ lineno + '\n'
|
||||
+ context + '\n\n'
|
||||
+ err.message;
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy properties in data object that are recognized as options to an
|
||||
* options object.
|
||||
*
|
||||
* This is used for compatibility with earlier versions of EJS and Express.js.
|
||||
*
|
||||
* @memberof module:ejs-internal
|
||||
* @param {Object} data data object
|
||||
* @param {Options} opts options object
|
||||
* @static
|
||||
*/
|
||||
|
||||
function cpOptsInData(data, opts) {
|
||||
_OPTS.forEach(function (p) {
|
||||
if (typeof data[p] != 'undefined') {
|
||||
opts[p] = data[p];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the given `str` of ejs into a template function.
|
||||
*
|
||||
* @param {String} template EJS template
|
||||
*
|
||||
* @param {Options} opts compilation options
|
||||
*
|
||||
* @return {(TemplateFunction|ClientFunction)}
|
||||
* Depending on the value of `opts.client`, either type might be returned.
|
||||
* @public
|
||||
*/
|
||||
|
||||
exports.compile = function compile(template, opts) {
|
||||
var templ;
|
||||
|
||||
// v1 compat
|
||||
// 'scope' is 'context'
|
||||
// FIXME: Remove this in a future version
|
||||
if (opts && opts.scope) {
|
||||
if (!scopeOptionWarned){
|
||||
console.warn('`scope` option is deprecated and will be removed in EJS 3');
|
||||
scopeOptionWarned = true;
|
||||
}
|
||||
if (!opts.context) {
|
||||
opts.context = opts.scope;
|
||||
}
|
||||
delete opts.scope;
|
||||
}
|
||||
templ = new Template(template, opts);
|
||||
return templ.compile();
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the given `template` of ejs.
|
||||
*
|
||||
* If you would like to include options but not data, you need to explicitly
|
||||
* call this function with `data` being an empty object or `null`.
|
||||
*
|
||||
* @param {String} template EJS template
|
||||
* @param {Object} [data={}] template data
|
||||
* @param {Options} [opts={}] compilation and rendering options
|
||||
* @return {String}
|
||||
* @public
|
||||
*/
|
||||
|
||||
exports.render = function (template, data, opts) {
|
||||
data = data || {};
|
||||
opts = opts || {};
|
||||
var fn;
|
||||
|
||||
// No options object -- if there are optiony names
|
||||
// in the data, copy them to options
|
||||
if (arguments.length == 2) {
|
||||
cpOptsInData(data, opts);
|
||||
}
|
||||
|
||||
return handleCache(opts, template)(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Render an EJS file at the given `path` and callback `cb(err, str)`.
|
||||
*
|
||||
* If you would like to include options but not data, you need to explicitly
|
||||
* call this function with `data` being an empty object or `null`.
|
||||
*
|
||||
* @param {String} path path to the EJS file
|
||||
* @param {Object} [data={}] template data
|
||||
* @param {Options} [opts={}] compilation and rendering options
|
||||
* @param {RenderFileCallback} cb callback
|
||||
* @public
|
||||
*/
|
||||
|
||||
exports.renderFile = function () {
|
||||
var args = Array.prototype.slice.call(arguments)
|
||||
, path = args.shift()
|
||||
, cb = args.pop()
|
||||
, data = args.shift() || {}
|
||||
, opts = args.pop() || {}
|
||||
, result;
|
||||
|
||||
// Don't pollute passed in opts obj with new vals
|
||||
opts = utils.shallowCopy({}, opts);
|
||||
|
||||
// No options object -- if there are optiony names
|
||||
// in the data, copy them to options
|
||||
if (arguments.length == 3) {
|
||||
cpOptsInData(data, opts);
|
||||
}
|
||||
opts.filename = path;
|
||||
|
||||
try {
|
||||
result = handleCache(opts)(data);
|
||||
}
|
||||
catch(err) {
|
||||
return cb(err);
|
||||
}
|
||||
return cb(null, result);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear intermediate JavaScript cache. Calls {@link Cache#reset}.
|
||||
* @public
|
||||
*/
|
||||
|
||||
exports.clearCache = function () {
|
||||
exports.cache.reset();
|
||||
};
|
||||
|
||||
function Template(text, opts) {
|
||||
opts = opts || {};
|
||||
var options = {};
|
||||
this.templateText = text;
|
||||
this.mode = null;
|
||||
this.truncate = false;
|
||||
this.currentLine = 1;
|
||||
this.source = '';
|
||||
this.dependencies = [];
|
||||
options.client = opts.client || false;
|
||||
options.escapeFunction = opts.escape || utils.escapeXML;
|
||||
options.compileDebug = opts.compileDebug !== false;
|
||||
options.debug = !!opts.debug;
|
||||
options.filename = opts.filename;
|
||||
options.delimiter = opts.delimiter || exports.delimiter || _DEFAULT_DELIMITER;
|
||||
options._with = typeof opts._with != 'undefined' ? opts._with : true;
|
||||
options.context = opts.context;
|
||||
options.cache = opts.cache || false;
|
||||
options.rmWhitespace = opts.rmWhitespace;
|
||||
this.opts = options;
|
||||
|
||||
this.regex = this.createRegex();
|
||||
}
|
||||
|
||||
Template.modes = {
|
||||
EVAL: 'eval'
|
||||
, ESCAPED: 'escaped'
|
||||
, RAW: 'raw'
|
||||
, COMMENT: 'comment'
|
||||
, LITERAL: 'literal'
|
||||
};
|
||||
|
||||
Template.prototype = {
|
||||
createRegex: function () {
|
||||
var str = _REGEX_STRING
|
||||
, delim = utils.escapeRegExpChars(this.opts.delimiter);
|
||||
str = str.replace(/%/g, delim);
|
||||
return new RegExp(str);
|
||||
}
|
||||
|
||||
, compile: function () {
|
||||
var src
|
||||
, fn
|
||||
, opts = this.opts
|
||||
, prepended = ''
|
||||
, appended = ''
|
||||
, escape = opts.escapeFunction;
|
||||
|
||||
if (opts.rmWhitespace) {
|
||||
// Have to use two separate replace here as `^` and `$` operators don't
|
||||
// work well with `\r`.
|
||||
this.templateText =
|
||||
this.templateText.replace(/\r/g, '').replace(/^\s+|\s+$/gm, '');
|
||||
}
|
||||
|
||||
// Slurp spaces and tabs before <%_ and after _%>
|
||||
this.templateText =
|
||||
this.templateText.replace(/[ \t]*<%_/gm, '<%_').replace(/_%>[ \t]*/gm, '_%>');
|
||||
|
||||
if (!this.source) {
|
||||
this.generateSource();
|
||||
prepended += ' var __output = [], __append = __output.push.bind(__output);' + '\n';
|
||||
if (opts._with !== false) {
|
||||
prepended += ' with (' + exports.localsName + ' || {}) {' + '\n';
|
||||
appended += ' }' + '\n';
|
||||
}
|
||||
appended += ' return __output.join("");' + '\n';
|
||||
this.source = prepended + this.source + appended;
|
||||
}
|
||||
|
||||
if (opts.compileDebug) {
|
||||
src = 'var __line = 1' + '\n'
|
||||
+ ' , __lines = ' + JSON.stringify(this.templateText) + '\n'
|
||||
+ ' , __filename = ' + (opts.filename ?
|
||||
JSON.stringify(opts.filename) : 'undefined') + ';' + '\n'
|
||||
+ 'try {' + '\n'
|
||||
+ this.source
|
||||
+ '} catch (e) {' + '\n'
|
||||
+ ' rethrow(e, __lines, __filename, __line);' + '\n'
|
||||
+ '}' + '\n';
|
||||
}
|
||||
else {
|
||||
src = this.source;
|
||||
}
|
||||
|
||||
if (opts.debug) {
|
||||
console.log(src);
|
||||
}
|
||||
|
||||
if (opts.client) {
|
||||
src = 'escape = escape || ' + escape.toString() + ';' + '\n' + src;
|
||||
if (opts.compileDebug) {
|
||||
src = 'rethrow = rethrow || ' + rethrow.toString() + ';' + '\n' + src;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
fn = new Function(exports.localsName + ', escape, include, rethrow', src);
|
||||
}
|
||||
catch(e) {
|
||||
// istanbul ignore else
|
||||
if (e instanceof SyntaxError) {
|
||||
if (opts.filename) {
|
||||
e.message += ' in ' + opts.filename;
|
||||
}
|
||||
e.message += ' while compiling ejs';
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (opts.client) {
|
||||
fn.dependencies = this.dependencies;
|
||||
return fn;
|
||||
}
|
||||
|
||||
// Return a callable function which will execute the function
|
||||
// created by the source-code, with the passed data as locals
|
||||
// Adds a local `include` function which allows full recursive include
|
||||
var returnedFn = function (data) {
|
||||
var include = function (path, includeData) {
|
||||
var d = utils.shallowCopy({}, data);
|
||||
if (includeData) {
|
||||
d = utils.shallowCopy(d, includeData);
|
||||
}
|
||||
return includeFile(path, opts)(d);
|
||||
};
|
||||
return fn.apply(opts.context, [data || {}, escape, include, rethrow]);
|
||||
};
|
||||
returnedFn.dependencies = this.dependencies;
|
||||
return returnedFn;
|
||||
}
|
||||
|
||||
, generateSource: function () {
|
||||
var self = this
|
||||
, matches = this.parseTemplateText()
|
||||
, d = this.opts.delimiter;
|
||||
|
||||
if (matches && matches.length) {
|
||||
matches.forEach(function (line, index) {
|
||||
var opening
|
||||
, closing
|
||||
, include
|
||||
, includeOpts
|
||||
, includeSrc;
|
||||
// If this is an opening tag, check for closing tags
|
||||
// FIXME: May end up with some false positives here
|
||||
// Better to store modes as k/v with '<' + delimiter as key
|
||||
// Then this can simply check against the map
|
||||
if ( line.indexOf('<' + d) === 0 // If it is a tag
|
||||
&& line.indexOf('<' + d + d) !== 0) { // and is not escaped
|
||||
closing = matches[index + 2];
|
||||
if (!(closing == d + '>' || closing == '-' + d + '>' || closing == '_' + d + '>')) {
|
||||
throw new Error('Could not find matching close tag for "' + line + '".');
|
||||
}
|
||||
}
|
||||
// HACK: backward-compat `include` preprocessor directives
|
||||
if ((include = line.match(/^\s*include\s+(\S+)/))) {
|
||||
opening = matches[index - 1];
|
||||
// Must be in EVAL or RAW mode
|
||||
if (opening && (opening == '<' + d || opening == '<' + d + '-' || opening == '<' + d + '_')) {
|
||||
includeOpts = utils.shallowCopy({}, self.opts);
|
||||
includeSrc = includeSource(include[1], includeOpts);
|
||||
includeSrc = ' ; (function(){' + '\n' + includeSrc +
|
||||
' ; })()' + '\n';
|
||||
self.source += includeSrc;
|
||||
self.dependencies.push(exports.resolveInclude(include[1],
|
||||
includeOpts.filename));
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.scanLine(line);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
, parseTemplateText: function () {
|
||||
var str = this.templateText
|
||||
, pat = this.regex
|
||||
, result = pat.exec(str)
|
||||
, arr = []
|
||||
, firstPos
|
||||
, lastPos;
|
||||
|
||||
while (result) {
|
||||
firstPos = result.index;
|
||||
lastPos = pat.lastIndex;
|
||||
|
||||
if (firstPos !== 0) {
|
||||
arr.push(str.substring(0, firstPos));
|
||||
str = str.slice(firstPos);
|
||||
}
|
||||
|
||||
arr.push(result[0]);
|
||||
str = str.slice(result[0].length);
|
||||
result = pat.exec(str);
|
||||
}
|
||||
|
||||
if (str) {
|
||||
arr.push(str);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
, scanLine: function (line) {
|
||||
var self = this
|
||||
, d = this.opts.delimiter
|
||||
, newLineCount = 0;
|
||||
|
||||
function _addOutput() {
|
||||
if (self.truncate) {
|
||||
line = line.replace('\n', '');
|
||||
self.truncate = false;
|
||||
}
|
||||
else if (self.opts.rmWhitespace) {
|
||||
// Gotta me more careful here.
|
||||
// .replace(/^(\s*)\n/, '$1') might be more appropriate here but as
|
||||
// rmWhitespace already removes trailing spaces anyway so meh.
|
||||
line = line.replace(/^\n/, '');
|
||||
}
|
||||
if (!line) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Preserve literal slashes
|
||||
line = line.replace(/\\/g, '\\\\');
|
||||
|
||||
// Convert linebreaks
|
||||
line = line.replace(/\n/g, '\\n');
|
||||
line = line.replace(/\r/g, '\\r');
|
||||
|
||||
// Escape double-quotes
|
||||
// - this will be the delimiter during execution
|
||||
line = line.replace(/"/g, '\\"');
|
||||
self.source += ' ; __append("' + line + '")' + '\n';
|
||||
}
|
||||
|
||||
newLineCount = (line.split('\n').length - 1);
|
||||
|
||||
switch (line) {
|
||||
case '<' + d:
|
||||
case '<' + d + '_':
|
||||
this.mode = Template.modes.EVAL;
|
||||
break;
|
||||
case '<' + d + '=':
|
||||
this.mode = Template.modes.ESCAPED;
|
||||
break;
|
||||
case '<' + d + '-':
|
||||
this.mode = Template.modes.RAW;
|
||||
break;
|
||||
case '<' + d + '#':
|
||||
this.mode = Template.modes.COMMENT;
|
||||
break;
|
||||
case '<' + d + d:
|
||||
this.mode = Template.modes.LITERAL;
|
||||
this.source += ' ; __append("' + line.replace('<' + d + d, '<' + d) + '")' + '\n';
|
||||
break;
|
||||
case d + '>':
|
||||
case '-' + d + '>':
|
||||
case '_' + d + '>':
|
||||
if (this.mode == Template.modes.LITERAL) {
|
||||
_addOutput();
|
||||
}
|
||||
|
||||
this.mode = null;
|
||||
this.truncate = line.indexOf('-') === 0 || line.indexOf('_') === 0;
|
||||
break;
|
||||
default:
|
||||
// In script mode, depends on type of tag
|
||||
if (this.mode) {
|
||||
// If '//' is found without a line break, add a line break.
|
||||
switch (this.mode) {
|
||||
case Template.modes.EVAL:
|
||||
case Template.modes.ESCAPED:
|
||||
case Template.modes.RAW:
|
||||
if (line.lastIndexOf('//') > line.lastIndexOf('\n')) {
|
||||
line += '\n';
|
||||
}
|
||||
}
|
||||
switch (this.mode) {
|
||||
// Just executing code
|
||||
case Template.modes.EVAL:
|
||||
this.source += ' ; ' + line + '\n';
|
||||
break;
|
||||
// Exec, esc, and output
|
||||
case Template.modes.ESCAPED:
|
||||
this.source += ' ; __append(escape(' +
|
||||
line.replace(_TRAILING_SEMCOL, '').trim() + '))' + '\n';
|
||||
break;
|
||||
// Exec and output
|
||||
case Template.modes.RAW:
|
||||
this.source += ' ; __append(' +
|
||||
line.replace(_TRAILING_SEMCOL, '').trim() + ')' + '\n';
|
||||
break;
|
||||
case Template.modes.COMMENT:
|
||||
// Do nothing
|
||||
break;
|
||||
// Literal <%% mode, append as raw output
|
||||
case Template.modes.LITERAL:
|
||||
_addOutput();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// In string mode, just add the output
|
||||
else {
|
||||
_addOutput();
|
||||
}
|
||||
}
|
||||
|
||||
if (self.opts.compileDebug && newLineCount) {
|
||||
this.currentLine += newLineCount;
|
||||
this.source += ' ; __line = ' + this.currentLine + '\n';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Express.js support.
|
||||
*
|
||||
* This is an alias for {@link module:ejs.renderFile}, in order to support
|
||||
* Express.js out-of-the-box.
|
||||
*
|
||||
* @func
|
||||
*/
|
||||
|
||||
exports.__express = exports.renderFile;
|
||||
|
||||
// Add require support
|
||||
/* istanbul ignore else */
|
||||
if (require.extensions) {
|
||||
require.extensions['.ejs'] = function (module, filename) {
|
||||
filename = filename || /* istanbul ignore next */ module.filename;
|
||||
var options = {
|
||||
filename: filename
|
||||
, client: true
|
||||
}
|
||||
, template = fs.readFileSync(filename).toString()
|
||||
, fn = exports.compile(template, options);
|
||||
module._compile('module.exports = ' + fn.toString() + ';', filename);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Version of EJS.
|
||||
*
|
||||
* @readonly
|
||||
* @type {String}
|
||||
* @public
|
||||
*/
|
||||
|
||||
exports.VERSION = _VERSION_STRING;
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (typeof window != 'undefined') {
|
||||
window.ejs = exports;
|
||||
}
|
||||
|
||||
},{"../package.json":6,"./utils":2,"fs":3,"path":4}],2:[function(require,module,exports){
|
||||
/*
|
||||
* EJS Embedded JavaScript templates
|
||||
* Copyright 2112 Matthew Eernisse (mde@fleegix.org)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Private utility functions
|
||||
* @module utils
|
||||
* @private
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var regExpChars = /[|\\{}()[\]^$+*?.]/g;
|
||||
|
||||
/**
|
||||
* Escape characters reserved in regular expressions.
|
||||
*
|
||||
* If `string` is `undefined` or `null`, the empty string is returned.
|
||||
*
|
||||
* @param {String} string Input string
|
||||
* @return {String} Escaped string
|
||||
* @static
|
||||
* @private
|
||||
*/
|
||||
exports.escapeRegExpChars = function (string) {
|
||||
// istanbul ignore if
|
||||
if (!string) {
|
||||
return '';
|
||||
}
|
||||
return String(string).replace(regExpChars, '\\$&');
|
||||
};
|
||||
|
||||
var _ENCODE_HTML_RULES = {
|
||||
'&': '&'
|
||||
, '<': '<'
|
||||
, '>': '>'
|
||||
, '"': '"'
|
||||
, "'": '''
|
||||
}
|
||||
, _MATCH_HTML = /[&<>\'"]/g;
|
||||
|
||||
function encode_char(c) {
|
||||
return _ENCODE_HTML_RULES[c] || c;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stringified version of constants used by {@link module:utils.escapeXML}.
|
||||
*
|
||||
* It is used in the process of generating {@link ClientFunction}s.
|
||||
*
|
||||
* @readonly
|
||||
* @type {String}
|
||||
*/
|
||||
|
||||
var escapeFuncStr =
|
||||
'var _ENCODE_HTML_RULES = {\n'
|
||||
+ ' "&": "&"\n'
|
||||
+ ' , "<": "<"\n'
|
||||
+ ' , ">": ">"\n'
|
||||
+ ' , \'"\': """\n'
|
||||
+ ' , "\'": "'"\n'
|
||||
+ ' }\n'
|
||||
+ ' , _MATCH_HTML = /[&<>\'"]/g;\n'
|
||||
+ 'function encode_char(c) {\n'
|
||||
+ ' return _ENCODE_HTML_RULES[c] || c;\n'
|
||||
+ '};\n';
|
||||
|
||||
/**
|
||||
* Escape characters reserved in XML.
|
||||
*
|
||||
* If `markup` is `undefined` or `null`, the empty string is returned.
|
||||
*
|
||||
* @implements {EscapeCallback}
|
||||
* @param {String} markup Input string
|
||||
* @return {String} Escaped string
|
||||
* @static
|
||||
* @private
|
||||
*/
|
||||
|
||||
exports.escapeXML = function (markup) {
|
||||
return markup == undefined
|
||||
? ''
|
||||
: String(markup)
|
||||
.replace(_MATCH_HTML, encode_char);
|
||||
};
|
||||
exports.escapeXML.toString = function () {
|
||||
return Function.prototype.toString.call(this) + ';\n' + escapeFuncStr
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy all properties from one object to another, in a shallow fashion.
|
||||
*
|
||||
* @param {Object} to Destination object
|
||||
* @param {Object} from Source object
|
||||
* @return {Object} Destination object
|
||||
* @static
|
||||
* @private
|
||||
*/
|
||||
exports.shallowCopy = function (to, from) {
|
||||
from = from || {};
|
||||
for (var p in from) {
|
||||
to[p] = from[p];
|
||||
}
|
||||
return to;
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple in-process cache implementation. Does not implement limits of any
|
||||
* sort.
|
||||
*
|
||||
* @implements Cache
|
||||
* @static
|
||||
* @private
|
||||
*/
|
||||
exports.cache = {
|
||||
_data: {},
|
||||
set: function (key, val) {
|
||||
this._data[key] = val;
|
||||
},
|
||||
get: function (key) {
|
||||
return this._data[key];
|
||||
},
|
||||
reset: function () {
|
||||
this._data = {};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
},{}],3:[function(require,module,exports){
|
||||
|
||||
},{}],4:[function(require,module,exports){
|
||||
(function (process){
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the
|
||||
// "Software"), to deal in the Software without restriction, including
|
||||
// without limitation the rights to use, copy, modify, merge, publish,
|
||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
// persons to whom the Software is furnished to do so, subject to the
|
||||
// following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included
|
||||
// in all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// resolves . and .. elements in a path array with directory names there
|
||||
// must be no slashes, empty elements, or device names (c:\) in the array
|
||||
// (so also no leading and trailing slashes - it does not distinguish
|
||||
// relative and absolute paths)
|
||||
function normalizeArray(parts, allowAboveRoot) {
|
||||
// if the path tries to go above the root, `up` ends up > 0
|
||||
var up = 0;
|
||||
for (var i = parts.length - 1; i >= 0; i--) {
|
||||
var last = parts[i];
|
||||
if (last === '.') {
|
||||
parts.splice(i, 1);
|
||||
} else if (last === '..') {
|
||||
parts.splice(i, 1);
|
||||
up++;
|
||||
} else if (up) {
|
||||
parts.splice(i, 1);
|
||||
up--;
|
||||
}
|
||||
}
|
||||
|
||||
// if the path is allowed to go above the root, restore leading ..s
|
||||
if (allowAboveRoot) {
|
||||
for (; up--; up) {
|
||||
parts.unshift('..');
|
||||
}
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
// Split a filename into [root, dir, basename, ext], unix version
|
||||
// 'root' is just a slash, or nothing.
|
||||
var splitPathRe =
|
||||
/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
|
||||
var splitPath = function(filename) {
|
||||
return splitPathRe.exec(filename).slice(1);
|
||||
};
|
||||
|
||||
// path.resolve([from ...], to)
|
||||
// posix version
|
||||
exports.resolve = function() {
|
||||
var resolvedPath = '',
|
||||
resolvedAbsolute = false;
|
||||
|
||||
for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
|
||||
var path = (i >= 0) ? arguments[i] : process.cwd();
|
||||
|
||||
// Skip empty and invalid entries
|
||||
if (typeof path !== 'string') {
|
||||
throw new TypeError('Arguments to path.resolve must be strings');
|
||||
} else if (!path) {
|
||||
continue;
|
||||
}
|
||||
|
||||
resolvedPath = path + '/' + resolvedPath;
|
||||
resolvedAbsolute = path.charAt(0) === '/';
|
||||
}
|
||||
|
||||
// At this point the path should be resolved to a full absolute path, but
|
||||
// handle relative paths to be safe (might happen when process.cwd() fails)
|
||||
|
||||
// Normalize the path
|
||||
resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
|
||||
return !!p;
|
||||
}), !resolvedAbsolute).join('/');
|
||||
|
||||
return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
|
||||
};
|
||||
|
||||
// path.normalize(path)
|
||||
// posix version
|
||||
exports.normalize = function(path) {
|
||||
var isAbsolute = exports.isAbsolute(path),
|
||||
trailingSlash = substr(path, -1) === '/';
|
||||
|
||||
// Normalize the path
|
||||
path = normalizeArray(filter(path.split('/'), function(p) {
|
||||
return !!p;
|
||||
}), !isAbsolute).join('/');
|
||||
|
||||
if (!path && !isAbsolute) {
|
||||
path = '.';
|
||||
}
|
||||
if (path && trailingSlash) {
|
||||
path += '/';
|
||||
}
|
||||
|
||||
return (isAbsolute ? '/' : '') + path;
|
||||
};
|
||||
|
||||
// posix version
|
||||
exports.isAbsolute = function(path) {
|
||||
return path.charAt(0) === '/';
|
||||
};
|
||||
|
||||
// posix version
|
||||
exports.join = function() {
|
||||
var paths = Array.prototype.slice.call(arguments, 0);
|
||||
return exports.normalize(filter(paths, function(p, index) {
|
||||
if (typeof p !== 'string') {
|
||||
throw new TypeError('Arguments to path.join must be strings');
|
||||
}
|
||||
return p;
|
||||
}).join('/'));
|
||||
};
|
||||
|
||||
|
||||
// path.relative(from, to)
|
||||
// posix version
|
||||
exports.relative = function(from, to) {
|
||||
from = exports.resolve(from).substr(1);
|
||||
to = exports.resolve(to).substr(1);
|
||||
|
||||
function trim(arr) {
|
||||
var start = 0;
|
||||
for (; start < arr.length; start++) {
|
||||
if (arr[start] !== '') break;
|
||||
}
|
||||
|
||||
var end = arr.length - 1;
|
||||
for (; end >= 0; end--) {
|
||||
if (arr[end] !== '') break;
|
||||
}
|
||||
|
||||
if (start > end) return [];
|
||||
return arr.slice(start, end - start + 1);
|
||||
}
|
||||
|
||||
var fromParts = trim(from.split('/'));
|
||||
var toParts = trim(to.split('/'));
|
||||
|
||||
var length = Math.min(fromParts.length, toParts.length);
|
||||
var samePartsLength = length;
|
||||
for (var i = 0; i < length; i++) {
|
||||
if (fromParts[i] !== toParts[i]) {
|
||||
samePartsLength = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var outputParts = [];
|
||||
for (var i = samePartsLength; i < fromParts.length; i++) {
|
||||
outputParts.push('..');
|
||||
}
|
||||
|
||||
outputParts = outputParts.concat(toParts.slice(samePartsLength));
|
||||
|
||||
return outputParts.join('/');
|
||||
};
|
||||
|
||||
exports.sep = '/';
|
||||
exports.delimiter = ':';
|
||||
|
||||
exports.dirname = function(path) {
|
||||
var result = splitPath(path),
|
||||
root = result[0],
|
||||
dir = result[1];
|
||||
|
||||
if (!root && !dir) {
|
||||
// No dirname whatsoever
|
||||
return '.';
|
||||
}
|
||||
|
||||
if (dir) {
|
||||
// It has a dirname, strip trailing slash
|
||||
dir = dir.substr(0, dir.length - 1);
|
||||
}
|
||||
|
||||
return root + dir;
|
||||
};
|
||||
|
||||
|
||||
exports.basename = function(path, ext) {
|
||||
var f = splitPath(path)[2];
|
||||
// TODO: make this comparison case-insensitive on windows?
|
||||
if (ext && f.substr(-1 * ext.length) === ext) {
|
||||
f = f.substr(0, f.length - ext.length);
|
||||
}
|
||||
return f;
|
||||
};
|
||||
|
||||
|
||||
exports.extname = function(path) {
|
||||
return splitPath(path)[3];
|
||||
};
|
||||
|
||||
function filter (xs, f) {
|
||||
if (xs.filter) return xs.filter(f);
|
||||
var res = [];
|
||||
for (var i = 0; i < xs.length; i++) {
|
||||
if (f(xs[i], i, xs)) res.push(xs[i]);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// String.prototype.substr - negative index don't work in IE8
|
||||
var substr = 'ab'.substr(-1) === 'b'
|
||||
? function (str, start, len) { return str.substr(start, len) }
|
||||
: function (str, start, len) {
|
||||
if (start < 0) start = str.length + start;
|
||||
return str.substr(start, len);
|
||||
}
|
||||
;
|
||||
|
||||
}).call(this,require('_process'))
|
||||
},{"_process":5}],5:[function(require,module,exports){
|
||||
// shim for using process in browser
|
||||
|
||||
var process = module.exports = {};
|
||||
var queue = [];
|
||||
var draining = false;
|
||||
|
||||
function drainQueue() {
|
||||
if (draining) {
|
||||
return;
|
||||
}
|
||||
draining = true;
|
||||
var currentQueue;
|
||||
var len = queue.length;
|
||||
while(len) {
|
||||
currentQueue = queue;
|
||||
queue = [];
|
||||
var i = -1;
|
||||
while (++i < len) {
|
||||
currentQueue[i]();
|
||||
}
|
||||
len = queue.length;
|
||||
}
|
||||
draining = false;
|
||||
}
|
||||
process.nextTick = function (fun) {
|
||||
queue.push(fun);
|
||||
if (!draining) {
|
||||
setTimeout(drainQueue, 0);
|
||||
}
|
||||
};
|
||||
|
||||
process.title = 'browser';
|
||||
process.browser = true;
|
||||
process.env = {};
|
||||
process.argv = [];
|
||||
process.version = ''; // empty string to avoid regexp issues
|
||||
|
||||
function noop() {}
|
||||
|
||||
process.on = noop;
|
||||
process.addListener = noop;
|
||||
process.once = noop;
|
||||
process.off = noop;
|
||||
process.removeListener = noop;
|
||||
process.removeAllListeners = noop;
|
||||
process.emit = noop;
|
||||
|
||||
process.binding = function (name) {
|
||||
throw new Error('process.binding is not supported');
|
||||
};
|
||||
|
||||
// TODO(shtylman)
|
||||
process.cwd = function () { return '/' };
|
||||
process.chdir = function (dir) {
|
||||
throw new Error('process.chdir is not supported');
|
||||
};
|
||||
process.umask = function() { return 0; };
|
||||
|
||||
},{}],6:[function(require,module,exports){
|
||||
module.exports={
|
||||
"name": "ejs",
|
||||
"description": "Embedded JavaScript templates",
|
||||
"keywords": [
|
||||
"template",
|
||||
"engine",
|
||||
"ejs"
|
||||
],
|
||||
"version": "2.3.3",
|
||||
"author": "Matthew Eernisse <mde@fleegix.org> (http://fleegix.org)",
|
||||
"contributors": [
|
||||
"Timothy Gu <timothygu99@gmail.com> (https://timothygu.github.io)"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/ejs.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/mde/ejs.git"
|
||||
},
|
||||
"bugs": "https://github.com/mde/ejs/issues",
|
||||
"homepage": "https://github.com/mde/ejs",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"browserify": "^8.0.3",
|
||||
"istanbul": "~0.3.5",
|
||||
"jake": "^8.0.0",
|
||||
"jsdoc": "^3.3.0-beta1",
|
||||
"lru-cache": "^2.5.0",
|
||||
"mocha": "^2.1.0",
|
||||
"rimraf": "^2.2.8",
|
||||
"uglify-js": "^2.4.16"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha",
|
||||
"coverage": "istanbul cover node_modules/mocha/bin/_mocha",
|
||||
"doc": "rimraf out && jsdoc -c jsdoc.json lib/* docs/jsdoc/*",
|
||||
"devdoc": "rimraf out && jsdoc -p -c jsdoc.json lib/* docs/jsdoc/*"
|
||||
}
|
||||
}
|
||||
},{}]},{},[1]);
|
1
nodejs/node_modules/ejs/ejs.min.js
generated
vendored
Normal file
1
nodejs/node_modules/ejs/ejs.min.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){"use strict";var fs=require("fs"),utils=require("./utils"),scopeOptionWarned=false,_VERSION_STRING=require("../package.json").version,_DEFAULT_DELIMITER="%",_DEFAULT_LOCALS_NAME="locals",_REGEX_STRING="(<%%|<%=|<%-|<%_|<%#|<%|%>|-%>|_%>)",_OPTS=["cache","filename","delimiter","scope","context","debug","compileDebug","client","_with","rmWhitespace"],_TRAILING_SEMCOL=/;\s*$/,_BOM=/^\uFEFF/;exports.cache=utils.cache;exports.localsName=_DEFAULT_LOCALS_NAME;exports.resolveInclude=function(name,filename){var path=require("path"),dirname=path.dirname,extname=path.extname,resolve=path.resolve,includePath=resolve(dirname(filename),name),ext=extname(name);if(!ext){includePath+=".ejs"}return includePath};function handleCache(options,template){var fn,path=options.filename,hasTemplate=arguments.length>1;if(options.cache){if(!path){throw new Error("cache option requires a filename")}fn=exports.cache.get(path);if(fn){return fn}if(!hasTemplate){template=fs.readFileSync(path).toString().replace(_BOM,"")}}else if(!hasTemplate){if(!path){throw new Error("Internal EJS error: no file name or template "+"provided")}template=fs.readFileSync(path).toString().replace(_BOM,"")}fn=exports.compile(template,options);if(options.cache){exports.cache.set(path,fn)}return fn}function includeFile(path,options){var opts=utils.shallowCopy({},options);if(!opts.filename){throw new Error("`include` requires the 'filename' option.")}opts.filename=exports.resolveInclude(path,opts.filename);return handleCache(opts)}function includeSource(path,options){var opts=utils.shallowCopy({},options),includePath,template;if(!opts.filename){throw new Error("`include` requires the 'filename' option.")}includePath=exports.resolveInclude(path,opts.filename);template=fs.readFileSync(includePath).toString().replace(_BOM,"");opts.filename=includePath;var templ=new Template(template,opts);templ.generateSource();return templ.source}function rethrow(err,str,filename,lineno){var lines=str.split("\n"),start=Math.max(lineno-3,0),end=Math.min(lines.length,lineno+3);var context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" >> ":" ")+curr+"| "+line}).join("\n");err.path=filename;err.message=(filename||"ejs")+":"+lineno+"\n"+context+"\n\n"+err.message;throw err}function cpOptsInData(data,opts){_OPTS.forEach(function(p){if(typeof data[p]!="undefined"){opts[p]=data[p]}})}exports.compile=function compile(template,opts){var templ;if(opts&&opts.scope){if(!scopeOptionWarned){console.warn("`scope` option is deprecated and will be removed in EJS 3");scopeOptionWarned=true}if(!opts.context){opts.context=opts.scope}delete opts.scope}templ=new Template(template,opts);return templ.compile()};exports.render=function(template,data,opts){data=data||{};opts=opts||{};var fn;if(arguments.length==2){cpOptsInData(data,opts)}return handleCache(opts,template)(data)};exports.renderFile=function(){var args=Array.prototype.slice.call(arguments),path=args.shift(),cb=args.pop(),data=args.shift()||{},opts=args.pop()||{},result;opts=utils.shallowCopy({},opts);if(arguments.length==3){cpOptsInData(data,opts)}opts.filename=path;try{result=handleCache(opts)(data)}catch(err){return cb(err)}return cb(null,result)};exports.clearCache=function(){exports.cache.reset()};function Template(text,opts){opts=opts||{};var options={};this.templateText=text;this.mode=null;this.truncate=false;this.currentLine=1;this.source="";this.dependencies=[];options.client=opts.client||false;options.escapeFunction=opts.escape||utils.escapeXML;options.compileDebug=opts.compileDebug!==false;options.debug=!!opts.debug;options.filename=opts.filename;options.delimiter=opts.delimiter||exports.delimiter||_DEFAULT_DELIMITER;options._with=typeof opts._with!="undefined"?opts._with:true;options.context=opts.context;options.cache=opts.cache||false;options.rmWhitespace=opts.rmWhitespace;this.opts=options;this.regex=this.createRegex()}Template.modes={EVAL:"eval",ESCAPED:"escaped",RAW:"raw",COMMENT:"comment",LITERAL:"literal"};Template.prototype={createRegex:function(){var str=_REGEX_STRING,delim=utils.escapeRegExpChars(this.opts.delimiter);str=str.replace(/%/g,delim);return new RegExp(str)},compile:function(){var src,fn,opts=this.opts,prepended="",appended="",escape=opts.escapeFunction;if(opts.rmWhitespace){this.templateText=this.templateText.replace(/\r/g,"").replace(/^\s+|\s+$/gm,"")}this.templateText=this.templateText.replace(/[ \t]*<%_/gm,"<%_").replace(/_%>[ \t]*/gm,"_%>");if(!this.source){this.generateSource();prepended+=" var __output = [], __append = __output.push.bind(__output);"+"\n";if(opts._with!==false){prepended+=" with ("+exports.localsName+" || {}) {"+"\n";appended+=" }"+"\n"}appended+=' return __output.join("");'+"\n";this.source=prepended+this.source+appended}if(opts.compileDebug){src="var __line = 1"+"\n"+" , __lines = "+JSON.stringify(this.templateText)+"\n"+" , __filename = "+(opts.filename?JSON.stringify(opts.filename):"undefined")+";"+"\n"+"try {"+"\n"+this.source+"} catch (e) {"+"\n"+" rethrow(e, __lines, __filename, __line);"+"\n"+"}"+"\n"}else{src=this.source}if(opts.debug){console.log(src)}if(opts.client){src="escape = escape || "+escape.toString()+";"+"\n"+src;if(opts.compileDebug){src="rethrow = rethrow || "+rethrow.toString()+";"+"\n"+src}}try{fn=new Function(exports.localsName+", escape, include, rethrow",src)}catch(e){if(e instanceof SyntaxError){if(opts.filename){e.message+=" in "+opts.filename}e.message+=" while compiling ejs"}throw e}if(opts.client){fn.dependencies=this.dependencies;return fn}var returnedFn=function(data){var include=function(path,includeData){var d=utils.shallowCopy({},data);if(includeData){d=utils.shallowCopy(d,includeData)}return includeFile(path,opts)(d)};return fn.apply(opts.context,[data||{},escape,include,rethrow])};returnedFn.dependencies=this.dependencies;return returnedFn},generateSource:function(){var self=this,matches=this.parseTemplateText(),d=this.opts.delimiter;if(matches&&matches.length){matches.forEach(function(line,index){var opening,closing,include,includeOpts,includeSrc;if(line.indexOf("<"+d)===0&&line.indexOf("<"+d+d)!==0){closing=matches[index+2];if(!(closing==d+">"||closing=="-"+d+">"||closing=="_"+d+">")){throw new Error('Could not find matching close tag for "'+line+'".')}}if(include=line.match(/^\s*include\s+(\S+)/)){opening=matches[index-1];if(opening&&(opening=="<"+d||opening=="<"+d+"-"||opening=="<"+d+"_")){includeOpts=utils.shallowCopy({},self.opts);includeSrc=includeSource(include[1],includeOpts);includeSrc=" ; (function(){"+"\n"+includeSrc+" ; })()"+"\n";self.source+=includeSrc;self.dependencies.push(exports.resolveInclude(include[1],includeOpts.filename));return}}self.scanLine(line)})}},parseTemplateText:function(){var str=this.templateText,pat=this.regex,result=pat.exec(str),arr=[],firstPos,lastPos;while(result){firstPos=result.index;lastPos=pat.lastIndex;if(firstPos!==0){arr.push(str.substring(0,firstPos));str=str.slice(firstPos)}arr.push(result[0]);str=str.slice(result[0].length);result=pat.exec(str)}if(str){arr.push(str)}return arr},scanLine:function(line){var self=this,d=this.opts.delimiter,newLineCount=0;function _addOutput(){if(self.truncate){line=line.replace("\n","");self.truncate=false}else if(self.opts.rmWhitespace){line=line.replace(/^\n/,"")}if(!line){return}line=line.replace(/\\/g,"\\\\");line=line.replace(/\n/g,"\\n");line=line.replace(/\r/g,"\\r");line=line.replace(/"/g,'\\"');self.source+=' ; __append("'+line+'")'+"\n"}newLineCount=line.split("\n").length-1;switch(line){case"<"+d:case"<"+d+"_":this.mode=Template.modes.EVAL;break;case"<"+d+"=":this.mode=Template.modes.ESCAPED;break;case"<"+d+"-":this.mode=Template.modes.RAW;break;case"<"+d+"#":this.mode=Template.modes.COMMENT;break;case"<"+d+d:this.mode=Template.modes.LITERAL;this.source+=' ; __append("'+line.replace("<"+d+d,"<"+d)+'")'+"\n";break;case d+">":case"-"+d+">":case"_"+d+">":if(this.mode==Template.modes.LITERAL){_addOutput()}this.mode=null;this.truncate=line.indexOf("-")===0||line.indexOf("_")===0;break;default:if(this.mode){switch(this.mode){case Template.modes.EVAL:case Template.modes.ESCAPED:case Template.modes.RAW:if(line.lastIndexOf("//")>line.lastIndexOf("\n")){line+="\n"}}switch(this.mode){case Template.modes.EVAL:this.source+=" ; "+line+"\n";break;case Template.modes.ESCAPED:this.source+=" ; __append(escape("+line.replace(_TRAILING_SEMCOL,"").trim()+"))"+"\n";break;case Template.modes.RAW:this.source+=" ; __append("+line.replace(_TRAILING_SEMCOL,"").trim()+")"+"\n";break;case Template.modes.COMMENT:break;case Template.modes.LITERAL:_addOutput();break}}else{_addOutput()}}if(self.opts.compileDebug&&newLineCount){this.currentLine+=newLineCount;this.source+=" ; __line = "+this.currentLine+"\n"}}};exports.__express=exports.renderFile;if(require.extensions){require.extensions[".ejs"]=function(module,filename){filename=filename||module.filename;var options={filename:filename,client:true},template=fs.readFileSync(filename).toString(),fn=exports.compile(template,options);module._compile("module.exports = "+fn.toString()+";",filename)}}exports.VERSION=_VERSION_STRING;if(typeof window!="undefined"){window.ejs=exports}},{"../package.json":6,"./utils":2,fs:3,path:4}],2:[function(require,module,exports){"use strict";var regExpChars=/[|\\{}()[\]^$+*?.]/g;exports.escapeRegExpChars=function(string){if(!string){return""}return String(string).replace(regExpChars,"\\$&")};var _ENCODE_HTML_RULES={"&":"&","<":"<",">":">",'"':""","'":"'"},_MATCH_HTML=/[&<>\'"]/g;function encode_char(c){return _ENCODE_HTML_RULES[c]||c}var escapeFuncStr="var _ENCODE_HTML_RULES = {\n"+' "&": "&"\n'+' , "<": "<"\n'+' , ">": ">"\n'+' , \'"\': """\n'+' , "\'": "'"\n'+" }\n"+" , _MATCH_HTML = /[&<>'\"]/g;\n"+"function encode_char(c) {\n"+" return _ENCODE_HTML_RULES[c] || c;\n"+"};\n";exports.escapeXML=function(markup){return markup==undefined?"":String(markup).replace(_MATCH_HTML,encode_char)};exports.escapeXML.toString=function(){return Function.prototype.toString.call(this)+";\n"+escapeFuncStr};exports.shallowCopy=function(to,from){from=from||{};for(var p in from){to[p]=from[p]}return to};exports.cache={_data:{},set:function(key,val){this._data[key]=val},get:function(key){return this._data[key]},reset:function(){this._data={}}}},{}],3:[function(require,module,exports){},{}],4:[function(require,module,exports){(function(process){function normalizeArray(parts,allowAboveRoot){var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up--;up){parts.unshift("..")}}return parts}var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;var splitPath=function(filename){return splitPathRe.exec(filename).slice(1)};exports.resolve=function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:process.cwd();if(typeof path!=="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){continue}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=path.charAt(0)==="/"}resolvedPath=normalizeArray(filter(resolvedPath.split("/"),function(p){return!!p}),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."};exports.normalize=function(path){var isAbsolute=exports.isAbsolute(path),trailingSlash=substr(path,-1)==="/";path=normalizeArray(filter(path.split("/"),function(p){return!!p}),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path};exports.isAbsolute=function(path){return path.charAt(0)==="/"};exports.join=function(){var paths=Array.prototype.slice.call(arguments,0);return exports.normalize(filter(paths,function(p,index){if(typeof p!=="string"){throw new TypeError("Arguments to path.join must be strings")}return p}).join("/"))};exports.relative=function(from,to){from=exports.resolve(from).substr(1);to=exports.resolve(to).substr(1);function trim(arr){var start=0;for(;start<arr.length;start++){if(arr[start]!=="")break}var end=arr.length-1;for(;end>=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i<length;i++){if(fromParts[i]!==toParts[i]){samePartsLength=i;break}}var outputParts=[];for(var i=samePartsLength;i<fromParts.length;i++){outputParts.push("..")}outputParts=outputParts.concat(toParts.slice(samePartsLength));return outputParts.join("/")};exports.sep="/";exports.delimiter=":";exports.dirname=function(path){var result=splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir};exports.basename=function(path,ext){var f=splitPath(path)[2];if(ext&&f.substr(-1*ext.length)===ext){f=f.substr(0,f.length-ext.length)}return f};exports.extname=function(path){return splitPath(path)[3]};function filter(xs,f){if(xs.filter)return xs.filter(f);var res=[];for(var i=0;i<xs.length;i++){if(f(xs[i],i,xs))res.push(xs[i])}return res}var substr="ab".substr(-1)==="b"?function(str,start,len){return str.substr(start,len)}:function(str,start,len){if(start<0)start=str.length+start;return str.substr(start,len)}}).call(this,require("_process"))},{_process:5}],5:[function(require,module,exports){var process=module.exports={};var queue=[];var draining=false;function drainQueue(){if(draining){return}draining=true;var currentQueue;var len=queue.length;while(len){currentQueue=queue;queue=[];var i=-1;while(++i<len){currentQueue[i]()}len=queue.length}draining=false}process.nextTick=function(fun){queue.push(fun);if(!draining){setTimeout(drainQueue,0)}};process.title="browser";process.browser=true;process.env={};process.argv=[];process.version="";function noop(){}process.on=noop;process.addListener=noop;process.once=noop;process.off=noop;process.removeListener=noop;process.removeAllListeners=noop;process.emit=noop;process.binding=function(name){throw new Error("process.binding is not supported")};process.cwd=function(){return"/"};process.chdir=function(dir){throw new Error("process.chdir is not supported")};process.umask=function(){return 0}},{}],6:[function(require,module,exports){module.exports={name:"ejs",description:"Embedded JavaScript templates",keywords:["template","engine","ejs"],version:"2.3.3",author:"Matthew Eernisse <mde@fleegix.org> (http://fleegix.org)",contributors:["Timothy Gu <timothygu99@gmail.com> (https://timothygu.github.io)"],license:"Apache-2.0",main:"./lib/ejs.js",repository:{type:"git",url:"git://github.com/mde/ejs.git"},bugs:"https://github.com/mde/ejs/issues",homepage:"https://github.com/mde/ejs",dependencies:{},devDependencies:{browserify:"^8.0.3",istanbul:"~0.3.5",jake:"^8.0.0",jsdoc:"^3.3.0-beta1","lru-cache":"^2.5.0",mocha:"^2.1.0",rimraf:"^2.2.8","uglify-js":"^2.4.16"},engines:{node:">=0.10.0"},scripts:{test:"mocha",coverage:"istanbul cover node_modules/mocha/bin/_mocha",doc:"rimraf out && jsdoc -c jsdoc.json lib/* docs/jsdoc/*",devdoc:"rimraf out && jsdoc -p -c jsdoc.json lib/* docs/jsdoc/*"}}},{}]},{},[1]);
|
723
nodejs/node_modules/ejs/lib/ejs.js
generated
vendored
Normal file
723
nodejs/node_modules/ejs/lib/ejs.js
generated
vendored
Normal file
@@ -0,0 +1,723 @@
|
||||
/*
|
||||
* EJS Embedded JavaScript templates
|
||||
* Copyright 2112 Matthew Eernisse (mde@fleegix.org)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @file Embedded JavaScript templating engine.
|
||||
* @author Matthew Eernisse <mde@fleegix.org>
|
||||
* @author Tiancheng "Timothy" Gu <timothygu99@gmail.com>
|
||||
* @project EJS
|
||||
* @license {@link http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0}
|
||||
*/
|
||||
|
||||
/**
|
||||
* EJS internal functions.
|
||||
*
|
||||
* Technically this "module" lies in the same file as {@link module:ejs}, for
|
||||
* the sake of organization all the private functions re grouped into this
|
||||
* module.
|
||||
*
|
||||
* @module ejs-internal
|
||||
* @private
|
||||
*/
|
||||
|
||||
/**
|
||||
* Embedded JavaScript templating engine.
|
||||
*
|
||||
* @module ejs
|
||||
* @public
|
||||
*/
|
||||
|
||||
var fs = require('fs')
|
||||
, utils = require('./utils')
|
||||
, scopeOptionWarned = false
|
||||
, _VERSION_STRING = require('../package.json').version
|
||||
, _DEFAULT_DELIMITER = '%'
|
||||
, _DEFAULT_LOCALS_NAME = 'locals'
|
||||
, _REGEX_STRING = '(<%%|<%=|<%-|<%_|<%#|<%|%>|-%>|_%>)'
|
||||
, _OPTS = [ 'cache', 'filename', 'delimiter', 'scope', 'context'
|
||||
, 'debug', 'compileDebug', 'client', '_with', 'rmWhitespace'
|
||||
]
|
||||
, _TRAILING_SEMCOL = /;\s*$/
|
||||
, _BOM = /^\uFEFF/;
|
||||
|
||||
/**
|
||||
* EJS template function cache. This can be a LRU object from lru-cache NPM
|
||||
* module. By default, it is {@link module:utils.cache}, a simple in-process
|
||||
* cache that grows continuously.
|
||||
*
|
||||
* @type {Cache}
|
||||
*/
|
||||
|
||||
exports.cache = utils.cache;
|
||||
|
||||
/**
|
||||
* Name of the object containing the locals.
|
||||
*
|
||||
* This variable is overriden by {@link Options}`.localsName` if it is not
|
||||
* `undefined`.
|
||||
*
|
||||
* @type {String}
|
||||
* @public
|
||||
*/
|
||||
|
||||
exports.localsName = _DEFAULT_LOCALS_NAME;
|
||||
|
||||
/**
|
||||
* Get the path to the included file from the parent file path and the
|
||||
* specified path.
|
||||
*
|
||||
* @param {String} name specified path
|
||||
* @param {String} filename parent file path
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
exports.resolveInclude = function(name, filename) {
|
||||
var path = require('path')
|
||||
, dirname = path.dirname
|
||||
, extname = path.extname
|
||||
, resolve = path.resolve
|
||||
, includePath = resolve(dirname(filename), name)
|
||||
, ext = extname(name);
|
||||
if (!ext) {
|
||||
includePath += '.ejs';
|
||||
}
|
||||
return includePath;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the template from a string or a file, either compiled on-the-fly or
|
||||
* read from cache (if enabled), and cache the template if needed.
|
||||
*
|
||||
* If `template` is not set, the file specified in `options.filename` will be
|
||||
* read.
|
||||
*
|
||||
* If `options.cache` is true, this function reads the file from
|
||||
* `options.filename` so it must be set prior to calling this function.
|
||||
*
|
||||
* @memberof module:ejs-internal
|
||||
* @param {Options} options compilation options
|
||||
* @param {String} [template] template source
|
||||
* @return {(TemplateFunction|ClientFunction)}
|
||||
* Depending on the value of `options.client`, either type might be returned.
|
||||
* @static
|
||||
*/
|
||||
|
||||
function handleCache(options, template) {
|
||||
var fn
|
||||
, path = options.filename
|
||||
, hasTemplate = arguments.length > 1;
|
||||
|
||||
if (options.cache) {
|
||||
if (!path) {
|
||||
throw new Error('cache option requires a filename');
|
||||
}
|
||||
fn = exports.cache.get(path);
|
||||
if (fn) {
|
||||
return fn;
|
||||
}
|
||||
if (!hasTemplate) {
|
||||
template = fs.readFileSync(path).toString().replace(_BOM, '');
|
||||
}
|
||||
}
|
||||
else if (!hasTemplate) {
|
||||
// istanbul ignore if: should not happen at all
|
||||
if (!path) {
|
||||
throw new Error('Internal EJS error: no file name or template '
|
||||
+ 'provided');
|
||||
}
|
||||
template = fs.readFileSync(path).toString().replace(_BOM, '');
|
||||
}
|
||||
fn = exports.compile(template, options);
|
||||
if (options.cache) {
|
||||
exports.cache.set(path, fn);
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template function.
|
||||
*
|
||||
* If `options.cache` is `true`, then the template is cached.
|
||||
*
|
||||
* @memberof module:ejs-internal
|
||||
* @param {String} path path for the specified file
|
||||
* @param {Options} options compilation options
|
||||
* @return {(TemplateFunction|ClientFunction)}
|
||||
* Depending on the value of `options.client`, either type might be returned
|
||||
* @static
|
||||
*/
|
||||
|
||||
function includeFile(path, options) {
|
||||
var opts = utils.shallowCopy({}, options);
|
||||
if (!opts.filename) {
|
||||
throw new Error('`include` requires the \'filename\' option.');
|
||||
}
|
||||
opts.filename = exports.resolveInclude(path, opts.filename);
|
||||
return handleCache(opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JavaScript source of an included file.
|
||||
*
|
||||
* @memberof module:ejs-internal
|
||||
* @param {String} path path for the specified file
|
||||
* @param {Options} options compilation options
|
||||
* @return {String}
|
||||
* @static
|
||||
*/
|
||||
|
||||
function includeSource(path, options) {
|
||||
var opts = utils.shallowCopy({}, options)
|
||||
, includePath
|
||||
, template;
|
||||
if (!opts.filename) {
|
||||
throw new Error('`include` requires the \'filename\' option.');
|
||||
}
|
||||
includePath = exports.resolveInclude(path, opts.filename);
|
||||
template = fs.readFileSync(includePath).toString().replace(_BOM, '');
|
||||
|
||||
opts.filename = includePath;
|
||||
var templ = new Template(template, opts);
|
||||
templ.generateSource();
|
||||
return templ.source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-throw the given `err` in context to the `str` of ejs, `filename`, and
|
||||
* `lineno`.
|
||||
*
|
||||
* @implements RethrowCallback
|
||||
* @memberof module:ejs-internal
|
||||
* @param {Error} err Error object
|
||||
* @param {String} str EJS source
|
||||
* @param {String} filename file name of the EJS file
|
||||
* @param {String} lineno line number of the error
|
||||
* @static
|
||||
*/
|
||||
|
||||
function rethrow(err, str, filename, lineno){
|
||||
var lines = str.split('\n')
|
||||
, start = Math.max(lineno - 3, 0)
|
||||
, end = Math.min(lines.length, lineno + 3);
|
||||
|
||||
// Error context
|
||||
var context = lines.slice(start, end).map(function (line, i){
|
||||
var curr = i + start + 1;
|
||||
return (curr == lineno ? ' >> ' : ' ')
|
||||
+ curr
|
||||
+ '| '
|
||||
+ line;
|
||||
}).join('\n');
|
||||
|
||||
// Alter exception message
|
||||
err.path = filename;
|
||||
err.message = (filename || 'ejs') + ':'
|
||||
+ lineno + '\n'
|
||||
+ context + '\n\n'
|
||||
+ err.message;
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy properties in data object that are recognized as options to an
|
||||
* options object.
|
||||
*
|
||||
* This is used for compatibility with earlier versions of EJS and Express.js.
|
||||
*
|
||||
* @memberof module:ejs-internal
|
||||
* @param {Object} data data object
|
||||
* @param {Options} opts options object
|
||||
* @static
|
||||
*/
|
||||
|
||||
function cpOptsInData(data, opts) {
|
||||
_OPTS.forEach(function (p) {
|
||||
if (typeof data[p] != 'undefined') {
|
||||
opts[p] = data[p];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compile the given `str` of ejs into a template function.
|
||||
*
|
||||
* @param {String} template EJS template
|
||||
*
|
||||
* @param {Options} opts compilation options
|
||||
*
|
||||
* @return {(TemplateFunction|ClientFunction)}
|
||||
* Depending on the value of `opts.client`, either type might be returned.
|
||||
* @public
|
||||
*/
|
||||
|
||||
exports.compile = function compile(template, opts) {
|
||||
var templ;
|
||||
|
||||
// v1 compat
|
||||
// 'scope' is 'context'
|
||||
// FIXME: Remove this in a future version
|
||||
if (opts && opts.scope) {
|
||||
if (!scopeOptionWarned){
|
||||
console.warn('`scope` option is deprecated and will be removed in EJS 3');
|
||||
scopeOptionWarned = true;
|
||||
}
|
||||
if (!opts.context) {
|
||||
opts.context = opts.scope;
|
||||
}
|
||||
delete opts.scope;
|
||||
}
|
||||
templ = new Template(template, opts);
|
||||
return templ.compile();
|
||||
};
|
||||
|
||||
/**
|
||||
* Render the given `template` of ejs.
|
||||
*
|
||||
* If you would like to include options but not data, you need to explicitly
|
||||
* call this function with `data` being an empty object or `null`.
|
||||
*
|
||||
* @param {String} template EJS template
|
||||
* @param {Object} [data={}] template data
|
||||
* @param {Options} [opts={}] compilation and rendering options
|
||||
* @return {String}
|
||||
* @public
|
||||
*/
|
||||
|
||||
exports.render = function (template, data, opts) {
|
||||
data = data || {};
|
||||
opts = opts || {};
|
||||
var fn;
|
||||
|
||||
// No options object -- if there are optiony names
|
||||
// in the data, copy them to options
|
||||
if (arguments.length == 2) {
|
||||
cpOptsInData(data, opts);
|
||||
}
|
||||
|
||||
return handleCache(opts, template)(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Render an EJS file at the given `path` and callback `cb(err, str)`.
|
||||
*
|
||||
* If you would like to include options but not data, you need to explicitly
|
||||
* call this function with `data` being an empty object or `null`.
|
||||
*
|
||||
* @param {String} path path to the EJS file
|
||||
* @param {Object} [data={}] template data
|
||||
* @param {Options} [opts={}] compilation and rendering options
|
||||
* @param {RenderFileCallback} cb callback
|
||||
* @public
|
||||
*/
|
||||
|
||||
exports.renderFile = function () {
|
||||
var args = Array.prototype.slice.call(arguments)
|
||||
, path = args.shift()
|
||||
, cb = args.pop()
|
||||
, data = args.shift() || {}
|
||||
, opts = args.pop() || {}
|
||||
, result;
|
||||
|
||||
// Don't pollute passed in opts obj with new vals
|
||||
opts = utils.shallowCopy({}, opts);
|
||||
|
||||
// No options object -- if there are optiony names
|
||||
// in the data, copy them to options
|
||||
if (arguments.length == 3) {
|
||||
cpOptsInData(data, opts);
|
||||
}
|
||||
opts.filename = path;
|
||||
|
||||
try {
|
||||
result = handleCache(opts)(data);
|
||||
}
|
||||
catch(err) {
|
||||
return cb(err);
|
||||
}
|
||||
return cb(null, result);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear intermediate JavaScript cache. Calls {@link Cache#reset}.
|
||||
* @public
|
||||
*/
|
||||
|
||||
exports.clearCache = function () {
|
||||
exports.cache.reset();
|
||||
};
|
||||
|
||||
function Template(text, opts) {
|
||||
opts = opts || {};
|
||||
var options = {};
|
||||
this.templateText = text;
|
||||
this.mode = null;
|
||||
this.truncate = false;
|
||||
this.currentLine = 1;
|
||||
this.source = '';
|
||||
this.dependencies = [];
|
||||
options.client = opts.client || false;
|
||||
options.escapeFunction = opts.escape || utils.escapeXML;
|
||||
options.compileDebug = opts.compileDebug !== false;
|
||||
options.debug = !!opts.debug;
|
||||
options.filename = opts.filename;
|
||||
options.delimiter = opts.delimiter || exports.delimiter || _DEFAULT_DELIMITER;
|
||||
options._with = typeof opts._with != 'undefined' ? opts._with : true;
|
||||
options.context = opts.context;
|
||||
options.cache = opts.cache || false;
|
||||
options.rmWhitespace = opts.rmWhitespace;
|
||||
this.opts = options;
|
||||
|
||||
this.regex = this.createRegex();
|
||||
}
|
||||
|
||||
Template.modes = {
|
||||
EVAL: 'eval'
|
||||
, ESCAPED: 'escaped'
|
||||
, RAW: 'raw'
|
||||
, COMMENT: 'comment'
|
||||
, LITERAL: 'literal'
|
||||
};
|
||||
|
||||
Template.prototype = {
|
||||
createRegex: function () {
|
||||
var str = _REGEX_STRING
|
||||
, delim = utils.escapeRegExpChars(this.opts.delimiter);
|
||||
str = str.replace(/%/g, delim);
|
||||
return new RegExp(str);
|
||||
}
|
||||
|
||||
, compile: function () {
|
||||
var src
|
||||
, fn
|
||||
, opts = this.opts
|
||||
, prepended = ''
|
||||
, appended = ''
|
||||
, escape = opts.escapeFunction;
|
||||
|
||||
if (opts.rmWhitespace) {
|
||||
// Have to use two separate replace here as `^` and `$` operators don't
|
||||
// work well with `\r`.
|
||||
this.templateText =
|
||||
this.templateText.replace(/\r/g, '').replace(/^\s+|\s+$/gm, '');
|
||||
}
|
||||
|
||||
// Slurp spaces and tabs before <%_ and after _%>
|
||||
this.templateText =
|
||||
this.templateText.replace(/[ \t]*<%_/gm, '<%_').replace(/_%>[ \t]*/gm, '_%>');
|
||||
|
||||
if (!this.source) {
|
||||
this.generateSource();
|
||||
prepended += ' var __output = [], __append = __output.push.bind(__output);' + '\n';
|
||||
if (opts._with !== false) {
|
||||
prepended += ' with (' + exports.localsName + ' || {}) {' + '\n';
|
||||
appended += ' }' + '\n';
|
||||
}
|
||||
appended += ' return __output.join("");' + '\n';
|
||||
this.source = prepended + this.source + appended;
|
||||
}
|
||||
|
||||
if (opts.compileDebug) {
|
||||
src = 'var __line = 1' + '\n'
|
||||
+ ' , __lines = ' + JSON.stringify(this.templateText) + '\n'
|
||||
+ ' , __filename = ' + (opts.filename ?
|
||||
JSON.stringify(opts.filename) : 'undefined') + ';' + '\n'
|
||||
+ 'try {' + '\n'
|
||||
+ this.source
|
||||
+ '} catch (e) {' + '\n'
|
||||
+ ' rethrow(e, __lines, __filename, __line);' + '\n'
|
||||
+ '}' + '\n';
|
||||
}
|
||||
else {
|
||||
src = this.source;
|
||||
}
|
||||
|
||||
if (opts.debug) {
|
||||
console.log(src);
|
||||
}
|
||||
|
||||
if (opts.client) {
|
||||
src = 'escape = escape || ' + escape.toString() + ';' + '\n' + src;
|
||||
if (opts.compileDebug) {
|
||||
src = 'rethrow = rethrow || ' + rethrow.toString() + ';' + '\n' + src;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
fn = new Function(exports.localsName + ', escape, include, rethrow', src);
|
||||
}
|
||||
catch(e) {
|
||||
// istanbul ignore else
|
||||
if (e instanceof SyntaxError) {
|
||||
if (opts.filename) {
|
||||
e.message += ' in ' + opts.filename;
|
||||
}
|
||||
e.message += ' while compiling ejs';
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (opts.client) {
|
||||
fn.dependencies = this.dependencies;
|
||||
return fn;
|
||||
}
|
||||
|
||||
// Return a callable function which will execute the function
|
||||
// created by the source-code, with the passed data as locals
|
||||
// Adds a local `include` function which allows full recursive include
|
||||
var returnedFn = function (data) {
|
||||
var include = function (path, includeData) {
|
||||
var d = utils.shallowCopy({}, data);
|
||||
if (includeData) {
|
||||
d = utils.shallowCopy(d, includeData);
|
||||
}
|
||||
return includeFile(path, opts)(d);
|
||||
};
|
||||
return fn.apply(opts.context, [data || {}, escape, include, rethrow]);
|
||||
};
|
||||
returnedFn.dependencies = this.dependencies;
|
||||
return returnedFn;
|
||||
}
|
||||
|
||||
, generateSource: function () {
|
||||
var self = this
|
||||
, matches = this.parseTemplateText()
|
||||
, d = this.opts.delimiter;
|
||||
|
||||
if (matches && matches.length) {
|
||||
matches.forEach(function (line, index) {
|
||||
var opening
|
||||
, closing
|
||||
, include
|
||||
, includeOpts
|
||||
, includeSrc;
|
||||
// If this is an opening tag, check for closing tags
|
||||
// FIXME: May end up with some false positives here
|
||||
// Better to store modes as k/v with '<' + delimiter as key
|
||||
// Then this can simply check against the map
|
||||
if ( line.indexOf('<' + d) === 0 // If it is a tag
|
||||
&& line.indexOf('<' + d + d) !== 0) { // and is not escaped
|
||||
closing = matches[index + 2];
|
||||
if (!(closing == d + '>' || closing == '-' + d + '>' || closing == '_' + d + '>')) {
|
||||
throw new Error('Could not find matching close tag for "' + line + '".');
|
||||
}
|
||||
}
|
||||
// HACK: backward-compat `include` preprocessor directives
|
||||
if ((include = line.match(/^\s*include\s+(\S+)/))) {
|
||||
opening = matches[index - 1];
|
||||
// Must be in EVAL or RAW mode
|
||||
if (opening && (opening == '<' + d || opening == '<' + d + '-' || opening == '<' + d + '_')) {
|
||||
includeOpts = utils.shallowCopy({}, self.opts);
|
||||
includeSrc = includeSource(include[1], includeOpts);
|
||||
includeSrc = ' ; (function(){' + '\n' + includeSrc +
|
||||
' ; })()' + '\n';
|
||||
self.source += includeSrc;
|
||||
self.dependencies.push(exports.resolveInclude(include[1],
|
||||
includeOpts.filename));
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.scanLine(line);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
, parseTemplateText: function () {
|
||||
var str = this.templateText
|
||||
, pat = this.regex
|
||||
, result = pat.exec(str)
|
||||
, arr = []
|
||||
, firstPos
|
||||
, lastPos;
|
||||
|
||||
while (result) {
|
||||
firstPos = result.index;
|
||||
lastPos = pat.lastIndex;
|
||||
|
||||
if (firstPos !== 0) {
|
||||
arr.push(str.substring(0, firstPos));
|
||||
str = str.slice(firstPos);
|
||||
}
|
||||
|
||||
arr.push(result[0]);
|
||||
str = str.slice(result[0].length);
|
||||
result = pat.exec(str);
|
||||
}
|
||||
|
||||
if (str) {
|
||||
arr.push(str);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
, scanLine: function (line) {
|
||||
var self = this
|
||||
, d = this.opts.delimiter
|
||||
, newLineCount = 0;
|
||||
|
||||
function _addOutput() {
|
||||
if (self.truncate) {
|
||||
line = line.replace('\n', '');
|
||||
self.truncate = false;
|
||||
}
|
||||
else if (self.opts.rmWhitespace) {
|
||||
// Gotta me more careful here.
|
||||
// .replace(/^(\s*)\n/, '$1') might be more appropriate here but as
|
||||
// rmWhitespace already removes trailing spaces anyway so meh.
|
||||
line = line.replace(/^\n/, '');
|
||||
}
|
||||
if (!line) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Preserve literal slashes
|
||||
line = line.replace(/\\/g, '\\\\');
|
||||
|
||||
// Convert linebreaks
|
||||
line = line.replace(/\n/g, '\\n');
|
||||
line = line.replace(/\r/g, '\\r');
|
||||
|
||||
// Escape double-quotes
|
||||
// - this will be the delimiter during execution
|
||||
line = line.replace(/"/g, '\\"');
|
||||
self.source += ' ; __append("' + line + '")' + '\n';
|
||||
}
|
||||
|
||||
newLineCount = (line.split('\n').length - 1);
|
||||
|
||||
switch (line) {
|
||||
case '<' + d:
|
||||
case '<' + d + '_':
|
||||
this.mode = Template.modes.EVAL;
|
||||
break;
|
||||
case '<' + d + '=':
|
||||
this.mode = Template.modes.ESCAPED;
|
||||
break;
|
||||
case '<' + d + '-':
|
||||
this.mode = Template.modes.RAW;
|
||||
break;
|
||||
case '<' + d + '#':
|
||||
this.mode = Template.modes.COMMENT;
|
||||
break;
|
||||
case '<' + d + d:
|
||||
this.mode = Template.modes.LITERAL;
|
||||
this.source += ' ; __append("' + line.replace('<' + d + d, '<' + d) + '")' + '\n';
|
||||
break;
|
||||
case d + '>':
|
||||
case '-' + d + '>':
|
||||
case '_' + d + '>':
|
||||
if (this.mode == Template.modes.LITERAL) {
|
||||
_addOutput();
|
||||
}
|
||||
|
||||
this.mode = null;
|
||||
this.truncate = line.indexOf('-') === 0 || line.indexOf('_') === 0;
|
||||
break;
|
||||
default:
|
||||
// In script mode, depends on type of tag
|
||||
if (this.mode) {
|
||||
// If '//' is found without a line break, add a line break.
|
||||
switch (this.mode) {
|
||||
case Template.modes.EVAL:
|
||||
case Template.modes.ESCAPED:
|
||||
case Template.modes.RAW:
|
||||
if (line.lastIndexOf('//') > line.lastIndexOf('\n')) {
|
||||
line += '\n';
|
||||
}
|
||||
}
|
||||
switch (this.mode) {
|
||||
// Just executing code
|
||||
case Template.modes.EVAL:
|
||||
this.source += ' ; ' + line + '\n';
|
||||
break;
|
||||
// Exec, esc, and output
|
||||
case Template.modes.ESCAPED:
|
||||
this.source += ' ; __append(escape(' +
|
||||
line.replace(_TRAILING_SEMCOL, '').trim() + '))' + '\n';
|
||||
break;
|
||||
// Exec and output
|
||||
case Template.modes.RAW:
|
||||
this.source += ' ; __append(' +
|
||||
line.replace(_TRAILING_SEMCOL, '').trim() + ')' + '\n';
|
||||
break;
|
||||
case Template.modes.COMMENT:
|
||||
// Do nothing
|
||||
break;
|
||||
// Literal <%% mode, append as raw output
|
||||
case Template.modes.LITERAL:
|
||||
_addOutput();
|
||||
break;
|
||||
}
|
||||
}
|
||||
// In string mode, just add the output
|
||||
else {
|
||||
_addOutput();
|
||||
}
|
||||
}
|
||||
|
||||
if (self.opts.compileDebug && newLineCount) {
|
||||
this.currentLine += newLineCount;
|
||||
this.source += ' ; __line = ' + this.currentLine + '\n';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Express.js support.
|
||||
*
|
||||
* This is an alias for {@link module:ejs.renderFile}, in order to support
|
||||
* Express.js out-of-the-box.
|
||||
*
|
||||
* @func
|
||||
*/
|
||||
|
||||
exports.__express = exports.renderFile;
|
||||
|
||||
// Add require support
|
||||
/* istanbul ignore else */
|
||||
if (require.extensions) {
|
||||
require.extensions['.ejs'] = function (module, filename) {
|
||||
filename = filename || /* istanbul ignore next */ module.filename;
|
||||
var options = {
|
||||
filename: filename
|
||||
, client: true
|
||||
}
|
||||
, template = fs.readFileSync(filename).toString()
|
||||
, fn = exports.compile(template, options);
|
||||
module._compile('module.exports = ' + fn.toString() + ';', filename);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Version of EJS.
|
||||
*
|
||||
* @readonly
|
||||
* @type {String}
|
||||
* @public
|
||||
*/
|
||||
|
||||
exports.VERSION = _VERSION_STRING;
|
||||
|
||||
/* istanbul ignore if */
|
||||
if (typeof window != 'undefined') {
|
||||
window.ejs = exports;
|
||||
}
|
141
nodejs/node_modules/ejs/lib/utils.js
generated
vendored
Normal file
141
nodejs/node_modules/ejs/lib/utils.js
generated
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* EJS Embedded JavaScript templates
|
||||
* Copyright 2112 Matthew Eernisse (mde@fleegix.org)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Private utility functions
|
||||
* @module utils
|
||||
* @private
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var regExpChars = /[|\\{}()[\]^$+*?.]/g;
|
||||
|
||||
/**
|
||||
* Escape characters reserved in regular expressions.
|
||||
*
|
||||
* If `string` is `undefined` or `null`, the empty string is returned.
|
||||
*
|
||||
* @param {String} string Input string
|
||||
* @return {String} Escaped string
|
||||
* @static
|
||||
* @private
|
||||
*/
|
||||
exports.escapeRegExpChars = function (string) {
|
||||
// istanbul ignore if
|
||||
if (!string) {
|
||||
return '';
|
||||
}
|
||||
return String(string).replace(regExpChars, '\\$&');
|
||||
};
|
||||
|
||||
var _ENCODE_HTML_RULES = {
|
||||
'&': '&'
|
||||
, '<': '<'
|
||||
, '>': '>'
|
||||
, '"': '"'
|
||||
, "'": '''
|
||||
}
|
||||
, _MATCH_HTML = /[&<>\'"]/g;
|
||||
|
||||
function encode_char(c) {
|
||||
return _ENCODE_HTML_RULES[c] || c;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stringified version of constants used by {@link module:utils.escapeXML}.
|
||||
*
|
||||
* It is used in the process of generating {@link ClientFunction}s.
|
||||
*
|
||||
* @readonly
|
||||
* @type {String}
|
||||
*/
|
||||
|
||||
var escapeFuncStr =
|
||||
'var _ENCODE_HTML_RULES = {\n'
|
||||
+ ' "&": "&"\n'
|
||||
+ ' , "<": "<"\n'
|
||||
+ ' , ">": ">"\n'
|
||||
+ ' , \'"\': """\n'
|
||||
+ ' , "\'": "'"\n'
|
||||
+ ' }\n'
|
||||
+ ' , _MATCH_HTML = /[&<>\'"]/g;\n'
|
||||
+ 'function encode_char(c) {\n'
|
||||
+ ' return _ENCODE_HTML_RULES[c] || c;\n'
|
||||
+ '};\n';
|
||||
|
||||
/**
|
||||
* Escape characters reserved in XML.
|
||||
*
|
||||
* If `markup` is `undefined` or `null`, the empty string is returned.
|
||||
*
|
||||
* @implements {EscapeCallback}
|
||||
* @param {String} markup Input string
|
||||
* @return {String} Escaped string
|
||||
* @static
|
||||
* @private
|
||||
*/
|
||||
|
||||
exports.escapeXML = function (markup) {
|
||||
return markup == undefined
|
||||
? ''
|
||||
: String(markup)
|
||||
.replace(_MATCH_HTML, encode_char);
|
||||
};
|
||||
exports.escapeXML.toString = function () {
|
||||
return Function.prototype.toString.call(this) + ';\n' + escapeFuncStr
|
||||
};
|
||||
|
||||
/**
|
||||
* Copy all properties from one object to another, in a shallow fashion.
|
||||
*
|
||||
* @param {Object} to Destination object
|
||||
* @param {Object} from Source object
|
||||
* @return {Object} Destination object
|
||||
* @static
|
||||
* @private
|
||||
*/
|
||||
exports.shallowCopy = function (to, from) {
|
||||
from = from || {};
|
||||
for (var p in from) {
|
||||
to[p] = from[p];
|
||||
}
|
||||
return to;
|
||||
};
|
||||
|
||||
/**
|
||||
* Simple in-process cache implementation. Does not implement limits of any
|
||||
* sort.
|
||||
*
|
||||
* @implements Cache
|
||||
* @static
|
||||
* @private
|
||||
*/
|
||||
exports.cache = {
|
||||
_data: {},
|
||||
set: function (key, val) {
|
||||
this._data[key] = val;
|
||||
},
|
||||
get: function (key) {
|
||||
return this._data[key];
|
||||
},
|
||||
reset: function () {
|
||||
this._data = {};
|
||||
}
|
||||
};
|
||||
|
60
nodejs/node_modules/ejs/package.json
generated
vendored
Normal file
60
nodejs/node_modules/ejs/package.json
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "ejs",
|
||||
"description": "Embedded JavaScript templates",
|
||||
"keywords": [
|
||||
"template",
|
||||
"engine",
|
||||
"ejs"
|
||||
],
|
||||
"version": "2.3.4",
|
||||
"author": {
|
||||
"name": "Matthew Eernisse",
|
||||
"email": "mde@fleegix.org",
|
||||
"url": "http://fleegix.org"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Timothy Gu",
|
||||
"email": "timothygu99@gmail.com",
|
||||
"url": "https://timothygu.github.io"
|
||||
}
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"main": "./lib/ejs.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/mde/ejs.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/mde/ejs/issues"
|
||||
},
|
||||
"homepage": "https://github.com/mde/ejs",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"browserify": "^8.0.3",
|
||||
"istanbul": "~0.3.5",
|
||||
"jake": "^8.0.0",
|
||||
"jsdoc": "^3.3.0-beta1",
|
||||
"lru-cache": "^2.5.0",
|
||||
"mocha": "^2.1.0",
|
||||
"rimraf": "^2.2.8",
|
||||
"uglify-js": "^2.4.16"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha",
|
||||
"coverage": "istanbul cover node_modules/mocha/bin/_mocha",
|
||||
"doc": "rimraf out && jsdoc -c jsdoc.json lib/* docs/jsdoc/*",
|
||||
"devdoc": "rimraf out && jsdoc -p -c jsdoc.json lib/* docs/jsdoc/*"
|
||||
},
|
||||
"readme": "# EJS\n\nEmbedded JavaScript templates\n\n[](https://travis-ci.org/mde/ejs)\n[](https://david-dm.org/mde/ejs#info=devDependencies)\n\n## Installation\n\n```bash\n$ npm install ejs\n```\n\n## Features\n\n * Control flow with `<% %>`\n * Escaped output with `<%= %>`\n * Unescaped raw output with `<%- %>`\n * Trim-mode ('newline slurping') with `-%>` ending tag\n * Custom delimiters (e.g., use '<? ?>' instead of '<% %>')\n * Includes\n * Client-side support\n * Static caching of intermediate JavaScript\n * Static caching of templates\n * Complies with the [Express](http://expressjs.com) view system\n\n## Example\n\n```html\n<% if (user) { %>\n <h2><%= user.name %></h2>\n<% } %>\n```\n\n## Usage\n\n```javascript\nvar template = ejs.compile(str, options);\ntemplate(data);\n// => Rendered HTML string\n\nejs.render(str, data, options);\n// => Rendered HTML string\n```\n\nYou can also use the shortcut `ejs.render(dataAndOptions);` where you pass\neverything in a single object. In that case, you'll end up with local variables\nfor all the passed options.\n\n## Options\n\n - `cache` Compiled functions are cached, requires `filename`\n - `filename` Used by `cache` to key caches, and for includes\n - `context` Function execution context\n - `compileDebug` When `false` no debug instrumentation is compiled\n - `client` Returns standalone compiled function\n - `delimiter` Character to use with angle brackets for open/close\n - `debug` Output generated function body\n - `_with` Whether or not to use `with() {}` constructs. If `false` then the locals will be stored in the `locals` object.\n - `rmWhitespace` Remove all safe-to-remove whitespace, including leading\n and trailing whitespace. It also enables a safer version of `-%>` line\n slurping for all scriptlet tags (it does not strip new lines of tags in\n the middle of a line).\n\n## Tags\n\n - `<%` 'Scriptlet' tag, for control-flow, no output\n - `<%=` Outputs the value into the template (HTML escaped)\n - `<%-` Outputs the unescaped value into the template\n - `<%#` Comment tag, no execution, no output\n - `<%%` Outputs a literal '<%'\n - `%>` Plain ending tag\n - `-%>` Trim-mode ('newline slurp') tag, trims following newline\n\n## Includes\n\nIncludes either have to be an absolute path, or, if not, are assumed as\nrelative to the template with the `include` call. (This requires the\n`filename` option.) For example if you are including `./views/user/show.ejs`\nfrom `./views/users.ejs` you would use `<%- include('user/show') %>`.\n\nYou'll likely want to use the raw output tag (`<%-`) with your include to avoid\ndouble-escaping the HTML output.\n\n```html\n<ul>\n <% users.forEach(function(user){ %>\n <%- include('user/show', {user: user}) %>\n <% }); %>\n</ul>\n```\n\nIncludes are inserted at runtime, so you can use variables for the path in the\n`include` call (for example `<%- include(somePath) %>`). Variables in your\ntop-level data object are available to all your includes, but local variables\nneed to be passed down.\n\nNOTE: Include preprocessor directives (`<% include user/show %>`) are\nstill supported.\n\n## Custom delimiters\n\nCustom delimiters can be applied on a per-template basis, or globally:\n\n```javascript\nvar ejs = require('ejs'),\n users = ['geddy', 'neil', 'alex'];\n\n// Just one template\nejs.render('<?= users.join(\" | \"); ?>', {users: users}, {delimiter: '?'});\n// => 'geddy | neil | alex'\n\n// Or globally\nejs.delimiter = '$';\nejs.render('<$= users.join(\" | \"); $>', {users: users});\n// => 'geddy | neil | alex'\n```\n\n## Caching\n\nEJS ships with a basic in-process cache for caching the intermediate JavaScript\nfunctions used to render templates. It's easy to plug in LRU caching using\nNode's `lru-cache` library:\n\n```javascript\nvar ejs = require('ejs')\n , LRU = require('lru-cache');\nejs.cache = LRU(100); // LRU cache with 100-item limit\n```\n\nIf you want to clear the EJS cache, call `ejs.clearCache`. If you're using the\nLRU cache and need a different limit, simple reset `ejs.cache` to a new instance\nof the LRU.\n\n## Layouts\n\nEJS does not specifically support blocks, but layouts can be implemented by\nincluding headers and footers, like so:\n\n\n```html\n<%- include('header') -%>\n<h1>\n Title\n</h1>\n<p>\n My page\n</p>\n<%- include('footer') -%>\n```\n\n## Client-side support\n\nGo to the [Latest Release](https://github.com/mde/ejs/releases/latest), download\n`./ejs.js` or `./ejs.min.js`.\n\nInclude one of these on your page, and `ejs.render(str)`.\n\n## Related projects\n\nThere are a number of implementations of EJS:\n\n * TJ's implementation, the v1 of this library: https://github.com/tj/ejs\n * Jupiter Consulting's EJS: http://www.embeddedjs.com/\n * EJS Embedded JavaScript Framework on Google Code: https://code.google.com/p/embeddedjavascript/\n * Sam Stephenson's Ruby implementation: https://rubygems.org/gems/ejs\n * Erubis, an ERB implementation which also runs JavaScript: http://www.kuwata-lab.com/erubis/users-guide.04.html#lang-javascript\n\n## License\n\nLicensed under the Apache License, Version 2.0\n(<http://www.apache.org/licenses/LICENSE-2.0>)\n\n- - -\nEJS Embedded JavaScript templates copyright 2112\nmde@fleegix.org.\n\n\n",
|
||||
"readmeFilename": "README.md",
|
||||
"_id": "ejs@2.3.4",
|
||||
"dist": {
|
||||
"shasum": "f23675dcdcda36d7337cfada2e2303d01e596238"
|
||||
},
|
||||
"_from": "ejs@>= 0.0.1",
|
||||
"_resolved": "https://registry.npmjs.org/ejs/-/ejs-2.3.4.tgz"
|
||||
}
|
866
nodejs/node_modules/ejs/test/ejs.js
generated
vendored
Normal file
866
nodejs/node_modules/ejs/test/ejs.js
generated
vendored
Normal file
@@ -0,0 +1,866 @@
|
||||
/* jshint mocha: true */
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var ejs = require('..')
|
||||
, fs = require('fs')
|
||||
, read = fs.readFileSync
|
||||
, assert = require('assert')
|
||||
, path = require('path')
|
||||
, LRU = require('lru-cache');
|
||||
|
||||
try {
|
||||
fs.mkdirSync(__dirname + '/tmp');
|
||||
} catch (ex) {
|
||||
if (ex.code !== 'EEXIST') {
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
// From https://gist.github.com/pguillory/729616
|
||||
function hook_stdio(stream, callback) {
|
||||
var old_write = stream.write;
|
||||
|
||||
stream.write = (function() {
|
||||
return function(string, encoding, fd) {
|
||||
callback(string, encoding, fd);
|
||||
};
|
||||
})(stream.write);
|
||||
|
||||
return function() {
|
||||
stream.write = old_write;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Load fixture `name`.
|
||||
*/
|
||||
|
||||
function fixture(name) {
|
||||
return read('test/fixtures/' + name, 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* User fixtures.
|
||||
*/
|
||||
|
||||
var users = [];
|
||||
users.push({name: 'geddy'});
|
||||
users.push({name: 'neil'});
|
||||
users.push({name: 'alex'});
|
||||
|
||||
suite('ejs.compile(str, options)', function () {
|
||||
test('compile to a function', function () {
|
||||
var fn = ejs.compile('<p>yay</p>');
|
||||
assert.equal(fn(), '<p>yay</p>');
|
||||
});
|
||||
|
||||
test('empty input works', function () {
|
||||
var fn = ejs.compile('');
|
||||
assert.equal(fn(), '');
|
||||
});
|
||||
|
||||
test('throw if there are syntax errors', function () {
|
||||
try {
|
||||
ejs.compile(fixture('fail.ejs'));
|
||||
}
|
||||
catch (err) {
|
||||
assert.ok(err.message.indexOf('compiling ejs') > -1);
|
||||
|
||||
try {
|
||||
ejs.compile(fixture('fail.ejs'), {filename: 'fail.ejs'});
|
||||
}
|
||||
catch (err) {
|
||||
assert.ok(err.message.indexOf('fail.ejs') > -1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new Error('no error reported when there should be');
|
||||
});
|
||||
|
||||
test('allow customizing delimiter local var', function () {
|
||||
var fn;
|
||||
fn = ejs.compile('<p><?= name ?></p>', {delimiter: '?'});
|
||||
assert.equal(fn({name: 'geddy'}), '<p>geddy</p>');
|
||||
|
||||
fn = ejs.compile('<p><:= name :></p>', {delimiter: ':'});
|
||||
assert.equal(fn({name: 'geddy'}), '<p>geddy</p>');
|
||||
|
||||
fn = ejs.compile('<p><$= name $></p>', {delimiter: '$'});
|
||||
assert.equal(fn({name: 'geddy'}), '<p>geddy</p>');
|
||||
});
|
||||
|
||||
test('default to using ejs.delimiter', function () {
|
||||
var fn;
|
||||
ejs.delimiter = '&';
|
||||
fn = ejs.compile('<p><&= name &></p>');
|
||||
assert.equal(fn({name: 'geddy'}), '<p>geddy</p>');
|
||||
|
||||
fn = ejs.compile('<p><|= name |></p>', {delimiter: '|'});
|
||||
assert.equal(fn({name: 'geddy'}), '<p>geddy</p>');
|
||||
delete ejs.delimiter;
|
||||
});
|
||||
|
||||
test('have a working client option', function () {
|
||||
var fn
|
||||
, str
|
||||
, preFn;
|
||||
fn = ejs.compile('<p><%= foo %></p>', {client: true});
|
||||
str = fn.toString();
|
||||
if (!process.env.running_under_istanbul) {
|
||||
eval('var preFn = ' + str);
|
||||
assert.equal(preFn({foo: 'bar'}), '<p>bar</p>');
|
||||
}
|
||||
});
|
||||
|
||||
test('support client mode without locals', function () {
|
||||
var fn
|
||||
, str
|
||||
, preFn;
|
||||
fn = ejs.compile('<p><%= "foo" %></p>', {client: true});
|
||||
str = fn.toString();
|
||||
if (!process.env.running_under_istanbul) {
|
||||
eval('var preFn = ' + str);
|
||||
assert.equal(preFn(), '<p>foo</p>');
|
||||
}
|
||||
});
|
||||
|
||||
test('not include rethrow() in client mode if compileDebug is false', function () {
|
||||
var fn = ejs.compile('<p><%= "foo" %></p>', {
|
||||
client: true
|
||||
, compileDebug: false
|
||||
});
|
||||
// There could be a `rethrow` in the function declaration
|
||||
assert((fn.toString().match(/rethrow/g) || []).length <= 1);
|
||||
});
|
||||
});
|
||||
|
||||
suite('ejs.render(str, data, opts)', function () {
|
||||
test('render the template', function () {
|
||||
assert.equal(ejs.render('<p>yay</p>'), '<p>yay</p>');
|
||||
});
|
||||
|
||||
test('empty input works', function () {
|
||||
assert.equal(ejs.render(''), '');
|
||||
});
|
||||
|
||||
test('undefined renders nothing escaped', function () {
|
||||
assert.equal(ejs.render('<%= undefined %>'), '');
|
||||
});
|
||||
|
||||
test('undefined renders nothing raw', function () {
|
||||
assert.equal(ejs.render('<%- undefined %>'), '');
|
||||
});
|
||||
|
||||
test('null renders nothing escaped', function () {
|
||||
assert.equal(ejs.render('<%= null %>'), '');
|
||||
});
|
||||
|
||||
test('null renders nothing raw', function () {
|
||||
assert.equal(ejs.render('<%- null %>'), '');
|
||||
});
|
||||
|
||||
test('zero-value data item renders something escaped', function () {
|
||||
assert.equal(ejs.render('<%= 0 %>'), '0');
|
||||
});
|
||||
|
||||
test('zero-value data object renders something raw', function () {
|
||||
assert.equal(ejs.render('<%- 0 %>'), '0');
|
||||
});
|
||||
|
||||
test('accept locals', function () {
|
||||
assert.equal(ejs.render('<p><%= name %></p>', {name: 'geddy'}),
|
||||
'<p>geddy</p>');
|
||||
});
|
||||
|
||||
test('accept locals without using with() {}', function () {
|
||||
assert.equal(ejs.render('<p><%= locals.name %></p>', {name: 'geddy'},
|
||||
{_with: false}),
|
||||
'<p>geddy</p>');
|
||||
assert.throws(function() {
|
||||
ejs.render('<p><%= name %></p>', {name: 'geddy'},
|
||||
{_with: false});
|
||||
}, /name is not defined/);
|
||||
});
|
||||
|
||||
test('accept custom name for locals', function () {
|
||||
ejs.localsName = 'it';
|
||||
assert.equal(ejs.render('<p><%= it.name %></p>', {name: 'geddy'},
|
||||
{_with: false}),
|
||||
'<p>geddy</p>');
|
||||
assert.throws(function() {
|
||||
ejs.render('<p><%= name %></p>', {name: 'geddy'},
|
||||
{_with: false});
|
||||
}, /name is not defined/);
|
||||
ejs.localsName = 'locals';
|
||||
});
|
||||
|
||||
test('support caching', function () {
|
||||
var file = __dirname + '/tmp/render.ejs'
|
||||
, options = {cache: true, filename: file}
|
||||
, out = ejs.render('<p>Old</p>', {}, options)
|
||||
, expected = '<p>Old</p>';
|
||||
assert.equal(out, expected);
|
||||
// Assert no change, still in cache
|
||||
out = ejs.render('<p>New</p>', {}, options);
|
||||
assert.equal(out, expected);
|
||||
});
|
||||
|
||||
test('support LRU caching', function () {
|
||||
var oldCache = ejs.cache
|
||||
, file = __dirname + '/tmp/render.ejs'
|
||||
, options = {cache: true, filename: file}
|
||||
, out
|
||||
, expected = '<p>Old</p>';
|
||||
|
||||
// Switch to LRU
|
||||
ejs.cache = LRU();
|
||||
|
||||
out = ejs.render('<p>Old</p>', {}, options);
|
||||
assert.equal(out, expected);
|
||||
// Assert no change, still in cache
|
||||
out = ejs.render('<p>New</p>', {}, options);
|
||||
assert.equal(out, expected);
|
||||
|
||||
// Restore system cache
|
||||
ejs.cache = oldCache;
|
||||
});
|
||||
|
||||
test('opts.context', function () {
|
||||
var ctxt = {foo: 'FOO'}
|
||||
, out = ejs.render('<%= this.foo %>', {}, {context: ctxt});
|
||||
assert.equal(out, ctxt.foo);
|
||||
});
|
||||
});
|
||||
|
||||
suite('ejs.renderFile(path, [data], [options], fn)', function () {
|
||||
test('render a file', function(done) {
|
||||
ejs.renderFile('test/fixtures/para.ejs', function(err, html) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
assert.equal(html, '<p>hey</p>\n');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('accept locals', function(done) {
|
||||
var data = {name: 'fonebone'}
|
||||
, options = {delimiter: '$'};
|
||||
ejs.renderFile('test/fixtures/user.ejs', data, options, function(err, html) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
assert.equal(html, '<h1>fonebone</h1>\n');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
test('accept locals without using with() {}', function(done) {
|
||||
var data = {name: 'fonebone'}
|
||||
, options = {delimiter: '$', _with: false}
|
||||
, doneCount = 0;
|
||||
ejs.renderFile('test/fixtures/user-no-with.ejs', data, options,
|
||||
function(err, html) {
|
||||
if (err) {
|
||||
if (doneCount === 2) {
|
||||
return;
|
||||
}
|
||||
doneCount = 2;
|
||||
return done(err);
|
||||
}
|
||||
assert.equal(html, '<h1>fonebone</h1>\n');
|
||||
doneCount++;
|
||||
if (doneCount === 2) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
ejs.renderFile('test/fixtures/user.ejs', data, options, function(err) {
|
||||
if (!err) {
|
||||
if (doneCount === 2) {
|
||||
return;
|
||||
}
|
||||
doneCount = 2;
|
||||
return done(new Error('error not thrown'));
|
||||
}
|
||||
doneCount++;
|
||||
if (doneCount === 2) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('not catch err thrown by callback', function(done) {
|
||||
var data = {name: 'fonebone'}
|
||||
, options = {delimiter: '$'}
|
||||
, counter = 0;
|
||||
|
||||
var d = require('domain').create();
|
||||
d.on('error', function (err) {
|
||||
assert.equal(counter, 1);
|
||||
assert.equal(err.message, 'Exception in callback');
|
||||
done();
|
||||
});
|
||||
d.run(function () {
|
||||
// process.nextTick() needed to work around mochajs/mocha#513
|
||||
//
|
||||
// tl;dr: mocha doesn't support synchronous exception throwing in
|
||||
// domains. Have to make it async. Ticket closed because: "domains are
|
||||
// deprecated :D"
|
||||
process.nextTick(function () {
|
||||
ejs.renderFile('test/fixtures/user.ejs', data, options,
|
||||
function(err) {
|
||||
counter++;
|
||||
if (err) {
|
||||
assert.notEqual(err.message, 'Exception in callback');
|
||||
return done(err);
|
||||
}
|
||||
throw new Error('Exception in callback');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('support caching', function (done) {
|
||||
var expected = '<p>Old</p>'
|
||||
, file = __dirname + '/tmp/renderFile.ejs'
|
||||
, options = {cache: true};
|
||||
fs.writeFileSync(file, '<p>Old</p>');
|
||||
|
||||
ejs.renderFile(file, {}, options, function (err, out) {
|
||||
if (err) {
|
||||
done(err);
|
||||
}
|
||||
fs.writeFileSync(file, '<p>New</p>');
|
||||
assert.equal(out, expected);
|
||||
|
||||
ejs.renderFile(file, {}, options, function (err, out) {
|
||||
if (err) {
|
||||
done(err);
|
||||
}
|
||||
// Assert no change, still in cache
|
||||
assert.equal(out, expected);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('opts.context', function (done) {
|
||||
var ctxt = {foo: 'FOO'};
|
||||
ejs.renderFile('test/fixtures/with-context.ejs', {},
|
||||
{context: ctxt}, function(err, html) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
assert.equal(html, ctxt.foo + '\n');
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
suite('cache specific', function () {
|
||||
test('`clearCache` work properly', function () {
|
||||
var expected = '<p>Old</p>'
|
||||
, file = __dirname + '/tmp/clearCache.ejs'
|
||||
, options = {cache: true, filename: file}
|
||||
, out = ejs.render('<p>Old</p>', {}, options);
|
||||
assert.equal(out, expected);
|
||||
|
||||
ejs.clearCache();
|
||||
|
||||
expected = '<p>New</p>';
|
||||
out = ejs.render('<p>New</p>', {}, options);
|
||||
assert.equal(out, expected);
|
||||
});
|
||||
|
||||
test('`clearCache` work properly, LRU', function () {
|
||||
var expected = '<p>Old</p>'
|
||||
, oldCache = ejs.cache
|
||||
, file = __dirname + '/tmp/clearCache.ejs'
|
||||
, options = {cache: true, filename: file}
|
||||
, out;
|
||||
|
||||
ejs.cache = LRU();
|
||||
|
||||
out = ejs.render('<p>Old</p>', {}, options);
|
||||
assert.equal(out, expected);
|
||||
ejs.clearCache();
|
||||
expected = '<p>New</p>';
|
||||
out = ejs.render('<p>New</p>', {}, options);
|
||||
assert.equal(out, expected);
|
||||
|
||||
ejs.cache = oldCache;
|
||||
});
|
||||
|
||||
test('LRU with cache-size 1', function () {
|
||||
var oldCache = ejs.cache
|
||||
, options
|
||||
, out
|
||||
, expected
|
||||
, file;
|
||||
|
||||
ejs.cache = LRU(1);
|
||||
|
||||
file = __dirname + '/tmp/render1.ejs';
|
||||
options = {cache: true, filename: file};
|
||||
out = ejs.render('<p>File1</p>', {}, options);
|
||||
expected = '<p>File1</p>';
|
||||
assert.equal(out, expected);
|
||||
|
||||
// Same filename, different template, but output
|
||||
// should be the same because cache
|
||||
file = __dirname + '/tmp/render1.ejs';
|
||||
options = {cache: true, filename: file};
|
||||
out = ejs.render('<p>ChangedFile1</p>', {}, options);
|
||||
expected = '<p>File1</p>';
|
||||
assert.equal(out, expected);
|
||||
|
||||
// Different filiename -- output should be different,
|
||||
// and previous cache-entry should be evicted
|
||||
file = __dirname + '/tmp/render2.ejs';
|
||||
options = {cache: true, filename: file};
|
||||
out = ejs.render('<p>File2</p>', {}, options);
|
||||
expected = '<p>File2</p>';
|
||||
assert.equal(out, expected);
|
||||
|
||||
// Entry with first filename should now be out of cache,
|
||||
// results should be different
|
||||
file = __dirname + '/tmp/render1.ejs';
|
||||
options = {cache: true, filename: file};
|
||||
out = ejs.render('<p>ChangedFile1</p>', {}, options);
|
||||
expected = '<p>ChangedFile1</p>';
|
||||
assert.equal(out, expected);
|
||||
|
||||
ejs.cache = oldCache;
|
||||
});
|
||||
});
|
||||
|
||||
suite('<%', function () {
|
||||
test('without semicolons', function () {
|
||||
assert.equal(ejs.render(fixture('no.semicolons.ejs')),
|
||||
fixture('no.semicolons.html'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('<%=', function () {
|
||||
test('escape &<script>', function () {
|
||||
assert.equal(ejs.render('<%= name %>', {name: ' <script>'}),
|
||||
'&nbsp;<script>');
|
||||
});
|
||||
|
||||
test('should escape \'', function () {
|
||||
assert.equal(ejs.render('<%= name %>', {name: 'The Jones\'s'}),
|
||||
'The Jones's');
|
||||
});
|
||||
|
||||
test('should escape &foo_bar;', function () {
|
||||
assert.equal(ejs.render('<%= name %>', {name: '&foo_bar;'}),
|
||||
'&foo_bar;');
|
||||
});
|
||||
});
|
||||
|
||||
suite('<%-', function () {
|
||||
test('not escape', function () {
|
||||
assert.equal(ejs.render('<%- name %>', {name: '<script>'}),
|
||||
'<script>');
|
||||
});
|
||||
|
||||
test('terminate gracefully if no close tag is found', function () {
|
||||
try {
|
||||
ejs.compile('<h1>oops</h1><%- name ->');
|
||||
throw new Error('Expected parse failure');
|
||||
}
|
||||
catch (err) {
|
||||
assert.ok(err.message.indexOf('Could not find matching close tag for') > -1);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
suite('%>', function () {
|
||||
test('produce newlines', function () {
|
||||
assert.equal(ejs.render(fixture('newlines.ejs'), {users: users}),
|
||||
fixture('newlines.html'));
|
||||
});
|
||||
test('works with `-%>` interspersed', function () {
|
||||
assert.equal(ejs.render(fixture('newlines.mixed.ejs'), {users: users}),
|
||||
fixture('newlines.mixed.html'));
|
||||
});
|
||||
test('consecutive tags work', function () {
|
||||
assert.equal(ejs.render(fixture('consecutive-tags.ejs')),
|
||||
fixture('consecutive-tags.html'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('-%>', function () {
|
||||
test('not produce newlines', function () {
|
||||
assert.equal(ejs.render(fixture('no.newlines.ejs'), {users: users}),
|
||||
fixture('no.newlines.html'));
|
||||
});
|
||||
test('stack traces work', function () {
|
||||
try {
|
||||
ejs.render(fixture('no.newlines.error.ejs'));
|
||||
}
|
||||
catch (e) {
|
||||
if (e.message.indexOf('>> 4| <%= qdata %>') > -1) {
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
throw new Error('Expected ReferenceError');
|
||||
});
|
||||
});
|
||||
|
||||
suite('<%%', function () {
|
||||
test('produce literals', function () {
|
||||
assert.equal(ejs.render('<%%- "foo" %>'),
|
||||
'<%- "foo" %>');
|
||||
});
|
||||
test('work without an end tag', function () {
|
||||
assert.equal(ejs.render('<%%'), '<%');
|
||||
assert.equal(ejs.render(fixture('literal.ejs'), {}, {delimiter: ' '}),
|
||||
fixture('literal.html'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('<%_ and _%>', function () {
|
||||
test('slurps spaces and tabs', function () {
|
||||
assert.equal(ejs.render(fixture('space-and-tab-slurp.ejs'), {users: users}),
|
||||
fixture('space-and-tab-slurp.html'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('single quotes', function () {
|
||||
test('not mess up the constructed function', function () {
|
||||
assert.equal(ejs.render(fixture('single-quote.ejs')),
|
||||
fixture('single-quote.html'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('double quotes', function () {
|
||||
test('not mess up the constructed function', function () {
|
||||
assert.equal(ejs.render(fixture('double-quote.ejs')),
|
||||
fixture('double-quote.html'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('backslashes', function () {
|
||||
test('escape', function () {
|
||||
assert.equal(ejs.render(fixture('backslash.ejs')),
|
||||
fixture('backslash.html'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('messed up whitespace', function () {
|
||||
test('work', function () {
|
||||
assert.equal(ejs.render(fixture('messed.ejs'), {users: users}),
|
||||
fixture('messed.html'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('exceptions', function () {
|
||||
test('produce useful stack traces', function () {
|
||||
try {
|
||||
ejs.render(fixture('error.ejs'), {}, {filename: 'error.ejs'});
|
||||
}
|
||||
catch (err) {
|
||||
assert.equal(err.path, 'error.ejs');
|
||||
assert.equal(err.stack.split('\n').slice(0, 8).join('\n'), fixture('error.out'));
|
||||
return;
|
||||
}
|
||||
throw new Error('no error reported when there should be');
|
||||
});
|
||||
|
||||
test('not include fancy stack info if compileDebug is false', function () {
|
||||
try {
|
||||
ejs.render(fixture('error.ejs'), {}, {
|
||||
filename: 'error.ejs',
|
||||
compileDebug: false
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
assert.ok(!err.path);
|
||||
assert.notEqual(err.stack.split('\n').slice(0, 8).join('\n'), fixture('error.out'));
|
||||
return;
|
||||
}
|
||||
throw new Error('no error reported when there should be');
|
||||
});
|
||||
|
||||
var unhook = null;
|
||||
test('log JS source when debug is set', function (done) {
|
||||
var out = ''
|
||||
, needToExit = false;
|
||||
unhook = hook_stdio(process.stdout, function (str) {
|
||||
out += str;
|
||||
if (needToExit) {
|
||||
return;
|
||||
}
|
||||
if (out.indexOf('__output')) {
|
||||
needToExit = true;
|
||||
unhook();
|
||||
unhook = null;
|
||||
return done();
|
||||
}
|
||||
});
|
||||
ejs.render(fixture('hello-world.ejs'), {}, {debug: true});
|
||||
});
|
||||
teardown(function() {
|
||||
if (!unhook) {
|
||||
return;
|
||||
}
|
||||
unhook();
|
||||
unhook = null;
|
||||
});
|
||||
});
|
||||
|
||||
suite('rmWhitespace', function () {
|
||||
test('works', function () {
|
||||
assert.equal(ejs.render(fixture('rmWhitespace.ejs'), {}, {rmWhitespace: true}),
|
||||
fixture('rmWhitespace.html'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('include()', function () {
|
||||
test('include ejs', function () {
|
||||
var file = 'test/fixtures/include-simple.ejs';
|
||||
assert.equal(ejs.render(fixture('include-simple.ejs'), {}, {filename: file}),
|
||||
fixture('include-simple.html'));
|
||||
});
|
||||
|
||||
test('include ejs fails without `filename`', function () {
|
||||
try {
|
||||
ejs.render(fixture('include-simple.ejs'));
|
||||
}
|
||||
catch (err) {
|
||||
assert.ok(err.message.indexOf('requires the \'filename\' option') > -1);
|
||||
return;
|
||||
}
|
||||
throw new Error('expected inclusion error');
|
||||
});
|
||||
|
||||
test('strips BOM', function () {
|
||||
assert.equal(
|
||||
ejs.render('<%- include("fixtures/includes/bom.ejs") %>',
|
||||
{}, {filename: path.join(__dirname, 'f.ejs')}),
|
||||
'<p>This is a file with BOM.</p>\n');
|
||||
});
|
||||
|
||||
test('include ejs with locals', function () {
|
||||
var file = 'test/fixtures/include.ejs';
|
||||
assert.equal(ejs.render(fixture('include.ejs'), {pets: users}, {filename: file, delimiter: '@'}),
|
||||
fixture('include.html'));
|
||||
});
|
||||
|
||||
test('include ejs with absolute path and locals', function () {
|
||||
var file = 'test/fixtures/include-abspath.ejs';
|
||||
assert.equal(ejs.render(fixture('include-abspath.ejs'),
|
||||
{dir: path.join(__dirname, 'fixtures'), pets: users, path: path},
|
||||
{filename: file, delimiter: '@'}),
|
||||
fixture('include.html'));
|
||||
});
|
||||
|
||||
test('work when nested', function () {
|
||||
var file = 'test/fixtures/menu.ejs';
|
||||
assert.equal(ejs.render(fixture('menu.ejs'), {pets: users}, {filename: file}),
|
||||
fixture('menu.html'));
|
||||
});
|
||||
|
||||
test('work with a variable path', function () {
|
||||
var file = 'test/fixtures/menu_var.ejs',
|
||||
includePath = 'includes/menu-item';
|
||||
assert.equal(ejs.render(fixture('menu.ejs'), {pets: users, varPath: includePath}, {filename: file}),
|
||||
fixture('menu.html'));
|
||||
});
|
||||
|
||||
test('include arbitrary files as-is', function () {
|
||||
var file = 'test/fixtures/include.css.ejs';
|
||||
assert.equal(ejs.render(fixture('include.css.ejs'), {pets: users}, {filename: file}),
|
||||
fixture('include.css.html'));
|
||||
});
|
||||
|
||||
test('pass compileDebug to include', function () {
|
||||
var file = 'test/fixtures/include.ejs'
|
||||
, fn;
|
||||
fn = ejs.compile(fixture('include.ejs'), {
|
||||
filename: file
|
||||
, delimiter: '@'
|
||||
, compileDebug: false
|
||||
});
|
||||
try {
|
||||
// Render without a required variable reference
|
||||
fn({foo: 'asdf'});
|
||||
}
|
||||
catch(e) {
|
||||
assert.equal(e.message, 'pets is not defined');
|
||||
assert.ok(!e.path);
|
||||
return;
|
||||
}
|
||||
throw new Error('no error reported when there should be');
|
||||
});
|
||||
|
||||
test('is dynamic', function () {
|
||||
fs.writeFileSync(__dirname + '/tmp/include.ejs', '<p>Old</p>');
|
||||
var file = 'test/fixtures/include_cache.ejs'
|
||||
, options = {filename: file}
|
||||
, out = ejs.compile(fixture('include_cache.ejs'), options);
|
||||
assert.equal(out(), '<p>Old</p>\n');
|
||||
|
||||
fs.writeFileSync(__dirname + '/tmp/include.ejs', '<p>New</p>');
|
||||
assert.equal(out(), '<p>New</p>\n');
|
||||
});
|
||||
|
||||
test('support caching', function () {
|
||||
fs.writeFileSync(__dirname + '/tmp/include.ejs', '<p>Old</p>');
|
||||
var file = 'test/fixtures/include_cache.ejs'
|
||||
, options = {cache: true, filename: file}
|
||||
, out = ejs.render(fixture('include_cache.ejs'), {}, options)
|
||||
, expected = fixture('include_cache.html');
|
||||
assert.equal(out, expected);
|
||||
out = ejs.render(fixture('include_cache.ejs'), {}, options);
|
||||
// No change, still in cache
|
||||
assert.equal(out, expected);
|
||||
fs.writeFileSync(__dirname + '/tmp/include.ejs', '<p>New</p>');
|
||||
out = ejs.render(fixture('include_cache.ejs'), {}, options);
|
||||
assert.equal(out, expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('preprocessor include', function () {
|
||||
test('work', function () {
|
||||
var file = 'test/fixtures/include_preprocessor.ejs';
|
||||
assert.equal(ejs.render(fixture('include_preprocessor.ejs'), {pets: users}, {filename: file, delimiter: '@'}),
|
||||
fixture('include_preprocessor.html'));
|
||||
});
|
||||
|
||||
test('no false positives', function () {
|
||||
assert.equal(ejs.render('<% %> include foo <% %>'), ' include foo ');
|
||||
});
|
||||
|
||||
test('fails without `filename`', function () {
|
||||
try {
|
||||
ejs.render(fixture('include_preprocessor.ejs'), {pets: users}, {delimiter: '@'});
|
||||
}
|
||||
catch (err) {
|
||||
assert.ok(err.message.indexOf('requires the \'filename\' option') > -1);
|
||||
return;
|
||||
}
|
||||
throw new Error('expected inclusion error');
|
||||
});
|
||||
|
||||
test('strips BOM', function () {
|
||||
assert.equal(
|
||||
ejs.render('<% include fixtures/includes/bom.ejs %>',
|
||||
{}, {filename: path.join(__dirname, 'f.ejs')}),
|
||||
'<p>This is a file with BOM.</p>\n');
|
||||
});
|
||||
|
||||
test('work when nested', function () {
|
||||
var file = 'test/fixtures/menu_preprocessor.ejs';
|
||||
assert.equal(ejs.render(fixture('menu_preprocessor.ejs'), {pets: users}, {filename: file}),
|
||||
fixture('menu_preprocessor.html'));
|
||||
});
|
||||
|
||||
test('tracks dependency correctly', function () {
|
||||
var file = 'test/fixtures/menu_preprocessor.ejs'
|
||||
, fn = ejs.compile(fixture('menu_preprocessor.ejs'), {filename: file});
|
||||
assert(fn.dependencies.length);
|
||||
});
|
||||
|
||||
test('include arbitrary files as-is', function () {
|
||||
var file = 'test/fixtures/include_preprocessor.css.ejs';
|
||||
assert.equal(ejs.render(fixture('include_preprocessor.css.ejs'), {pets: users}, {filename: file}),
|
||||
fixture('include_preprocessor.css.html'));
|
||||
});
|
||||
|
||||
test('pass compileDebug to include', function () {
|
||||
var file = 'test/fixtures/include_preprocessor.ejs'
|
||||
, fn;
|
||||
fn = ejs.compile(fixture('include_preprocessor.ejs'), {
|
||||
filename: file
|
||||
, delimiter: '@'
|
||||
, compileDebug: false
|
||||
});
|
||||
try {
|
||||
// Render without a required variable reference
|
||||
fn({foo: 'asdf'});
|
||||
}
|
||||
catch(e) {
|
||||
assert.equal(e.message, 'pets is not defined');
|
||||
assert.ok(!e.path);
|
||||
return;
|
||||
}
|
||||
throw new Error('no error reported when there should be');
|
||||
});
|
||||
|
||||
test('is static', function () {
|
||||
fs.writeFileSync(__dirname + '/tmp/include_preprocessor.ejs', '<p>Old</p>');
|
||||
var file = 'test/fixtures/include_preprocessor_cache.ejs'
|
||||
, options = {filename: file}
|
||||
, out = ejs.compile(fixture('include_preprocessor_cache.ejs'), options);
|
||||
assert.equal(out(), '<p>Old</p>\n');
|
||||
|
||||
fs.writeFileSync(__dirname + '/tmp/include_preprocessor.ejs', '<p>New</p>');
|
||||
assert.equal(out(), '<p>Old</p>\n');
|
||||
});
|
||||
|
||||
test('support caching', function () {
|
||||
fs.writeFileSync(__dirname + '/tmp/include_preprocessor.ejs', '<p>Old</p>');
|
||||
var file = 'test/fixtures/include_preprocessor_cache.ejs'
|
||||
, options = {cache: true, filename: file}
|
||||
, out = ejs.render(fixture('include_preprocessor_cache.ejs'), {}, options)
|
||||
, expected = fixture('include_preprocessor_cache.html');
|
||||
assert.equal(out, expected);
|
||||
fs.writeFileSync(__dirname + '/tmp/include_preprocessor.ejs', '<p>New</p>');
|
||||
out = ejs.render(fixture('include_preprocessor_cache.ejs'), {}, options);
|
||||
assert.equal(out, expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('comments', function () {
|
||||
test('fully render with comments removed', function () {
|
||||
assert.equal(ejs.render(fixture('comments.ejs')),
|
||||
fixture('comments.html'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('require', function () {
|
||||
|
||||
// Only works with inline/preprocessor includes
|
||||
test('allow ejs templates to be required as node modules', function () {
|
||||
var file = 'test/fixtures/include_preprocessor.ejs'
|
||||
, template = require(__dirname + '/fixtures/menu_preprocessor.ejs');
|
||||
if (!process.env.running_under_istanbul) {
|
||||
assert.equal(template({filename: file, pets: users}),
|
||||
fixture('menu_preprocessor.html'));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
suite('examples', function () {
|
||||
function noop () {}
|
||||
fs.readdirSync('examples').forEach(function (f) {
|
||||
if (!/\.js$/.test(f)) {
|
||||
return;
|
||||
}
|
||||
suite(f, function () {
|
||||
test('doesn\'t throw any errors', function () {
|
||||
var stderr = hook_stdio(process.stderr, noop)
|
||||
, stdout = hook_stdio(process.stdout, noop);
|
||||
try {
|
||||
require('../examples/' + f);
|
||||
}
|
||||
catch (ex) {
|
||||
stdout();
|
||||
stderr();
|
||||
throw ex;
|
||||
}
|
||||
stdout();
|
||||
stderr();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
1
nodejs/node_modules/ejs/test/fixtures/backslash.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/backslash.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
\foo
|
1
nodejs/node_modules/ejs/test/fixtures/backslash.html
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/backslash.html
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
\foo
|
7
nodejs/node_modules/ejs/test/fixtures/comments.ejs
generated
vendored
Normal file
7
nodejs/node_modules/ejs/test/fixtures/comments.ejs
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<li><a href="foo"><% // double-slash comment %>foo</li>
|
||||
<li><a href="bar"><% /* C-style comment */ %>bar</li>
|
||||
<li><a href="baz"><% // double-slash comment with newline
|
||||
%>baz</li>
|
||||
<li><a href="qux"><% var x = 'qux'; // double-slash comment @ end of line %><%= x %></li>
|
||||
<li><a href="fee"><%# ERB style comment %>fee</li>
|
||||
<li><a href="bah"><%= 'not a ' + '//' + ' comment' %></a></li>
|
6
nodejs/node_modules/ejs/test/fixtures/comments.html
generated
vendored
Normal file
6
nodejs/node_modules/ejs/test/fixtures/comments.html
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
<li><a href="foo">foo</li>
|
||||
<li><a href="bar">bar</li>
|
||||
<li><a href="baz">baz</li>
|
||||
<li><a href="qux">qux</li>
|
||||
<li><a href="fee">fee</li>
|
||||
<li><a href="bah">not a // comment</a></li>
|
1
nodejs/node_modules/ejs/test/fixtures/consecutive-tags.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/consecutive-tags.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<% var a = 'foo' %><% var b = 'bar' %><%= a %>
|
1
nodejs/node_modules/ejs/test/fixtures/consecutive-tags.html
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/consecutive-tags.html
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
foo
|
1
nodejs/node_modules/ejs/test/fixtures/double-quote.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/double-quote.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<p><%= "lo" + 'ki' %>'s "wheelchair"</p>
|
1
nodejs/node_modules/ejs/test/fixtures/double-quote.html
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/double-quote.html
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<p>loki's "wheelchair"</p>
|
5
nodejs/node_modules/ejs/test/fixtures/error.ejs
generated
vendored
Normal file
5
nodejs/node_modules/ejs/test/fixtures/error.ejs
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<ul>
|
||||
<% if (users) { %>
|
||||
<p>Has users</p>
|
||||
<% } %>
|
||||
</ul>
|
8
nodejs/node_modules/ejs/test/fixtures/error.out
generated
vendored
Normal file
8
nodejs/node_modules/ejs/test/fixtures/error.out
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
ReferenceError: error.ejs:2
|
||||
1| <ul>
|
||||
>> 2| <% if (users) { %>
|
||||
3| <p>Has users</p>
|
||||
4| <% } %>
|
||||
5| </ul>
|
||||
|
||||
users is not defined
|
1
nodejs/node_modules/ejs/test/fixtures/fail.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/fail.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<% function foo() return 'foo'; %>
|
1
nodejs/node_modules/ejs/test/fixtures/hello-world.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/hello-world.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<p>Hello world!</p>
|
5
nodejs/node_modules/ejs/test/fixtures/include-abspath.ejs
generated
vendored
Normal file
5
nodejs/node_modules/ejs/test/fixtures/include-abspath.ejs
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<ul>
|
||||
<@ pets.forEach(function(pet){ @>
|
||||
<@- include(path.join(dir, 'pet'), {pet: pet}); @>
|
||||
<@ }); @>
|
||||
</ul>
|
3
nodejs/node_modules/ejs/test/fixtures/include-simple.ejs
generated
vendored
Normal file
3
nodejs/node_modules/ejs/test/fixtures/include-simple.ejs
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<ul>
|
||||
<%- include('hello-world'); %>
|
||||
</ul>
|
4
nodejs/node_modules/ejs/test/fixtures/include-simple.html
generated
vendored
Normal file
4
nodejs/node_modules/ejs/test/fixtures/include-simple.html
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<ul>
|
||||
<p>Hello world!</p>
|
||||
|
||||
</ul>
|
1
nodejs/node_modules/ejs/test/fixtures/include.css.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/include.css.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<style><%- include('style.css', {value: 'bar'}); %></style>
|
4
nodejs/node_modules/ejs/test/fixtures/include.css.html
generated
vendored
Normal file
4
nodejs/node_modules/ejs/test/fixtures/include.css.html
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<style>body {
|
||||
foo: 'bar';
|
||||
}
|
||||
</style>
|
5
nodejs/node_modules/ejs/test/fixtures/include.ejs
generated
vendored
Normal file
5
nodejs/node_modules/ejs/test/fixtures/include.ejs
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<ul>
|
||||
<@ pets.forEach(function(pet){ @>
|
||||
<@- include('pet', {pet: pet}); @>
|
||||
<@ }); @>
|
||||
</ul>
|
12
nodejs/node_modules/ejs/test/fixtures/include.html
generated
vendored
Normal file
12
nodejs/node_modules/ejs/test/fixtures/include.html
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<ul>
|
||||
|
||||
<li>geddy</li>
|
||||
|
||||
|
||||
<li>neil</li>
|
||||
|
||||
|
||||
<li>alex</li>
|
||||
|
||||
|
||||
</ul>
|
1
nodejs/node_modules/ejs/test/fixtures/include_cache.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/include_cache.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<%- include('../tmp/include') %>
|
1
nodejs/node_modules/ejs/test/fixtures/include_cache.html
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/include_cache.html
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<p>Old</p>
|
1
nodejs/node_modules/ejs/test/fixtures/include_preprocessor.css.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/include_preprocessor.css.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<style><% var value = 'bar' %><% include style.css %></style>
|
4
nodejs/node_modules/ejs/test/fixtures/include_preprocessor.css.html
generated
vendored
Normal file
4
nodejs/node_modules/ejs/test/fixtures/include_preprocessor.css.html
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<style>body {
|
||||
foo: 'bar';
|
||||
}
|
||||
</style>
|
5
nodejs/node_modules/ejs/test/fixtures/include_preprocessor.ejs
generated
vendored
Normal file
5
nodejs/node_modules/ejs/test/fixtures/include_preprocessor.ejs
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<ul>
|
||||
<@ pets.forEach(function(pet){ @>
|
||||
<@ include pet @>
|
||||
<@ }) @>
|
||||
</ul>
|
12
nodejs/node_modules/ejs/test/fixtures/include_preprocessor.html
generated
vendored
Normal file
12
nodejs/node_modules/ejs/test/fixtures/include_preprocessor.html
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<ul>
|
||||
|
||||
<li>geddy</li>
|
||||
|
||||
|
||||
<li>neil</li>
|
||||
|
||||
|
||||
<li>alex</li>
|
||||
|
||||
|
||||
</ul>
|
1
nodejs/node_modules/ejs/test/fixtures/include_preprocessor_cache.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/include_preprocessor_cache.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<%- include ../tmp/include_preprocessor %>
|
1
nodejs/node_modules/ejs/test/fixtures/include_preprocessor_cache.html
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/include_preprocessor_cache.html
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<p>Old</p>
|
1
nodejs/node_modules/ejs/test/fixtures/includes/bom.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/includes/bom.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<p>This is a file with BOM.</p>
|
1
nodejs/node_modules/ejs/test/fixtures/includes/menu-item.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/includes/menu-item.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<li><% include menu/item %></li>
|
1
nodejs/node_modules/ejs/test/fixtures/includes/menu/item.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/includes/menu/item.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<a href="/<%= url %>"><%= title %></a>
|
3
nodejs/node_modules/ejs/test/fixtures/literal.ejs
generated
vendored
Normal file
3
nodejs/node_modules/ejs/test/fixtures/literal.ejs
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<pre>There should be a space followed by a less-than sign and then two more
|
||||
spaces in the next line:
|
||||
< .</pre>
|
3
nodejs/node_modules/ejs/test/fixtures/literal.html
generated
vendored
Normal file
3
nodejs/node_modules/ejs/test/fixtures/literal.html
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<pre>There should be a space followed by a less-than sign and then two more
|
||||
spaces in the next line:
|
||||
< .</pre>
|
15
nodejs/node_modules/ejs/test/fixtures/menu.ejs
generated
vendored
Normal file
15
nodejs/node_modules/ejs/test/fixtures/menu.ejs
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<%- include('includes/menu-item', {
|
||||
url: '/foo'
|
||||
, title: 'Foo'
|
||||
}); -%>
|
||||
|
||||
<%- include('includes/menu-item', {
|
||||
url: '/bar'
|
||||
, title: 'Bar'
|
||||
}); -%>
|
||||
|
||||
<%- include('includes/menu-item', {
|
||||
url: '/baz'
|
||||
, title: 'Baz'
|
||||
}); -%>
|
||||
|
9
nodejs/node_modules/ejs/test/fixtures/menu.html
generated
vendored
Normal file
9
nodejs/node_modules/ejs/test/fixtures/menu.html
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<li><a href="//foo">Foo</a>
|
||||
</li>
|
||||
|
||||
<li><a href="//bar">Bar</a>
|
||||
</li>
|
||||
|
||||
<li><a href="//baz">Baz</a>
|
||||
</li>
|
||||
|
11
nodejs/node_modules/ejs/test/fixtures/menu_preprocessor.ejs
generated
vendored
Normal file
11
nodejs/node_modules/ejs/test/fixtures/menu_preprocessor.ejs
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
<% var url = '/foo' -%>
|
||||
<% var title = 'Foo' -%>
|
||||
<% include includes/menu-item -%>
|
||||
|
||||
<% var url = '/bar' -%>
|
||||
<% var title = 'Bar' -%>
|
||||
<% include includes/menu-item -%>
|
||||
|
||||
<% var url = '/baz' -%>
|
||||
<% var title = 'Baz' -%>
|
||||
<% include includes/menu-item -%>
|
8
nodejs/node_modules/ejs/test/fixtures/menu_preprocessor.html
generated
vendored
Normal file
8
nodejs/node_modules/ejs/test/fixtures/menu_preprocessor.html
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
<li><a href="//foo">Foo</a>
|
||||
</li>
|
||||
|
||||
<li><a href="//bar">Bar</a>
|
||||
</li>
|
||||
|
||||
<li><a href="//baz">Baz</a>
|
||||
</li>
|
15
nodejs/node_modules/ejs/test/fixtures/menu_var.ejs
generated
vendored
Normal file
15
nodejs/node_modules/ejs/test/fixtures/menu_var.ejs
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
<%- include(varPath, {
|
||||
url: '/foo'
|
||||
, title: 'Foo'
|
||||
}); -%>
|
||||
|
||||
<%- include(varPath, {
|
||||
url: '/bar'
|
||||
, title: 'Bar'
|
||||
}); -%>
|
||||
|
||||
<%- include(varPath, {
|
||||
url: '/baz'
|
||||
, title: 'Baz'
|
||||
}); -%>
|
||||
|
1
nodejs/node_modules/ejs/test/fixtures/messed.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/messed.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<ul><%users.forEach(function(user){%><li><%=user.name%></li><%})%></ul>
|
1
nodejs/node_modules/ejs/test/fixtures/messed.html
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/messed.html
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<ul><li>geddy</li><li>neil</li><li>alex</li></ul>
|
5
nodejs/node_modules/ejs/test/fixtures/newlines.ejs
generated
vendored
Normal file
5
nodejs/node_modules/ejs/test/fixtures/newlines.ejs
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<ul>
|
||||
<% users.forEach(function(user){ %>
|
||||
<li><%= user.name %></li>
|
||||
<% }) %>
|
||||
</ul>
|
9
nodejs/node_modules/ejs/test/fixtures/newlines.html
generated
vendored
Normal file
9
nodejs/node_modules/ejs/test/fixtures/newlines.html
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<ul>
|
||||
|
||||
<li>geddy</li>
|
||||
|
||||
<li>neil</li>
|
||||
|
||||
<li>alex</li>
|
||||
|
||||
</ul>
|
6
nodejs/node_modules/ejs/test/fixtures/newlines.mixed.ejs
generated
vendored
Normal file
6
nodejs/node_modules/ejs/test/fixtures/newlines.mixed.ejs
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
<ul>
|
||||
<% var unused1 = 'blah' -%>
|
||||
<% var unused2 = 'bleh' %>
|
||||
<% var unused3 = 'bloh' -%>
|
||||
<% var unused4 = 'bluh' %>
|
||||
</ul>
|
4
nodejs/node_modules/ejs/test/fixtures/newlines.mixed.html
generated
vendored
Normal file
4
nodejs/node_modules/ejs/test/fixtures/newlines.mixed.html
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<ul>
|
||||
|
||||
|
||||
</ul>
|
5
nodejs/node_modules/ejs/test/fixtures/no.newlines.ejs
generated
vendored
Normal file
5
nodejs/node_modules/ejs/test/fixtures/no.newlines.ejs
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<ul>
|
||||
<% users.forEach(function(user){ -%>
|
||||
<li><%= user.name %></li>
|
||||
<% }) -%>
|
||||
</ul>
|
5
nodejs/node_modules/ejs/test/fixtures/no.newlines.error.ejs
generated
vendored
Normal file
5
nodejs/node_modules/ejs/test/fixtures/no.newlines.error.ejs
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
AAA
|
||||
<% data = "test"; -%>
|
||||
BBB
|
||||
<%= qdata %>
|
||||
CCC
|
5
nodejs/node_modules/ejs/test/fixtures/no.newlines.html
generated
vendored
Normal file
5
nodejs/node_modules/ejs/test/fixtures/no.newlines.html
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<ul>
|
||||
<li>geddy</li>
|
||||
<li>neil</li>
|
||||
<li>alex</li>
|
||||
</ul>
|
8
nodejs/node_modules/ejs/test/fixtures/no.semicolons.ejs
generated
vendored
Normal file
8
nodejs/node_modules/ejs/test/fixtures/no.semicolons.ejs
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
This document does not use semicolons in scriptlets.
|
||||
<%
|
||||
var a = 'b'
|
||||
var b = 'c'
|
||||
var c
|
||||
c = b
|
||||
%>
|
||||
The value of c is: <%= c %>
|
3
nodejs/node_modules/ejs/test/fixtures/no.semicolons.html
generated
vendored
Normal file
3
nodejs/node_modules/ejs/test/fixtures/no.semicolons.html
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
This document does not use semicolons in scriptlets.
|
||||
|
||||
The value of c is: c
|
1
nodejs/node_modules/ejs/test/fixtures/para.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/para.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<p>hey</p>
|
1
nodejs/node_modules/ejs/test/fixtures/pet.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/pet.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<li><@= pet.name @></li>
|
14
nodejs/node_modules/ejs/test/fixtures/rmWhitespace.ejs
generated
vendored
Normal file
14
nodejs/node_modules/ejs/test/fixtures/rmWhitespace.ejs
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
<tag1>
|
||||
<tag2>
|
||||
A very long piece of text very long piece of text very long piece of
|
||||
text very long piece <% var f = 'f' %>of text very long piece of
|
||||
tex
|
||||
t very long piece of<% %>text very long
|
||||
adsffadsfadsfad<%= f %>
|
||||
|
||||
piece of text.
|
||||
<% var a = 'a' %>
|
||||
Text again.
|
||||
<% var b = 'b' %>
|
||||
<% var c = 'c'
|
||||
var d = 'd' %>
|
8
nodejs/node_modules/ejs/test/fixtures/rmWhitespace.html
generated
vendored
Normal file
8
nodejs/node_modules/ejs/test/fixtures/rmWhitespace.html
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
<tag1>
|
||||
<tag2>
|
||||
A very long piece of text very long piece of text very long piece of
|
||||
text very long piece of text very long piece of
|
||||
text very long piece oftext very long
|
||||
adsffadsfadsfadfpiece of text.
|
||||
Text again.
|
||||
Another text. c
|
1
nodejs/node_modules/ejs/test/fixtures/single-quote.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/single-quote.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<p><%= 'loki' %>'s wheelchair</p>
|
1
nodejs/node_modules/ejs/test/fixtures/single-quote.html
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/single-quote.html
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<p>loki's wheelchair</p>
|
5
nodejs/node_modules/ejs/test/fixtures/space-and-tab-slurp.ejs
generated
vendored
Normal file
5
nodejs/node_modules/ejs/test/fixtures/space-and-tab-slurp.ejs
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<ul>
|
||||
<%_ users.forEach(function(user){ _%>
|
||||
<li><%= user.name %></li>
|
||||
<%_ }) _%>
|
||||
</ul>
|
5
nodejs/node_modules/ejs/test/fixtures/space-and-tab-slurp.html
generated
vendored
Normal file
5
nodejs/node_modules/ejs/test/fixtures/space-and-tab-slurp.html
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
<ul>
|
||||
<li>geddy</li>
|
||||
<li>neil</li>
|
||||
<li>alex</li>
|
||||
</ul>
|
3
nodejs/node_modules/ejs/test/fixtures/style.css
generated
vendored
Normal file
3
nodejs/node_modules/ejs/test/fixtures/style.css
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
body {
|
||||
foo: '<%= value %>';
|
||||
}
|
1
nodejs/node_modules/ejs/test/fixtures/user-no-with.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/user-no-with.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<h1><$= locals.name $></h1>
|
1
nodejs/node_modules/ejs/test/fixtures/user.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/user.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<h1><$= name $></h1>
|
1
nodejs/node_modules/ejs/test/fixtures/with-context.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/fixtures/with-context.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<%= this.foo %>
|
3
nodejs/node_modules/ejs/test/mocha.opts
generated
vendored
Normal file
3
nodejs/node_modules/ejs/test/mocha.opts
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
--ui tdd
|
||||
--reporter spec
|
||||
--check-leaks
|
1
nodejs/node_modules/ejs/test/tmp/include.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/tmp/include.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<p>New</p>
|
1
nodejs/node_modules/ejs/test/tmp/include_preprocessor.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/tmp/include_preprocessor.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<p>New</p>
|
1
nodejs/node_modules/ejs/test/tmp/renderFile.ejs
generated
vendored
Normal file
1
nodejs/node_modules/ejs/test/tmp/renderFile.ejs
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<p>New</p>
|
7
nodejs/node_modules/express/.npmignore
generated
vendored
Normal file
7
nodejs/node_modules/express/.npmignore
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.git*
|
||||
docs/
|
||||
examples/
|
||||
support/
|
||||
test/
|
||||
testing.js
|
||||
.DS_Store
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user