<commit_before>/*
 * grunt-contrib-websquaremin
 * https://github.com/inswave/grunt-contrib-websquaremin
 *
 * Copyright (c) 2013 inswave
 * Licensed under the MIT license.
 */

module.exports = function(grunt) {
    'use strict';

    var pd          = require('pretty-data').pd,
        helper      = require('grunt-lib-contrib').init(grunt),
        uglify      = require('uglify-js'),
        _s          = require('underscore.string'),
        path        = require('path'),
        CleanCSS    = require('clean-css');

    grunt.registerMultiTask('websquaremin', 'Minify WebSquare XML', function() {
        var options = this.options({
                preserveComments: false
            }),
            dest,
            isExpandedPair,
            fileType,
            tally = {
                dirs: 0,
                xml: 0,
                js: 0,
                css: 0,
                png: 0,
                jpg: 0
            },
            scriptRegex = /(<script[\s\S]*?type=[\"\']javascript[\"\'][\s\S]*?>[\s]*<!\[CDATA\[)([\s\S]*?)(\]\]>[\s]*<\/script>)/ig,
            styleRegex  = /(<style[\s\S]*?type=[\"\']text\/css[\"\'][\s\S]*?>[\s]*<!\[CDATA\[)([\s\S]*?)(\]\]>[\s]*<\/style>)/ig,
            exceptRegex = /return\s*/,
            eventRegex  = /ev\:event/,
            pseudoFunc  = ['(function(){', '})'],
            min = '',
            max = '',
            checkFilter = function ( source ) {
                if ( options.filter instanceof RegExp ) {
                    if ( options.filter.test( source ) ) {
                        return false;
                    }
                } else if ( typeof options.filter === 'function' ) {
                    return options.filter( source );
                }
                return true;
            },
            detectDestType = function ( dest ) {
                if( _s.endsWith( dest, '/' ) ) {
                    return 'directory';
                } else {
                    return 'file';
                }
            },
            detectFileType = function ( src ) {
                if( _s.endsWith( src, '.xml' ) ) {
                    return 'XML';
                } else if( _s.endsWith( src, '.js' ) ) {
                    return 'JS';
                } else if( _s.endsWith( src, '.css' ) ) {
                    return 'CSS';
                } else if( _s.endsWith( src, '.png' ) ) {
//                    return 'PNG';
                    return '';
                } else if( _s.endsWith( src, '.jpg' ) ) {
//                    return 'JPG';
                    return '';
                } else {
                    return '';
                }
            },
            countWithFileType = function ( fileType ) {
                if( fileType === 'XML' ) {
                    tally.xml++;
                } else if( fileType === 'JS' ) {
                    tally.js++;
                } else if( fileType === 'CSS' ) {
                    tally.css++;
                } else if( fileType === 'PNG' ) {
                    tally.png++;
                } else if( fileType === 'JPG' ) {
                    tally.jpg++;
                }
            },
            unixifyPath = function ( filepath ) {
                if( process.platform === 'win32' ) {
                    return filepath.replace( /\\/g, '/' );
                } else {
                    return filepath;
                }
            },
            minifyJS = function ( source, options, startTag ) {
                if( startTag && eventRegex.test(startTag) && exceptRegex.test( source ) ) {
                    source = uglify.parse( pseudoFunc[0] + source + pseudoFunc[1], options ).print_to_string();
                    source = source.substring( pseudoFunc[0].length, source.lastIndexOf(pseudoFunc[1]) );
                    return source;
                } else if( eventRegex.test(source) ) {
                    grunt.verbose.writeln( 'skip - ev:event is included in source.' );
                    return source;
                }

                return uglify.parse( source, options ).print_to_string();
            },
            minifyCSS = function ( source, options ) {
                return new CleanCSS( options ).minify( source );
            },
            printSummary = function () {
                var isWrite = false;

                if( tally.dirs ) {
                    grunt.log.write( 'Created ' + tally.dirs.toString().cyan + ' directories' );
                    isWrite = true;
                }

                if( tally.xml ) {
                    grunt.log.write( ( isWrite ? ', minified ' : 'Minified ' ) + tally.xml.toString().cyan + ' xml' );
                    isWrite = true;
                }

                if( tally.js ) {
                    grunt.log.write( ( isWrite ? ', minified ' : 'Minified ' ) + tally.js.toString().cyan + ' js' );
                    isWrite = true;
                }

                if( tally.css ) {
                    grunt.log.write( ( isWrite ? ', minified ' : 'Minified ' ) + tally.css.toString().cyan + ' css' );
//                    isWrite = true;
                }

                grunt.log.writeln();
            };

        grunt.verbose.writeflags( options, 'Options' );

        this.files.forEach( function( filePair ) {
            isExpandedPair = filePair.orig.expand || false;

            filePair.src.forEach( function( src ) {

                if( checkFilter( src ) ) {
                    if( detectDestType( filePair.dest ) === 'directory' ) {
                        dest = (isExpandedPair) ? filePair.dest : unixifyPath( path.join( filePair.dest, src ) );
                    } else {
                        dest = filePair.dest;
                    }

                    if( grunt.file.isDir( src ) ) {
                        grunt.verbose.writeln( 'Creating ' + dest.cyan );
                        grunt.file.mkdir( dest );
                        tally.dirs++;
                    } else {
                        fileType = detectFileType( src );

                        if( fileType ) {
                            grunt.verbose.writeln( fileType + ' Minifing ' + src.cyan + ' -> ' + dest.cyan );

                            max = grunt.file.read( src ) + grunt.util.normalizelf( grunt.util.linefeed );

                            try {
                                if( fileType === 'XML' ) {
                                    max = max.replace( scriptRegex, function( all, g1, g2, g3 ) {
                                        return g1 + minifyJS( g2, {}, g1 ) + g3;
                                    });

                                    max = max.replace( styleRegex, function( all, g1, g2, g3 ) {
                                        return g1 + minifyCSS( g2, {} ) + g3;

                                    });

                                    min = pd.xmlmin( max, options.preserveComments );
                                } if( fileType === 'JS' ) {
                                    min = minifyJS( max, {} );                          // option 처리
                                } if( fileType === 'CSS' ) {
                                    min = minifyCSS( max, {} );                         // option 처리
                                }
                            } catch( err ) {
                                grunt.warn( src + '\n' + err );
                            }

                            if( min.length < 1 ) {
                                grunt.log.warn( 'Destination not written because minified ' + src.cyan + ' was empty.' );
                            } else {
                                grunt.file.write( dest, min );
                                grunt.verbose.writeln( fileType + ' minified ' + src.cyan + ' -> ' + dest.cyan );
                                helper.minMaxInfo( min, max );
                                countWithFileType( fileType );
                            }
                        } else {
                            grunt.verbose.writeln( src.cyan + ' is skiped' );
                        }
                    }
                } else {
                    grunt.verbose.writeln( filePair.src[0] + ' is filtered.' );
                }
            });
        });

        printSummary();
    });
};
<commit_msg>compress, mangle 처리<commit_after>/*
 * grunt-contrib-websquaremin
 * https://github.com/inswave/grunt-contrib-websquaremin
 *
 * Copyright (c) 2013 inswave
 * Licensed under the MIT license.
 */

module.exports = function(grunt) {
    'use strict';

    var pd          = require('pretty-data').pd,
        helper      = require('grunt-lib-contrib').init(grunt),
        uglify      = require('uglify-js'),
        _s          = require('underscore.string'),
        path        = require('path'),
        CleanCSS    = require('clean-css');

    grunt.registerMultiTask('websquaremin', 'Minify WebSquare XML', function() {
        var options = this.options({
                preserveComments: false
            }),
//            xmlOptions = options.xml || {},
            jsOptions  = options.js || {},
            cssOptions = options.css || {},
            ast,
            compressor,
            dest,
            isExpandedPair,
            fileType,
            tally = {
                dirs: 0,
                xml: 0,
                js: 0,
                css: 0,
                png: 0,
                jpg: 0
            },
            scriptRegex = /(<script[\s\S]*?type=[\"\']javascript[\"\'][\s\S]*?>[\s]*<!\[CDATA\[)([\s\S]*?)(\]\]>[\s]*<\/script>)/ig,
            styleRegex  = /(<style[\s\S]*?type=[\"\']text\/css[\"\'][\s\S]*?>[\s]*<!\[CDATA\[)([\s\S]*?)(\]\]>[\s]*<\/style>)/ig,
            exceptRegex = /return\s*/,
            eventRegex  = /ev\:event/,
            pseudoFunc  = ['(function(){', '})'],
            min = '',
            max = '',
            checkFilter = function ( source ) {
                if ( options.filter instanceof RegExp ) {
                    if ( options.filter.test( source ) ) {
                        return false;
                    }
                } else if ( typeof options.filter === 'function' ) {
                    return options.filter( source );
                }
                return true;
            },
            detectDestType = function ( dest ) {
                if( _s.endsWith( dest, '/' ) ) {
                    return 'directory';
                } else {
                    return 'file';
                }
            },
            detectFileType = function ( src ) {
                if( _s.endsWith( src, '.xml' ) ) {
                    return 'XML';
                } else if( _s.endsWith( src, '.js' ) ) {
                    return 'JS';
                } else if( _s.endsWith( src, '.css' ) ) {
                    return 'CSS';
                } else if( _s.endsWith( src, '.png' ) ) {
//                    return 'PNG';
                    return '';
                } else if( _s.endsWith( src, '.jpg' ) ) {
//                    return 'JPG';
                    return '';
                } else {
                    return '';
                }
            },
            countWithFileType = function ( fileType ) {
                if( fileType === 'XML' ) {
                    tally.xml++;
                } else if( fileType === 'JS' ) {
                    tally.js++;
                } else if( fileType === 'CSS' ) {
                    tally.css++;
                } else if( fileType === 'PNG' ) {
                    tally.png++;
                } else if( fileType === 'JPG' ) {
                    tally.jpg++;
                }
            },
            unixifyPath = function ( filepath ) {
                if( process.platform === 'win32' ) {
                    return filepath.replace( /\\/g, '/' );
                } else {
                    return filepath;
                }
            },
            _minifyJS = function ( ast ) {
                if ( jsOptions.compress !== false ) {
                    debugger;
                    ast.figure_out_scope();

                    if ( jsOptions.compress.warnings !== true ) {
                        jsOptions.compress.warnings = false;
                    }
                    compressor = uglify.Compressor( jsOptions.compress );
                    ast = ast.transform( compressor );
                }

                if ( jsOptions.mangle !== false ) {
                    ast.figure_out_scope();
                    ast.compute_char_frequency();
                    ast.mangle_names( jsOptions.mangle );
                }
                return ast.print_to_string();
            },
            minifyJS = function ( source, options, startTag ) {
                if( startTag && eventRegex.test(startTag) && exceptRegex.test( source ) ) {
                    ast = uglify.parse( pseudoFunc[0] + source + pseudoFunc[1], options );
                    source = _minifyJS( ast );
                    source = source.substring( pseudoFunc[0].length, source.lastIndexOf(pseudoFunc[1]) );
                    return source;
                } else if( eventRegex.test(source) ) {
                    grunt.verbose.writeln( 'skip - ev:event is included in source.' );
                    return source;
                }

                ast =  uglify.parse( source, options );
                return _minifyJS( ast );
            },
            minifyCSS = function ( source, options ) {
                return new CleanCSS( options ).minify( source );
            },
            printSummary = function () {
                var isWrite = false;

                if( tally.dirs ) {
                    grunt.log.write( 'Created ' + tally.dirs.toString().cyan + ' directories' );
                    isWrite = true;
                }

                if( tally.xml ) {
                    grunt.log.write( ( isWrite ? ', minified ' : 'Minified ' ) + tally.xml.toString().cyan + ' xml' );
                    isWrite = true;
                }

                if( tally.js ) {
                    grunt.log.write( ( isWrite ? ', minified ' : 'Minified ' ) + tally.js.toString().cyan + ' js' );
                    isWrite = true;
                }

                if( tally.css ) {
                    grunt.log.write( ( isWrite ? ', minified ' : 'Minified ' ) + tally.css.toString().cyan + ' css' );
//                    isWrite = true;
                }

                grunt.log.writeln();
            };

        grunt.verbose.writeflags( options, 'Options' );

        this.files.forEach( function( filePair ) {
            isExpandedPair = filePair.orig.expand || false;

            filePair.src.forEach( function( src ) {

                if( checkFilter( src ) ) {
                    if( detectDestType( filePair.dest ) === 'directory' ) {
                        dest = (isExpandedPair) ? filePair.dest : unixifyPath( path.join( filePair.dest, src ) );
                    } else {
                        dest = filePair.dest;
                    }

                    if( grunt.file.isDir( src ) ) {
                        grunt.verbose.writeln( 'Creating ' + dest.cyan );
                        grunt.file.mkdir( dest );
                        tally.dirs++;
                    } else {
                        fileType = detectFileType( src );

                        if( fileType ) {
                            grunt.verbose.writeln( fileType + ' Minifing ' + src.cyan + ' -> ' + dest.cyan );

                            max = grunt.file.read( src ) + grunt.util.normalizelf( grunt.util.linefeed );

                            try {
                                if( fileType === 'XML' ) {
                                    max = max.replace( scriptRegex, function( all, g1, g2, g3 ) {
                                        return g1 + minifyJS( g2, jsOptions, g1 ) + g3;
                                    });

                                    max = max.replace( styleRegex, function( all, g1, g2, g3 ) {
                                        return g1 + minifyCSS( g2, cssOptions ) + g3;

                                    });

                                    min = pd.xmlmin( max, options.preserveComments );
                                } if( fileType === 'JS' ) {
                                    min = minifyJS( max, jsOptions );                          // option 처리
                                } if( fileType === 'CSS' ) {
                                    min = minifyCSS( max, cssOptions );                         // option 처리
                                }
                            } catch( err ) {
                                grunt.warn( src + '\n' + err );
                            }

                            if( min.length < 1 ) {
                                grunt.log.warn( 'Destination not written because minified ' + src.cyan + ' was empty.' );
                            } else {
                                grunt.file.write( dest, min );
                                grunt.verbose.writeln( fileType + ' minified ' + src.cyan + ' -> ' + dest.cyan );
                                helper.minMaxInfo( min, max );
                                countWithFileType( fileType );
                            }
                        } else {
                            grunt.verbose.writeln( src.cyan + ' is skiped' );
                        }
                    }
                } else {
                    grunt.verbose.writeln( filePair.src[0] + ' is filtered.' );
                }
            });
        });

        printSummary();
    });
};
<|endoftext|>


<commit_before>import log from './utils/log';
import Texture from './gl/texture';
import WorkerBroker from './utils/worker_broker';

export default class FeatureSelection {

    constructor(gl, workers, lock_fn) {
        this.gl = gl;
        this.workers = workers; // pool of workers to request feature look-ups from, keyed by id
        this._lock_fn = (typeof lock_fn === 'function') && lock_fn; // indicates if safe to read/write selection buffer this frame
        this.init();
    }

    init() {
        // Selection state tracking
        this.requests = {}; // pending selection requests
        this.feature = null; // currently selected feature
        this.read_delay = 0; // delay time from selection render to framebuffer sample, to avoid CPU/GPU sync lock
        this.read_delay_timer = null; // current timer (setTimeout) for delayed selection reads

        this.pixel = new Uint8Array(4);
        this.pixel32 = new Float32Array(this.pixel.buffer);

        // Frame buffer for selection
        // TODO: initiate lazily in case we don't need to do any selection
        this.fbo = this.gl.createFramebuffer();
        this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.fbo);
        this.fbo_size = { width: 256, height: 256 }; // TODO: make configurable / adaptive based on canvas size
        this.fbo_size.aspect = this.fbo_size.width / this.fbo_size.height;

        // Texture for the FBO color attachment
        var fbo_texture = Texture.create( this.gl, 'selection_fbo', { filtering: 'nearest' });
        fbo_texture.setData(this.fbo_size.width, this.fbo_size.height, null, { filtering: 'nearest' });
        this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, fbo_texture.texture, 0);

        // Renderbuffer for the FBO depth attachment
        var fbo_depth_rb = this.gl.createRenderbuffer();
        this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, fbo_depth_rb);
        this.gl.renderbufferStorage(this.gl.RENDERBUFFER, this.gl.DEPTH_COMPONENT16, this.fbo_size.width, this.fbo_size.height);
        this.gl.framebufferRenderbuffer(this.gl.FRAMEBUFFER, this.gl.DEPTH_ATTACHMENT, this.gl.RENDERBUFFER, fbo_depth_rb);

        this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
    }

    destroy() {
        if (this.gl && this.fbo) {
            this.gl.deleteFramebuffer(this.fbo);
            this.fbo = null;
            this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
        }

        // TODO: free texture?
    }

    // external lock function determines when it's safe to read/write from selection buffer
    get locked () {
        return (this._lock_fn && this._lock_fn()) || false;
    }

    bind() {
        // Switch to FBO
        this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.fbo);
        this.gl.viewport(0, 0, this.fbo_size.width, this.fbo_size.height);
    }

    // Request feature selection
    // Runs asynchronously, schedules selection buffer to be updated
    getFeatureAt(point) {
        return new Promise((resolve, reject) => {
            // Queue requests for feature selection, and they will be picked up by the render loop
            this.selection_request_id = (this.selection_request_id + 1) || 0;
            this.requests[this.selection_request_id] = {
                id: this.selection_request_id,
                point,
                resolve,
                reject
            };
        });
    }

    // Any pending selection requests
    pendingRequests() {
        return Object.keys(this.requests).length && this.requests;
    }

    clearPendingRequests() {
        for (var r in this.requests) {
            var request = this.requests[r];

            // This request was already sent to the worker, we're just awaiting its reply
            if (request.sent) {
                continue;
            }

            // Reject request since it will never be fulfilled
            // TODO: pass a reason for rejection?
            request.reject({ request });
            delete this.requests[r];
        }
    }

    // Read pending results from the selection buffer. Called after rendering to selection buffer.
    read() {
        // Delay reading the pixel result from the selection buffer to avoid CPU/GPU sync lock.
        // Calling readPixels synchronously caused a massive performance hit, presumably since it
        // forced this function to wait for the GPU to finish rendering and retrieve the texture contents.
        if (this.read_delay_timer != null) {
            clearTimeout(this.read_delay_timer);
        }
        this.read_delay_timer = setTimeout(() => {
            if (this.locked) {
                return;
            }

            var gl = this.gl;

            gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo);

            for (var r in this.requests) {
                var request = this.requests[r];

                // This request was already sent to the worker, we're just awaiting its reply
                if (request.sent) {
                    continue;
                }

                // Check selection map against FBO
                gl.readPixels(
                    Math.floor(request.point.x * this.fbo_size.width),
                    Math.floor((1 - request.point.y) * this.fbo_size.height),
                    1, 1, gl.RGBA, gl.UNSIGNED_BYTE, this.pixel);
                var feature_key = (this.pixel[0] + (this.pixel[1] << 8) + (this.pixel[2] << 16) + (this.pixel[3] << 24)) >>> 0;

                // If feature found, ask appropriate web worker to lookup feature
                var worker_id = this.pixel[3];
                if (worker_id !== 255) { // 255 indicates an empty selection buffer pixel
                    if (this.workers[worker_id] != null) {
                        WorkerBroker.postMessage(
                            this.workers[worker_id],
                            'self.getFeatureSelection',
                            { id: request.id, key: feature_key })
                        .then(message => {
                            this.finishRead(message);
                        });
                    }
                }
                // No feature found, but still need to resolve promise
                else {
                    this.finishRead({ id: request.id });
                }

                request.sent = true;
            }

            gl.bindFramebuffer(gl.FRAMEBUFFER, null);

        }, this.read_delay);
    }

    // Called on main thread when a web worker finds a feature in the selection buffer
    finishRead (message) {
        var request = this.requests[message.id];
        if (!request) {
            log('error', "FeatureSelection.finishRead(): could not find message", message);
            return; // request was cleared before it returned
        }

        var feature = message.feature;
        var changed = false;
        if ((feature != null && this.feature == null) ||
            (feature == null && this.feature != null) ||
            (feature != null && this.feature != null &&
                JSON.stringify(feature) !== JSON.stringify(this.feature))) {
            changed = true;
        }

        this.feature = feature; // store the most recently selected feature

        // Resolve the request
        request.resolve({ feature, changed, request });
        delete this.requests[message.id]; // done processing this request
    }

    // Selection map generation
    // Each worker will create its own independent, 'local' selection map

    // Create a unique 32-bit color to identify a feature
    // Workers independently create/modify selection colors in their own threads, but we also
    // need the main thread to know where each feature color originated. To accomplish this,
    // we partition the map by setting the 4th component (alpha channel) to the worker's id.
    static makeEntry(tile) {
        // 32-bit color key
        this.map_entry++;
        var ir = this.map_entry & 255;
        var ig = (this.map_entry >> 8) & 255;
        var ib = (this.map_entry >> 16) & 255;
        var ia = this.map_prefix;
        var r = ir / 255;
        var g = ig / 255;
        var b = ib / 255;
        var a = ia / 255;
        var key = (ir + (ig << 8) + (ib << 16) + (ia << 24)) >>> 0; // need unsigned right shift to convert to positive #

        this.map[key] = {
            color: [r, g, b, a],
        };
        this.map_size++;

        // Initialize tile-specific tracking info
        if (!this.tiles[tile.key]) {
            this.tiles[tile.key] = {
                entries: [],                        // set of feature entries in this thread
                tile: {                             // subset of tile properties to pass back with feature
                    key: tile.key,
                    coords: tile.coords,
                    style_zoom: tile.style_zoom,
                    source: tile.source,
                    generation: tile.generation
                }
            };
        }

        this.tiles[tile.key].entries.push(key);

        return this.map[key];
    }

    static makeColor(feature, tile, context) {
        var selector = this.makeEntry(tile);
        selector.feature = {
            id: feature.id,
            properties: feature.properties,
            source_name: context.source,
            source_layer: context.layer,
            layers: context.layers,
            tile: this.tiles[tile.key].tile
        };

        return selector.color;
    }

    static reset() {
        this.tiles = {};
        this.map = {};
        this.map_size = 0;
        this.map_entry = 0;
    }

    static clearTile(key) {
        if (this.tiles[key]) {
            this.tiles[key].entries.forEach(k => delete this.map[k]);
            this.map_size -= this.tiles[key].entries.length;
            delete this.tiles[key];
        }
    }

    static getMapSize() {
        return this.map_size;
    }

    static setPrefix(prefix) {
        this.map_prefix = prefix;
    }

}

// Static properties
FeatureSelection.map = {};   // this will be unique per module instance (so unique per worker)
FeatureSelection.tiles = {}; // selection keys, by tile
FeatureSelection.map_size = 0;
FeatureSelection.map_entry = 0;
FeatureSelection.map_prefix = 0; // set by worker to worker id #
FeatureSelection.defaultColor = [0, 0, 0, 1];
<commit_msg>selection: ensure requested point is in canvas bounds<commit_after>import log from './utils/log';
import Texture from './gl/texture';
import WorkerBroker from './utils/worker_broker';

export default class FeatureSelection {

    constructor(gl, workers, lock_fn) {
        this.gl = gl;
        this.workers = workers; // pool of workers to request feature look-ups from, keyed by id
        this._lock_fn = (typeof lock_fn === 'function') && lock_fn; // indicates if safe to read/write selection buffer this frame
        this.init();
    }

    init() {
        // Selection state tracking
        this.requests = {}; // pending selection requests
        this.feature = null; // currently selected feature
        this.read_delay = 0; // delay time from selection render to framebuffer sample, to avoid CPU/GPU sync lock
        this.read_delay_timer = null; // current timer (setTimeout) for delayed selection reads

        this.pixel = new Uint8Array(4);
        this.pixel32 = new Float32Array(this.pixel.buffer);

        // Frame buffer for selection
        // TODO: initiate lazily in case we don't need to do any selection
        this.fbo = this.gl.createFramebuffer();
        this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.fbo);
        this.fbo_size = { width: 256, height: 256 }; // TODO: make configurable / adaptive based on canvas size
        this.fbo_size.aspect = this.fbo_size.width / this.fbo_size.height;

        // Texture for the FBO color attachment
        var fbo_texture = Texture.create( this.gl, 'selection_fbo', { filtering: 'nearest' });
        fbo_texture.setData(this.fbo_size.width, this.fbo_size.height, null, { filtering: 'nearest' });
        this.gl.framebufferTexture2D(this.gl.FRAMEBUFFER, this.gl.COLOR_ATTACHMENT0, this.gl.TEXTURE_2D, fbo_texture.texture, 0);

        // Renderbuffer for the FBO depth attachment
        var fbo_depth_rb = this.gl.createRenderbuffer();
        this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, fbo_depth_rb);
        this.gl.renderbufferStorage(this.gl.RENDERBUFFER, this.gl.DEPTH_COMPONENT16, this.fbo_size.width, this.fbo_size.height);
        this.gl.framebufferRenderbuffer(this.gl.FRAMEBUFFER, this.gl.DEPTH_ATTACHMENT, this.gl.RENDERBUFFER, fbo_depth_rb);

        this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
    }

    destroy() {
        if (this.gl && this.fbo) {
            this.gl.deleteFramebuffer(this.fbo);
            this.fbo = null;
            this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
        }

        // TODO: free texture?
    }

    // external lock function determines when it's safe to read/write from selection buffer
    get locked () {
        return (this._lock_fn && this._lock_fn()) || false;
    }

    bind() {
        // Switch to FBO
        this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, this.fbo);
        this.gl.viewport(0, 0, this.fbo_size.width, this.fbo_size.height);
    }

    // Request feature selection
    // Runs asynchronously, schedules selection buffer to be updated
    getFeatureAt(point) {
        // ensure requested point is in canvas bounds
        if (!point || point.x < 0 || point.y < 0 || point.x > 1 || point.y > 1) {
            return Promise.resolve({ feature: null, changed: false });
        }

        return new Promise((resolve, reject) => {
            // Queue requests for feature selection, and they will be picked up by the render loop
            this.selection_request_id = (this.selection_request_id + 1) || 0;
            this.requests[this.selection_request_id] = {
                id: this.selection_request_id,
                point,
                resolve,
                reject
            };
        });
    }

    // Any pending selection requests
    pendingRequests() {
        return Object.keys(this.requests).length && this.requests;
    }

    clearPendingRequests() {
        for (var r in this.requests) {
            var request = this.requests[r];

            // This request was already sent to the worker, we're just awaiting its reply
            if (request.sent) {
                continue;
            }

            // Reject request since it will never be fulfilled
            // TODO: pass a reason for rejection?
            request.reject({ request });
            delete this.requests[r];
        }
    }

    // Read pending results from the selection buffer. Called after rendering to selection buffer.
    read() {
        // Delay reading the pixel result from the selection buffer to avoid CPU/GPU sync lock.
        // Calling readPixels synchronously caused a massive performance hit, presumably since it
        // forced this function to wait for the GPU to finish rendering and retrieve the texture contents.
        if (this.read_delay_timer != null) {
            clearTimeout(this.read_delay_timer);
        }
        this.read_delay_timer = setTimeout(() => {
            if (this.locked) {
                return;
            }

            var gl = this.gl;

            gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo);

            for (var r in this.requests) {
                var request = this.requests[r];

                // This request was already sent to the worker, we're just awaiting its reply
                if (request.sent) {
                    continue;
                }

                // Check selection map against FBO
                gl.readPixels(
                    Math.floor(request.point.x * this.fbo_size.width),
                    Math.floor((1 - request.point.y) * this.fbo_size.height),
                    1, 1, gl.RGBA, gl.UNSIGNED_BYTE, this.pixel);
                var feature_key = (this.pixel[0] + (this.pixel[1] << 8) + (this.pixel[2] << 16) + (this.pixel[3] << 24)) >>> 0;

                // If feature found, ask appropriate web worker to lookup feature
                var worker_id = this.pixel[3];
                if (worker_id !== 255) { // 255 indicates an empty selection buffer pixel
                    if (this.workers[worker_id] != null) {
                        WorkerBroker.postMessage(
                            this.workers[worker_id],
                            'self.getFeatureSelection',
                            { id: request.id, key: feature_key })
                        .then(message => {
                            this.finishRead(message);
                        });
                    }
                }
                // No feature found, but still need to resolve promise
                else {
                    this.finishRead({ id: request.id });
                }

                request.sent = true;
            }

            gl.bindFramebuffer(gl.FRAMEBUFFER, null);

        }, this.read_delay);
    }

    // Called on main thread when a web worker finds a feature in the selection buffer
    finishRead (message) {
        var request = this.requests[message.id];
        if (!request) {
            log('error', "FeatureSelection.finishRead(): could not find message", message);
            return; // request was cleared before it returned
        }

        var feature = message.feature;
        var changed = false;
        if ((feature != null && this.feature == null) ||
            (feature == null && this.feature != null) ||
            (feature != null && this.feature != null &&
                JSON.stringify(feature) !== JSON.stringify(this.feature))) {
            changed = true;
        }

        this.feature = feature; // store the most recently selected feature

        // Resolve the request
        request.resolve({ feature, changed, request });
        delete this.requests[message.id]; // done processing this request
    }

    // Selection map generation
    // Each worker will create its own independent, 'local' selection map

    // Create a unique 32-bit color to identify a feature
    // Workers independently create/modify selection colors in their own threads, but we also
    // need the main thread to know where each feature color originated. To accomplish this,
    // we partition the map by setting the 4th component (alpha channel) to the worker's id.
    static makeEntry(tile) {
        // 32-bit color key
        this.map_entry++;
        var ir = this.map_entry & 255;
        var ig = (this.map_entry >> 8) & 255;
        var ib = (this.map_entry >> 16) & 255;
        var ia = this.map_prefix;
        var r = ir / 255;
        var g = ig / 255;
        var b = ib / 255;
        var a = ia / 255;
        var key = (ir + (ig << 8) + (ib << 16) + (ia << 24)) >>> 0; // need unsigned right shift to convert to positive #

        this.map[key] = {
            color: [r, g, b, a],
        };
        this.map_size++;

        // Initialize tile-specific tracking info
        if (!this.tiles[tile.key]) {
            this.tiles[tile.key] = {
                entries: [],                        // set of feature entries in this thread
                tile: {                             // subset of tile properties to pass back with feature
                    key: tile.key,
                    coords: tile.coords,
                    style_zoom: tile.style_zoom,
                    source: tile.source,
                    generation: tile.generation
                }
            };
        }

        this.tiles[tile.key].entries.push(key);

        return this.map[key];
    }

    static makeColor(feature, tile, context) {
        var selector = this.makeEntry(tile);
        selector.feature = {
            id: feature.id,
            properties: feature.properties,
            source_name: context.source,
            source_layer: context.layer,
            layers: context.layers,
            tile: this.tiles[tile.key].tile
        };

        return selector.color;
    }

    static reset() {
        this.tiles = {};
        this.map = {};
        this.map_size = 0;
        this.map_entry = 0;
    }

    static clearTile(key) {
        if (this.tiles[key]) {
            this.tiles[key].entries.forEach(k => delete this.map[k]);
            this.map_size -= this.tiles[key].entries.length;
            delete this.tiles[key];
        }
    }

    static getMapSize() {
        return this.map_size;
    }

    static setPrefix(prefix) {
        this.map_prefix = prefix;
    }

}

// Static properties
FeatureSelection.map = {};   // this will be unique per module instance (so unique per worker)
FeatureSelection.tiles = {}; // selection keys, by tile
FeatureSelection.map_size = 0;
FeatureSelection.map_entry = 0;
FeatureSelection.map_prefix = 0; // set by worker to worker id #
FeatureSelection.defaultColor = [0, 0, 0, 1];
<|endoftext|>


<commit_before>import scrollbarWidth from 'scrollbarwidth'
import debounce from 'lodash.debounce'

import './simplebar.css'

export default class SimpleBar {
    constructor(element, options) {
        this.el = element;
        this.track;
        this.scrollbar;
        this.flashTimeout;
        this.contentEl          = this.el;
        this.scrollContentEl    = this.el;
        this.dragOffset         = { x: 0, y: 0 };
        this.isVisible          = { x: true, y: true };
        this.scrollOffsetAttr   = { x: 'scrollLeft', y: 'scrollTop' };
        this.sizeAttr           = { x: 'offsetWidth', y: 'offsetHeight' };
        this.scrollSizeAttr     = { x: 'scrollWidth', y: 'scrollHeight' };
        this.offsetAttr         = { x: 'left', y: 'top' };
        this.observer;
        this.currentAxis;
        this.enabled;

        this.options = Object.assign({}, SimpleBar.defaultOptions, options);
        this.classNames = this.options.classNames;

        this.flashScrollbar = this.flashScrollbar.bind(this);
        this.startScroll = this.startScroll.bind(this);
        this.startDrag = this.startDrag.bind(this);
        this.drag = this.drag.bind(this);
        this.endDrag = this.endDrag.bind(this);

        this.init();

        this.recalculate = debounce(this.recalculate, 100, { leading: true });
    }

    static get defaultOptions() {
        return {
            wrapContent: true,
            autoHide: true,
            forceEnabled: false,
            classNames: {
                content: 'simplebar-content',
                scrollContent: 'simplebar-scroll-content',
                scrollbar: 'simplebar-scrollbar',
                track: 'simplebar-track'
            }
        }
    }

    static get htmlAttributes() {
        return [{
            autoHide: 'data-simplebar-autohide',
            forceEnabled: 'data-simplebar-force-enabled'
        }]
    }

    static initHtmlApi() {
        // MutationObserver is IE11+
        if (typeof MutationObserver !== 'undefined') {
            // Mutation observer to observe dynamically added elements
            this.observer = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    Array.from(mutation.addedNodes).forEach(addedNode => {
                        if (addedNode.nodeType === 1) {
                            if (addedNode.hasAttribute('data-simplebar')) {
                                new SimpleBar(addedNode, SimpleBar.getElOptions(addedNode));
                            } else {
                                Array.from(addedNode.querySelectorAll('[data-simplebar]')).forEach(el => {
                                    new SimpleBar(el, SimpleBar.getElOptions(el));
                                });
                            }
                        }
                    });

                    Array.from(mutation.removedNodes).forEach(removedNode => {
                        if (removedNode.nodeType === 1) {
                            if (removedNode.hasAttribute('data-simplebar')) {
                                removedNode.SimpleBar && removedNode.SimpleBar.unMount();
                            } else {
                                Array.from(removedNode.querySelectorAll('[data-simplebar]')).forEach(el => {
                                    el.SimpleBar && el.SimpleBar.unMount();
                                });
                            }
                        }
                    });
                });
            });

            this.observer.observe(document, { childList: true, subtree: true });
        }

        // Instantiate elements already present on the page
        document.addEventListener('DOMContentLoaded', () => {
            Array.from(document.querySelectorAll('[data-simplebar]')).forEach(el => {
                new SimpleBar(el, SimpleBar.getElOptions(el));
            });
        });
    }

    // Helper function to retrieve options from element attributes
    static getElOptions(el) {
        const options = SimpleBar.htmlAttributes.reduce((acc, obj) => {
            let attribute = obj[Object.keys(obj)[0]];
            acc[Object.keys(obj)[0]] = el.hasAttribute(attribute) ? el.getAttribute(attribute) === 'false' ? false : true : true;
            return acc;
        }, {})

        return options;
    }

    static removeObserver() {
        this.observer && this.observer.disconnect();
    }

    init() {
        // Save a reference to the instance, so we know this DOM node has already been instancied
        this.el.SimpleBar = this;

        // If scrollbar is a floating scrollbar, disable the plugin
        this.enabled = scrollbarWidth() !== 0;

        if (!this.enabled && !this.options.forceEnabled) {
            this.el.style.overflow = 'auto';

            return
        }

        this.initDOM();

        this.trackX = this.el.querySelector(`.${this.classNames.track}.horizontal`);
        this.trackY = this.el.querySelector(`.${this.classNames.track}.vertical`);

        this.scrollbarX = this.trackX.querySelector(`.${this.classNames.scrollbar}`);
        this.scrollbarY = this.trackY.querySelector(`.${this.classNames.scrollbar}`);
        this.scrollContentEl = this.el.querySelector('.' + this.classNames.scrollContent);
        this.contentEl = this.el.querySelector('.' + this.classNames.content);

        // Calculate content size
        this.resizeScrollContent();
        this.resizeScrollbar('x');
        this.resizeScrollbar('y');

        if (!this.options.autoHide) {
            this.showScrollbar('x');
            this.showScrollbar('y');
        }

        this.initListeners();
    }

    initDOM() {
        if (this.el.querySelectorAll(`.${this.classNames.content}`).length) {
            return;
        }

        // Prepare DOM
        if (this.options.wrapContent) {
            const wrapperScrollContent = document.createElement('div');
            const wrapperContent = document.createElement('div');

            wrapperScrollContent.classList.add(this.classNames.scrollContent);
            wrapperContent.classList.add(this.classNames.content);

            while (this.el.firstChild)
                wrapperContent.appendChild(this.el.firstChild)

            wrapperScrollContent.appendChild(wrapperContent);
            this.el.appendChild(wrapperScrollContent);
        }

        const track = document.createElement('div');
        const scrollbar = document.createElement('div');

        track.classList.add(this.classNames.track);
        scrollbar.classList.add(this.classNames.scrollbar);

        track.appendChild(scrollbar);

        this.trackX = track.cloneNode(true);
        this.trackX.classList.add('horizontal');

        this.trackY = track.cloneNode(true);
        this.trackY.classList.add('vertical');

        this.el.insertBefore(this.trackX, this.el.firstChild);
        this.el.insertBefore(this.trackY, this.el.firstChild);
    }

    initListeners() {
        // Event listeners
        if (this.options.autoHide) {
            this.el.addEventListener('mouseenter', this.flashScrollbar);
        }

        this.scrollbarX.addEventListener('mousedown', (e) => this.startDrag(e, 'x'));
        this.scrollbarY.addEventListener('mousedown', (e) => this.startDrag(e, 'y'));

        this.scrollContentEl.addEventListener('scroll', this.startScroll);

        // MutationObserver is IE11+
        if (typeof MutationObserver !== 'undefined') {
            // create an observer instance
            this.observer = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    if (mutation.target === this.el || mutation.addedNodes.length) {
                        this.recalculate();
                    }
                });
            });

            // pass in the target node, as well as the observer options
            this.observer.observe(this.el, { attributes: true, childList: true, characterData: true, subtree: true });
        }
    }

    removeListeners() {
        // Event listeners
        if (this.options.autoHide) {
            this.el.removeEventListener('mouseenter', this.flashScrollbar);
        }

        this.scrollbarX.removeEventListener('mousedown', (e) => this.startDrag(e, 'x'));
        this.scrollbarY.removeEventListener('mousedown', (e) => this.startDrag(e, 'y'));

        this.scrollContentEl.removeEventListener('scroll', this.startScroll);

        this.observer && this.observer.disconnect();
    }

    /**
     * Start scrollbar handle drag
     */
    startDrag(e, axis = 'y') {
        // Preventing the event's default action stops text being
        // selectable during the drag.
        e.preventDefault()

        let scrollbar = axis === 'y' ? this.scrollbarY : this.scrollbarX;
        // Measure how far the user's mouse is from the top of the scrollbar drag handle.
        let eventOffset = axis === 'y' ? e.pageY : e.pageX;

        this.dragOffset[axis] = eventOffset - scrollbar.getBoundingClientRect()[this.offsetAttr[axis]];
        this.currentAxis = axis;

        document.addEventListener('mousemove', this.drag);
        document.addEventListener('mouseup', this.endDrag);
    }

    /**
     * Drag scrollbar handle
     */
    drag(e) {
        e.preventDefault();

        let eventOffset = this.currentAxis === 'y' ? e.pageY : e.pageX;
        let track = this.currentAxis === 'y' ? this.trackY : this.trackX;

        // Calculate how far the user's mouse is from the top/left of the scrollbar (minus the dragOffset).
        let dragPos = eventOffset - track.getBoundingClientRect()[this.offsetAttr[this.currentAxis]] - this.dragOffset[this.currentAxis];

        // Convert the mouse position into a percentage of the scrollbar height/width.
        let dragPerc = dragPos / track[this.sizeAttr[this.currentAxis]];

        // Scroll the content by the same percentage.
        let scrollPos = dragPerc * this.contentEl[this.scrollSizeAttr[this.currentAxis]];

        this.scrollContentEl[this.scrollOffsetAttr[this.currentAxis]] = scrollPos;
    }

    /**
     * End scroll handle drag
     */
    endDrag() {
        document.removeEventListener('mousemove', this.drag);
        document.removeEventListener('mouseup', this.endDrag);
    }

    /**
     * Resize scrollbar
     */
    resizeScrollbar(axis = 'y') {
        let track;
        let scrollbar;

        if (axis === 'x') {
            track = this.trackX;
            scrollbar = this.scrollbarX;
        } else { // 'y'
            track = this.trackY;
            scrollbar = this.scrollbarY;
        }

        let contentSize     = this.contentEl[this.scrollSizeAttr[axis]],
            scrollOffset    = this.scrollContentEl[this.scrollOffsetAttr[axis]], // Either scrollTop() or scrollLeft().
            scrollbarSize   = track[this.sizeAttr[axis]],
            scrollbarRatio  = scrollbarSize / contentSize,
            // Calculate new height/position of drag handle.
            // Offset of 2px allows for a small top/bottom or left/right margin around handle.
            handleOffset    = Math.round(scrollbarRatio * scrollOffset) + 2,
            handleSize      = Math.floor(scrollbarRatio * (scrollbarSize - 2)) - 2;

        // Set isVisible to false if scrollbar is not necessary (content is shorter than wrapper)
        this.isVisible[axis] = scrollbarSize < contentSize

        if (this.isVisible[axis]) {
            track.style.visibility = 'visible';

            if (axis === 'x') {
                scrollbar.style.left = `${handleOffset}px`;
                scrollbar.style.width = `${handleSize}px`;
            } else {
                scrollbar.style.top = `${handleOffset}px`;
                scrollbar.style.height = `${handleSize}px`;
            }
        } else {
            track.style.visibility = 'hidden';
        }
    }

    /**
     * Resize content element
     */
    resizeScrollContent() {
        const _scrollbarWidth = scrollbarWidth();

        // If no X overflow
        if (this.contentEl.scrollWidth <= this.el.offsetWidth) {
            // If no Y overflow
            if (this.contentEl.scrollHeight <= this.el.offsetHeight) {
                this.scrollContentEl.style.width = 'auto';
                this.scrollContentEl.style.height = 'auto';
            } else { // If no X overflow && Y overflow
                this.scrollContentEl.style.width = `${this.el.offsetWidth + _scrollbarWidth}px`;
                this.scrollContentEl.style.height = `${this.el.offsetHeight}px`;
            }
        } else { // If X overflow
            // If If X overflow && no Y overflow
            if (this.contentEl.scrollHeight <= this.el.offsetHeight) {
                this.scrollContentEl.style.width = 'auto';
                this.scrollContentEl.style.height = `${this.el.offsetHeight + _scrollbarWidth}px`;
            } else { // If X overflow && Y overflow
                this.scrollContentEl.style.height = `${this.el.offsetHeight + _scrollbarWidth}px`;
                this.scrollContentEl.style.width = `${this.el.offsetWidth + _scrollbarWidth}px`;
            }
        }
    }

    /**
     * On scroll event handling
     */
    startScroll() {
        this.flashScrollbar();
    }

    /**
     * Flash scrollbar visibility
     */
    flashScrollbar() {
        this.resizeScrollbar('x');
        this.resizeScrollbar('y');
        this.showScrollbar('x');
        this.showScrollbar('y');
    }

    /**
     * Show scrollbar
     */
    showScrollbar(axis = 'y') {
        if (!this.isVisible[axis]) {
            return
        }

        if (axis === 'x') {
            this.scrollbarX.classList.add('visible');
        } else {
            this.scrollbarY.classList.add('visible');
        }

        if (!this.options.autoHide) {
            return
        }
        if(typeof this.flashTimeout === 'number') {
            window.clearTimeout(this.flashTimeout);
        }

        this.flashTimeout = window.setTimeout(this.hideScrollbar.bind(this), 1000);
    }

    /**
     * Hide Scrollbar
     */
    hideScrollbar() {
        this.scrollbarX.classList.remove('visible');
        this.scrollbarY.classList.remove('visible');

        if(typeof this.flashTimeout === 'number') {
            window.clearTimeout(this.flashTimeout);
        }
    }

    /**
     * Recalculate scrollbar
     */
    recalculate() {
        if (!this.enabled) return;

        this.resizeScrollContent();
        this.resizeScrollbar();
    }

    /**
     * Getter for original scrolling element
     */
    getScrollElement() {
        return this.scrollContentEl;
    }

    /**
     * Getter for content element
     */
    getContentElement() {
        return this.contentEl;
    }

    /**
     * UnMount mutation observer and delete SimpleBar instance from DOM element
     */
    unMount() {
        this.removeListeners();
        this.el.SimpleBar = null;
    }
}

/**
 * HTML API
 */
SimpleBar.initHtmlApi();<commit_msg>fix the scrollbar position calculation (should now work no matter the scrollbar size)<commit_after>import scrollbarWidth from 'scrollbarwidth'
import debounce from 'lodash.debounce'

import './simplebar.css'

export default class SimpleBar {
    constructor(element, options) {
        this.el = element;
        this.track;
        this.scrollbar;
        this.flashTimeout;
        this.contentEl          = this.el;
        this.scrollContentEl    = this.el;
        this.dragOffset         = { x: 0, y: 0 };
        this.isVisible          = { x: true, y: true };
        this.scrollOffsetAttr   = { x: 'scrollLeft', y: 'scrollTop' };
        this.sizeAttr           = { x: 'offsetWidth', y: 'offsetHeight' };
        this.scrollSizeAttr     = { x: 'scrollWidth', y: 'scrollHeight' };
        this.offsetAttr         = { x: 'left', y: 'top' };
        this.observer;
        this.currentAxis;
        this.enabled;

        this.options = Object.assign({}, SimpleBar.defaultOptions, options);
        this.classNames = this.options.classNames;

        this.flashScrollbar = this.flashScrollbar.bind(this);
        this.startScroll = this.startScroll.bind(this);
        this.startDrag = this.startDrag.bind(this);
        this.drag = this.drag.bind(this);
        this.endDrag = this.endDrag.bind(this);

        this.init();

        this.recalculate = debounce(this.recalculate, 100, { leading: true });
    }

    static get defaultOptions() {
        return {
            wrapContent: true,
            autoHide: true,
            forceEnabled: false,
            classNames: {
                content: 'simplebar-content',
                scrollContent: 'simplebar-scroll-content',
                scrollbar: 'simplebar-scrollbar',
                track: 'simplebar-track'
            },
            scrollbarMinSize: 10
        }
    }

    static get htmlAttributes() {
        return [{
            autoHide: 'data-simplebar-autohide',
            forceEnabled: 'data-simplebar-force-enabled'
        }]
    }

    static initHtmlApi() {
        // MutationObserver is IE11+
        if (typeof MutationObserver !== 'undefined') {
            // Mutation observer to observe dynamically added elements
            this.observer = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    Array.from(mutation.addedNodes).forEach(addedNode => {
                        if (addedNode.nodeType === 1) {
                            if (addedNode.hasAttribute('data-simplebar')) {
                                new SimpleBar(addedNode, SimpleBar.getElOptions(addedNode));
                            } else {
                                Array.from(addedNode.querySelectorAll('[data-simplebar]')).forEach(el => {
                                    new SimpleBar(el, SimpleBar.getElOptions(el));
                                });
                            }
                        }
                    });

                    Array.from(mutation.removedNodes).forEach(removedNode => {
                        if (removedNode.nodeType === 1) {
                            if (removedNode.hasAttribute('data-simplebar')) {
                                removedNode.SimpleBar && removedNode.SimpleBar.unMount();
                            } else {
                                Array.from(removedNode.querySelectorAll('[data-simplebar]')).forEach(el => {
                                    el.SimpleBar && el.SimpleBar.unMount();
                                });
                            }
                        }
                    });
                });
            });

            this.observer.observe(document, { childList: true, subtree: true });
        }

        // Instantiate elements already present on the page
        document.addEventListener('DOMContentLoaded', () => {
            Array.from(document.querySelectorAll('[data-simplebar]')).forEach(el => {
                new SimpleBar(el, SimpleBar.getElOptions(el));
            });
        });
    }

    // Helper function to retrieve options from element attributes
    static getElOptions(el) {
        const options = SimpleBar.htmlAttributes.reduce((acc, obj) => {
            let attribute = obj[Object.keys(obj)[0]];
            acc[Object.keys(obj)[0]] = el.hasAttribute(attribute) ? el.getAttribute(attribute) === 'false' ? false : true : true;
            return acc;
        }, {})

        return options;
    }

    static removeObserver() {
        this.observer && this.observer.disconnect();
    }

    init() {
        // Save a reference to the instance, so we know this DOM node has already been instancied
        this.el.SimpleBar = this;

        // If scrollbar is a floating scrollbar, disable the plugin
        this.enabled = scrollbarWidth() !== 0;

        if (!this.enabled && !this.options.forceEnabled) {
            this.el.style.overflow = 'auto';

            return
        }

        this.initDOM();

        this.trackX = this.el.querySelector(`.${this.classNames.track}.horizontal`);
        this.trackY = this.el.querySelector(`.${this.classNames.track}.vertical`);

        this.scrollbarX = this.trackX.querySelector(`.${this.classNames.scrollbar}`);
        this.scrollbarY = this.trackY.querySelector(`.${this.classNames.scrollbar}`);
        this.scrollContentEl = this.el.querySelector('.' + this.classNames.scrollContent);
        this.contentEl = this.el.querySelector('.' + this.classNames.content);

        // Calculate content size
        this.resizeScrollContent();
        this.resizeScrollbar('x');
        this.resizeScrollbar('y');

        if (!this.options.autoHide) {
            this.showScrollbar('x');
            this.showScrollbar('y');
        }

        this.initListeners();
    }

    initDOM() {
        if (this.el.querySelectorAll(`.${this.classNames.content}`).length) {
            return;
        }

        // Prepare DOM
        if (this.options.wrapContent) {
            const wrapperScrollContent = document.createElement('div');
            const wrapperContent = document.createElement('div');

            wrapperScrollContent.classList.add(this.classNames.scrollContent);
            wrapperContent.classList.add(this.classNames.content);

            while (this.el.firstChild)
                wrapperContent.appendChild(this.el.firstChild)

            wrapperScrollContent.appendChild(wrapperContent);
            this.el.appendChild(wrapperScrollContent);
        }

        const track = document.createElement('div');
        const scrollbar = document.createElement('div');

        track.classList.add(this.classNames.track);
        scrollbar.classList.add(this.classNames.scrollbar);

        track.appendChild(scrollbar);

        this.trackX = track.cloneNode(true);
        this.trackX.classList.add('horizontal');

        this.trackY = track.cloneNode(true);
        this.trackY.classList.add('vertical');

        this.el.insertBefore(this.trackX, this.el.firstChild);
        this.el.insertBefore(this.trackY, this.el.firstChild);
    }

    initListeners() {
        // Event listeners
        if (this.options.autoHide) {
            this.el.addEventListener('mouseenter', this.flashScrollbar);
        }

        this.scrollbarX.addEventListener('mousedown', (e) => this.startDrag(e, 'x'));
        this.scrollbarY.addEventListener('mousedown', (e) => this.startDrag(e, 'y'));

        this.scrollContentEl.addEventListener('scroll', this.startScroll);

        // MutationObserver is IE11+
        if (typeof MutationObserver !== 'undefined') {
            // create an observer instance
            this.observer = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    if (mutation.target === this.el || mutation.addedNodes.length) {
                        this.recalculate();
                    }
                });
            });

            // pass in the target node, as well as the observer options
            this.observer.observe(this.el, { attributes: true, childList: true, characterData: true, subtree: true });
        }
    }

    removeListeners() {
        // Event listeners
        if (this.options.autoHide) {
            this.el.removeEventListener('mouseenter', this.flashScrollbar);
        }

        this.scrollbarX.removeEventListener('mousedown', (e) => this.startDrag(e, 'x'));
        this.scrollbarY.removeEventListener('mousedown', (e) => this.startDrag(e, 'y'));

        this.scrollContentEl.removeEventListener('scroll', this.startScroll);

        this.observer && this.observer.disconnect();
    }

    /**
     * Start scrollbar handle drag
     */
    startDrag(e, axis = 'y') {
        // Preventing the event's default action stops text being
        // selectable during the drag.
        e.preventDefault()

        let scrollbar = axis === 'y' ? this.scrollbarY : this.scrollbarX;
        // Measure how far the user's mouse is from the top of the scrollbar drag handle.
        let eventOffset = axis === 'y' ? e.pageY : e.pageX;

        this.dragOffset[axis] = eventOffset - scrollbar.getBoundingClientRect()[this.offsetAttr[axis]];
        this.currentAxis = axis;

        document.addEventListener('mousemove', this.drag);
        document.addEventListener('mouseup', this.endDrag);
    }

    /**
     * Drag scrollbar handle
     */
    drag(e) {
        e.preventDefault();

        let eventOffset = this.currentAxis === 'y' ? e.pageY : e.pageX;
        let track = this.currentAxis === 'y' ? this.trackY : this.trackX;

        // Calculate how far the user's mouse is from the top/left of the scrollbar (minus the dragOffset).
        let dragPos = eventOffset - track.getBoundingClientRect()[this.offsetAttr[this.currentAxis]] - this.dragOffset[this.currentAxis];

        // Convert the mouse position into a percentage of the scrollbar height/width.
        let dragPerc = dragPos / track[this.sizeAttr[this.currentAxis]];

        // Scroll the content by the same percentage.
        let scrollPos = dragPerc * this.contentEl[this.scrollSizeAttr[this.currentAxis]];

        this.scrollContentEl[this.scrollOffsetAttr[this.currentAxis]] = scrollPos;
    }

    /**
     * End scroll handle drag
     */
    endDrag() {
        document.removeEventListener('mousemove', this.drag);
        document.removeEventListener('mouseup', this.endDrag);
    }

    /**
     * Resize scrollbar
     */
    resizeScrollbar(axis = 'y') {
        let track;
        let scrollbar;

        if (axis === 'x') {
            track = this.trackX;
            scrollbar = this.scrollbarX;
        } else { // 'y'
            track = this.trackY;
            scrollbar = this.scrollbarY;
        }

        let contentSize     = this.contentEl[this.scrollSizeAttr[axis]];
        let scrollOffset    = this.scrollContentEl[this.scrollOffsetAttr[axis]]; // Either scrollTop() or scrollLeft().
        let scrollbarSize   = track[this.sizeAttr[axis]];
        let scrollbarRatio  = scrollbarSize / contentSize;
        let scrollPourcent  = scrollOffset / (contentSize - scrollbarSize);
            // Calculate new height/position of drag handle.
            // Offset of 2px allows for a small top/bottom or left/right margin around handle.
        let handleSize      = Math.max(Math.floor(scrollbarRatio * (scrollbarSize - 2)) - 2, this.options.scrollbarMinSize);
        let handleOffset    = (scrollbarSize - 4 - handleSize) * scrollPourcent + 2;

        // Set isVisible to false if scrollbar is not necessary (content is shorter than wrapper)
        this.isVisible[axis] = scrollbarSize < contentSize

        if (this.isVisible[axis]) {
            track.style.visibility = 'visible';

            if (axis === 'x') {
                scrollbar.style.left = `${handleOffset}px`;
                scrollbar.style.width = `${handleSize}px`;
            } else {
                scrollbar.style.top = `${handleOffset}px`;
                scrollbar.style.height = `${handleSize}px`;
            }
        } else {
            track.style.visibility = 'hidden';
        }
    }

    /**
     * Resize content element
     */
    resizeScrollContent() {
        const _scrollbarWidth = scrollbarWidth();

        // If no X overflow
        if (this.contentEl.scrollWidth <= this.el.offsetWidth) {
            // If no Y overflow
            if (this.contentEl.scrollHeight <= this.el.offsetHeight) {
                this.scrollContentEl.style.width = 'auto';
                this.scrollContentEl.style.height = 'auto';
            } else { // If no X overflow && Y overflow
                this.scrollContentEl.style.width = `${this.el.offsetWidth + _scrollbarWidth}px`;
                this.scrollContentEl.style.height = `${this.el.offsetHeight}px`;
            }
        } else { // If X overflow
            // If If X overflow && no Y overflow
            if (this.contentEl.scrollHeight <= this.el.offsetHeight) {
                this.scrollContentEl.style.width = 'auto';
                this.scrollContentEl.style.height = `${this.el.offsetHeight + _scrollbarWidth}px`;
            } else { // If X overflow && Y overflow
                this.scrollContentEl.style.height = `${this.el.offsetHeight + _scrollbarWidth}px`;
                this.scrollContentEl.style.width = `${this.el.offsetWidth + _scrollbarWidth}px`;
            }
        }
    }

    /**
     * On scroll event handling
     */
    startScroll() {
        this.flashScrollbar();
    }

    /**
     * Flash scrollbar visibility
     */
    flashScrollbar() {
        this.resizeScrollbar('x');
        this.resizeScrollbar('y');
        this.showScrollbar('x');
        this.showScrollbar('y');
    }

    /**
     * Show scrollbar
     */
    showScrollbar(axis = 'y') {
        if (!this.isVisible[axis]) {
            return
        }

        if (axis === 'x') {
            this.scrollbarX.classList.add('visible');
        } else {
            this.scrollbarY.classList.add('visible');
        }

        if (!this.options.autoHide) {
            return
        }
        if(typeof this.flashTimeout === 'number') {
            window.clearTimeout(this.flashTimeout);
        }

        this.flashTimeout = window.setTimeout(this.hideScrollbar.bind(this), 1000);
    }

    /**
     * Hide Scrollbar
     */
    hideScrollbar() {
        this.scrollbarX.classList.remove('visible');
        this.scrollbarY.classList.remove('visible');

        if(typeof this.flashTimeout === 'number') {
            window.clearTimeout(this.flashTimeout);
        }
    }

    /**
     * Recalculate scrollbar
     */
    recalculate() {
        if (!this.enabled) return;

        this.resizeScrollContent();
        this.resizeScrollbar();
    }

    /**
     * Getter for original scrolling element
     */
    getScrollElement() {
        return this.scrollContentEl;
    }

    /**
     * Getter for content element
     */
    getContentElement() {
        return this.contentEl;
    }

    /**
     * UnMount mutation observer and delete SimpleBar instance from DOM element
     */
    unMount() {
        this.removeListeners();
        this.el.SimpleBar = null;
    }
}

/**
 * HTML API
 */
SimpleBar.initHtmlApi();<|endoftext|>


<commit_before>'use strict';
var _ = require('lodash');

var rootURL = 'http://data.gramene.org/search/';
var suggestURL = 'http://data.gramene.org/suggest/';
function isNumeric(fieldName) {
  return fieldName && (
    _.endsWith(fieldName, '_i') ||
    _.endsWith(fieldName, '_is') ||
    fieldName === 'id' ||
    fieldName === '_genes'
  );
}
function suggestParams(queryString) {
  return {
    q: '_terms:' + queryString.replace(/\s/g,'*') + '*',
    fl: '_terms,id,_genes,id_s,name_s',
    sort: '_genes desc'
  };
}
function suggestFormatter(response,queryString,core) {
  // this function reformats the responses from the aux cores
  // there is another defined for genes
  var reList = queryString.split('*').map(function(term) {
    return new RegExp('\\b('+term+')','gi');
  });
  var suggestions = [];
  response.data.response.docs.forEach(function(doc){
    var hl = doc._terms;
    reList.forEach(function(re) {
      hl= hl.replace(re,'$1');
    });
    suggestions.push({
      term: hl,
      weight: doc._genes,
      fq: cores[core].fqField+':'+doc.id,
      name: doc.name_s,
      id: doc.id_s
    });
  });
  return suggestions;
}
var cores = {
  genes: {
    displayName: 'Genes',
    fqField: 'text',
    xref: {
      taxon_id: {core: 'taxonomy', displayName: 'Species'},
      interpro_xrefi: {core: 'interpro', displayName: 'Domain'},
      GO_xrefi: {core: 'GO', displayName: 'GO'},
      PO_xrefi: {core: 'PO', displayName: 'PO'}
    },
    isNumeric: function(fieldName) {
      return fieldName && (
        _.endsWith(fieldName, '_bin') ||
        _.endsWith(fieldName, '_xrefi') ||
        _.endsWith(fieldName, '_ancestors') ||
        fieldName === 'taxon_id' ||
        fieldName === 'start' ||
        fieldName === 'end' ||
        fieldName === 'strand'
      );
    },
    suggestUrl: suggestURL,
    suggestParams: function(queryString) {
      return {
        q:queryString
      }
    },
    suggestFormatter: function(response,queryString,core) {
      var suggestions = response.data.suggest.terms[queryString].suggestions;
      return suggestions.map(function(sug) {
        var cleanTerm = sug.term.replace(/<\/?b>/g,'');
        sug.term = sug.term.replace(/b>/g,'em>');
        sug.fq = cores[core].fqField+':'+cleanTerm;
        sug.label = cleanTerm;
        delete(sug.payload);
        return sug;
      });
      return suggestions;
    }
  },
  taxonomy: {
    displayName: 'Taxonomy',
    fqField:'NCBITaxon_ancestors',
    isNumeric: isNumeric,
    suggestUrl: rootURL,
    suggestParams: suggestParams,
    suggestFormatter: suggestFormatter
  },
  interpro: {
    displayName: 'Domains',
    fqField:'interpro_ancestors',
    isNumeric: isNumeric,
    suggestUrl: rootURL,
    suggestParams: suggestParams,
    suggestFormatter: suggestFormatter
  },
  GO: {
    displayName: 'Gene ontology',
    fqField:'GO_ancestors',
    isNumeric: isNumeric,
    suggestUrl: rootURL,
    suggestParams: suggestParams,
    suggestFormatter: suggestFormatter
  },
  PO: {
    displayName: 'Plant ontology',
    fqField:'PO_ancestors',
    isNumeric: isNumeric,
    suggestUrl: rootURL,
    suggestParams: suggestParams,
    suggestFormatter: suggestFormatter
  }
};

exports.getUrlForCore = function (core) {
  if (!cores[core]) throw new Error(core + ' is not a Gramene SOLR search core');
  return rootURL + core + '?';
};

exports.getXrefDisplayName = function (core, xrefName) {
  var xref;
  if (cores[core].hasOwnProperty('xref'))
    xref = cores[core].xref[xrefName];
  return xref ? xref.displayName : xrefName;
};

exports.getCoreDisplayName = function (core) {
  return cores[core].displayName;
};

exports.valuesAreNumeric = function (core, fieldName) {
  return cores[core].isNumeric(fieldName);
};

exports.coreNames = function() {
  return Object.keys(cores);
};

exports.getSuggestUrl = function(core) {
  return cores[core].suggestUrl + core + '?';
}
exports.getSuggestParams = function(core, queryString) {
  return cores[core].suggestParams(queryString);
}

exports.handleSuggestResponse = function(core, response, queryString) {
  return cores[core].suggestFormatter(response,queryString,core);
}<commit_msg>tweaking the suggestParams and highlighting<commit_after>'use strict';
var _ = require('lodash');

var rootURL = 'http://data.gramene.org/search/';
var suggestURL = 'http://data.gramene.org/suggest/';
function isNumeric(fieldName) {
  return fieldName && (
    _.endsWith(fieldName, '_i') ||
    _.endsWith(fieldName, '_is') ||
    fieldName === 'id' ||
    fieldName === '_genes'
  );
}
function suggestParams(queryString) {
  queryString += '*';
  if (queryString.match(/\s/)) {
    queryString = '\''+queryString+'\'';
  }
  return {
    q: '_terms:' + queryString,
    fl: '_terms,id,_genes,id_s,name_s',
    sort: '_genes desc'
  };
}
function suggestFormatter(response,queryString,core) {
  // this function reformats the responses from the aux cores
  // there is another defined for genes
  var reList = queryString.split(/\s/);
  for(var i=0;i<reList.length;i++) {
    var term = reList[i];
    if (i+1==reList.length) {
      reList[i] = new RegExp('\\b(' + term + ')', 'gi');
    }
    else {
      reList[i] = new RegExp('\\b(' + term + ')\\b', 'gi');
    }
  }
  var suggestions = [];
  response.data.response.docs.forEach(function(doc){
    var hl = doc._terms;
    reList.forEach(function(re) {
      hl= hl.replace(re,'$1');
    });
    suggestions.push({
      term: hl,
      weight: doc._genes,
      fq: cores[core].fqField+':'+doc.id,
      name: doc.name_s,
      id: doc.id_s
    });
  });
  return suggestions;
}
var cores = {
  genes: {
    displayName: 'Genes',
    fqField: 'text',
    xref: {
      taxon_id: {core: 'taxonomy', displayName: 'Species'},
      interpro_xrefi: {core: 'interpro', displayName: 'Domain'},
      GO_xrefi: {core: 'GO', displayName: 'GO'},
      PO_xrefi: {core: 'PO', displayName: 'PO'}
    },
    isNumeric: function(fieldName) {
      return fieldName && (
        _.endsWith(fieldName, '_bin') ||
        _.endsWith(fieldName, '_xrefi') ||
        _.endsWith(fieldName, '_ancestors') ||
        fieldName === 'taxon_id' ||
        fieldName === 'start' ||
        fieldName === 'end' ||
        fieldName === 'strand'
      );
    },
    suggestUrl: suggestURL,
    suggestParams: function(queryString) {
      return {
        q:queryString
      }
    },
    suggestFormatter: function(response,queryString,core) {
      var suggestions = response.data.suggest.terms[queryString].suggestions;
      return suggestions.map(function(sug) {
        var cleanTerm = sug.term.replace(/<\/?b>/g,'');
        sug.term = sug.term.replace(/b>/g,'em>');
        sug.fq = cores[core].fqField+':'+cleanTerm;
        sug.label = cleanTerm;
        delete(sug.payload);
        return sug;
      });
      return suggestions;
    }
  },
  taxonomy: {
    displayName: 'Taxonomy',
    fqField:'NCBITaxon_ancestors',
    isNumeric: isNumeric,
    suggestUrl: rootURL,
    suggestParams: suggestParams,
    suggestFormatter: suggestFormatter
  },
  interpro: {
    displayName: 'Domains',
    fqField:'interpro_ancestors',
    isNumeric: isNumeric,
    suggestUrl: rootURL,
    suggestParams: suggestParams,
    suggestFormatter: suggestFormatter
  },
  GO: {
    displayName: 'Gene ontology',
    fqField:'GO_ancestors',
    isNumeric: isNumeric,
    suggestUrl: rootURL,
    suggestParams: suggestParams,
    suggestFormatter: suggestFormatter
  },
  PO: {
    displayName: 'Plant ontology',
    fqField:'PO_ancestors',
    isNumeric: isNumeric,
    suggestUrl: rootURL,
    suggestParams: suggestParams,
    suggestFormatter: suggestFormatter
  }
};

exports.getUrlForCore = function (core) {
  if (!cores[core]) throw new Error(core + ' is not a Gramene SOLR search core');
  return rootURL + core + '?';
};

exports.getXrefDisplayName = function (core, xrefName) {
  var xref;
  if (cores[core].hasOwnProperty('xref'))
    xref = cores[core].xref[xrefName];
  return xref ? xref.displayName : xrefName;
};

exports.getCoreDisplayName = function (core) {
  return cores[core].displayName;
};

exports.valuesAreNumeric = function (core, fieldName) {
  return cores[core].isNumeric(fieldName);
};

exports.coreNames = function() {
  return Object.keys(cores);
};

exports.getSuggestUrl = function(core) {
  return cores[core].suggestUrl + core + '?';
}
exports.getSuggestParams = function(core, queryString) {
  return cores[core].suggestParams(queryString);
}

exports.handleSuggestResponse = function(core, response, queryString) {
  return cores[core].suggestFormatter(response,queryString,core);
}<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var yaml = require('js-yaml');
var Parser = require('./parser');
var generator = require('./generator');
var _ = require('lodash');
var findRoot = require('find-root');

var jsExtensions = require('common-js-file-extensions');
var markdownExtensions = require('markdown-extensions');
var cssExtensions = ['css', 'less', 'scss', 'sass'];
var defaultMatchExtensions = _(jsExtensions.code)
	.concat(markdownExtensions)
	.concat(cssExtensions)
	.thru(exts => new RegExp('\\.(' + exts.join('|') + ')$'))
	.value();

var defaultExcludeDirectories = ['.git', 'node_modules'];

function generate(params) {
	var input = path.resolve(params.input);
	var output = path.resolve(params.output);
	var configPath = params.configPath ? path.resolve(params.configPath) : path.resolve(input, '.stylemark.yml');
	var options = getConfig(configPath);

	if (!fs.existsSync(configPath)) {
		console.error('Missing configuration file');
		process.exit(1);
	}

	options.input = input;
	options.output = output;
	options.match = options.match || defaultMatchExtensions;
	options.excludeDir = defaultExcludeDirectories.concat(options.excludeDir);
	options.baseDir = options.baseDir ? findRoot(path.resolve(options.baseDir)) : findRoot(configPath);

	['match', 'excludeDir'].forEach(name => {
		options[name] = _.isString(options[name]) ? new RegExp(options[name]) : options[name];
	});

	var parser = new Parser(options);

	parser.parseDir(input, (error, docs) => {
		if (error) {
			console.error(error, error.stack);
			return;
		}
		generator.generate(docs, output, options);
	});
}

function getConfig(filepath) {
	if (!fs.existsSync(filepath)) {
		return {};
	}
	var contents = fs.readFileSync(filepath, 'utf8');
	var config = yaml.safeLoad(contents);
	return config;
}

module.exports = generate;
<commit_msg>Improved baseDir handling within options, so it doesn't go out to the package.json directory. (#57)<commit_after>var fs = require('fs');
var path = require('path');
var yaml = require('js-yaml');
var Parser = require('./parser');
var generator = require('./generator');
var _ = require('lodash');
var findRoot = require('find-root');

var jsExtensions = require('common-js-file-extensions');
var markdownExtensions = require('markdown-extensions');
var cssExtensions = ['css', 'less', 'scss', 'sass'];
var defaultMatchExtensions = _(jsExtensions.code)
	.concat(markdownExtensions)
	.concat(cssExtensions)
	.thru(exts => new RegExp('\\.(' + exts.join('|') + ')$'))
	.value();

var defaultExcludeDirectories = ['.git', 'node_modules'];

function generate(params) {
	var input = path.resolve(params.input);
	var output = path.resolve(params.output);
	var configPath = params.configPath ? path.resolve(params.configPath) : path.resolve(input, '.stylemark.yml');
	var options = getConfig(configPath);

	if (!fs.existsSync(configPath)) {
		console.error('Missing configuration file');
		process.exit(1);
	}

	options.input = input;
	options.output = output;
	options.match = options.match || defaultMatchExtensions;
	options.excludeDir = defaultExcludeDirectories.concat(options.excludeDir);
	options.baseDir = options.baseDir ? path.resolve(options.baseDir) : findRoot(configPath);

	['match', 'excludeDir'].forEach(name => {
		options[name] = _.isString(options[name]) ? new RegExp(options[name]) : options[name];
	});

	var parser = new Parser(options);

	parser.parseDir(input, (error, docs) => {
		if (error) {
			console.error(error, error.stack);
			return;
		}
		generator.generate(docs, output, options);
	});
}

function getConfig(filepath) {
	if (!fs.existsSync(filepath)) {
		return {};
	}
	var contents = fs.readFileSync(filepath, 'utf8');
	var config = yaml.safeLoad(contents);
	return config;
}

module.exports = generate;
<|endoftext|>


<commit_before>var cache = require('../lib/Cache.js'), assert = require('assert');
(function(){
    var c=new cache({'maximumSize':6}),
        res;

    function testPutAndGet() {
        for (var i = 0; i < 6; i++) {
            c.put('k' + i, 'v' + i);
        }
        for (i = 1; i < 4; i++) {
            res = c.get('k2');//repeated lookup
            assert.deepEqual(res, 'v2');
        }
        matchEntriesInOrder([ 'k2', 'k5', 'k4', 'k3', 'k1', 'k0', undefined ])

    }

    function testLRU() {
        for (var i = 1; i < 4; i++) {
            c.get('k' + i);//lookup few to move them to head
        }
        matchEntriesInOrder([ 'k3', 'k2', 'k1', 'k5', 'k4', 'k0', undefined ])
        c.invalidate('k5');
        matchEntriesInOrder([ 'k3', 'k2', 'k1', 'k4', 'k0', undefined ]);
        return i;
    }

    function testRedundantPut() {
//add more elements
        for (var i = 5; i < 20; i++) {
            c.put('k' + i, 'v' + i);
        }
        matchEntriesInOrder([ 'k19', 'k18', 'k17', 'k16', 'k15', 'k14', undefined ])
        for (i = 5; i < 20; i++) {
            c.put('k20', 'v20');//repeatedly put
        }
        matchEntriesInOrder([ 'k20', 'k19', 'k18', 'k17', 'k16', 'k15', undefined ]);
    }

    function testMixed(){
        c=new cache({'maximumSize':20,
            'weigherFunction':function(key){return 10}
        ,'expiresAfterWrite':10,'maximumWeight':1000})

    }

    function testCacheclear(){
        c.invalidateAll();
        assert.equal(c.size,0);
        c.get('something');//shouldn't throw error
        c.put('new','value');
        assert.deepEqual(c.get('new'),'value');
        matchEntriesInOrder(['new',undefined]);

    }

    function testWriteExpiry(){
        c=new cache({'maximumSize':100,'expiresAfterWrite':10});

        c.put('expirethiskey','expirewrite');
        c.put('expirethiskey2','expirewrite2');
        assert.deepEqual(c.get('expirethiskey'),'expirewrite');
        setTimeout(function(){
            assert.deepEqual(c.get('expirethiskey'),'expirewrite');
            assert.deepEqual(c.get('expirethiskey2'),'expirewrite2');
            matchEntriesInOrder(['expirethiskey2','expirethiskey',undefined]);
        },1);

        setTimeout(function(){
            assert.equal(c.get('expirethiskey'),undefined)
            assert.equal(c.get('expirethiskey2'),undefined)
            assert.equal(c.size,0)
        },150);

    }

    function testStats(){
        var ch=new cache({'maximumSize':100,"loaderFunction":function(key){
            if(parseInt(key.substring(1,key.length))<15)
                return "value"+key;

        },'recordStats':1})
        //load keys
        for (var i = 1; i < 11; i++) {
            ch.get('k' + i);
        }
        console.log(ch.stats)
        //access few
        for (i = 1; i < 11; i+=2) {
            ch.get('k' + i);
        }
        console.log(ch.stats)
        //access unknown values
        for (i = 11; i < 20; i++) {
            try {
                ch.get('k' + i);
            } catch (e) {
                //ignore
            }
        }
        assert.deepEq(ch.stats)
        console.log(ch.size);
    }

    function matchEntriesInOrder(expected){
        var entries=[];
        c._headEntry.forEach(function(e){
            entries.push(e.key);
        })
        console.log(entries);
        assert.deepEqual(entries,expected);
    }

    testPutAndGet()
    testLRU()
    testRedundantPut()
    testCacheclear()
    testWriteExpiry()
    testStats()

}())<commit_msg>cache weigher and cache max size tests<commit_after>var cache = require('../lib/Cache.js'), assert = require('assert');
(function(){
    var c=new cache({'maximumSize':6}),
        res;

    function testPutAndGet() {
        for (var i = 0; i < 6; i++) {
            c.put('k' + i, 'v' + i);
        }
        for (i = 1; i < 4; i++) {
            res = c.get('k2');//repeated lookup
            assert.deepEqual(res, 'v2');
        }
        matchEntriesInOrder([ 'k2', 'k5', 'k4', 'k3', 'k1', 'k0', undefined ])

    }

    function testLRU() {
        for (var i = 1; i < 4; i++) {
            c.get('k' + i);//lookup few to move them to head
        }
        matchEntriesInOrder([ 'k3', 'k2', 'k1', 'k5', 'k4', 'k0', undefined ])
        c.invalidate('k5');
        matchEntriesInOrder([ 'k3', 'k2', 'k1', 'k4', 'k0', undefined ]);
        return i;
    }

    function testRedundantPut() {
//add more elements
        for (var i = 5; i < 20; i++) {
            c.put('k' + i, 'v' + i);
        }
        matchEntriesInOrder([ 'k19', 'k18', 'k17', 'k16', 'k15', 'k14', undefined ])
        for (i = 5; i < 20; i++) {
            c.put('k20', 'v20');//repeatedly put
        }
        matchEntriesInOrder([ 'k20', 'k19', 'k18', 'k17', 'k16', 'k15', undefined ]);
    }

    function testMaxWeight(){
        assert.throws(function(){new cache({'maximumSize':20,
            'weigherFunction':function(key){return 10}
        ,'expiresAfterWrite':10,'maximumWeight':1000})});
        assert.throws(function(){new cache({
            'werFunction':function(key){return 10}
            ,'expiresAfterWrite':10,'maximumWeight':1000})});
        var ch=new cache({
            'weigherFunction':function(key){return 10}
            ,'expiresAfterWrite':10,'maximumWeight':1000});

    }

    function testCacheclear(){
        c.invalidateAll();
        assert.equal(c.size,0);
        c.get('something');//shouldn't throw error
        c.put('new','value');
        assert.deepEqual(c.get('new'),'value');
        matchEntriesInOrder(['new',undefined]);

    }

    function testWriteExpiry(){
        c=new cache({'maximumSize':100,'expiresAfterWrite':10});

        c.put('expirethiskey','expirewrite');
        c.put('expirethiskey2','expirewrite2');
        assert.deepEqual(c.get('expirethiskey'),'expirewrite');
        setTimeout(function(){
            assert.deepEqual(c.get('expirethiskey'),'expirewrite');
            assert.deepEqual(c.get('expirethiskey2'),'expirewrite2');
            matchEntriesInOrder(['expirethiskey2','expirethiskey',undefined]);
        },1);

        setTimeout(function(){
            assert.equal(c.get('expirethiskey'),undefined)
            assert.equal(c.get('expirethiskey2'),undefined)
            assert.equal(c.size,0)
        },150);

    }

    function testStats(){
        var ch=new cache({'maximumSize':100,"loaderFunction":function(key){
            if(parseInt(key.substring(1,key.length))<15)
                return "value"+key;

        },'recordStats':1})
        //load keys
        for (var i = 1; i < 11; i++) {
            ch.get('k' + i);
        }
        console.log(ch.stats)
        //access few
        for (i = 1; i < 11; i+=2) {
            ch.get('k' + i);
        }
        console.log(ch.stats)
        //access unknown values
        for (i = 11; i < 20; i++) {
            try {
                ch.get('k' + i);
            } catch (e) {
                //ignore
            }
        }
        assert.deepEqual(ch.stats,{"hitCount":5,"missCount":14,"requestCount":24})
        assert.deepEqual(ch.size,14);
    }

    function matchEntriesInOrder(expected){
        var entries=[];
        c._headEntry.forEach(function(e){
            entries.push(e.key);
        })
        console.log(entries);
        assert.deepEqual(entries,expected);
    }

    testPutAndGet()
    testLRU()
    testRedundantPut()
    testCacheclear()
    testWriteExpiry()
    testStats()
    testMaxWeight()

}())<|endoftext|>