My yeoman generator copies files from template to destination path:
this.fs.copyTpl(
this.templatePath(),
this.destinationPath(), {
appName: this.props.appName
});
During project generation, I need to assign value of this.props.appName to some of filenames.
Unfortunately I can't do this that way like I could do inside this files:
<%=appName%>-project.sln
All files that need to be renamed have appTemplate in their names, so what I need to do is simply replace appTemplate with value of this.props.appName.
Can I somehow configure copyTpl to rename some of files while copying them to another destination?
OK, I found a solution. According to yeoman docs:
Any generator author can register a transformStream to modify the file path and/or the content.
Using this method:
this.registerTransformStream();
What that means is I can pipe all generated files through some script:
var rename = require("gulp-rename");
//other dependecies...
module.exports = yeoman.Base.extend({
//some other things generator do...
writing: function() {
var THAT = this;
this.registerTransformStream(rename(function(path) {
path.basename = path.basename.replace(/(666replacethat666)/g, THAT.props.appName);
path.dirname = path.dirname.replace(/(666replacethat666)/g, THAT.props.appName);
}));
this.fs.copyTpl(
this.templatePath(),
this.destinationPath(), {
appName: this.props.appName
});
}
});
This script will pipe all files through gulp-rename, changing 666replacethat666 to something more intelligent.
If you cannot use registerTransformStream because you are using the composeWith() feature in Yeoman (which disconnects transform stream registrations), you can use the processDestinationPath, which works when you select multiple files (not when you specify a specific file in the first argument, for some reason).
this.fs.copyTpl(
this.templatePath("**/{.*,*}"),
this.destinationPath(),
{ /* usually your prompt answers are here */ },
{},
{
processDestinationPath: (filePath: string) =>
filePath.replace(/somedir\/a-file.js/g, 'newdir/better-filename.js'),
},
);
Source to documentation options: https://yeoman.github.io/generator/actions_fs.html#.copyTemplate
Which is based on https://github.com/SBoudrias/mem-fs-editor#copyfrom-to-options-context-templateoptions-
registerTransformStream with gulp-rename is still an issue. However, I get it working with glob.
const glob = require('glob');
writing() {
const files = glob.sync('**', { dot: true, nodir: true, cwd: this.templatePath() })
for (let i in files) {
this.fs.copyTpl(
this.templatePath(files[i]),
this.destinationPath( this.props.destinationFolderPath + '\\' + files[i].replace(/__fileName__/g,this.props.fileName)),
this.props
)
}
}
After copy, iterate over the paths of the output dir and regex replace all occurrences.
const getReplacement = (base, pathRel, match, replace) => {
let pathRelNew = pathRel.replace(match, replace);
let oldPathAbs = path.join(base, pathRel);
let newPathAbs = path.join(base, pathRelNew);
if (oldPathAbs != newPathAbs) {
return {
oldPath: oldPathAbs,
newPath: newPathAbs
}
}
}
const getReplacementsRecursive = (base, match, replace, replacements = []) => {
let pathsRel = fs.readdirSync(base);
pathsRel.forEach(pathRel => {
if (fs.statSync(path.join(base, pathRel)).isDirectory()) {
replacements = getReplacementsRecursive(path.join(base, pathRel), match, replace, replacements);
var replacement = getReplacement(base, pathRel, match, replace)
if (replacement) replacements.push(replacement);
} else {
var replacement = getReplacement(base, pathRel, match, replace)
if (replacement) replacements.push(replacement);
}
});
return replacements;
};
function replaceMatches(dir, match, replace) {
var replacements = getReplacementsRecursive(dir, match, replace);
replacements.forEach(function(replacement) {
fs.renameSync(replacement.oldPath, replacement.newPath);
});
}
module.exports = class extends Generator {
// ...
writing() {
// don't forget to set the output directory
let OUTPUT_DIR = "./out";
// this.fs.copyTpl(...);
// setTimeout is used to give some time for the copyTpl to finish
setTimeout(
() => {
var match = new RegExp( "666replacethat666", 'g' );
replaceMatches(OUTPUT_DIR, match, this.props.appName);
}, 1000);
}
}
Related
Using TipTap, I'm trying to avoid adding a <br />, but create a <p></p> instead, with the focus inside that <p>|</p> when the user hit shift-Enter but I can't make it work.
Here's what I did so far:
new (class extends Extension {
keys () {
return {
'Shift-Enter' (state, dispatch, view) {
const { schema, tr } = view.state
const paragraph = schema.nodes.paragraph
console.log(tr.storedMarks)
const transaction = tr.deleteSelection().replaceSelectionWith(paragraph.create(), true).scrollIntoView()
view.dispatch(transaction)
return true
}
}
}
})()
How can I do this?
I don't know if this is still relevant but as I was looking for the same thing, I found two ways to make this work.
NOTE:
I'm using tiptap v2, if that's not a problem, then:
I overrode the HardBreak extension, since it's the one that use the Shift-Enter keybinding. It looks something like;
const CustomHardBreak = HardBreak.extend({
addKeyboardShortcuts() {
return {
"Mod-Enter": () => this.editor.commands.setHardBreak(),
"Shift-Enter": () => this.editor.commands.addNewline(),
};
},
});
And used it like so;
editor = new Editor({
extensions: [
customNewline,
CustomHardBreak,
]
});
Use the default editor command createParagraphNear. E.g this.editor.commands.createParagraphNear()
I tried creating a custom extension from your code and ended up with something similar to the command above, i.e;
export const customNewline = Extension.create({
name: "newline",
priority: 1000, // Optional
addCommands() {
return {
addNewline:
() =>
({ state, dispatch }) => {
const { schema, tr } = state;
const paragraph = schema.nodes.paragraph;
const transaction = tr
.deleteSelection()
.replaceSelectionWith(paragraph.create(), true)
.scrollIntoView();
if (dispatch) dispatch(transaction);
return true;
},
};
},
addKeyboardShortcuts() {
return {
"Shift-Enter": () => this.editor.commands.addNewline(),
};
},
});
And added this as an extension in my editor instance.
PS:
They both work, almost exactly the same, I haven't found a difference yet. But there's somewhat of a 'catch' if you would call it that; Both these methods don't work on empty lines/nodes, a character has to be added before the cursor for it to work, any character, even a space.
In TipTap 2.0 I am able to use this custom extension:
const ShiftEnterCreateExtension = Extension.create({
addKeyboardShortcuts() {
return {
"Shift-Enter": ({ editor }) => {
editor.commands.enter();
return true;
},
};
},
});
To make shift + enter behave like enter.
In my case I actually wanted enter to do something different. So I use prosemirror events to set a ref flag on whether shift was pressed. Than I check that flag under the "Enter" keyboard event -- which could be triggered normally or through the shift + enter extension.
I'd like to know if is there any way or library available to import all modules from a folder without know the names.
Example:
└── routes/
└──── x.routes.ts
└──── y.routes.ts
└──── z.routes.ts
└──── ...
Then makes those available from mymodule.ts for example.
Unfortunately, import works only with explicit paths.
But webpack provides the following feature:
https://webpack.js.org/guides/dependency-management/#context-module-api
I was able to do reading the files path from a folder with fs.readDirSync()
Then importing them like:
const { default: Module } = require( _module.path ) (modules that was exported by default) all into an iterator.
I share my entire solution:
This abstracted function returns all modules paths in a directory based on a dir, a criteria for get them and an exclude list of filenames:
export const getModules = ( params: IM ): IModule[] => {
const { dir, criteria, excludeList } = params
let { modulesList=[] } = params
const modulePrefix: string = getModulePrefix() // includes slash
const modulesDir: string = path.resolve( `${modulePrefix}${dir}` )
const modules: string[] = fs.readdirSync( modulesDir )
const r: RegExp[] = ( process.platform === 'win32' ) ? [/\\\w+$/, /^\\/] : [/\/\w+$/, /^\//]
modules.forEach(( _module: string ) => {
const modulePath: string = path.resolve( modulesDir, _module )
if ( fs.statSync(modulePath).isDirectory() ) {
// Recursive call
modulesList = getModules({ dir:modulePath, criteria, excludeList, modulesList })
} else if ( criteria.test(_module) && !excludeList.includes(_module) ) {
modulesList.push({
path: modulePath,
filename: _module,
type: modulesDir.match(r[0])![0].replace(r[1], '')
})
}
})
return modulesList
}
The IM params types:
interface IM {
readonly dir: string
readonly criteria: RegExp
readonly excludeList: string[]
modulesList?: IModule[]
}
This is a cross-platform one. It will works on unix and windows based systems.
The modules types returned are described by:
export interface IModule {
path: string
filename: string
type: string
}
And the function above is used by other functions (not abstracted) that get those module paths based in some rules as follows (in this case, for middlewares types in middlewares folder):
export const getMiddlewares = (): IModule[] => {
const dir: string = './src/middlewares'
const criteria: RegExp = /^.+\.(ts|js)$/
const excludeList: string[] = [
'tmpl.ts',
'tmpl.js'
]
return getModules({ dir, criteria, excludeList })
}
Then modules can be imported as resources like (I have this in another function called loadResources()):
// modulePaths has IModule[] as a type
modulePaths.forEach(( _module: IModule ) => {
const { default: Module } = require( _module.path )
if ( Module instanceof Function ) {
const $resource: Resource = Module( service )
if ( callback instanceof Function )
callback( $resource )
;
} else {
console.warn( chalk.yellow(
`WARNING: #${_module.type} ‚Üí ${_module.filename.replace(/\.(ts|js)$/, '')} must export a function by default`
))
}
})
I Hope to help to any one else who is searching for this solution.
The getModules() function is re-usable and it is abstracted to import any thing you want based on your own rules.
Source code can be reviewed in my repository: https://github.com/bananasplit-js/bananasplit-js/blob/65d7cdd9dca4c89f8f32a7ae13c63ed098dab7c2/src/providers/core/helpers/resources.ts#L43
The plugin gulp-pug allows to pass global variables to pug files via data property.
What if we don't need full data set in each .pug file? To implement conditional data injection, we need to access to current vinyl file instance inside pipe(this.gulpPlugins.pug({}) or at least to know the source file absolute path. Possible?
const dataSetForTopPage = {
foo: "alpha",
bar: "bravo"
};
const dataSetForAboutPage = {
baz: "charlie",
hoge: "delta"
};
gulp.src(sourceFileGlobsOrAbsolutePath)
.pipe(gulpPlugins.pug({
data: /*
if path is 'top.pug' -> 'dataSetForTopPage',
else if path is 'about.pug' -> 'dataSetForAboutPage'
else -> empty object*/
}))
.pipe(Gulp.dest("output"));
I am using gulp-intercept plugin. But how to synchronize it with gulpPlugins.pug?
gulp.src(sourceFileGlobsOrAbsolutePath)
.pipe(this.gulpPlugins.intercept(vinylFile => {
// I can compute conditional data set here
// but how to execute gulpPlugins.pug() here?
}))
// ...
It was just one example, but we will deal with same problem when need to conditional plugins options for other gulp plugins, too. E. g:
.pipe(gulpPlugins.htmlPrettify({
indent_char: " ",
indent_size: // if source file in 'admin/**' -> 2, else if in 'auth/**' -> 3 else 4
}))
You'll need to modify the stream manually - through2 is probably the most used package for this purpose. Once in the through2 callback, you can pass the stream to your gulp plugins (as long as their transform functions are exposed) and conditionally pass them options. For example, here is a task:
pugtest = () => {
const dataSet = {
'top.pug': {
foo: "alpha",
bar: "bravo"
},
'about.pug': {
foo: "charlie",
bar: "delta"
}
};
return gulp.src('src/**/*.pug')
.pipe(through2.obj((file, enc, next) =>
gulpPlugins.pug({
// Grab the filename, and set pug data to the value found in dataSet by that name
data: dataSet[file.basename] || {}
})._transform(file, enc, next)
))
.pipe(through2.obj((file, enc, next) => {
const options = {
indent_char: ' ',
indent_size: 4
};
if(file.relative.match(/admin\//)) {
options.indent_size = 2;
} else if(file.relative.match(/auth\//)) {
options.indent_size = 3;
}
file.contents = new Buffer.from(html.prettyPrint(String(file.contents), options), enc);
next(null, file);
}))
.pipe(gulp.dest('output'));
}
For the pug step, we call through2.obj and create the pug plugin, passing it data grabbed from our object literal, indexed by filename in this example. So now the data passed into the compiler comes from that object literal.
For the html step you mention, gulp-html-prettify doesn't expose its transform function, so we can't reach into it and pass the transform back to the stream. But in this case that's OK, if you look at the source it's just a wrapper to prettyPrint in the html package. That's quite literally all it is doing. So we can just rig up our step using through2 to do the same thing, but changing our options based on the vinyl file's relative path.
That's it! For a working example see this repo: https://github.com/joshdavenport/stack-overflow-61314141-gulp-pug-conditional
How can I do a condition inside Gulp pipe to output to a different destination.
g.task('sass', function() {
return g.src(sources.sass).pipe(changed(output.css)).pipe(sass({
style: 'compressed',
sourcemap: true
})).pipe(function() {
if (..) {
g.dest(output.css);
} else {
g.dest(output.css2);
}
}).pipe(notify('scss converted to css and
compressed
<%=f ile.relative %>'));
});
Use the gulp-if plugin:
var gulpif = require('gulp-if');
g.task('sass', function() {
return g.src(sources.sass)
.pipe(changed(output.css))
.pipe(sass({style:'compressed', sourcemap:true}))
// Conditional output
.pipe(gulpif(condition1, g.dest(output.css)))
.pipe(gulpif(condition2, g.dest(output.css2)))
.pipe(notify('scss converted to css and compressed <%= file.relative %>'));
});
The solution without any new dependencies:
The gulp.src function returns a stream, so you can use it ;)
Look at the gulp docs.
gulp.task('task', function () {
let stream = gulp.src(sources.sass)
.pipe(changed(output.css)).pipe(sass({
style: 'compressed',
sourcemap: true
}));
if (2 + 2 === 4) {
stream = stream
.pipe(someModule())
.pipe(someModule2());
}
else {
stream = stream
.pipe(someModule3())
.pipe(someModule4());
}
stream = stream.pipe(notify('scss converted to css and compressed'));
return stream;
});
I suggest you to use expression something like this:
g.task('sass', function(){
var destinationFileName;
if (...) {
destinationFileName = 'output.css';
} else {
destinationFileName = 'output2.css';
}
return g.src(sources.sass)
.pipe(changed(output.css))
.pipe(sass({style:'compressed', sourcemap:true}))
.pipe(g.dest(destinationFileName))
.pipe(notify('scss converted to css and compressed <%= file.relative %>'));
});
If you do not want to add any extra dependency, you can trust good old Elvis (?:):
g.task('sass', function() {
return g.src(sources.sass)
.pipe(changed(output.css))
.pipe(sass({style:'compressed', sourcemap:true}))
// Conditional output
.pipe(condition ? g.dest(output.css) : g.dest(output.css2))
.pipe(notify('scss converted to css and compressed <%= file.relative %>'));
});
Or for multiple cases:
.pipe(condition1 ? g.dest(output.css) : gulp.util.noop())
.pipe(condition2 ? g.dest(output2.css) : gulp.util.noop())
If I want a module that is instantiable, let say, a module that handles storing preferences in a subcookies, and i want the main cookie to be configurable, but i don't want it to be a widget... what patterns should i use with YUI?
the end code should be something:
Y.use('my-pref-manager', function(Y){
var A = Y.my-pref-manager.prefStore('A"),
B = Y.my-pref-manager.prefStore('B");
// A and B are now loaded with the contents of cookies A and B, if they exist
A.set('xy', 123 );
});
So far i either found patterns that create widgets within my-module or i have to use methods directly in my-method which will be globals and lack initializers, etc.
There is a bunch of ways of doing this. You could do it using Y.Base.create, like below. The code might not be production ready, or even working properly, but hopefully it answers how you can create a module without it being a Widget.
The code below creates a module that extends Y.Base. This let us use Attributes and other cool things. Check the doc for Y.Base.
YUI.add('my-pref-manager', function (Y) {
var PrefManager = Y.Base.create('myPrefManager', Y.Base, [], {
initializer: function () {
this.after('prefsChange', this.changePref);
},
changePref: function (e) {
Y.Cookie.setSub(this.get('prefStore'), e.subAttrName, this.get(e.subAttrName));
},
setPref: function (name, val) {
this.set('prefs.'+name, val);
},
getPref: function (name) {
return this.get('prefs.'+name);
}
}, {
ATTRS: {
prefStore: {
value: null,
setter: function (val) {
return Y.Cookie.set(val, val);
}
},
prefs: {
value: {}
}
}
});
Y.namespace('My').PrefManager = PrefManager;
}, '0.0.1', {
requires: ['base', 'cookie']
});
YUI().use('my-pref-manager', function (Y) {
var A = new Y.My.PrefManager({prefStore: 'userPrefs'}),
B = new Y.My.PrefManager({prefStore: 'displayPrefs'});
A.setPref('x', 3);
A.setPref('y', 54);
B.setPref('tty', 7);
console.log(A.getPref('x')); // 3
});
Try it out: http://jsfiddle.net/B62nu/