1 | import process from 'process'; |
||
2 | import fs from 'fs'; |
||
3 | import { promisify, props, resolve } from 'bluebird'; |
||
4 | import { |
||
5 | T, |
||
6 | __, |
||
7 | assoc, |
||
8 | concat, |
||
9 | cond, |
||
10 | contains, |
||
11 | curry, |
||
12 | filter as filterWhere, |
||
13 | has, |
||
14 | keys, |
||
15 | map, |
||
16 | mapObjIndexed, |
||
17 | merge, |
||
18 | pathEq, |
||
19 | reduce, |
||
20 | toPairs, |
||
21 | when |
||
22 | } from 'ramda'; |
||
23 | import { cyan, green, red, yellow } from 'colors/safe'; |
||
24 | import { createPromptModule } from 'inquirer'; |
||
25 | import { validator, filter } from './types'; |
||
26 | import getAutocompleteSources, { compileClosure } from './autocomplete'; |
||
27 | |||
28 | /** |
||
29 | * Emits an info message to stdout |
||
30 | * |
||
31 | * @param {String} message |
||
32 | * @return {Promise} |
||
33 | */ |
||
34 | export const emitInfo = concat(' ℹ Info: ') & cyan & console.log & resolve; |
||
35 | |||
36 | /** |
||
37 | * Emits a warning to stdout |
||
38 | * |
||
39 | * @param {String} message |
||
40 | * @return {Promise} |
||
41 | */ |
||
42 | export const emitWarning = concat(' ⚠ Warning: ') & yellow & console.log & resolve; |
||
43 | |||
44 | /** |
||
45 | * Emits an error to stdout |
||
46 | * |
||
47 | * @param {String} message |
||
48 | * @return {Promise} |
||
49 | */ |
||
50 | export const emitError = concat(' ✗ Error: ') & red & console.log & resolve; |
||
51 | |||
52 | /** |
||
53 | * Emits a success message |
||
54 | * |
||
55 | * @param {String} message |
||
56 | * @return {Promise} |
||
57 | */ |
||
58 | export const emitSuccess = concat(' ✔ Success: ') & green & console.log & resolve; |
||
59 | |||
60 | /** |
||
61 | * Renames the keys of an object |
||
62 | * |
||
63 | * @sig {a: b} -> {a: *} -> {b: *} |
||
64 | */ |
||
65 | const renameKeys = curry((keysMap, obj) => reduce((acc, key) => |
||
66 | assoc(keysMap[key] || key, obj[key], acc), {}, keys(obj))); |
||
67 | |||
68 | const objectToChoices = toPairs & map(([value, name]) => ({ name, value })); |
||
69 | |||
70 | export const components = { |
||
71 | Calendar: ~{ |
||
72 | type: 'datetime', |
||
73 | format: ['m', '/', 'd', '/', 'yy'], |
||
74 | filter: filter.Calendar }, |
||
75 | Char: ({ length }) => ({ filter: filter.Char(length) }), |
||
76 | Checkbox: ~{ type: 'confirm' }, |
||
77 | Color: ~{ |
||
78 | type: 'chalk-pipe', |
||
79 | validate: validator.Color }, |
||
80 | DoubleRange: ({ from, to }) => ({ |
||
81 | filter: filter.Double, |
||
82 | validate: validator.Range(from, to) }), |
||
83 | DateTime: ~{ type: 'datetime' }, |
||
84 | Double: ~{ validate: validator.Double, filter: filter.Double }, |
||
85 | Email: ~{ validate: validator.Email }, |
||
86 | Integer: ~{ validate: validator.Integer, filter: filter.Integer }, |
||
87 | IntegerRange: ({ from, to }) => ({ |
||
88 | filter: filter.Integer, |
||
89 | validate: validator.Range(from, to) }), |
||
90 | IntegerMultiRange: ({ from, to }) => ({ |
||
91 | filter: filter.IntegerMultiRange, |
||
92 | validate: validator.IntegerMultiRange(from, to) }), |
||
93 | Natural: ~{ validate: validator.Natural, filter: filter.Integer }, |
||
94 | OneOf: ({ values }) => ({ |
||
95 | type: 'list', |
||
96 | choices: values, |
||
97 | validate: validator.OneOf(values) }), |
||
98 | String: ~{ type: 'input' }, |
||
99 | Url: ~{ validate: validator.Url }, |
||
100 | Money: ~{ validate: validator.Money, filter: filter.Money }, |
||
101 | SelectBox: ({ values }) => ({ |
||
102 | type: 'list', |
||
103 | choices: objectToChoices(values) }), |
||
104 | MultiSelectBox: ({ values }) => ({ |
||
105 | type: 'checkbox', |
||
106 | choices: objectToChoices(values) }), |
||
107 | File: ~{ type: 'filePath', basePath: process.cwd() } |
||
108 | }; |
||
109 | |||
110 | /** |
||
111 | * Custom autocomplete component |
||
112 | * |
||
113 | * @param {String} name |
||
114 | * @param {String} source |
||
115 | * @return {Function} |
||
116 | */ |
||
117 | const getAutocompleteComponent = (name, source) => { |
||
118 | if (!source) { |
||
119 | throw new Error(`aren't you missing 'autocomplete/${name}.js'?`); |
||
120 | } |
||
121 | |||
122 | return ~{ type: 'autocomplete', source: compileClosure(name, source) }; |
||
123 | }; |
||
124 | |||
125 | /** |
||
126 | * Converts a Rung CLI question object to an Inquirer question object |
||
127 | * |
||
128 | * @author Marcelo Haskell Camargo |
||
129 | * @param {String[]} sources |
||
130 | * @param {String} name |
||
0 ignored issues
–
show
Documentation
introduced
by
![]() |
|||
131 | * @param {Object} config |
||
0 ignored issues
–
show
|
|||
132 | * @return {Object} |
||
133 | */ |
||
134 | const toInquirerQuestion = curry((sources, [name, config]) => { |
||
135 | const component = components |
||
136 | | cond([ |
||
137 | [~(config.type.name === 'AutoComplete'), ~getAutocompleteComponent(name, sources[name])], |
||
138 | [has(config.type.name), _[config.type.name]], |
||
0 ignored issues
–
show
The variable
_ seems to be never declared. If this is a global, consider adding a /** global: _ */ comment.
This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed. To learn more about declaring variables in Javascript, see the MDN. ![]() |
|||
139 | [T, _.String] |
||
140 | ]); |
||
141 | |||
142 | return merge(config |
||
143 | | renameKeys({ description: 'message' }) |
||
144 | | merge(__, { name }), component(config.type)); |
||
145 | }); |
||
146 | |||
147 | const readFile = promisify(fs.readFile); |
||
148 | |||
149 | /** |
||
150 | * Opens the provided files and returns them as node buffers |
||
151 | * |
||
152 | * @param {String[]} fields |
||
153 | * @param {Object} answers |
||
0 ignored issues
–
show
|
|||
154 | * @return {Promise} |
||
155 | */ |
||
156 | const openFiles = fields => |
||
157 | mapObjIndexed((value, param) => value |
||
158 | | when(~contains(param, fields), concat(process.cwd() + '/') & readFile)) |
||
159 | & props; |
||
160 | |||
161 | /** |
||
162 | * Returns the pure JS values from received questions that will be answered |
||
163 | * |
||
164 | * @author Marcelo Haskell Camargo |
||
165 | * @param {Object} questions |
||
166 | * @return {Promise} answers for the questions by key |
||
167 | */ |
||
168 | export function ask(questions) { |
||
169 | const DatePickerPrompt = require('inquirer-datepicker-prompt'); |
||
170 | const ChalkPipe = require('inquirer-chalk-pipe'); |
||
171 | const AutocompletePrompt = require('inquirer-autocomplete-prompt'); |
||
172 | const FilePath = require('inquirer-file-path'); |
||
173 | const fileFields = questions |
||
174 | | filterWhere(pathEq(['type', 'name'], 'File')) |
||
175 | | keys; |
||
176 | |||
177 | const prompt = createPromptModule(); |
||
178 | prompt.registerPrompt('datetime', DatePickerPrompt); |
||
179 | prompt.registerPrompt('chalk-pipe', ChalkPipe); |
||
180 | prompt.registerPrompt('autocomplete', AutocompletePrompt); |
||
181 | prompt.registerPrompt('filePath', FilePath); |
||
182 | return getAutocompleteSources() |
||
183 | .then(autocompleteSources => questions |
||
184 | | toPairs |
||
185 | | map(toInquirerQuestion(autocompleteSources)) |
||
186 | | prompt) |
||
187 | .then(openFiles(fileFields)); |
||
188 | } |
||
189 |