index.js 4.6 KB

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