Completed
Push — master ( f2a0af...598926 )
by Fike
39s
created

_common.js ➔ ... ➔ path   A

Complexity

Conditions 1
Paths 2

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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