If you're looking for a bit more customization, then you've come to the right place.

[TOC]

## Using Tags

<div class="section">

Tags are the simplest "advanced" feature of Canto. You add an arbitrary number of tags to every feed:

    :::python
    add("http://reddit.com/.rss", tags=["Reddit", "news"])
    add("http://rss.slashdot.org/slashdot/Slashdot", tags=[None, "news"])

Now, in the default view, the first tag in every feed is used to categorize them. So with just these two statements, Canto displays two tags by default, Reddit, and Slashdot (because the `None` indicates that the given feed name should be used and for Slashdot, that returns "Slashdot"). This is the typical view and is functionally identical to just adding two feeds without adding any tags to them.

Adding the line:

    :::python
    tags = [ None, ["news"]]

You can now switch between the default view (the first tag for every defined feed), and the "news" tag. Under the "news" tag, all of the Reddit and Slashdot items will be in one group, which is less than useful. However, now you can customize the organization of those items when you're just viewing the "news" tag, by explicitly using `add_tag()`

    :::python
    add_tag("news", sorts=[[by_date]])

Now when you switch to the "news" tag (defaults `<` and `>` to switch), you'll get both Slashdot and Reddit items, but they will be all sorted by date. The `sorts` and `filters` options can be set for all implicit (not added with `add_tag`) can be set with `tag_sorts` and `tag_filters` like so:

    ::python
    tag_sorts=[[None],[by_unread, by_len]]
    tag_filters=[None, show_unread]


</div>

## Hooks

<div class="section">

Hooks are used in order to execute code on a particular event. These hooks are all used in your config by setting

    :::python
    somehook = somefunction

A basic example of a hook, as seen in the example config is as follows:

    :::python
    def my_resize_hook(cfg):
        cfg.columns = cfg.width / 70

    resize_hook = my_resize_hook

Extremely simple and effective, it resets the number of columns whenever the screen is resized.

There are a number of different hooks.

<table>
<tr>
<td>Hook name</td>
<td>Called when...</td>
<td>Args</td>
</tr>
<tr>
<td><code>start_hook</code></td>
<td>Called once, at startup.</td>
<td>Passed the current `Gui()` object</td>
</tr>
<tr>
<td><code>resize_hook</code></td>
<td>Called once at startup and again every time the screen is resized.</td>
<td>Passed the `Cfg()` object</td>
</tr>
<tr>
<td><code>new_hook</code></td>
<td>Called once per item, ever, regardless of state.</td>
<td>Passed the `Tag()` and `Story()` objects for the item</td>
</tr>
<tr>
<td><code>select_hook</code></td>
<td>Called when an item is selected.</td>
<td>Passed the `Tag()` and `Story()` objects for the item selected</td>
</tr>
<tr>
<td><code>unselect_hook</code></td>
<td>Called when an item is unselected.</td>
<td>Passed the `Tag()` and `Story()` objects for the item unselected</td>
</tr>
<tr>
<td><code>update_hook</code></td>
<td>Called every time the `Gui()` is refreshed with new items.</td>
<td>Passed the current `Gui()` object.</td>
</tr>
<tr>
<td><code>end_hook</code></td>
<td>Called once on exit.</td>
<td>Passed the current `Gui()` object.</td>
</tr>
</table>

Each hook is passed the relevant information. It may require a little [browsing of the code](http://codezen.org/cgi-bin/gitweb.cgi?p=canto.git) to make use of them, but the ability is there. The canto.extra module already has some example hooks for setting/unsetting the xterm title. If you need help using a hook, come by #canto in irc.freenode.net.


</div>

## Filters

<div class="section">

Filters allow you to arbitrarily remove items from the list. A filter takes the form of a class with at least two methods. For example, `show_unread()` from canto.extra:

    :::python
    class show_unread():
        def __str__(self):
            return "Show unread"

        def __call__(self, tag, item):
            return not item.wasread()

The class show_unread has two methods. `__str__` returns the string representation of the filter (used for the little blurbs in the message bar when you change feeds), and `__call__` is the actual filter. You get the tag and the item and based on that, the filter returns 1 (keep this item) or 0 (ignore this item).

Of course, they need not be so simplistic. Take `only_with` from canto.extra for example:

    :::python
    class only_with():
        def __init__(self, keyword, **kwargs):
            self.keyword = keyword
            if kwargs.has_key("regex") and kwargs["regex"]:
                self.match = re.compile(keyword)
            else:
                self.match = re.compile(".*" + keyword + ".*")

        def __str__(self):
            return "With %s" % self.keyword

        def __call__(self, tag, item):
            return self.match.match(item["title"])

A little more complicated, but it essentially takes one argument and an optional named argument, regex. Based on that it creates a regex to match based on that keyword.

Lastly, because they are classes, you can make them inherit from each other. Take `only_without` from canto.extra:

    :::python
    class only_without(only_with):
        def __str__(self):
            return "Without %s" % self.keyword

        def __call__(self, tag, item):
            return not self.match.match(item["title"])

Back to two lines of real code, inheriting the rest from `only_with`.


</div>

### Types of filters

<div class="section">

Here are the three types of filters:

1. **Feed filters**. This is a hard filter specified on a single feed that cannot be changed at runtime. This is intended to permanently ignore stories based based on criteria that don't really apply to other feed's items. For example, with a webcomic feed, like Penny Arcade. Inside the feed, some items start with "NEWS:" and some start with "COMIC:". To filter out all news items, you could use: `add(..., filter=only_without("NEWS:"))`. News items will be totally ignored, but any other items in the same tag (a "comics" tag, for example) won't be affected. **WARNING**: In <= 0.6.6 `canto/extra` filters don't work. This will be rectified in 0.6.7. In the meantime, you can define feed filters like this:

        :::python
        def feed_only_without(s):
            return lambda x: s not in x["title"]

        def feed_only_with(s):
            return lambda x: s in x["title"]

        add("URL", filter=feed_only_without("NEWS:"))


2. **Tag filters**. This is a much more flexible use of filters. You can define a list of filters for a given tag and cycle through them ('{' and '}' by default). These are useful for toggling between subcategories in an arbitrary tag, or filtering out some stories in a given tag. Using the webcomic example above, if you wanted to just show "COMIC:" items by default, but wanted to still have access to "NEWS:" items every once in awhile, you could `add_tag("Super Comic", filters=[only_with("COMIC:"), None])`. Because the first filter is the default filter, initially you would only see "COMIC:" items, but using '}', you'd switch to the `None` filter and see all of the items. The default set of tag filters for all tags can be set with `set_default_tag_filters([...])`.

3. **Global filters**. These function similarly to tag filters. This is useful for filtering out by item state. For example `filters=[show_unread, None]` will make Canto show only unread items by default, but show all items if you cycle to the next filter ('[' and ']' cycle global filters, by default). The effect of global filters is cumulative with tag filters, so if the global filter is `show_unread` and the tag_filter is `only_with("COMIC:")`, then only unread stories that have "COMIC:" in the title will be shown for that tag.

The definition of all three types of tag are identical, but only tag and global filters support cycling through lists. Tag and global filters can also be set directly with keybinds.

Here's a config snippet summarizing the above syntax:

    :::python
    # Hard feed filter
    add(..., filter=only_without("NEWS:"))

    # Tag filters
    set_default_tag_filters([None, show_unread])
    add_tag("Slashdot", filters=[None, only_without("Windows")])

    # Global filters
    filters = [None, show_unread]

    # Filter keybinds
    keys['a'] = set_filter(show_unread) 
    keys['b'] = set_tag_filter(show_unread)



</div>

## Sorting

<div class="section">

Sorting a feed in Canto is just like sorting any list in Python: You define a function that determines whether one item is ahead of another. Let's look at the unread sort (from [canto/extra.py](http://codezen.org/cgi-bin/gitweb.cgi?p=canto.git;a=blob;f=canto/extra.py;hb=HEAD")). This function is wrapped in a class to provide some extra info for the GUI.

    :::python
    class by_unread:
        def __str__(self):
            return "By Unread"

        def __call__(self, x, y):
            if x.wasread() and not y.wasread():
                return 1
            if y.wasread() and not x.wasread():
                return -1
            return 0

This sort makes all of the unread stories float at the top of the list. In simple terms, the class' sort function is passed two stories, x and y. If x has been read and y hasn't clearly x should come after y so we return a positive integer (1) meaning "x should be after y". If y was read and x hasn't, we return a negative integer (-1) meaning "y should be after x", otherwise return zero, meaning "no change."

The basics of Python sorting are covered in the Python wiki [here](http://wiki.python.org/moin/HowTo/Sorting).

A little note on writing a good sort function: the return value should be consistent. For example, if sort(x,y) returns 1, then `sort(y,x)` better return -1. If your values are inconsistent, the sort may not work properly some (or most) of the time. Lastly, all sorts are wrapped in an exception wrapper, so it won't kill Canto, but if your sort function throws an exception you should at least return 0 to maintain the feed's order.

Also note that the values inside of the stories (`story["title"]`, etc.) include all of the HTML and entities that aren't filtered. This is important because, for example, the `by_alpha` and `by_len` sorts just function on the raw number of characters in the title, not the displayed content so, "&someentity;" will sort like it's longer than "string", or "<em>Zoo opening</em>" will sort like it's alphabetically before "aardvark" (symbols come before letters). This is easily solvable but for a lot of feeds it's unnecessary.

Sorts can only be used on a tag basis because sorts don't really stack like filters do. You can't really sort on a global level (unless you use the "*" tag and then apply tag sort, which is simple enough). You can, however, set all tags to have a certain sort using `set_all_sorts` (from canto/extra). As such you can only set sorts with `add_tag`, or set the default with `tag_sorts=[...]`

Each sort is a list of sorting functions, used as follows:

    :::python
    add_tag("Slashdot", sorts=[[by_alpha, by_unread], [None]])

This will, by default, sort the Slashdot tag alphabetically, and then by unread status. Because both of those sorts are well-behaved, the read  and unread portions of the list will still be in alphabetical order. You can then use the `next_tag_sort` and `prev_stag_sort` keys (=/- by default) to switch between the two sort lists. Note that `[None]` will not sort at all, and this is the default.


</div>

## Advanced Keybinds

<div class="section">

Keybinds can be strings or functions or lists intermixing both.

A simple example:

    :::python
    def custom_keybind(gui):
        gui.cfg.log("Selected: %s" % gui.sel["title"])

    keys['b'] = custom_keybind

Which makes the 'b' key print "Selected: *item-title*" to the log. Totally useless, but it illustrates a pattern. A more complex (and useful example) is the search helper from canto.extra:

    :::python
    def search(s, **kwargs):
        if kwargs.has_key("regex") and kwargs["regex"]:
            return lambda x : x.do_inline_search(re.compile(s))
        else:
            return lambda x : x.do_inline_search(re.compile(".*" + s + ".*"))

Looks rather complex, but it does essentially the same thing as the only_with filter did, only this time it passes that regex to the `Gui()` `do_inline_search` function which highlights all items matching the regex.

This now allows us to use an advanced keybind easily (i.e. without much Python)

    :::python
    keys['1'] = search(".*[Ll]inux.*", regex=True)
    keys['2'] = search("Obama")

Other nice keybind defined in `canto/extra.py`:

    :::python
    keys['a'] = set_filter(show_unread) # Set the global filter with a keybind
    keys['s'] = set_tag_filter(show_unread) # Set the tag_filter with a keybind
    keys['d'] = set_tag_sort(by_alpha) # Set tag sort with a keybind
    keys['b'] = set_tags(["Reddit", "Proggit"]) # Set current tags

</div>
