qutebrowser/doc/HACKING.asciidoc
2014-06-21 17:02:30 +02:00

362 lines
11 KiB
Plaintext

qutebrowser HACKING
===================
The Compiler <mail@qutebrowser.org>
:icons:
:data-uri:
:toc:
I `&lt;3` footnote:[Of course, that says `<3` in HTML.] contributors!
This document contains guidelines for contributing to qutebrowser, as well as
useful hints when doing so.
If anything mentioned here would prevent you from contributing, please let me
know, and contribute anyways! The guidelines are only meant to make life easier
for me, but if you don't follow anything in here, I won't be mad at you. I will
probably change it for you then, though.
If you have any problems, I'm more than happy to help! You can get help in
several ways:
* Send a mail to the mailing list at qutebrowser@lists.qutebrowser.org
(optionally
https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[subscribe]
first).
* Join the IRC channel `#qutebrowser` on http://www.freenode.org/[Freenode].
Finding something to work on
----------------------------
Chances are you already know something to improve or add when you're reading
this. It might be a good idea to ask on the mailing list or IRC channel to make
sure nobody else started working on the same thing already.
If you want to find something useful to do, check the `BUGS` and `TODO` files.
There are also some things to do if you don't want to write code:
* Help the community, e.g. on the mailinglist and the IRC channel.
* Improve the documentation.
* Help on the website and graphics (logo, etc.).
Using git
---------
qutebrowser uses http://git-scm.com/[git] for its development. You can clone
the repo like this:
----
git clone git://the-compiler.org/qutebrowser
----
If you don't know git, a http://git-scm.com/[git cheatsheet] might come in
handy. Of course, if using git is the issue which prevents you from
contributing, feel free to send normal patches instead, e.g. generated via
`diff -Nur`.
Finding the correct branch
~~~~~~~~~~~~~~~~~~~~~~~~~~
Currently, qutebrowser is developed in the `master` branch. Feature branches
are used by me occasionally and pushed as a backup, but frequently
force-pushed. Do *not* base your work on any of the feature branches, use
`master` instead.
After v0.1 is released, an additional `development` branch will be added. Then,
base new features on the `development` branch, and bugfixes on the `master`
branch.
You can then checkout the correct branch via:
----
git checkout base-branch <1>
----
<1> Of course replace `base-branch` by `development` or `master`.
Getting patches
~~~~~~~~~~~~~~~
After you finished your work and did `git commit`, you can get patches of your
changes like this:
----
git format-patch origin/master <1>
----
<1> Replace `master` by the branch your work was based on, e.g.
`origin/develop`.
Useful utilities
----------------
Checkers
~~~~~~~~
In the _scripts/_ subfolder, there is a `run_checks.py` script.
It runs a bunch of static checks on all source files, using the following
checkers:
* Unit tests using the Python
https://docs.python.org/3.4/library/unittest.html[unittest] framework
* https://pypi.python.org/pypi/flake8/1.3.1[flake8]
* https://github.com/GreenSteam/pep257/[pep257]
* http://pylint.org/[pylint]
* A custom checker for the following things:
- untracked git files
- whitespace/CRLF problems
- `set_trace` invocations
- VCS conflict markers.
If you changed `setup.py` or `MANIFEST.in`, add the `--setup` argument to run
the following additional checkers:
* https://pypi.python.org/pypi/pyroma/0.9.3[pyroma]
* https://github.com/mgedmin/check-manifest[check-manifest]
Please make sure this script runs without any warnings on your new
contributions. There's of course the possibility of false-positives, and the
following techniques are useful to handle these:
* Use `_foo` for unused parameters, with `foo` being a descriptive name. Using
`_` is discouraged.
* If you think you have a good reason to suppress a message, add the following
comment:
+
----
# pylint: disable=message-name
----
+
Note you can add this per line, per function/class, or per file. Please use the
smallest scope which makes sense. Most of the time, this will be line scope.
+
* If you really think a check shouldn't be done globally as it yields a lot of
false-positives, let me know! I'm still tweaking the parameters.
Profiling
~~~~~~~~~
In the _scripts/_ subfolder there's a `run_profile.py` which profiles the code
and shows a graphical representation of what takes how much time.
It needs https://pypi.python.org/pypi/pyprof2calltree/[pyprof2calltree] and
http://kcachegrind.sourceforge.net/html/Home.html[KCacheGrind]. It uses the
built-in Python https://docs.python.org/3.4/library/profile.html[cProfile]
module.
Debugging
~~~~~~~~~
In the `qutebrowser.utils.debug` and `qutebrowser.utils.signal` modules there
are some useful functions for debugging. In particular you should use
`set_trace` from `qutebrowser.utils.debug` instead of `pdb` to set breakpoints
or you'll get annoying Qt error messages.
When starting qutebrowser with the `--debug` flag you also get useful debug
logs. You can add +--logfilter _category[,category,...]_+ to restrict logging
to the given categories.
With `--debug` there are also some additional +debug-_*_+ commands available,
for example `:debug-all-objects` and `:debug-all-widgets` which print a list of
all Qt objects/widgets to the debug log -- this is very useful for finding
memory leaks.
Useful websites
~~~~~~~~~~~~~~~
Some resources which might be handy:
* http://qt-project.org/doc/qt-5/classes.html[The Qt5 reference]
* https://docs.python.org/3/library/index.html[The Python reference]
* http://httpbin.org/[httpbin, a test service for HTTP requests/responses]
* http://requestb.in/[RequestBin, a service to inspect HTTP requests]
* http://greenbytes.de/tech/tc2231/[Test cases for the `Content-Disposition`
header]
Hints
-----
Logging
~~~~~~~
Logging is used at various places throughout the qutebrowser code. If you add a
new feature, you should also add some strategic debug logging.
Unless other Python projects, qutebrowser doesn't use a logger per file,
instead it uses custom-named loggers.
The existing loggers are defined in `qutebrowser.utils.log`. If your feature
doesn't fit in any of the logging categories, simply add a new line like this:
[source,python]
----
foo = getLogger('foo')
----
Then in your source files, you have two different possibilities:
[source,python]
----
from qutebrowser.utils.log import foo as logger
...
logger.debug("Hello World")
----
or
[source,python]
----
import qutebrowser.utils.log as log
...
log.foo.debug("Hello World")
----
Use the first approach if you only need to use a single logging category. If
you need more than one category, use the second approach instead.
The following logging levels are available for every logger:
[width="75%",cols="25%,75%"]
|=======================================================================
|criticial |Critical issue, qutebrowser can't continue to run.
|error |There was an issue and some kind of operation was abandoned.
|warning |There was an issue but the operation can continue running.
|info |General informational messages.
|debug |Verbose debugging informations.
|=======================================================================
Commands
~~~~~~~~
qutebrowser has the concept of functions which are exposed to the user as
commands.
Creating a new command is straightforward:
[source,python]
----
import qutebrowser.commands.utils as cmdutils
...
@cmdutils.register(...)
def foo():
...
----
The commands arguments are automatically deduced by inspecting your function.
If your function has a `count` argument with a default, the command will
support a count which will be passed in the argument.
If the function is a method of a class, the `@cmdutils.register` decorator
needs to have an `instance=...` parameter which points to the (single/main)
instance of the class. Registering commands with multiple instances is not
supported. For an example on how to solve that problem, see
`qutebrowser.browser.command` which dispatches commands to the currently active
tab.
The `instance` parameter is a dotted attribute-path, leading from the main
Application instance to your object instance.
For example, if the Application object has an attribute `status`, which then
has an attribute `prompt` and commands are registered there, the parameter
would be `status.prompt`.
There are also other arguments to customize the way the command is registered,
see the class documentation for `register` in `qutebrowser.commands.utils` for
details.
Handling URLs
~~~~~~~~~~~~~
qutebrowser handles two different types of URLs: URLs as a string, and URLs as
the Qt `QUrl` type. As this can get confusing quickly, please follow the
following guidelines:
* Convert a string to a QUrl object as early as possible, i.e. directly after
the user did enter it.
- Use `utils.url.fuzzy_url` if the URL is entered by the user somewhere.
- Be sure you handle `utils.url.FuzzyError` and display an error
message to the user.
* Convert a `QUrl` object to a string as late as possible, e.g. before
displaying it to the user.
- If you want to display the URL to the user, use `url.toDisplayString()`
so password information is removed.
- If you want to get the URL as string for some other reason, you most
likely want to add the `QUrl.EncodeFully` and `QUrl.RemovePassword`
flags.
* Name a string URL something like `urlstr`, and a `QUrl` something like `url`.
* Mention in the docstring whether your function needs a URL string or a
`QUrl`.
* Call `qt_ensure_valid` from `utils.misc` whenever getting or creating a
`QUrl` and take appropriate action if not.
Style conventions
-----------------
qutebrowser's coding conventions are based on
http://legacy.python.org/dev/peps/pep-0008/[PEP8] and the https://google-styleguide.googlecode.com/svn/trunk/pyguide.html[Google Python style guidelines] with some additions:
* Methods overriding Qt methods (obviously!) don't follow the naming schemes.
* Everything else does though, even slots.
* Docstrings should look like described in
http://legacy.python.org/dev/peps/pep-0257/[PEP257] and the google guidelines.
* Class docstrings have additional _Attributes:_, _Class attributes:_ and
_Signals:_ sections, method/function docstrings have an _Emit:_ section.
+
Example for a class docstring:
+
[source,python]
----
"""Some object.
Attributes:
blub: The current thing to handle.
Signals:
valueChanged: Emitted when a value changed.
arg: The new value
"""
----
+
Example for a method/function docstring:
+
[source,python]
----
"""Do something special.
Args:
foo: ...
Return:
True if something, False if something else.
Raise:
ValueError if foo is None
Emit:
value_changed
"""
----
+
* The layout of a module should be roughly like this:
- Shebang (`#!/usr/bin/python`, if needed)
- vim-modeline (`# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et`)
- Copyright
- GPL boilerplate
- Module docstring
- Python standard library imports
- PyQt imports
- qutebrowser imports
- functions
- classes
* The layout of a class should be like this:
- docstring
- `__magic__` methods
- properties
- _private methods
- public methods
- `on_*` methods
- overrides of Qt methods