<commit_before>var grpc = require('grpc');
var schema = require('protocol-buffers-schema');
var promise = require('bluebird');
var fs = require('fs')

var GrpcClient = function(config){
  if (typeof String.prototype.endsWith !== 'function') {
    String.prototype.endsWith = function(suffix) {
      return this.indexOf(suffix, this.length - suffix.length) !== -1;
    };
  }

  this.host = config.host || "";
  this.protodir = config.dir || "";
  this.header = config.header || {};
  this.currentCall = null;
  this.log = config.log || console;
  return this;
}

// parse proto file and create action;
GrpcClient.prototype.proto = function(protofile){
  var protoFilePath = "";
  if(this.protodir){
    if(this.protodir.endsWith("/")){
      protoFilePath = this.protodir + (protofile.endsWith(".proto")? protofile: protofile + ".proto");
    }else{
      protoFilePath = this.protodir + "/" + (protofile.endsWith(".proto")? protofile: protofile + ".proto");
    }
  }
  var sch = schema(fs.readFileSync(protoFilePath));
  var grpcLoadStr = "grpc.load(protoFilePath)." + sch.package;
  var ProtoClass = eval(grpcLoadStr);//grpc.load(protoFilePath)[sch.package];
  for (var i = 0; i < sch.service.services.length; i++) {
    (function(serviceFuncName){
      GrpcClient.prototype[serviceFuncName] = GrpcClient.prototype[firstToLowerCase(serviceFuncName)] = promise.promisify(function(data,cb){
        var startTime = new Date();
        var client;
        if(grpc.Credentials){
          client = new ProtoClass[sch.service.name](this.host,grpc.Credentials.createInsecure());
        }else{
          client = new ProtoClass[sch.service.name](this.host);
        }

        var injectionLogCb = function(err,result){
          var endTime = new Date();
          var executeTime = endTime-startTime;
          var logInfo = {type:"grpc",path:protoFilePath,headers:this.header,params:data,executeTime:executeTime}
          this.log.info(logInfo);
          return cb(err,result);
        }.bind(this);
        client[firstToLowerCase(serviceFuncName)](data,injectionLogCb,this.header);
      });
    }).call(this,sch.service.services[i])
  }
  return this;
}

GrpcClient.prototype.call = function(name){
  this.currentCall = name;
  return this;
}

GrpcClient.prototype.send = function(data){
  return this[this.currentCall](data);
}

GrpcClient.prototype.host = function(host){
  this.host = host;
  return this;
}

GrpcClient.prototype.dir = function(dir){
  this.protodir = dir;
  return this;
}

GrpcClient.prototype.header = function(header){
  this.header = header;
  return this;
}

function firstToLowerCase(str){
  return str.replace(/\b\w+\b/g,function(tmpStr){
    return tmpStr.substring(0,1).toLowerCase()+tmpStr.substring(1);
  })
}
module.exports = GrpcClient;<commit_msg>增加credentials的cretaInsecure是否存在判断<commit_after>var grpc = require('grpc');
var schema = require('protocol-buffers-schema');
var promise = require('bluebird');
var fs = require('fs')

var GrpcClient = function(config){
  if (typeof String.prototype.endsWith !== 'function') {
    String.prototype.endsWith = function(suffix) {
      return this.indexOf(suffix, this.length - suffix.length) !== -1;
    };
  }

  this.host = config.host || "";
  this.protodir = config.dir || "";
  this.header = config.header || {};
  this.currentCall = null;
  this.log = config.log || console;
  return this;
}

// parse proto file and create action;
GrpcClient.prototype.proto = function(protofile){
  var protoFilePath = "";
  if(this.protodir){
    if(this.protodir.endsWith("/")){
      protoFilePath = this.protodir + (protofile.endsWith(".proto")? protofile: protofile + ".proto");
    }else{
      protoFilePath = this.protodir + "/" + (protofile.endsWith(".proto")? protofile: protofile + ".proto");
    }
  }
  var sch = schema(fs.readFileSync(protoFilePath));
  var grpcLoadStr = "grpc.load(protoFilePath)." + sch.package;
  var ProtoClass = eval(grpcLoadStr);//grpc.load(protoFilePath)[sch.package];
  for (var i = 0; i < sch.service.services.length; i++) {
    (function(serviceFuncName){
      GrpcClient.prototype[serviceFuncName] = GrpcClient.prototype[firstToLowerCase(serviceFuncName)] = promise.promisify(function(data,cb){
        var startTime = new Date();
        var client;
        if(grpc.Credentials !== undefined && grpc.Credentials.createInsecure !== undefined){
          client = new ProtoClass[sch.service.name](this.host,grpc.Credentials.createInsecure());
        }else{
          client = new ProtoClass[sch.service.name](this.host);
        }

        var injectionLogCb = function(err,result){
          var endTime = new Date();
          var executeTime = endTime-startTime;
          var logInfo = {type:"grpc",path:protoFilePath,headers:this.header,params:data,executeTime:executeTime}
          this.log.info(logInfo);
          return cb(err,result);
        }.bind(this);
        client[firstToLowerCase(serviceFuncName)](data,injectionLogCb,this.header);
      });
    }).call(this,sch.service.services[i])
  }
  return this;
}

GrpcClient.prototype.call = function(name){
  this.currentCall = name;
  return this;
}

GrpcClient.prototype.send = function(data){
  return this[this.currentCall](data);
}

GrpcClient.prototype.host = function(host){
  this.host = host;
  return this;
}

GrpcClient.prototype.dir = function(dir){
  this.protodir = dir;
  return this;
}

GrpcClient.prototype.header = function(header){
  this.header = header;
  return this;
}

function firstToLowerCase(str){
  return str.replace(/\b\w+\b/g,function(tmpStr){
    return tmpStr.substring(0,1).toLowerCase()+tmpStr.substring(1);
  })
}
module.exports = GrpcClient;<|endoftext|>


<commit_before>"use strict"

var runner = function(json){
  var message = document.getElementById("enterbar").value
  if (message !== ""){
    var finalmessage = ""
    if (message.startsWith("#")){
      var ret = {};
      for(var key in json){
        ret[json[key]] = key
      }
      for (var n = 1; n < message.length; n++){
        var jsonindex = message[n]
        if (jsonindex in json) {
          finalmessage += ret[jsonindex]
        } else {
          finalmessage += jsonindex
        }
      }
    } else {
      finalmessage += "#"
      for (var n = 0; n < message.length; n++){
        var jsonindex = message[n]
        if (jsonindex in json) {
          finalmessage += json[jsonindex]
        } else {
          finalmessage += jsonindex
        }
      }
    }
    document.getElementById("encodedmessage").innerHTML = finalmessage
  } else {
    document.getElementById("encodedmessage").innerHTML = 'Message'
  }
}

var copytoclip = function (){
  var tobecopied = document.getElementById("encodedmessage").innerHTML
  var copyspot = document.getElementById("copyspot")
  copyspot.value = tobecopied
  copyspot.select()
  document.execCommand('copy')
}
<commit_msg>All done... Everything looks very nice<commit_after>"use strict"

var runner = function(json){
  var message = document.getElementById("enterbar").value
  if (message !== ""){
    var finalmessage = ""
    if (message.startsWith("#")){
      var ret = {};
      for(var key in json){
        ret[json[key]] = key
      }
      for (var n = 1; n < message.length; n++){
        var jsonindex = message[n]
        if (jsonindex in json) {
          finalmessage += ret[jsonindex]
        } else {
          finalmessage += jsonindex
        }
      }
    } else {
      finalmessage += "#"
      for (var n = 0; n < message.length; n++){
        var jsonindex = message[n]
        if (jsonindex in json) {
          finalmessage += json[jsonindex]
        } else {
          finalmessage += jsonindex
        }
      }
    }
    document.getElementById("encodedmessage").innerHTML = finalmessage
  } else {
    document.getElementById("encodedmessage").innerHTML = 'Encrypted'
  }
}

var copytoclip = function (){
  var tobecopied = document.getElementById("encodedmessage").innerHTML
  var copyspot = document.getElementById("copyspot")
  copyspot.value = tobecopied
  copyspot.select()
  document.execCommand('copy')
}
<|endoftext|>


<commit_before>
const word = encodeURI(Array.prototype.slice.call(process.argv, 2).join(' '));
const ora = require('ora');

var dictList = ["bing", "youdao"]; /// 对应lib文件夹的文件名

(async () => {
	const spinner = ora().start();
	for (let i = 0, len = dictList.length; i < len; i++) {
		try {
			const result = await require('./lib/' + dictList[i])(word);
			spinner.succeed(result);
			break;
		} catch (err) {
			if (i + 1 === len) {
				spinner.fail('查询失败');
			}
		}
	}
})();
<commit_msg>添加输出版本号<commit_after>
const word = encodeURI(Array.prototype.slice.call(process.argv, 2).join(' '));
const ora = require('ora');
const dictList = ["bing", "youdao"]; /// 对应lib文件夹的文件名

if (['-v', '--version'].indexOf(word.toLowerCase()) > -1) {
	console.log(require('./package.json').version);
	return;
}

(async () => {
	const spinner = ora().start();
	for (let i = 0, len = dictList.length; i < len; i++) {
		try {
			const result = await require('./lib/' + dictList[i])(word);
			spinner.succeed(result);
			break;
		} catch (err) {
			if (i + 1 === len) {
				spinner.fail('查询失败');
			}
		}
	}
})();
<|endoftext|>


<commit_before>/*
    Date: 2016-01-05
*/

var configMenu = {isVisible:false};
var feedList   = {isVisible:false};
var story      = {isVisible:false};
var feedInput  = {isVisible:false};

$('#appIcon').on('click', function(event) {
    console.log('#appIcon');
    if (configMenu.isVisible) {
        $('#feedContainter').removeClass('hidden'); // show the feeds
        $('#configMenu').addClass('hidden');       // hide our config menu
        configMenu.isVisible = false;
    } else {
        $('#feedContainter').addClass('hidden');    // hide the feeds
        $('#configMenu').removeClass('hidden');    // show our config menu
        configMenu.isVisible = true;
    }
});

$('#menuIcon').on('click', function(event) {
    console.log('#menuIcon');
    if (feedList.isVisible) {
        $('#feedContainter').removeClass('hidden');  // show the feeds
        $('#RSSListContainter').addClass('hidden'); // hide our config menu
        feedList.isVisible = false;
    } else {
        $('#feedContainter').addClass('hidden');        // hide the feeds
        $('#RSSListContainter').removeClass('hidden');  // show our config menu
        feedList.isVisible = true;
    }
});

$('#getData').on('click', function(event) {
    console.log('#getData');
    $('.feedStatus').html('#getData');
    if (readerApp.needFeed) {
        $('.feedStatus').html('getting Data');
        var passingReference = {
            // Reduce
            draw  : function (parm) {
                $('#toggleStory').html(parm.title);
                $('.storyStatus').html(parm.entries.length);

                var s = '';
                //now "shadow" draw the list
                $.each(parm.entries, function(i, v) {
                    s += '
' + v.title + '
';
                });
                $("#linksList li").remove();
                $("#linksList").append(s);
                buttons.rebind();
            },
            // output of status
            status: function (parm) { $('.feedStatus').html(parm); },
        };
        readerApp.getFeed(passingReference);
        readerApp.needFeed = false;
    }
});

$('#getFeeds').on('click', function(event) {
    console.log('#getFeeds');
    $('.feedStatus').html('#getData');
            var s = '';
            console.log(localStore.length());
     $('.feedStatus').html(localStore.length());
            //now "shadow" draw the list
            for (i = 0; i < localStore.length(); i++ ) {
               var v = localStore.get(i);
               s += '
' + v + '
';
            };
            s += '
Add a Feed
';
            console.log(s);
//    $('.feedStatus').html('code' + s + '');
            $("#RSSList li").remove();
            $("#RSSList").append(s);
            buttons.dynamic2();
    addFeedBinding();
});

$('#Cordova').on('click', function(event) {
    console.log('#Cordova');
    $('#getData').trigger('click');
});

// Toggle the visibility of the "Story"
$('#toggleStory').on('click', function(event) {
    console.log('#toggleStory');
    if (story.isVisible) {
        $('#story').addClass('hidden');
        $('#linksList').removeClass('hidden');
        story.isVisible = false;
    } else {
        var loadStory = function (parm) {
            $('#story').html(
                '
'  + parm.title + '
' +
                '
' + parm.description  + '
' +
                '<button id=readMore>Read More ..</button>'
            );
            buttons.readmore(parm.link);   // create handler to open browser
        };
        readerApp.getStory(loadStory);
        $('#story').removeClass('hidden'); // make story visible
        $('#linksList').addClass('hidden');
        story.isVisible = true;
    }
});

addFeedBinding = function () {
$('#addFeed').on('click', function(event) {
    console.log('#addFeed');
    if (feedInput.isVisible) {
        $('#feedInput').addClass('hidden');
        feedInput.isVisible = false;
    } else {
        $('#feedInput').removeClass('hidden');
        feedInput.isVisible = true;
    }
});
};
addFeedBinding();

$('#addBtn').on('click', function(event) {
    console.log('#addBtn');
    readerApp.addFeed($('#addField').val());  // Add the data to storage
    //$('#addField').val('');                 // clear the field
    //$('#addFeed').trigger('click');         // trigger the toggle button
});

$('#cancelBtn').on('click', function(event) {
    console.log('#cancelBtn');
    $('#addField').val('');                   // clear the field
    $('#addFeed').trigger('click');           // trigger the toggle button
});

/*
    Since the list is dynamically created, the rebinding of buttons
    has to be done everytime the list is created. These button
    module deals with that.
*/
var buttons = {
    listTag : null,
    storyTag : null,
    browserTag : null,

    init : function (tag) {
        buttons.listTag = tag.list;
        buttons.storyTag = tag.story;
        buttons.browserTag = tag.browser;
        console.log("buttons.init");
        buttons.dynamic();
    },

    dynamic : function () {
        if (buttons.listTag && buttons.storyTag) {
            $(buttons.listTag).on('click', function(event) {
                $('.lastReadLink').html(event.target.id);
                currentFeed.selectedStory = event.target.id;
                $(buttons.storyTag).trigger('click');
            });
        } else {
            alert('Cannot bind dynamic buttons');
        }
    },

    dynamic2 : function () {
            $('.feedLink').on('click', function(event) {
                //$('.lastReadLink').html(event.target.id);
                //currentFeed.selectedStory = event.target.id;
                console.log('id:' + event.target.id);
                console.log(localStore.get(event.target.id));
                // reset the need for a feed.
                readerApp.needFeed = true;
                readerApp.entries  = [];
                currentFeed.RSS    = localStore.get(event.target.id);
                $('#getData').trigger('click');
            });
    },

    readmore : function (link, tag) {
        if (! tag) {
            tag = buttons.browserTag;
        }
        if (tag && link) {
            $(tag).on('click', function(event) {
                window.open(link, '_system');
            });
        } else {
            alert('Cannot bind dynamic buttons #2');
        }
    },

    rebind : function () {
        buttons.dynamic();
    }
}<commit_msg>update<commit_after>/*
    Date: 2016-01-05
*/

var configMenu = {isVisible:false};
var feedList   = {isVisible:false};
var story      = {isVisible:false};
var feedInput  = {isVisible:false};

$('#appIcon').on('click', function(event) {
    console.log('#appIcon');
    if (configMenu.isVisible) {
        $('#feedContainter').removeClass('hidden'); // show the feeds
        $('#configMenu').addClass('hidden');       // hide our config menu
        configMenu.isVisible = false;
    } else {
        $('#feedContainter').addClass('hidden');    // hide the feeds
        $('#configMenu').removeClass('hidden');    // show our config menu
        configMenu.isVisible = true;
    }
});

$('#menuIcon').on('click', function(event) {
    console.log('#menuIcon');
    if (feedList.isVisible) {
        $('#feedContainter').removeClass('hidden');  // show the feeds
        $('#RSSListContainter').addClass('hidden'); // hide our config menu
        feedList.isVisible = false;
    } else {
        $('#feedContainter').addClass('hidden');        // hide the feeds
        $('#RSSListContainter').removeClass('hidden');  // show our config menu
        feedList.isVisible = true;
    }
});

$('#getData').on('click', function(event) {
    console.log('#getData');
    $('.feedStatus').html('#getData');
    if (readerApp.needFeed) {
        $('.feedStatus').html('getting Data');
        var passingReference = {
            // Reduce
            draw  : function (parm) {
                $('#toggleStory').html(parm.title);
                $('.storyStatus').html(parm.entries.length);

                var s = '';
                //now "shadow" draw the list
                $.each(parm.entries, function(i, v) {
                    s += '
' + v.title + '
';
                });
                $("#linksList li").remove();
                $("#linksList").append(s);
                buttons.rebind();
            },
            // output of status
            status: function (parm) { $('.feedStatus').html(parm); },
        };
        readerApp.getFeed(passingReference);
        readerApp.needFeed = false;
    }
});

$('#getFeeds').on('click', function(event) {
    console.log('#getFeeds');
    $('.feedStatus').html('#getData');
            var s = '';
            console.log(localStore.length());
     $('.feedStatus').html(localStore.length());
            //now "shadow" draw the list
            for (i = 0; i < localStore.length(); i++ ) {
               var v = localStore.get(i);
               s += '
' + v + '
';
            };
            s += '
Add a Feed
';
            console.log(s);
//    $('.feedStatus').html('code' + s + '');
            $("#RSSList li").remove();
            $("#RSSList").append(s);
            buttons.dynamic2();
    addFeedBinding();
});

$('#Cordova').on('click', function(event) {
    console.log('#Cordova');
    $('#getData').trigger('click');
});

// Toggle the visibility of the "Story"
$('#toggleStory').on('click', function(event) {
    console.log('#toggleStory');
    if (story.isVisible) {
        $('#story').addClass('hidden');
        $('#linksList').removeClass('hidden');
        story.isVisible = false;
    } else {
        var loadStory = function (parm) {
            $('#story').html(
                '
'  + parm.title + '
' +
                '
' + parm.description  + '
' +
                '<button id=readMore>Read More ..</button>'
            );
            buttons.readmore(parm.link);   // create handler to open browser
        };
        readerApp.getStory(loadStory);
        $('#story').removeClass('hidden'); // make story visible
        $('#linksList').addClass('hidden');
        story.isVisible = true;
    }
});

addFeedBinding = function () {
$('#addFeed').on('click', function(event) {
    console.log('#addFeed');
    if (feedInput.isVisible) {
        $('#feedInput').addClass('hidden');
        feedInput.isVisible = false;
    } else {
        $('#feedInput').removeClass('hidden');
        feedInput.isVisible = true;
    }
});
};
addFeedBinding();

$('#addBtn').on('click', function(event) {
    console.log('#addBtn');
    readerApp.addFeed($('#addField').val());  // Add the data to storage
    //$('#addField').val('');                 // clear the field
    //$('#addFeed').trigger('click');         // trigger the toggle button
});

$('#cancelBtn').on('click', function(event) {
    console.log('#cancelBtn');
    $('#addField').val('');                   // clear the field
    $('#addFeed').trigger('click');           // trigger the toggle button
});

/*
    Since the list is dynamically created, the rebinding of buttons
    has to be done everytime the list is created. These button
    module deals with that.
*/
var buttons = {
    listTag : null,
    storyTag : null,
    browserTag : null,

    init : function (tag) {
        buttons.listTag = tag.list;
        buttons.storyTag = tag.story;
        buttons.browserTag = tag.browser;
        console.log("buttons.init");
        buttons.dynamic();
    },

    dynamic : function () {
        if (buttons.listTag && buttons.storyTag) {
            $(buttons.listTag).on('click', function(event) {
                $('.lastReadLink').html(event.target.id);
                currentFeed.selectedStory = event.target.id;
                $(buttons.storyTag).trigger('click');
            });
        } else {
            alert('Cannot bind dynamic buttons');
        }
    },

    dynamic2 : function () {
            $('.feedLink').on('click', function(event) {
                //$('.lastReadLink').html(event.target.id);
                //currentFeed.selectedStory = event.target.id;
                console.log('id:' + event.target.id);
                console.log(localStore.get(event.target.id));
                // reset the need for a feed.
                readerApp.needFeed = true;
                currentFeed.entries  = [];
                currentFeed.RSS    = localStore.get(event.target.id);
                $('#getData').trigger('click');
            });
    },

    readmore : function (link, tag) {
        if (! tag) {
            tag = buttons.browserTag;
        }
        if (tag && link) {
            $(tag).on('click', function(event) {
                window.open(link, '_system');
            });
        } else {
            alert('Cannot bind dynamic buttons #2');
        }
    },

    rebind : function () {
        buttons.dynamic();
    }
}<|endoftext|>


<commit_before>// Morse code as of ITU M.1677-1-200910.
//
// As Morse code is a digital protocol at the bottom (signal / no signal), it
// is represented using true (signal) and false (no signal).

const assert = require('chai').assert;
const expect = require('chai').expect;

const table = {
  'a': '.-',
  'b': '-...',
  'c': '-.-.',
  'd': '-..',
  'e': '.',
  'f': '..-.',
  'g': '--.',
  'h': '....',
  'i': '..',
  'j': '.---',
  'k': '-.-',
  'l': '.-..',
  'm': '--',
  'n': '-.',
  'o': '---',
  'p': '.--.',
  'q': '--.-',
  'r': '.-.',
  's': '...',
  't': '-',
  'u': '..-',
  'v': '...-',
  'w': '.--',
  'x': '-..-',
  'y': '-.--',
  'z': '--..',

  '1': '.----',
  '2': '..---',
  '3': '...--',
  '4': '....-',
  '5': '.....',
  '6': '-....',
  '7': '--...',
  '8': '---..',
  '9': '----.',
  '0': '-----'

  // TODO Add punctuation from 1.1.3
};

expect(table.e).to.equal('.');
expect(table['7']).to.equal('--...');

// Representation of a long 'dah'
const dah = '-';

// Representation of a short 'dit'
const dit = '.';

// Representation of pause/ break/ nothing
const pause = ' ';

// Return true if character is a long 'dah' in morse notation.
// :: String[1] -> Boolean
const isDah = function (c) {
  return dah === c;
};

expect(isDah(dah)).to.be.true;
expect(isDah(dit)).to.be.false;

// Return true if character is a short 'dit' in morse notation.
// :: String[1] -> Boolean
const isDit = function (c) {
  return dit === c;
};

expect(isDit(dit)).to.be.true;
expect(isDit(dah)).to.be.false;

// 2.1 A dash is equal to three dots.
// :: Int
const dashDotRatio = 3;

// 2.2 The space between the signals forming the same letter is equal to one
// dot.
// :: Int
const symbolDotRatio = 1;

// 2.3 The space between two letters is equal to three dots.
// :: Int
const letterDotRatio = 3;

// 2.4: The space between two words is equal to seven dots.
const wordDotRatio = 7;

// Utility: Flatten array
// :: [a] -> [b]
const flatten = function (as) {
  return [].concat.apply([], as);
};

// Utility: Create array of length n, and prepopulate with d
// :: a -> Number -> [a]
const mkArray = function (d, n) {
  return Array.apply(null, new Array(n)).map(_ => d);
};

expect(mkArray(true, 2)).to.be.eql([true, true]);
expect(mkArray(0, 3)).to.be.eql([0, 0, 0]);

const mkPause = function (n) {
  return mkArray(' ', n).join('');
}
expect(mkPause(2)).to.equal('  ');

// Encode a character text into morse.
// :: String[1] -> String
const encodeLetter = function (c) {
  return table[c];
};

expect(encodeLetter('a')).to.equal('.-');

// Encode a word into morse.
// :: String -> String
const encodeWord = function(s) {
  // HERE const separator = mkPause(3);
  const separator = mkPause(1);
  return s.split('').map(encodeLetter).join(separator);
}

expect(encodeWord('ee')).to.equal('. .');

// Encode text into morse.
// Follows ITU spec 2.4: The space between two words is equal to seven dots.
// TODO Using a String is suboptimal, because it hardcodes the underlying buffer
// resp. transport representation (could be file based, stream based, ...).
// :: String -> String
const encode = function (s) {
  // HERE const separator = mkPause(wordDotRatio);
  const separator = mkPause(3);
  return s
    .toLowerCase()
    .split(' ')
    .map(encodeWord)
    .join(separator);
}

expect(encode('morse code')).to.equal('-- --- .-. ... .   -.-. --- -.. .');
expect(encode('FREIE ENZYKLOPAEDIE'))
    .to
    .equal('..-. .-. . .. .   . -. --.. -.-- -.- .-.. --- .--. .- . -.. .. .');

// Convert morse into time notation, where short is represented as '.' and
// a long '-' as '='.
// Characters other than 'dit' and 'dah' are represented as '?'.
// :: String[1] -> String[1]
const timeNotation = function (b) {
  return isDit(b) ? '.' : (isDah(b) ? '=' : '?');
};
expect(table.c
    .split('')
    .map(timeNotation)
    .join(''))
  .to.equal('=.=.');

const s3 = '===.===...===.===.===...=.===.=...=.=.=...=' +
  '.......===.=.===.=...===.===.===...===.=.=...=';
// EOF
<commit_msg>feat: Add signal function<commit_after>// Morse code as of ITU M.1677-1-200910.
//
// As Morse code is a digital protocol at the bottom (signal / no signal), it
// is represented using true (signal) and false (no signal).

// Look mom - no var!
// Look dad - no for!

const assert = require('chai').assert;
const expect = require('chai').expect;

const table = {
  'a': '.-',
  'b': '-...',
  'c': '-.-.',
  'd': '-..',
  'e': '.',
  'f': '..-.',
  'g': '--.',
  'h': '....',
  'i': '..',
  'j': '.---',
  'k': '-.-',
  'l': '.-..',
  'm': '--',
  'n': '-.',
  'o': '---',
  'p': '.--.',
  'q': '--.-',
  'r': '.-.',
  's': '...',
  't': '-',
  'u': '..-',
  'v': '...-',
  'w': '.--',
  'x': '-..-',
  'y': '-.--',
  'z': '--..',

  '1': '.----',
  '2': '..---',
  '3': '...--',
  '4': '....-',
  '5': '.....',
  '6': '-....',
  '7': '--...',
  '8': '---..',
  '9': '----.',
  '0': '-----'

  // TODO Add punctuation from 1.1.3
};

expect(table.e).to.equal('.');
expect(table['7']).to.equal('--...');

// Representation of a long 'dah'
const dah = '-';

// Representation of a short 'dit'
const dit = '.';

// Representation of pause/ break/ nothing
const pause = ' ';

// Return true if character is a long 'dah' in morse notation.
// :: String[1] -> Boolean
const isDah = c => { return dah === c; };

// Avoid .to.be.true because linter will complain about expression
expect(isDah(dah)).to.equal(true);
expect(isDah(dit)).to.equal(false);

// Return true if character is a short 'dit' in morse notation.
// :: String[1] -> Boolean
const isDit = function (c) {
  return dit === c;
};

expect(isDit(dit)).to.equal(true);
expect(isDit(dah)).to.equal(false);

// 2.1 A dash is equal to three dots.
// :: Int
const dashDotRatio = 3;

// 2.2 The space between the signals forming the same letter is equal to one
// dot.
// :: Int
const symbolDotRatio = 1;

// 2.3 The space between two letters is equal to three dots.
// :: Int
const letterDotRatio = 3;

// 2.4: The space between two words is equal to seven dots.
const wordDotRatio = 7;

// Utility: Flatten array
// :: [a] -> [b]
const flatten = as => { return [].concat.apply([], as); };

// Utility: Create array of length n, and prepopulate with d
// :: a -> Number -> [a]
const mkArray = (d, n) => {
  return Array.apply(null, new Array(n)).map(_ => d);
};

expect(mkArray(true, 2)).to.be.eql([true, true]);
expect(mkArray(0, 3)).to.be.eql([0, 0, 0]);

const mkPause = n => { return mkArray(' ', n).join(''); };
expect(mkPause(2)).to.equal('  ');

// Encode a character text into morse.
// :: String[1] -> String
const encodeLetter = c => { return table[c]; };

expect(encodeLetter('a')).to.equal('.-');

// Encode a word into morse.
// :: String -> String
const encodeWord = s => {
  return s
    .split('')
    .map(encodeLetter)
    .join(mkPause(1));
};

expect(encodeWord('ee')).to.equal('. .');

// Encode text into morse.
// TODO Using a String is suboptimal, because it hardcodes the underlying buffer
// resp. transport representation (could be file based, stream based, ...).
// :: String -> String
const encode = s => {
  return s
    .toLowerCase()
    .split(' ')
    .map(encodeWord)
    .join(mkPause(3));
};

expect(encode('morse code')).to.equal('-- --- .-. ... .   -.-. --- -.. .');
expect(encode('FREIE ENZYKLOPAEDIE'))
    .to
    .equal('..-. .-. . .. .   . -. --.. -.-- -.- .-.. --- .--. .- . -.. .. .');

////////////////////////////////////////////////////////////////////////////////
//
// Signal functions
//
////////////////////////////////////////////////////////////////////////////////

// Map true -> '=' and false -> '_'
// :: Boolean -> Character
const wikipediaNotation = a => { return a ? '=' : '_'; };

expect(wikipediaNotation([ true, true, false ])).to.eql('=', '_', '=');

// Convert a text into a signal (on/ off).
// :: String -> [Boolean]
const signal = s => {
  // ECMAScripts Array.prototype.join() does not offer anything other than
  // Strings, so instead of boolean true and false the implementation is based
  // on characters '0' and '1'.
  const off = '0';
  const on = '1';

  // A little macro-ish like helper
  const sep0 = n => { return mkArray(off, n).join(''); };
  const sep1 = n => { return mkArray(on, n).join(''); };

  const wordSep = sep0(wordDotRatio);
  const letterSep = sep0(letterDotRatio);
  const symbolSep = sep0(symbolDotRatio);

  const dah = sep1(dashDotRatio);
  const dit = sep1(1);
  return s
    .toLowerCase()
    .split(' ')
    .map(word => {
      // console.log('* Word: ' + word);
      return word
        .split('')
        .map(letter => {
          // console.log('* * Letter: ' + letter);
          return encodeLetter(letter)
            .split('')
            .map(symbol => {
              // console.log('* * * Symbol: ' + symbol);
              return (isDah(symbol) ? dah : dit);
            })
            .join(symbolSep);
        })
        .join(letterSep);
    })
    .join(wordSep)
    .split('')
    .map(c => { return c === on ? true : false; });
};

expect(signal('e')).to.eql([true]);
expect(signal('ee')).to.eql([
    true,
    false, false, false, // letter separator
    true ]);

expect(signal('morse code')
    .map(wikipediaNotation)
    .join(''))
  .to
  .equal('===_===___===_===_===___=_===_=___=_=_=___=_______' +
      '===_=_===_=___===_===_===___===_=_=___=');

// EOF
<|endoftext|>


<commit_before>'use strict';

/*

CassandraStore is a Cassandra tile storage.
 */

var util = require('util');
var BBPromise = require('bluebird');
var core = require('kartotherian-core');
var cassandra = require('cassandra-driver');
var multistream = require('multistream');
var promistreamus = require('promistreamus');

var prepared = {prepare: true};

BBPromise.promisifyAll(cassandra.Client.prototype);

CassandraStore.registerProtocols = function(tilelive) {
    tilelive.protocols['cassandra:'] = CassandraStore;
};

function CassandraStore(uri, callback) {
    var self = this;
    var params;
    this.batchMode = 0;
    this.batch = [];

    this.throwError = function (msg) {
        throw new Error(util.format.apply(null, arguments) + JSON.stringify(uri));
    };

    this.attachUri = function (err) {
        err.moduleUri = JSON.stringify(uri);
        throw err;
    };

    return BBPromise.try(function () {
        params = core.normalizeUri(uri).query;
        if (!params.cp) {
            self.throwError("Uri must include at least one 'cp' connect point query parameter");
        } else if (typeof params.cp === 'string') {
            self.contactPoints = [params.cp];
        } else {
            self.contactPoints = params.cp;
        }
        if (!params.keyspace || !/^[a-zA-Z][a-zA-Z0-9]*$/.test(params.keyspace)) {
            self.throwError("Uri must have a valid 'keyspace' query parameter");
        }
        if (params.table && !/^[a-zA-Z][a-zA-Z0-9]*$/.test(params.table)) {
            self.throwError("Optional uri 'table' param must be a valid value");
        }
        if (params.repclass && !/^[a-zA-Z][a-zA-Z0-9]*$/.test(params.repclass)) {
            self.throwError("Uri 'repclass' must be a valid value");
        }
        self.keyspace = params.keyspace;
        self.table = params.table || 'tiles';
        self.repclass = params.repclass || 'SimpleStrategy';
        self.repfactor = typeof params.repfactor === 'undefined' ? 3 : parseInt(params.repfactor);
        var dw = params.durablewrite;
        self.durablewrite = (typeof dw === 'undefined' || (dw && dw !== 'false' && dw !== '0')) ? 'true' : 'false';
        self.minzoom = typeof params.minzoom === 'undefined' ? 0 : parseInt(params.minzoom);
        self.maxzoom = typeof params.maxzoom === 'undefined' ? 15 : parseInt(params.maxzoom);
        self.blocksize = typeof params.blocksize === 'undefined' ? 32768 : parseInt(params.blocksize);
        self.maxBatchSize = typeof params.maxBatchSize === 'undefined' ? undefined : parseInt(params.maxBatchSize);
        var clientOpts = {contactPoints: self.contactPoints};
        if (params.username || params.password) {
            clientOpts.authProvider = new cassandra.auth.PlainTextAuthProvider(params.username, params.password);
        }
        self.client = new cassandra.Client(clientOpts);
        self.headers = {
            'Content-Type': 'application/x-protobuf',
            'Content-Encoding': 'gzip'
        };
        return self.client.connectAsync();
    }).then(function () {
        return self.client.executeAsync(
            "CREATE KEYSPACE IF NOT EXISTS " + self.keyspace +
            " WITH REPLICATION = {'class': '" + self.repclass + "'," +
            " 'replication_factor': " + self.repfactor + "}" +
            " AND DURABLE_WRITES = " + self.durablewrite);
    }).then(function () {
        return self.client.executeAsync("USE " + self.keyspace);
    }).then(function () {
        var createTableSql = "CREATE TABLE IF NOT EXISTS " + self.table + " (" +
            " zoom int," +
            (self.blocksize ? " block int," : "") +
            " idx int," +
            " tile blob," +
            (self.blocksize
                ? " PRIMARY KEY (zoom, block, idx)"
                : " PRIMARY KEY (zoom, idx)") +
            ")";
        return self.client.executeAsync(createTableSql);
    }).catch(function (err) {
        return self.closeAsync().finally(function () {
            throw err;
        });
    }).then(function () {
        var whereClause = ' WHERE zoom = ? AND idx = ?';
        if (self.blocksize)
            whereClause += ' AND block = ?';
        self.queries = {
            getTile: 'SELECT tile FROM ' + self.table + whereClause,
            getWriteTime: 'SELECT WRITETIME(tile) AS wt FROM ' + self.table + whereClause,
            getTileAndWt: 'SELECT tile, WRITETIME(tile) AS wt FROM ' + self.table + whereClause,
            set: 'UPDATE ' + self.table + ' SET tile = ?' + whereClause,
            delete: 'DELETE FROM ' + self.table + whereClause
        };

        return self;
    }).catch(this.attachUri).nodeify(callback);
}

CassandraStore.registerProtocols = function(tilelive) {
    tilelive.protocols['cassandra:'] = CassandraStore;
};

CassandraStore.prototype.getTile = function(z, x, y, callback) {
    var self = this;
    return this.queryTileAsync({zoom: z, idx: core.xyToIndex(x, y)}).then(function(row) {
        if (row) {
            return [row.tile, self.headers];
        } else {
            self.throwError('Tile (%j, %j, %j) does not exist', z, x, y);
        }
    }).catch(this.attachUri).nodeify(callback, {spread: true});
};

CassandraStore.prototype.getInfo = function(callback) {
    callback(null, {
        "bounds": "-180,-85.0511,180,85.0511",
        "center": "0,0,2",
        "description": "",
        "maxzoom": this.maxzoom,
        "minzoom": this.minzoom,
        "name": "cassandra",
        "template": "",
        "version": "1.0.0"
    });
};

CassandraStore.prototype.putTile = function(z, x, y, tile, callback) {
    var self = this;
    BBPromise.try(function() {
        var query, params;
        var idx = core.xyToIndex(x, y);
        if (tile && tile.length > 0) {
            query = self.queries.set;
            params = [tile, z, idx];
        } else {
            query = self.queries.delete;
            params = [z, idx];
        }
        if (self.blocksize)
            params.push(Math.floor(idx / self.blocksize));
        if (!self.batchMode || !self.maxBatchSize) {
            return self.client.executeAsync(query, params, prepared);
        } else {
            self.batch.push({query: query, params: params});
            if (Object.keys(self.batch).length > self.maxBatchSize) {
                return self.flushAsync();
            }
        }
    }).catch(this.attachUri).nodeify(callback);
};

CassandraStore.prototype.close = function(callback) {
    var cl = this.client;
    if (!cl) {
        callback(null);
    } else {
        var self = this;
        BBPromise.try(function () {
            return (self.batchMode && self.maxBatchSize) ? self.flushAsync() : true;
        }).then(function () {
            delete self.client;
            self.batchMode = 0;
            return cl.shutdownAsync();
        }).catch(this.attachUri).nodeify(callback);
    }
};

CassandraStore.prototype.startWriting = function(callback) {
    this.batchMode++;
    callback(null);
};

CassandraStore.prototype.flush = function(callback) {
    var batch = this.batch;
    if (Object.keys(batch).length > 0) {
        this.batch = [];
        this.client
            .batchAsync(batch, prepared)
            .catch(this.attachUri)
            .nodeify(callback);
    } else {
        callback();
    }
};

CassandraStore.prototype.stopWriting = function(callback) {
    var self = this;
    BBPromise.try(function () {
        if (self.batchMode === 0) {
            self.throwError('stopWriting() called more times than startWriting()')
        }
        self.batchMode--;
        return self.flushAsync();
    })
        .catch(this.attachUri)
        .nodeify(callback);
};

CassandraStore.prototype.queryTileAsync = function(options) {
    var self = this, getTile, getWriteTime, getSize;

    return BBPromise.try(function() {
        if (!core.isInteger(options.zoom))
            self.throwError('Options must contain integer zoom parameter. Opts=%j', options);
        if (!core.isInteger(options.idx))
            self.throwError('Options must contain an integer idx parameter. Opts=%j', options);
        var maxEnd = Math.pow(4, options.zoom);
        if (options.idx < 0 || options.idx >= maxEnd)
            self.throwError('Options must satisfy: 0 <= idx < %d. Opts=%j', maxEnd, options);
        getTile = typeof options.getTile === 'undefined' ? true : options.getTile;
        getWriteTime = typeof options.getWriteTime === 'undefined' ? false : options.getWriteTime;
        getSize = typeof options.getSize === 'undefined' ? false : options.getSize;
        var query;
        if (getWriteTime && (getTile || getSize))
            query = self.queries.getTileAndWt;
        else if (getTile || getSize)
            query = self.queries.getTile;
        else if (getWriteTime)
            query = self.queries.getWriteTime;
        else
            self.throwError('Either getTile or getWriteTime or both must be requested. Opts=%j', options);
        var params = [options.zoom, options.idx];
        if (self.blocksize)
            params.push(Math.floor(options.idx / self.blocksize));
        return self.client.executeAsync(query, params, prepared);
    }).then(function(res) {
        if ('rows' in res && res.rows.length === 1) {
            var row = res.rows[0];
            var resp = {};
            if (getTile) resp.tile = row.tile;
            if (getSize) resp.size = row.tile.length; // TODO: Use UDF in the next Cassandra ver
            if (getWriteTime) resp.writeTime = new Date(row.wt / 1000);
            return resp;
        } else {
            return false;
        }
    }).catch(this.attachUri);
};

/**
 * Query database for all tiles that match conditions
 * @param options - an object that must have an integer 'zoom' value.
 * Optional values:
 *  idxFrom - int index to start iteration from (inclusive)
 *  idxBefore - int index to stop iteration at (exclusive)
 *  dateFrom - Date value - return only tiles whose write time is on or after this date (inclusive)
 *  dateBefore - Date value - return only tiles whose write time is before this date (exclusive)
 *  biggerThan - number - return only tiles whose compressed size is bigger than this value (inclusive)
 *  smallerThan - number - return only tiles whose compressed size is smaller than this value (exclusive)
 * @returns {Function} - a function that returns a promise. If promise resolves to undefined, there are no more values
 * in the stream.
 */
CassandraStore.prototype.query = function(options) {
    var self = this,
        dateBefore, dateFrom;

    if (!core.isInteger(options.zoom))
        self.throwError('Options must contain integer zoom parameter. Opts=%j', options);
    if (typeof options.idxFrom !== 'undefined' && !core.isInteger(options.idxFrom))
        self.throwError('Options may contain an integer idxFrom parameter. Opts=%j', options);
    if (typeof options.idxBefore !== 'undefined' && !core.isInteger(options.idxBefore))
        self.throwError('Options may contain an integer idxBefore parameter. Opts=%j', options);
    if (typeof options.dateBefore !== 'undefined' && Object.prototype.toString.call(options.dateBefore) !== '[object Date]')
        self.throwError('Options may contain a Date dateBefore parameter. Opts=%j', options);
    if (typeof options.dateFrom !== 'undefined' && Object.prototype.toString.call(options.dateFrom) !== '[object Date]')
        self.throwError('Options may contain a Date dateFrom parameter. Opts=%j', options);
    if (typeof options.biggerThan !== 'undefined' && typeof options.biggerThan !== 'number')
        self.throwError('Options may contain a biggerThan numeric parameter. Opts=%j', options);
    if ((typeof options.smallerThan !== 'undefined' && typeof options.smallerThan !== 'number') || options.smallerThan <= 0)
        self.throwError('Options may contain a smallerThan numeric parameter that is bigger than 0. Opts=%j', options);
    var maxEnd = Math.pow(4, options.zoom);
    var start = options.idxFrom || 0;
    var end = options.idxBefore || maxEnd;
    if (start > end || end > maxEnd)
        self.throwError('Options must satisfy: idxFrom <= idxBefore <= %d. Opts=%j', maxEnd, options);
    if (options.dateFrom >= options.dateBefore)
        self.throwError('Options must satisfy: dateFrom < dateBefore. Opts=%j', options);
    dateFrom = options.dateFrom ? options.dateFrom.valueOf() * 1000 : false;
    dateBefore = options.dateBefore ? options.dateBefore.valueOf() * 1000 : false;

    var fields = 'idx';
    if (options.getTiles || options.smallerThan || options.biggerThan) {
        // If tile size check is requested, we have to get the whole tile at this point...
        // TODO: in the next Cassandra, UDFs should help with this
        // Optimization - if biggerThan is 0, it will not be used
        fields += ', tile';
    }
    if (dateBefore !== false || dateFrom !== false) {
        fields += ', WRITETIME(tile) AS wt';
    }

    var createStream = function(blockStart, blockEnd) {
        var conds = 'zoom = ?',
            params = [options.zoom];
        if (self.blocksize) {
            conds += ' AND block = ?';
            params.push(Math.floor(blockStart / self.blocksize));
        }
        if (start > blockStart) {
            conds += ' AND idx >= ?';
            params.push(start);
        }
        if (end < blockEnd) {
            conds += ' AND idx < ?';
            params.push(end);
        }
        var query = 'SELECT ' + fields + ' FROM ' + self.table + ' WHERE ' + conds;
        return self.client.stream(query, params, {prepare: true, autoPage: true});
    };

    var ms;
    if (self.blocksize) {
        var blockIdx = Math.floor(start / self.blocksize);
        var toBlockIdx = Math.floor((end - 1) / self.blocksize);
        ms = multistream.obj(function() {
            if (blockIdx > toBlockIdx) return false;
            var s = createStream(blockIdx * self.blocksize,
                Math.min(maxEnd, (blockIdx + 1) * self.blocksize));
            blockIdx++;
            return s;
        });
    } else {
        ms = createStream(0, maxEnd);
    }

    return promistreamus(ms, function(value) {
        if ((dateBefore !== false && value.wt >= dateBefore) ||
            (dateFrom !== false && value.wt < dateFrom) ||
            (options.smallerThan && value.tile.length >= options.smallerThan) ||
            (options.biggerThan && value.tile.length < options.biggerThan)
        ) {
            return undefined;
        }
        var res = {
            zoom: options.zoom,
            idx: value.idx
        };
        if (options.getTiles) {
            res.tile = value.tile;
        }
        return res;
    });
};

BBPromise.promisifyAll(CassandraStore.prototype);
module.exports = CassandraStore;
<commit_msg>Fixed missing tile error<commit_after>'use strict';

/*

CassandraStore is a Cassandra tile storage.
 */

var util = require('util');
var BBPromise = require('bluebird');
var core = require('kartotherian-core');
var cassandra = require('cassandra-driver');
var multistream = require('multistream');
var promistreamus = require('promistreamus');

var prepared = {prepare: true};

BBPromise.promisifyAll(cassandra.Client.prototype);

CassandraStore.registerProtocols = function(tilelive) {
    tilelive.protocols['cassandra:'] = CassandraStore;
};

function CassandraStore(uri, callback) {
    var self = this;
    var params;
    this.batchMode = 0;
    this.batch = [];

    this.throwError = function (msg) {
        throw new Error(util.format.apply(null, arguments) + JSON.stringify(uri));
    };

    this.attachUri = function (err) {
        err.moduleUri = JSON.stringify(uri);
        throw err;
    };

    return BBPromise.try(function () {
        params = core.normalizeUri(uri).query;
        if (!params.cp) {
            self.throwError("Uri must include at least one 'cp' connect point query parameter");
        } else if (typeof params.cp === 'string') {
            self.contactPoints = [params.cp];
        } else {
            self.contactPoints = params.cp;
        }
        if (!params.keyspace || !/^[a-zA-Z][a-zA-Z0-9]*$/.test(params.keyspace)) {
            self.throwError("Uri must have a valid 'keyspace' query parameter");
        }
        if (params.table && !/^[a-zA-Z][a-zA-Z0-9]*$/.test(params.table)) {
            self.throwError("Optional uri 'table' param must be a valid value");
        }
        if (params.repclass && !/^[a-zA-Z][a-zA-Z0-9]*$/.test(params.repclass)) {
            self.throwError("Uri 'repclass' must be a valid value");
        }
        self.keyspace = params.keyspace;
        self.table = params.table || 'tiles';
        self.repclass = params.repclass || 'SimpleStrategy';
        self.repfactor = typeof params.repfactor === 'undefined' ? 3 : parseInt(params.repfactor);
        var dw = params.durablewrite;
        self.durablewrite = (typeof dw === 'undefined' || (dw && dw !== 'false' && dw !== '0')) ? 'true' : 'false';
        self.minzoom = typeof params.minzoom === 'undefined' ? 0 : parseInt(params.minzoom);
        self.maxzoom = typeof params.maxzoom === 'undefined' ? 15 : parseInt(params.maxzoom);
        self.blocksize = typeof params.blocksize === 'undefined' ? 32768 : parseInt(params.blocksize);
        self.maxBatchSize = typeof params.maxBatchSize === 'undefined' ? undefined : parseInt(params.maxBatchSize);
        var clientOpts = {contactPoints: self.contactPoints};
        if (params.username || params.password) {
            clientOpts.authProvider = new cassandra.auth.PlainTextAuthProvider(params.username, params.password);
        }
        self.client = new cassandra.Client(clientOpts);
        self.headers = {
            'Content-Type': 'application/x-protobuf',
            'Content-Encoding': 'gzip'
        };
        return self.client.connectAsync();
    }).then(function () {
        return self.client.executeAsync(
            "CREATE KEYSPACE IF NOT EXISTS " + self.keyspace +
            " WITH REPLICATION = {'class': '" + self.repclass + "'," +
            " 'replication_factor': " + self.repfactor + "}" +
            " AND DURABLE_WRITES = " + self.durablewrite);
    }).then(function () {
        return self.client.executeAsync("USE " + self.keyspace);
    }).then(function () {
        var createTableSql = "CREATE TABLE IF NOT EXISTS " + self.table + " (" +
            " zoom int," +
            (self.blocksize ? " block int," : "") +
            " idx int," +
            " tile blob," +
            (self.blocksize
                ? " PRIMARY KEY (zoom, block, idx)"
                : " PRIMARY KEY (zoom, idx)") +
            ")";
        return self.client.executeAsync(createTableSql);
    }).catch(function (err) {
        return self.closeAsync().finally(function () {
            throw err;
        });
    }).then(function () {
        var whereClause = ' WHERE zoom = ? AND idx = ?';
        if (self.blocksize)
            whereClause += ' AND block = ?';
        self.queries = {
            getTile: 'SELECT tile FROM ' + self.table + whereClause,
            getWriteTime: 'SELECT WRITETIME(tile) AS wt FROM ' + self.table + whereClause,
            getTileAndWt: 'SELECT tile, WRITETIME(tile) AS wt FROM ' + self.table + whereClause,
            set: 'UPDATE ' + self.table + ' SET tile = ?' + whereClause,
            delete: 'DELETE FROM ' + self.table + whereClause
        };

        return self;
    }).catch(this.attachUri).nodeify(callback);
}

CassandraStore.registerProtocols = function(tilelive) {
    tilelive.protocols['cassandra:'] = CassandraStore;
};

CassandraStore.prototype.getTile = function(z, x, y, callback) {
    var self = this;
    return this.queryTileAsync({zoom: z, idx: core.xyToIndex(x, y)}).then(function(row) {
        if (row) {
            return [row.tile, self.headers];
        } else {
            core.throwNoTile();
        }
    }).catch(this.attachUri).nodeify(callback, {spread: true});
};

CassandraStore.prototype.getInfo = function(callback) {
    callback(null, {
        "bounds": "-180,-85.0511,180,85.0511",
        "center": "0,0,2",
        "description": "",
        "maxzoom": this.maxzoom,
        "minzoom": this.minzoom,
        "name": "cassandra",
        "template": "",
        "version": "1.0.0"
    });
};

CassandraStore.prototype.putTile = function(z, x, y, tile, callback) {
    var self = this;
    BBPromise.try(function() {
        var query, params;
        var idx = core.xyToIndex(x, y);
        if (tile && tile.length > 0) {
            query = self.queries.set;
            params = [tile, z, idx];
        } else {
            query = self.queries.delete;
            params = [z, idx];
        }
        if (self.blocksize)
            params.push(Math.floor(idx / self.blocksize));
        if (!self.batchMode || !self.maxBatchSize) {
            return self.client.executeAsync(query, params, prepared);
        } else {
            self.batch.push({query: query, params: params});
            if (Object.keys(self.batch).length > self.maxBatchSize) {
                return self.flushAsync();
            }
        }
    }).catch(this.attachUri).nodeify(callback);
};

CassandraStore.prototype.close = function(callback) {
    var cl = this.client;
    if (!cl) {
        callback(null);
    } else {
        var self = this;
        BBPromise.try(function () {
            return (self.batchMode && self.maxBatchSize) ? self.flushAsync() : true;
        }).then(function () {
            delete self.client;
            self.batchMode = 0;
            return cl.shutdownAsync();
        }).catch(this.attachUri).nodeify(callback);
    }
};

CassandraStore.prototype.startWriting = function(callback) {
    this.batchMode++;
    callback(null);
};

CassandraStore.prototype.flush = function(callback) {
    var batch = this.batch;
    if (Object.keys(batch).length > 0) {
        this.batch = [];
        this.client
            .batchAsync(batch, prepared)
            .catch(this.attachUri)
            .nodeify(callback);
    } else {
        callback();
    }
};

CassandraStore.prototype.stopWriting = function(callback) {
    var self = this;
    BBPromise.try(function () {
        if (self.batchMode === 0) {
            self.throwError('stopWriting() called more times than startWriting()')
        }
        self.batchMode--;
        return self.flushAsync();
    })
        .catch(this.attachUri)
        .nodeify(callback);
};

CassandraStore.prototype.queryTileAsync = function(options) {
    var self = this, getTile, getWriteTime, getSize;

    return BBPromise.try(function() {
        if (!core.isInteger(options.zoom))
            self.throwError('Options must contain integer zoom parameter. Opts=%j', options);
        if (!core.isInteger(options.idx))
            self.throwError('Options must contain an integer idx parameter. Opts=%j', options);
        var maxEnd = Math.pow(4, options.zoom);
        if (options.idx < 0 || options.idx >= maxEnd)
            self.throwError('Options must satisfy: 0 <= idx < %d. Opts=%j', maxEnd, options);
        getTile = typeof options.getTile === 'undefined' ? true : options.getTile;
        getWriteTime = typeof options.getWriteTime === 'undefined' ? false : options.getWriteTime;
        getSize = typeof options.getSize === 'undefined' ? false : options.getSize;
        var query;
        if (getWriteTime && (getTile || getSize))
            query = self.queries.getTileAndWt;
        else if (getTile || getSize)
            query = self.queries.getTile;
        else if (getWriteTime)
            query = self.queries.getWriteTime;
        else
            self.throwError('Either getTile or getWriteTime or both must be requested. Opts=%j', options);
        var params = [options.zoom, options.idx];
        if (self.blocksize)
            params.push(Math.floor(options.idx / self.blocksize));
        return self.client.executeAsync(query, params, prepared);
    }).then(function(res) {
        if ('rows' in res && res.rows.length === 1) {
            var row = res.rows[0];
            var resp = {};
            if (getTile) resp.tile = row.tile;
            if (getSize) resp.size = row.tile.length; // TODO: Use UDF in the next Cassandra ver
            if (getWriteTime) resp.writeTime = new Date(row.wt / 1000);
            return resp;
        } else {
            return false;
        }
    }).catch(this.attachUri);
};

/**
 * Query database for all tiles that match conditions
 * @param options - an object that must have an integer 'zoom' value.
 * Optional values:
 *  idxFrom - int index to start iteration from (inclusive)
 *  idxBefore - int index to stop iteration at (exclusive)
 *  dateFrom - Date value - return only tiles whose write time is on or after this date (inclusive)
 *  dateBefore - Date value - return only tiles whose write time is before this date (exclusive)
 *  biggerThan - number - return only tiles whose compressed size is bigger than this value (inclusive)
 *  smallerThan - number - return only tiles whose compressed size is smaller than this value (exclusive)
 * @returns {Function} - a function that returns a promise. If promise resolves to undefined, there are no more values
 * in the stream.
 */
CassandraStore.prototype.query = function(options) {
    var self = this,
        dateBefore, dateFrom;

    if (!core.isInteger(options.zoom))
        self.throwError('Options must contain integer zoom parameter. Opts=%j', options);
    if (typeof options.idxFrom !== 'undefined' && !core.isInteger(options.idxFrom))
        self.throwError('Options may contain an integer idxFrom parameter. Opts=%j', options);
    if (typeof options.idxBefore !== 'undefined' && !core.isInteger(options.idxBefore))
        self.throwError('Options may contain an integer idxBefore parameter. Opts=%j', options);
    if (typeof options.dateBefore !== 'undefined' && Object.prototype.toString.call(options.dateBefore) !== '[object Date]')
        self.throwError('Options may contain a Date dateBefore parameter. Opts=%j', options);
    if (typeof options.dateFrom !== 'undefined' && Object.prototype.toString.call(options.dateFrom) !== '[object Date]')
        self.throwError('Options may contain a Date dateFrom parameter. Opts=%j', options);
    if (typeof options.biggerThan !== 'undefined' && typeof options.biggerThan !== 'number')
        self.throwError('Options may contain a biggerThan numeric parameter. Opts=%j', options);
    if ((typeof options.smallerThan !== 'undefined' && typeof options.smallerThan !== 'number') || options.smallerThan <= 0)
        self.throwError('Options may contain a smallerThan numeric parameter that is bigger than 0. Opts=%j', options);
    var maxEnd = Math.pow(4, options.zoom);
    var start = options.idxFrom || 0;
    var end = options.idxBefore || maxEnd;
    if (start > end || end > maxEnd)
        self.throwError('Options must satisfy: idxFrom <= idxBefore <= %d. Opts=%j', maxEnd, options);
    if (options.dateFrom >= options.dateBefore)
        self.throwError('Options must satisfy: dateFrom < dateBefore. Opts=%j', options);
    dateFrom = options.dateFrom ? options.dateFrom.valueOf() * 1000 : false;
    dateBefore = options.dateBefore ? options.dateBefore.valueOf() * 1000 : false;

    var fields = 'idx';
    if (options.getTiles || options.smallerThan || options.biggerThan) {
        // If tile size check is requested, we have to get the whole tile at this point...
        // TODO: in the next Cassandra, UDFs should help with this
        // Optimization - if biggerThan is 0, it will not be used
        fields += ', tile';
    }
    if (dateBefore !== false || dateFrom !== false) {
        fields += ', WRITETIME(tile) AS wt';
    }

    var createStream = function(blockStart, blockEnd) {
        var conds = 'zoom = ?',
            params = [options.zoom];
        if (self.blocksize) {
            conds += ' AND block = ?';
            params.push(Math.floor(blockStart / self.blocksize));
        }
        if (start > blockStart) {
            conds += ' AND idx >= ?';
            params.push(start);
        }
        if (end < blockEnd) {
            conds += ' AND idx < ?';
            params.push(end);
        }
        var query = 'SELECT ' + fields + ' FROM ' + self.table + ' WHERE ' + conds;
        return self.client.stream(query, params, {prepare: true, autoPage: true});
    };

    var ms;
    if (self.blocksize) {
        var blockIdx = Math.floor(start / self.blocksize);
        var toBlockIdx = Math.floor((end - 1) / self.blocksize);
        ms = multistream.obj(function() {
            if (blockIdx > toBlockIdx) return false;
            var s = createStream(blockIdx * self.blocksize,
                Math.min(maxEnd, (blockIdx + 1) * self.blocksize));
            blockIdx++;
            return s;
        });
    } else {
        ms = createStream(0, maxEnd);
    }

    return promistreamus(ms, function(value) {
        if ((dateBefore !== false && value.wt >= dateBefore) ||
            (dateFrom !== false && value.wt < dateFrom) ||
            (options.smallerThan && value.tile.length >= options.smallerThan) ||
            (options.biggerThan && value.tile.length < options.biggerThan)
        ) {
            return undefined;
        }
        var res = {
            zoom: options.zoom,
            idx: value.idx
        };
        if (options.getTiles) {
            res.tile = value.tile;
        }
        return res;
    });
};

BBPromise.promisifyAll(CassandraStore.prototype);
module.exports = CassandraStore;
<|endoftext|>


<commit_before>
module.exports = {
  Root: require('./src/root'),
  WebLink: require('./src/web_link'),
  Resource: require('./src/resource'),
  Form: require('./src/form'),
  FieldUtils: require('./src/field_utils'),
  LinkCollection: require('./src/link_collection'),
  HalExtension: require('./src/hal'),
  JsonExtension: require('./src/json'),
  LinkHeaderExtension: require('./src/link_header'),
  SirenExtension: require('./src/siren'),
  CollectionJsonExtension: require('./src/collection_json')
};
<commit_msg>Publish the new TextExtension from the toplevel index.js.<commit_after>
module.exports = {
  Root: require('./src/root'),
  WebLink: require('./src/web_link'),
  Resource: require('./src/resource'),
  Form: require('./src/form'),
  FieldUtils: require('./src/field_utils'),
  LinkCollection: require('./src/link_collection'),
  HalExtension: require('./src/hal'),
  JsonExtension: require('./src/json'),
  TextExtension: require('./src/text'),
  LinkHeaderExtension: require('./src/link_header'),
  SirenExtension: require('./src/siren'),
  CollectionJsonExtension: require('./src/collection_json')
};
<|endoftext|>


<commit_before>// Homebridge Plugin for Mode Lighting System using Remote Control Interface
// Need to enhance with more error checking

var Service, Characteristic;

module.exports = function (homebridge) {

    // Service and Characteristic are from hap-nodejs
    Service = homebridge.hap.Service;
    Characteristic = homebridge.hap.Characteristic;
    homebridge.registerAccessory("homebridge-modelighting", "modelighting", ModeLightingAccessory);
}

function ModeLightingAccessory (log, config) {
    this.log = log;

    // Get Room Name from config.json
    this.name = config["name"];

    // Get Mode Lighting On/Off Scene Numbers for Room Name and NPU IP Address from config.json
    this.on_scene = config["on_scene"];
    this.off_scene = config["off_scene"];
    this.NPU_IP = config["NPU_IP"];
}

ModeLightingAccessory.prototype = {

    sceneRequest: function (scene, NPU_IP, callback) {

        var telnet = require('telnet-client');

        var connection = new telnet();

        var params = {
            host: this.NPU_IP,
            port: 22,
            shellPrompt: '',
            negotiationMandatory: false
        };

        // Callback handler for error event
        connection.on('error', function () {
            console.log('Error in connecting');
            connection.end();
        });

        // Callback handler for close event
        connection.on('close', function () {
            console.log('Connection to Mode NPU closed');
        });

        // Callback handler for timeout event
        connection.on('timeout', function () {
            console.log('Connection to Mode NPU timed out!');
        });

        // Callback handler for ready event
        connection.on('ready', function () {

            scene = 'SCENE' + scene + 'GO';
            console.log('Invoking ' + scene);

            connection.send(scene, null, function() {
                console.log('Scene Sent to NPU Remote Control');
            });

            // Close connection immediately after sending data
            connection.end();
        });

        // Callback handler for connect event
        connection.on('connect', function () {
            console.log('Connected to Mode NPU.');
        });

        // Connect to Mode NPU Remote Control Port
        this.log ("Connecting to Mode NPU at IP address " + NPU_IP + "with scene: " + scene);
        connection.connect(params);
    },

    setPowerState: function (powerOn, callback) {

        var scene;

        var NPU_IP=this.NPU_IP;

        if (powerOn) {
            scene = this.on_scene;
            this.log("Invoking on scene");
        } else {
            scene = this.off_scene;
            this.log("Invoking off scene");
        }

        this.sceneRequest(scene, NPU_IP);
        // , function (error) {
        //    if (error) {
        //        this.log('Scene function failed!');
        //        callback(error);
        //    } else {
        //        this.log('Scene function succeeded!');
        //        callback();
        //    }
        //}.bind(this));
        callback();
    },

    identify: function (callback) {
        this.log("Identify requested!");
        callback(); // success
    },

    getServices: function () {

        // you can OPTIONALLY create an information service if you wish to override
        // the default values for things like serial number, model, etc.
        var informationService = new Service.AccessoryInformation();

        informationService
			.setCharacteristic(Characteristic.Manufacturer, "Mode Lighting")
			.setCharacteristic(Characteristic.Model, "NPU 1.3.0.99")
			.setCharacteristic(Characteristic.SerialNumber, "");

        var switchService = new Service.Switch(this.name);

        switchService
			.getCharacteristic(Characteristic.On)
			.on('set', this.setPowerState.bind(this));

        return [switchService];
    }
};<commit_msg>Try this<commit_after>// Homebridge Plugin for Mode Lighting System using Remote Control Interface
// Need to enhance with more error checking

var Service, Characteristic;

module.exports = function (homebridge) {

    // Service and Characteristic are from hap-nodejs
    Service = homebridge.hap.Service;
    Characteristic = homebridge.hap.Characteristic;
    homebridge.registerAccessory("homebridge-modelighting", "modelighting", ModeLightingAccessory);
}

function ModeLightingAccessory (log, config) {
    this.log = log;

    // Get Room Name from config.json
    this.name = config["name"];

    // Get Mode Lighting On/Off Scene Numbers for Room Name and NPU IP Address from config.json
    this.on_scene = config["on_scene"];
    this.off_scene = config["off_scene"];
    this.NPU_IP = config["NPU_IP"];
}

ModeLightingAccessory.prototype = {

    sceneRequest: function (scene, NPU_IP) {

        var telnet = require('telnet-client');

        var connection = new telnet();

        var params = {
            host: this.NPU_IP,
            port: 22,
            shellPrompt: '',
            negotiationMandatory: false
        };

        // Callback handler for error event
        connection.on('error', function () {
            console.log('Error in connecting');
            connection.end();
        });

        // Callback handler for close event
        connection.on('close', function () {
            console.log('Connection to Mode NPU closed');
        });

        // Callback handler for timeout event
        connection.on('timeout', function () {
            console.log('Connection to Mode NPU timed out!');
        });

        // Callback handler for ready event
        connection.on('ready', function () {

            scene = 'SCENE' + scene + 'GO';
            console.log('Invoking ' + scene);

            connection.send(scene, null, function() {
                console.log('Scene Sent to NPU Remote Control');
            });

            // Close connection immediately after sending data
            connection.end();
        });

        // Callback handler for connect event
        connection.on('connect', function () {
            console.log('Connected to Mode NPU.');
        });

        // Connect to Mode NPU Remote Control Port
        this.log ("Connecting to Mode NPU at IP address " + NPU_IP + "with scene: " + scene);
        connection.connect(params);
    },

    setPowerState: function (powerOn, callback) {

        var scene;

        var NPU_IP=this.NPU_IP;

        if (powerOn) {
            scene = this.on_scene;
            this.log("Invoking on scene");
        } else {
            scene = this.off_scene;
            this.log("Invoking off scene");
        }

        this.sceneRequest(scene, NPU_IP);
        // , function (error) {
        //    if (error) {
        //        this.log('Scene function failed!');
        //        callback(error);
        //    } else {
        //        this.log('Scene function succeeded!');
        //        callback();
        //    }
        //}.bind(this));
        callback();
    },

    identify: function (callback) {
        this.log("Identify requested!");
        callback(); // success
    },

    getServices: function () {

        // you can OPTIONALLY create an information service if you wish to override
        // the default values for things like serial number, model, etc.
        var informationService = new Service.AccessoryInformation();

        informationService
			.setCharacteristic(Characteristic.Manufacturer, "Mode Lighting")
			.setCharacteristic(Characteristic.Model, "NPU 1.3.0.99")
			.setCharacteristic(Characteristic.SerialNumber, "");

        var switchService = new Service.Switch(this.name);

        switchService
			.getCharacteristic(Characteristic.On)
			.on('set', this.setPowerState.bind(this));

        return [switchService];
    }
};<|endoftext|>


<commit_before>/**!
 * koa-userauth - index.js
 *
 * Copyright(c) koajs and other contributors.
 * MIT Licensed
 *
 * Authors:
 *   dead_horse <dead_horse@qq.com>
 *   fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
 */

'use strict';

/**
 * Module dependencies.
 */

var debug = require('debug')('koa-userauth');
var path = require('path');
var is = require('is-type-of');
var copy = require('copy-to');
var urlparse = require('url').parse;
var route = require('path-match')({
  end: false,
  strict: false,
  sensitive: false
});

var defaultOptions = {
  userField: 'user',
  rootPath: '/',
  loginPath: '/login',
  logoutPath: '/logout'
};

/**
 * User auth middleware.
 *
 * @param {String|Regex|Function(pathname, ctx)} match, detect which url need to check user auth.
 * @param {Object} options
 *  - {Function(url, rootPath)} loginURLFormater, format the login url.
 *  - {String} [rootPath], custom app url root path, default is '/'.
 *  - {String} [loginPath], default is '/login'.
 *  - {String} [loginCallbackPath], default is `options.loginPath + '/callback'`.
 *  - {String} [logoutPath], default is '/logout'.
 *  - {String} [userField], logined user field name on `this.session`, default is 'user', `this.session.user`.
 *  - {Function* (ctx)} getUser, get user function, must get user info with `req`.
 *  - {Function* (ctx, user)} [loginCallback], you can handle user login logic here,return [user, redirectUrl]
 *  - {Function(ctx)} [loginCheck], return true meaning logined. default is `true`.
 *  - {Function* (ctx, user)} [logoutCallback], you can handle user logout logic here.return redirectUrl
 * @return {Function* (next)} userauth middleware
 * @public
 */

module.exports = function (options) {
  // userauth(match, options)
  if (arguments.length === 2) {
    options = arguments[1];
    options.match = arguments[0];
  }

  copy(defaultOptions).to(options);

  options.loginCallbackPath = options.loginCallbackPath
    || options.loginPath + '/callback';

  if (options.rootPath !== '/') {
    options.loginPath = path.join(options.rootPath, options.loginPath);
    options.logoutPath = path.join(options.rootPath, options.logoutPath);
    options.loginCallbackPath = path.join(options.rootPath, options.loginCallbackPath);
  }

  options.loginURLFormater = options.loginURLFormater || options.loginURLForamter;
  options.getUser = options.getUser;
  options.redirectHandler = options.redirectHandler || defaultRedirectHandler;

  var match = options.match;
  var ignore = options.ignore;

  // need login checker
  var needLogin = match;
  if (is.string(match)) {
    needLogin = route(match);
  } else if (is.regExp(match)) {
    needLogin = function (path) {
      return match.test(path);
    };
  }

  if (!is.function(needLogin)) {
    if (is.string(ignore)) {
      var pathMatch = route(ignore);
      needLogin = function (path) {
        return !pathMatch(path);
      };
    } else if (is.regExp(ignore)) {
      needLogin = function (path) {
        return !ignore.test(path);
      };
    } else if (is.function(ignore)) {
      needLogin = function (path) {
        return !ignore(path);
      };
    } else {
      // ignore all
      needLogin = function () {};
      debug('ignore all paths');
    }
  }

  options.loginCallback = options.loginCallback || defaultLoginCallback;
  options.logoutCallback = options.logoutCallback || defaultLogoutCallback;
  options.loginCheck = options.loginCheck || defaultLoginCheck;

  var loginHandler = login(options);
  var loginCallbackHandler = loginCallback(options);
  var logoutHandler = logout(options);

  /**
   * login flow:
   *
   * 1. unauth user, redirect to `$loginPath?redirect=$currentURL`
   * 2. user visit `$loginPath`, redirect to `options.loginURLFormater()` return login url.
   * 3. user visit $loginCallbackPath, handler login callback logic.
   * 4. If user login callback check success, will set `req.session[userField]`,
   *    and redirect to `$currentURL`.
   * 5. If login check callback error, next(err).
   * 6. user visit `$logoutPath`, set `req.session[userField] = null`, and redirect back.
   */

  return function* userauth(next) {
    var loginRequired = !!needLogin(this.path);
    debug('url: %s, path: %s, loginPath: %s, session exists: %s, login required: %s',
      this.url, this.path, options.loginPath, !!this.session, loginRequired);

    if (!this.session) {
      debug('this.session not exists');
      // ignore not match path
      if (!loginRequired) {
        debug('not match needLogin path, %j', this.path);
        return yield* next;
      }
      debug('relogin again');
      return yield* loginHandler.call(this, next);
    }

    // get login path
    if (this.path === options.loginPath) {
      debug('match login path');
      return yield* loginHandler.call(this, next);
    }

    // get login callback
    if (this.path === options.loginCallbackPath) {
      debug('match login clalback path');
      return yield* loginCallbackHandler.call(this, next);
    }

    // get logout
    if (this.path === options.logoutPath) {
      debug('match logout path');
      return yield* logoutHandler.call(this, next);
    }

    // ignore not match path
    if (!loginRequired) {
      debug('ignore %j', this.path);
      return yield* next;
    }

    if (this.session[options.userField]
      && options.loginCheck(this)) {
      // 4. user logined, next() handler
      debug('already logined');
      return yield* next;
    }

    // try to getUser directly
    var user;
    try {
      user = yield* options.getUser(this);
    } catch (err) {
      console.error('[koa-userauth] options.getUser error: %s', err.stack);
    }

    if (!user) {
      debug('can not get user');
      var ctx = this;

      // make next handle a generator
      // so it can use yield* next in redirectHandle
      var nextHandler = (function* () {
        var redirectURL = ctx.url;
        try {
          redirectURL = encodeURIComponent(redirectURL);
        } catch (e) {
          // URIError: URI malformed
          // use source url
        }
        var loginURL = options.loginPath + '?redirect=' + redirectURL;
        debug('redirect to %s', loginURL);
        redirect(ctx, loginURL);
      })();

      return yield* options.redirectHandler.call(this, nextHandler);
    }

    debug('get user directly');
    var res = yield* options.loginCallback(this, user);
    var loginUser = res[0];
    var redirectURL = res[1];
    this.session[options.userField] = loginUser;
    if (redirectURL) {
      return redirect(this, redirectURL);
    }
    yield* next;
  };
};

function* defaultRedirectHandler(nextHandler) {
  yield* nextHandler;
}

function* defaultLoginCallback(ctx, user) {
  return [user, null];
}

function* defaultLogoutCallback(ctx, user, callback) {
  return null;
}

function defaultLoginCheck() {
  return true;
}

/**
 * Send redirect response.
 *
 * @param  {Context} ctx
 * @param  {String} url, redirect URL
 * @param  {Number|String} status, response status code, default is `302`
 *
 * @api public
 */

function redirect(ctx, url, status) {
  if (ctx.accepts('html', 'json') === 'json') {
    ctx.set('Location', url);
    ctx.status = 401;
    ctx.body = {
      error: '401 Unauthorized'
    };
    return;
  }
  return ctx.redirect(url, status);
}

/**
 * formate referer
 * @param {Context} ctx
 * @param {String} pathname
 * @param {String} rootPath
 * @return {String}
 *
 * @api private
 */

function formatReferer(ctx, pathname, rootPath) {
  var query = ctx.query;
  var referer = query.redirect || ctx.get('referer') || rootPath;
  if (referer[0] !== '/') {
    // ignore protocol://xxx/abc
    referer = rootPath;
  } else if (referer.indexOf(pathname) >= 0) {
    referer = rootPath;
  }
  return referer;
}

/**
 * return a loginHandler
 * @param {Object} options
 * @return {Function}
 */

function login(options) {
  var protocol = options.protocol || 'http';
  var defaultHost = options.host;
  return function* loginHandler() {
    var loginCallbackPath = options.loginCallbackPath;
    var loginPath = options.loginPath;
    // this.session should be exists
    if (this.session) {
      this.session.userauthLoginReferer = formatReferer(this, loginPath, options.rootPath);
      debug('set loginReferer into session: %s', this.session.userauthLoginReferer);
    }

    var host = defaultHost || this.host;
    var currentURL = protocol + '://' + host + loginCallbackPath;
    var loginURL = options.loginURLFormater(currentURL, options.rootPath);
    debug('login redrect to loginURL: %s', loginURL);
    redirect(this, loginURL);
  };
}

/**
 * return a loginCallbackHandler
 * @param {Object} options
 * @return {Function}
 */

function loginCallback(options) {
  return function *loginCallbackHandler() {
    var referer = this.session.userauthLoginReferer || options.rootPath;
    debug('loginReferer in session: %j', this.session.userauthLoginReferer);
    var user = this.session[options.userField];
    if (user) {
      // already login
      return redirect(this, referer);
    }
    user = yield* options.getUser(this);
    if (!user) {
      return redirect(this, referer);
    }

    var res = yield *options.loginCallback(this, user);
    var loginUser = res[0];
    var redirectURL = res[1];
    this.session[options.userField] = loginUser;
    if (redirectURL) {
      referer = redirectURL;
    }
    redirect(this, referer);
  };
}

/**
 * return a logoutHandler
 * @param {Object} options
 * @return {Function}
 */

function logout(options) {
  return function* logoutHandler() {
    var referer = formatReferer(this, options.logoutPath, options.rootPath);
    var user = this.session[options.userField];
    if (!user) {
      return redirect(this, referer);
    }

    var redirectURL = yield* options.logoutCallback(this, user);

    this.session[options.userField] = null;
    if (redirectURL) {
      referer = redirectURL;
    }
    redirect(this, referer);
  };
}
<commit_msg>feat: add ctx in needLogin<commit_after>/**!
 * koa-userauth - index.js
 *
 * Copyright(c) koajs and other contributors.
 * MIT Licensed
 *
 * Authors:
 *   dead_horse <dead_horse@qq.com>
 *   fengmk2 <fengmk2@gmail.com> (http://fengmk2.github.com)
 */

'use strict';

/**
 * Module dependencies.
 */

var debug = require('debug')('koa-userauth');
var path = require('path');
var is = require('is-type-of');
var copy = require('copy-to');
var urlparse = require('url').parse;
var route = require('path-match')({
  end: false,
  strict: false,
  sensitive: false
});

var defaultOptions = {
  userField: 'user',
  rootPath: '/',
  loginPath: '/login',
  logoutPath: '/logout'
};

/**
 * User auth middleware.
 *
 * @param {String|Regex|Function(pathname, ctx)} match, detect which url need to check user auth.
 * @param {Object} options
 *  - {Function(url, rootPath)} loginURLFormater, format the login url.
 *  - {String} [rootPath], custom app url root path, default is '/'.
 *  - {String} [loginPath], default is '/login'.
 *  - {String} [loginCallbackPath], default is `options.loginPath + '/callback'`.
 *  - {String} [logoutPath], default is '/logout'.
 *  - {String} [userField], logined user field name on `this.session`, default is 'user', `this.session.user`.
 *  - {Function* (ctx)} getUser, get user function, must get user info with `req`.
 *  - {Function* (ctx, user)} [loginCallback], you can handle user login logic here,return [user, redirectUrl]
 *  - {Function(ctx)} [loginCheck], return true meaning logined. default is `true`.
 *  - {Function* (ctx, user)} [logoutCallback], you can handle user logout logic here.return redirectUrl
 * @return {Function* (next)} userauth middleware
 * @public
 */

module.exports = function (options) {
  // userauth(match, options)
  if (arguments.length === 2) {
    options = arguments[1];
    options.match = arguments[0];
  }

  copy(defaultOptions).to(options);

  options.loginCallbackPath = options.loginCallbackPath
    || options.loginPath + '/callback';

  if (options.rootPath !== '/') {
    options.loginPath = path.join(options.rootPath, options.loginPath);
    options.logoutPath = path.join(options.rootPath, options.logoutPath);
    options.loginCallbackPath = path.join(options.rootPath, options.loginCallbackPath);
  }

  options.loginURLFormater = options.loginURLFormater || options.loginURLForamter;
  options.getUser = options.getUser;
  options.redirectHandler = options.redirectHandler || defaultRedirectHandler;

  var match = options.match;
  var ignore = options.ignore;

  // need login checker
  var needLogin = match;
  if (is.string(match)) {
    needLogin = route(match);
  } else if (is.regExp(match)) {
    needLogin = function (path) {
      return match.test(path);
    };
  }

  if (!is.function(needLogin)) {
    if (is.string(ignore)) {
      var pathMatch = route(ignore);
      needLogin = function (path) {
        return !pathMatch(path);
      };
    } else if (is.regExp(ignore)) {
      needLogin = function (path) {
        return !ignore.test(path);
      };
    } else if (is.function(ignore)) {
      needLogin = function (path) {
        return !ignore(path);
      };
    } else {
      // ignore all
      needLogin = function () {};
      debug('ignore all paths');
    }
  }

  options.loginCallback = options.loginCallback || defaultLoginCallback;
  options.logoutCallback = options.logoutCallback || defaultLogoutCallback;
  options.loginCheck = options.loginCheck || defaultLoginCheck;

  var loginHandler = login(options);
  var loginCallbackHandler = loginCallback(options);
  var logoutHandler = logout(options);

  /**
   * login flow:
   *
   * 1. unauth user, redirect to `$loginPath?redirect=$currentURL`
   * 2. user visit `$loginPath`, redirect to `options.loginURLFormater()` return login url.
   * 3. user visit $loginCallbackPath, handler login callback logic.
   * 4. If user login callback check success, will set `req.session[userField]`,
   *    and redirect to `$currentURL`.
   * 5. If login check callback error, next(err).
   * 6. user visit `$logoutPath`, set `req.session[userField] = null`, and redirect back.
   */

  return function* userauth(next) {
    var loginRequired = !!needLogin(this.path, this);
    debug('url: %s, path: %s, loginPath: %s, session exists: %s, login required: %s',
      this.url, this.path, options.loginPath, !!this.session, loginRequired);

    if (!this.session) {
      debug('this.session not exists');
      // ignore not match path
      if (!loginRequired) {
        debug('not match needLogin path, %j', this.path);
        return yield* next;
      }
      debug('relogin again');
      return yield* loginHandler.call(this, next);
    }

    // get login path
    if (this.path === options.loginPath) {
      debug('match login path');
      return yield* loginHandler.call(this, next);
    }

    // get login callback
    if (this.path === options.loginCallbackPath) {
      debug('match login clalback path');
      return yield* loginCallbackHandler.call(this, next);
    }

    // get logout
    if (this.path === options.logoutPath) {
      debug('match logout path');
      return yield* logoutHandler.call(this, next);
    }

    // ignore not match path
    if (!loginRequired) {
      debug('ignore %j', this.path);
      return yield* next;
    }

    if (this.session[options.userField]
      && options.loginCheck(this)) {
      // 4. user logined, next() handler
      debug('already logined');
      return yield* next;
    }

    // try to getUser directly
    var user;
    try {
      user = yield* options.getUser(this);
    } catch (err) {
      console.error('[koa-userauth] options.getUser error: %s', err.stack);
    }

    if (!user) {
      debug('can not get user');
      var ctx = this;

      // make next handle a generator
      // so it can use yield* next in redirectHandle
      var nextHandler = (function* () {
        var redirectURL = ctx.url;
        try {
          redirectURL = encodeURIComponent(redirectURL);
        } catch (e) {
          // URIError: URI malformed
          // use source url
        }
        var loginURL = options.loginPath + '?redirect=' + redirectURL;
        debug('redirect to %s', loginURL);
        redirect(ctx, loginURL);
      })();

      return yield* options.redirectHandler.call(this, nextHandler);
    }

    debug('get user directly');
    var res = yield* options.loginCallback(this, user);
    var loginUser = res[0];
    var redirectURL = res[1];
    this.session[options.userField] = loginUser;
    if (redirectURL) {
      return redirect(this, redirectURL);
    }
    yield* next;
  };
};

function* defaultRedirectHandler(nextHandler) {
  yield* nextHandler;
}

function* defaultLoginCallback(ctx, user) {
  return [user, null];
}

function* defaultLogoutCallback(ctx, user, callback) {
  return null;
}

function defaultLoginCheck() {
  return true;
}

/**
 * Send redirect response.
 *
 * @param  {Context} ctx
 * @param  {String} url, redirect URL
 * @param  {Number|String} status, response status code, default is `302`
 *
 * @api public
 */

function redirect(ctx, url, status) {
  if (ctx.accepts('html', 'json') === 'json') {
    ctx.set('Location', url);
    ctx.status = 401;
    ctx.body = {
      error: '401 Unauthorized'
    };
    return;
  }
  return ctx.redirect(url, status);
}

/**
 * formate referer
 * @param {Context} ctx
 * @param {String} pathname
 * @param {String} rootPath
 * @return {String}
 *
 * @api private
 */

function formatReferer(ctx, pathname, rootPath) {
  var query = ctx.query;
  var referer = query.redirect || ctx.get('referer') || rootPath;
  if (referer[0] !== '/') {
    // ignore protocol://xxx/abc
    referer = rootPath;
  } else if (referer.indexOf(pathname) >= 0) {
    referer = rootPath;
  }
  return referer;
}

/**
 * return a loginHandler
 * @param {Object} options
 * @return {Function}
 */

function login(options) {
  var protocol = options.protocol || 'http';
  var defaultHost = options.host;
  return function* loginHandler() {
    var loginCallbackPath = options.loginCallbackPath;
    var loginPath = options.loginPath;
    // this.session should be exists
    if (this.session) {
      this.session.userauthLoginReferer = formatReferer(this, loginPath, options.rootPath);
      debug('set loginReferer into session: %s', this.session.userauthLoginReferer);
    }

    var host = defaultHost || this.host;
    var currentURL = protocol + '://' + host + loginCallbackPath;
    var loginURL = options.loginURLFormater(currentURL, options.rootPath);
    debug('login redrect to loginURL: %s', loginURL);
    redirect(this, loginURL);
  };
}

/**
 * return a loginCallbackHandler
 * @param {Object} options
 * @return {Function}
 */

function loginCallback(options) {
  return function *loginCallbackHandler() {
    var referer = this.session.userauthLoginReferer || options.rootPath;
    debug('loginReferer in session: %j', this.session.userauthLoginReferer);
    var user = this.session[options.userField];
    if (user) {
      // already login
      return redirect(this, referer);
    }
    user = yield* options.getUser(this);
    if (!user) {
      return redirect(this, referer);
    }

    var res = yield *options.loginCallback(this, user);
    var loginUser = res[0];
    var redirectURL = res[1];
    this.session[options.userField] = loginUser;
    if (redirectURL) {
      referer = redirectURL;
    }
    redirect(this, referer);
  };
}

/**
 * return a logoutHandler
 * @param {Object} options
 * @return {Function}
 */

function logout(options) {
  return function* logoutHandler() {
    var referer = formatReferer(this, options.logoutPath, options.rootPath);
    var user = this.session[options.userField];
    if (!user) {
      return redirect(this, referer);
    }

    var redirectURL = yield* options.logoutCallback(this, user);

    this.session[options.userField] = null;
    if (redirectURL) {
      referer = redirectURL;
    }
    redirect(this, referer);
  };
}
<|endoftext|>