NUEVA BUSQUEDA DE DATOS SRI
This commit is contained in:
327
node_modules/ejs/ejs.js
generated
vendored
327
node_modules/ejs/ejs.js
generated
vendored
@@ -1,4 +1,4 @@
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.ejs = f()}})(function(){var define,module,exports;return (function(){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}return e})()({1:[function(require,module,exports){
|
||||
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.ejs = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
/*
|
||||
* EJS Embedded JavaScript templates
|
||||
* Copyright 2112 Matthew Eernisse (mde@fleegix.org)
|
||||
@@ -45,6 +45,7 @@
|
||||
* @public
|
||||
*/
|
||||
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var utils = require('./utils');
|
||||
@@ -65,6 +66,7 @@ var _OPTS_PASSABLE_WITH_DATA = ['delimiter', 'scope', 'context', 'debug', 'compi
|
||||
// so we make an exception for `renderFile`
|
||||
var _OPTS_PASSABLE_WITH_DATA_EXPRESS = _OPTS_PASSABLE_WITH_DATA.concat('cache');
|
||||
var _BOM = /^\uFEFF/;
|
||||
var _JS_IDENTIFIER = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
|
||||
|
||||
/**
|
||||
* EJS template function cache. This can be a LRU object from lru-cache NPM
|
||||
@@ -128,6 +130,23 @@ exports.resolveInclude = function(name, filename, isDir) {
|
||||
return includePath;
|
||||
};
|
||||
|
||||
/**
|
||||
* Try to resolve file path on multiple directories
|
||||
*
|
||||
* @param {String} name specified path
|
||||
* @param {Array<String>} paths list of possible parent directory paths
|
||||
* @return {String}
|
||||
*/
|
||||
function resolvePaths(name, paths) {
|
||||
var filePath;
|
||||
if (paths.some(function (v) {
|
||||
filePath = exports.resolveInclude(name, v, true);
|
||||
return fs.existsSync(filePath);
|
||||
})) {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the included file by Options
|
||||
*
|
||||
@@ -143,7 +162,12 @@ function getIncludePath(path, options) {
|
||||
|
||||
// Abs path
|
||||
if (match && match.length) {
|
||||
includePath = exports.resolveInclude(path.replace(/^\/*/,''), options.root || '/', true);
|
||||
path = path.replace(/^\/*/, '');
|
||||
if (Array.isArray(options.root)) {
|
||||
includePath = resolvePaths(path, options.root);
|
||||
} else {
|
||||
includePath = exports.resolveInclude(path, options.root || '/', true);
|
||||
}
|
||||
}
|
||||
// Relative paths
|
||||
else {
|
||||
@@ -155,15 +179,10 @@ function getIncludePath(path, options) {
|
||||
}
|
||||
}
|
||||
// Then look in any views directories
|
||||
if (!includePath) {
|
||||
if (Array.isArray(views) && views.some(function (v) {
|
||||
filePath = exports.resolveInclude(path, v, true);
|
||||
return fs.existsSync(filePath);
|
||||
})) {
|
||||
includePath = filePath;
|
||||
}
|
||||
if (!includePath && Array.isArray(views)) {
|
||||
includePath = resolvePaths(path, views);
|
||||
}
|
||||
if (!includePath) {
|
||||
if (!includePath && typeof options.includer !== 'function') {
|
||||
throw new Error('Could not find the include file "' +
|
||||
options.escapeFunction(path) + '"');
|
||||
}
|
||||
@@ -289,8 +308,19 @@ function fileLoader(filePath){
|
||||
*/
|
||||
|
||||
function includeFile(path, options) {
|
||||
var opts = utils.shallowCopy({}, options);
|
||||
var opts = utils.shallowCopy(utils.createNullProtoObjWherePossible(), options);
|
||||
opts.filename = getIncludePath(path, opts);
|
||||
if (typeof options.includer === 'function') {
|
||||
var includerResult = options.includer(path, opts.filename);
|
||||
if (includerResult) {
|
||||
if (includerResult.filename) {
|
||||
opts.filename = includerResult.filename;
|
||||
}
|
||||
if (includerResult.template) {
|
||||
return handleCache(opts, includerResult.template);
|
||||
}
|
||||
}
|
||||
}
|
||||
return handleCache(opts);
|
||||
}
|
||||
|
||||
@@ -384,8 +414,8 @@ exports.compile = function compile(template, opts) {
|
||||
*/
|
||||
|
||||
exports.render = function (template, d, o) {
|
||||
var data = d || {};
|
||||
var opts = o || {};
|
||||
var data = d || utils.createNullProtoObjWherePossible();
|
||||
var opts = o || utils.createNullProtoObjWherePossible();
|
||||
|
||||
// No options object -- if there are optiony names
|
||||
// in the data, copy them to options
|
||||
@@ -456,7 +486,7 @@ exports.renderFile = function () {
|
||||
opts.filename = filename;
|
||||
}
|
||||
else {
|
||||
data = {};
|
||||
data = utils.createNullProtoObjWherePossible();
|
||||
}
|
||||
|
||||
return tryHandleCache(opts, data, cb);
|
||||
@@ -478,8 +508,8 @@ exports.clearCache = function () {
|
||||
};
|
||||
|
||||
function Template(text, opts) {
|
||||
opts = opts || {};
|
||||
var options = {};
|
||||
opts = opts || utils.createNullProtoObjWherePossible();
|
||||
var options = utils.createNullProtoObjWherePossible();
|
||||
this.templateText = text;
|
||||
/** @type {string | null} */
|
||||
this.mode = null;
|
||||
@@ -499,6 +529,7 @@ function Template(text, opts) {
|
||||
options.cache = opts.cache || false;
|
||||
options.rmWhitespace = opts.rmWhitespace;
|
||||
options.root = opts.root;
|
||||
options.includer = opts.includer;
|
||||
options.outputFunctionName = opts.outputFunctionName;
|
||||
options.localsName = opts.localsName || exports.localsName || _DEFAULT_LOCALS_NAME;
|
||||
options.views = opts.views;
|
||||
@@ -550,6 +581,8 @@ Template.prototype = {
|
||||
var escapeFn = opts.escapeFunction;
|
||||
/** @type {FunctionConstructor} */
|
||||
var ctor;
|
||||
/** @type {string} */
|
||||
var sanitizedFilename = opts.filename ? JSON.stringify(opts.filename) : 'undefined';
|
||||
|
||||
if (!this.source) {
|
||||
this.generateSource();
|
||||
@@ -557,12 +590,21 @@ Template.prototype = {
|
||||
' var __output = "";\n' +
|
||||
' function __append(s) { if (s !== undefined && s !== null) __output += s }\n';
|
||||
if (opts.outputFunctionName) {
|
||||
if (!_JS_IDENTIFIER.test(opts.outputFunctionName)) {
|
||||
throw new Error('outputFunctionName is not a valid JS identifier.');
|
||||
}
|
||||
prepended += ' var ' + opts.outputFunctionName + ' = __append;' + '\n';
|
||||
}
|
||||
if (opts.localsName && !_JS_IDENTIFIER.test(opts.localsName)) {
|
||||
throw new Error('localsName is not a valid JS identifier.');
|
||||
}
|
||||
if (opts.destructuredLocals && opts.destructuredLocals.length) {
|
||||
var destructuring = ' var __locals = (' + opts.localsName + ' || {}),\n';
|
||||
for (var i = 0; i < opts.destructuredLocals.length; i++) {
|
||||
var name = opts.destructuredLocals[i];
|
||||
if (!_JS_IDENTIFIER.test(name)) {
|
||||
throw new Error('destructuredLocals[' + i + '] is not a valid JS identifier.');
|
||||
}
|
||||
if (i > 0) {
|
||||
destructuring += ',\n ';
|
||||
}
|
||||
@@ -581,8 +623,7 @@ Template.prototype = {
|
||||
if (opts.compileDebug) {
|
||||
src = 'var __line = 1' + '\n'
|
||||
+ ' , __lines = ' + JSON.stringify(this.templateText) + '\n'
|
||||
+ ' , __filename = ' + (opts.filename ?
|
||||
JSON.stringify(opts.filename) : 'undefined') + ';' + '\n'
|
||||
+ ' , __filename = ' + sanitizedFilename + ';' + '\n'
|
||||
+ 'try {' + '\n'
|
||||
+ this.source
|
||||
+ '} catch (e) {' + '\n'
|
||||
@@ -608,7 +649,7 @@ Template.prototype = {
|
||||
}
|
||||
if (opts.compileDebug && opts.filename) {
|
||||
src = src + '\n'
|
||||
+ '//# sourceURL=' + opts.filename + '\n';
|
||||
+ '//# sourceURL=' + sanitizedFilename + '\n';
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -654,13 +695,14 @@ Template.prototype = {
|
||||
// Adds a local `include` function which allows full recursive include
|
||||
var returnedFn = opts.client ? fn : function anonymous(data) {
|
||||
var include = function (path, includeData) {
|
||||
var d = utils.shallowCopy({}, data);
|
||||
var d = utils.shallowCopy(utils.createNullProtoObjWherePossible(), data);
|
||||
if (includeData) {
|
||||
d = utils.shallowCopy(d, includeData);
|
||||
}
|
||||
return includeFile(path, opts)(d);
|
||||
};
|
||||
return fn.apply(opts.context, [data || {}, escapeFn, include, rethrow]);
|
||||
return fn.apply(opts.context,
|
||||
[data || utils.createNullProtoObjWherePossible(), escapeFn, include, rethrow]);
|
||||
};
|
||||
if (opts.filename && typeof Object.defineProperty === 'function') {
|
||||
var filename = opts.filename;
|
||||
@@ -937,6 +979,8 @@ if (typeof window != 'undefined') {
|
||||
'use strict';
|
||||
|
||||
var regExpChars = /[|\\{}()[\]^$+*?.]/g;
|
||||
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
var hasOwn = function (obj, key) { return hasOwnProperty.apply(obj, [key]); };
|
||||
|
||||
/**
|
||||
* Escape characters reserved in regular expressions.
|
||||
@@ -1009,9 +1053,25 @@ exports.escapeXML = function (markup) {
|
||||
: String(markup)
|
||||
.replace(_MATCH_HTML, encode_char);
|
||||
};
|
||||
exports.escapeXML.toString = function () {
|
||||
|
||||
function escapeXMLToString() {
|
||||
return Function.prototype.toString.call(this) + ';\n' + escapeFuncStr;
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof Object.defineProperty === 'function') {
|
||||
// If the Function prototype is frozen, the "toString" property is non-writable. This means that any objects which inherit this property
|
||||
// cannot have the property changed using an assignment. If using strict mode, attempting that will cause an error. If not using strict
|
||||
// mode, attempting that will be silently ignored.
|
||||
// However, we can still explicitly shadow the prototype's "toString" property by defining a new "toString" property on this object.
|
||||
Object.defineProperty(exports.escapeXML, 'toString', { value: escapeXMLToString });
|
||||
} else {
|
||||
// If Object.defineProperty() doesn't exist, attempt to shadow this property using the assignment operator.
|
||||
exports.escapeXML.toString = escapeXMLToString;
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Unable to set escapeXML.toString (is the Function prototype frozen?)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Naive copy of properties from one object to another.
|
||||
@@ -1026,8 +1086,16 @@ exports.escapeXML.toString = function () {
|
||||
*/
|
||||
exports.shallowCopy = function (to, from) {
|
||||
from = from || {};
|
||||
for (var p in from) {
|
||||
to[p] = from[p];
|
||||
if ((to !== null) && (to !== undefined)) {
|
||||
for (var p in from) {
|
||||
if (!hasOwn(from, p)) {
|
||||
continue;
|
||||
}
|
||||
if (p === '__proto__' || p === 'constructor') {
|
||||
continue;
|
||||
}
|
||||
to[p] = from[p];
|
||||
}
|
||||
}
|
||||
return to;
|
||||
};
|
||||
@@ -1045,10 +1113,20 @@ exports.shallowCopy = function (to, from) {
|
||||
* @private
|
||||
*/
|
||||
exports.shallowCopyFromList = function (to, from, list) {
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var p = list[i];
|
||||
if (typeof from[p] != 'undefined') {
|
||||
to[p] = from[p];
|
||||
list = list || [];
|
||||
from = from || {};
|
||||
if ((to !== null) && (to !== undefined)) {
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
var p = list[i];
|
||||
if (typeof from[p] != 'undefined') {
|
||||
if (!hasOwn(from, p)) {
|
||||
continue;
|
||||
}
|
||||
if (p === '__proto__' || p === 'constructor') {
|
||||
continue;
|
||||
}
|
||||
to[p] = from[p];
|
||||
}
|
||||
}
|
||||
}
|
||||
return to;
|
||||
@@ -1078,10 +1156,51 @@ exports.cache = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Transforms hyphen case variable into camel case.
|
||||
*
|
||||
* @param {String} string Hyphen case string
|
||||
* @return {String} Camel case string
|
||||
* @static
|
||||
* @private
|
||||
*/
|
||||
exports.hyphenToCamel = function (str) {
|
||||
return str.replace(/-[a-z]/g, function (match) { return match[1].toUpperCase(); });
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a null-prototype object in runtimes that support it
|
||||
*
|
||||
* @return {Object} Object, prototype will be set to null where possible
|
||||
* @static
|
||||
* @private
|
||||
*/
|
||||
exports.createNullProtoObjWherePossible = (function () {
|
||||
if (typeof Object.create == 'function') {
|
||||
return function () {
|
||||
return Object.create(null);
|
||||
};
|
||||
}
|
||||
if (!({__proto__: null} instanceof Object)) {
|
||||
return function () {
|
||||
return {__proto__: null};
|
||||
};
|
||||
}
|
||||
// Not possible, just pass through
|
||||
return function () {
|
||||
return {};
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
|
||||
},{}],3:[function(require,module,exports){
|
||||
|
||||
},{}],4:[function(require,module,exports){
|
||||
(function (process){
|
||||
// .dirname, .basename, and .extname methods are extracted from Node.js v8.11.1,
|
||||
// backported and transplited with Babel, with backwards-compat fixes
|
||||
|
||||
// Copyright Joyent, Inc. and other Node contributors.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
@@ -1133,14 +1252,6 @@ function normalizeArray(parts, allowAboveRoot) {
|
||||
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() {
|
||||
@@ -1256,37 +1367,120 @@ exports.relative = function(from, to) {
|
||||
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 '.';
|
||||
exports.dirname = function (path) {
|
||||
if (typeof path !== 'string') path = path + '';
|
||||
if (path.length === 0) return '.';
|
||||
var code = path.charCodeAt(0);
|
||||
var hasRoot = code === 47 /*/*/;
|
||||
var end = -1;
|
||||
var matchedSlash = true;
|
||||
for (var i = path.length - 1; i >= 1; --i) {
|
||||
code = path.charCodeAt(i);
|
||||
if (code === 47 /*/*/) {
|
||||
if (!matchedSlash) {
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// We saw the first non-path separator
|
||||
matchedSlash = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (dir) {
|
||||
// It has a dirname, strip trailing slash
|
||||
dir = dir.substr(0, dir.length - 1);
|
||||
if (end === -1) return hasRoot ? '/' : '.';
|
||||
if (hasRoot && end === 1) {
|
||||
// return '//';
|
||||
// Backwards-compat fix:
|
||||
return '/';
|
||||
}
|
||||
|
||||
return root + dir;
|
||||
return path.slice(0, end);
|
||||
};
|
||||
|
||||
function basename(path) {
|
||||
if (typeof path !== 'string') path = path + '';
|
||||
|
||||
exports.basename = function(path, ext) {
|
||||
var f = splitPath(path)[2];
|
||||
// TODO: make this comparison case-insensitive on windows?
|
||||
var start = 0;
|
||||
var end = -1;
|
||||
var matchedSlash = true;
|
||||
var i;
|
||||
|
||||
for (i = path.length - 1; i >= 0; --i) {
|
||||
if (path.charCodeAt(i) === 47 /*/*/) {
|
||||
// If we reached a path separator that was not part of a set of path
|
||||
// separators at the end of the string, stop now
|
||||
if (!matchedSlash) {
|
||||
start = i + 1;
|
||||
break;
|
||||
}
|
||||
} else if (end === -1) {
|
||||
// We saw the first non-path separator, mark this as the end of our
|
||||
// path component
|
||||
matchedSlash = false;
|
||||
end = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (end === -1) return '';
|
||||
return path.slice(start, end);
|
||||
}
|
||||
|
||||
// Uses a mixed approach for backwards-compatibility, as ext behavior changed
|
||||
// in new Node.js versions, so only basename() above is backported here
|
||||
exports.basename = function (path, ext) {
|
||||
var f = basename(path);
|
||||
if (ext && f.substr(-1 * ext.length) === ext) {
|
||||
f = f.substr(0, f.length - ext.length);
|
||||
}
|
||||
return f;
|
||||
};
|
||||
|
||||
exports.extname = function (path) {
|
||||
if (typeof path !== 'string') path = path + '';
|
||||
var startDot = -1;
|
||||
var startPart = 0;
|
||||
var end = -1;
|
||||
var matchedSlash = true;
|
||||
// Track the state of characters (if any) we see before our first dot and
|
||||
// after any path separator we find
|
||||
var preDotState = 0;
|
||||
for (var i = path.length - 1; i >= 0; --i) {
|
||||
var code = path.charCodeAt(i);
|
||||
if (code === 47 /*/*/) {
|
||||
// If we reached a path separator that was not part of a set of path
|
||||
// separators at the end of the string, stop now
|
||||
if (!matchedSlash) {
|
||||
startPart = i + 1;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (end === -1) {
|
||||
// We saw the first non-path separator, mark this as the end of our
|
||||
// extension
|
||||
matchedSlash = false;
|
||||
end = i + 1;
|
||||
}
|
||||
if (code === 46 /*.*/) {
|
||||
// If this is our first dot, mark it as the start of our extension
|
||||
if (startDot === -1)
|
||||
startDot = i;
|
||||
else if (preDotState !== 1)
|
||||
preDotState = 1;
|
||||
} else if (startDot !== -1) {
|
||||
// We saw a non-dot and non-path separator before our dot, so we should
|
||||
// have a good chance at having a non-empty extension
|
||||
preDotState = -1;
|
||||
}
|
||||
}
|
||||
|
||||
exports.extname = function(path) {
|
||||
return splitPath(path)[3];
|
||||
if (startDot === -1 || end === -1 ||
|
||||
// We saw a non-dot character immediately before the dot
|
||||
preDotState === 0 ||
|
||||
// The (right-most) trimmed path component is exactly '..'
|
||||
preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
|
||||
return '';
|
||||
}
|
||||
return path.slice(startDot, end);
|
||||
};
|
||||
|
||||
function filter (xs, f) {
|
||||
@@ -1503,33 +1697,38 @@ module.exports={
|
||||
"engine",
|
||||
"ejs"
|
||||
],
|
||||
"version": "3.0.2",
|
||||
"version": "3.1.8",
|
||||
"author": "Matthew Eernisse <mde@fleegix.org> (http://fleegix.org)",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"ejs": "./bin/cli.js"
|
||||
},
|
||||
"main": "./lib/ejs.js",
|
||||
"jsdelivr": "ejs.min.js",
|
||||
"unpkg": "ejs.min.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": {},
|
||||
"dependencies": {
|
||||
"jake": "^10.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "^13.1.1",
|
||||
"eslint": "^4.14.0",
|
||||
"browserify": "^16.5.1",
|
||||
"eslint": "^6.8.0",
|
||||
"git-directory-deploy": "^1.5.1",
|
||||
"jake": "^10.3.1",
|
||||
"jsdoc": "^3.4.0",
|
||||
"jsdoc": "^4.0.2",
|
||||
"lru-cache": "^4.0.1",
|
||||
"mocha": "^5.0.5",
|
||||
"mocha": "^10.2.0",
|
||||
"uglify-js": "^3.3.16"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha",
|
||||
"postinstall": "node --harmony ./postinstall.js"
|
||||
"test": "mocha -u tdd"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user