index.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. /*!
  2. * parse-git-config <https://github.com/jonschlinkert/parse-git-config>
  3. *
  4. * Copyright (c) 2015-present, Jon Schlinkert.
  5. * Released under the MIT License.
  6. */
  7. 'use strict';
  8. const fs = require('fs');
  9. const os = require('os');
  10. const path = require('path');
  11. const util = require('util');
  12. const ini = require('ini');
  13. const configPath = require('git-config-path');
  14. const expand = str => (str ? str.replace(/^~/, os.homedir()) : '');
  15. /**
  16. * Asynchronously parse a `.git/config` file. If only the callback is passed,
  17. * the `.git/config` file relative to `process.cwd()` is used.
  18. *
  19. * ```js
  20. * parse((err, config) => {
  21. * if (err) throw err;
  22. * // do stuff with config
  23. * });
  24. *
  25. * // or, using async/await
  26. * (async () => {
  27. * console.log(await parse());
  28. * console.log(await parse({ cwd: 'foo' }));
  29. * console.log(await parse({ cwd: 'foo', path: 'some/.git/config' }));
  30. * })();
  31. * ```
  32. * @name parse
  33. * @param {Object|String|Function} `options` Options with `cwd` or `path`, the cwd to use, or the callback function.
  34. * @param {Function} `callback` callback function if the first argument is options or cwd.
  35. * @return {Object}
  36. * @api public
  37. */
  38. const parse = (options, callback) => {
  39. if (typeof options === 'function') {
  40. callback = options;
  41. options = null;
  42. }
  43. if (typeof callback !== 'function') {
  44. return parse.promise(options);
  45. }
  46. return parse.promise(options)
  47. .then(config => callback(null, config))
  48. .catch(callback);
  49. };
  50. parse.promise = options => {
  51. let filepath = parse.resolveConfigPath(options);
  52. let read = util.promisify(fs.readFile);
  53. let stat = util.promisify(fs.stat);
  54. if (!filepath) return Promise.resolve(null);
  55. return stat(filepath)
  56. .then(() => read(filepath, 'utf8'))
  57. .then(str => {
  58. if (options && options.include === true) {
  59. str = injectInclude(str, path.resolve(path.dirname(filepath)));
  60. }
  61. return parseIni(str, options);
  62. });
  63. };
  64. /**
  65. * Synchronously parse a `.git/config` file. If no arguments are passed,
  66. * the `.git/config` file relative to `process.cwd()` is used.
  67. *
  68. * ```js
  69. * console.log(parse.sync());
  70. * console.log(parse.sync({ cwd: 'foo' }));
  71. * console.log(parse.sync({ cwd: 'foo', path: 'some/.git/config' }));
  72. * ```
  73. * @name .sync
  74. * @param {Object|String} `options` Options with `cwd` or `path`, or the cwd to use.
  75. * @return {Object}
  76. * @api public
  77. */
  78. parse.sync = options => {
  79. let filepath = parse.resolveConfigPath(options);
  80. if (filepath && fs.existsSync(filepath)) {
  81. let input = fs.readFileSync(filepath, 'utf8');
  82. if (options && options.include === true) {
  83. let cwd = path.resolve(path.dirname(filepath));
  84. input = injectInclude(input, cwd);
  85. }
  86. return parseIni(input, options);
  87. }
  88. return {};
  89. };
  90. /**
  91. * Resolve the git config path
  92. */
  93. parse.resolveConfigPath = options => {
  94. if (typeof options === 'string') options = { type: options };
  95. const opts = Object.assign({ cwd: process.cwd() }, options);
  96. const fp = opts.path ? expand(opts.path) : configPath(opts.type);
  97. return fp ? path.resolve(opts.cwd, fp) : null;
  98. };
  99. /**
  100. * Deprecated: use `.resolveConfigPath` instead
  101. */
  102. parse.resolve = options => parse.resolveConfigPath(options);
  103. /**
  104. * Returns an object with only the properties that had ini-style keys
  105. * converted to objects.
  106. *
  107. * ```js
  108. * const config = parse.sync({ path: '/path/to/.gitconfig' });
  109. * const obj = parse.expandKeys(config);
  110. * ```
  111. * @name .expandKeys
  112. * @param {Object} `config` The parsed git config object.
  113. * @return {Object}
  114. * @api public
  115. */
  116. parse.expandKeys = config => {
  117. for (let key of Object.keys(config)) {
  118. let m = /(\S+) "(.*)"/.exec(key);
  119. if (!m) continue;
  120. let prop = m[1];
  121. config[prop] = config[prop] || {};
  122. config[prop][m[2]] = config[key];
  123. delete config[key];
  124. }
  125. return config;
  126. };
  127. function parseIni(str, options) {
  128. let opts = Object.assign({}, options);
  129. str = str.replace(/\[(\S+) "(.*)"\]/g, (m, $1, $2) => {
  130. return $1 && $2 ? `[${$1} "${$2.split('.').join('\\.')}"]` : m;
  131. });
  132. let config = ini.parse(str);
  133. if (opts.expandKeys === true) {
  134. return parse.expandKeys(config);
  135. }
  136. return config;
  137. }
  138. function injectInclude(input, cwd) {
  139. let lines = input.split('\n').filter(line => line.trim() !== '');
  140. let len = lines.length;
  141. let res = [];
  142. for (let i = 0; i < len; i++) {
  143. let line = lines[i];
  144. if (line.indexOf('[include]') === 0) {
  145. let filepath = lines[i + 1].replace(/^\s*path\s*=\s*/, '');
  146. let fp = path.resolve(cwd, expand(filepath));
  147. res.push(fs.readFileSync(fp));
  148. } else {
  149. res.push(line);
  150. }
  151. }
  152. return res.join('\n');
  153. }
  154. /**
  155. * Expose `parse`
  156. */
  157. module.exports = parse;