Representing dynamic path variable in static JSON config file - javascript

I have this JSON config for a backend CLI utility:
{
"searchRoots": ["$HOME"],
"list": []
}
if I did this with JavaScript, I would do:
module.exports = {
searchRoots": [process.env.HOME],
list: []
};
the problem with using JSON instead of JS, is that I cannot programmatically reference environment variables or any variables really, everything is hardcoded.
So what I will do in my Node.js code, is do:
const conf = require('./conf.json');
const searchRoots = conf.searchRoots.map(function(item){
return process.env[item];
});
Is there another good way to do this? Without env variables?
What is the best way to include some sort of variable in JSON that can be interpreted by the reader of the file?
Is there a way to do bash-style interpretation of the path via Node.js?

Seems like this is the best way to handle this type of situation. Use bash-style path interpretation, and call bash from your process.
With your JSON config file like so:
{
"searchRoots": ["$HOME"],
"list": []
}
You can write a routine like this, which will use bash to interpret the strings, and turn them into absolute paths:
searchRoots: function (cb) {
const mappedRoots = searchRoots.map(function (v) {
return `echo "${v}";`;
});
const k = cp.spawn('bash');
k.stdin.write('\n' + mappedRoots + '\n');
process.nextTick(function () {
k.stdin.end();
});
const data = [];
k.stderr.pipe(process.stderr);
k.stdout.on('data', function (d) {
data.push(d);
});
k.once('close', function (code) {
if (code > 0) {
return cb({
code: code
});
}
cb(null, data.map(function (d) {
return String(d).trim();
}));
});
},
This will transform the original data to:
{
"searchRoots": ["/Users/YourHomeDir"],
"list": []
}
And it's generic, so it can handle other env variables, not just the $HOME variable. That's the big win here.

For this exact reason I use a .js file instead of .json for the config. So as an example ... the file would be called config.js and would have the following code.
module.exports = {
"searchRoots": [ process.env.HOME ],
"list": []
}

if you structure your library properly, it should have a method to update your variable or you should be able to access your global variable as long as the variable makes part of the same environment. you can check any documentation or JS book on accessing local and global variables.

Related

Accessing typescript file variable values using gulp

I have several typescript files, some of them export a const named APIS.
I'm trying to access those exports (I want to concatenated all of them to a single file), but it doesn't seem to work. I'm obviously doing something wrong, but I'm not sure what.
For example, I have a folder named services, with 2 files: service1.ts, service2.ts.
service1.ts:
...
export const APIS = [ { "field1" : "blabla" } ];
service2.ts: does not contain the APIS var.
This is my gulpfile.js:
var gulp = require('gulp');
var concat = require('gulp-concat');
var map = require('gulp-map');
gulp.task('default', function() {
return gulp.src('.../services/*.ts')
.pipe(map(function(file) {
return file.APIS;
}))
.pipe(concat('all.js'))
.pipe(gulp.dest('./test/'));
});
When I run this task, I get nothing. When I added console.log(file.APIS); to the map function, I get undefined for all the values (although it is defined in service1.ts!).
This is following to: Extracting typescript exports to json file using gulp
EDIT: OK, so I tried saving the exports in a .js file instead of a .ts file, and now I can access those vars using require:
gulp.task('default', function() {
return gulp.src('./**/*.service.export.js')
.pipe(map(function(file) {
var fileObj = require(file.path);
...
}))
Now if I try console.log(fileObj.APIS); I get the correct values. What I'm still confused about is how I can pass these value on, and create a single file out of all these vars. Is it possible to push them into an array?
This will not work as you think it would work. Gulp itself knows nothing about typescript files, that file is a vinyl-file and has no knowledge about the typescript code within its content.
Edit
Based on your example, you can do something like this:
var gulp = require('gulp');
var concat = require('gulp-concat');
var map = require('gulp-map');
var fs = require('fs');
gulp.task('test', function ()
{
var allConstants = [];
var stream = gulp.src('./**/*.service.export.js')
.pipe(map(function(file)
{
var obj = require(file.path);
if (obj.APIS != null)
allConstants = allConstants.concat(obj.APIS);
return file;
}));
stream.on("end", function (cb)
{
// Do your own formatting here
var content = allConstants.map(function (constants)
{
return Object.keys(constants).reduce(function (aggregatedString, key)
{
return aggregatedString + key + " : " + constants[key];
}, "");
}).join(", ");
fs.writeFile('filename.txt', content, cb);
});
return stream;
});
Suggestion
If you want to collect multiple variables into a single file i.e. a common variables file I suggest gulp-replace.
Steps
Create a file, require it and use tags within that file to place your variables.
Advice
If you are already using services don't create an array. Instead create an object (JSON) where every property is a constant. i.e.
var constants = {
const_1: 0,
const_2: 1,
const_3: 2,
}

Extracting typescript exports to json file using gulp

I have several typescript files, some of them export a certain var - named APIS - which is an array of objects.
I want to extract the values of all of these exports, and pipe them to a json file using gulp.
For example, I have a folder named services, with 3 files: service1.ts, service2.ts, service3.ts.
service1.ts:
...
export const APIS = [ { "field1" : "blabla" } ];
service2.ts:
...
export const APIS = [ { "field2" : "yadayada" }, { "field3" : "yadabla" } ];
service3.ts: - does not export the APIS var.
I want to use gulp in oder create a json file that looks something like this:
[ { "field1" : "blabla" }, { "field2" : "yadayada" }, { "field3" : "yadabla" } ]
gulpfile.js - the ??? is a placeholder for the missing code.
gulp.task('default', function () {
return gulp.src('.../services/*.ts')
.pipe(???)
.pipe(concat('export.json'))
.pipe(gulp.dest('./test'));
});
I'm new to both typescript & gulp, so I'm not sure how to achieve this... any ideas? :)
EDIT: So, I understand that there's no OOTB solution for this, and I need to writer my own task / plugin. I'm not really sure how to achieve that, though.
Ideally, what I want is to find a gulp plugin (or a combination of plugins) that can handle ts / js files as objects with properties. Then I can extract the var I need from the file.
I couldn't really find something like that, only string manipulation plugins - Treating my ts file as a string and using search with regex seems overly complicated to me. Is there something I'm missing here? is there a more straight-forward way to do this?
The typescript compiler API is relevant here, as this is what you need to parse and understand the ts-code properly. Unfortunately, I don't think there is a gulp plugin that implements this API.
I think your best bet is to change strategy completely here and solve your problem in another way, or to use regex to try to extract the constants that you want. Unless you want to write your own gulp-plugin using the compiler API.
This is what I ended up doing, and it worked for me. I'm positing it here in case anyone else finds it useful. :)
Instead of .ts, I saved the exports in .js files, i.e:
service2.export.js:
exports.APIS = [ { "field2" : "yadayada" }, { "field3" : "yadabla" } ];
Based on the answer given here: https://stackoverflow.com/a/36869651/3007732 I created a gulp task as following:
var gulp = require('gulp');
var concat = require('gulp-concat');
var map = require('gulp-map');
var fs = require('fs');
var allServices;
gulp.task('default', function() {
var allServices = [];
var stream = gulp.src('./**/*.export.js')
.pipe(map(function(file) {
var obj = require(file.path);
if (obj.APIS != null) {
allServices.push.apply(allServices, obj.APIS);
}
return file;
}));
stream.on("end", function (cb)
{
fs.writeFile('./export.json', JSON.stringify(allServices), cb);
});
return stream;
});
and now I get the following output in export.json:
[ { "field1" : "blabla" }, { "field2" : "yadayada" }, { "field3" : "yadabla" } ]
which is exactly what I wanted.

Nodejs: load javascript from external file containing functions

I'm trying to load javascript object from a file on my filesystem using Nodejs application.
Sample object stored in filesystem file ('myOwnFunction.js'):
exports.myOwnFunction = {
"var2": "My value1",
"var2": 100,
"var3": true,
"var4": function(abc) {
var var5 = "some other value";
runOtherFunction(var5)
}
}
So, in the application itself, I would like to "load" several object like the sample above.
fs.readdir("./", function (err, files) {
files.forEach(function(file) { // so loading all js files in folder
var foo = require( "./"+file ).myOwnFunction;
});
});
As a consequence, the script return the following error message:
ReferenceError: runOtherFunction is not defined
I guess this is absolutely normal ; a question of (a)synchroneous and also because "runOtherFunction" is not available on the 'myOwnFunction.js' file.
Could you give me clues how to proceed or any alternative solution.
I'm quite sure I'm doing this the wrong way - but I'm bit lost..
Thanks a lot.
The way you are exporting your modules is a little weird. I don't know it's wrong per se, but it doesn't follow node.js conventions. Try this:
myOwnFunction.js:
module.exports = function() {
"var2": "My value1",
"var2": 100,
"var3": true,
"var4": function(abc) {
var var5 = "some other value";
runOtherFunction(var5)
}
});
In the file you want to use this function:
myOwnFunction = require('./myOwnFunction');
fs.readdir("./", function (err, files) {
files.forEach(function(file) { // so loading all js files in folder
var foo = require( "./"+file ).myOwnFunction;
});
});

browserify transform options are disappearing

I am working on a browserify transform and I am passing in options.
//excerpt from package.json of my application that is using my transform and extension
"browserify": {
"transform": [
["mytransform", {"extensions": ["my-extension"] } ]
]
}
The transform is working and is called on the first file and then on files depended on by that file. The problem I am having is that I am losing the options.
In my transform I have
module.exports = function (file, options) {
console.log('processing? ', file, options);
options = options || {};
options.extensions = options.extensions || [];
var extensions = options.extensions.map(function(extensionId){
return require(extensionId)();
}),
data = '';
var stream = through(write, end);
return stream;
function write(buf) { data += buf; }
function end() {
var out = processFile(extensions, file, data);
stream.queue(out || data);
stream.queue(null);
}
};
The following is the output. The options are there for the first file, but then nothing in the second file
processing? /path/to/cascadingAmdDepWithPlugin.js { extensions: [ 'my-extension' ]}
processing? /path/to/node_modules/dojo/number.js { }
How do I get my options to carry through to all files being handled by my transform?
Things are working as expected. I didn't carefully read the documentation carefully enough. Transforms aren't applied to files within node_modules. Solutions are to either specify the transform as global or update package.json of the project within node_modules. Once I did either of those things, my code worked as expected.

Creating multiple HTML files from a single Jade template with Grunt

I'm looking to create multiple HTML files from a single Jade template using Grunt.
Here's what I'm doing:
Grabbing the JSON data from an external file
Looping through that object
Creating a grunt config task for each value in that JSON object
Here's my code:
neighborhoods = grunt.file.readJSON('data/neighborhoods.json');
for(var i = 0; i < Object.keys(neighborhoods).length; i++) {
var neighborhood = {
"title" : Object.keys(neighborhoods)[i],
"data" : neighborhoods[Object.keys(neighborhoods)[i]]
};
grunt.config(['jade', neighborhood.title], {
options: {
data: function() {
return {
neighborhoods: neighborhood.data
}
}
},
files: {
"build/neighborhoods/<%= neighborhood.title %>.html": "layouts/neighborhood.jade"
}
});
}
The problem that I'm running in to is this
Running "jade:Art Museum" (jade) task
Warning: An error occurred while processing a template (Cannot read property 'title' of undefined). Use --force to continue.
If I make the filename a string, it runs fine but obviously creates all the files with the same filename, thus only creating one file. I need to make that filename dynamic.
I found the solution here:
Use Global Variable to Set Build Output Path in Grunt
The issue is that the module exports before those global variables get set, so they are all undefined in subsequent tasks defined within the initConfig() task.
This did the trick!
var neighborhoods = grunt.file.readJSON('data/neighborhoods.json');
for(var i = 0; i < Object.keys(neighborhoods).length; i++) {
var neighborhood = {
"title" : Object.keys(neighborhoods)[i],
"data" : neighborhoods[Object.keys(neighborhoods)[i]]
};
/*
* DEFINE VALUE AS GRUNT OPTION
*/
grunt.option('neighborhood_title', neighborhood.title);
grunt.config(['jade', neighborhood.title], {
options: {
data: function() {
return {
neighborhoods: neighborhood.data,
neighborhood_title: neighborhood.title
}
}
},
/*
* OUTPUT GRUNT OPTION AS FILENAME
*/
files: {
"build/neighborhoods/<%= grunt.option('neighborhood_title') %>.html": "layouts/neighborhood.jade"
}
});
}
This results in the desired output:
Running "jade:East Passyunk" (jade) task
File build/neighborhoods/Society Hill.html created.
Running "jade:Fishtown" (jade) task
File build/neighborhoods/Society Hill.html created.
Running "jade:Graduate Hospital" (jade) task
File build/neighborhoods/Society Hill.html created.
Running "jade:Midtown Village" (jade) task
File build/neighborhoods/Society Hill.html created.
Running "jade:Northern Liberties" (jade) task
File build/neighborhoods/Society Hill.html created.
...
I know this is an old post but I kept coming back here whilst trying to solve a similar problem. I wanted to output multiple html files from a single jade template file using a for-loop.
The two problems I came across was setting the output filename (a javascript object literal KEY) and making sure inline javascript functions are run immediately so that the loop variables are available.
Here is my full source code with comments. I hope this is of use to anyone else stumbling across this post.
Gruntfile.js:
module.exports = function(grunt) {
// Create basic grunt config (e.g. watch files)
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
watch: {
grunt: { files: ['Gruntfile.js'] },
jade: {
files: 'src/*.jade',
tasks: ['jade']
}
}
});
// Load json to populate jade templates and build loop
var json = grunt.file.readJSON('test.json');
for(var i = 0; i < json.length; i++) {
var obj = json[i];
// For each json item create a new jade task with a custom 'target' name.
// Because a custom target is provided don't nest options/data/file parameters
// in another target like 'compile' as grunt wont't be able to find them
// Make sure that functions are called using immediate invocation or the variables will be lost
// http://stackoverflow.com/questions/939386/immediate-function-invocation-syntax
grunt.config(['jade', obj.filename], {
options: {
// Pass data to the jade template
data: (function(dest, src) {
return {
myJadeName: obj.myname,
from: src,
to: dest
};
}()) // <-- n.b. using() for immediate invocation
},
// Add files using custom function
files: (function() {
var files = {};
files['build/' + obj.filename + '.html'] = 'src/index.jade';
return files;
}()) // <-- n.b. using () for immediate invocation
});
}
grunt.loadNpmTasks('grunt-contrib-jade');
grunt.loadNpmTasks('grunt-contrib-watch');
// Register all the jade tasks using top level 'jade' task
// You can also run subtasks using the target name e.g. 'jade:cats'
grunt.registerTask('default', ['jade', 'watch']);
};
src/index.jade:
doctype html
html(lang="en")
head
title= pageTitle
script(type='text/javascript').
if (foo) {
bar(1 + 5)
}
body
h1 #{myJadeName} - node template engine
#container.col
p.
Jade is a terse and simple
templating language with a
strong focus on performance
and powerful features.
test.json:
[{
"id" : "1",
"filename" : "cats",
"tid" : "2016-01-01 23:35",
"myname": "Cat Lady"
},
{
"id" : "2",
"filename" : "dogs",
"tid" : "2016-01-01 23:45",
"myname": "Dog Man"
}]
After running 'grunt' the output should be:
build/cats.html
build/dogs.html
Came across a similar requirement for a project I'm working on but couldn't get this to work. I kept getting only one file generated since the grunt option had same value for all tasks (the last value). So I ended up using <%= grunt.task.current.target %> for the file name which in your case would be same as neighborhood.title.

Categories