Closed tommcdo closed 10 years ago
Let's use this issue as a place to start collecting real world transformation requirements. I don't want to dig the current hole too deep as I suspect there are more variants than the current one character expansion system can handle.
I think a good start would be getting all the cases handled in abolish's coercion -- preferably with the relevant characters matching up.
Emphasis on real world.
Maybe some kind of modifier to the existing ones would make more sense. The opposite case to my example may be necessary, so Foo/Bar
needs to be foo_bar
-- in this scenario, %u
doesn't seem right, as %u
should be the un-case-transformed variety.
I can see 3 case-transformation modifiers, as @qstrahl sort of mentioned: lower case, UPPER CASE, and Mixed Case. I'm having a hard time coming up with 3 modifier characters that seem related and intuitive, but something like %mu
could mean Mixed_Case
, where %Cd
could mean mean UPPER.CASE
. Are letters good modifiers?
Head and tail may also be necessary. Real-world example: PHP namespace and classname. So foo/bar/baz
may be for code like this:
namespace foo\bar;
class Baz { ... }
(And this also leads to converting /
to \
. Maybe a generic "change / to {char}" is needed?)
/
to .
(but would we actually need to do this?)/
to .
and _
to -
.::
. /
to -
is a gem naming convention but I can't think of a reason projectile would care.::
but no case transformation, as I recall./
to _
for table names. Plus the ruby stuff, but I can't recall needing to stack those with these._
to
, capitalize first letter for feature title./
to _
(for include guards) and to #
(for function names). Camelization would be helpful for GetFooIndent()
I think there should be support for applying an arbitrary number of transformations to a placeholder, and this may even eliminate the need for %u
and %d
(or they can exist as synonyms).
A categorized summary of transformations:
.
, _
, ::
, \
, -
, #
, delimiter removalMaybe a pipeline style syntax would be good, after giving each transformation a name: %{s|upper|colons|plural|head}
, given foo/bar/baz
would yield FOO::BARS
.
EDIT: I didn't mean to make an implicit requirement in there. I put plural
before head
and the result still came out pluralized, but supporting that would over-complicate the code. Linearly applying transformations in the order they're listed would be totally sufficient.
EDIT 2: I think %{upper|colons|plural|head}s
looks nicer.
Just thought of a potential advantage to this arbitrary pipeline style. Functions for placeholder transformations can be defined in some namespace, e.g. projectile#placeholder#upper
, and these functions can be directly called by name without the need for configuration. Then users can easily add other transformation functions and they'll just work naturally. The common ones that we've come up with so far can ship with the core plugin. This could even lead to trimming out some of the ones that don't seem too vital, leaving them to users to define if needed.
What do you think?
That looks like a very flexible and helpful solution, and I can't spot any immediate flaws with it. :+1:
I want to allow for a bit more data to pour in before jumping to the implementation phase. The latest suggestion is still a bit hypothetical obsessed (uppercase/lowercase/camelcase) rather than practical (Cucumber feature case).
I like the curly brace notation. One weakness is that it doesn't allow for parameters, so it doesn't allow for arbitrary transformations like _
to -
or /
to a character we haven't thought of.
I considered that weakness. If it's decided to omit the ability to include parameters, it will almost surely come up as a feature request.
It could be solved by just defining another function. I'd imagine the complexity of supporting parameters outweighs the minor inconvenience of having to write another function (and it probably won't be needed too often).
Python support could be performed loosely like Java support. Fortunately for projectile, Java has the convention one class per file while Python does not. That alone may make it more of a hassle for you since people will probably perpetually file bug reports complaining.
Had a bit of an aha moment today when I realized we could mitigate the need for parameters by allowing transformations to be composed of two functions. Something like {from-underscore|to-hyphen}
. Behind the scenes the from
function could swap out the target for some binary character, and the to
function could target that binary character. And maybe we could surround the whole thing with an implicit from-slash
and to-slash
to shorten the most common case.
And thinking out loud here, we could probably just implement all possibilities as pairs of from/tos, although a few get a bit contrived. Here are a few examples using a from:to,from2:to2
style notation (identical to 'matchpairs'
):
{/:.,_:-}
{/:colons,_:capitalize,start:capitalize}
{/:head,/:\}
and {/:tail,_:capitalize,start:capitalize}
That PHP example has a bit of repetition, which we could get more clever about:
{/:head:\}
and {/:tail,words:capitalize}
I think this is probably the direction to go in. Thoughts?
It's getting pretty hard to read. Might just be me, but my brain is starting to see pairs of comma-delimited values separated by colons.
Also, specifying the from
seems kinda redundant, as we should know the initial form ahead of time. Plus, we basically still have zero-parameter transformations capable of exactly one thing.
Maybe I'm not seeing the big picture. I'll keep thinking about it for a while.
I don't see what's redundant about the from. {from-slash|to-hyphen}
and {from-underscore|to-hyphen}
are both valid. You could just make two different transformations, but breaking them apart changes the worst case scenario from n * m
to n + m
.
I think the real pattern I'm latching onto is that pretty much all of the transformations can be thought of some combination of "split", "transform", and "join". Character replacement is just split on one character and join on another. Head and tail split and join on slashes and transform by dropping some components. Camelization splits on _
(or maybe some other character), transforms by capitalizing each component, and joins on an empty string. The only real exception is inflection, but you can also think about that as having a split that just returns one element.
My thinking is continuing to evolve, but I do think having a way to reuse the slash modifications on other characters is going to be a piece of the final product.
I think I'm starting to agree. My original assumption was that the first input was always going to be a file path component, so the only delimiter to worry about was /
. But even under the assumption that we always start with a filename, there are other from-
characters to worry about -- for instance, since a lot of JavaScript code is done without a predefined filename-to-identifier mapping, one might decide to make up his own conventions: foo/bar-baz/qux
might map to Foo.BarBaz.Qux
. (That example might be a little contrived but it's not a far stretch.)
If I can offer a suggestion: to improve readability, maybe we can avoid using delimiter characters as transformation names, and prefer words. So slash
instead of /
. This will also ease the pain of transforming to or from ,
and :
, which could get quite hard to read (for humans and computers alike).
I'm actually circling back to the earlier proposal, because my whole "split/transform/join" vision has some unanswered questions, and I don't see any reason why we can't gracefully transition to it later if/when those questions are answered.
The new system is documented in the help file, and I plan to axe the old system before 1.0. I think I've covered all the real world transformations except inflections (which best I can tell is a Rails only thing) and _
to `(because I haven't decided what to name it, since
spacewould conventionally be
/to
). You'll have to build your own mixed case transformation out of
{camelcase|capitalize}`.
:+1:
Just thought of something. {}
is a feasible occurrence in code, e.g.
class Foo {}
Should there be a way to prevent this from being expanded?
{open}{close}
Guess I should have read the docs.
Added a couple more transformations, and calling this goose cooked. I'd still like to find a way to do arbitrary punctuation. Naming everything doesn't really scale well. On the plus side I still have yet to want to act on anything other than a /
or a _
, so maybe only the replacement has to be arbitrary.
I just ran into another transformation that might be generally useful. Prepend a leading /
only when a:input
is not empty. This would imply that something like dirname
was called first. Here's the use case:
:Econtroller Foo
should generate this:
<?php
namespace Controllers;
class Foo extends Controller {
}
:Econtroller Foo/Bar
should generate this:
<?php
namespace Controllers\Foo;
class Bar extends Controller {
}
Since Controllers
is part not part of the wildcard match, it has to be inserted directly into the template, but it needs decide whether or not to include a \
for sub-namespaces.
My .projections.json
file looks kinda like this:
{
"app/src/Controllers/*.php": {
"command": "controller",
"template": [
"<?php",
"namespace Controller{dirname|leading|backslash};",
"class {basename} extends Controller {open}",
"{close}"
]
}
}
I've defined the leading
transformation as follows:
function! g:projectile_transformations.leading(input, o)
return substitute(a:input, '^\ze.\+$', '/', '')
endfunction
Do you think this would be useful for more cases?
Can you work around with a separate "app/src/Controllers*.php"
projection?
I considered it, but it's really quite undesirable to include the leading /
in the name. Then I'd also have to think about whether I should invoke it using the leading /
or not, depending on whether there's a subdirectory.
Anyway, my g:projectile_transformations.leading
extension works just fine for me, but I was wondering if it's general enough to make it into the core plugin. Admittedly it's kind of a weird one; it's only more useful than hard-coding the character directly in your template when there's a chance that the expansion is empty (meaning, as far as I can tell, you must have used dirname
first).
I mean a separate projection with just the template.
Oh, that separate. I suppose it wouldn't be too bad to use the leading /
when I know I'm creating one, but I'd still have to choose to omit it when creating a new class with no subdirectory. Unless I'm still missing something.
This objection is just about a bit of weirdness in the projection definitions, and not any usability weirdness, right? I think "leading" is kind of weird too, so I consider that a draw.
It is a usability weirdness, unless, again, I'm still missing something. Basically, in some cases the template needs to put a /
and in some cases it doesn't. The former is determined by presence of a subdirectory, and the latter by absence. If I define one projection as "app/src/Controllers*.php"
, then it will take care of the cases where a /
is needed in the template, but it can't be used for the cases where it's not needed. Likewise, the "app/src/Controllers/*.php"
projection is good for when a /
is not needed, but no good for when it's needed.
I'm imagining that the thing I'm missing is that projectile.vim will match one projection and use a template from another -- is this the case? Otherwise I'm just not seeing it.
By usability I mean things like navigation commands work the same, or that you literally can't create a configuration that supports all your requirements. {dirname}
on "/foo"
should return ""
in our implementation, so as I understand it, a "app/src/Controllers*.php"
should cover all controller cases, even if that is a bit inconsistent with "app/src/*.php"
.
"Leading" feels awkward, and I have a hunch subsequent generalization (assuming that actually happens) will shine a light on a better solution. If the only cost of kicking this down the road is you have to think a bit harder about when to use a slash when you first create your templates, I'm happy to pay it.
Ah, there's the thing I was missing. I'm satisfied for now. Thanks!
Could {camelcase}
remove hyphens along with underscores?
What's the use case?
The convention for filenames on a project I'm working on is something like views/edit-step.coffee
. If I want to make a new view with :Eview edit-your-mom
, I'd like the template to produce something like:
App.Views.EditYourMom = Backbone.view.extend
Is it part of a framework or home grown? Adding either way but the latter carries significantly less weight if this conflicts with someone else's usage.
It's not a framework, but it's the convention on this project. Is there a way to define my own transformations? I don't see anything about it in the docs.
You can define your own like this:
if !exists('g:projectile_transformations')
g:projectile_transformations = {}
endif
function! g:projectile_transformations.foo(input, o)
return some_manipulation_of(a:input)
endfunction
This makes foo
available as a transformation function.
Unofficial, fwiw. In particular the idea of sharing a .projections.json
that depends on custom transformations strikes me as broken.
The camelcase change has been added, as was promised.
Right, but if I want another developer to be able to use it, it's not as simple as having the .projections.json
file in the project anymore. It would be nice if you could do:
{
"transformations": [
"function! g:projectile_transformations.foo(input, o)",
" return some_manipulation_of(a:input)",
"endfunction"
]
}
Or maybe something simpler even?
{
"transformations": {
"foo": "some_manipulation_of(a:input)"
}
}
Arbitrary code evaluation aside, I don't want to tie the format to VimL.
Ah, good point.
In some of my projects, I have lowercase filenames mapped to mixed case class names. So, for example, the class
Foo_Bar
is defined inclasses/foo/bar.php
.In addition to the placeholders like
%u
, it would be nice to have a corresponding placeholder like%U
that would transformfoo/bar
intoFoo_Bar
.