Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support functions for all options (fix #139) #168

Merged
merged 1 commit into from
Jun 28, 2016

Conversation

hgwood
Copy link
Contributor

@hgwood hgwood commented May 2, 2016

It is not entirely clear from the issue if it is only about src or if dest is also in scope. Anyway, here it is for src. I can replicate the behavior for dest very simply.

A few notes:

  • I think mixing vinyl-fs options and through2 options in the option object can be error-prone (Quirk in passing read option from src to through2? #153) and can complexify the implementation (see how I need to avoid calling through2 options that are functions?). May I suggest moving through2 options to a through2 property in vinyl-fs options? I believe that would be simpler and clearer, both for the caller and the callee. It would be a breaking change.
  • I did not use value-or-function because I did not see the benefit of checking the types of the options. Also I saw defaulting to null as a bit dangerous and surprising for users of vinyl-fs.
  • What about jumping to Node 4 LTS compatibility for vinyl-fs 3? That would enable a bunch of ES6 features.

@phated
Copy link
Member

phated commented May 2, 2016

@hgwood thanks for submitting this. I believe we wanted the logic to be applied to .dest and .symlink options.

  • Wouldn't we want to resolve the through2 options that are a function? Since the bug was caused by read being a function, it should always be resolved to a value.
  • value-or-function should be used because we only want options to accept a certain type. I'm also noticing that you missed the fact that each function-option should be called with the vinyl file object (one of the primary reasons value-or-function exists).
  • We are sticking with backwards compat to node 0.10 because it is heavily used (myself included).

@hgwood
Copy link
Contributor Author

hgwood commented May 3, 2016

I believe we wanted the logic to be applied to .dest and .symlink options.

Will do.

you missed the fact that each function-option should be called with the vinyl file object

I meant to forward the glob to function-options but forgot. Is the glob what you mean by "vinyl file object"? Otherwise could you specify what you mean? I don't see anything in src() that is available before options are used.

Wouldn't we want to resolve the through2 options that are a function? Since the bug was caused by read being a function, it should always be resolved to a value.

Just to be sure we share the same understanding:

  • read is a vinyl-fs option expecting a boolean.
  • read is also a through2 option (stream.Readable option actually) expecting a function.
  • vinyl-fs resolves this ambiguity by always considering the read option is meant for itself. It never forward the read option to through2.
  • There are other through2 options that expect a function, like write and writev.

If vinyl-fs supported passing function-options for through2 options then how could it distinguish between someone trying to pass a function to through2 and someone trying to pass a function-option that returns a function for through2? I think this could become very confusing for users. So I was careful to stay clear of this and my implementation only calls function-options for vinyl-fs own options.

we only want options to accept a certain type

Well, I'm not a fan of this (I prefer to embrace coercion), but I have noticed that's how vinyl-fs currently works: all options are type-checked already. As a result, adding value-or-function on top would amount to no gain. Unless all current type-checks are removed of course.

Quick pros and cons of value-or-function over current:

  • Pro: some (not all) type-checking throughout the program may be removed
  • Con: still have to type-check for "subtypes" of object (Date, RegExp, ...) (e.g. since)
  • Con: have to maintain a option-to-type map
  • Con: introduces complexity when checking an option for multiple possible types (e.g. since)
  • Con: introduces questions like "should filterSince do its own type-checking or should it rely on its parameter being an option that has been type-checked"?

That said, this is pretty much up to personal preference. You are the maintainer of this project and you decide. :)

@yocontra
Copy link
Member

yocontra commented May 4, 2016

@phated Any reason why we shouldn't apply this across the board? (src, dest, and symlink)

@hgwood
Copy link
Contributor Author

hgwood commented May 4, 2016

I've applied the behavior to dest and symlink. Function-options for src are passed glob and function-options for dest and symlink are passed file, except for dest's sourcemaps option, which is resolved prior to getting access to file.

@yocontra
Copy link
Member

yocontra commented May 4, 2016

@hgwood Assuming this looks good to @phated would need docs updates before merge

Thanks again for tackling this one! Really appreciate it 🙏

@hgwood
Copy link
Contributor Author

hgwood commented May 4, 2016

I have noticed booleanOrFunc and stringOrFunc in prepare-write.js. Their behavior matches value-or-function (returns null if the type's wrong). I don't want to break this behavior so now I have a good reason to use value-or-function. I'll refactor.

@phated
Copy link
Member

phated commented May 5, 2016

@hgwood correct, value-or-function was written based on the behavior already in vinyl-fs as a way to normalize all the options. Please use that in this implementation. If you need other functionality in that module, feel free to submit a PR and I'll review.

@hgwood
Copy link
Contributor Author

hgwood commented May 5, 2016

The new proposal leverages value-or-function. Thank you for you patience as I get more familiar with the code base. :)

I guess the only significant remaining question is: do we agree on the fact that we should only process options that are for vinyl-fs itself? See the second point in my earlier comment.

@phated
Copy link
Member

phated commented May 5, 2016

@hgwood I'm not really happy with the colliding options, so maybe our read option needs to be named something else. I don't really like the idea of having them as separate keys (e.g. options.through2 or similar). I'd like to hear @contra and @erikkemperman's thoughts.

@hgwood
Copy link
Contributor Author

hgwood commented May 5, 2016

@phated Sorry I think I might have confused you. The colliding option is actually not the problem.

You said:

Wouldn't we want to resolve the through2 options that are a function?

The thing is, some through2 options are functions (namely read, write, writev). So how can vinyl-fs tell the difference between a function that should be passed verbatim to through2, or a function that is a function-option to be passed to value-or-function? I don't see how it can. That's why my proposal do not process options that are not known to vinyl-fs itself.

@phated
Copy link
Member

phated commented May 5, 2016

@hgwood The value-or-function module already handles function options using the .function() method (which would always result in true for a function so it wouldn't resolve the function), but that isn't the actual problem. The actual problem is that we have a read option that is a boolean but it collides with through2's read option that is a function.

Edit: I agree that we shouldn't process options that vinyl-fs doesn't know or care about. Again, not the issue at hand in regards to read

@hgwood
Copy link
Contributor Author

hgwood commented May 5, 2016

Oh, right. The .function() thing was obvious, sorry.

@erikkemperman
Copy link
Member

Renaming the read option would work, but I guess I would also prefer having a separate namespace for the through2 options. For one thing, I think renaming will be inconvenient for lots of people. Also, what if future versions of through2 and/or vinyl-fs introduced other colliding option keys?

@phated
Copy link
Member

phated commented May 6, 2016

Separate namespaces get really messy, as we pass options down through glob-stream into node-glob and so forth. Would they be objects nested in objects nested in objects? How far does it go? It seems really clunky

@erikkemperman
Copy link
Member

True, it's not pretty. But eliminates collisions, current or future. Renaming the vinyl-fs option (to what?) is also not exactly elegant, imho.

I don't have a strong preference either way, but since you asked :-)

var overwrite = booleanOrFunc(options.overwrite, file);
options.flag = (overwrite ? 'w' : 'wx');
var options = normalizeOptions(opt, {
cwd: { type: 'string', defaultValue: process.cwd() },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awkward. Would there be a better way to implement it? Maybe something like defaultValue(valueOrFunction.string(opt.cwd, file), process.cwd())?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain why you find it awkward?

As for the alternative solution, do you mean I should duplicate this line for every option? Like:

var options = {
  cwd: defaultValue(valueOrFunction.string(opt.cwd, file), process.cwd()),
  mode: defaultValue(valueOrFunction.number(opt.mode, file), (file.stat ? file.stat.mode : null)),
  dirMode: ...
};

Copy link
Member

@phated phated May 12, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel it's awkward because you invented a DSL for the option + type + default. Your alternative is kind of what I was thinking. It might even look cleaner if we "destructured" the methods from value-or-function, e.g. var str = require('value-or-function').string;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand what you're saying. I'll have another go.

@hgwood
Copy link
Contributor Author

hgwood commented May 27, 2016

@phated I've refactored the code. Tell me if you think it's better.

}

var options = assign({}, opt, {
buffer: normalizeOption('boolean', true, opt.buffer, glob),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure how I'm feeling about the glob being passed in now that I see it. I don't think it is useful to determine the options. I'm going to think a bit more on a way to defer option resolution until we have a Vinyl File object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed glob. Deferring to get the file can be a PR on its own I think.


Default: The part of the path before the glob (if any) begins. For example, `path/to/**/*.js` would resolve to `path/to`. If there is no glob (i.e. a file path with no pattern), then the dirname of the path is used. For example, `path/to/some/file.js` would resolve to `path/to/some`.
- Values passed to the options must be of the right type, otherwise they will be ignored.
- All options can be passed a function instead of a value. The function will be called with `glob` as its only argument and must return a value of the right type for the option.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the functions aren't called with the glob

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right.

@phated
Copy link
Member

phated commented Jun 21, 2016

@hgwood just a few things. Feel free to squash everything together if you want.

@phated phated closed this Jun 21, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants