Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

api.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
  4. var path = _interopDefault(require('path'));
  5. var minimatch = _interopDefault(require('minimatch'));
  6. var createDebug = _interopDefault(require('debug'));
  7. var objectSchema = require('@humanwhocodes/object-schema');
  8. /**
  9. * @fileoverview ConfigSchema
  10. * @author Nicholas C. Zakas
  11. */
  12. //------------------------------------------------------------------------------
  13. // Helpers
  14. //------------------------------------------------------------------------------
  15. /**
  16. * Assets that a given value is an array.
  17. * @param {*} value The value to check.
  18. * @returns {void}
  19. * @throws {TypeError} When the value is not an array.
  20. */
  21. function assertIsArray(value) {
  22. if (!Array.isArray(value)) {
  23. throw new TypeError('Expected value to be an array.');
  24. }
  25. }
  26. /**
  27. * Assets that a given value is an array containing only strings and functions.
  28. * @param {*} value The value to check.
  29. * @returns {void}
  30. * @throws {TypeError} When the value is not an array of strings and functions.
  31. */
  32. function assertIsArrayOfStringsAndFunctions(value, name) {
  33. assertIsArray(value);
  34. if (value.some(item => typeof item !== 'string' && typeof item !== 'function')) {
  35. throw new TypeError('Expected array to only contain strings.');
  36. }
  37. }
  38. //------------------------------------------------------------------------------
  39. // Exports
  40. //------------------------------------------------------------------------------
  41. /**
  42. * The base schema that every ConfigArray uses.
  43. * @type Object
  44. */
  45. const baseSchema = Object.freeze({
  46. name: {
  47. required: false,
  48. merge() {
  49. return undefined;
  50. },
  51. validate(value) {
  52. if (typeof value !== 'string') {
  53. throw new TypeError('Property must be a string.');
  54. }
  55. }
  56. },
  57. files: {
  58. required: false,
  59. merge() {
  60. return undefined;
  61. },
  62. validate(value) {
  63. // first check if it's an array
  64. assertIsArray(value);
  65. // then check each member
  66. value.forEach(item => {
  67. if (Array.isArray(item)) {
  68. assertIsArrayOfStringsAndFunctions(item);
  69. } else if (typeof item !== 'string' && typeof item !== 'function') {
  70. throw new TypeError('Items must be a string, a function, or an array of strings and functions.');
  71. }
  72. });
  73. }
  74. },
  75. ignores: {
  76. required: false,
  77. merge() {
  78. return undefined;
  79. },
  80. validate: assertIsArrayOfStringsAndFunctions
  81. }
  82. });
  83. /**
  84. * @fileoverview ConfigArray
  85. * @author Nicholas C. Zakas
  86. */
  87. //------------------------------------------------------------------------------
  88. // Helpers
  89. //------------------------------------------------------------------------------
  90. const debug = createDebug('@hwc/config-array');
  91. const MINIMATCH_OPTIONS = {
  92. matchBase: true
  93. };
  94. /**
  95. * Shorthand for checking if a value is a string.
  96. * @param {any} value The value to check.
  97. * @returns {boolean} True if a string, false if not.
  98. */
  99. function isString(value) {
  100. return typeof value === 'string';
  101. }
  102. /**
  103. * Normalizes a `ConfigArray` by flattening it and executing any functions
  104. * that are found inside.
  105. * @param {Array} items The items in a `ConfigArray`.
  106. * @param {Object} context The context object to pass into any function
  107. * found.
  108. * @returns {Array} A flattened array containing only config objects.
  109. * @throws {TypeError} When a config function returns a function.
  110. */
  111. async function normalize(items, context) {
  112. // TODO: Allow async config functions
  113. function *flatTraverse(array) {
  114. for (let item of array) {
  115. if (typeof item === 'function') {
  116. item = item(context);
  117. }
  118. if (Array.isArray(item)) {
  119. yield * flatTraverse(item);
  120. } else if (typeof item === 'function') {
  121. throw new TypeError('A config function can only return an object or array.');
  122. } else {
  123. yield item;
  124. }
  125. }
  126. }
  127. return [...flatTraverse(items)];
  128. }
  129. /**
  130. * Determines if a given file path is matched by a config. If the config
  131. * has no `files` field, then it matches; otherwise, if a `files` field
  132. * is present then we match the globs in `files` and exclude any globs in
  133. * `ignores`.
  134. * @param {string} filePath The absolute file path to check.
  135. * @param {Object} config The config object to check.
  136. * @returns {boolean} True if the file path is matched by the config,
  137. * false if not.
  138. */
  139. function pathMatches(filePath, basePath, config) {
  140. // a config without a `files` field always matches
  141. if (!config.files) {
  142. return true;
  143. }
  144. // if files isn't an array, throw an error
  145. if (!Array.isArray(config.files) || config.files.length === 0) {
  146. throw new TypeError('The files key must be a non-empty array.');
  147. }
  148. const relativeFilePath = path.relative(basePath, filePath);
  149. // match both strings and functions
  150. const match = pattern => {
  151. if (isString(pattern)) {
  152. return minimatch(relativeFilePath, pattern, MINIMATCH_OPTIONS);
  153. }
  154. if (typeof pattern === 'function') {
  155. return pattern(filePath);
  156. }
  157. };
  158. // check for all matches to config.files
  159. let matches = config.files.some(pattern => {
  160. if (Array.isArray(pattern)) {
  161. return pattern.every(match);
  162. }
  163. return match(pattern);
  164. });
  165. /*
  166. * If the file path matches the config.files patterns, then check to see
  167. * if there are any files to ignore.
  168. */
  169. if (matches && config.ignores) {
  170. matches = !config.ignores.some(pattern => {
  171. return minimatch(filePath, pattern, MINIMATCH_OPTIONS);
  172. });
  173. }
  174. return matches;
  175. }
  176. /**
  177. * Ensures that a ConfigArray has been normalized.
  178. * @param {ConfigArray} configArray The ConfigArray to check.
  179. * @returns {void}
  180. * @throws {Error} When the `ConfigArray` is not normalized.
  181. */
  182. function assertNormalized(configArray) {
  183. // TODO: Throw more verbose error
  184. if (!configArray.isNormalized()) {
  185. throw new Error('ConfigArray must be normalized to perform this operation.');
  186. }
  187. }
  188. //------------------------------------------------------------------------------
  189. // Public Interface
  190. //------------------------------------------------------------------------------
  191. const ConfigArraySymbol = {
  192. isNormalized: Symbol('isNormalized'),
  193. configCache: Symbol('configCache'),
  194. schema: Symbol('schema'),
  195. finalizeConfig: Symbol('finalizeConfig'),
  196. preprocessConfig: Symbol('preprocessConfig')
  197. };
  198. /**
  199. * Represents an array of config objects and provides method for working with
  200. * those config objects.
  201. */
  202. class ConfigArray extends Array {
  203. /**
  204. * Creates a new instance of ConfigArray.
  205. * @param {Iterable|Function|Object} configs An iterable yielding config
  206. * objects, or a config function, or a config object.
  207. * @param {string} [options.basePath=""] The path of the config file
  208. * @param {boolean} [options.normalized=false] Flag indicating if the
  209. * configs have already been normalized.
  210. * @param {Object} [options.schema] The additional schema
  211. * definitions to use for the ConfigArray schema.
  212. */
  213. constructor(configs, { basePath = '', normalized = false, schema: customSchema } = {}) {
  214. super();
  215. /**
  216. * Tracks if the array has been normalized.
  217. * @property isNormalized
  218. * @type boolean
  219. * @private
  220. */
  221. this[ConfigArraySymbol.isNormalized] = normalized;
  222. /**
  223. * The schema used for validating and merging configs.
  224. * @property schema
  225. * @type ObjectSchema
  226. * @private
  227. */
  228. this[ConfigArraySymbol.schema] = new objectSchema.ObjectSchema({
  229. ...customSchema,
  230. ...baseSchema
  231. });
  232. /**
  233. * The path of the config file that this array was loaded from.
  234. * This is used to calculate filename matches.
  235. * @property basePath
  236. * @type string
  237. */
  238. this.basePath = basePath;
  239. /**
  240. * A cache to store calculated configs for faster repeat lookup.
  241. * @property configCache
  242. * @type Map
  243. * @private
  244. */
  245. this[ConfigArraySymbol.configCache] = new Map();
  246. // load the configs into this array
  247. if (Array.isArray(configs)) {
  248. this.push(...configs);
  249. } else {
  250. this.push(configs);
  251. }
  252. }
  253. /**
  254. * Prevent normal array methods from creating a new `ConfigArray` instance.
  255. * This is to ensure that methods such as `slice()` won't try to create a
  256. * new instance of `ConfigArray` behind the scenes as doing so may throw
  257. * an error due to the different constructor signature.
  258. * @returns {Function} The `Array` constructor.
  259. */
  260. static get [Symbol.species]() {
  261. return Array;
  262. }
  263. /**
  264. * Returns the `files` globs from every config object in the array.
  265. * Negated patterns (those beginning with `!`) are not returned.
  266. * This can be used to determine which files will be matched by a
  267. * config array or to use as a glob pattern when no patterns are provided
  268. * for a command line interface.
  269. * @returns {string[]} An array of string patterns.
  270. */
  271. get files() {
  272. assertNormalized(this);
  273. const result = [];
  274. for (const config of this) {
  275. if (config.files) {
  276. config.files.forEach(filePattern => {
  277. if (Array.isArray(filePattern)) {
  278. result.push(...filePattern.filter(pattern => {
  279. return isString(pattern) && !pattern.startsWith('!');
  280. }));
  281. } else if (isString(filePattern) && !filePattern.startsWith('!')) {
  282. result.push(filePattern);
  283. }
  284. });
  285. }
  286. }
  287. return result;
  288. }
  289. /**
  290. * Returns the file globs that should always be ignored regardless of
  291. * the matching `files` fields in any configs. This is necessary to mimic
  292. * the behavior of things like .gitignore and .eslintignore, allowing a
  293. * globbing operation to be faster.
  294. * @returns {string[]} An array of string patterns to be ignored.
  295. */
  296. get ignores() {
  297. assertNormalized(this);
  298. const result = [];
  299. for (const config of this) {
  300. if (config.ignores && !config.files) {
  301. result.push(...config.ignores.filter(isString));
  302. }
  303. }
  304. return result;
  305. }
  306. /**
  307. * Indicates if the config array has been normalized.
  308. * @returns {boolean} True if the config array is normalized, false if not.
  309. */
  310. isNormalized() {
  311. return this[ConfigArraySymbol.isNormalized];
  312. }
  313. /**
  314. * Normalizes a config array by flattening embedded arrays and executing
  315. * config functions.
  316. * @param {ConfigContext} context The context object for config functions.
  317. * @returns {ConfigArray} A new ConfigArray instance that is normalized.
  318. */
  319. async normalize(context = {}) {
  320. if (!this.isNormalized()) {
  321. const normalizedConfigs = await normalize(this, context);
  322. this.length = 0;
  323. this.push(...normalizedConfigs.map(this[ConfigArraySymbol.preprocessConfig]));
  324. this[ConfigArraySymbol.isNormalized] = true;
  325. // prevent further changes
  326. Object.freeze(this);
  327. }
  328. return this;
  329. }
  330. /**
  331. * Finalizes the state of a config before being cached and returned by
  332. * `getConfig()`. Does nothing by default but is provided to be
  333. * overridden by subclasses as necessary.
  334. * @param {Object} config The config to finalize.
  335. * @returns {Object} The finalized config.
  336. */
  337. [ConfigArraySymbol.finalizeConfig](config) {
  338. return config;
  339. }
  340. /**
  341. * Preprocesses a config during the normalization process. This is the
  342. * method to override if you want to convert an array item before it is
  343. * validated for the first time. For example, if you want to replace a
  344. * string with an object, this is the method to override.
  345. * @param {Object} config The config to preprocess.
  346. * @returns {Object} The config to use in place of the argument.
  347. */
  348. [ConfigArraySymbol.preprocessConfig](config) {
  349. return config;
  350. }
  351. /**
  352. * Returns the config object for a given file path.
  353. * @param {string} filePath The complete path of a file to get a config for.
  354. * @returns {Object} The config object for this file.
  355. */
  356. getConfig(filePath) {
  357. assertNormalized(this);
  358. // first check the cache to avoid duplicate work
  359. let finalConfig = this[ConfigArraySymbol.configCache].get(filePath);
  360. if (finalConfig) {
  361. return finalConfig;
  362. }
  363. // No config found in cache, so calculate a new one
  364. const matchingConfigs = [];
  365. for (const config of this) {
  366. if (pathMatches(filePath, this.basePath, config)) {
  367. debug(`Matching config found for ${filePath}`);
  368. matchingConfigs.push(config);
  369. } else {
  370. debug(`No matching config found for ${filePath}`);
  371. }
  372. }
  373. finalConfig = matchingConfigs.reduce((result, config) => {
  374. return this[ConfigArraySymbol.schema].merge(result, config);
  375. }, {}, this);
  376. finalConfig = this[ConfigArraySymbol.finalizeConfig](finalConfig);
  377. this[ConfigArraySymbol.configCache].set(filePath, finalConfig);
  378. return finalConfig;
  379. }
  380. }
  381. exports.ConfigArray = ConfigArray;
  382. exports.ConfigArraySymbol = ConfigArraySymbol;