-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
110 lines (93 loc) · 3.13 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
const DEFAULTS = {
delimiter: '.',
argumentDelimiter: /, ?/,
includeLeadingDelimiter: true,
}
const isFunction = f => typeof f === 'function'
const isPromise = p => p instanceof Promise
const isAnyObject = o => o instanceof Object
const flattenObject = (obj, delimiter, prefix = '') =>
Object.getOwnPropertyNames(obj).reduce((result, key) => {
const descriptor = Object.getOwnPropertyDescriptor(obj, key)
if (descriptor.hasOwnProperty('value')) {
const { value } = descriptor
if (isFunction(value) || isPromise(value) || !isAnyObject(value)) {
result[prefix + key] = value
return result
}
return Object.assign(result, flattenObject(value, delimiter, prefix + key + delimiter))
}
return Object.defineProperty(result, key, descriptor)
}, {})
function chatCommandFactory (namespace, actions, overrides) {
const { delimiter, argumentDelimiter, includeLeadingDelimiter } = {...DEFAULTS, ...overrides}
const flatActions = flattenObject(actions, delimiter)
const maybeDelimitedNamespace = (
(namespace && includeLeadingDelimiter) ? namespace + delimiter :
namespace ? namespace :
''
)
const couldMatchNamespace = str => {
for (let i = 0; i < str.length; i++) {
if (str[i] !== maybeDelimitedNamespace[i]) return false
}
return true
}
const parse = text => {
const commands = []
let isCommand = !maybeDelimitedNamespace
let isReadingArguments = false
let substr = ''
const addCommand = () => {
commands.push(substr)
substr = ''
isCommand = !maybeDelimitedNamespace
isReadingArguments = false
}
const isBreakChar = char => (/[\n ]/.test(char) || char == null)
for (let i = 0; i < text.length; i++) {
const char = text[i]
const nextChar = text[i + 1]
if (isCommand) {
if (!isBreakChar(char) || isReadingArguments) {
substr += char
}
if (char === '(') {
isReadingArguments = true
} else if (isBreakChar(nextChar) && (!isReadingArguments || char === ')')) {
addCommand()
}
} else if (substr === maybeDelimitedNamespace) {
isCommand = true
substr = char
} else if (couldMatchNamespace(substr + char)) {
substr += char
} else if (couldMatchNamespace(char)) {
substr = char
} else {
substr = ''
}
}
if (substr && isCommand && !isReadingArguments) {
addCommand(substr)
}
return commands
}
const execute = command => {
const arg = command.replace(/^[^(]*\((.*)\)$/, '$1')
const value = flatActions[arg ? command.split('(')[0] : command]
if (!isFunction(value)) return value
return arg ? value(...arg.split(argumentDelimiter)) : value()
}
const executeAll = commands => commands.map(execute)
const parseAndExecuteAll = text => executeAll(parse(text))
function ChatCommand (text) {
return parseAndExecuteAll(text)
}
ChatCommand.parse = parse
ChatCommand.execute = execute
ChatCommand.executeAll = executeAll
ChatCommand.parseAndExecuteAll = parseAndExecuteAll
return ChatCommand
}
module.exports = chatCommandFactory