During my NodeJS learning journey I found this sample code in a book (NodeJS in Practice) which uses streams to find some matches in data coming from another stream.
var Writable = require('stream').Writable;
var util = require('util');
module.exports = CountStream;
util.inherits(CountStream, Writable);
function CountStream(matchText, options) {
Writable.call(this, options);
this.count = 0;
this.matcher = new RegExp(matchText, 'ig');
}
CountStream.prototype._write = function(chunk, encoding, cb) {
var matches = chunk.toString().match(this.matcher);
if (matches) {
this.count += matches.length;
}
cb();
};
CountStream.prototype.end = function() {
this.emit('total', this.count);
};
And the code which uses the stream:
var CountStream = require('./countstream');
var countStream = new CountStream('book');
var http = require('http');
http.get('http://www.manning.com', function(res) {
res.pipe(countStream);
});
countStream.on('total', function(count) {
console.log('Total matches:', count);
});
Isn't it possible to lose some matches, if a match breaks in two chunks of data?
For example first chunk of data contain 'This a bo' and the other chunk contains 'ok of mine.' which no one has not the book independently but the whole data contains a book.
What would be the best solution to find all matches?
So, Like I explain in my comments, if you know the max length of strings matched by your regex (to compute the max length, see the very good answer at https://stackoverflow.com/a/31173778/4114922), you could cache the previous chunk and concatenate it to the new chunk.
With this method, I think you're not going to lose any match.
var Writable = require('stream').Writable;
var util = require('util');
module.exports = CountStream;
util.inherits(CountStream, Writable);
function CountStream(matchText, maxPatternLength, options) {
Writable.call(this, options);
this.count = 0;
this.matcher = new RegExp(matchText, 'ig');
this.previousCache = undefined;
this.maxPatternLength = maxPatternLength;
}
CountStream.prototype._write = function(chunk, encoding, cb) {
var text;
if(this.previousCache === undefined) {
text = chunk.toString();
}
else {
text = this.previousCache + chunk.toString();
}
var matches = text.match(this.matcher);
if (matches) {
this.count += matches.length;
}
this.previousCache = text.substring(text.length - this.maxPatternLength);
cb();
};
CountStream.prototype.end = function() {
this.emit('total', this.count);
};
Related
As the title states, I'm having trouble with Promises in Parse.
I'm struggling to firstly understand exactly how Promises themselves work, especially in Parse.
I have been stuck on this for about three weeks and the closest I've come to a solution is having an empty array returned.
What I'm trying to do is scrape a site and then create objects from the table (this is working)
Where there trouble comes in, is I am then running a for loop on the results and querying each Dam name to get the resulting objectid from the database.
Here is my code:
var c = new Crawler({
maxConnections: 10,
// This will be called for each crawled page
callback: function(err, res, done) {
if (err) {
console.log(err);
} else {
var $ = res.$;
// $ is Cheerio by default
//a lean implementation of core jQuery designed specifically for the server
console.log($("title").text());
}
done();
}
});
The Function which Creates objects from the Dom and adds them to an array:
function getDamObjects(Dom) {
var dom = Dom;
var LevelObjects = [];
for (i = 1; i < dom.length - 1; i++) {
var TableRow = dom.eq(i);
var NameString = TableRow.children().eq(0).text();
var RiverString = TableRow.children().eq(1).text();
var FSCString = TableRow.children().eq(4).text();
var ThisWeekString = TableRow.children().eq(5).text();
var LastWeekString = TableRow.children().eq(6).text();
var LastYearString = TableRow.children().eq(7).text();
NameString = NameString.replace('#', '');
NameString = NameString.replace('$', '');
NameString = NameString.replace('&', '');
NameString = NameString.replace('#', '');
ThisWeekString = ThisWeekString.replace('#', '');
ThisWeekString = ThisWeekString.replace('$', '');
ThisWeekString = ThisWeekString.replace('&', '');
ThisWeekString = ThisWeekString.replace('#', '');
LastWeekString = LastWeekString.replace('#', '');
LastWeekString = LastWeekString.replace('$', '');
LastWeekString = LastWeekString.replace('&', '');
LastWeekString = LastWeekString.replace('#', '');
LastYearString = LastYearString.replace('#', '');
LastYearString = LastYearString.replace('$', '');
LastYearString = LastYearString.replace('&', '');
LastYearString = LastYearString.replace('#', '');
var level = {};
/*
getDamObject(NameString).then(function(DamObject){
let DamID = DamObject.id;
*/
level['Dam'] = NameString; //DamID;
level['ThisWeek'] = ThisWeekString;
level['LastWeek'] = LastWeekString;
level['LastYear'] = LastYearString;
LevelObjects.push(level);
};
return LevelObjects;
};
The Get Dam Object Code:
function getDamObject(Dam) {
var promise = new Parse.Promise();
var query = new Parse.Query("DayZeroDams");
query.equalTo("Name", Dam);
query.first().then(function(DamObject) {
promise.resolve(DamObject);
}, function(error) {
promise.reject(error);
});
return promise;
}
The Cloud Code Called:
Parse.Cloud.define('jsdom', function(request, response) {
c.queue([{
uri: 'xxxxxx',
// The global callback won't be called
callback: function(err, res, done) {
if (err) {
response.error(err);
} else {
var $ = res.$;
var ResultsArray = [];
var dom = res.$('#mainContent_tw').children('tr');
return Parse.Promise.as().then(function() {
var promise = Parse.Promise.as();
var LevelObjects = getDamObjects(dom);
_.each(LevelObjects, function(DamLevel) {
promise = promise.then(function() {
var Name = DamLevel["Dam"];
var query = new Parse.Query("DayZeroDams");
query.equalTo("Name", Name);
return query.first().then(function(result) {
let damID = result.id;
ResultsArray.push(damID);
return Parse.Promise.as();
}, function(error) {
response.error(error);
});
});
});
return promise;
}).then(function() {
response.success(ResultsArray);
}, function(error) {
response.error(error);
});
//response.success(LevelObjects);
}
done();
}
}]);
});
Please take note that I am fairly novice when it comes to Javascript, I have only recently started learning it in order to work with my server code.
Convert getDamObjects into an async function and then await the result of each row, pushing it to the array:
function replaceSymbols(input) {
return input.replace(/[#\$&#]/g, '');
}
async function getDamObjects(Dom) {
const dom = Dom;
const levelObjects = [];
for (let i = 1; i < dom.length - 1; i++) {
const children = dom.eq(i).children();
const NameString = replaceSymbols(children.eq(0).text());
const RiverString = children.eq(1).text();
const FSCString = children.eq(4).text();
const ThisWeek = replaceSymbols(children.eq(5).text());
const LastWeek = replaceSymbols(children.eq(6).text());
const LastYear = replaceSymbols(children.eq(7).text());
const Dam = await getDamObject(NameString);
levelObjects.push({
Dam,
ThisWeek,
LastWeek,
LastYear,
});
}
return levelObjects;
}
Remember that now that getDamObjects is an async function, it will return a Promise that resolves to the array once iterations are complete. Consume it using await getDamObjects in another async function (or use .then)
What is the best way to target an array or a string, so that when a user types a letter it finds a match and logs the letter and its index in the array (or the string)?For example (set-up):
GUESS THIS MOVIE:
how to train your dragon
___ __ _____ ____ ______
Type a letter to guess, you have 10 TRIES:
User Typed: o
Result: _o_ _o _____ _o__ ____o_
HERES MY CODE:
var fs = require('fs');
var inquirer = require('inquirer');
var displayProgress = require('./checkGuess');
// var checkGuess = require('./checkGuess');
var PlayFunc = function() {
var blanksArr = [];
var currentWord = [];
this.getData = function() {
var stackOv = "";
fs.readFile("words.txt", "utf8", function(error, data){
if (error) throw error;
dataType = data.toLowerCase();
//data in array
var wordArr = dataType.split(',');
//select random from word from data
var compWord = wordArr[Math.floor(Math.random() * wordArr.length)];//random
//split chosen word
currentWord = compWord.split('');
console.log("========================\n\n\n");
//Looping through the word
for (var i = 0; i <= currentWord.length - 1; i++) {
// pushing blanks
var gArr = blanksArr.push("_");
//HYPHENS, COLONS, SPACES SHOULD BE PASSED
stackOv = currentWord.join("").replace(/[^- :'.]/g, "_");
wordString = currentWord.join("");
}
console.log("GUESS THIS MOVIE: ");
fs.writeFile("blanks.txt", stackOv, (err) => {
if (err) throw err;
console.log(wordString);
fs.readFile('blanks.txt', "utf8",(err, word) => {
if (err) throw err;
// console.log("GUESS THIS MOVIE: " + compWord);
blanksTxt = word.split(''); //console.log(string.join('').replace(/[^-: '.]/g, "_"));
displayProgress = new displayProgress();
displayProgress.checkGuess();
});
});
});
}
}
module.exports = PlayFunc;
ON THE NEXT FILE CALLED checkGuess.js I Plan to do the checking (which goes back to my original question (OP).
var fs = require('fs');
var inquirer = require('inquirer');
var PlayFunc = require('./PlayFunc');
var displayProgress = function (){
// console.log("WORKING CONNECTED CHECKGUESS MODULE");
// PlayFunc = new PlayFunc();
// PlayFunc.getData();
var a = blanksTxt.join(''); console.log(a); //string a
var manipulateThisArray = blanksTxt;//reading from blanks.txt
// console.log(manipulateThisArray);
this.checkGuess = function(){
inquirer.prompt([
{
type: "input",
name: "letter",
message: "Type a letter to guess, you have 10 TRIES:"
}
]).then(function(userInput) {
var correctArray = [];
// console.log(userInput.letter);
letterTyped = userInput.letter;
//logic
//test if we can parse through the array
for (var i = 0; i <= manipulateThisArray.length - 1; i++) {
x = manipulateThisArray[i]; console.log(x);
// if userinput letter-value matches chosen words letter value
// replace this chosen worsa letter with userinput value
// if(letterTyped == x.charAt(i)) {
console.log("THERES A MATCH " + x.charAt(i));
// }else {
// console.log("NO MATCH");
// }
}
});
}
}
// checkGuess();
module.exports = displayProgress;
//declaration of the variables we need
//the original string:
const string = "how to train your dragon";
//a string of characters we want to show
const show = "o";
//A resulting string we can work with
var result = "";
//Now we go over every character of our string and
for(const character of string){
//check if the character is inside the characters we want to show
if(show.includes(character)){
//if so we show it by adding it to the result
result += character;
}else{
//If not we add an _ instead
result += "_";
}
}
//At the end we can show the result
alert(result);
For this i would use the 'foo'.replace('bar', 'bar') and make sure that the String contains the input 'foo'.includes('input') also you could use a RegExp.
To get the index you could do a loop through the length of the String and use the 'foo'.indexOf('input', number) that will return -1 if no coincidence
I'm making a game with socket.io and nodejs, and I'm making a module called rooms.js, this module require users.js module and fiveSocket.js module
but when I call Rooms.New from the main server file, it says that fiveSocket is undefined, same problem when Rooms.New calls a users.js function, I got TypeError: Cannot read property 'getSocketIDbyId' of undefined
rooms.js:
var mysql = require('../mysql/mysql.js');
var headers = require('./headers.js');
var users = require('./users.js');
var fiveSocket = require('./sockets.js');
var Rooms = {
Obj: {},
Room: function(data) {
var room = this;
this.name = data.name;
this.users = [];
this.floorCode = data.floor;
this.description = data.desc;
this.maxUsers = data.maxUsers;
this.owner = data.owner;
this.setTime = new Date().getTime();
this.dbID = data.dbID;
this.doorx = data.doorx;
this.doory = data.doory;
this.doordir = data.doordir;
},
New: function(socketID, roomID) {
var keys = Object.keys(Rooms.Obj).length;
var id = keys + 1;
var callback = function(row) {
fiveSocket.emitClient(socketID, headers.roomData, {
title: row.title,
desc: row.description,
mapStr: row.floorCode,
doorx: row.doorx,
doory: row.doory,
doordir: row.doordir
});
var uid = users.getIdBySocketID(socketID);
users.Obj[uid].curRoom = roomID;
var rid = Rooms.getIdByDbID(roomID);
Rooms.Obj[rid].users.push(uid);
}
if(Rooms.getIdByDbID(roomID) != false) {
var room = Rooms.getIdByDbID(roomID);
var row = { title: room.name, description: room.description, floorCode: room.foorCode, doorx: room.doorx, doory: room.doory, doordir: room.doordir };
callback(row);
} else {
mysql.Query('SELECT * FROM rooms WHERE id = ? LIMIT 1', roomID, function(rows) {
if(rows.length > 0) {
var row = rows[0];
Rooms.Obj[id] = new Rooms.Room({name: row.title, floorCode: row.floorCode, desc: row.description, maxUsers: row.maxUsers, owner: row.owner, dbID: row.id, doorx: row.doorx, doory: row.doory, doordir: row.doordir});
callback(row);
}
});
}
},
removeUser: function(DBroomID, userID) {
var rid = Rooms.getIdByDbID(DBroomID);
var room = Rooms.Obj[rid];
var index = room.indexOf(userID);
if (index > -1) array.splice(index, 1);
},
Listener: function(users) {
setInterval(function(){
for(var roomID in Rooms.Obj) {
var room = Rooms.Obj[roomID];
// send users coordinates
room.users.forEach(function(uid) {
var socketID = users.getSocketIDbyId(uid);
var data = Rooms.getUsersInRoomData(roomID);
fiveSocket.emitClient(socketID, headers.roomUsers, data);
});
// unload inactive rooms (no users after 10 seconds)
var activeUsers = room.users.length;
var timestamp = room.setTime;
var t = new Date(); t.setSeconds(t.getSeconds() + 10);
var time2 = t.getTime();
if(activeUsers <= 0 && timestamp < time2) {
Rooms.Remove(roomID);
}
}
}, 1);
},
getUsersInRoomData: function(roomID) {
var room = Rooms.Obj[roomID];
var obj = {};
room.users.forEach(function(uid) {
var user = users.Obj[uid];
obj[uid] = {
username: user.username,
position: user.position,
figure: user.figure
};
});
return obj;
},
Remove: function(id) {
delete Rooms.Obj[id];
},
getIdByDbID: function(dbID) {
var result = null;
for(var room in Rooms.Obj) {
var u = Rooms.Obj[room];
if(u.dbID == dbID) var result = room;
}
if(result == null) return false;
else return result;
},
getDbIDbyId: function(id) {
return Rooms.Obj[id].dbID;
}
}
Rooms.Listener();
module.exports = Rooms;
EDIT: (if it can be helpful)
When I console.log fiveSocket on the main file
When I console.log fiveSocket on the rooms.js file
EDIT2: When I've removed var users = require('./users.js'); from fiveSocket, when I console.log it in rooms.js it works, why ?
EDIT3: I still have the problem
If you need the others modules sources:
Users.JS: http://pastebin.com/Ynq9Qvi7
sockets.JS http://pastebin.com/wpmbKeAA
"Rooms" requires "Users" and vice versa, so you are trying to perform "circular dependency".
Quick search for node.js require circular dependencies gives a lot of stuff, for example :
"Circular Dependencies in modules can be tricky, and hard to debug in
node.js. If module A requires('B') before it has finished setting up
it's exports, and then module B requires('A'), it will get back an
empty object instead what A may have intended to export. It makes
logical sense that if the export of A wasn't setup, requiring it in B
results in an empty export object. All the same, it can be a pain to
debug, and not inherently obvious to developers used to having those
circular dependencies handled automatically. Fortunately, there are
rather simple approaches to resolving the issue."
or
How to deal with cyclic dependencies in Node.js
I have the following module :
// vote.js
var db = require(./dirty-wrapper);
module.exports = function vote() {
var obj = {};
// increase the score
obj.inc = function(key) {
var pval = db.get(key);
if (!pval) pval = 0;
db.set(key, pval + 1);
};
// decrease the score
obj.dec = function(key) {
var pval = db.get(key);
if (!pval) pval = 0;
db.set(key, pval - 1);
};
// reset the score to 0
obj.reset = function(key) {
db.set(key, 0);
};
obj.get = function(key) {
return db.get(key);
};
return obj;
};
Which uses this simple wrapper for dirty:
// dirty-wrapper.js
var dirty = require('dirty');
var db = dirty('vote.db');
module.exports = db.on('load', function() {
var obj = {};
obj.set = function(key, val, callback) {
db.set(key, val);
return callback();
};
obj.get = function(key, callback) {
return callback(db.get(key));
};
obj.reset = function(callback) {
db.forEach(function(key, val) {
val = 0;
});
return callback();
};
return obj;
});
and this is my simple client :
// client.js
var vote = require('./vote.js')();
vote.inc('michael');
vote.inc('michael');
vote.inc('michael');
vote.inc('michael');
console.log('michael: ' + vote.get('michael')); // output = michael: 4
Problem is that when the run stops and I start the client again, The output is again michael: 4
after the second run, vote.db contains the following :
$ cat vote.db
{"key":"michael","val":1}
{"key":"michael","val":4}
{"key":"michael","val":4}
{"key":"michael","val":4}
{"key":"michael","val":1}
{"key":"michael","val":4}
{"key":"michael","val":4}
{"key":"michael","val":4}
First, would be great if someone would explain the append-only strategy. Second I would like to understand why node-dirty doesn't persists even though it writes to the disk each run.
Thanks ;)
You are using the database before it has loaded, and are essentially starting from 0 every time. module.exports runs immediately, but the load event has to wait for the disk io.
You have to wait for the file to be loaded and parsed before you get values from the db.
From the readme:
dirty event: 'load' (length)
Emitted once the database file has finished loading. It is not safe to access records before this event fires. Writing records however should be fine.
You can remove the dirty-wrapper file, and just use vote as the wrapper. Make a minor change to how you are using it, keeping in mind that you have to wait for the load event.
Your vote interface:
// vote.js
var events = require('events');
var dirty = require('dirty');
var db = dirty('vote.db');
var obj = new events.EventEmitter();
// increase the score
obj.inc = function(key) {
var pval = db.get(key);
if (!pval) pval = 0;
db.set(key, pval + 1);
};
// decrease the score
obj.dec = function(key) {
var pval = db.get(key);
if (!pval) pval = 0;
db.set(key, pval - 1);
};
// reset the score to 0
obj.reset = function(key) {
db.set(key, 0);
};
obj.get = function(key) {
return db.get(key);
};
db.on('load', function() {
obj.emit('load');
});
module.exports = obj;
And your main script:
// client.js
var vote = require('./vote.js');
vote.on('load', function() {
vote.inc('michael');
vote.inc('michael');
vote.inc('michael');
vote.inc('michael');
console.log('michael: ' + vote.get('michael')); // output = michael: 4
});
This will output 4 more votes every run.
alert(line) alerts 'ac'
typeof(line) is 'string'
When I run line.charAt(0), charAt is not a function.
When line is 'http://www.google.com/', it works,
I think it's the UTF-8 encoding of the file that I opened...
How to make charAt work with UTF-8?
UPDATED:
http://mxr.mozilla.org/mozilla-central/source/netwerk/dns/src/effective_tld_names.dat?raw=1 is in my extension's chrome folder as effective_tld_names.dat
To run the code:
authority = 'orkut.com.br';
lines = sc_geteffectivetldnames();
lines = sc_preparetouse(lines);
domainname = sc_extractdomainname(authority, lines);
The code:
function sc_geteffectivetldnames () {
var MY_ID = "my#email.com";
var em = Components.classes["#mozilla.org/extensions/manager;1"].
getService(Components.interfaces.nsIExtensionManager);
var file = em.getInstallLocation(MY_ID).getItemFile(MY_ID, "chrome/effective_tld_names.dat");
var istream = Components.classes["#mozilla.org/network/file-input-stream;1"].
createInstance(Components.interfaces.nsIFileInputStream);
istream.init(file, 0x01, 0444, 0);
istream.QueryInterface(Components.interfaces.nsILineInputStream);
var line = {}, lines = [], hasmore;
do {
hasmore = istream.readLine(line);
lines.push(line.value);
} while(hasmore);
istream.close();
return lines;
}
function sc_preparetouse(lines) {
lines = sc_notcomment(lines);
lines = sc_notempty(lines);
return lines;
}
function sc_notcomment(lines) {
var line;
var commentre;
var matchedcomment;
var replacedlines;
replacedlines = new Array();
var i = 0;
while (i < lines.length) {
line = lines[i];
commentre = new RegExp("^//", 'i');
matchedcomment = line.match(commentre);
if(matchedcomment) {
lines.splice(i, 1);
} else {
i++;
}
}
return lines;
}
function sc_notempty(lines) {
var line;
var emptyre;
var matchedempty;
var replacedlines;
replacedlines = new Array();
var i = 0;
while (i < lines.length) {
line = lines[i];
emptyre = new RegExp("^$", 'i');
matchedempty = line.match(emptyre);
if(matchedempty) {
lines.splice(i, 1);
} else {
i++;
}
}
return lines;
}
function sc_extractdomainname(authority, lines) {
for (var i = 0; i < lines.length; i++) {
line = lines[i];
alert(line);
alert(typeof(line));
if (line.chatAt(0) == '*') {
alert('test1');
continue;
}
if (line.chatAt(0) == '!') {
alert('test2');
line.chatAt(0) = '';
}
alert('test3');
checkline = sc_checknotasteriskline(authority, line);
if (checkline) {
domainname = checkline;
}
}
if (!domainname) {
for (var i = 0; i < lines.length; i++) {
line = lines[i];
alert(line);
if (line.chatAt(0) != '*') {
alert('test4');
continue;
}
if (line.chatAt(0) == '!') {
alert('test5');
line.chatAt(0) = '';
}
alert('test6');
checkline = sc_checkasteriskline(authority, line);
if (checkline) {
domainname = checkline;
}
}
}
return domainname;
}
It alerts 'ac', then 'string', then nothing.
UPDATED:
I'm thinking there is a difference between files opened with nsIExtensionManager and NSIIOService, because that real code doesn't work, but this test code works:
function makeURI(aURL, aOriginCharset, aBaseURI) {
var ioService = Components.classes["#mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
return ioService.newURI(aURL, aOriginCharset, aBaseURI);
}
URL = makeURI('file://C:/test/TLDs.dat');
// URL is a nsIURI; see nsIIOService::newURI for getting a string into a nsIURI.
var file = URL.QueryInterface(Components.interfaces.nsIFileURL).file;
// file is now a nsIFile
// open an input stream from file
var istream = Components.classes["#mozilla.org/network/file-input-stream;1"].
createInstance(Components.interfaces.nsIFileInputStream);
istream.init(file, 0x01, 0444, 0);
istream.QueryInterface(Components.interfaces.nsILineInputStream);
// read lines into array
var line = {}, lines = [], hasmore;
do {
hasmore = istream.readLine(line);
lines.push(line.value);
} while(hasmore);
istream.close();
// do something with read data
lines[0].charAt(0);
It's hard to tell what's going on without seeing any code, but remember that not all properties that evaluate as strings are really strings. A good example of this is the location object. Use of the object on its own will give you a string value, but you can't use any methods that are available to native strings on that string value.
// Although `window.location` returns a string, you cannot use String methods on it
alert(window.location.charAt(0)); // error
alert(window.location.href.charAt(0)); // no error
The same could be true of strings provided by external interfaces, such as plugins or ActiveX controls. The solution to this problem is to cast to a native string:
alert((""+window.location).charAt(0)); // auto casting with concatenation
alert(String(window.location).charAt(0)); // with the String() constructor
alert(window.location.toString().charAt(0)); // with toString()
At least the first two of those methods should solve your problem (replace window.location with your var). If not, try posting some code so we can get a better idea of what's happening.
Looking at your code, I can only assume that what I said above is correct. The readLine method returns a line object that contains the non-native string property value (which is rather odd, considering). I would suggest editing your code to look like this:
function sc_geteffectivetldnames () {
var MY_ID = "my#email.com";
var em = Components.classes["#mozilla.org/extensions/manager;1"].
getService(Components.interfaces.nsIExtensionManager);
var file = em.getInstallLocation(MY_ID).getItemFile(MY_ID, "chrome/effective_tld_names.dat");
var istream = Components.classes["#mozilla.org/network/file-input-stream;1"].
createInstance(Components.interfaces.nsIFileInputStream);
istream.init(file, 0x01, 0444, 0);
istream.QueryInterface(Components.interfaces.nsILineInputStream);
var line = {}, lines = [], hasmore;
do {
hasmore = istream.readLine(line);
lines.push(String(line.value)); // <--- or ""+line.value
} while(hasmore);
istream.close();
return lines;
}
I found URI Parsing for Firefox in MDC.
https://developer.mozilla.org/en/Code_snippets/URI_parsing
Somehow, it's not appearing on Google.