<commit_before>import Container from "./Container.js";
import { addrToGeoFull,geoToCityAndDistrict} from "./utils/bdHelper.js";
import {filter as positionFilter} from "./utils/ETL/positionETL.js";
import StatisticData from "./statistic/statisticData.js";

export default class Main {
    constructor(db, table, year, month) {
        this.db = db;
        this.table = table;
        this.containerList = [];
        this.year = year;
        this.month = month;
    }

    addConfig(config) {
        var container = new Container({ db: this.db, table: this.table, config: config, year: this.year, month: this.month });
        this.containerList.push(container);
    }

    //抓取数据完成
    async robotData() {
        await this.stepList();
        await this.stepToJob();
        await this.stepInfo();
        console.log("robotData flinsh")
    }

    async analyseCompany() {
        await this.groupCompany();
        await this.parseFromJobs();
        await this.compareCompany();
        await this.compareAddress();
        await this.loadGeo();
        await this.fixedGeo();
        await this.etlDistrict();
        await this.noLoadToRepertory();
        console.log("analyseCompany flinsh")
    }

    async statistic(){
        await this.compareCompany();
        await this.positionToJob();
        var statisticData = new StatisticData(this.db,this.table,this.year,this.month);
	    statisticData.show();
    }

    //获取职位列表
    async stepList() {
        //加载列表
        for (const item of this.containerList) {
            await item.list();
        }
        console.log("StepList finish");
    }

    //列表转换职位
    async stepToJob() {
        for (const item of this.containerList) {
            await item.pageToJob();
        }
        console.log("StepToJob finish");
    }

    //职位详细信息
    async stepInfo() {
        for (const item of this.containerList) {
            await item.info();
        }
        console.log("stepInfo finish");
    }

    //bdStatus：
    //0-未识别
    //1-自动识别
    //2-库识别
    //3-地图识别
    //99-手动审核
    //77-回收站

    async groupCompany() {

        await this.db.open(this.table.job);
        const dataSet = await this.db.collection.group({
            "companyAlias": true
        }, {}, { count:0},new this.db.Code(
             function (doc, prev) {
                prev.count++
            }
            )
        );

        this.db.close();

        await this.db.open(this.table.company);
        await this.db.collection.remove({});

        await this.db.collection.insertMany(dataSet);
        this.db.close();
        console.log("groupCompany finish");
    }

    //根据别名聚合公司
    async parseFromJobs(){
        await this.db.open(this.table.company);
        const companyList = await this.db.findToArray({}, { companyAlias: 1});
        this.db.close();
        for (const company of companyList) {
            const data = await this.parseCompanyFromJob(company)

            await this.db.open(this.table.company);
            await this.db.updateById(company._id, data);
        }
        this.db.close();
        console.log("parseCompanyFromJobs finish");
    }

    //解析聚合公司展示
    async parseCompanyFromJob(it){
        const companyAlias = it.companyAlias;
        await this.db.open(this.table.job);
        const jobs = await this.db.findToArray({companyAlias:companyAlias});
        this.db.close();

        let company,alias,logo=null,salary=0,description,position=null,addr=null,count=0,bdStatus=0,companyId=null,source;
        jobs.map((job)=>{
            companyId = (!companyId)?job.companyId:companyId;
            source = (companyId==job.companyId)?job.source:source;
            company = job.company;
            alias = job.companyAlias;
            logo = (!logo)?job.companyLogo:logo;
            salary = (salary>job.average)?salary:job.average;
            addr = findBeseString(addr,job.addr);
            description= findBeseString(description,job.companyDetail);
            position = job.position;
            bdStatus = !position?0:1;
        });
        return {company,alias,logo,salary,description,position,addr,bdStatus,companyId,source};

        function findBeseString(t1,t2){
            if(t1==null){
                return t2;
            }
            if(t2==null){
                return t1;
            }
            return t1.length>t2.length?t1:t2;
        }
    }

    //从系统库中比较公司
    async compareCompany(){
        await this.db.open(this.table.repertoryCompany);
        const dataset = await this.db.collection.find({}).toArray();
        this.db.close();
        await this.db.open(this.table.company);
        for (const company of dataset) {
            const alias = company.alias;
            const i = await this.db.collection.findOne({
                alias: alias
            })
            if (!i) return;
            await this.db.updateById(i._id, {
                position: i.position,
                addr:(!i.addr)?i.addr:null,
                city: i.city,
                district: i.district,
                bdStatus:2,
                noLoad:true
            })
        }
        this.db.close()
        console.log("compareCompany finish")
    }

    async compareAddress(){
        await this.db.open(this.table.address);
        var RegExpPositionFilter = await this.db.findToArray({});
        this.db.close();

        function getPositionByAddr(address){
            let city=null,district=null,position=null,bdStatus=2;
            RegExpPositionFilter.forEach((x) => {
                if (address.match(x.reg)) {
                    //console.log("regmatch:"+x.reg);
                    city = x.city;
                    district = x.district;
                    position = x.position;
                    return;
                }
            })
            //console.log(address,position,city,district);
            return {district,position,city,bdStatus}
        }

        await this.db.open(this.table.company);
        const companyList = await this.db.findToArray({position: null,addr:{$ne:null}}, { addr:1});
        for (const company of companyList) {
            const t = getPositionByAddr(company.addr);
            if(t.position){
                await this.db.updateById(company._id, t);
            }
        }
        this.db.close();
        console.log("compareAddress finish");
    }

    //从百度地图加载
    async loadGeo() {
        await this.db.open(this.table.company);
        //解决根据地址查
        const addrs = await this.db.findToArray({position:null,addr:{$ne:null},noLoad:null},{addr:1});
        await updateGeo(this,addrs,"addr");

        //根据名称查
        const names = await this.db.findToArray({position:null,company:{$ne:null},noLoad:null},{company:1});
        await updateGeo(this,names,"name");

        this.db.close();

        console.log("loadGeo finish");

        async function updateGeo(context,arr,key){
            for (const it of arr) {
                let {position,city,district} = await addrToGeoFull(it[key]); //获取百度坐标
                await context.db.updateById(it._id, {position,city,district,bdStatus:3})
            }
        }
    }

    async fixedGeo() {
        //console.log(1);
        await this.db.open(this.table.company);
        //console.log(2);
        let arr = await this.db.findToArray({position: { $ne: null }, district: null, bdStatus: 3}, {position: 1 });
        for (const data of arr) {
            let cityAndDistrict = await geoToCityAndDistrict(data.position);
            //console.log(cityAndDistrict)
            await this.db.updateById(data._id, cityAndDistrict);
        }
        this.db.close();
        console.log("fixedGeo success");
    }

    async etlDistrict(){
        await this.db.open(this.table.company);
        const list = await this.db.findToArray({bdStatus:3},{addr:1,district:1,city:1});
        for (const it of list) {
            let {city,district} = positionFilter(it); //获取百度坐标
            await this.db.updateById(it._id, {city,district})
        }
        this.db.close();
        console.log("ETLDistrict finish");
    }

    //TODO:这里可以优化，如果部分属性有更新那么我们需要去刷新。
    async noLoadToRepertory(){
       await this.db.open(this.table.company);
       const data = await this.db.findToArray({noLoad: null});
       this.db.close();

       if(data.length == 0){
         return;
       }

       await this.db.open(this.table.repertoryCompany);
       await this.db.collection.insertMany(data);
       this.db.close();
       console.log("noLoadToRepertory success");

    }

    async positionToJob(){
        await this.db.open(this.table.company);
        let arr = await this.db.collection.find({"position":{"$ne":null},bdStatus:{"$ne":77}}, {
            position: 1,
            alias: 1,
            district:1,
            _id: 0
        }).toArray();
        this.db.close();
        await this.db.open(this.table.job);
        for(const i of arr){
            await this.db.collection.updateMany({
                companyAlias: i.alias,
                position:null
            }, {
                $set: {
                    district:i.district,
                    position: i.position
                }
            })
        }
        this.db.close();
        console.log("positionToJob success");
    }
}<commit_msg>更新同步库方案<commit_after>import Container from "./Container.js";
import { addrToGeoFull,geoToCityAndDistrict} from "./utils/bdHelper.js";
import {filter as positionFilter} from "./utils/ETL/positionETL.js";
import StatisticData from "./statistic/statisticData.js";

export default class Main {
    constructor(db, table, year, month) {
        this.db = db;
        this.table = table;
        this.containerList = [];
        this.year = year;
        this.month = month;
    }

    addConfig(config) {
        var container = new Container({ db: this.db, table: this.table, config: config, year: this.year, month: this.month });
        this.containerList.push(container);
    }

    //抓取数据完成
    async robotData() {
        await this.stepList();
        await this.stepToJob();
        await this.stepInfo();
        console.log("robotData flinsh")
    }

    async analyseCompany() {
        // await this.groupCompany();
        // await this.parseFromJobs();
        // await this.compareCompany();
        // await this.compareAddress();
        // await this.loadGeo();
        // await this.fixedGeo();
        // await this.etlDistrict();
        await this.noLoadToRepertory();
        console.log("analyseCompany flinsh")
    }

    async statistic(){
        await this.compareCompany();
        await this.positionToJob();
        var statisticData = new StatisticData(this.db,this.table,this.year,this.month);
	    statisticData.show();
    }

    //获取职位列表
    async stepList() {
        //加载列表
        for (const item of this.containerList) {
            await item.list();
        }
        console.log("StepList finish");
    }

    //列表转换职位
    async stepToJob() {
        for (const item of this.containerList) {
            await item.pageToJob();
        }
        console.log("StepToJob finish");
    }

    //职位详细信息
    async stepInfo() {
        for (const item of this.containerList) {
            await item.info();
        }
        console.log("stepInfo finish");
    }

    //bdStatus：
    //0-未识别
    //1-自动识别
    //2-库识别
    //3-地图识别
    //99-手动审核
    //77-回收站

    async groupCompany() {

        await this.db.open(this.table.job);
        const dataSet = await this.db.collection.group({
            "companyAlias": true
        }, {}, { count:0},new this.db.Code(
             function (doc, prev) {
                prev.count++
            }
            )
        );

        this.db.close();

        await this.db.open(this.table.company);
        await this.db.collection.remove({});

        await this.db.collection.insertMany(dataSet);
        this.db.close();
        console.log("groupCompany finish");
    }

    //根据别名聚合公司
    async parseFromJobs(){
        await this.db.open(this.table.company);
        const companyList = await this.db.findToArray({}, { companyAlias: 1});
        this.db.close();
        for (const company of companyList) {
            const data = await this.parseCompanyFromJob(company)

            await this.db.open(this.table.company);
            await this.db.updateById(company._id, data);
        }
        this.db.close();
        console.log("parseCompanyFromJobs finish");
    }

    //解析聚合公司展示
    async parseCompanyFromJob(it){
        const companyAlias = it.companyAlias;
        await this.db.open(this.table.job);
        const jobs = await this.db.findToArray({companyAlias:companyAlias});
        this.db.close();

        let company,alias,logo=null,salary=0,description,position=null,addr=null,count=0,bdStatus=0,companyId=null,source;
        jobs.map((job)=>{
            companyId = (!companyId)?job.companyId:companyId;
            source = (companyId==job.companyId)?job.source:source;
            company = job.company;
            alias = job.companyAlias;
            logo = (!logo)?job.companyLogo:logo;
            salary = (salary>job.average)?salary:job.average;
            addr = findBeseString(addr,job.addr);
            description= findBeseString(description,job.companyDetail);
            position = job.position;
            bdStatus = !position?0:1;
        });
        return {company,alias,logo,salary,description,position,addr,bdStatus,companyId,source};

        function findBeseString(t1,t2){
            if(t1==null){
                return t2;
            }
            if(t2==null){
                return t1;
            }
            return t1.length>t2.length?t1:t2;
        }
    }

    //从系统库中比较公司
    async compareCompany(){
        await this.db.open(this.table.repertoryCompany);
        const dataset = await this.db.collection.find({}).toArray();
        this.db.close();
        await this.db.open(this.table.company);
        for (const company of dataset) {
            const alias = company.alias;
            const i = await this.db.collection.findOne({
                alias: alias
            })
            if (!i) return;
            await this.db.updateById(i._id, {
                position: i.position,
                addr:(!i.addr)?i.addr:null,
                city: i.city,
                district: i.district,
                bdStatus:2,
                noLoad:true
            })
        }
        this.db.close()
        console.log("compareCompany finish")
    }

    async compareAddress(){
        await this.db.open(this.table.address);
        var RegExpPositionFilter = await this.db.findToArray({});
        this.db.close();

        function getPositionByAddr(address){
            let city=null,district=null,position=null,bdStatus=2;
            RegExpPositionFilter.forEach((x) => {
                if (address.match(x.reg)) {
                    //console.log("regmatch:"+x.reg);
                    city = x.city;
                    district = x.district;
                    position = x.position;
                    return;
                }
            })
            //console.log(address,position,city,district);
            return {district,position,city,bdStatus}
        }

        await this.db.open(this.table.company);
        const companyList = await this.db.findToArray({position: null,addr:{$ne:null}}, { addr:1});
        for (const company of companyList) {
            const t = getPositionByAddr(company.addr);
            if(t.position){
                await this.db.updateById(company._id, t);
            }
        }
        this.db.close();
        console.log("compareAddress finish");
    }

    //从百度地图加载
    async loadGeo() {
        await this.db.open(this.table.company);
        //解决根据地址查
        const addrs = await this.db.findToArray({position:null,addr:{$ne:null},noLoad:null},{addr:1});
        await updateGeo(this,addrs,"addr");

        //根据名称查
        // const names = await this.db.findToArray({position:null,company:{$ne:null},noLoad:null},{company:1});
        // await updateGeo(this,names,"name");

        this.db.close();

        console.log("loadGeo finish");

        async function updateGeo(context,arr,key){
            for (const it of arr) {
                let {position,city,district} = await addrToGeoFull(it[key]); //获取百度坐标
                await context.db.updateById(it._id, {position,city,district,bdStatus:3})
            }
        }
    }

    async fixedGeo() {
        //console.log(1);
        await this.db.open(this.table.company);
        //console.log(2);
        let arr = await this.db.findToArray({position: { $ne: null }, district: null, bdStatus: 3}, {position: 1 });
        for (const data of arr) {
            let cityAndDistrict = await geoToCityAndDistrict(data.position);
            //console.log(cityAndDistrict)
            await this.db.updateById(data._id, cityAndDistrict);
        }
        this.db.close();
        console.log("fixedGeo success");
    }

    async etlDistrict(){
        await this.db.open(this.table.company);
        const list = await this.db.findToArray({bdStatus:3},{addr:1,district:1,city:1});
        for (const it of list) {
            let {city,district} = positionFilter(it); //获取百度坐标
            await this.db.updateById(it._id, {city,district})
        }
        this.db.close();
        console.log("ETLDistrict finish");
    }

    //TODO:这里可以优化，如果部分属性有更新那么我们需要去刷新。
    async noLoadToRepertory(){

        //未在库中的，加入库
       await this.db.open(this.table.company);
       const data = await this.db.findToArray({noLoad: null});
       this.db.close();

       if(data.length == 0){
         return;
       }

       await this.db.open(this.table.repertoryCompany);
       await this.db.collection.insertMany(data);
       this.db.close();

       //已在库中的 如果有信息更新那么更新信息
       await this.db.open(this.table.company);
       const ALLCompany = await this.db.findToArray({noLoad: true});
       this.db.close();

       await this.db.open(this.table.repertoryCompany);
       for(const it of ALLCompany){
            const nologo = await this.db.collection.findOne({alias: alias,logo:null},{alias:1});
            if(nologo){
                await this.db.updateById(nologo._id, {logo:it.logo});
            }
            const nodescription = await this.db.collection.findOne({alias: alias,description:null},{aliass:1});
            if(nodescription){
                await this.db.updateById(nodescription._id, {description:it.description});
            }
       }
       this.db.close();

       console.log("noLoadToRepertory success");

    }

    async positionToJob(){
        await this.db.open(this.table.company);
        let arr = await this.db.collection.find({"position":{"$ne":null},bdStatus:{"$ne":77}}, {
            position: 1,
            alias: 1,
            district:1,
            _id: 0
        }).toArray();
        this.db.close();
        await this.db.open(this.table.job);
        for(const i of arr){
            await this.db.collection.updateMany({
                companyAlias: i.alias,
                position:null
            }, {
                $set: {
                    district:i.district,
                    position: i.position
                }
            })
        }
        this.db.close();
        console.log("positionToJob success");
    }
}<|endoftext|>


<commit_before>import minimist from 'minimist'
import push from './push'
import label from './label'

function help () {
  const help = `
      Usage
        $ wtr <action>

      Options
        -s, --sprint  Sprint number
        -c, --cohort  Team name (also org and repo name)
        -a, --assign  Specify individual assignee
        -b, --branch  Specify source branch

      Examples
        $ wtr push -s 1 -c kauri-2016
        $ wtr push -s 9 -c kotare-2015
        $ wtr push --sprint 4 kereru-2016
        $ wtr push -s 8 --cohort nikau-2016
        $ wtr push -s 1.1 -c tieke-2016
        $ wtr push -s 3 -c nikau-2016 -a richchurcher
        $ wtr push -s 5 -c kotare-2016 -b alternate-curriculum
        $ wtr label kahikatea-2016
  `
  console.log(help)
}

function waffle () {
  const argv = minimist(process.argv, {
    alias: {
      a: 'assign',
      b: 'branch',
      c: 'cohort',
      s: 'sprint'

    },
    string: [
      'action',
      'assign',
      'branch',
      'cohort',
      'sprint'
    ]
  })

  const action = argv._[2]
  switch (action) {
    case 'push':
      push(argv)
      break

    case 'label':
      label(argv)
      break

    default:
      help()
  }
}

export default waffle
<commit_msg>Add -e --except<commit_after>import minimist from 'minimist'
import push from './push'
import label from './label'

function help () {
  const help = `
      Usage
        $ wtr <action>

      Options
        -a, --assign  Specify individual assignee(s) (can be comma-separated list)
        -b, --branch  Specify source branch
        -c, --cohort  Team name (also org and repo name)
        -e, --except  Specify individuals NOT to assign (can be comma-separated list)
        -s, --sprint  Sprint number

      Examples
        $ wtr push -s 1 -c kauri-2016
        $ wtr push -s 9 -c kotare-2015
        $ wtr push --sprint 4 kereru-2016
        $ wtr push -s 8 --cohort nikau-2016
        $ wtr push -s 1.1 -c tieke-2016
        $ wtr push -s 3 -c nikau-2016 -a richchurcher
        $ wtr push -s 5 -c kotare-2016 -b alternate-curriculum
        $ wtr push -s 2 -c roa-2015 -e richchurcher
        $ wtr label kahikatea-2016
  `
  console.log(help)
}

function waffle () {
  const argv = minimist(process.argv, {
    alias: {
      a: 'assign',
      b: 'branch',
      c: 'cohort',
      e: 'except',
      s: 'sprint'

    },
    string: [
      'action',
      'assign',
      'branch',
      'cohort',
      'except',
      'sprint'
    ]
  })

  const action = argv._[2]
  switch (action) {
    case 'push':
      push(argv)
      break

    case 'label':
      label(argv)
      break

    default:
      help()
  }
}

export default waffle
<|endoftext|>


<commit_before>const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

const promisify = require('es6-promisify');

const stat = promisify(fs.stat);
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);

const mkdirp = promisify(require('mkdirp'));

const request = promisify(require('request'));

const cacheManager = require('cache-manager');

const memoryCache = cacheManager.caching({
    'max': 100,
    'store': 'memory'
});

const getMemoryCache = promisify(memoryCache.get);
const setMemoryCache = promisify(memoryCache.set);

const milliseconds = 1000;

const OPTION_DEFAULTS = {
    'cacheDirectory': 'temp/cache/',
    'cacheMemory': false,
    'ttl': 86400
};

/**
 * Request from URL from remote resource.
 *
 * @example raspar.requestFromUrl('http://www.google.com/humans.txt').then(() => {});
 * @param {String} url A URL string.
 * @param {Object} [options] Options object.
 * @param {Boolean} [options.ttl] TTL (Time to live) for local/memory based cache.
 * @param {Boolean} [options.cacheDirectory] Directory to store cache. Defaults to `temp/cache/`.
 * @param {Boolean} [options.cacheMemory] Store cache in memory instead of local files. Defaults to `false`.
 * @return {Object} Promise
 * @private
 */

const requestFromUrl = (url, options) => {

    const settings = Object.assign({}, OPTION_DEFAULTS, options);

    const hash = crypto.createHash('sha1')
        .update(url)
        .digest('hex');

    return request(url)
        .then((res) => {

            if (settings.cacheMemory) {

                return setMemoryCache(hash, JSON.stringify(res), {
                    'ttl': settings.ttl
                }).then(() => res);

            } else if (settings.cacheDirectory) {

                const cachePath = path.join(settings.cacheDirectory, hash);

                return mkdirp(settings.cacheDirectory)
                    .then(() => writeFile(cachePath, JSON.stringify(res.toJSON())))
                    .then(() => res);

            }

            return res;

        });

};

/**
 * Parses local cache and adds cached flag to the returned result.
 *
 * @example raspar.formatCache({}).then(function (res) { console.log(res.cached); });
 * @param {Object} [res] Stored cache response.
 * @return {Object} Promise
 * @public
 */

const formatCache = (res) => Object.assign(JSON.parse(res), {'cached': true});

/**
 * Requests a URL from cache.
 *
 * @example raspar.requestFromCache('http://www.google.com/humans.txt').then(() => {});
 * @param {String} url A URL string.
 * @param {Object} [options] Options object.
 * @param {Boolean} [options.ttl] TTL (Time to live) for local/memory based cache.
 * @param {Boolean} [options.cacheDirectory] Directory to store cache. Defaults to `temp/cache/`.
 * @param {Boolean} [options.cacheMemory] Store cache in memory instead of local files. Defaults to `false`.
 * @return {Object} Promise
 * @private
 */

const requestFromCache = (url, options) => {

    const settings = Object.assign({}, OPTION_DEFAULTS, options);

    const hash = crypto.createHash('sha1')
        .update(url)
        .digest('hex');

    if (settings.cacheMemory) {

        return getMemoryCache(hash).then((result) => {

            if (!result) {

                throw new Error('Current request not in cache.');

            }

            return formatCache(result);

        });

    } else if (settings.cacheDirectory) {

        const cachePath = path.join(settings.cacheDirectory, hash);

        return stat(cachePath)
            .then((stats) => {

                if (stats && ((Date.now() - stats.mtime.getTime()) / milliseconds) < settings.ttl) {

                    return readFile(cachePath, 'utf8').then(formatCache);

                }

                throw new Error('File no longer in cache');

            });

    }

    return new Promise(() => {

        throw new Error('No cache requested');

    });

};

/**
 * Requests a URL from either local cache or remote resource.
 *
 * @example raspar.requestFromUrlorCache('http://www.google.com/humans.txt').then(() => {});
 * @param {String} url A URL string.
 * @param {Object} [options] Options object.
 * @param {Boolean} [options.ttl] TTL (Time to live) for local/memory based cache.
 * @param {Boolean} [options.cacheDirectory] Directory to store cache. Defaults to `temp/cache/`.
 * @param {Boolean} [options.cacheMemory] Store cache in memory instead of local files. Defaults to `false`.
 * @return {Object} Promise
 * @private
 */

const requestFromUrlorCache = (url, options) => requestFromCache(url, options)
    .catch(() => requestFromUrl(url, options));

/**
 * Requests a URL or an array of URLs.
 *
 * @example raspar.get('http://www.google.com/humans.txt').then(() => {});
 * @example raspar.get(['http://www.google.com/humans.txt']).then(() => {});
 * @param {String|Array} url Either a URL string or an array of URLs.
 * @param {Object} [options] Options object.
 * @param {Boolean} [options.ttl] TTL (Time to live) for local/memory based cache.
 * @param {Boolean} [options.cacheDirectory] Directory to store cache. Defaults to `temp/cache/`.
 * @param {Boolean} [options.cacheMemory] Store cache in memory instead of local files. Defaults to `false`.
 * @return {Object} Promise
 * @public
 */

const get = (input, options) => {

    if (Array.isArray(input)) {

        return Promise.all(input.map((url) => requestFromUrlorCache(url, options)));

    }

    return requestFromUrlorCache(input, options);

};

module.exports = {
    formatCache,
    get,
    requestFromCache,
    requestFromUrl,
    requestFromUrlorCache
};
<commit_msg>Removed redundant toJSON call.<commit_after>const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

const promisify = require('es6-promisify');

const stat = promisify(fs.stat);
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);

const mkdirp = promisify(require('mkdirp'));

const request = promisify(require('request'));

const cacheManager = require('cache-manager');

const memoryCache = cacheManager.caching({
    'max': 100,
    'store': 'memory'
});

const getMemoryCache = promisify(memoryCache.get);
const setMemoryCache = promisify(memoryCache.set);

const milliseconds = 1000;

const OPTION_DEFAULTS = {
    'cacheDirectory': 'temp/cache/',
    'cacheMemory': false,
    'ttl': 86400
};

/**
 * Request from URL from remote resource.
 *
 * @example raspar.requestFromUrl('http://www.google.com/humans.txt').then(() => {});
 * @param {String} url A URL string.
 * @param {Object} [options] Options object.
 * @param {Boolean} [options.ttl] TTL (Time to live) for local/memory based cache.
 * @param {Boolean} [options.cacheDirectory] Directory to store cache. Defaults to `temp/cache/`.
 * @param {Boolean} [options.cacheMemory] Store cache in memory instead of local files. Defaults to `false`.
 * @return {Object} Promise
 * @private
 */

const requestFromUrl = (url, options) => {

    const settings = Object.assign({}, OPTION_DEFAULTS, options);

    const hash = crypto.createHash('sha1')
        .update(url)
        .digest('hex');

    return request(url)
        .then((res) => {

            if (settings.cacheMemory) {

                return setMemoryCache(hash, JSON.stringify(res), {
                    'ttl': settings.ttl
                }).then(() => res);

            } else if (settings.cacheDirectory) {

                const cachePath = path.join(settings.cacheDirectory, hash);

                return mkdirp(settings.cacheDirectory)
                    .then(() => writeFile(cachePath, JSON.stringify(res)))
                    .then(() => res);

            }

            return res;

        });

};

/**
 * Parses local cache and adds cached flag to the returned result.
 *
 * @example raspar.formatCache({}).then(function (res) { console.log(res.cached); });
 * @param {Object} [res] Stored cache response.
 * @return {Object} Promise
 * @public
 */

const formatCache = (res) => Object.assign(JSON.parse(res), {'cached': true});

/**
 * Requests a URL from cache.
 *
 * @example raspar.requestFromCache('http://www.google.com/humans.txt').then(() => {});
 * @param {String} url A URL string.
 * @param {Object} [options] Options object.
 * @param {Boolean} [options.ttl] TTL (Time to live) for local/memory based cache.
 * @param {Boolean} [options.cacheDirectory] Directory to store cache. Defaults to `temp/cache/`.
 * @param {Boolean} [options.cacheMemory] Store cache in memory instead of local files. Defaults to `false`.
 * @return {Object} Promise
 * @private
 */

const requestFromCache = (url, options) => {

    const settings = Object.assign({}, OPTION_DEFAULTS, options);

    const hash = crypto.createHash('sha1')
        .update(url)
        .digest('hex');

    if (settings.cacheMemory) {

        return getMemoryCache(hash).then((result) => {

            if (!result) {

                throw new Error('Current request not in cache.');

            }

            return formatCache(result);

        });

    } else if (settings.cacheDirectory) {

        const cachePath = path.join(settings.cacheDirectory, hash);

        return stat(cachePath)
            .then((stats) => {

                if (stats && ((Date.now() - stats.mtime.getTime()) / milliseconds) < settings.ttl) {

                    return readFile(cachePath, 'utf8').then(formatCache);

                }

                throw new Error('File no longer in cache');

            });

    }

    return new Promise(() => {

        throw new Error('No cache requested');

    });

};

/**
 * Requests a URL from either local cache or remote resource.
 *
 * @example raspar.requestFromUrlorCache('http://www.google.com/humans.txt').then(() => {});
 * @param {String} url A URL string.
 * @param {Object} [options] Options object.
 * @param {Boolean} [options.ttl] TTL (Time to live) for local/memory based cache.
 * @param {Boolean} [options.cacheDirectory] Directory to store cache. Defaults to `temp/cache/`.
 * @param {Boolean} [options.cacheMemory] Store cache in memory instead of local files. Defaults to `false`.
 * @return {Object} Promise
 * @private
 */

const requestFromUrlorCache = (url, options) => requestFromCache(url, options)
    .catch(() => requestFromUrl(url, options));

/**
 * Requests a URL or an array of URLs.
 *
 * @example raspar.get('http://www.google.com/humans.txt').then(() => {});
 * @example raspar.get(['http://www.google.com/humans.txt']).then(() => {});
 * @param {String|Array} url Either a URL string or an array of URLs.
 * @param {Object} [options] Options object.
 * @param {Boolean} [options.ttl] TTL (Time to live) for local/memory based cache.
 * @param {Boolean} [options.cacheDirectory] Directory to store cache. Defaults to `temp/cache/`.
 * @param {Boolean} [options.cacheMemory] Store cache in memory instead of local files. Defaults to `false`.
 * @return {Object} Promise
 * @public
 */

const get = (input, options) => {

    if (Array.isArray(input)) {

        return Promise.all(input.map((url) => requestFromUrlorCache(url, options)));

    }

    return requestFromUrlorCache(input, options);

};

module.exports = {
    formatCache,
    get,
    requestFromCache,
    requestFromUrl,
    requestFromUrlorCache
};
<|endoftext|>


<commit_before>var assert = require("assert");
var path = require("path");
var Q = require("q");
var createHash = require("crypto").createHash;
var getRequiredIDs = require("install").getRequiredIDs;
var slice = Array.prototype.slice;

var BuildContext = require("./context").BuildContext;

function ModuleReader(context, resolvers, builders) {
    var self = this;
    assert.ok(self instanceof ModuleReader);
    assert.ok(context instanceof BuildContext);
    assert.ok(resolvers instanceof Array);
    assert.ok(builders instanceof Array);

    var hash = createHash("sha1").update(context.configHash + "\0");

    function hashCallbacks(salt, cbs) {
        hash.update(salt + "\0");

        cbs = cbs.concat(slice.call(arguments, 2));
        cbs.forEach(function(cb) {
            assert.strictEqual(typeof cb, "function");
            hash.update(cb + "\0");
        });

        return cbs;
    }

    resolvers = hashCallbacks("resolvers", resolvers);
    builders = hashCallbacks("builders", builders, wrapModule);

    Object.defineProperties(self, {
        context: { value: context },
        resolvers: { value: resolvers },
        builders: { value: builders },
        salt: { value: hash.digest("hex") },
        promiseCache: { value: {} }
    });
}

ModuleReader.prototype = {
    getSourceP: function(id) {
        var context = this.context;
        var copy = this.resolvers.slice(0).reverse();
        assert.ok(copy.length > 0, "no source resolvers registered");

        function tryNextResolverP() {
            var resolve = copy.pop();
            var result = resolve && resolve.call(context, id);
            var promise = Q.resolve(result);
            return resolve ? promise.then(function(result) {
                return result || tryNextResolverP();
            }) : promise;
        }

        return tryNextResolverP();
    },

    readModuleP: function(id) {
        var cache = this.promiseCache;
        if (cache.hasOwnProperty(id))
            return cache[id];

        // TODO Invalidate cache when files change on disk.
        return cache[id] = this.noCacheReadModuleP(id);
    },

    noCacheReadModuleP: function(id) {
        var reader = this;

        var hash = createHash("sha1")
            .update("module\0")
            .update(id + "\0")
            .update(reader.salt + "\0");

        var promise = reader.getSourceP(id).then(function(source) {
            assert.strictEqual(
                typeof source, "string",
                "unable to resolve module " + JSON.stringify(id));
            hash.update(source.length + "\0" + source);
            return source;
        });

        reader.builders.forEach(function(build) {
            promise = promise.then(function(source) {
                return build.call(reader.context, id, source);
            });
        });

        return promise.then(function(source) {
            var deps = getRequiredIDs(id, source);
            return new Module(reader, id, hash.digest("hex"), deps, source);
        });
    },

    readMultiP: function(ids) {
        return Q.all(ids.map(this.readModuleP, this));
    }
};

exports.ModuleReader = ModuleReader;

function wrapModule(id, source) {
    return "install(ID,function(require,exports,module){SRC});"
        .replace("ID", JSON.stringify(id))
        .replace("SRC", source);
}

function Module(reader, id, hash, deps, source) {
    var self = this;
    assert.ok(self instanceof Module);
    assert.ok(reader instanceof ModuleReader);

    Object.defineProperties(self, {
        reader: { value: reader },
        id: { value: id },
        hash: { value: hash },
        deps: { value: deps },
        source: { value: source }
    });
}

Module.prototype = {
    getRequiredP: function() {
        return this.reader.readMultiP(this.deps);
    },

    toString: function() {
        return "Module(" + JSON.stringify(this.id) + ")";
    },

    resolveId: function(id) {
        return path.normalize(path.join(this.id, "..", id));
    }
};
<commit_msg>Avoid bizarre String.prototype.replace backreferencing behavior.<commit_after>var assert = require("assert");
var path = require("path");
var Q = require("q");
var createHash = require("crypto").createHash;
var getRequiredIDs = require("install").getRequiredIDs;
var slice = Array.prototype.slice;

var BuildContext = require("./context").BuildContext;

function ModuleReader(context, resolvers, builders) {
    var self = this;
    assert.ok(self instanceof ModuleReader);
    assert.ok(context instanceof BuildContext);
    assert.ok(resolvers instanceof Array);
    assert.ok(builders instanceof Array);

    var hash = createHash("sha1").update(context.configHash + "\0");

    function hashCallbacks(salt, cbs) {
        hash.update(salt + "\0");

        cbs = cbs.concat(slice.call(arguments, 2));
        cbs.forEach(function(cb) {
            assert.strictEqual(typeof cb, "function");
            hash.update(cb + "\0");
        });

        return cbs;
    }

    resolvers = hashCallbacks("resolvers", resolvers);
    builders = hashCallbacks("builders", builders, wrapModule);

    Object.defineProperties(self, {
        context: { value: context },
        resolvers: { value: resolvers },
        builders: { value: builders },
        salt: { value: hash.digest("hex") },
        promiseCache: { value: {} }
    });
}

ModuleReader.prototype = {
    getSourceP: function(id) {
        var context = this.context;
        var copy = this.resolvers.slice(0).reverse();
        assert.ok(copy.length > 0, "no source resolvers registered");

        function tryNextResolverP() {
            var resolve = copy.pop();
            var result = resolve && resolve.call(context, id);
            var promise = Q.resolve(result);
            return resolve ? promise.then(function(result) {
                return result || tryNextResolverP();
            }) : promise;
        }

        return tryNextResolverP();
    },

    readModuleP: function(id) {
        var cache = this.promiseCache;
        if (cache.hasOwnProperty(id))
            return cache[id];

        // TODO Invalidate cache when files change on disk.
        return cache[id] = this.noCacheReadModuleP(id);
    },

    noCacheReadModuleP: function(id) {
        var reader = this;

        var hash = createHash("sha1")
            .update("module\0")
            .update(id + "\0")
            .update(reader.salt + "\0");

        var promise = reader.getSourceP(id).then(function(source) {
            assert.strictEqual(
                typeof source, "string",
                "unable to resolve module " + JSON.stringify(id));
            hash.update(source.length + "\0" + source);
            return source;
        });

        reader.builders.forEach(function(build) {
            promise = promise.then(function(source) {
                return build.call(reader.context, id, source);
            });
        });

        return promise.then(function(source) {
            var deps = getRequiredIDs(id, source);
            return new Module(reader, id, hash.digest("hex"), deps, source);
        });
    },

    readMultiP: function(ids) {
        return Q.all(ids.map(this.readModuleP, this));
    }
};

exports.ModuleReader = ModuleReader;

function wrapModule(id, source) {
    return "install(" + JSON.stringify(id) +
        ",function(require,exports,module){" +
        source +
        "});";
}

function Module(reader, id, hash, deps, source) {
    var self = this;
    assert.ok(self instanceof Module);
    assert.ok(reader instanceof ModuleReader);

    Object.defineProperties(self, {
        reader: { value: reader },
        id: { value: id },
        hash: { value: hash },
        deps: { value: deps },
        source: { value: source }
    });
}

Module.prototype = {
    getRequiredP: function() {
        return this.reader.readMultiP(this.deps);
    },

    toString: function() {
        return "Module(" + JSON.stringify(this.id) + ")";
    },

    resolveId: function(id) {
        return path.normalize(path.join(this.id, "..", id));
    }
};
<|endoftext|>


<commit_before>define(function (require, exports, module) {

/**
 * 组件基类
 *
 * @module Widget
 */

'use strict';

var $ = require('$'),
  Base = require('base');

var DELEGATE_SPLITTER = /^(\S+)\s*(.*)$/,
  DELEGATE_NAMESPACE = '.delegate-widget-',
  DATA_WIDGET_UNIQUEID = 'data-widget-uid';

var cachedInstances = {};

// TODO: 检查是否有内存泄漏发生

/**
 * 组件基类
 *
 * @class Widget
 * @constructor
 * @extends Base
 *
 * @example
 * ```
 * // 创建子类
 * var PersonWidget = Widget.extend({
 *   setup: function () {
 *     // 通知事件 `setup`
 *     this.fire('setup', this.option('name'), this.option('age'));
 *   }
 * });
 * // 创建子类实例
 * var tom = new PersonWidget({
 *   name: 'Tom',
 *   age: 21,
 *   // 订阅事件
 *   events: {
 *     // `setup`
 *     setup: function (e, name, age) {
 *       // e.type === 'setup'
 *       // name === 'Tom'
 *       // age === 21
 *       // this === tom
 *     },
 *     // AOP `before:setup`
 *     'before:setup': function (e) {
 *       // 执行 `setup` 方法前执行
 *       // 此处返回 `false` 将阻止 `setup` 方法执行
 *     },
 *     // AOP `before:setup`
 *     'before:setup': function (e) {
 *       // 执行 `setup` 方法后执行
 *       // 如果 `setup` 方法被阻止，就不会执行到这里
 *     }
 *   },
 *   // 代理事件
 *   delegates: {
 *     'click': function (e) {
 *       // e.target === this.element[0]
 *       // this === tom
 *     },
 *     'mouseover .avatar': function (e) {
 *       // 鼠标悬停在 element 里的 `.avatar` 元素
 *     }
 *   }
 * });
 * ```
 */
var Widget = Base.extend({

  /**
   * 初始化函数，将自动执行；实现事件自动订阅与初始化组件参数
   *
   * @method initialize
   * @param {Object} options 组件参数
   */
  initialize: function (options) {
    var self = this;

    Widget.superclass.initialize.apply(self, arguments);

    // self.UI = {
    //   'default': {}
    // };

    /**
     * 实例唯一ID
     *
     * @property {String} uniqueId
     */
    self.uniqueId = uniqueId();

    /**
     * 用于 DOM 事件绑定的 NAMESPACE
     *
     * @property {String} delegateNS
     */
    self.delegateNS = DELEGATE_NAMESPACE + self.uniqueId;

    self.initCnE();

    self.initDnV();

    self.initDelegates();

    self.initTrigger();

    self.setup();

    // 储存实例
    cachedInstances[self.uniqueId] = self;
  },

  /**
   * 默认参数，子类自动继承并覆盖
   *
   * @property {Object} defaults
   * @type {Object}
   */
  defaults: {
    // 默认插入到的容器，设置为 `null` 则不执行插入
    container: 'body',
    classPrefix: 'ue-component',
    // CSS表，初始化时自动设置
    css: { },
    // 模板数据
    data: { },
    // TODO: ue-component 改成 pandora 之类的，以与旧版组件做区别？
    element: '

',
    // 实现 element 插入到 DOM，基于 container
    insert: function () {
      this.container.length && this.container.append(this.element);
    }
  },

  /**
   * 寻找 element 后代，参数为空时，返回 element
   *
   * @method $
   * @param {Mixed} [selector] 选择符
   * @return {Object} jQuery 包装的 DOM 节点
   */
  $: function (selector) {
    return selector ? this.element.find(selector) : this.element;
  },

  /**
   * 获取 role 对应的元素，通过 [data-role=xxx]
   *
   * @method role
   * @param {Mixed} role data-role 的值
   * @return {Object} jQuery 包装的 DOM 节点
   */
  role: function (role) {
    return this.$('[data-role=' + role + ']');
  },

  /**
   * 获取模板，基于 `option` 方法
   *
   * @method data
   * @param {String} [key] 键
   * @param {Mixed} [value] 值
   * @return {Mixed} 整个 data 参数列表或指定参数值
   */
  data: function (key, value) {
    return this.option(key, value, this.option('data'));
  },

  /**
   * 初始化 `container` 与 `element`
   *
   * @method initCnE
   * @return {Object} 当前实例
   */
  initCnE: function () {
    /**
     * 容器/插入参考点
     *
     * @property {Object} container（容器
     */
    this.container = $(this.option('container'));

    /**
     * 组件根元素
     *
     * @property {Object} element
     */
    this.element = $(this.option('element'))
        .attr(DATA_WIDGET_UNIQUEID, this.uniqueId)
        .addClass(this.option('classPrefix'))
        .css(this.option('css'));

    return this;
  },

  /**
   * 初始化 `document` 与 `viewport`
   *
   * @method initDnV
   * @return {Object} 当前实例
   */
  initDnV: function () {
    /**
     * `element` 所在的 `document` 对象
     *
     * @property {Document} document
     */
    this.document = this.element.prop('ownerDocument');

    /**
     * `element` 所在的 `window` 对象
     *
     * @property {Window} viewport
     */
    this.viewport = (function (doc) {
      return doc.defaultView || doc.parentWindow;
    })(this.document);

    return this;
  },

  /**
   * 事件代理，绑定在 element 上
   *
   * @method initDelegates
   * @param {Object|Function} [delegates] 代理事件列表
   * @return {Object} 当前实例
   */
  initDelegates: function (delegates) {
    var self = this;

    delegates || (delegates = self.option('delegates'));

    if (!delegates) {
      return self;
    }

    if (typeof delegates === 'function') {
      delegates = delegates.call(self);
    }

    $.each(delegates, function (key, callback) {
      var match = key.match(DELEGATE_SPLITTER),
        event = match[1] + self.delegateNS;

      if (typeof callback === 'string') {
        callback = self[callback];
      }

      if (match[2]) {
        self.element.on(event, match[2], function () {
          callback.apply(self, arguments);
        });
      } else {
        self.element.on(event, function () {
          callback.apply(self, arguments);
        });
      }
    });

    return self;
  },

  /**
   * 显示触发器
   *
   * @method initTrigger
   * @param {String} [trigger] 触发器
   * @return {Object} 当前实例
   */
  initTrigger: function (trigger) {
    var self = this;

    trigger || (trigger = self.option('trigger'));

    if (!trigger) {
      return self;
    }

    $(self.document)
      .on('click' + self.delegateNS, trigger, function (e) {
        e.preventDefault();

        self.activeTrigger = e.currentTarget;

        if (!self.rendered) {
          self.render();
        }

        self.show();
      });

    // 如果有 trigger，则默认隐藏
    self.element.hide();

    return self;
  },

  /**
   * 自动执行的设置函数，预留用于子类覆盖
   *
   * @method setup
   */
  setup: function () {
  },

  /**
   * 插入elemnt到container
   * @method render
   * @return {Object} 当前实例
   */
  render: function () {
    var self = this,
      html,
      template = self.option('template');

    if (typeof template === 'function') {
      html = template(self.option('data'));
    } else {
      html = self.option('content');
    }

    if (typeof html !== 'undefined') {
      self.element.html(html);
    }

    if (!self.rendered) {
      // 插入到容器中
      self.option('insert').call(self);

      self.rendered = true;
    }

    /**
     * 通知渲染
     *
     * @event render
     * @param {Object} e Event.
     */
    self.fire('render');

    return self;
  },

  /**
   * 判断：显示状态等，参见 `jq` 的 `is`
   *
   * @example
   * ```
   * instance.is(':hidden')
   * ```
   *
   * @method is
   * @param {Mixed} condition 判断语句
   * @return {Boolean}
   */
  is: function (contidtion) {
    return this.element.is(contidtion);
  },

  /**
   * 显示
   *
   * @method show
   * @return {Object} 当前实例
   */
  show: function () {
    this.element.show();

    /**
     * 通知显示
     *
     * @event show
     * @param {Object} e Event.
     */
    this.fire('show');

    return this;
  },

  /**
   * 隐藏
   *
   * @method hide
   * @return {Object} 当前实例
   */
  hide: function () {
    this.element.hide();

    /**
     * 通知隐藏
     *
     * @event hide
     * @param {Object} e Event.
     */
    this.fire('hide');

    return this;
  },

  /**
   * 销毁当前组件对象
   * @method destroy
   */
  destroy: function () {
    /**
     * 通知销毁
     *
     * @event destroy
     * @param {Object} e Event.
     */
    this.fire('destroy');

    // 移除 element 事件代理
    this.element && this.element.off(this.delegateNS);

    // 移除 document 事件代理
    this.trigger && this.document && $(this.document).off(this.delegateNS);

    // 从DOM中移除element
    this.element.remove();

    Widget.superclass.destroy.apply(this);
  }

});

var uniqueId = (function () {
  var ids = {};
  return function () {
    var id = Math.random().toString(36).substr(2);
    if (ids[id]) {
      return uniqueId();
    }
    ids[id] = true;
    return id;
  };
})();

// For memory leak, from aralejs
$(window).unload(function() {
  var uid;
  for(uid in cachedInstances) {
    cachedInstances[uid].destroy();
  }
});

/**
 * 通过 element 取 widget 实例
 *
 * @method get
 * @return {Mixed} selector
 * @static
 */
Widget.get = function (selector) {
  var element = $(selector).eq(0), uid;
  element.length && (uid = element.attr(DATA_WIDGET_UNIQUEID));
  return cachedInstances[uid];
};

module.exports = Widget;

});
<commit_msg>解决BUG<commit_after>define(function (require, exports, module) {

/**
 * 组件基类
 *
 * @module Widget
 */

'use strict';

var $ = require('$'),
  Base = require('base');

var DELEGATE_SPLITTER = /^(\S+)\s*(.*)$/,
  DELEGATE_NAMESPACE = '.delegate-widget-',
  DATA_WIDGET_UNIQUEID = 'data-widget-uid';

var cachedInstances = {};

// TODO: 检查是否有内存泄漏发生

/**
 * 组件基类
 *
 * @class Widget
 * @constructor
 * @extends Base
 *
 * @example
 * ```
 * // 创建子类
 * var PersonWidget = Widget.extend({
 *   setup: function () {
 *     // 通知事件 `setup`
 *     this.fire('setup', this.option('name'), this.option('age'));
 *   }
 * });
 * // 创建子类实例
 * var tom = new PersonWidget({
 *   name: 'Tom',
 *   age: 21,
 *   // 订阅事件
 *   events: {
 *     // `setup`
 *     setup: function (e, name, age) {
 *       // e.type === 'setup'
 *       // name === 'Tom'
 *       // age === 21
 *       // this === tom
 *     },
 *     // AOP `before:setup`
 *     'before:setup': function (e) {
 *       // 执行 `setup` 方法前执行
 *       // 此处返回 `false` 将阻止 `setup` 方法执行
 *     },
 *     // AOP `before:setup`
 *     'before:setup': function (e) {
 *       // 执行 `setup` 方法后执行
 *       // 如果 `setup` 方法被阻止，就不会执行到这里
 *     }
 *   },
 *   // 代理事件
 *   delegates: {
 *     'click': function (e) {
 *       // e.target === this.element[0]
 *       // this === tom
 *     },
 *     'mouseover .avatar': function (e) {
 *       // 鼠标悬停在 element 里的 `.avatar` 元素
 *     }
 *   }
 * });
 * ```
 */
var Widget = Base.extend({

  /**
   * 初始化函数，将自动执行；实现事件自动订阅与初始化组件参数
   *
   * @method initialize
   * @param {Object} options 组件参数
   */
  initialize: function (options) {
    var self = this;

    Widget.superclass.initialize.apply(self, arguments);

    // self.UI = {
    //   'default': {}
    // };

    /**
     * 实例唯一ID
     *
     * @property {String} uniqueId
     */
    self.uniqueId = uniqueId();

    /**
     * 用于 DOM 事件绑定的 NAMESPACE
     *
     * @property {String} delegateNS
     */
    self.delegateNS = DELEGATE_NAMESPACE + self.uniqueId;

    self.initCnE();

    self.initDnV();

    self.initDelegates();

    self.initTrigger();

    self.setup();

    // 储存实例
    cachedInstances[self.uniqueId] = self;
  },

  /**
   * 默认参数，子类自动继承并覆盖
   *
   * @property {Object} defaults
   * @type {Object}
   */
  defaults: {
    // 默认插入到的容器，设置为 `null` 则不执行插入
    container: 'body',
    classPrefix: 'ue-component',
    // CSS表，初始化时自动设置
    css: { },
    // 模板数据
    data: { },
    // TODO: ue-component 改成 pandora 之类的，以与旧版组件做区别？
    element: '

',
    // 实现 element 插入到 DOM，基于 container
    insert: function () {
      this.container.length && this.container.append(this.element);
    }
  },

  /**
   * 寻找 element 后代，参数为空时，返回 element
   *
   * @method $
   * @param {Mixed} [selector] 选择符
   * @return {Object} jQuery 包装的 DOM 节点
   */
  $: function (selector) {
    return selector ? this.element.find(selector) : this.element;
  },

  /**
   * 获取 role 对应的元素，通过 [data-role=xxx]
   *
   * @method role
   * @param {Mixed} role data-role 的值
   * @return {Object} jQuery 包装的 DOM 节点
   */
  role: function (role) {
    return this.$('[data-role=' + role + ']');
  },

  /**
   * 获取模板，基于 `option` 方法
   *
   * @method data
   * @param {String} [key] 键
   * @param {Mixed} [value] 值
   * @return {Mixed} 整个 data 参数列表或指定参数值
   */
  data: function (key, value) {
    return this.option(key, value, this.option('data'));
  },

  /**
   * 初始化 `container` 与 `element`
   *
   * @method initCnE
   * @return {Object} 当前实例
   */
  initCnE: function () {
    /**
     * 容器/插入参考点
     *
     * @property {Object} container（容器
     */
    this.container = $(this.option('container'));

    /**
     * 组件根元素
     *
     * @property {Object} element
     */
    this.element = $(this.option('element'))
        .attr(DATA_WIDGET_UNIQUEID, this.uniqueId)
        .addClass(this.option('classPrefix'))
        .css(this.option('css'));

    return this;
  },

  /**
   * 初始化 `document` 与 `viewport`
   *
   * @method initDnV
   * @return {Object} 当前实例
   */
  initDnV: function () {
    /**
     * `element` 所在的 `document` 对象
     *
     * @property {Document} document
     */
    this.document = this.element.prop('ownerDocument');

    /**
     * `element` 所在的 `window` 对象
     *
     * @property {Window} viewport
     */
    this.viewport = (function (doc) {
      return doc.defaultView || doc.parentWindow;
    })(this.document);

    return this;
  },

  /**
   * 事件代理，绑定在 element 上
   *
   * @method initDelegates
   * @param {Object|Function} [delegates] 代理事件列表
   * @return {Object} 当前实例
   */
  initDelegates: function (delegates) {
    var self = this;

    delegates || (delegates = self.option('delegates'));

    if (!delegates) {
      return self;
    }

    if (typeof delegates === 'function') {
      delegates = delegates.call(self);
    }

    $.each(delegates, function (key, callback) {
      var match = key.match(DELEGATE_SPLITTER),
        event = match[1] + self.delegateNS;

      if (typeof callback === 'string') {
        callback = self[callback];
      }

      if (match[2]) {
        self.element.on(event, match[2], function () {
          callback.apply(self, arguments);
        });
      } else {
        self.element.on(event, function () {
          callback.apply(self, arguments);
        });
      }
    });

    return self;
  },

  /**
   * 显示触发器
   *
   * @method initTrigger
   * @param {String} [trigger] 触发器
   * @return {Object} 当前实例
   */
  initTrigger: function (trigger) {
    var self = this;

    trigger || (trigger = self.option('trigger'));

    if (!trigger) {
      return self;
    }

    $(self.document)
      .on('click' + self.delegateNS, trigger, function (e) {
        e.preventDefault();

        self.activeTrigger = e.currentTarget;

        if (!self.rendered) {
          self.render();
        }

        self.show();
      });

    // 如果有 trigger，则默认隐藏
    self.element.hide();

    return self;
  },

  /**
   * 自动执行的设置函数，预留用于子类覆盖
   *
   * @method setup
   */
  setup: function () {
  },

  /**
   * 插入elemnt到container
   * @method render
   * @return {Object} 当前实例
   */
  render: function () {
    var self = this,
      html,
      template = self.option('template');

    if (typeof template === 'function') {
      html = template(self.option('data'));
    } else {
      html = self.option('content');
    }

    if (typeof html !== 'undefined') {
      self.element.html(html);
    }

    if (!self.rendered) {
      // 插入到容器中
      self.option('insert').call(self);

      self.rendered = true;
    }

    /**
     * 通知渲染
     *
     * @event render
     * @param {Object} e Event.
     */
    self.fire('render');

    return self;
  },

  /**
   * 判断：显示状态等，参见 `jq` 的 `is`
   *
   * @example
   * ```
   * instance.is(':hidden')
   * ```
   *
   * @method is
   * @param {Mixed} condition 判断语句
   * @return {Boolean}
   */
  is: function (contidtion) {
    return this.element.is(contidtion);
  },

  /**
   * 显示
   *
   * @method show
   * @return {Object} 当前实例
   */
  show: function () {
    this.element.show();

    /**
     * 通知显示
     *
     * @event show
     * @param {Object} e Event.
     */
    this.fire('show');

    return this;
  },

  /**
   * 隐藏
   *
   * @method hide
   * @return {Object} 当前实例
   */
  hide: function () {
    this.element.hide();

    /**
     * 通知隐藏
     *
     * @event hide
     * @param {Object} e Event.
     */
    this.fire('hide');

    return this;
  },

  /**
   * 销毁当前组件对象
   * @method destroy
   */
  destroy: function () {
    /**
     * 通知销毁
     *
     * @event destroy
     * @param {Object} e Event.
     */
    this.fire('destroy');

    // 移除 element 事件代理
    this.element && this.element.off(this.delegateNS);

    // 移除 document 事件代理
    this.document && $(this.document).off(this.delegateNS);

    // 从DOM中移除element
    this.element.remove();

    Widget.superclass.destroy.apply(this);
  }

});

var uniqueId = (function () {
  var ids = {};
  return function () {
    var id = Math.random().toString(36).substr(2);
    if (ids[id]) {
      return uniqueId();
    }
    ids[id] = true;
    return id;
  };
})();

// For memory leak, from aralejs
$(window).unload(function() {
  var uid;
  for(uid in cachedInstances) {
    cachedInstances[uid].destroy();
  }
});

/**
 * 通过 element 取 widget 实例
 *
 * @method get
 * @return {Mixed} selector
 * @static
 */
Widget.get = function (selector) {
  var element = $(selector).eq(0), uid;
  element.length && (uid = element.attr(DATA_WIDGET_UNIQUEID));
  return cachedInstances[uid];
};

module.exports = Widget;

});
<|endoftext|>


<commit_before>var util = require('util'),
    Writable = require('stream').Writable;

var Reader = exports.Reader = function (input) {
    this._helper = new StreamReaderHelper(input);
};

Reader.prototype = {
    read: function *(encoding) {
        var chunk = yield this._helper.readData();
        return chunk && (encoding ? chunk.toString(encoding) : chunk);
    }
};

var LineReader = exports.LineReader = function (input) {
    this._helper = new StreamReaderHelper(input);
    this._lines = [];
};

LineReader.prototype = {
    read: function *() {
        if (this._lines.length > 0) {
            return this._lines.shift().replace('\r', '');
        } else {
            while (this._lines.length === 0) {
                var chunk = yield this._helper.readData();
                if (!chunk) {
                    var ret = this._incomplete;
                    this._incomplete = null;
                    return ret;
                }

                var data = chunk.toString(),
                    lines = data.split('\n'),
                    incomplete = data[data.length - 1] !== '\n';

                if (this._incomplete) lines[0] = this._incomplete + lines[0];
                this._incomplete = incomplete ? lines.pop() : null;

                this._lines = lines;
            }

            return this._lines.shift().replace('\r', '');
        }
    }
};

function StreamReaderHelper (input) {
    Writable.call(this);

    var me = this;
    input.pipe(this);
    input.on('error', function (err) {
        console.log('err');
        me._err = err;
        if (me._readCb) {
            me._readCb(err);
            me._readCb = null;
        }
    });

    this.on('finish', function () {
        console.log('finish');
        me._end = true;
        if (me._readCb) {
            me._readCb();
            me._readCb = null;
        }
    });
}

util.inherits(StreamReaderHelper, Writable);

StreamReaderHelper.prototype.readData = function () {
    var me = this;

    return function (cb) {
        if (me._data) {
            var data = me._data,
                dataCb = me._dataCb;

            me._data = null;
            me._dataCb = null;

            dataCb();
            process.nextTick(function () { cb(null, data) });
        } else if (me._end) {
            console.log('end');
            cb();
        } else if (me._err) {
            console.log('err');
            cb(me._err);
        } else {
            me._readCb = cb;
        }
    };
};

StreamReaderHelper.prototype._write = function (chunk, encoding, callback) {
    if (this._readCb) {
        this._readCb(null, chunk);
        this._readCb = null;
        callback();
    } else {
        this._data = chunk;
        this._dataCb = callback;
    }
};
<commit_msg>remove log.<commit_after>var util = require('util'),
    Writable = require('stream').Writable;

var Reader = exports.Reader = function (input) {
    this._helper = new StreamReaderHelper(input);
};

Reader.prototype = {
    read: function *(encoding) {
        var chunk = yield this._helper.readData();
        return chunk && (encoding ? chunk.toString(encoding) : chunk);
    }
};

var LineReader = exports.LineReader = function (input) {
    this._helper = new StreamReaderHelper(input);
    this._lines = [];
};

LineReader.prototype = {
    read: function *() {
        if (this._lines.length > 0) {
            return this._lines.shift().replace('\r', '');
        } else {
            while (this._lines.length === 0) {
                var chunk = yield this._helper.readData();
                if (!chunk) {
                    var ret = this._incomplete;
                    this._incomplete = null;
                    return ret;
                }

                var data = chunk.toString(),
                    lines = data.split('\n'),
                    incomplete = data[data.length - 1] !== '\n';

                if (this._incomplete) lines[0] = this._incomplete + lines[0];
                this._incomplete = incomplete ? lines.pop() : null;

                this._lines = lines;
            }

            return this._lines.shift().replace('\r', '');
        }
    }
};

function StreamReaderHelper (input) {
    Writable.call(this);

    var me = this;
    input.pipe(this);
    input.on('error', function (err) {
        me._err = err;
        if (me._readCb) {
            me._readCb(err);
            me._readCb = null;
        }
    });

    this.on('finish', function () {
        me._end = true;
        if (me._readCb) {
            me._readCb();
            me._readCb = null;
        }
    });
}

util.inherits(StreamReaderHelper, Writable);

StreamReaderHelper.prototype.readData = function () {
    var me = this;

    return function (cb) {
        if (me._data) {
            var data = me._data,
                dataCb = me._dataCb;

            me._data = null;
            me._dataCb = null;

            dataCb();
            process.nextTick(function () { cb(null, data) });
        } else if (me._end) {
            cb();
        } else if (me._err) {
            cb(me._err);
        } else {
            me._readCb = cb;
        }
    };
};

StreamReaderHelper.prototype._write = function (chunk, encoding, callback) {
    if (this._readCb) {
        this._readCb(null, chunk);
        this._readCb = null;
        callback();
    } else {
        this._data = chunk;
        this._dataCb = callback;
    }
};
<|endoftext|>


<commit_before>/**
 * Created by huangxinghui on 2015/5/15.
 */

var $ = require('jquery');
var _ = require('underscore');
var Class = require('./clazz');
var delegateEventSplitter = /^(\S+)\s*(.*)$/;

module.exports = Class.extend({
    options: null,
    events: null,

    init: function (element, options) {
        this.$element = $(element);
        this.options = $.extend({}, this.options, this.$element.data(), options);

        this._create();
        this.delegateEvents();
    },

    _create: $.noop,

    delegateEvents: function (events) {
        if (!(events || (events = this.events))) return;

        this.undelegateEvents();
        for (var key in events) {
            var method = events[key];
            if (!_.isFunction(method)) method = this[events[key]];
            if (!method) continue;
            var match = key.match(delegateEventSplitter);
            this.delegate(match[1], match[2], _.bind(method, this));
        }
    },

    delegate: function (eventName, selector, listener) {
        this.$element.on(eventName + '.' + this.widgetName, selector, listener);
    },

    undelegateEvents: function () {
        if (this.$element)
            this.$element.off('.' + this.widgetName);
    },

    undelegate: function (eventName, selector, listener) {
        this.$element.off(eventName + '.' + this.widgetName, selector, listener);
    },

    trigger: function (type, event, data) {
        var prop, orig,
            callback = this.options[type];

        data = data || {};
        event = $.Event(event);
        event.type = (type + '.' + this.widgetName).toLowerCase();
        event.target = this.$element[0];

        // copy original event properties over to the new event
        orig = event.originalEvent;
        if (orig) {
            for (prop in orig) {
                if (!( prop in event )) {
                    event[prop] = orig[prop];
                }
            }
        }

        this.$element.trigger(event, data);
    },

    destroy: function () {
        this.undelegateEvents();
        this.$element.removeData(this.widgetName);
    }
});<commit_msg>change widget trigger method<commit_after>/**
 * Created by huangxinghui on 2015/5/15.
 */

var $ = require('jquery');
var _ = require('underscore');
var Class = require('./clazz');
var delegateEventSplitter = /^(\S+)\s*(.*)$/;

module.exports = Class.extend({
    options: null,
    events: null,

    init: function (element, options) {
        this.$element = $(element);
        this.options = $.extend({}, this.options, this.$element.data(), options);

        this._create();
        this.delegateEvents();
    },

    _create: $.noop,

    delegateEvents: function (events) {
        if (!(events || (events = this.events))) return;

        this.undelegateEvents();
        for (var key in events) {
            var method = events[key];
            if (!_.isFunction(method)) method = this[events[key]];
            if (!method) continue;
            var match = key.match(delegateEventSplitter);
            this.delegate(match[1], match[2], _.bind(method, this));
        }
    },

    delegate: function (eventName, selector, listener) {
        this.$element.on(eventName + '.' + this.widgetName, selector, listener);
    },

    undelegateEvents: function () {
        if (this.$element)
            this.$element.off('.' + this.widgetName);
    },

    undelegate: function (eventName, selector, listener) {
        this.$element.off(eventName + '.' + this.widgetName, selector, listener);
    },

    trigger: function (type, data) {
        this.$element.trigger((type + '.' + this.widgetName).toLowerCase(), data);
    },

    destroy: function () {
        this.undelegateEvents();
        this.$element.removeData(this.widgetName);
    }
});<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var debug = require('debug')('wpcom:reblog');

/**
 * Reblog methods
 *
 * @param {String} pid post id
 * @param {String} sid site id
 * @param {WPCOM} wpcom
 * @api public
 */

function Reblog(pid, sid, wpcom) {
  if (!sid) {
    throw new Error('`site id` is not correctly defined');
  }

  if (!pid) {
    throw new Error('`post id` is not correctly defined');
  }

  if (!(this instanceof Reblog)) {
    return new Reblog(pid, sid, wpcom);
  }

  this.wpcom = wpcom;
  this._pid = pid;
  this._sid = sid;
}

/**
 * Get your reblog status for a Post
 *
 * @param {Object} [query]
 * @param {Function} fn
 * @api public
 */

Reblog.prototype.mine =
Reblog.prototype.state = function (query, fn) {
  var path = '/sites/' + this._sid + '/posts/' + this._pid + '/reblogs/mine';
  return this.wpcom.req.get(path, query, fn);
};

/**
 * Reblog a post
 *
 * @param {Object} [query]
 * @param {Object} body
 * @param {Function} fn
 * @api public
 */

Reblog.prototype.add = function (query, body, fn) {
  if ('function' === typeof body) {
    fn = body;
    body = query;
    query = {};
  }

  if (body && !body.destination_site_id) {
    return fn(new Error('destination_site_id is not defined'));
  }

  var path = '/sites/' + this._sid + '/posts/' + this._pid + '/reblogs/new';
  return this.wpcom.req.put(path, query, body, fn);
};

/**
 * Reblog a post to
 * It's almost an alias of Reblogs#add
 *
 * @param {Number|String} dest site id destination
 * @param {String} [note]
 * @param {Function} fn
 * @api public
 */

Reblog.prototype.to = function (dest, note, fn) {
  if ('function' === typeof note) {
    fn = note;
    note = null;
  }

  this.add({ note: note, destination_site_id: dest }, fn);
};

/**
 * Expose `Reblog` module
 */

module.exports = Reblog;<commit_msg>Prepare `reblog` for promises<commit_after>
/**
 * Module dependencies.
 */

var debug = require('debug')('wpcom:reblog');

/**
 * Reblog methods
 *
 * @param {String} pid post id
 * @param {String} sid site id
 * @param {WPCOM} wpcom
 * @api public
 */

function Reblog(pid, sid, wpcom) {
  if (!sid) {
    throw new Error('`site id` is not correctly defined');
  }

  if (!pid) {
    throw new Error('`post id` is not correctly defined');
  }

  if (!(this instanceof Reblog)) {
    return new Reblog(pid, sid, wpcom);
  }

  this.wpcom = wpcom;
  this._pid = pid;
  this._sid = sid;
}

/**
 * Get your reblog status for a Post
 *
 * @param {Object} [query]
 * @param {Function} fn
 * @api public
 */

Reblog.prototype.mine =
Reblog.prototype.state = function (query, fn) {
  var path = '/sites/' + this._sid + '/posts/' + this._pid + '/reblogs/mine';
  return this.wpcom.req.get(path, query, fn);
};

/**
 * Reblog a post
 *
 * @param {Object} [query]
 * @param {Object} body
 * @param {Function} fn
 * @api public
 */

Reblog.prototype.add = function (query, body, fn) {
  if ('function' === typeof body) {
    fn = body;
    body = query;
    query = {};
  }

  if (body && !body.destination_site_id) {
    return fn(new Error('destination_site_id is not defined'));
  }

  var path = '/sites/' + this._sid + '/posts/' + this._pid + '/reblogs/new';
  return this.wpcom.req.put(path, query, body, fn);
};

/**
 * Reblog a post to
 * It's almost an alias of Reblogs#add
 *
 * @param {Number|String} dest site id destination
 * @param {String} [note]
 * @param {Function} fn
 * @api public
 */

Reblog.prototype.to = function (dest, note, fn) {
  if ( undefined === fn ) {
    if ( undefined === note ) {
      note = null;
    } else if ('function' === typeof note) {
      fn = note;
      note = null;
    }
  }

  return this.add({ note: note, destination_site_id: dest }, fn);
};

/**
 * Expose `Reblog` module
 */

module.exports = Reblog;
<|endoftext|>


<commit_before>require('underscore');

var sys = require('sys');
var redis = require('redis-node');
var EventEmitter = require('events').EventEmitter;

var Redobl = exports.Redobl = function () {
};

Redobl.get_serializer = function (descr) {
  if (descr.of) {
    var of = descr.of;
    //sys.log("Choosing 'of' serializer for " + of.className);
    return {
      serialize: function(x) {
        //sys.log("serialize: " + x + " of " + of.className + " => " + x.id);
        return x.id;
      },

      deserialize: function(x, cb) {
        //sys.log("deserialize: " + x + " of " + of.className);
        return of.find(x, cb);
      },

      deserialize_all: function(arr, cb) {
        //sys.log("deserialize_all: " + x + " of " + of.className);
        return of.find(arr, function(err, objs) {
          var result = new Array(arr.length);
          if (! err) {
            for (var i = 0; i < arr.length; ++i) {
              result[i] = objs[arr[i]];
            }
          }
          cb.call(this, err, result);
        });
      }
    }
  } else if (descr.json) {
    //sys.log("Choosing JSON serializer");
    return {
      serialize: function(x) {
        return JSON.stringify(x);
      },

      deserialize: function(x, cb) {
        cb.call(this, null, JSON.parse(x));
      },

      deserialize_all: function(arr, cb) {
        for (var i = 0; i < arr.length; ++i) {
          arr[i] = JSON.parse(arr[i]);
        }
        cb.call(this, null, arr);
      }
    }
  } else {
    return {
      serialize: function(x) { return x; },
      deserialize: function(x, cb) { cb.call(this, null, x); },
      deserialize_all: function(arr, cb) { cb.call(this, null, arr); }
    }
  }
}

// Class methods
Redobl.define = function(name, config, proto) {
  // The name argument is optional.
  if (typeof name !== 'string') {
    proto = config; config = name;
    name = config.name;
  }

  //sys.log("Redobl.define " + name + " config = " + sys.inspect(config));
  var type_f = Redobl._getClass((config && config.type) || 'object');

  var f = function(setup) {
    var args = this._rsetup(setup || {});
    type_f.call(this, setup);
    if (this.init) { this.init.apply(this, _.rest(arguments)); }
  };

  // Inherit instance methods from
  f.prototype = new type_f();
  _.extend(f.prototype, {
    constructor: f,
    className: name
  });
  // and extend with the proto options.
  _.extend(f.prototype, proto);

  // Inherit class methods from type_f (i.e. the specific subclass of Redobl)
  _.extend(f, type_f.classMethods);

  // Now, call the 'constructor' for this new type.
  f._classConstructor(name, config || {});

  return f;
}

Redobl.prototype = {
  _rsetup: function(setup) {
    this._id = setup.id; delete setup.id;

    this.emitter = new EventEmitter();

    // Attach properties to each instance.
    this.constructor.instantiators(this);
    this.constructor.attachProperties(this);

    // Associate the class serializer with this instance
    Object.defineProperty(this, "serializer", {
      writable: false,
      value: this.constructor.serializer
    });

    // If we've instantiated with ID then run after_id.
    if (this.id) { this.emitter.emit('after_id'); }
  },

  get id() {
    return this._id;
  },

  get client() {
    return this.constructor.client;
  },

  get pubsubClient() {
    var client = this.client;
    if (! client.pubsub) {
      // Duplicate the client for pubsub access.
      client.pubsub = redis.createClient(client.port, client.host);
      client.pubsub.select(client.currDB);
    }
    return client.pubsub;
  },

  get redis_key() {
    if (! this._redis_key) {
      this._redis_key = this.redisKeyFor("id");
    }
    return this._redis_key;
  },

  redisKeyFor: function(attr) {
    if (! this.id || this._generating) {
      sys.log("ERROR: Generating key for ID-less object");
    }
    return this.constructor.redisKeyFor(attr, this.id);
  },

  // Provide a standard means for handling errors and binding to this.
  redisHandler: function(func, callback) {
    var self = this;
    return function(err, status) {
      self.error = err;
      self.log(func, [], err ? (" ERROR: " + err) : (" => " + status));

      if (callback) { callback.call(self, err, status); }
    }
  },

  // Encapsulate a call that must have a new ID set to execute.
  generatingID: function(callback) {
    if (this._generating) {
      // Waiting for generation to finish...
      var that = this;
      //this.log("wait for ID");
      this.emitter.on('after_id', function () { callback.call(that); });
    } else if (this._id) {
      callback.call(this, null);
    } else {
      //this.log("generate ID");
      this._generating = true;
      this.client.incr(this.className,
                       this.redisHandler(this.className + '.incr', function(err, next_id) {
        if (! err) {
          delete this._generating;
          this._id = next_id;
          this.emitter.emit('after_id');
        }
        callback.call(this, err);
      }));
    }
  },

  transaction: function(callback) {
    this.generatingID(function () {
      this.client.transaction(this.redisHandler(this.className + ".transaction", callback));
    });
  },

  load: function(callback) {
    callback.call(this, null, this);
  },

  save: function(callback) {
    this.generatingID(function(err) {
      this.error = err;
      callback.call(this, err, this);
    });
  },

  serialize: function(x) {
    return this.serializer.serialize(x);
  },

  deserialize: function(x, cb) {
    this.serializer.deserialize.call(this, x, cb);
  },

  deserialize_all: function(arr, cb) {
    this.serializer.deserialize_all.call(this, arr, cb);
  },

  log: function(op, args, extra) {
    if (Redobl.logging) {
      args = Array.prototype.slice.call(args);
      if (typeof args[args.length-1] === 'function') {
        // Remove callback if exists.
        args.pop();
      }
      sys.log("Redobl: " +
              (this.id ? this.redis_key + "." : "") + op +
              (args.length > 0 ? sys.inspect(args) : "") +
              (extra ? extra : ""));
    }
  }
};

Redobl.logging = false;

Redobl.classes = {};
Redobl.defaults = {};

_.extend(Redobl, Redobl.classMethods = {
  _classConstructor: function(name, comps) {
    //sys.log(name + "._classConstructor");
    this.className = name;
    this.client = comps.client || this.defaultClient();

    this._instantiators = [];
    this.validations = [];
    this.associations = {};
    this.properties = {};

    // Should this class serialize its arguments?
    var serialize = (_.isUndefined(comps.serialize) ? true : comps.serialize);
    this.serializer = Redobl.get_serializer({json: serialize});

    //sys.log(name + "._setup = " + this._setup);
    if (this._setup) { this._setup(comps); }

    //sys.log(name + " = " + sys.inspect(this));
    if (comps.schema) { this.defineSchema(comps.schema); }
  },

  create: function(setup, callback) {
    new this(setup).save(callback);
  },

  addClass: function(name, klass) {
    Redobl.classes[name] = klass;
  },

  _getClass: function(type) {
    return Redobl.classes[type] || Redobl.Object;
  },

  defaultClient: function() {
    if (! Redobl.defaults.client) {
      sys.log("WARNING: Redobl.defaults.client undefined. Creating system default.");
      Redobl.defaults.client = redis.createClient();
    }
    return Redobl.defaults.client;
  },

  defineSchema: function(schema) {
    for (var k in schema) {
      var v = schema[k];
      //sys.log(this.className + " schema " + k);
      if (_.isString(v)) {
        // Define a value attribute
        this.defineAttributeOrAssociation(k, {type: v});
      } else {
        this.defineAttributeOrAssociation(k, v);
      }
    }
  },

  defineAttributeOrAssociation: function(name, options) {
    //sys.log(this.className + "." + name + " => " + sys.inspect(options));
    if (_.isString(options.type)) {
      options.type = options.type.toLowerCase();
      if (Redobl.classes[options.type]) {
        this.defineAssociation(name, options);
      } else {
        this.defineAttribute(name, options);
      }
    } else if (_.isFunction(options.type)) {
      this.defineAssociation(name, options);
    } else {
      this.defineAttribute(name, options);
    }
  },

  defineAssociation: function(aname, options) {
    this.associations[aname] = _.clone(options);
    var assoc_type = Redobl.define(this.className + "::" + aname, options);
    this.addInstantiator(function () {
      // We can't instantiate these association objects until after the object's
      // id is defined.
      var that = this;
      // TODO: Change to once when we upgrade to node 0.3.0
      this.emitter.on('after_id', function () {
        var assoc = new assoc_type({id: that.id});
        Object.defineProperty(that, aname, {
          value: assoc,
          writable: false,
          enumerable: true
        });
        return true;
      });
    });
  },

  defineProperty: function(name, options) {
    this.properties[name] = options;
  },

  addInstantiator: function(f) {
    this._instantiators.push(f);
  },

  instantiators: function(instance) {
    this._instantiators.forEach(function (f) {
      f.call(instance);
    });
  },

  attachProperties: function(object) {
    //sys.log(this.className + ".attachProperties(" + sys.inspect(object) + ")");
    for (var prop in this.properties) {
      //sys.log("Attach property " + prop + " to " + object);
      Object.defineProperty(object, prop, this.properties[prop]);
    }
  },

  redisKeyFor: function(attr, id) {
    return this.className + ":" + attr + ":" + id;
  },

  find: function(id, callback) {
    if (_.isArray(id)) {
      this.find_by_ids(id, callback);
    } else {
      this.find_by_id(id, callback);
    }
  },

  find_by_id: function(id, callback) {
    var inst = new this({id: id});
    inst.load(callback);
  },

  find_by_ids: function(ids, callback) {
    var self = this;
    var insts = {};
    _.each(ids, function(id) { insts[id] = new self({id: id}); });
    callback.call(this, null, insts);
  },

  // This is used by subclasses to define the REDIS client functions that they handle.
  define_client_func: function(fname, opts) {
    var klass = this;
    //sys.log("Define List.prototype."+fname+" opts=" + sys.inspect(opts));

    this.prototype[fname] = function() {
      var args = Array.prototype.slice.call(arguments);

      var callback = args.pop();
      // A non-existent callback should be ignored...
      if (!_.isUndefined(callback) && typeof callback !== 'function') {
        args.push(callback);
        callback = undefined;
      }

      // Make sure we have an ID...
      this.generatingID(function(err) {
        if (err) {
          if (callback) callback.call(this, err);
          return;
        }

        if (opts.serialize) {
          // Either serialize those arguments indicated or all of them...
          var indices = _.isArray(opts.serialize) ? opts.serialize : _.range(args.length);
          for (var i in indices) {
            args[i] = this.serialize(args[i]);
          }
        }

        if (callback) {
          // Process outputs if necessary. Put callback at end of args.
          args.push(this.redisHandler(opts.rfunc || fname, function(err, retval) {
            // Handle argument deserialization.
            //sys.log("list: return from " + (opts.rfunc || fname) + " err = " + err);
            if (! err && opts.deserialize) {
              if (_.isArray(retval)) {
                this.deserialize_all(retval, callback);
              } else {
                //sys.log("list: deserialize " + sys.inspect(retval));
                this.deserialize(retval, callback);
              }
            } else {
              callback.call(this, err, retval);
            }
          }));
        }

        // Now call the function...
        this.log(opts.rfunc || fname, args);
        args.unshift(this.redis_key);
        //sys.log("list: call " + (opts.rfunc || fname) + " with " + sys.inspect(args));
        var client = (opts.pubsub ? this.pubsubClient : this.client);
        client[opts.rfunc || fname].apply(client, args);
      });
    };
  }
});

// Define the mediated functions for this class.
(function(options) {
  for (var name in options) {
    var opts = options[name];
    Redobl.define_client_func(name, options[name]);
  }
})({
  exists: {},
  expire: {},
  expireat: {},
  ttl: {},
  publish: {pubsub: true, serialize: true},
  subscribe: {pubsub: true, deserialize: true},
  unsubscribe: {pubsub: true},
  psubscribe: {pubsub: true, deserialize: true},
  punsubscribe: {pubsub: true}
});

// Load subclasses.
Redobl.Set = require('./set').Set;
Redobl.ZSet = require('./zset').ZSet;
Redobl.SortedSet = require('./zset').SortedSet;
Redobl.Hash = require('./hash').Hash;
Redobl.List = require('./list').List;
Redobl.Object = require('./object').RedObject;
<commit_msg>Inherit Redobl objects from EnevtEmitter<commit_after>require('underscore');

var sys = require('sys');
var redis = require('redis-node');
var EventEmitter = require('events').EventEmitter;

var Redobl = exports.Redobl = function () {
};

Redobl.get_serializer = function (descr) {
  if (descr.of) {
    var of = descr.of;
    //sys.log("Choosing 'of' serializer for " + of.className);
    return {
      serialize: function(x) {
        //sys.log("serialize: " + x + " of " + of.className + " => " + x.id);
        return x.id;
      },

      deserialize: function(x, cb) {
        //sys.log("deserialize: " + x + " of " + of.className);
        return of.find(x, cb);
      },

      deserialize_all: function(arr, cb) {
        //sys.log("deserialize_all: " + x + " of " + of.className);
        return of.find(arr, function(err, objs) {
          var result = new Array(arr.length);
          if (! err) {
            for (var i = 0; i < arr.length; ++i) {
              result[i] = objs[arr[i]];
            }
          }
          cb.call(this, err, result);
        });
      }
    }
  } else if (descr.json) {
    //sys.log("Choosing JSON serializer");
    return {
      serialize: function(x) {
        return JSON.stringify(x);
      },

      deserialize: function(x, cb) {
        cb.call(this, null, JSON.parse(x));
      },

      deserialize_all: function(arr, cb) {
        for (var i = 0; i < arr.length; ++i) {
          arr[i] = JSON.parse(arr[i]);
        }
        cb.call(this, null, arr);
      }
    }
  } else {
    return {
      serialize: function(x) { return x; },
      deserialize: function(x, cb) { cb.call(this, null, x); },
      deserialize_all: function(arr, cb) { cb.call(this, null, arr); }
    }
  }
}

// Class methods
Redobl.define = function(name, config, proto) {
  // The name argument is optional.
  if (typeof name !== 'string') {
    proto = config; config = name;
    name = config.name;
  }

  //sys.log("Redobl.define " + name + " config = " + sys.inspect(config));
  var type_f = Redobl._getClass((config && config.type) || 'object');

  var f = function(setup) {
    var args = this._rsetup(setup || {});
    EventEmitter.call(this);
    type_f.call(this, setup);
    if (this.init) { this.init.apply(this, _.rest(arguments)); }
  };

  // Inherit instance methods from
  f.prototype = _.extend(new type_f(), EventEmitter.prototype);
  _.extend(f.prototype, {
    constructor: f,
    className: name
  });
  // and extend with the proto options.
  _.extend(f.prototype, proto);

  // Inherit class methods from type_f (i.e. the specific subclass of Redobl)
  _.extend(f, type_f.classMethods);

  // Now, call the 'constructor' for this new type.
  f._classConstructor(name, config || {});

  return f;
}

Redobl.prototype = {
  _rsetup: function(setup) {
    this._id = setup.id; delete setup.id;

    // Attach properties to each instance.
    this.constructor.instantiators(this);
    this.constructor.attachProperties(this);

    // Associate the class serializer with this instance
    Object.defineProperty(this, "serializer", {
      writable: false,
      value: this.constructor.serializer
    });

    // If we've instantiated with ID then run after_id.
    if (this.id) { this.emit('after_id'); }
  },

  get id() {
    return this._id;
  },

  get client() {
    return this.constructor.client;
  },

  get pubsubClient() {
    var that = this;
    var client = this.client;
    if (! client.pubsub) {
      // Duplicate the client for pubsub access.
      client.pubsub = redis.createClient(client.port, client.host);
      client.pubsub.select(client.currDB);
    }
    return client.pubsub;
  },

  get redis_key() {
    if (! this._redis_key) {
      this._redis_key = this.redisKeyFor("id");
    }
    return this._redis_key;
  },

  redisKeyFor: function(attr) {
    if (! this.id || this._generating) {
      sys.log("ERROR: Generating key for ID-less object");
    }
    return this.constructor.redisKeyFor(attr, this.id);
  },

  // Provide a standard means for handling errors and binding to this.
  redisHandler: function(func, callback) {
    var self = this;
    return function(err, status) {
      self.error = err;
      self.log(func, [], err ? (" ERROR: " + err) : (" => " + status));

      if (callback) { callback.call(self, err, status); }
    }
  },

  // Encapsulate a call that must have a new ID set to execute.
  generatingID: function(callback) {
    if (this._generating) {
      // Waiting for generation to finish...
      var that = this;
      //this.log("wait for ID");
      this.on('after_id', function () { callback.call(that); });
    } else if (this._id) {
      callback.call(this, null);
    } else {
      //this.log("generate ID");
      this._generating = true;
      this.client.incr(this.className,
                       this.redisHandler(this.className + '.incr', function(err, next_id) {
        if (! err) {
          delete this._generating;
          this._id = next_id;
          this.emit('after_id');
        }
        callback.call(this, err);
      }));
    }
  },

  transaction: function(callback) {
    this.generatingID(function () {
      this.client.transaction(this.redisHandler(this.className + ".transaction", callback));
    });
  },

  load: function(callback) {
    callback.call(this, null, this);
  },

  save: function(callback) {
    this.generatingID(function(err) {
      this.error = err;
      callback.call(this, err, this);
    });
  },

  serialize: function(x) {
    return this.serializer.serialize(x);
  },

  deserialize: function(x, cb) {
    this.serializer.deserialize.call(this, x, cb);
  },

  deserialize_all: function(arr, cb) {
    this.serializer.deserialize_all.call(this, arr, cb);
  },

  log: function(op, args, extra) {
    if (Redobl.logging) {
      args = Array.prototype.slice.call(args);
      if (typeof args[args.length-1] === 'function') {
        // Remove callback if exists.
        args.pop();
      }
      sys.log("Redobl: " +
              (this.id ? this.redis_key + "." : "") + op +
              (args.length > 0 ? sys.inspect(args) : "") +
              (extra ? extra : ""));
    }
  }
};

Redobl.logging = false;

Redobl.classes = {};
Redobl.defaults = {};

_.extend(Redobl, Redobl.classMethods = {
  _classConstructor: function(name, comps) {
    //sys.log(name + "._classConstructor");
    this.className = name;
    this.client = comps.client || this.defaultClient();

    this._instantiators = [];
    this.validations = [];
    this.associations = {};
    this.properties = {};

    // Should this class serialize its arguments?
    var serialize = (_.isUndefined(comps.serialize) ? true : comps.serialize);
    this.serializer = Redobl.get_serializer({json: serialize});

    //sys.log(name + "._setup = " + this._setup);
    if (this._setup) { this._setup(comps); }

    //sys.log(name + " = " + sys.inspect(this));
    if (comps.schema) { this.defineSchema(comps.schema); }
  },

  create: function(setup, callback) {
    new this(setup).save(callback);
  },

  addClass: function(name, klass) {
    Redobl.classes[name] = klass;
  },

  _getClass: function(type) {
    return Redobl.classes[type] || Redobl.Object;
  },

  defaultClient: function() {
    if (! Redobl.defaults.client) {
      sys.log("WARNING: Redobl.defaults.client undefined. Creating system default.");
      Redobl.defaults.client = redis.createClient();
    }
    return Redobl.defaults.client;
  },

  defineSchema: function(schema) {
    for (var k in schema) {
      var v = schema[k];
      //sys.log(this.className + " schema " + k);
      if (_.isString(v)) {
        // Define a value attribute
        this.defineAttributeOrAssociation(k, {type: v});
      } else {
        this.defineAttributeOrAssociation(k, v);
      }
    }
  },

  defineAttributeOrAssociation: function(name, options) {
    //sys.log(this.className + "." + name + " => " + sys.inspect(options));
    if (_.isString(options.type)) {
      options.type = options.type.toLowerCase();
      if (Redobl.classes[options.type]) {
        this.defineAssociation(name, options);
      } else {
        this.defineAttribute(name, options);
      }
    } else if (_.isFunction(options.type)) {
      this.defineAssociation(name, options);
    } else {
      this.defineAttribute(name, options);
    }
  },

  defineAssociation: function(aname, options) {
    this.associations[aname] = _.clone(options);
    var assoc_type = Redobl.define(this.className + "::" + aname, options);
    this.addInstantiator(function () {
      // We can't instantiate these association objects until after the object's
      // id is defined.
      var that = this;
      // TODO: Change to once when we upgrade to node 0.3.0
      this.on('after_id', function () {
        var assoc = new assoc_type({id: that.id});
        Object.defineProperty(that, aname, {
          value: assoc,
          writable: false,
          enumerable: true
        });
        return true;
      });
    });
  },

  defineProperty: function(name, options) {
    this.properties[name] = options;
  },

  addInstantiator: function(f) {
    this._instantiators.push(f);
  },

  instantiators: function(instance) {
    this._instantiators.forEach(function (f) {
      f.call(instance);
    });
  },

  attachProperties: function(object) {
    //sys.log(this.className + ".attachProperties(" + sys.inspect(object) + ")");
    for (var prop in this.properties) {
      //sys.log("Attach property " + prop + " to " + object);
      Object.defineProperty(object, prop, this.properties[prop]);
    }
  },

  redisKeyFor: function(attr, id) {
    return this.className + ":" + attr + ":" + id;
  },

  find: function(id, callback) {
    if (_.isArray(id)) {
      this.find_by_ids(id, callback);
    } else {
      this.find_by_id(id, callback);
    }
  },

  find_by_id: function(id, callback) {
    var inst = new this({id: id});
    inst.load(callback);
  },

  find_by_ids: function(ids, callback) {
    var self = this;
    var insts = {};
    _.each(ids, function(id) { insts[id] = new self({id: id}); });
    callback.call(this, null, insts);
  },

  // This is used by subclasses to define the REDIS client functions that they handle.
  define_client_func: function(fname, opts) {
    var klass = this;
    //sys.log("Define List.prototype."+fname+" opts=" + sys.inspect(opts));

    this.prototype[fname] = function() {
      var args = Array.prototype.slice.call(arguments);

      var callback = args.pop();
      // A non-existent callback should be ignored...
      if (!_.isUndefined(callback) && typeof callback !== 'function') {
        args.push(callback);
        callback = undefined;
      }

      // Make sure we have an ID...
      this.generatingID(function(err) {
        if (err) {
          if (callback) callback.call(this, err);
          return;
        }

        if (opts.serialize) {
          // Either serialize those arguments indicated or all of them...
          var indices = _.isArray(opts.serialize) ? opts.serialize : _.range(args.length);
          for (var i in indices) {
            args[i] = this.serialize(args[i]);
          }
        }

        if (callback) {
          // Process outputs if necessary. Put callback at end of args.
          args.push(this.redisHandler(opts.rfunc || fname, function(err, retval) {
            // Handle argument deserialization.
            //sys.log("list: return from " + (opts.rfunc || fname) + " err = " + err);
            if (! err && opts.deserialize) {
              if (_.isArray(retval)) {
                this.deserialize_all(retval, callback);
              } else {
                //sys.log("list: deserialize " + sys.inspect(retval));
                this.deserialize(retval, callback);
              }
            } else {
              callback.call(this, err, retval);
            }
          }));
        }

        // Now call the function...
        this.log(opts.rfunc || fname, args);
        args.unshift(this.redis_key);
        //sys.log("list: call " + (opts.rfunc || fname) + " with " + sys.inspect(args));
        var client = (opts.pubsub ? this.pubsubClient : this.client);
        client[opts.rfunc || fname].apply(client, args);
      });
    };
  }
});

// Define the mediated functions for this class.
(function(options) {
  for (var name in options) {
    var opts = options[name];
    Redobl.define_client_func(name, options[name]);
  }
})({
  exists: {},
  expire: {},
  expireat: {},
  ttl: {},
  publish: {pubsub: true, serialize: true},
  subscribe: {pubsub: true, deserialize: true},
  unsubscribe: {pubsub: true},
  psubscribe: {pubsub: true, deserialize: true},
  punsubscribe: {pubsub: true}
});

// Load subclasses.
Redobl.Set = require('./set').Set;
Redobl.ZSet = require('./zset').ZSet;
Redobl.SortedSet = require('./zset').SortedSet;
Redobl.Hash = require('./hash').Hash;
Redobl.List = require('./list').List;
Redobl.Object = require('./object').RedObject;
<|endoftext|>


<commit_before>module.exports = function(remoteUrl) {

  var request = require('request');

  function remoteCommand(method, localDumpName, callback) {

    if (typeof localDumpName === 'function') {
      callback = localDumpName;
      localDumpName = '';
    }
    // TODO: shouldn’t we have some kind of authentication here?
    request({
      url: remoteUrl + 'syncdb/syncdb.php',
      method: 'POST',
      json: true,
      body: {
        method: method,
        localDumpName: localDumpName // empty string for pull!
      }
    }, function(error, response, body) {
      callback(error, body);
    });
  }

  function remotePush(dumpName, callback) {
    remoteCommand('push', dumpName, callback);
  }

  function remotePull(callback) {
    remoteCommand('pull', function (error, body) {
      if (error) {
        callback(error);
      } else {
        callback(error, 'syncdb/' + body.remoteDumpName);
      }
    });
  }

  return {
    push: remotePush,
    pull: remotePull
  };
}
<commit_msg>re-add erroneously removed slash in remote path<commit_after>module.exports = function(remoteUrl) {

  var request = require('request');

  function remoteCommand(method, localDumpName, callback) {

    if (typeof localDumpName === 'function') {
      callback = localDumpName;
      localDumpName = '';
    }
    // TODO: shouldn’t we have some kind of authentication here?
    request({
      url: remoteUrl + '/syncdb/syncdb.php',
      method: 'POST',
      json: true,
      body: {
        method: method,
        localDumpName: localDumpName // empty string for pull!
      }
    }, function(error, response, body) {
      callback(error, body);
    });
  }

  function remotePush(dumpName, callback) {
    remoteCommand('push', dumpName, callback);
  }

  function remotePull(callback) {
    remoteCommand('pull', function (error, body) {
      if (error) {
        callback(error);
      } else {
        callback(error, 'syncdb/' + body.remoteDumpName);
      }
    });
  }

  return {
    push: remotePush,
    pull: remotePull
  };
}
<|endoftext|>