1 | import _ from 'lodash';
|
2 | import Time from './time';
|
3 | import { OPERATORS, TYPE_ANY, TYPES } from './constants';
|
4 |
|
5 | const DAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
6 |
|
7 | const MONTH = [
|
8 | 'Jan',
|
9 | 'Feb',
|
10 | 'Mar',
|
11 | 'Apr',
|
12 | 'May',
|
13 | 'Jun',
|
14 | 'Jul',
|
15 | 'Aug',
|
16 | 'Sep',
|
17 | 'Oct',
|
18 | 'Nov',
|
19 | 'Dec'
|
20 | ];
|
21 |
|
22 | const PROPERTY_FORMATTER = {
|
23 | [TYPE_ANY]: (value) => value,
|
24 | [TYPES.continuous]: (number) => `${Math.round(number * 100) / 100}`,
|
25 | [TYPES.time_of_day]: (time) => {
|
26 | const _time = time instanceof Time ? time.time_of_day : time;
|
27 | const hours = Math.floor(_time);
|
28 | const hoursStr = _.padStart(hours, 2, '0');
|
29 | const decMinutes = Math.round((_time - hours) * 60 * 100) / 100;
|
30 | const minutes = Math.floor(decMinutes);
|
31 | const minutesStr = _.padStart(minutes, 2, '0');
|
32 | const seconds = Math.round((decMinutes - minutes) * 60);
|
33 | const secondsStr = _.padStart(seconds, 2, '0');
|
34 |
|
35 | if (seconds > 0) {
|
36 | return `${hoursStr}:${minutesStr}:${secondsStr}`;
|
37 | }
|
38 | else {
|
39 | return `${hoursStr}:${minutesStr}`;
|
40 | }
|
41 | },
|
42 | [TYPES.day_of_week]: (day) => {
|
43 | const _day = day instanceof Time ? day.day_of_week : day;
|
44 | return DAYS[_day];
|
45 | },
|
46 | [TYPES.day_of_month]: (day) => {
|
47 | const _day = day instanceof Time ? day.day_of_month : day;
|
48 | return _.padStart(_day, 2, '0');
|
49 | },
|
50 |
|
51 | [TYPES.month_of_year]: (month) => {
|
52 | const _month = month instanceof Time ? month.month_of_year : month;
|
53 | return MONTH[_month - 1];
|
54 | }
|
55 | };
|
56 |
|
57 | export function formatProperty(type, value = undefined) {
|
58 | const formatter = PROPERTY_FORMATTER[type] || PROPERTY_FORMATTER[TYPE_ANY];
|
59 | if (value !== undefined) {
|
60 | return formatter(value);
|
61 | }
|
62 | return formatter;
|
63 | }
|
64 |
|
65 | const FORMATTER_FROM_DECISION_RULE = {
|
66 | [OPERATORS.IS]: {
|
67 | [TYPE_ANY]: ({ property, operand, operandFormatter }) => {
|
68 | if (property) {
|
69 | return `'${property}' is ${operandFormatter(operand)}`;
|
70 | }
|
71 | return `is ${operandFormatter(operand)}`;
|
72 | }
|
73 | },
|
74 | [OPERATORS.IN]: {
|
75 | [TYPE_ANY]: ({ property, operand, operandFormatter }) => {
|
76 | if (property) {
|
77 | return `'${property}' in [${operandFormatter(
|
78 | operand[0]
|
79 | )}, ${operandFormatter(operand[1])}[`;
|
80 | }
|
81 | return `[${operandFormatter(operand[0])}, ${operandFormatter(
|
82 | operand[1]
|
83 | )}[`;
|
84 | },
|
85 | [TYPES.day_of_week]: ({ property, operand, operandFormatter }) => {
|
86 | const day_from = Math.floor(operand[0]);
|
87 | const day_to = Math.floor(operand[1]);
|
88 |
|
89 | if (day_to - day_from == 1 || (day_from == 6 && day_to == 0)) {
|
90 | if (property) {
|
91 | return `'${property}' is ${operandFormatter(day_from)}`;
|
92 | }
|
93 | return operandFormatter(day_from);
|
94 | }
|
95 | else {
|
96 | if (property) {
|
97 | return `'${property}' from ${operandFormatter(
|
98 | day_from
|
99 | )} to ${operandFormatter((7 + day_to - 1) % 7)}`;
|
100 | }
|
101 | return `${operandFormatter(day_from)} to ${operandFormatter(
|
102 | (7 + day_to - 1) % 7
|
103 | )}`;
|
104 | }
|
105 | },
|
106 | [TYPES.month_of_year]: ({ property, operand, operandFormatter }) => {
|
107 | const month_from = Math.floor(operand[0]);
|
108 | const month_to = Math.floor(operand[1]);
|
109 | if (month_to - month_from == 1 || (month_from == 12 && month_to == 1)) {
|
110 |
|
111 | if (property) {
|
112 | return `'${property}' is ${operandFormatter(month_from)}`;
|
113 | }
|
114 | return operandFormatter(month_from);
|
115 | }
|
116 | else if (month_to == 1) {
|
117 |
|
118 | if (property) {
|
119 | return `'${property}' from ${operandFormatter(
|
120 | month_from
|
121 | )} to ${operandFormatter(12)}`;
|
122 | }
|
123 | return `${operandFormatter(month_from)} to ${operandFormatter(12)}`;
|
124 | }
|
125 | else {
|
126 | if (property) {
|
127 | return `'${property}' from ${operandFormatter(
|
128 | month_from
|
129 | )} to ${operandFormatter(month_to - 1)}`;
|
130 | }
|
131 | return `${operandFormatter(month_from)} to ${operandFormatter(
|
132 | month_to - 1
|
133 | )}`;
|
134 | }
|
135 | }
|
136 | },
|
137 | [OPERATORS.GTE]: {
|
138 | [TYPE_ANY]: ({ property, operand, operandFormatter }) => {
|
139 | if (property) {
|
140 | return `'${property}' >= ${operandFormatter(operand)}`;
|
141 | }
|
142 | return `>= ${operandFormatter(operand)}`;
|
143 | }
|
144 | },
|
145 | [OPERATORS.LT]: {
|
146 | [TYPE_ANY]: ({ property, operand, operandFormatter }) => {
|
147 | if (property) {
|
148 | return `'${property}' < ${operandFormatter(operand)}`;
|
149 | }
|
150 | return `< ${operandFormatter(operand)}`;
|
151 | }
|
152 | }
|
153 | };
|
154 |
|
155 | export function formatDecisionRules(decisionRules) {
|
156 | return decisionRules
|
157 | .map(({ property, type, operand, operator }) => {
|
158 | const operatorFormatters = FORMATTER_FROM_DECISION_RULE[operator];
|
159 | if (!operatorFormatters) {
|
160 | throw new Error(
|
161 | `Unable to format the given decision rule: unknown operator '${operator}'.`
|
162 | );
|
163 | }
|
164 | const formatter =
|
165 | operatorFormatters[type] || operatorFormatters[TYPE_ANY];
|
166 | const operandFormatter = formatProperty(type || TYPE_ANY);
|
167 | return formatter({ property, type, operator, operandFormatter, operand });
|
168 | })
|
169 | .join(' and ');
|
170 | }
|