lib/logger/_common.js   B
last analyzed

Complexity

Total Complexity 37
Complexity/F 1.68

Size

Lines of Code 312
Function Count 22

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 0
wmc 37
c 2
b 0
f 0
nc 1
mnd 2
bc 33
fnc 22
dl 0
loc 312
rs 8.6
bpm 1.5
cpm 1.6818
noi 0

2 Functions

Rating   Name   Duplication   Size   Complexity  
B _common.js ➔ Context 0 122 1
B _common.js ➔ TreeNode 0 149 1
1
/**
2
 * @module logger/_common
3
 */
4
5
/**
6
 * @enum
7
 * @readonly
8
 */
9
var Level = require('./Level').Level
10
11
/**
12
 * This class represents basic tree branch consisting of a node and (optionally)
13
 * child nodes. It is created with sole purpose to provide easy support for
14
 * package hierarchy tooling, so `ama-team.voxengine` would be presented as node
15
 * (root) -> (ama-team) -> (voxengine), and it's properties would be stored as
16
 * trees, allowing easy and configurable overlapping.
17
 *
18
 * @class
19
 *
20
 * @template {V} Wrapped value type
21
 *
22
 * @param {V|null} value Initial value
23
 */
24
function TreeNode (value) {
25
  var self = this
26
  /**
27
   * @template {V}
28
   * @typedef {Object.<string, TreeNode<V>>}
29
   */
30
  var children = {}
31
32
  /**
33
   * Adds new child to current node.
34
   *
35
   * @param {string} name Child name
36
   * @param {TreeNode<V>} child Child value
37
   * @return {TreeNode<V>} Returns added child
38
   */
39
  this.addChild = function (name, child) {
40
    children[name] = child
41
    return child
42
  }
43
44
  /**
45
   * Removes child
46
   *
47
   * @param {string} name Child name
48
   * @return {TreeNode<V>|null} Removed child value
49
   */
50
  this.removeChild = function (name) {
51
    var value = children[name]
52
    delete children[name]
53
    return value || null
54
  }
55
56
  /**
57
   * Fetches current node child by it's name.
58
   *
59
   * @param {string} name Child name.
60
   *
61
   * @return {TreeNode.<V>|null} Existing node or nothing.
62
   */
63
  this.child = function (name) { return children[name] || null }
64
65
  /**
66
   * Sets value for current node.
67
   *
68
   * @param {V} v
69
   * @return {TreeNode} Returns current instance.
70
   */
71
  this.set = function (v) {
72
    value = v
73
    return self
74
  }
75
76
  /**
77
   * Returns current node value.
78
   *
79
   * @return {V}
80
   */
81
  this.get = function () { return typeof value === 'undefined' ? null : value }
82
83
  /**
84
   * Retrieves node at path
85
   *
86
   * @param {string[]} path
87
   * @return {TreeNode<V>|null}
88
   */
89
  this.traverse = function (path) {
90
    if (path.length === 0) {
91
      return this
92
    }
93
    var name = path.shift()
94
    var cursor = this
95
    while (name && cursor) {
96
      cursor = cursor.child(name)
97
      name = path.shift()
98
    }
99
    return cursor || null
100
  }
101
102
  /**
103
   * Picks branch as a list of nodes (starting from the root) that match
104
   * requested path the most. In the best case, branch would contain root and
105
   * all requested nodes, in the worst - branch would consist of root only.
106
   *
107
   * @param {string[]} path Branch path as list of child names.
108
   * @return {[TreeNode<V>]} Picked branch.
109
   */
110
  this.branch = function (path) {
111
    var branch = [self]
112
    var cursor = self
113
    var name = path.shift()
114
    while (name) {
115
      cursor = cursor.child(name)
116
      if (!cursor) {
117
        break
118
      }
119
      branch.push(cursor)
120
      name = path.shift()
121
    }
122
    return branch
123
  }
124
125
  /**
126
   * Puts provided value at designated path down the tree.
127
   *
128
   * @param {string[]} path Path at which value has to be set.
129
   * @param {V} value Value to be set.
130
   * @return {TreeNode<V>} Created or updated node.
131
   */
132
  this.put = function (path, value) {
133
    var cursor = self
134
    var name = path.shift()
135
    while (name) {
136
      if (!cursor.child(name)) {
137
        cursor.addChild(name, new TreeNode(null))
138
      }
139
      cursor = cursor.child(name)
140
      name = path.shift()
141
    }
142
    return cursor.set(value)
143
  }
144
145
  /**
146
   * Fetches value closest to provided path. If node contains falsey value, it
147
   * is skipped.
148
   *
149
   * @param {string[]} path Path to pick the branch.
150
   * @return {V|null} Target value or null
151
   */
152
  this.retrieve = function (path) {
153
    return self.branch(path).reverse().reduce(function (carrier, node) {
154
      return carrier || node.get()
155
    }, null)
156
  }
157
158
  /**
159
   * Removes node at path and returns it's value
160
   * @param {string[]} path
161
   * @return {V|undefined}
162
   */
163
  this.remove = function (path) {
164
    if (path.length === 0) {
165
      throw new Error('You can\'t remove root node')
166
    }
167
    var segment = path.pop()
168
    var node = this.traverse(path)
169
    var child = node ? node.removeChild(segment) : null
170
    return child ? child.get() : null
171
  }
172
}
173
174
/**
175
 * This class acts as a structured closure in which logger may operate.
176
 *
177
 * It encloses logger settings - currently writer and threshold - for every
178
 * specified package, so loggers refer to context they were associated in to
179
 * find their settings, and end user may configure any logger he wants without
180
 * knowing about those loggers (unless particular level has been associated
181
 * with particular logger - in that case end user has to override it directly).
182
 *
183
 * @class
184
 * @template T
185
 * @implements ILoggerContext.<T>
186
 *
187
 * @param {Level} [lvl] Root threshold, {@see Level.Info} by default
188
 * @param {IWritable} [writer]
189
 */
190
function Context (lvl, writer) {
191
  var self = this
192
  var writers
193
  var levels
194
195
  function reset (lvl, writer) {
196
    writers = new TreeNode(writer)
197
    levels = new TreeNode(Level.find(lvl) || Level.Info)
198
  }
199
200
  reset(lvl, writer)
201
202
  this.reset = reset
203
204
  function path (name) {
205
    return (name || '').toString().split('.').filter(function (_) { return _ })
206
  }
207
208
  this.path = path
209
210
  /**
211
   * Retrieves level for provided logger
212
   *
213
   * @param {string} name
214
   * @return {Level}
215
   */
216
  this.getLevel = function (name) { return levels.retrieve(path(name)) }
217
218
  /**
219
   * Retrieves level for provided logger
220
   *
221
   * @deprecated
222
   *
223
   * @param {string} name
224
   * @return {Level}
225
   */
226
  this.getThreshold = this.getLevel
227
228
  /**
229
   * Sets level for specified logger
230
   *
231
   * @param {string} [name]
232
   * @param {Level} lvl
233
   */
234
  this.setLevel = function (name, lvl) {
235
    if (!lvl) {
236
      lvl = name
237
      name = null
238
    }
239
    levels.put(path(name), Level.find(lvl))
240
    return self
241
  }
242
243
  /**
244
   * Sets level for specified logger
245
   *
246
   * @deprecated
247
   *
248
   * @param {string} [name]
249
   * @param {Level} level
250
   */
251
  this.setThreshold = this.setLevel
252
253
  /**
254
   * Removes and returns level at provided path
255
   *
256
   * @param {string} name
257
   * @return {Level|undefined}
258
   */
259
  this.removeLevel = function (name) {
260
    var segments = path(name)
261
    if (segments.length === 0) {
262
      throw new Error('You can\'t remove default writer')
263
    }
264
    return levels.remove(segments)
265
  }
266
267
  /**
268
   * @param {string} name
269
   *
270
   * @return {IWritable}
271
   */
272
  this.getWriter = function (name) { return writers.retrieve(path(name)) }
273
274
  /**
275
   * @param {string} [name] Logger name
276
   * @param {IWritable} writer
277
   */
278
  this.setWriter = function (name, writer) {
279
    if (!writer) {
280
      writer = name
281
      name = null
282
    }
283
    writers.put(path(name), writer)
284
    return self
285
  }
286
287
  /**
288
   * Removes and returns writer under provided path
289
   *
290
   * @throws If attempted to remove root writer
291
   *
292
   * @param {string} name
293
   * @return {IWritable|undefined}
294
   */
295
  this.removeWriter = function (name) {
296
    var segments = path(name)
297
    if (segments.length === 0) {
298
      throw new Error('You can\'t remove default writer')
299
    }
300
    return writers.remove(segments)
301
  }
302
303
  /**
304
   * @abstract
305
   * @function Context#create
306
   * @param {string} name
307
   * @param {Level} [threshold]
308
   * @param {IWritable} [writer]
309
   * @return {T}
310
   */
311
}
312
313
/**
314
 * @namespace
315
 */
316
module.exports = {
317
  Level: Level,
318
  Context: Context,
319
  TreeNode: TreeNode
320
}
321