I am creating an extension for simple git commands, and when a user enters a command in the Command Palette, like Init, I want to call git init on their current directory.
Unfortunately, there is no documentation on executing code locally with the VSCode extensions API. Is there any way to do this?
Yes, this is possible, by using child_process.spawn. I have used it in my extension to run a Java jar. The core of the execution is shown here:
let spawnOptions = { cwd: options.baseDir ? options.baseDir : undefined };
let java = child_process.spawn("java", parameters, spawnOptions);
let buffer = "";
java.stderr.on("data", (data) => {
let text = data.toString();
if (text.startsWith("Picked up _JAVA_OPTIONS:")) {
let endOfInfo = text.indexOf("\n");
if (endOfInfo == -1) {
text = "";
} else {
text = text.substr(endOfInfo + 1, text.length);
}
}
if (text.length > 0) {
buffer += "\n" + text;
}
});
java.on("close", (code) => {
// Handle the result + errors (i.e. the text in "buffer") here.
}
Related
I have this simple code
const os = require('os')
const pty = require('node-pty')
const process = require('process')
const { msleep } = require('sleep');
const { readFileSync, writeFileSync } = require('fs');
const { exit } = require('process');
const usage = `Usage: term-record [OPTION]
OPTION:
play [Filename] Play a recorded .json file
record [Filename] Record your terminal session to a .json file`
var shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash'
var lastRecordTimestamp = null
var recording = []
var args = process.argv
args.splice(0, 2)
function getDuration() {
var now = new Date().getMilliseconds()
var duration = now - lastRecordTimestamp
lastRecordTimestamp = new Date().getMilliseconds()
return duration
}
function play(filename) {
try {
var data = readFileSync(filename, { encoding: 'utf8', flag: 'r'})
} catch (err) {
if (err.code == 'ENOENT') {
console.error("Error: File Not Found!")
exit(1)
} else {
console.error(err)
exit(1)
}
}
try {
data = JSON.parse(data)
} catch (err) {
console.error("Error: Invalid File!");
exit(1)
}
console.log("------------ STARTING ------------");
for (let i = 0; i < data.length; i++) {
process.stdout.write(data[i].content);
msleep(data[i].delay)
}
console.log("-------------- END ---------------");
}
function record(filename) {
var ptyProcess = pty.spawn(shell, [], {
name: 'TermRecord Session',
cols: process.stdout.columns,
rows: process.stdout.rows,
cwd: process.env.HOME,
env: process.env
});
process.stdout.setDefaultEncoding('utf8');
process.stdin.setEncoding('utf8')
process.stdin.setRawMode(true)
process.stdin.resume();
ptyProcess.on('data', function(data) {
process.stdout.write(data)
var duration = getDuration();
if (duration < 5) {
duration = 100
}
recording.push({
delay: Math.abs(duration),
content: data
});
});
ptyProcess.on('exit', () => {
process.stdin.setRawMode(false);
process.stdin.pause()
recording[0].delay = 1000
try {
writeFileSync(filename, JSON.stringify(recording, null, '\t')); // JSON.stringify(recording, null, '\t') For Tabs
} catch (err) {
console.log(err);
}
})
var onInput = ptyProcess.write.bind(ptyProcess)
process.stdin.on('data', onInput)
}
if (args.length === 2) {
var file = args[1]
if (args[0] == "record") {
console.info("Setting App Mode to 'Record'")
console.info("Setting Output file To '" + file + "'")
record(file)
}
if (args[0] == "play") {
console.info("Setting App Mode to 'Play'")
console.info("Setting Input file To '" + file + "'")
play(file)
}
} else {
console.log(usage);
}
The record function takes a argument filename and then starts a new terminal using node-pty module, and when on data event occurs it simply calculates the the milliseconds from the last time this on data event triggered, and pushes a object into recording array, and this object has two properties, 1st is delay, and second is the text. and when the on exit event triggers, it simply closes the terminal and saves the recording array to a json file with name equal to the variable filename
The play function takes a argument filename and then reads the data from the file and parses it to a JavaScript Array which contains multiple objects, and if something goes wrong it throws an error. after parsing it simply uses a for loop to iterate over the array and writes the data to the console and waits for some milliseconds.
Problem is, when I record my session, and when i press Backspace key to remove a character, then it weirdly puts a space between it, like shown below:
In the gif, after I ran the first command and typed out ls -ls then i pressed Backspace 2 Times, which resulted a weird blank space 2 times.
and after i pressed enter it showed a error ls: cannot access '-': No such file or directory which meant the Backspace key removed 2 characters from the input but and it executed ls - instead of ls -ls but for some reason those 2 characters were not removed from the console when i pressed Backspace twice instead it added a weird blank space
How do i fix this issue?
This is what my package.json looks like:
{
"name": "term-record",
"version": "0.0.1",
"description": "A Simple Terminal Session Recorder",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js"
},
"author": "ADITYA MISHRA",
"license": "MIT",
"dependencies": {
"node-pty": "^0.10.1",
"sleep": "^6.3.0"
}
}
My NodeJS version: v16.11.1
My NPM version: 8.1.2
My Linux Distro: Arch Linux with XFCE 4
I tried switching to nodejs version 14.18.1-1, but that didn't help too
Since i had my keyboard layout poorly selected, which caused the backspace key to add a space.
I ran the following command setxkbmap -layout us, to change my keyboard layout to US and now it works
Say you want to run / debug a HackerRank solution locally on your WebStorm / IntelliJ Idea IDE for for macOS before submitting it. What are the needed steps, considering node.js is already installed in your machine?
Sample Hello.js file as below:
const fs = require('fs');
function processData(input) {
const ws = fs.createWriteStream(process.env.OUTPUT_PATH);
var i, result = "";
for (i = 0; i < parseInt(input); i++) {
result += 'Hello - ' + i + '\n';
}
ws.write(result + "\n");
ws.end();
}
process.stdin.resume();
process.stdin.setEncoding("ascii");
_input = "";
process.stdin.on("data", function (input) {
_input += input;
});
process.stdin.on("end", function () {
processData(_input);
});
On macOS Mojave the steps are:
On Preferences > Keymap, add a keyboard shortcut Ctrl+D to run the Other > Send EOF action; beware this may remove other actions associated to this shortcut, such as "Debug" (credits to this answer)
Add the Hello.js file to your project and open it in the editor
Enter Modify Run Configuration... dialog (Cmd+Shift+A`, type "modify ..." and select it)
make sure Node interpreter:, Working directory: and JavaScript file: are properly set
(if the solution writes output to a file) Set also the Environment variables: to OUTPUT_PATH=./hello.txt or as desired
Save it
Run the configuration. In the terminal pane that opens:
provide the needed input; e.g. 4, then Enter
press Ctrl+D to fire the "end" event
check the generated file hello.txt
You may want to use console.log() to help debugging; make sure it's commented out before submitting your solution back to HackerRank.
Run Configuration:
Run Tool Window:
Try this works on windows, you can run it using node here end event gets triggered when you hit ctrl+D just like hackerrank or Mac os
'use strict';
const { METHODS } = require('http');
const readline = require('readline')
process.stdin.resume();
process.stdin.setEncoding('utf-8');
readline.emitKeypressEvents(process.stdin);
let inputString = '';
let currentLine = 0;
process.stdin.setRawMode(false)
process.stdin.on('data', inputStdin => {
inputString += inputStdin;
});
process.stdin.on('keypress', (str, key) => {
if (key && key.ctrl && key.name == 'd'){
inputString = inputString.trim().split('\n').map(string => {
return string.trim();
})
main();
}
});
function readLine() {
return inputString[currentLine++];
}
function method() {
}
function main() {
const n = parseInt(readLine().trim());
const arr = readLine().replace(/\s+$/g, '').split(' ').map(qTemp =>parseInt(qTemp,10))
method();
}
Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 3 years ago.
Improve this question
Anyone able to explain what I'm doing wrong with my use of asynchronous functions in Javascript?
Basically, I must use an asynchronous in my Node.js code to grab an open port for me to use. There is a local variable that is being set outside of the asynchronous call that I can access/use just fine until I await for the asynchronous function to return. After that, the local variable is undefined.
(async () => {
console.log("CHECK AFTER ASYNC1: " + csvFilePath);
// First, grab a valid open port
var port;
while (!port || portsInProcess.indexOf(port) >= 0) {
console.log("CHECK AFTER ASYNC2: " + csvFilePath);
port = await getPort();
console.log(port);
}
console.log("CHECK AFTER ASYNC3: " + csvFilePath);
portsInProcess.push(port);
// ... more code below...
Checks #1 and 2 are fine for the csvFilePath variable, but check #3 shows that it's undefined. The port number, however, is fine. This leads me to believe that there's some weirdness with asynchronous function calls in Javascript that ONLY affects local variables; the global variables I use further down are just fine. Unfortunately here, I cannot make the csvFilePath variable global since that will introduce race conditions on that variable too (which I'm preventing elsewhere; the while loop is to help prevent race conditions on the port number, which is basically unused in my simple tests on localhost).
Just in case it's helpful, here's the output I'm getting:
CHECK AFTER ASYNC1: data/text/crescent_topics.csv
CHECK AFTER ASYNC2: data/text/crescent_topics.csv
58562
CHECK AFTER ASYNC3: null
It might also be worth mentioning it's really only those first few lines of code to dynamically grab an open port that are the lines of code I added. The code that I had before which used a fixed port number worked just fine (including this csvFilePath variable remaining stable).
My understanding of the await functionality was that it makes the asynchronous function act more or less synchronously, which is what seems to be happening here; the code I have farther down that uses the port number is not running until after the port number is set. (But even if that wasn't the case, why is the csvFilePath variable being unset since I'm not altering it or using it in any way here?)
EDIT: Here's some more code to provide additional context
var spawn = require('child_process').spawn;
var fs = require("fs");
var async = require('async');
var zmq = require('zmq');
var readline = require('readline');
const getPort = require('get-port');
/* Export the Nebula class */
module.exports = Nebula;
/* Location of the data for the Crescent dataset */
var textDataPath = "data/text/";
var crescentRawDataPath = textDataPath + "crescent_raw";
var crescentTFIDF = textDataPath + "crescent tfidf.csv";
var crescentTopicModel = textDataPath + "crescent_topics.csv";
/* Location of the data for the UK Health dataset */
var ukHealthRawDataPath = textDataPath + "uk_health_raw";
var ukHealthTFIDF = textDataPath + "uk_health.csv";
/* Map CSV files for text data to raw text location */
var textRawDataMappings = {};
textRawDataMappings[crescentTFIDF] = crescentRawDataPath;
textRawDataMappings[crescentTopicModel] = crescentRawDataPath;
textRawDataMappings[ukHealthTFIDF] = ukHealthRawDataPath;
textRawDataMappings[textDataPath + "uk_health_sm.csv"] = ukHealthRawDataPath;
/* The pipelines available to use */
var flatTextUIs = ["cosmos", "composite", "sirius", "centaurus"];
var pipelines = {
andromeda: {
file: "pipelines/andromeda.py",
defaultData: "data/highD/Animal_Data_study.csv"
},
cosmos: {
file: "pipelines/cosmos.py",
defaultData: textDataPath + "crescent tfidf.csv"
},
sirius: {
file: "pipelines/sirius.py",
defaultData: "data/highD/Animal_Data_paper.csv"
},
centaurus: {
file: "pipelines/centaurus.py",
defaultData: "data/highD/Animal_Data_paper.csv"
},
twitter: {
file: "pipelines/twitter.py",
},
composite: {
file: "pipelines/composite.py",
defaultData: textDataPath + "crescent tfidf.csv"
},
elasticsearch: {
file: "pipelines/espipeline.py",
args: []
}
};
/* The locations of the different types of datasets on the server */
var textDataFolder = "data/text/";
var highDDataFolder = "data/highD/";
var customCSVFolder = "data/customCSV/";
var sirius_prototype = 2;
// An array to track the ports being processed to eliminate race conditions
// as much as possible
var portsInProcess = [];
var nextSessionNumber = 0;
var usedSessionNumbers = [];
/* Nebula class constructor */
function Nebula(io, pipelineAddr) {
/* This allows you to use "Nebula(obj)" as well as "new Nebula(obj)" */
if (!(this instanceof Nebula)) {
return new Nebula(io);
}
/* The group of rooms currently active, each with a string identifier
* Each room represents an instance of a visualization that can be shared
* among clients.
*/
this.rooms = {};
this.io = io;
/* For proper use in callback functions */
var self = this;
/* Accept new WebSocket clients */
io.on('connection', function(socket) {
// Skipped some irrelevant Socket.io callbacks
**// Use the csvFilePath to store the name of a user-defined CSV file
var csvFilePath = null;**
/* Helper function to tell the client that the CSV file is now ready for them
* to use. They are also sent a copy of the data
*/
var csvFileReady = function(csvFilePath) {
// Let the client know that the CSV file is now ready to be used on
// the server
socket.emit("csvDataReady");
// Prepare to parse the CSV file
var csvData = [];
const rl = readline.createInterface({
input: fs.createReadStream(csvFilePath),
crlfDelay: Infinity
});
// Print any error messages we encounter
rl.on('error', function (err) {
console.log("Error while parsing CSV file: " + csvFilePath);
console.log(err);
});
// Read each line of the CSV file one at a time and parse it
var columnHeaders = [];
var firstColumnName;
rl.on('line', function (data) {
var dataColumns = data.split(",");
// If we haven't saved any column names yet, do so first
if (columnHeaders.length == 0) {
columnHeaders = dataColumns;
firstColumnName = columnHeaders[0];
}
// Process each individual line of data in the CSV file
else {
var dataObj = {};
var i;
for (i = 0; i < dataColumns.length; i++) {
var key = columnHeaders[i];
var value = dataColumns[i];
dataObj[key] = value
}
csvData.push(dataObj);
}
});
// All lines are read, file is closed now.
rl.on('close', function () {
// On certain OSs, like Windows, an extra, blank line may be read
// Check for this and remove it if it exists
var lastObservation = csvData[csvData.length-1];
var lastObservationKeys = Object.keys(lastObservation);
if (lastObservationKeys.length = 1 && lastObservation[lastObservationKeys[0]] == "") {
csvData.pop();
}
// Provide the CSV data to the client
socket.emit("csvDataReadComplete", csvData, firstColumnName);
});
};
**/* Allows the client to specify a CSV file already on the server to use */
socket.on("setCSV", function(csvName) {
console.log("setCSV CALLED");
csvFilePath = "data/" + csvName;
csvFileReady(csvFilePath);
console.log("CSV FILE SET: " + csvFilePath);
});**
// Skipped some more irrelevant callbacks
/* a client/ a room. If the room doesn't next exist yet,
* initiate it and send the new room to the client. Otherwise, send
* the client the current state of the room.
*/
socket.on('join', function(roomName, user, pipeline, args) {
console.log("Join called for " + pipeline + " pipeline; room " + roomName);
socket.roomName = roomName;
socket.user = user;
socket.join(roomName);
console.log("CSV FILE PATH: " + csvFilePath);
var pipelineArgsCopy = [];
if (!self.rooms[roomName]) {
var room = {};
room.name = roomName;
room.count = 1;
room.points = new Map();
room.similarity_weights = new Map();
if (pipeline == "sirius" || pipeline == "centaurus") {
room.attribute_points = new Map();
room.attribute_similarity_weights = new Map();
room.observation_data = [];
room.attribute_data = [];
}
/* Create a pipeline client for this room */
console.log("CHECK BEFORE ASYNC: " + csvFilePath);
**// Here's the code snippet I provided above**
**(async () => {
console.log("CHECK AFTER ASYNC1: " + csvFilePath);
// First, grab a valid open port
var port;
while (!port || portsInProcess.indexOf(port) >= 0) {
console.log("CHECK AFTER ASYNC2: " + csvFilePath);
port = await getPort();
console.log(port);
}
console.log("CHECK AFTER ASYNC3: " + csvFilePath);**
portsInProcess.push(port);
console.log("CHECK AFTER ASYNC4: " + csvFilePath);
if (!pipelineAddr) {
var pythonArgs = ["-u"];
if (pipeline in pipelines) {
// A CSV file path should have already been set. This
// file path should be used to indicate where to find
// the desired file
console.log("LAST CHECK: " + csvFilePath);
if (!csvFilePath) {
csvFilePath = pipelines[pipeline].defaultData;
}
console.log("FINAL CSV FILE: " + csvFilePath);
pipelineArgsCopy.push(csvFilePath);
// If the UI supports reading flat text files, tell the
// pipeline where to find the files
if (flatTextUIs.indexOf(pipeline) >= 0) {
pipelineArgsCopy.push(textRawDataMappings[csvFilePath]);
}
// Set the remaining pipeline args
pythonArgs.push(pipelines[pipeline].file);
pythonArgs.push(port.toString());
if (pipeline != "twitter" && pipeline != "elasticsearch") {
pythonArgs = pythonArgs.concat(pipelineArgsCopy);
}
}
else {
pythonArgs.push(pipelines.cosmos.file);
pythonArgs.push(port.toString());
pythonArgs.push(pipelines.cosmos.defaultData);
pythonArgs.push(crescentRawDataPath);
}
// used in case of CosmosRadar
for (var key in args) {
if (args.hasOwnProperty(key)) {
pythonArgs.push("--" + key);
pythonArgs.push(args[key]);
}
}
// Dynamically determine which distance function should be
// used
if (pythonArgs.indexOf("--dist_func") < 0) {
if (pipeline === "twitter" || pipeline === "elasticsearch" ||
csvFilePath.startsWith(textDataPath)) {
pythonArgs.push("--dist_func", "cosine");
}
else {
pythonArgs.push("--dist_func", "euclidean");
}
}
console.log(pythonArgs);
console.log("");
var pipelineInstance = spawn("python2.7", pythonArgs, {stdout: "inherit"});
pipelineInstance.on("error", function(err) {
console.log("python2.7.exe not found. Trying python.exe");
pipelineInstance = spawn("python", pythonArgs,{stdout: "inherit"});
pipelineInstance.stdout.on("data", function(data) {
console.log("Pipeline: " + data.toString());
});
pipelineInstance.stderr.on("data", function(data) {
console.log("Pipeline error: " + data.toString());
});
});
/* Data received by node app from python process,
* ouptut this data to output stream(on 'data'),
* we want to convert that received data into a string and
* append it to the overall data String
*/
pipelineInstance.stdout.on("data", function(data) {
console.log("Pipeline STDOUT: " + data.toString());
});
pipelineInstance.stderr.on("data", function(data) {
console.log("Pipeline error: " + data.toString());
});
room.pipelineInstance = pipelineInstance;
}
/* Connect to the pipeline */
pipelineAddr = pipelineAddr || "tcp://127.0.0.1:" + port.toString();
room.pipelineSocket = zmq.socket('pair');
room.pipelineSocket.connect(pipelineAddr);
pipelineAddr = null;
portsInProcess.splice(portsInProcess.indexOf(port), 1);
/* Listens for messages from the pipeline */
room.pipelineSocket.on('message', function (msg) {
self.handleMessage(room, msg);
});
self.rooms[roomName] = socket.room = room;
invoke(room.pipelineSocket, "reset");
})();
}
else {
socket.room = self.rooms[roomName];
socket.room.count += 1;
if (pipeline == "sirius" || pipeline == "centaurus") {
socket.emit('update', sendRoom(socket.room, true), true);
socket.emit('update', sendRoom(socket.room, false), false);
}
else {
socket.emit('update', sendRoom(socket.room));
}
}
// Reset the csvFilePath to null for future UIs...
// I don't think this is actually necessary since
// csvFilePath is local to the "connections" message,
// which is called for every individual room
csvFilePath = null;
});
// Skipped the rest of the code; it's irrelevant
});
}
Full printouts:
setCSV CALLED
CSV FILE SET: data/text/crescent_topics.csv
Join called for sirius pipeline; room sirius0
CSV FILE PATH: data/text/crescent_topics.csv
CHECK BEFORE ASYNC: data/text/crescent_topics.csv
CHECK AFTER ASYNC1: data/text/crescent_topics.csv
CHECK AFTER ASYNC2: data/text/crescent_topics.csv
58562
CHECK AFTER ASYNC3: null
CHECK AFTER ASYNC4: null
LAST CHECK: null
FINAL CSV FILE: data/highD/Animal_Data_paper.csv
[ '-u',
'pipelines/sirius.py',
'58562',
'data/highD/Animal_Data_paper.csv',
undefined,
'--dist_func',
'euclidean' ]
Since bolding of code doesn't work, just search for the "**" to find the relevant pieces I've marked.
TL;DR There's a lot of communication happening between the client and server to establish an individualized communication that is directly linked to a specific dataset. The user has the ability to upload a custom CSV file to the system, but the code I'm working with right now is just trying to select an existing CSV file on the server, so I omitted the callbacks for the custom CSV file. Once the file has been selected, the client asks to "join" a room/session. The case I'm working with right now assumes that this is a new room/session as opposed to trying to do some shared room/session with another client. (Yes, I know, the code is messy for sharing rooms/sessions, but it works for the most part for now and is not my main concern.) Again, all this code worked just fine before the asynchronous code was added (and using a static port variable), so I don't know what changed so much by adding it.
Since you now included the whole code context, we can see that the issue is that the code after your async IIFE is what is causing the problem.
An async function returns a promise as soon as it hits an await. And, while that await is waiting for its asynchronous operation, the code following the call to the async function runs. In your case, you're essentially doing this:
var csvFilePath = someGoodValue;
(async () => {
port = await getPort();
console.log(csvFilePath); // this will be null
})();
csvFilePath = null; // this runs as soon as the above code hits the await
So, as soon as you hit your first await, the async function returns a promise and the code following it continues to run, hitting the line of code that resets your csvFilePath.
There are probably cleaner ways to restructure your code, but a simple thing you could do is this:
var csvFilePath = someGoodValue;
(async () => {
port = await getPort();
console.log(csvFilePath); // this will be null
})().finally(() => {
csvFilePath = null;
});
Note: .finally() is supported in node v10+. If you're using an older version, you can reset the path in both .then() and .catch().
Or, as your comment says, maybe you can just remove the resetting of the csvFilePath entirely.
I realized after some silly tests I tried that I'm resetting csvFilePath to null outside the asynchronous call, which is what is causing the error... Oops!
I'm using a Jakefile to help me update Wordpress pages from the command line. I'm using Jake's watch task to re-build ever time I edit a file. When I edit a file with Vim, after the first successful build, Jake fails with following error :
WatchTask started for: default
cp home.html dist/home.html
exec wp --path=../wordpress post list --post_type=page --format=json --fields=ID,post_name { silent: true }
exec wp --path=../wordpress post update 2 dist/home.html --post_type=page
Success: Updated post 2.
jake aborted.
Error: File-task home.html has no existing file, and no action to create one.
at FileBase.isNeeded (/usr/local/lib/node_modules/jake/lib/task/file_task.js:50:17)
at TaskBase.run (/usr/local/lib/node_modules/jake/lib/task/task.js:256:26)
(See full trace by running task with --trace)
I've tried using a sleep function in the rule to delay the rebuild. I tried this because Vim, when saving a file, write the contents to a new temp file and then renames the new temp file to the original file name. I think the build fails because it's trying to build before the file is fully renamed. Using the sleep doesn't work reliably, it may work once or twice but then it fails the same way as above.
Here is my Jakefile:
var shell = require('shelljs');
var sleep = require('sleep');
shell.config.verbose = true;
const destDir = 'dist';
const wpDir = '../wordpress';
var files = new jake.FileList();
files.include('*.html');
var outputFiles = files.toArray().map(function(fileName){
return destDir + '/' + fileName;
});
var sourceFile = function(name) {
return name.substr(name.lastIndexOf('/') + 1);
}
function objectToStr(object) {
var s = '';
for(var property in object){
s += property + ': ' + object[property] + '\n';
}
return s;
}
function rmExt(name) {
return name.substr(0, name.lastIndexOf('.'));
}
directory(destDir);
task('default', [destDir].concat(outputFiles));
task('clean', function() {
jake.rmRf(destDir);
});
rule('dist/%.html', sourceFile, function() {
shell.cp(this.source, this.name);
var pages = JSON.parse(shell.exec('wp --path=' + wpDir
+ ' post list --post_type=page --format=json --fields=ID,post_name',
{ silent: true }).stdout);
var postId = null;
var l = pages.length;
for(var i = 0; i < l; i++){
if(pages[i].post_name === rmExt(this.source)){
postId = pages[i].ID;
break;
}
}
if(postId !== null){
shell.exec('wp --path=' + wpDir + ' post update ' + postId
+ ' ' + this.name + ' --post_type=page');
}else{
shell.echo('Unable to find matching post ID for file: ' + this.name);
}
shell.echo('1');
sleep.sleep(2);
shell.echo('2');
});
watchTask('watch', ['default'], function() {
this.watchFiles.include('*.html');
}
I found a reliable solution using the library node-watch. I replaced the watchTask with a normal Jake task and invoked the task I wanted to call using node-watch to watch the current working directory. The library node-watch is able filter files based on regex so the watch won't be trigger for the Vim swap file and the like. Below is the code I used:
task('watch', function() {
var defaultTask = jake.Task['default'];
defaultTask.invoke();
defaultTask.reenable(true);
watch('./', { filter: /.*.html$/ }, function(evt, name) {
defaultTask.invoke();
defaultTask.reenable(true);
});
});
Note: I also invoked the task I want to call when the watch task is first run to make sure any changes made before the watch started are built.
I'm trying to write a file templating script using Node.js. I have a JSON file called template.json which stores template information. The idea behind my script is that, if I type something like:
tmpl.js java Joe
it will do the following:
Call touch Joe.java
Read template.json to get the template for Java files
Use its information to replace all the placeholders with Joe
Write the result to Joe.java
Execute emacsclient Joe.java
Now, I wrote this script as follows:
#!/usr/local/bin/node --harmony
var templates = require('./config/template.json'),
args = process.argv;
if (args.length < 4) {
console.log("Not enough arguments!");
}
else {
var type = args[2],
name = args[3];
if (type in templates) {
var tmpl = templates[type],
contents = make_output(tmpl["format"],name),
file_name = name + tmpl["extension"],
command = "touch " + file_name + " && echo -e '" + contents +
"' &> " + file_name + " && emacsclient " + file_name;
invoke(command);
}
else {
console.log("No template for %s", type);
}
}
//Helpers
//Invokes comm with args in the terminal, returns all output
//Does not play nice with command redirection
function invoke(comm) {
var exec = require('child_process').exec,
child = exec(comm,
function (error, stdout, stderr) {
if (error !== null) {
console.log(stderr);
}
});
}
//If template is a format string, processes it with x as the
//replacement. Otherwise, just evaluates.
//Limited to one replacement at most.
function make_output(template, x) {
if(/.*\%s.*/i.test(template)) {
var util = require('util');
return util.format(template,x);
}
else {
return template;
}
}
Basically, the command it ends up building is something like:
touch Joe.java && echo -e `bunch of template stuffs` &> Joe.java && emacsclient Joe.java
Now, the problem I am getting is that the above command relies on output redirection, which my invoke command doesn't deal with very well - specifically, everything executes, but I get an empty file! Is there a way I can change either invoke or what I'm constructing to be invoked to avoid this problem?
The issue is that Node's child_process.exec starts sh but you are using features that are peculiar to bash. The &> is interpreted as & > in sh (two operators: a control operator and a redirection operator) and echo -e will use sh's builtin implementation of echo, which does not understand -e.
It would probably be possible to work around the issues above but using the shell like you do is fragile. For instance if your template contains single quotes ('), these quotes may interfere with the single quotes you use in your command. A more robust way to do it would be to change the main part of your code to use fs.writeFileSync rather than using shell commands to write to your file:
var templates = require('./config/template.json'),
fs = require("fs"),
args = process.argv;
if (args.length < 4) {
console.log("Not enough arguments!");
}
else {
var type = args[2],
name = args[3];
if (type in templates) {
var tmpl = templates[type],
contents = make_output(tmpl["format"],name),
file_name = name + tmpl["extension"],
command = "emacsclient " + file_name;
fs.writeFileSync(file_name, contents);
invoke(command);
}
else {
console.log("No template for %s", type);
}
}
You'd also want to modify make_output to perform the transformations that echo -e would have done for you.