UNPKG

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