public.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. /*
  2. * Copyright (c) 2015-present, Vitaly Tomilov
  3. *
  4. * See the LICENSE file at the top-level directory of this distribution
  5. * for licensing information.
  6. *
  7. * Removal or modification of this copyright notice is prohibited.
  8. */
  9. const {assert} = require('../assert');
  10. const npm = {
  11. fs: require('fs'),
  12. path: require('path'),
  13. utils: require('./'),
  14. package: require('../../package.json')
  15. };
  16. /**
  17. * @method utils.camelize
  18. * @description
  19. * Camelizes a text string.
  20. *
  21. * Case-changing characters include:
  22. * - _hyphen_
  23. * - _underscore_
  24. * - _period_
  25. * - _space_
  26. *
  27. * @param {string} text
  28. * Input text string.
  29. *
  30. * @returns {string}
  31. * Camelized text string.
  32. *
  33. * @see
  34. * {@link utils.camelizeVar camelizeVar}
  35. *
  36. */
  37. function camelize(text) {
  38. text = text.replace(/[-_\s.]+(.)?/g, (_, c) => c ? c.toUpperCase() : '');
  39. return text.substring(0, 1).toLowerCase() + text.substring(1);
  40. }
  41. /**
  42. * @method utils.camelizeVar
  43. * @description
  44. * Camelizes a text string, while making it compliant with JavaScript variable names:
  45. * - contains symbols `a-z`, `A-Z`, `0-9`, `_` and `$`
  46. * - cannot have leading digits
  47. *
  48. * First, it removes all symbols that do not meet the above criteria, except for _hyphen_, _period_ and _space_,
  49. * and then it forwards into {@link utils.camelize camelize}.
  50. *
  51. * @param {string} text
  52. * Input text string.
  53. *
  54. * If it doesn't contain any symbols to make up a valid variable name, the result will be an empty string.
  55. *
  56. * @returns {string}
  57. * Camelized text string that can be used as an open property name.
  58. *
  59. * @see
  60. * {@link utils.camelize camelize}
  61. *
  62. */
  63. function camelizeVar(text) {
  64. text = text.replace(/[^a-zA-Z0-9$_\-\s.]/g, '').replace(/^[0-9_\-\s.]+/, '');
  65. return camelize(text);
  66. }
  67. function _enumSql(dir, options, cb, namePath) {
  68. const tree = {};
  69. npm.fs.readdirSync(dir).forEach(file => {
  70. let stat;
  71. const fullPath = npm.path.join(dir, file);
  72. try {
  73. stat = npm.fs.statSync(fullPath);
  74. } catch (e) {
  75. // while it is very easy to test manually, it is very difficult to test for
  76. // access-denied errors automatically; therefore excluding from the coverage:
  77. // istanbul ignore next
  78. if (options.ignoreErrors) {
  79. return; // on to the next file/folder;
  80. }
  81. // istanbul ignore next
  82. throw e;
  83. }
  84. if (stat.isDirectory()) {
  85. if (options.recursive) {
  86. const dirName = camelizeVar(file);
  87. const np = namePath ? (namePath + '.' + dirName) : dirName;
  88. const t = _enumSql(fullPath, options, cb, np);
  89. if (Object.keys(t).length) {
  90. if (!dirName.length || dirName in tree) {
  91. if (!options.ignoreErrors) {
  92. throw new Error('Empty or duplicate camelized folder name: ' + fullPath);
  93. }
  94. }
  95. tree[dirName] = t;
  96. }
  97. }
  98. } else {
  99. if (npm.path.extname(file).toLowerCase() === '.sql') {
  100. const name = camelizeVar(file.replace(/\.[^/.]+$/, ''));
  101. if (!name.length || name in tree) {
  102. if (!options.ignoreErrors) {
  103. throw new Error('Empty or duplicate camelized file name: ' + fullPath);
  104. }
  105. }
  106. tree[name] = fullPath;
  107. if (cb) {
  108. const result = cb(fullPath, name, namePath ? (namePath + '.' + name) : name);
  109. if (result !== undefined) {
  110. tree[name] = result;
  111. }
  112. }
  113. }
  114. }
  115. });
  116. return tree;
  117. }
  118. /**
  119. * @method utils.enumSql
  120. * @description
  121. * Synchronously enumerates all SQL files (within a given directory) into a camelized SQL tree.
  122. *
  123. * All property names within the tree are camelized via {@link utils.camelizeVar camelizeVar},
  124. * so they can be used in the code directly, as open property names.
  125. *
  126. * @param {string} dir
  127. * Directory path where SQL files are located, either absolute or relative to the current directory.
  128. *
  129. * SQL files are identified by using `.sql` extension (case-insensitive).
  130. *
  131. * @param {{}} [options]
  132. * Search options.
  133. *
  134. * @param {boolean} [options.recursive=false]
  135. * Include sub-directories into the search.
  136. *
  137. * Sub-directories without SQL files will be skipped from the result.
  138. *
  139. * @param {boolean} [options.ignoreErrors=false]
  140. * Ignore the following types of errors:
  141. * - access errors, when there is no read access to a file or folder
  142. * - empty or duplicate camelized property names
  143. *
  144. * This flag does not affect errors related to invalid input parameters, or if you pass in a
  145. * non-existing directory.
  146. *
  147. * @param {function} [cb]
  148. * A callback function that takes three arguments:
  149. * - `file` - SQL file path, relative or absolute, according to how you specified the search directory
  150. * - `name` - name of the property that represents the SQL file
  151. * - `path` - property resolution path (full property name)
  152. *
  153. * If the function returns anything other than `undefined`, it overrides the corresponding property value in the tree.
  154. *
  155. * @returns {object}
  156. * Camelized SQL tree object, with each value being an SQL file path (unless changed via the callback).
  157. *
  158. * @example
  159. *
  160. * // simple SQL tree generation for further processing:
  161. * const tree = pgp.utils.enumSql('../sql', {recursive: true});
  162. *
  163. * @example
  164. *
  165. * // generating an SQL tree for dynamic use of names:
  166. * const sql = pgp.utils.enumSql(__dirname, {recursive: true}, file => {
  167. * return new pgp.QueryFile(file, {minify: true});
  168. * });
  169. *
  170. * @example
  171. *
  172. * const {join: joinPath} = require('path');
  173. *
  174. * // replacing each relative path in the tree with a full one:
  175. * const tree = pgp.utils.enumSql('../sql', {recursive: true}, file => {
  176. * return joinPath(__dirname, file);
  177. * });
  178. *
  179. */
  180. function enumSql(dir, options, cb) {
  181. if (!npm.utils.isText(dir)) {
  182. throw new TypeError('Parameter \'dir\' must be a non-empty text string.');
  183. }
  184. options = assert(options, ['recursive', 'ignoreErrors']);
  185. cb = (typeof cb === 'function') ? cb : null;
  186. return _enumSql(dir, options, cb, '');
  187. }
  188. /**
  189. * @method utils.taskArgs
  190. * @description
  191. * Normalizes/prepares arguments for tasks and transactions.
  192. *
  193. * Its main purpose is to simplify adding custom methods {@link Database#task task}, {@link Database#taskIf taskIf},
  194. * {@link Database#tx tx} and {@link Database#txIf txIf} within event {@link event:extend extend}, as the those methods use fairly
  195. * complex logic for parsing inputs.
  196. *
  197. * @param args {Object}
  198. * Array-like object of `arguments` that was passed into the method. It is expected that the `arguments`
  199. * are always made of two parameters - `(options, cb)`, same as all the default task/transaction methods.
  200. *
  201. * And if your custom method needs additional parameters, they should be passed in as extra properties within `options`.
  202. *
  203. * @returns {Array}
  204. * Array of arguments that can be passed into a task or transaction.
  205. *
  206. * It is extended with properties `options` and `cb` to access the corresponding array elements `[0]` and `[1]` by name.
  207. *
  208. * @example
  209. *
  210. * // Registering a custom transaction method that assigns a default Transaction Mode:
  211. *
  212. * const initOptions = {
  213. * extend: obj => {
  214. * obj.myTx = function(options, cb) {
  215. * const args = pgp.utils.taskArgs(arguments); // prepare arguments
  216. *
  217. * if (!('mode' in args.options)) {
  218. * // if no 'mode' was specified, set default for transaction mode:
  219. * args.options.mode = myTxModeObject; // of type pgp.txMode.TransactionMode
  220. * }
  221. *
  222. * return obj.tx.apply(this, args);
  223. * // or explicitly, if needed:
  224. * // return obj.tx.call(this, args.options, args.cb);
  225. * }
  226. * }
  227. * };
  228. *
  229. */
  230. function taskArgs(args) {
  231. if (!args || typeof args.length !== 'number') {
  232. throw new TypeError('Parameter \'args\' must be an array-like object of arguments.');
  233. }
  234. let options = args[0], cb;
  235. if (typeof options === 'function') {
  236. cb = options;
  237. options = {};
  238. if (cb.name) {
  239. options.tag = cb.name;
  240. }
  241. } else {
  242. if (typeof args[1] === 'function') {
  243. cb = args[1];
  244. }
  245. if (typeof options === 'string' || typeof options === 'number') {
  246. options = {tag: options};
  247. } else {
  248. options = (typeof options === 'object' && options) || {};
  249. if (!('tag' in options) && cb && cb.name) {
  250. options.tag = cb.name;
  251. }
  252. }
  253. }
  254. const res = [options, cb];
  255. Object.defineProperty(res, 'options', {
  256. get: function () {
  257. return this[0];
  258. },
  259. set: function (newValue) {
  260. this[0] = newValue;
  261. },
  262. enumerable: true
  263. });
  264. Object.defineProperty(res, 'cb', {
  265. get: function () {
  266. return this[1];
  267. },
  268. set: function (newValue) {
  269. this[1] = newValue;
  270. },
  271. enumerable: true
  272. });
  273. return res;
  274. }
  275. /**
  276. * @namespace utils
  277. *
  278. * @description
  279. * Namespace for general-purpose static functions, available as `pgp.utils`, before and after initializing the library.
  280. *
  281. * @property {function} camelize
  282. * {@link utils.camelize camelize} - camelizes a text string
  283. *
  284. * @property {function} camelizeVar
  285. * {@link utils.camelizeVar camelizeVar} - camelizes a text string as a variable
  286. *
  287. * @property {function} enumSql
  288. * {@link utils.enumSql enumSql} - enumerates SQL files in a directory
  289. *
  290. * @property {function} taskArgs
  291. * {@link utils.taskArgs taskArgs} - prepares arguments for tasks and transactions
  292. */
  293. module.exports = {
  294. camelize,
  295. camelizeVar,
  296. enumSql,
  297. taskArgs
  298. };