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
Add more variables (cont.) #4652
Conversation
This adds more variables, in particular: {url:domain} {url:auth} {url:scheme} {url:user} {url:password} {url:host} {url:port} {url:path} {url:query}
Variables such as {url} and {selection} can now be explicitly escaped by prefixing them with a backslash: \{url} Only valid variables that would've been replaced are escaped, so '\{notavariable}' stays '\{notavariable}' See qutebrowser#1861
This reverts commit c6f51c4.
tests/end2end/features/misc.feature
Outdated
@@ -441,6 +441,49 @@ Feature: Various utility commands. | |||
And I run :message-info {clipboard}bar{url} | |||
Then the message "{url}barhttp://localhost:*/hello.txt" should be shown | |||
|
|||
Scenario: Variable {url:pretty} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any chance you could take a quick look into what it'd take to make those unit rather than end2end tests? If it ends up not being possible easily then those are fine, but if it's possible, I'd strongly prefer that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I can make it unit tests (because most variable expansion requires a tabbedbrowser, which I'm not entirey sure I can properly mock), but I did get it to a python e2e test, does that sound ok?
tests/end2end/features/misc.feature
Outdated
Then the message "foo" should be shown | ||
|
||
# Tests for HTTP basic auth variables are missing here, pytest-bdd bug | ||
# https://github.com/The-Compiler/qutebrowser/pull/1921#issuecomment-244695985 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in September 2016, so it should be possible to add those.
Can you elaborate? I'm getting a bit worried we're starting to micro-optimize things which don't actually make a difference in practice (a few microseconds or whatever when executing a command really doesn't make a difference). How is it "extremely" inefficient? Why is it unnecessary? I'm really grateful for your performance work in the areas where it's really needed (and there are some of those, granted), but when deciding between readability/simplicity vs. performance, I'm still going to default to the former, especially when there's no evidence that it makes a difference in practice. |
Florian Bruhin writes:
Can you elaborate? I'm getting a bit worried we're starting to
micro-optimize things which don't actually make a difference in practice (a
few microseconds or whatever when executing a command really doesn't make a
difference). How is it "extremely" inefficient? Why is it unnecessary? I'm
really grateful for your performance work in the areas where it's really
needed (and there are some of those, granted), but when deciding between
readability/simplicity vs. performance, I'm still going to default to the
former, especially when there's no evidence that it makes a difference in
practice.
Firstly, for me, the inefficient code (just building and iterating over the
mapping, the things that could be eliminated by declaring a global and passing
the tabbedbrowser in) had an overhead of ~0.01 ms on my machine. The new code
has an overhead of ~0.04 ms. I really don't like introducing performance
regressions, and a 0.03ms regression per command is pretty large. I run a lot
of commands per page, which means I will spend a comparable amount of time in
this code as in my python adblocker on lightweight pages (which is currently
about 0.2 ms per request overhead. And of course, this is all on my ~months
old, 1k$ laptop.
If this work was justified, I wouldn't mind too much, but to me, this is just
pure wasted cycles. This code sets a bad example of using a mapping as a list.
Maybe it's just me, but sending code off to (I'm guessing) ~1000s of machines
to run pointless loops over hashmaps dosen't feel right - it's easily
preventable and wastes the user's resources. Even worse, a new programmer
might come across this code someday, and think that looping over mappings is
the proper way to use them. When a new contributor adds a new expansion, they
will be increasing the regression even further (at no fault of their own). I
don't really see how this particular structure saves anyone any time, whareas
I have wasted a LOT of my time profiling/scouring the codebase only to find
many instances of this issue (rather than more interesting discoveries), and I
don't want to do that forever. Fixing only the hottest instances would take
much, much more time than just doing it properly everywhere, and result in a
slower final product.
Does it make a difference in this case? Probably not, but that's only because
there's a much larger version of this same problem (the config system
iterating over dicts and keybindings iterating over the binding dict)
overshadowing everything else.
Finally, in this particular case, I would say this version is more arcane.
Having the dictionary inline dilutes the purpose of this function and makes it
harder to understand. Declaring it as a constant in the module with a real
name and docstring (not 'variables') would be much more readable and
maintainable, imho.
|
It's not exactly "extremely inefficient", but I can see no benefit in doing it - it doesn't make the code simpler, or easier to read, or something like that. |
Thanks for the explanation. Before this, I never realized that you regard this specific thing as a (recurring) problem - as far as I remember, you never told me 😉 I agree it's something which should be done rather sparingly, but in some cases there isn't really a better solution - like for matching keybindings IIRC, where the matching algorithm isn't just a fixed lookup. Those things could be a list of tuples instead (because they aren't really used as a dict), but that wouldn't really change anything either, and just make the syntax more awkward, so I don't really see the gain. They probably should be more fitting data structures (perhaps a trie for keybindings?) though. Either way, I'll try to keep an eye on it in the future. As for this specific case:
So what would you (both) suggest instead? Note that:
Here's what I can think of:
I'm open for suggestions - I'd probably go for 1. in the short term and 3. in the long term. 2. would avoid the loop, but it seems to be like that would indeed make the code harder to read (or at least more verbose). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some small issues with the tests, but I'll fix that up while merging.
|
||
|
||
def test_command_expansion_clipboard(quteproc): | ||
quteproc.send_cmd(':debug-set-fake-clipboard "{}"'.format('foo')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formatting with a constant string makes little sense...
command_expansion_base( | ||
quteproc, '{clipboard}bar{url}', | ||
"foobarhttp://localhost:*/hello.txt") | ||
quteproc.send_cmd(':debug-set-fake-clipboard "{}"'.format('{{url}}')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same
|
||
|
||
def test_command_expansion_basic_auth(quteproc, server): | ||
url = 'http://user1:password1@localhost:{port}/basic-auth/user1/password1' \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer adding parens to backslash continuation
@The-Compiler Regarding the "related" problem you mentioned: I implemented trie data structure for keybindings (https://paste.the-compiler.org/view/c15f385c (by the way the captcha says "type in the letters" while reCaptcha isn't that)), however the implementation is incomplete:
I may open a PR later, when it works properly and I can check that it does improve performance. |
@user202729 Nice! I opened a separate issue for that: #4721 |
Florian Bruhin writes:
- If no replacement is used, commands should be able to execute without a tabbed_browser/URL being available.
- Using `{{url}}` should escape the `{url}` variable.
Here's what I can think of:
- Moving the `variables` dict and `replace_variables` into `CommandRunner`, and filling it up with the escaped replacements in `__init__`
- For every replacement like `url`, add `{url}: lambda: {url}` hardcoded into the dict (with the cost of making it double as verbose)
- Have a real commandline parser (the goal of #2017)
I'm open for suggestions - I'd probably go for 1. in the short term and 3. in the long term. 2. would avoid the loop, but it seems to be like that would indeed make the code harder to read (or at least more verbose).
I'm not sure exactly what you meant by "should be able to execute without a
tabbed_browser/URL being available". In this function, I think we're getting
the tabbedbrowser/url every time (is there a reason to avoid that). I think we
can get something much better by just lifting the logic that dosen't change
into the root level (or CommandRunner, but that would be more
invasive/confusing imo).
Does http://paste.debian.net/plain/1078135 seem like it has any big issues?
Naively timing it seems to make 'replace_variables' a little under an order of
magnitude faster for me.
|
This is a continuation of #1937
I'm going to ignore the extremely inefficient and unnecessary dict creation/loop in the interest of making the diff as small as possible.
Together with #4651, will close #4647.
This change is