I'm trying to create a little Node.js app that can read Markdown text and converts it to HTML. To achieve this, I wanted to create a transform stream that would get a char at a time, figure the meaning, then return the HTML version of it.
So, for example, if I pass the transform stream a *, it should return <b>(or </b>).
However, the Transform stream does not actually transform data, whatever I push to it, it just comes back like I pushed it, and when I put a console.log statement into the transform method of the stream, I saw no output, as if the method isn't even called.
Here's the file with the Stream:
module.exports = function returnStream() {
const Transform = require('stream').Transform;
let trckr = {
bold: false
};
const compiler = new Transform({
transform(chunk, encoding, done) {
const input = chunk.toString();
console.log(input); // No output
let output;
switch (input) {
case '*':
if (trckr.bold === true) {
output = '</b>';
trckr.bold = false;
} else {
output = '<b>';
trckr.bold = true;
}
break;
default:
output = input;
}
done(null, output);
}
});
return compiler;
};
Example file that uses the Stream:
const transformS = require('./index.js')();
transformS.on('data', data => {
console.log(data.toString());
});
transformS.push('*');
Thanks!
done(null, output) and transformS.push() are performing the exact same function: they push data to the readable (the output) side of the Transform stream. What you need to do instead of calling transformS.push() is to write to the writable (the input) side of the Transform stream with transformS.write('*').
I should also point out that you should not make assumptions about the contents of chunk in your transform function. It could be a single character or a bunch of characters (in which case input may never equal '*').
Related
I'm doing an API for a gallery; so, I create a method that let copy an image from the database.
Now, I want to add a number at the end of the copy-image name. For example:
-original image name: image
-copy image name: image(1)
-2nd copy image name: image(2)
How can I add the number to the name of copied name automatically?
'use strict'
let imageObject= require('../models/image-object');
let fs=require('fs');
let path= require('path');
let gallery_controllers={
copyImage:function(req,res){
//get the id param of the image to copy
let imageId=req.params.id;
if(imageId==null) return res.status(404).send({message:"no ID defined"});
//I search the requiere image on the database
imageObject.findById(imageId,(err,image)=>{
if(err) return res.status(500).send({message:'err to response data'});
if(!image) return res.status(404).send({message:'image not found'});
if(image){
//set a new model-object
let imageCopied= new imageObject();
imageCopied.name= image.name;
imageCopied.image=image.image;
//save image copied on the database
imageCopied.save((err,image_copied)=>{
if(err) return res.status(500).send({message:"error 500"});
if(!image_copied) return res.status(404).send({message:"error 404"});
return res.status(200).send({
image:image_copied
})
})
}
})
},
}
Here's a function that looks in the directory passed to it for files of the name file(nnn) where nnn is some sequence of digits and returns back to you the full path of the next one in sequence (the one after the highest number that already exists).
This function pre-creates a placeholder file by that name to avoid concurrency issues with multiple asynchronous operations calling this function and potentially conflicting (if it only returned the filename without creating the file). To further handle conflicts, it creates the placeholder file in a mode that fails if it already exists (so only one invocation of this function will ever create that particular file) and it automatically retries to find a new number if it gets a conflict (e.g. someone else created the next available file before we got to it). All of this logic is to avoid the subtleties of possible race conditions in creating the next filename in the sequence.
Once the caller has a unique filename that this resolves to, then it is expected that they will overwrite the placeholder contents with their own contents.
// pass directory to look in
// pass base file name so it will look for next in sequence as in "file(3)"
// returns the full path of the unique placeholder file it has created
// the caller is then responsible for that file
// calling multiple times will create a new placeholder file each time
async function findNextName(dir, base) {
let cntr = 0;
const cntr_max = 5;
const regex = new RegExp(`^${base}\\((\\d+)\\)$`);
async function run() {
const files = await fs.promises.readdir(dir);
let highest = 0;
for (let f of files) {
let matches = f.match(regex);
if (matches) {
let num = parseInt(matches[1]);
if (num > highest) {
highest = num;
}
}
}
let name = `${base}(${highest + 1})`;
// create placeholder file with this name to avoid concurrency issues
// of another request also trying to use the same next file
try {
// write to file, fail if the file exists due to a concurrency issue
const fullPath = path.resolve(path.join(dir, name));
await fs.promises.writeFile(fullPath, "placeholder", { flag: "wx" });
return fullPath;
} catch (e) {
// if this fails because of a potential concurrency issue, then try again
// up to cntr_max times to avoid looping forever on a persistent error
if (++cntr < cntr_max) {
return run();
} else {
throw e;
}
}
}
return run();
}
You could call it like this:
findNextName(".", "file").then(filename=> {
console.log(filename);
}).catch(err => {
console.log(err);
});
I'm building a Discord bot which will allow users to create custom commands.
It works in this way, user enters "!addcommand !commandname:command value". The program will then split the string, and add !commandname:command value to a txt file. Whenever somebody types !commandname into discord, the bot will then output "command value" into chat.
The program is supposed to check if the new command exists whenever an if statement is triggered. However, this only seems to be checking the first time the program is run, which causes new commands to not be recognized unless the program is restarted.
Note:
Client.on listens to the channel, and contents are run every time someone says something in chat.
!addcommand appears to be functioning correctly, and I'm able to confirm the lines are being written to the file as intended.
I don't know what else to try.
Main file:
//Assume that requires etc are included
client.on('message', message => {
const pingCheck = message.content.charAt(0);
const commandCheck = message.content.split(" ");
if (pingCheck === "!") {
//Populates the list of custom commands. Must be done on every check, or new commands will not be recognized.
//Currently, this seems to only update once the service/program is restarted
var commandList = customCommands.returnPhrase();
//If the commandList object contains the correct key (stored in commandCheck[0]) such as !commandname, the bot will send the value "command value" as a string to the discord chat.
if (commandList.hasOwnProperty(commandCheck[0])) {
message.channel.send(commandList[commandCheck[0]]);
}
//If the key does not exist, the program then checks predefined commands. Other commands exist here, but for the purposes of this question I'll show only the !addcommand, which is used to create a new custom command.
else {
switch (commandCheck[0]) {
case "!addcommand":
//This checks that the command is formatted properly, "!commandname:commandvalue". If it does not start with ! or contain : somewhere in the string, it's probably an invalid format.
//Technically this still allows for a !:commandvalue format. I haven't implemented a check for this yet.
if (commandCheck[1].startsWith("!") && commandCheck[1].includes(":")) {
//While loop reconstructs the command key to be passed in, ignores slot 0 as this is the !addcommand
var gs = "";
var x = 1;
while (x < commandCheck.length) {
gs += gs +commandCheck[x] + " ";
x++;
}
gs = gs.slice(0,-1)+"\r\n"; //removes the last " " from the input string, and adds line-break
addCommands.addPhrase(gs);//passes reconstructed command to be added to commandlist.txt
message.channel.send("I have added " + commandCheck[1] + " to the command list.");
break;
}
default:
message.channel.send("I dont recognize that command.");
}
}
}
});
Module which adds commands:
const fs = require('fs');
var createCommand = {
addPhrase: function(x) {
fs.appendFile("commandlist.txt", x, function(err){
if(err) throw err;
console.log(err)
});
}
}
module.exports = createCommand;
Module which populates list of custom commands:
const fs = require('fs');
var commandFile = fs.readFileSync('commandlist.txt','utf8');
var dataSplit = commandFile.split("\r\n");
var readCommand = {
returnPhrase: function(){
var splitSplit = {};
var i = 0;
//populates splitSplit with keys and values based on text file
while (i<dataSplit.length){
var newKey = dataSplit[i].split(':');
splitSplit[newKey[0]] = newKey[1];
i++
};
return splitSplit;
},
};
module.exports = readCommand;
Better readability: https://repl.it/repls/DarkvioletDeafeningAutomaticparallelization
Expected: commandList is populated everytime if statement is triggered
Actual: commandList populates first time statement is triggered
You write to the file whenever a new command comes in, however you only read once from it when the server starts, so you won't keep track of changes (untill you restart the server which will read the file again). Now you could theoretically listen for filechanges and reload then, but that is overcomplicating things, the filesystem is not meant to achieve that. Instead, just keep your commands in an object and export some methods for adding / checking:
let commands = {};
// ¹
module.exports = {
addCommand(key, value) {
commands[key] = value;
// ²
},
getValue(key) {
return commands[key];
}
};
Now when you add a command, it directly gets added to the object, and that can then directly be read out.
Now as objects aren't persisted accross restarts, you will lose all commands then. But that is easy to fix: You could just reflect the object to a file whenever it updates, and then load it on every start. Instead of creating a custom format for that, I'd just use JSON. The code above can easily be extended:
// ¹
try {
commands = JSON.parse( fs.readFileSync("commands.txt") );
} catch(e) { /* ignore if file doesnt exist yet */ }
// ²
fs.writeFile("commands.txt", JSON.stringify(commands), err => {
if(err) console.error(err);
});
How I would write the bot:
const playerCommands = require("./commands.js");
const defaultCommands = {
addCommand(...pairs) {
let result = "";
for(const pair of pairs) {
const [name, value] = pair.split(":");
playerCommands.addCommand(name, value);
result += `${name} added with value ${value}\n`;
}
return result;
}
};
client.on('message', ({ content, channel }) => {
if(content[0] !== "!") return; // ignore non-commandd
const [commandName, ...args] = content.slice(1).split(" ");
if(defaultCommands[commandName])
return channel.send(defaultCommands[commandName](...args));
const value = playerCommands.getValue(commandName);
if(value) return channel.send(value);
return channel.send("Command not found!");
});
I'm currently searching for a way to create a JSON file (versions.json) with a key and a value from an object within JavaScript. To create the JSON file, I've this object here:
["V1_config-interfaces.json","V2_config-interfaces.json","V3_config-interfaces.json","versions.json"]
I need to loop now some way over this object and check if the current file is not the versions.json because this is the created file.
The JSON file must looks like this:
{
"V1": "V1_config-interfaces.json",
"V2": "V2_config-interfaces.json",
"V3": "V3_config-interfaces.json"
}
So the key is always the version number before the underscore. What I've tried is this here:
const fs = require('fs');
const interfaces = fs.readdirSync('./src/interfaces/');
fs.writeFile('./src/interfaces/versions.json', JSON.stringify(interfaces), (err) => {
if (err) throw err;
console.log('versions.js successfully created');
});
But this generates the same result like the object looks like. So how can I reach my goals?
Use Array#reduce and regex. This strips the file version and adds it as a key to your object and ignores anything that doesn't have a version number. It also checks if the version has _ character following immediately after.
const data = ["V1_config-interfaces.json","V2_config-interfaces.json","V3_config-interfaces.json","versions.json", "V4shouldntwork.json", "shouldntwork_V5_.json", "V123_shouldwork.json"];
const res = data.reduce((a,v)=>{
const version = v.match(/^V[1-9]+(?=_)/);
if(version === null) return a;
a[version.shift()] = v;
return a;
}, {});
console.log(res);
Hi people and happy holidays!
I'm trying to consume a stream of csv rows with highland. To do some special processing and avoid passing down the headers to the stream, I'm calling .consume() and then I wanted the outcome in an array. The problem is that the callback of .toArray() is never called. I'm just curious about this as I already changed my solution to .map().toArray() but I still think .consume() would be a more elegant solution. ;) This is the piece of code that reads from a csv file and processes the rows:
const fs = require('fs');
const _ = require('highland');
const dataStream = fs.createReadStream('file.csv', 'utf8');
_(dataStream)
.split()
.consume((err, row, push, next) => {
console.log('consuming a row', row); // <-- this shows data
if (err) {
push(err);
return next();
}
// This condition is needed as .consume() passes an extra {} at the end of the stream <-- donno what this happens!!
if (typeof row !== 'string') {
return next();
}
const cells = row.split(',');
if (cells[0] === 'CODE') { // <-- header row
const { columnNames, columnIndexes } = processHeader(cells)); // <-- not relevant, works okay
return next();
}
console.log('processin content here', processRow(cells)); // <-- not relevant, works okay
push(null, processRow(cells));
return next();
})
.toArray(rows => console.log('ROWS: ', rows)); // <-- I don't get anything here
Thanks a lot!
Both consume and toArray consume a stream. You can only consume a stream once. If you try to consume it multiple times the "stream" will be empty.
I'm trying to get away from using horrible switch cases in node.js. I am looking for a more efficient way of testing an input against various regex cases. Dependent on the case that is matched I either fire an event or I do some transformation of the input before running another function.
To save having a really long block of code I have cut down my function to the skeleton below so it shows a focus on the switch.
I've taken a look at the possibility of using .map to return a true false but I'm unsure how best to implement that also.
Any advise or suggestions on the best way to do this?
function ParseLogMessages(message, config, callback){
var _this = this;
try {
//Define regex in order to match strings based on case
_this.to_group = new RegExp("^\\[\\d{2}:\\d{2}\\]\\s+\\w+\\s+tg+\\s\\>{3}");
_this.from_group=new RegExp("^\\[\\d\\d:\\d\\d\\]\\s\\w+\\s\\w+\\s\\>{3}");
_this.to_person = new RegExp("^\\[\\d{2}:\\d{2}\\]\\s[a-zA-Z0-9 \\- _]+\\s\\<{3}.+");
_this.from_person = new RegExp("^\\[\\d{2}:\\d{2}\\]\\s\\w+\\s\\>{3}");
_this.contact = new RegExp("(User #+\\d+:)");
_this.contact = new RegExp("(User #+\\d+:)");
//Test message against each to find type
switch (true){
//Message sent to a group chat
case _this.to_group.test(_this.payload.raw):
break;
//Message from a group chat
case _this.from_group.test(_this.payload.raw):
break;
//Message sent to a person from the bot
case _this.to_person.test(_this.payload.raw):
break;
//Message sent from a person to the bot
case _this.from_person.test(_this.payload.raw):
break;
//Contact shared
case _this.contact.test(_this.payload.raw):
break;
default:
break;
}
callback(null,"Logfile message parsed ok!");
} catch(err) {
log.error(err);
return callback(err,null);
}
}
You can create an array of regex/function pairs and loop through the array:
_this.tests = [
{ regex: new RegExp("^\\[\\d{2}:\\d{2}\\]\\s+\\w+\\s+tg+\\s\\>{3}"), // to_group
action: ... // action for to_group
},
{ regex : new RegExp("^\\[\\d\\d:\\d\\d\\]\\s\\w+\\s\\w+\\s\\>{3}"), // from_group
action: ... // action for from_group
},
// etc.
];
Then you can loop through the array, testing, and breaking when the test works:
for (i=0; i<tests.length; ++i) {
if (tests[i].regex.test(_this.payload.raw) {
tests[i].action();
break;
}
}
You can put the objects in an array and call the test function until one returns true:
var o = [
_this.to_group,
_this.from_group,
_this.to_person,
_this.from_person,
_this.contact
];
for (var i in o) {
if (o[i].test(_this.payload.raw)) {
// got a match
break;
}
}
What you want is to convert that into an associative array and match with a loop. Untested code that should work:
let patterns = {
"^\\[\\d{2}:\\d{2}\\]\\s+\\w+\\s+tg+\\s\\>{3}": funcToGroup /* code for this case, preferably a [reference to a] function object without the parens */,
"^\\[\\d\\d:\\d\\d\\]\\s\\w+\\s\\w+\\s\\>{3}": function () {
// An inline anonymous function is also fine
},
"^\\[\\d{2}:\\d{2}\\]\\s[a-zA-Z0-9 \\- _]+\\s\\<{3}.+": funcToPerson,
"^\\[\\d{2}:\\d{2}\\]\\s\\w+\\s\\>{3}": funcFromPerson,
"(User #+\\d+:)": funcContactShared
};
for (let pat in _this.patterns) {
if (new RegExp(pat).test(_this.payload.raw)) {
_this.patterns[pat](); // Actually execute the relevant case
}
}
That should handle all the code within the try block.