Skip to content

Commit a056146

Browse files
committed
refactor(main): make use inquirer
1 parent 4e616ac commit a056146

1 file changed

Lines changed: 131 additions & 130 deletions

File tree

lib/main.js

Lines changed: 131 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const path = require('path')
22
const fs = require('fs')
33
const _ = require('lodash')
44
const execSync = require('child_process').execSync
5-
const prompt = require('prompt-sync')()
5+
const inquirer = require('inquirer');
66
const spellcheck = require('nodehun-sentences')
77
const nodehun = require('nodehun')
88
const emoji = require('node-emoji')
@@ -12,91 +12,69 @@ const colors = require('./colors')
1212
//
1313
// Functions
1414
//
15-
const formatChoices = (choices) => {
16-
const maxNameLength = _(choices).map('name').map((n) => { return n.length }).max()
17-
const maxEmojiLength = _(choices).map('emoji').map((n) => { return n.length }).max()
18-
19-
return _.map(choices, (choice) => {
20-
const n = _.padEnd(choice.name, maxNameLength)
21-
const e = maxEmojiLength === 0 ? '' : _.padEnd(choice.emoji, 3)
22-
return [n, e, choice.description].join(" ")
23-
}).join("\n")
15+
const getCurrentBranchName = () => {
16+
return execSync("git rev-parse --abbrev-ref HEAD").toString().trim()
2417
}
2518

26-
const select = (term, definition) => {
27-
const none = "(none)"
28-
const header = `Select ${term}: `
29-
const choices = _.map(definition.values, function (value) {
30-
const name = value.name
31-
const description = value.description
32-
let emojiStr = ''
19+
const branchText = (definition) => {
20+
const currentBranchName = getCurrentBranchName()
21+
const regExp = new RegExp(definition.regExp, definition.regExpFlag)
22+
const matchNum = definition.regExpMatchNum || 1
23+
const match = currentBranchName.match(regExp)
24+
const value = match ? match[matchNum] : ''
25+
return value
26+
}
3327

34-
if (/^:.+:$/.test(name) && emoji.hasEmoji(name)) emojiStr = emoji.get(name)
35-
return { name: name, emoji: emojiStr, description: description }
36-
})
37-
if (!definition.required) choices.unshift({ name: '', emoji: '', description: none })
28+
const checkAddedFile = () => {
29+
const addedFiles = execSync(`git diff --name-only --cached 2> /dev/null`).toString().trim()
30+
if (_.isEmpty(addedFiles)) throw new Error(`no changes added to commit.`)
31+
}
3832

39-
const command = `echo "${formatChoices(choices).replace(/"/g, '\\"')}" | fzf --reverse --cycle --header="${header}"`
33+
const checkSpell = (message) => {
34+
const dictionaryBase = path.dirname(require.resolve('dictionary-en-us'))
35+
const hunspell = new nodehun(
36+
fs.readFileSync(path.join(dictionaryBase, 'index.aff')),
37+
fs.readFileSync(path.join(dictionaryBase, 'index.dic'))
38+
)
4039

41-
const selectedValue = execSync(command, { stdio: ['inherit', 'pipe', 'inherit'], shell: true }).toString().trim()
42-
const value = selectedValue.split(" ")[0]
43-
const v = value === none ? '' : value
44-
console.log(`${header}${value}`)
45-
return v
46-
}
40+
spellcheck(hunspell, message, (e, typos) => {
41+
if (e) throw e
4742

48-
const inputString = (term, _definition) => {
49-
return prompt(`${colors.message}Enter ${term}: ${colors.reset}`)
43+
_.forEach(typos, (typo) => {
44+
process.stdout.write(`${colors.warning}Is '${typo.word}' misspelled? ${colors.reset}`)
45+
console.log(`${colors.warning}Did you mean that? ${typo.suggestions.map((s) => { return `'${s}'` }).join(', ')}${colors.reset}`)
46+
})
47+
})
5048
}
5149

52-
const inputText = (term, _definition) => {
53-
console.log(`${colors.message}Enter ${term} multiline:${colors.reset}`)
54-
let values = []
55-
let value = ""
50+
const createChoicesNames = (values) => {
51+
const maxLength = _(values).map('name').map((n) => { return n.length }).max()
5652

57-
do {
58-
value = prompt()
59-
values.push(value)
60-
} while (!_.isEmpty(value))
53+
const result = {}
54+
_.forEach(values, (value) => {
55+
const temp = []
56+
temp.push(_.padEnd(value.name, maxLength))
57+
if (/^:.+:$/.test(value.name) && emoji.hasEmoji(value.name)) temp.push(emoji.get(value.name) + ' ')
58+
temp.push(value.description)
6159

62-
return values.join("\n").trim()
60+
result[value.name] = temp.join(' ')
61+
})
62+
return result
6363
}
6464

65-
const getCurrentBranchName = () => {
66-
return execSync("git rev-parse --abbrev-ref HEAD").toString().trim()
67-
}
65+
const createChoices = (definition) => {
66+
const names = createChoicesNames(definition.values)
6867

69-
const branchText = (_term, definition) => {
70-
const currentBranchName = getCurrentBranchName()
71-
const regExp = new RegExp(definition.regExp, definition.regExpFlag)
72-
const matchNum = definition.regExpMatchNum || 1
73-
const match = currentBranchName.match(regExp)
74-
const value = match ? match[matchNum] : ''
75-
return value
76-
}
68+
const choices = _.map(definition.values, (value) => {
69+
return {
70+
name: names[value.name],
71+
value: value.name,
72+
short: value.name,
73+
}
74+
})
75+
if (!definition.required) choices.unshift({ name: 'none', value: '', short: 'none' })
7776

78-
const input = (term, definition) => {
79-
const type = definition.type
80-
let inputValue
81-
switch (type) {
82-
case 'enum':
83-
inputValue = select(term, definition)
84-
break
85-
case 'string':
86-
inputValue = inputString(term, definition)
87-
break
88-
case 'text':
89-
inputValue = inputText(term, definition)
90-
break
91-
case 'branch':
92-
inputValue = branchText(term, definition)
93-
break
94-
case 'variable':
95-
break
96-
default:
97-
throw new Error(`${type} is not defined.`)
98-
}
99-
return inputValue
77+
return choices
10078
}
10179

10280
const checkValue = (term, value, definition) => {
@@ -147,74 +125,97 @@ const checkValue = (term, value, definition) => {
147125
return errorMessages.join("\n")
148126
}
149127

150-
const replaceTerms = (program, template, definitions, terms) => {
151-
if (_.isEmpty(terms)) {
152-
return template
153-
} else {
154-
const term = terms[0]
155-
const definition = definitions[term]
156-
template = replaceTerm(program, template, definition, term)
157-
return replaceTerms(program, template, definitions, _.tail(terms))
158-
}
159-
}
160-
161-
const replaceTerm = (program, template, definition, term) => {
162-
let value = definition.type === 'variable' ? program[definition.origin] : program[term]
163-
164-
if (program.interactive) {
165-
if (_.isUndefined(value) || _.isNull(value)) {
166-
if (definition.required || !program.skipOptions) {
167-
value = input(term, definition)
168-
} else {
169-
value = ''
128+
const createQuestion = (definition, term) => {
129+
let result = {}
130+
switch (definition.type) {
131+
case 'enum':
132+
result = {
133+
type: 'list',
134+
name: term,
135+
message: `Select ${term}:`,
136+
choices: createChoices(definition)
170137
}
171-
let errorMessages = checkValue(term, value, definition)
172-
while (!_.isEmpty(errorMessages)) {
173-
console.log(`${colors.warning}${errorMessages}${colors.reset}`)
174-
value = input(term, definition)
175-
errorMessages = checkValue(term, value, definition)
138+
if(definition.default) result.default = definition.default
139+
return result
140+
case 'string':
141+
result = {
142+
type: 'input',
143+
name: term,
144+
message: `Write ${term}:`,
176145
}
177-
}
178-
} else {
179-
if (_.isEmpty(value) && definition.required) throw new Error(`${term} is required.`)
146+
if(definition.default) result.default = definition.default
147+
return result
148+
case 'text':
149+
result = {
150+
type: 'editor',
151+
name: term,
152+
message: `Write ${term}:`,
153+
}
154+
if(definition.default) result.default = definition.default
155+
return result
156+
case 'variable':
157+
case 'branch':
158+
return
159+
default:
160+
throw new Error(`${ruleName} is not defined.`)
180161
}
162+
}
181163

182-
const errorMessages = checkValue(term, value, definition)
183-
if (!_.isEmpty(errorMessages)) throw new Error(errorMessages)
184-
185-
program[term] = value
164+
const createQuestions = (program, definitions, terms) => {
165+
return _(terms).map((term) => {
166+
const definition = definitions[term]
167+
if(!_.isUndefined(program[term]) || ! _.isUndefined(definition.default)) return
168+
return createQuestion(definitions[term], term)
169+
}).compact().value()
170+
}
186171

187-
let decoratedValue = value
188-
if (!_.isEmpty(value)) {
189-
if (definition.prefix) decoratedValue = definition.prefix + decoratedValue
190-
if (definition.suffix) decoratedValue = decoratedValue + definition.suffix
172+
const getValue = (program, definition, answer, term) => {
173+
switch (definition.type) {
174+
case 'enum':
175+
case 'string':
176+
case 'text':
177+
if(! _.isUndefined(program[term])) return program[term]
178+
if(! _.isUndefined(answer)) return answer
179+
if(! _.isUndefined(definition.default)) return definition.default
180+
return ''
181+
case 'variable':
182+
if(! _.isUndefined(program[definition.origin])) return program[definition.origin]
183+
return ''
184+
case 'branch':
185+
const val = branchText(definition)
186+
if(! _.isUndefined(val)) return val
187+
return ''
188+
default:
189+
throw new Error(`${definition.type} is not defined.`)
191190
}
192-
193-
return _.replace(template, `<${term}>`, decoratedValue)
194191
}
195192

196-
const checkSpell = (message) => {
197-
const dictionaryBase = path.dirname(require.resolve('dictionary-en-us'))
198-
const hunspell = new nodehun(
199-
fs.readFileSync(path.join(dictionaryBase, 'index.aff')),
200-
fs.readFileSync(path.join(dictionaryBase, 'index.dic'))
201-
)
193+
const replaceTerms = (program, template, definitions, terms, callback) => {
194+
inquirer.prompt(createQuestions(program, definitions, terms)).then((answers) => {
195+
let result = template
196+
_.forEach(terms, (term) => {
197+
const definition = definitions[term]
198+
const answer = answers[term]
199+
const value = getValue(program, definition, answer, term)
202200

203-
spellcheck(hunspell, message, (e, typos) => {
204-
if (e) throw e
201+
program[term] = value
205202

206-
_.forEach(typos, (typo) => {
207-
process.stdout.write(`${colors.warning}Is '${typo.word}' misspelled? ${colors.reset}`)
208-
console.log(`${colors.warning}Did you mean that? ${typo.suggestions.map((s) => { return `'${s}'` }).join(', ')}${colors.reset}`)
203+
const errorMessages = checkValue(term, value, definition)
204+
if (!_.isEmpty(errorMessages)) throw new Error(errorMessages)
205+
206+
let decoratedValue = value
207+
if (!_.isEmpty(value)) {
208+
if (definition.prefix) decoratedValue = definition.prefix + decoratedValue
209+
if (definition.suffix) decoratedValue = decoratedValue + definition.suffix
210+
}
211+
result = _.replace(result, `<${term}>`, decoratedValue)
209212
})
213+
callback(result)
214+
}).catch((e) => {
215+
console.error(`${colors.error}${e.message}${colors.reset}`)
210216
})
211217
}
212218

213-
const checkAddedFile = () => {
214-
const addedFiles = execSync(`git diff --name-only --cached 2> /dev/null`).toString().trim()
215-
if (_.isEmpty(addedFiles)) throw new Error(`no changes added to commit.`)
216-
}
217-
218219
const gitCommit = (commitMessage, duet = false, silent = false, dryRun = false) => {
219220
const escapedCommitMessage = commitMessage.replace(/"/g, '\\"')
220221

@@ -242,9 +243,9 @@ const gitCommit = (commitMessage, duet = false, silent = false, dryRun = false)
242243
module.exports = (program, template, definitions, terms) => {
243244
if (!program.dryRun) checkAddedFile()
244245

245-
const commitMessage = replaceTerms(program, template, definitions, terms)
246+
replaceTerms(program, template, definitions, terms, (commitMessage) => {
247+
if (program.typoCheck) checkSpell(commitMessage)
246248

247-
if (program.typoCheck) checkSpell(commitMessage)
248-
249-
gitCommit(commitMessage.trim(), program.duet, program.silent, program.dryRun)
249+
gitCommit(commitMessage.trim(), program.duet, program.silent, program.dryRun)
250+
})
250251
}

0 commit comments

Comments
 (0)