UNPKG

7.9 kBJavaScriptView Raw
1// deep grep
2//
3// if you're like me, you like data structures and you don't mind them
4// being nested to various levels and of various types.
5//
6// sometimes, though, when you want something deep in one of these
7// structures, it can be a pain in the ass to ask it for:
8//
9// var w = Object.keys( ds ).map( function (f) {
10// return ds[f].forEach( function (elem) {
11// return ds[f][elem].get_records( { matching: 'foo[^.]+.txt' } )
12// } )
13// } )
14//
15// and similar sometimes you just want the things and you don't want to have
16// to describe your data structure or nested object methods in excruciating
17// detail to go and find them.
18//
19// also i get sick of the amount of nested scoping node does, and this
20// allows me to abstract some of that away into a lib.
21//
22
23"use strict";
24
25var logger = require( 'log4js' ).getLogger();
26
27var S = require('singleton').get();
28S.results = [ ]; // the results we will be returning
29S.deep = [ ]; // the to-search pile
30
31function bonk (los) {
32 return los.filter( function (s) {
33 if (typeof s != 'string') {
34 return JSON.stringify(s, null, 2);
35 }
36 else {
37 return s;
38 }
39 } ).join( '\n\t' );
40}
41
42function testwrapper (thing, target) {
43 if (thing.test && ((typeof thing.test) == 'function')) {
44 return thing.test( target );
45 }
46 else if ((typeof thing) == 'function') {
47 return thing( target );
48 }
49 else {
50 logger.error( 'Failing on confusing object from testwrapper.' );
51 return new Error();
52 }
53}
54
55function arrayhandler (a) {
56 // This is suspicious but I don't remember why I did it.
57 //
58 if ( Object.prototype.toString.call( a ).substr(8, 5) === 'Array' ) {
59 // Explode arrays
60 //
61 logger.debug( 'Encountered array, pushing ' + a.length + ' elements onto stack.' );
62 a.forEach( function (subelement) { S.deep.push( subelement ) } );
63 return true;
64 }
65}
66
67exports.deeply = function ( deep, test, parameters ) {
68 if (S.deep.length > 0) {
69 logger.error( 'Called deeply when sdeep stack not empty.' );
70 }
71 else {
72 // Blowing away the singleton because deeply is being called anew
73 //
74 S.deep = deep;
75 S.results = [ ];
76 }
77
78 // See: https://gist.github.com/janearc/5721358bdc69f3982b72
79 //
80 // Probably this design could be improved, but for now it works pretty
81 // alright.
82 //
83
84 for (; function (t) {
85 if (S.deep.length > 0) {
86 var element = S.deep.shift();
87 logger.debug( 'Shifted sdeep, ' + S.deep.length + ' elements remain.' );
88
89 // Handle arrays, break if this is something to explode and handle
90 // later.
91 //
92 if (arrayhandler( element )) { return true }
93
94 // Inspect hashlike elements
95 //
96 if ((typeof element == 'object') && (Object.keys( element ).length > 0)) {
97 // Looks like it's a hashlike thingie. Check our parameters, behave
98 // accordingly.
99 //
100 Object.keys( element ).forEach( function (key) {
101 var matched = false;
102 var match = undefined;
103 if (parameters['check-keys']) {
104 if (testwrapper(test, key)) {
105 matched = true;
106 match = key;
107 }
108 }
109 if (parameters['check-values']) {
110 if (testwrapper(test, element[key])) {
111 matched = true;
112 match = element[key];
113 }
114 }
115 if (matched == true) {
116 if (parameters['return-hash-tuples']) {
117 match = element;
118 }
119 logger.debug( 'Found a hashlike match.' );
120 S.results.push( match );
121 }
122 else {
123 logger.debug( 'Discarded a hashlike match.' );
124 }
125 } );
126 }
127
128 if (testwrapper(test, element)) {
129 logger.debug( 'Test accepted ' , element );
130 S.results.push( element )
131 }
132 else {
133 logger.debug( 'Test rejected ' , element );
134 }
135 }
136 else {
137 logger.debug( 'Finished sdeep.' );
138 return false;
139 }
140 return true;
141 }( test ); ) { } // for
142
143 return S.results;
144}
145
146// Flatten the nested elements of a list into a single scope
147//
148exports.flatten = function ( list, behavior ) {
149 var flat = [ ]
150 , local = list;
151
152 // Rather than recurse through ourselves over and over, we are going to
153 // invoke 'for' here in a possibly-rude way, basically as a 'while' with
154 // a break, and just explode everything in list that is also a list back
155 // onto the stack (local), and continue to walk it until it is extinguished.
156 //
157 for (; local.length; ) {
158 var element = local.shift();
159
160 // So reference the 'complex data' documentation here:
161 //
162 // https://github.com/janearc/deep-grep/blob/master/README.md#usage
163 //
164 // the 'behavior' hash, above, will determine what we actually do
165 // during the flatten operation. For the moment (<= 0.0.2), it is unused
166 // and lists are assumed.
167 //
168
169 // this makes me feel so dirty:
170 // Object.prototype.toString.call( f ).substr(8, 5)
171 if ( Object.prototype.toString.call( element ).substr(8, 5) === 'Array' ) {
172 element.forEach( function (subelement) { local.push( subelement ) } );
173 }
174 else {
175 flat.push( element );
176 }
177 }
178
179 return flat;
180
181}
182
183// Return the unique elements of a (optionally nested) list
184//
185exports.unique = function ( list ) {
186 var flist = exports.flatten( list )
187 , utable = { }
188
189 flist.forEach( function (k) { if (utable[k]) { utable[k]++ } else { utable[k] = 1 } } );
190
191 return Object.keys( utable );
192}
193
194// Perform a synchronous (no-promise, blocking) grep
195//
196exports.sync = function ( List, expression ) {
197 // Please note that the map() here in node is.. ridiculous. See the following gist:
198 // https://gist.github.com/janearc/9e31fcc455617c769d65
199 var retval = [ ];
200 if ((typeof expression) == 'object') {
201 // Looks like they sent us an object. Verify it has test()
202 //
203 if (expression.test && ((typeof expression.test) == 'function')) {
204 // We allow them to send us any object that can test. We call this
205 // polymorphism. It's a good thing.
206 //
207 retval = List.filter( function (t) {
208 // Without the wrapper above, we get:
209 //
210 // TypeError: Method RegExp.prototype.test called on incompatible receiver undefined
211 //
212 // which is .. it seems to be an error in their library. There's
213 // implicit coercion to strings with the passed function wrapper
214 //
215 // console.log( 't: ' + t );
216 return expression.test(t);
217 } );
218 return retval;
219 }
220 }
221 else if ((typeof expression) == 'function') {
222 retval = List.filter( expression );
223 return retval;
224 }
225 else {
226 return new Error( 'Sorry, unclear what ' + expression + ' is.' );
227 }
228}
229
230// Perform an asynchronous (promise, but probably still blocking) grep
231//
232exports.async = function ( List, expression, Callback ) {
233 var q = require('q')
234 , deferred = q.defer()
235 , retval = [ ];
236
237 if ((typeof expression) == 'object') {
238 // Looks like they sent us an object. Verify it has exec()
239 //
240 if (expression.test && ((typeof expression.test) == 'function')) {
241 return q.all( function () {
242 retval = List.filter( expression.test )
243 deferred.resolve( retval );
244 return deferred.promise;
245 } ).then( function (results) {
246 return results;
247 } );
248 }
249 }
250 else if (typeof expression === 'function') {
251 return q.all( function () {
252 retval = List.filter( expression );
253 deferred.resolve( retval );
254 return deferred.promise;
255 } ).then( function (results) {
256 return Callback( results );
257 } );
258 }
259 else {
260 return new Error( 'Sorry, unclear what ' + expression + ' is.' );
261 }
262}
263
264// Just figure out whether Test exists in List
265//
266exports.in = function ( List, Test ) {
267 var yes = false;
268
269 List.forEach( function (t) {
270 if (t == Test) {
271 yes = true;
272 }
273 } )
274
275 return yes;
276
277}
278
279// Return all the values of List that are also in Test_List ('intersection')
280// It's not really super complicated but it is a nice idiom to have a shorthand
281// for.
282//
283exports.all_in = function ( List, Test_List ) {
284 var results = [ ];
285 List.forEach( function (record) {
286 exports.sync( Test_List, function (t) { if (t == record) { return true } } )
287 .forEach( function (r) { results.push( r ) } )
288 } );
289 return results;
290}