Passed
Push — master ( beba88...91d9d9 )
by Björn
02:26
created

lib/save.js (4 issues)

1
var extend        = require('deep-extend');
2
var curl          = require('curl'); // 0.1.4
3
var queryString   = require('query-string'); // 4.3.4
4
var fm            = require('front-matter');
5
var fs            = require('fs-extra');
6
var glob          = require('glob');
7
var path          = require('path');
8
var chalk         = require('chalk');
9
var through       = require('through2');
10
var slash         = require('slash');
11
var jsonfile      = require('jsonfile');
12
var Panini        = require('panini'); 
13
var superCollider = require('supercollider');
14
var marked        = require('marked');
15
16
// import Foundation Docs handlebar helpers
17
require('./vendor/handlebars');
18
19
//import Patternlibrary classes
20
var PatternlibraryConfig      = require('./patternlibrary/config');
21
22
var PatternlibraryFileStream  = require('./patternlibrary/filestream');
23
var PatternlibraryDocParser   = require('./patternlibrary/doc-parser');
24
var PatternlibraryDocRenderer = require('./patternlibrary/doc-renderer');
25
26
var PatternlibraryMiddleware  = require('./patternlibrary/middleware');
27
28
29
/** @var string patternlibrary version */
30
const PATTERNLIBRARY_VERSION = require('../package.json').version; // '0.0.1';
31
32
/**
33
 * Patternlibrary Generator
34
 * 
35
 * @package Patternlibrary
36
 * @namespace Patternlibrary
37
 * @author Björn Bartels <[email protected]>
38
 */
39
class Patternlibrary {
40
    
41
    /**
42
     * Initializes an instance of Patternlibrary.
43
     * @constructor
44
     * @param {object} options - Configuration options to use.
45
     */
46
    constructor ( options ) {
47
        
48
        /** @var string */
49
        this.patternlibrary_version = PATTERNLIBRARY_VERSION;
50
        
51
        /** @var object */
52
        this.options = extend(this.defaults, options);
53
        
54
        if (!this.options.patterns) {
55
            throw new Error('Patternlibrary error: you must specify the directories for patterns.');
56
        }
57
58
        if (!this.options.root) {
59
            throw new Error('Patternlibrary error: you must specify a root directory.')
60
        }
61
        if (!this.options.layouts) {
62
            throw new Error('Patternlibrary error: you must specify a layouts directory.')
63
        }
64
        if (!this.options.partials) {
65
            throw new Error('Patternlibrary error: you must specify a partials directory.')
66
        }
67
68
        /** @var object */
69
        this.layouts = {};
70
71
        /** @var object */
72
        this.data = {};
73
        
74
        this._init();
75
        
76
    }
77
    
78
    /**
79
     * initialize pattern-library object
80
     */
81
    _init ( ) {
82
        
83
        this.log('setup pattern-library');
84
        
85
        /** @var PatternlibraryDocParser */
86
        this.DocParser = null;
87
88
        /** @var PatternlibraryRenderer */
89
        this.DocRenderer = null;
90
        
91
        /** @var PatternlibraryMiddleware */
92
        this.Middleware = null;
93
94
        /** @var PatternlibraryFileStream */
95
        this.FileStream = null;
96
97
        
98
        /** @var PatternlibraryConfig */
99
        this.Config = new PatternlibraryConfig(this.options);
100
        
101
        
102
        this._initDestination();
103
        
104
        this._initPanini();
105
106
        /** @var Handlebars */
107
        this.Handlebars = this.Panini.Handlebars;
108
        
109
        this._initSupercollider();
110
        
111
    }
112
    
113
    
114
    
115
    
116
    /**
117
     * retrieve middleware instance
118
     * 
119
     * @param boolean reset force to initialize new middleware instance
120
     * @return PatternlibraryMiddleware
121
     */
122
    getMiddleware ( reset ) {
123
        
124
        if ( reset || (this.Middleware === null) ) {
125
            this.log('initialize middleware hook');
126
            this.Middleware = new PatternlibraryMiddleware(
127
                this.Config.get('plugins').middleware
128
            );
129
            this.Middleware.bind(this);
130
        }
131
        return (this.Middleware);
132
        
133
    }
134
    
135
136
    /**
137
     * reset middleware instance
138
     * 
139
     * @return Patternlibrary
140
     */
141
    resetMiddleware ( ) {
142
        
143
        this.Middleware = null;
144
        this.getMiddleware( true );
145
        return (this);
146
        
147
    }
148
    
149
    
150
    
151
    /**
152
     * retrieve (vinyl/gulp) file-stream instance
153
     * 
154
     * @param boolean reset force to initialize new file-stream instance
155
     * @return PatternlibraryFileStream
156
     */
157
    getFileStream ( reset ) {
158
        
159
        if ( reset || (this.FileStream === null) ) {
160
            this.log('initialize file-stream hook');
161
            this.FileStream = new PatternlibraryFileStream(
162
                this.Config.get('plugins').filestream
163
            );
164
            this.FileStream.bind(this);
165
        }
166
        return (this.FileStream);
167
        
168
    }
169
    
170
171
    /**
172
     * reset file-stream instance
173
     * 
174
     * @return Patternlibrary
175
     */
176
    resetFileStream ( ) {
177
        
178
        this.FileStream = null;
179
        this.getFileStream( true );
180
        return (this);
181
        
182
    }
183
    
184
    
185
    
186
    /**
187
     * retrieve doc-parser instance
188
     * 
189
     * @param boolean reset force to initialize new file-stream instance
190
     * @return PatternlibraryDocParser
191
     */
192
    getDocParser ( reset ) {
193
        
194
        if ( reset || (this.DocParser === null) ) {
195
            this.log('initialize documentaion info parser');
196
            this.DocParser = new PatternlibraryDocParser(
197
                this.Config.get('plugins').docparser
198
            );
199
            this.DocParser.bind(this);
200
        }
201
        return (this.DocParser);
202
        
203
    }
204
    
205
206
    /**
207
     * reset doc-parser instance
208
     * 
209
     * @return Patternlibrary
210
     */
211
    resetDocParser ( ) {
212
        
213
        this.DocParser = null;
214
        this.getDocParser( true );
215
        return (this);
216
        
217
    }
218
    
219
    
220
    
221
    /**
222
     * retrieve doc-renderer instance
223
     * 
224
     * @param boolean reset force to initialize new file-stream instance
225
     * @return PatternlibraryDocRenderer
226
     */
227
    getDocRenderer ( reset ) {
228
        
229
        if ( reset || (this.DocRenderer === null) ) {
230
            this.log('initialize documentation pages generator');
231
            this.DocRenderer = new PatternlibraryDocRenderer(
232
                this.Config.get('plugins').docrenderer
233
            );
234
            this.DocRenderer.bind(this);
235
        }
236
        return (this.DocRenderer);
237
        
238
    }
239
    
240
241
    /**
242
     * reset doc-renderer instance
243
     * 
244
     * @return Patternlibrary
245
     */
246
    resetDocRenderer ( ) {
247
        
248
        this.DocRenderer = null;
249
        this.getDocRenderer( true );
250
        return (this);
251
        
252
    }
253
    
254
    
255
    
256
    
257
258
    /**
259
     * generate the whole Patternlibrary data, pages and stuff...
260
     * 
261
     * @return Patternlibrary
262
     */
263
    run ( ) {
264
        
265
        try {
266
            // ... do stuff ^^
267
            
268
            // -> parse patterns
269
            this.getDocParser().parse();
270
            
271
            // -> generate docs
272
            this.getDocRenderer().renderDocs();
273
            
274
            // -> write data files
275
            
276
            // -> generate index pages
277
            
278
            // ...?!
279
        } catch (err) {
280
            this.warn(err);
281
            this.debug(err);
282
        }
283
        return (this);
284
    }
285
    
286
287
    
288
    
289
    
290
291
    /**
292
     * output warn message to console
293
     * 
294
     * @return Patternlibrary
295
     */
296
    warn ( msg, err ) {
297
        var strings = {
298
            time    : chalk.yellow( (new Date()).toLocaleTimeString() ),
299
            message : chalk.yellow( msg )
300
        };
301
        console.log(
302
            '['+(strings.time)+'] Patternlibrary: ' + (strings.message) // + "\n"
303
        );
304
        if (err) this.debug(err);
305
        return (this);
306
    }
307
308
    /**
309
     * output log message to console if verbose option is set to `true`
310
     * 
311
     * @return Patternlibrary
312
     */
313
    log ( msg ) {
314
        if (this.options.verbose === true) {
315
            var strings = {
316
                time    : chalk.grey( (new Date()).toLocaleTimeString() ),
317
                message : chalk.cyan( msg )
318
            };
319
            console.log(
320
                '['+(strings.time)+'] Patternlibrary: ' + (strings.message) // + "\n"
321
            );
322
        }
323
        return (this);
324
    }
325
326
    /**
327
     * output debug info to console if verbose and debug options are set to `true`
328
     * 
329
     * @return Patternlibrary
330
     */
331
    debug ( ) {
332
        if (this.options.verbose === true) {
333
            var strings = {
334
                time : chalk.grey( (new Date()).toLocaleTimeString() ),
335
                title : chalk.yellow( 'Patternlibrary Debug:' )
336
            };
337
            console.log(
338
                '['+(strings.time)+'] '+(strings.title)+' '+"\n",
339
                chalk.yellow(' >>> =================================================== <<< ') // +"\n"
340
            );
341
            for (var arg of arguments) {
342
                if (arg) console.log(arg);
343
            }
344
            console.log(
345
                chalk.yellow(' >>> =================================================== <<< ') // +"\n"
346
            );
347
        }
348
        return (this);
349
    }
350
    
351
    
352
    /**
353
     * increase usage on a given pattern by a given consumer
354
     * 
355
     * @param string patternname
356
     * @param object consumer
357
     */
358
    patternUsage ( patternname, consumer ) { 
359
360
        if ( typeof this.data[patternname] != 'undefined') {
361
            if ( typeof this.data[patternname].usage == 'undefined') {
362
                this.data[patternname].usage = {
363
                    count: 0,
364
                    consumers: {}
365
                };
366
                
367
                // increase counter
368
                this.data[patternname].usage.count = this.data[patternname].usage.count + 1;
369
                
370
                if (consumer && consumer.pattern) {
371
                    // add consumer, increase consumer counter
372
                    var consumerName = consumer.pattern.name;
373
                    if (!this.data[patternname].usage.consumers[consumerName]) {
374
                        this.data[patternname].usage.consumers[consumerName] = 1;
375
                    } else {
376
                        this.data[patternname].usage.consumers[consumerName] = 
377
                            this.data[patternname].usage.consumers[consumerName] + 1;
378
                    }
379
                }
380
            }    
381
        }
382
        
383
    }
384
    
385
    /**
386
     * updates the pattern-library JSON data-file
387
     */
388
    updateDataFile ( ) { 
389
        var patterns    = this.getPatterns();
390
        var categories  = this.getCategories();
391
        var tree        = this.Supercollider.tree;
392
        
393
        // complete library data
394
        var file = this.options.dest+'/patternlibrary.json';
395
        jsonfile.writeFileSync(file, {
396
            patterns  : patterns,
397
            categories: categories,
398
            tree      : tree,
399
        }, {spaces: 4, flag: 'w'});
400
        
401
        // search data
402
        this.Supercollider.options.pageRoot = this.Config.get('root');
403
        this.Supercollider.buildSearch(this.options.dest+'/search.json', function() {
404
            // file written...
405
        });
406
407
408
        // categorie data
409
        var file = this.options.dest+'/patterns.json';
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable file already seems to be declared on line 394. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
410
        jsonfile.writeFileSync(file, patterns, {spaces: 4, flag: 'w'});
411
412
        // categorie data
413
        var file = this.options.dest+'/categories.json';
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable file already seems to be declared on line 394. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
414
        jsonfile.writeFileSync(file, categories, {spaces: 4, flag: 'w'});
415
416
        // categorie data
417
        var file = this.options.dest+'/tree.json';
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable file already seems to be declared on line 394. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
418
        jsonfile.writeFileSync(file, tree, {spaces: 4, flag: 'w'});
419
        
420
    }
421
    
422
    
423
    
424
    /**
425
     * refresh/reload pattern-library data
426
     */
427
    refresh ( ) { 
428
        
429
        return this.run();
430
        
431
    }
432
    
433
    
434
    
435
    /**
436
     * retrieve current pattern-library data
437
     */
438
    getPatterns ( type_or_category ) { 
439
        if (type_or_category) {
440
            var patterns = {};
441
            for (var patternkey in this.data) {
442
                var pattern = this.data[patternkey];
443
                if ( 
444
                    ( String(patternkey).indexOf(type_or_category) != -1 ) ||
445
                    ( pattern.pattern.categories && ( pattern.pattern.categories.indexOf(type_or_category) != -1 ) )
446
                ) {
447
                    patterns[patternkey] = pattern;                
448
                }
449
            }
450
            return patterns;
451
        }
452
        
453
        return this.data; 
454
    
455
    }
456
    
457
    
458
    
459
    
460
    /**
461
     * retrieve current list of pattern categories
462
     */
463
    getCategories ( ) { 
464
        var patterns  = this.getPatterns();
465
466
        function isCatInList ( arr, obj, idx ) {
467
        	for (var i in arr) {
468
        		if ( arr[i][idx] && obj[idx] && (arr[i][idx] == obj[idx]) ) {
469
        			return (true);
470
        		}
471
        	}
472
        	return false;
473
        }
474
        function sortByKey(array, key) {
475
            return array.sort(function(a, b) {
476
                var x = a[key]; var y = b[key];
477
                return ((x < y) ? -1 : ((x > y) ? 1 : 0));
478
            });
479
        }
480
        
481
        // categories data
482
        var categories = [];
483
        for (var pattern in patterns) {
484
            var patterncategories = 
485
                !Array.isArray(patterns[pattern].pattern.categories) ? 
486
                    [patterns[pattern].pattern.categories] : 
487
                        patterns[pattern].pattern.categories;
488
            for (var i in patterncategories) {
489
                var cat = { 
490
                    name: String(patterncategories[i]),
491
                    slug: String(patterncategories[i])
492
                };
493
                if ( !isCatInList( categories, cat, 'slug' ) ) {
494
                	var catPatterns = this.getPatterns(cat.slug);
495
                	cat.patterns = catPatterns;
496
                	cat.patternsCount = ((b) => {
497
                		var c = 0; for (var a in b) { c++; }; return c;
498
                	})(catPatterns);
499
                    categories.push(cat);
500
                }
501
            }
502
        }
503
        
504
        categories = sortByKey(categories, 'slug');
505
        return (categories);
506
    }
507
    
508
    
509
    
510
    
511
    
512
    //
513
    // helpers
514
    //
515
    
516
    /**
517
     * recursivly read a list of files from a given directory 
518
     * according to a given file-path pattern
519
     * 
520
     * scans relative to project root
521
     * 
522
     * @param string|[string] dir
523
     * @param string pattern
524
     * @return [string]  
525
     */
526
    _readdir ( dir, pattern ) {
527
        
528
        var files = [];
529
530
        dir = !Array.isArray(dir) ? [dir] : dir;
531
532
        for (var i in dir) {
533
            files = files.concat(
534
                glob.sync( path.join( process.cwd(), dir[i], pattern ) )
535
            );
536
        }
537
538
        return files;
539
        
540
    }
541
    
542
    /**
543
     * initialize destination directory
544
     */
545
    _initDestination ( ) {
546
        try {
547
            fs.ensureDirSync(this.options.dest);
548
        } catch (err) {
549
            if (err.code !== 'EEXIST') throw err
550
        }
551
    }
552
    
553
    /**
554
     * initialize 'Panini' object
555
     */
556
    _initPanini ( ) {
557
        
558
        if (!this.Panini) {
559
            
560
            let paniniOptions = {};
561
            
562
            paniniOptions.root     = this.options.root;
563
            paniniOptions.layouts  = this.options.layouts;
564
            paniniOptions.partials = this.options.partials;
565
            paniniOptions.data     = this.options.data || null;
566
            paniniOptions.helpers  = this.options.helpers || null;
567
            
568
            this.Panini = new Panini.Panini(paniniOptions);
569
            this.Panini.loadBuiltinHelpers();
570
            
571
            // import Foundation Docs handlebar helpers
572
            this.Panini.Handlebars = require('./vendor/handlebars');
573
574
            this._registerPatternHelpers();
575
576
            
577
            // detect layouts, pages, data, etc...
578
            this.Panini.refresh();
579
            
580
        }
581
    }
582
    
583
    /**
584
     * register new Panini/Handlebars helpers
585
     */
586
    _registerPatternHelpers ( ) {
587
        
588
        // Capitalizes the first letter of a string
589
        this.Panini.Handlebars.registerHelper('toUpper', function(str) {
590
          return str[0].toUpperCase() + str.slice(1);
591
        });
592
593
        // Formats a mixin using a SassDoc mixin object to look like this:
594
        // @include mixinName($param, $param) { }
595
        this.Panini.Handlebars.registerHelper('writeMixin', function(mixin) {
596
          var name = mixin['context']['name'];
597
          var params = mixin['parameter'];
598
599
          var str = '@include ';
600
          str += name + '(';
601
          for (var i in params) {
602
            str += '$' + params[i]['name'] + ', ';
603
          }
604
          if (params) str = str.slice(0, -2);
605
          str += ') { }';
606
607
          return str;
608
        });
609
610
        // Formats a function using a SassDoc function object to look like this:
611
        // function($param, $param)
612
        this.Panini.Handlebars.registerHelper('writeFunction', function(func) {
613
          var name = func['context']['name'];
614
          var params = func['parameter'];
615
616
          var str = '';
617
          str += name + '(';
618
          for (var i in params) {
619
            str += '$' + params[i]['name'] + ', ';
620
          }
621
          if (params) str = str.slice(0, -2);
622
          str += ')';
623
624
          return str;
625
        });
626
627
        // Converts a Markdown string to HTML
628
        this.Panini.Handlebars.registerHelper('md', function(text) {
629
          return marked(text);
630
        });
631
632
        var relPath = path.relative( process.cwd(), path.join(__dirname, '') );
633
        // register 'local' helper stuff
634
        this.Panini.loadLayouts([path.join(relPath, '../layouts/'), '../layouts/','layouts',
635
            (this.options.layouts || 'layouts')
636
        ]);
637
        this.Panini.loadHelpers([path.join(relPath, '../helpers/'), '../helpers/', 'helpers',
638
            (this.options.helpers || 'helpers')
639
        ]);
640
641
        // special partial helper
642
        this.Panini.Handlebars.registerHelper('PL', require( path.join('../helpers/patternlibrary') ));
643
    }
644
    
645
    /**
646
     * initialize 'Panini' object
647
     */
648
    _initSupercollider ( ) {
649
650
        if (!this.Supercollider) {
651
            var $this = this;
0 ignored issues
show
The variable $this seems to be never used. Consider removing it.
Loading history...
652
            
653
            this.Supercollider       = new require('supercollider');
654
            this.Supercollider.$PL   = this; // Patternlibrary reference to Supercollider
655
            this.Supercollider.init  = require('./supercollider/supercollider_init_path_awareness');
656
            this.Supercollider.parse = require('./supercollider/supercollider_parse_extended');
657
            this.Supercollider
658
                //.adapter('sass')
659
                .adapter('js')
660
            ;
661
            this.Supercollider.adapter('sass',      require('./supercollider/adapters/sass.js'));
662
            this.Supercollider.adapter('source',    require('./supercollider/adapters/sourcecode.js'));
663
            this.Supercollider.adapter('example',   require('./supercollider/adapters/sourceexample.js'));
664
            this.Supercollider.adapter('specs',     require('./supercollider/adapters/patternspecs.js'));
665
            this.Supercollider.adapter('changelog', require('./supercollider/adapters/changelog.js'));
666
            this.Supercollider.adapter('gitinfo',   require('./supercollider/adapters/gitinfo.js'));
667
            this.Supercollider.adapter('tests',     require('./supercollider/adapters/testresults.js'));
668
            
669
            
670
        }
671
    }
672
    
673
    /**
674
     * determine the relative path between 2 given paths
675
     * 
676
     * @param string page
677
     * @param string root
678
     * @return string
679
     */
680
    _relativeRoot ( page, root ) {
681
        var pagePath = path.dirname(page);
682
        var rootPath = path.join(process.cwd(), root);
683
684
        var relativePath = path.relative(pagePath, rootPath);
685
686
        if (relativePath.length > 0) {
687
            relativePath += '/';
688
        }
689
690
        // On Windows, paths are separated with a "\"
691
        // However, web browsers use "/" no matter the platform
692
        return slash(relativePath);
693
    }
694
}
695
696
/**
697
 * pattern-library default options
698
 */
699
Patternlibrary.prototype.defaults = require('./config/default.js');
700
701
/**
702
 * export pattern-library object
703
 */
704
module.exports = Patternlibrary;