server/rules.js   B
last analyzed

Complexity

Total Complexity 46
Complexity/F 2.88

Size

Lines of Code 239
Function Count 16

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 0
nc 1
dl 0
loc 239
rs 8.3999
c 1
b 0
f 0
wmc 46
mnd 3
bc 44
fnc 16
bpm 2.75
cpm 2.875
noi 17

15 Functions

Rating   Name   Duplication   Size   Complexity  
B rules.js ➔ log 0 24 2
A Router.post(ꞌ/portal/rulesꞌ) 0 5 2
B rules.js ➔ compare 0 16 10
A rules.js ➔ indexOf 0 6 3
A rules.js ➔ getLogs 0 3 1
A rules.js ➔ save 0 18 4
A rules.js ➔ decode 0 4 2
B rules.js ➔ next 0 30 1
B rules.js ➔ getRules 0 25 5
A RuleEngine.before 0 1 1
A rules.js ➔ init 0 4 1
A rules.js ➔ dispatch 0 7 2
A RuleEngine.after 0 1 1
A Router.all(ꞌ/rules/scriptꞌ) 0 3 1
C rules.js ➔ iterate 0 33 8

How to fix   Complexity   

Complexity

Complex classes like server/rules.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
var express = require('express');
2
var extend  = require('extend');
3
4
// ------------------------------------------
5
//  CONSTRUCTOR
6
// ------------------------------------------
7
8
var init = function(){
9
  info('Starting RuleEngine ...');
10
  return RuleEngine;
11
}
12
13
// ------------------------------------------
14
//  SCRIPT
15
// ------------------------------------------
16
17
var dispatch = function(name, data, callback){
18
  // 1. Answer now to callback
19
  if (callback) { callback(data); }
20
  
21
  // 2. Then iterate on explicit rules
22
  iterate(name, data, 0);
23
}
24
25
var iterate = function(name, data, i){
26
  var options = data; // backward compatibility
0 ignored issues
show
Unused Code introduced by
The variable options seems to be never used. Consider removing it.
Loading history...
27
  var rules = Config.rules;
0 ignored issues
show
Bug introduced by
The variable Config seems to be never declared. If this is a global, consider adding a /** global: Config */ 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.

Loading history...
28
  if (!rules){ return false; }
29
  
30
  for (; i < rules.length ; i++){
31
    try {
32
      var rule = rules[i];
33
      if (rule['disabled']){ continue; }
34
      if (rule['if'] != name){ continue; }
35
      if (rule['script']){ eval(decode(rule['script'])); }
0 ignored issues
show
Security Performance introduced by
Calls to eval are slow and potentially dangerous, especially on untrusted code. Please consider whether there is another way to achieve your goal.
Loading history...
36
      if (!rule['then']){ continue; }
37
      
38
      info('iterate ', i, rule);
39
      return SARAH.run(rule['then'], data, function(result){
0 ignored issues
show
Bug introduced by
The variable SARAH seems to be never declared. If this is a global, consider adding a /** global: SARAH */ 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.

Loading history...
40
        if (result) {
41
          // Handle speech
42
          SARAH.speak(result.tts);
0 ignored issues
show
Bug introduced by
The variable SARAH seems to be never declared. If this is a global, consider adding a /** global: SARAH */ 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.

Loading history...
43
          
44
          // Merge resutls
45
          extend(true, data, result);
46
        }
47
        
48
        // Recursive call
49
        iterate(name, data, i+1);
0 ignored issues
show
Bug introduced by
The variable i is changed as part of the for loop for example by i++ on line 30. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
50
      });
51
      
52
    } catch(ex) { 
53
      warn('Rule %s:', name, rule, ex); 
54
      iterate(name, data, i+1);
55
    }
56
  }
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
57
}
58
59
var decode = function(str){
60
  if (!str){ return str; }
61
  return str.replace(/&quote;/g, '"').replace(/&#10;/g, '\n').trim();
62
}
63
64
// ------------------------------------------
65
//  SORTING MANAGER
66
// ------------------------------------------
67
68
var SEEK   = 1*60*3;  //  3 minutes
69
var SCOPE  = 1*60*10; // 10 minutes
70
var moment = require('moment'); moment.locale('fr');
71
72
var log = function(name, data){
73
  var now = moment();
74
  var day = moment().startOf('day');
75
  
76
  var entry        = {};
77
  entry.name       = name;
78
  entry.data       = data;
79
  entry.timeOfDay  = now.diff(day) / 1000;
80
  entry.dayOfWeek  = now.day();
81
  entry.dayOfMonth = now.date();
82
  entry.location   = 'house';
83
  entry.count      = 0;
84
  
85
  var i = indexOf(entry);
86
  if (i < 0){ 
87
    logs.push(entry); 
88
  } else {  
89
    entry = logs[i];
90
    entry.count++;
91
  }
92
  logs.sort(compare);
93
  
94
  return entry;
95
}
96
97
var indexOf = function(entry){
98
  for (var i = 0 ; i < logs.length; i++){
99
    if (compare(entry, logs[i]) == 0){ return i }
100
  }
101
  return -1;
102
}
103
104
var compare = function(x, y){
105
  var tod = x.timeOfDay  - y.timeOfDay;
106
  var dow = x.dayOfWeek  - y.dayOfWeek;
107
  var dom = x.dayOfMonth - y.dayOfMonth;
108
  var cnt = x.count - y.count;
109
  
110
  if (Math.abs(tod) < SCOPE ){
111
    if (x.name === y.name){ return 0; }
112
    var delta = (Math.abs(dom) + Math.abs(dow) + Math.abs(tod)) / ((Math.abs(cnt)+1) * SCOPE);
113
    if (dom != 0){ return dom>0 ? delta : -delta; }
114
    if (dow != 0){ return dow>0 ? delta : -delta; }
115
    if (cnt != 0){ return cnt>0 ? delta : -delta; }
116
    return tod>0 ? delta : -delta; //x.name.localeCompare(y.name); 
117
  }
118
  else { return tod; }
119
}
120
121
var next = function(entry){
0 ignored issues
show
Unused Code introduced by
The parameter entry is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
122
  
123
  /*
124
  var i  = indexOf(entry);
125
  var d1 = 1000000;
126
  var e1 = undefined;
127
  
128
  var d2 = 1000000;
129
  var e2 = undefined;
130
  
131
  console.log(i,entry.name,'--------------------');
132
  for (var j in logs){
133
    console.log(j, logs[j].name);
134
  }
135
  
136
  if (i+1 < logs.length){
137
    e1 = logs[i+1];
138
    d1 = compare(e1, entry);
139
    console.log('i+1 <'+logs.length, e1.name, d1);
140
  }
141
  
142
  if (i-1 >= 0){
143
    e2 = logs[i-1];
144
    d2 = compare(entry, e2);
145
    console.log('i-1 >= 0', e2.name, d2);
146
  }
147
  
148
  return d1 < d2 ? e1 : e2;
149
  */
150
}
151
152
var logs = []
153
var getLogs = function(){
154
  return logs;
155
}
156
157
158
// ------------------------------------------
159
//  RULES
160
// ------------------------------------------
161
162
var getRules = function(ifname){
163
  
164
  // Set empty rules
165
  if (!Config.rules){
0 ignored issues
show
Bug introduced by
The variable Config seems to be never declared. If this is a global, consider adding a /** global: Config */ 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.

Loading history...
166
    Config.rules = [
167
      { "if": "before", "then": "", "script" : ""},
168
      { "if": "after",  "then": "", "script" : ""}
169
    ];
170
  }
171
  
172
  // Return all rules if there is no name
173
  if (!ifname){
174
    return Config.rules;
175
  }
176
  
177
  // Filter on name
178
  var tmp = [];
179
  for (var i in Config.rules){
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
180
    var rule = Config.rules[i];
181
    if (ifname == rule['if']){
182
      tmp.push(rule); 
183
    }
184
  }
185
  return tmp;
186
}
187
188
var save = function(body){
189
  if (!body['if']){ return; }
190
  
191
  var tmp = []
192
  for(var i in body['if']){
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
193
    var ifname = Helper.parse(body['if'][i]);
0 ignored issues
show
Bug introduced by
The variable Helper seems to be never declared. If this is a global, consider adding a /** global: Helper */ 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.

Loading history...
194
    if (!ifname) continue;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
195
    
196
    tmp.push({
197
      "if" : ifname,
198
      "then" : Helper.parse(body['then'][i]),
199
      "script" : Helper.parse(body['script'][i]),
200
      "disabled" : Helper.parse(body['disabled'][i]),
201
    });
202
  }
203
  Config.rules = tmp;
0 ignored issues
show
Bug introduced by
The variable Config seems to be never declared. If this is a global, consider adding a /** global: Config */ 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.

Loading history...
204
  SARAH.ConfigManager.save();
0 ignored issues
show
Bug introduced by
The variable SARAH seems to be never declared. If this is a global, consider adding a /** global: SARAH */ 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.

Loading history...
205
}
206
207
// ------------------------------------------
208
//  ROUTER
209
// ------------------------------------------
210
211
var Router = express.Router();
212
Router.post('/portal/rules', function(req, res, next) {
0 ignored issues
show
Unused Code introduced by
The parameter next is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
213
  var name = req.body.opSave;
214
  if (name){ save(req.body); }
215
  res.redirect('/portal/rules');
216
});
217
218
Router.all('/rules/script', function(req, res, next) {
0 ignored issues
show
Unused Code introduced by
The parameter next is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
219
  res.render('rules/rules-script.ejs', {'title' : i18n('modal.rules.script')});
220
});
221
222
// ------------------------------------------
223
//  PUBLIC
224
// ------------------------------------------
225
226
var RuleEngine = {
227
  'init' : init,
228
  'dispatch': dispatch,
229
  'log': log,
230
  'next': next,
231
  'getLogs': getLogs,
232
  'getRules': getRules,
233
  'before': function(){ return getRules('before')[0] },
234
  'after' : function(){ return getRules('after')[0]  },
235
  'Router' : Router
236
}
237
238
// Exports Manager
239
exports.init = RuleEngine.init;