Skip to content
This repository
Browse code

Merge branch 'develop' for release 0.3.0.

  • Loading branch information...
commit 0e7a3642499554edc8265fdf1ba6c5ee567daa78 2 parents e92ddbf + dea2cab
Jonathan Beluch authored February 20, 2013
1  .gitignore
@@ -9,3 +9,4 @@ XBMC_Swift.egg-info/
9 9
 docs/_build/
10 10
 TODO
11 11
 XBMC_Swift.egg-info/
  12
+xbmcswift2.egg-info/
2  AUTHORS
@@ -12,3 +12,5 @@ Patches and Suggestions
12 12
 - Tristan Fischer (sphere@dersphere.de)
13 13
 - beenje
14 14
 - takoi
  15
+- cancan101
  16
+- ulion
29  CHANGES
... ...
@@ -1,7 +1,34 @@
1 1
 0.3
2 2
 ===
3 3
 
4  
-Unreleased.
  4
+- Allow ints to be passed to plugin.url_for() and str() them.
  5
+- plugin.set_resolved_url() now takes an item dict instead of a URL. (The URL
  6
+  is still allowed for backwards compatibility but is decprecated).
  7
+- Ability to replace the context menu items by setting 'replace_context_menu'
  8
+  to True in an item dict.
  9
+- pluign.get_setting() now requires an extra paramter, *converter*, which
  10
+  converts the item from XML to a python type specified by converter.
  11
+- Major bug fix to accomodate handles > 0 in XBMC Frodo.
  12
+- Auto-call plugin.finish(succeeded=False) if a view returns None.
  13
+- XBMC now decides the default player when using plugin.play_video().
  14
+- Storages now call sync() when clear() is called.
  15
+- Added plugin.clear_function_cache() which clears the storage behind the
  16
+  @cached and @cached_route decorators.
  17
+- Add ability to set stream info for list items. Also, devs can now add
  18
+  stream_info to item dicts.
  19
+- Added the actions module. Contains actions.background() and
  20
+  actions.update_view() which are convenience wrappers for RunPlugin() and
  21
+  Container.Update().
  22
+- Allow passing of functions to url_for.
  23
+- 'properties' key in an item dict should now be a dictionary. A list of tuples
  24
+  is still allowed for backwards compatibility.
  25
+- Addon user is now prompted and storage automatically cleared if it becomes
  26
+  corrupted.
  27
+- Added subtitles support. A 'subtitles' parameter was added to
  28
+  plugin.set_resolved_url().
  29
+- xbmcswift2 URLs and routing code is now indifferent to a trailing slash.
  30
+- Bug fixes. See https://github.com/jbeluch/xbmcswift2/issues?milestone=3.
  31
+
5 32
 
6 33
 0.2.2
7 34
 =====
7  README.md
Source Rendered
@@ -60,8 +60,13 @@ Bugs, patches and suggestions are all welcome. I'm working on adding tests and
60 60
 getting better coverage. Please ensure that your patches include tests as well
61 61
 as updates to the documentation. Thanks!
62 62
 
63  
-## Contact
  63
+## Support
  64
+
  65
+\#xbmcswift on freenode
64 66
 
65 67
 https://github.com/jbeluch/xbmcswift2
66 68
 
  69
+Subscribe to the mailing list to be notified of new releases or to get help.
  70
+Send an email to xbmcswift@librelist.com to subscribe.
  71
+
67 72
 web@jonathanbeluch.com
9  docs/api.rst
Source Rendered
@@ -31,9 +31,18 @@ Request
31 31
     :inherited-members:
32 32
 
33 33
 
  34
+Actions
  35
+-------
  36
+
  37
+.. automodule:: xbmcswift2.actions
  38
+    :members:
  39
+
  40
+
34 41
 Extended API
35 42
 ------------
36 43
 
  44
+.. module:: xbmcswift2 
  45
+
37 46
 Module
38 47
 ``````
39 48
 
23  docs/caching.rst
Source Rendered
... ...
@@ -1,14 +1,19 @@
1 1
 .. _caching:
2 2
 
3 3
 
4  
-Caching
5  
-=======
  4
+Caching and Storage
  5
+===================
6 6
 
7  
-xbmcswift2 offers lots of caching options to help improve the user experience
8  
-for your addon. xbmcswift2 offers a storage mechanism that allows you to store
9  
-arbitrary python objects to use across requests. The storage options also allow
10  
-for an optionan lifetime to be specified for each object.
  7
+xbmcswift2 offers a few options for caching and storage to help improve the
  8
+user experience of your addon. swift offers a simple storage mechanism that
  9
+allows you to store arbitraty python objects to use between requests.
11 10
 
  11
+.. warning::
  12
+    
  13
+    The current implementation of xbmcswift2's storage is very basic and is not
  14
+    thread safe. If your addon does background calls via the context menu and
  15
+    manipulates storages in these backgound threads, you might run into some
  16
+    issues.
12 17
 
13 18
 Storing Arbitraty Python Objects
14 19
 --------------------------------
@@ -61,7 +66,7 @@ time the addon is run. So we can use the caching decorator with a TTL argument.
61 66
 
62 67
 .. sourcecode:: python
63 68
 
64  
-    @plugin.cache(TTL=60*24)
  69
+    @plugin.cached(TTL=60*24)
65 70
     def get_api_data();
66 71
         # make remote request
67 72
         data = get_remote_data()
@@ -90,6 +95,10 @@ for this decorator; it defaults to 24 hours.
90 95
              cache the view. See the below section 'Caveats' for more
91 96
              information.
92 97
 
  98
+.. warning:: It is currently only possible to attach a single cached_route to a
  99
+             view. If you have multiple routes on a given view, try refactoring
  100
+             some logic out to a new function that can be cached, instead of
  101
+             using the cached_route decorator.
93 102
 Caveats
94 103
 -------
95 104
 
85  docs/commandline.rst
Source Rendered
... ...
@@ -1,32 +1,41 @@
1 1
 .. _commandline:
2 2
 
3 3
 
4  
-Running Addons on the Command Line
5  
-==================================
  4
+Running xbmcswift2 on the Command Line
  5
+======================================
6 6
 
7 7
 
8  
-Options
9  
--------
  8
+Commands
  9
+--------
10 10
 
11  
-To see the command line help, simply execute ``xbmcswift2 run -h``.::
  11
+When running xbmcswift2 from the command line, there are two commands
  12
+available, *create* and *run*. *create* is a script that will create the basic
  13
+scaffolding and necessary files for an XBMC addon and personalize it by asking
  14
+you a few questions. *run* enables you to debug your addon on the command line.
12 15
 
13  
-    $ xbmcswift2 run -h
14  
-    Usage: xbmcswift2 run [once|interactive|crawl] [url]
  16
+To see the command line help, simply execute ``xbmcswift2 -h``. Both of the
  17
+commands are explained further below.
15 18
 
16  
-    Options:
17  
-      -h, --help     show this help message and exit
18  
-      -q, --quiet    set logging level to quiet
19  
-      -v, --verbose  set logging level to verbose
20 19
 
  20
+create
  21
+~~~~~~
21 22
 
22  
-There are three different run modes available, once_ (default),
23  
-interactive_, and crawl_.
  23
+To create a new addon, change your current working directory to a location
  24
+where you want your addon folder to be created. Then execute ``xbmcswift2
  25
+create``. After answering a few questions, you should have the basic addon
  26
+structure in place.
24 27
 
25  
-There is also a second positional argument which is optional, ``url``. By
26  
-default, xbmcswift2 will run the root URL,
  28
+run
  29
+~~~
  30
+
  31
+When running an addon on the command line, there are three different run modes
  32
+available, once_, interactive_, and crawl_. 
  33
+
  34
+There is also a second positional argument, ``url``, which is optional. By
  35
+default, xbmcswift2 will run the root URL of your addon (a path of '/'), e.g.
27 36
 ``plugin://plugin.video.academicearth/``. This is the same default URL that
28  
-XBMC uses when you first enter an addon. You can gather runnable URLs from the
29  
-output of xbmcswift2.
  37
+XBMC uses when you first enter an addon. You can gather URLs from the output of
  38
+xbmcswift2.
30 39
 
31 40
 The options ``-q`` and ``-v`` decrease and increase the logging level.
32 41
 
@@ -35,27 +44,29 @@ The options ``-q`` and ``-v`` decrease and increase the logging level.
35 44
     To enable running on the command line, xbmcswift2 attempts to mock a
36 45
     portion of the XBMC python bindings. Certain functions behave properly like
37 46
     looking up strings. However, if a function has not been implemented,
38  
-    xbmcswift2 lets the function call pass silently to avoid Exceptions and
39  
-    allow the plugin to run in a limited fashion. This is why you'll very often
40  
-    see WARNING log messages when running on the command line.
  47
+    xbmcswift2 lets the function call pass silently to avoid exceptions and
  48
+    allow the plugin to run in a limited fashion. This is why you'll often see
  49
+    WARNING log messages when running on the command line.
41 50
 
42  
-    This is also why you should import the xbmc python modules from xbmcswift2::
  51
+    If you plan on using the command line to develop your addons, you should
  52
+    always import the xbmc modules from xbmcswift2::
43 53
 
44 54
         from xbcmswift2 import xbmcgui
45 55
 
46  
-    xbmcswift2 will correctly import the proper XBMC modules in the correct
47  
-    environments so you don't have to worry about it.
48  
-
49  
-
  56
+    xbmcswift2 will correctly import the proper module based on the
  57
+    environment. When running in XBMC, it will import the actual modules, and
  58
+    when running on the command line it will import mocked modules without
  59
+    error.
50 60
 
51 61
 
52 62
 once
53  
-~~~~
  63
+____
54 64
 
55 65
 Executes the addon once then quits. Useful for testing when used
56 66
 with the optional ``url`` argument.::
57 67
 
58  
-    $ xbmcswift2 run once 2>/dev/null
  68
+    $ xbmcswift2 run once # you can omit the once argument as it is the default
  69
+
59 70
     ------------------------------------------------------------
60 71
      #  Label    Path
61 72
     ------------------------------------------------------------
@@ -63,8 +74,20 @@ with the optional ``url`` argument.::
63 74
     ------------------------------------------------------------
64 75
 
65 76
 
  77
+    $ xbmcswift2 run once plugin://plugin.video.academicearth/subjects/
  78
+    ----------------------------------------------------------------------------------------------------------------------------------------------------------
  79
+     #   Label                    Path
  80
+    ----------------------------------------------------------------------------------------------------------------------------------------------------------
  81
+     [ 0] ACT                      (plugin://plugin.video.academicearth/subjects/http%3A%2F%2Fwww.academicearth.org%2Fsubjects%2Fact/)
  82
+     [ 1] Accounting               (plugin://plugin.video.academicearth/subjects/http%3A%2F%2Fwww.academicearth.org%2Fsubjects%2Faccounting/)
  83
+     [ 2] Algebra                  (plugin://plugin.video.academicearth/subjects/http%3A%2F%2Fwww.academicearth.org%2Fsubjects%2Falgebra/)
  84
+     [ 3] Anthropology             (plugin://plugin.video.academicearth/subjects/http%3A%2F%2Fwww.academicearth.org%2Fsubjects%2Fanthropology/)
  85
+     [ 4] Applied CompSci          (plugin://plugin.video.academicearth/subjects/http%3A%2F%2Fwww.academicearth.org%2Fsubjects%2Fapplied-computer-science/)
  86
+     ...
  87
+
  88
+
66 89
 interactive
67  
-~~~~~~~~~~~
  90
+___________
68 91
 
69 92
 Allows the user to step through their addon using an interactive session. This
70 93
 is meant to mimic the basic XBMC interface of clicking on a listitem, which
@@ -72,7 +95,7 @@ then brings up a new directory listing. After each listing is displayed the
72 95
 user will be prompted for a listitem to select.  There will always be a ``..``
73 96
 option to return to the previous directory (except for the initial URL).::
74 97
 
75  
-    (xbmc-academic-earth)jon@lenovo ~/Code/xbmc-academic-earth (master) $ xbmcswift2 run interactive 2>/dev/null
  98
+    $ xbmcswift2 run interactive
76 99
     ------------------------------------------------------------
77 100
      #  Label    Path
78 101
     ------------------------------------------------------------
@@ -100,12 +123,12 @@ option to return to the previous directory (except for the initial URL).::
100 123
 
101 124
 
102 125
 crawl
103  
-~~~~~
  126
+_____
104 127
 
105 128
 Used to crawl every available path in your addon. In between each request the
106 129
 user will be prompted to hit Enter to continue.::
107 130
 
108  
-    Choose an item or "q" to quit: (xbmc-academic-earth)jon@lenovo ~/Code/xbmc-academic-earth (master) $ xbmcswift2 run crawl 2>/dev/null
  131
+    $ xbmcswift2 run crawl 2>/dev/null
109 132
     ------------------------------------------------------------
110 133
      #  Label    Path
111 134
     ------------------------------------------------------------
22  docs/index.rst
Source Rendered
... ...
@@ -1,25 +1,20 @@
1  
-.. XBMC Swift documentation master file, created by
2  
-   sphinx-quickstart on Sat Jan 21 15:24:10 2012.
3  
-   You can adapt this file completely to your liking, but it should at least
4  
-   contain the root `toctree` directive.
5 1
 
6  
-Welcome to XBMC Swift's documentation!
  2
+Welcome to xbmcswift2's documentation!
7 3
 ======================================
8 4
 
9  
-Welcome to the documentation for xbmcswift2. This documentation is divided into
10  
-several parts. If you are new, you should start with the :ref:`installation`
11  
-and then move on to :ref:`quickstart`. If you would prefer a more detailed
12  
-walkthrough, try the :ref:`tutorial`.
  5
+Welcome to the documentation for xbmcswift2. xbmcswift2 is a small framework to
  6
+ease development of XBMC addons. Whether you are an experienced addon
  7
+developer, or just coding your first addon, you'll find benefits to using xbmcswift2.
  8
+
  9
+This documentation is divided into several parts. If you are new, you should
  10
+start with the :ref:`installation` and then move on to :ref:`quickstart`. If
  11
+you would prefer a more detailed walkthrough, try the :ref:`tutorial`.
13 12
 
14 13
 To get a deeper understanding of xbmcswift2, check out :ref:`routing`,
15 14
 :ref:`caching` and the complete :ref:`api` reference. For specific code
16 15
 samples, check out :ref:`patterns`. If you are upgrading from xbmcswift, check
17 16
 out the :ref:`upgrading` page.
18 17
 
19  
-xbmcswift2 doesn't rely on any external dependencies to get started. However,
20  
-when writing plugins I like to use http://BeautifulSoup.org. Also, when it
21  
-comes to advanced testing of plugins, I like to use http://nosetests.org.
22  
-
23 18
 For a list of XBMC addons which use xbmcswift2, see :ref:`poweredby`.
24 19
 
25 20
 
@@ -32,6 +27,7 @@ Basic Info
32 27
     installation
33 28
     quickstart
34 29
     tutorial
  30
+    item
35 31
     commandline
36 32
 
37 33
 Advanced User Guide
27  docs/installation.rst
Source Rendered
@@ -9,12 +9,15 @@ Installation
9 9
     command line as well as in XBMC. This means that we will have to install
10 10
     xbmcswift2 twice, once for the command line and once as an XBMC addon.
11 11
 
  12
+    The XBMC version of xbmcswift2 is a specially packaged version of the main
  13
+    release. It excludes some CLI code and tests. It also contains XBMC
  14
+    required files like addon.xml.
12 15
 
13 16
 The easiest way to get the most recent version of xbmcswift2 for XBMC is to
14 17
 install an addon that requires xbmcswift2. You can find a list of such addons
15  
-on the :ref:`poweredby` page. The other options is download the correct version
16  
-from https://github.com/jbeluch/xbmcswift2-xbmc-dist/tags and unpack it into
17  
-your addons folder.  
  18
+on the :ref:`poweredby` page. The other options is download the current XBMC
  19
+distribution from https://github.com/jbeluch/xbmcswift2-xbmc-dist/tags and
  20
+unpack it into your addons folder.  
18 21
 
19 22
 Now, on to installing xbmcswift2 for use on the command line.
20 23
 
@@ -22,10 +25,12 @@ virtualenv
22 25
 ----------
23 26
 
24 27
 Virtualenv is an awesome tool that enables clean installation and removal of
25  
-python libraries into a "virtual environment". Using a virtual environment
  28
+python libraries into a sequestered environment. Using a virtual environment
26 29
 means that when you install a library, it doesn't pollute your system-wide
27 30
 python installation. This makes it possible to install different versions of a
28  
-library in different environments and they will never conflict.
  31
+library in different environments and they will never conflict. It's a good
  32
+habit to get into when doing python development. So, first we're going to
  33
+install virtualenv.
29 34
 
30 35
 If you already have pip installed, you can simply::
31 36
 
@@ -36,7 +41,7 @@ or if you only have easy_install::
36 41
     $ sudo easy_install virtualenv
37 42
 
38 43
 I also like to use some helpful virtualenv scripts, so install
39  
-virtualenv-wrapper::
  44
+virtualenv-wrapper as well::
40 45
 
41 46
     $ sudo pip install virtualenv-wrapper
42 47
 
@@ -47,16 +52,20 @@ Now we can create our virtualenv::
47 52
 
48 53
     $ mkvirtualenv xbmcswift2
49 54
 
50  
-When this completes, your prompt should now be preceded by `(xbmcswift2)`.
51  
-Now we install xbmcswift2::
  55
+When this completes, your prompt should now be prefixed by `(xbmcswift2)`. The
  56
+new prompt signals that we are now working within our virtualenv. Any libraries
  57
+that we install via pip will only be available in this environment. Now we'll
  58
+install xbmcswift2::
52 59
 
53 60
     $ pip install xbmcswift2
54 61
 
55 62
 Everything should be good to go. When you would like to work on your project
56  
-again, simply::
  63
+in the future, issue the following command to start your virtual env::
57 64
 
58 65
     $ workon xbmcswift2
59 66
 
60 67
 and to deactive the virtualenv::
61 68
 
62 69
     $ deactivate
  70
+
  71
+You should check out the :ref:`commandline` page next.
135  docs/item.rst
Source Rendered
... ...
@@ -0,0 +1,135 @@
  1
+.. _item:
  2
+
  3
+
  4
+The ListItem
  5
+============
  6
+
  7
+xbmcswift2 prefers to represent XBMC list items as plain python dictionaries as
  8
+much as possible. Views return lists of dictionaries, where each dict
  9
+represents an XBMC listitem. The list of valid keys in an item dict can always
  10
+be validated by reviewing the available arguments to
  11
+:meth:`xbmcswift2.ListItem.from_dict`. However, we'll go into more detail here.
  12
+
  13
+Valid keys in an item dict are:
  14
+
  15
+* `label`_
  16
+* `label2`_
  17
+* `icon`_
  18
+* `thumbnail`_
  19
+* `path`_
  20
+* `selected`_
  21
+* `info`_
  22
+* `properties`_
  23
+* `context_menu`_
  24
+* `replace_context_menu`_
  25
+* `is_playable`_
  26
+* `info_type`_
  27
+* `stream_info`_
  28
+
  29
+label
  30
+-----
  31
+
  32
+A required string. Used as the main display label for the list item.
  33
+
  34
+
  35
+label2
  36
+------
  37
+
  38
+A string. Used as the alternate display label for the list item.
  39
+
  40
+
  41
+icon
  42
+----
  43
+
  44
+A path to an icon image.
  45
+
  46
+
  47
+thumbnail
  48
+---------
  49
+
  50
+A path to a thumbnail image.
  51
+
  52
+
  53
+path
  54
+----
  55
+
  56
+A required string.
  57
+
  58
+For non-playable items, this is typically a URL for a different path in the
  59
+same addon. To derive URLs for other views within your addon, use
  60
+:meth:`xbmcswift2.Plugin.url_for`.
  61
+
  62
+For playable items, this is typically a URL to a remote media file. (One
  63
+exception, is if you are using the set_resolved_url pattern, the URL will be
  64
+playable but will also call back into your addon.)
  65
+
  66
+
  67
+selected
  68
+--------
  69
+
  70
+A boolean which will set the item as selected. False is default.
  71
+
  72
+
  73
+info
  74
+----
  75
+
  76
+A dictionary of key/values of metadata information about the item. See the
  77
+`XBMC docs
  78
+<http://mirrors.xbmc.org/docs/python-docs/xbmcgui.html#ListItem-setInfo>`_ for
  79
+a list of valid info items. Keys are always strings but values should be the
  80
+correct type required by XBMC.
  81
+
  82
+Also, see the related `info_type`_ key.
  83
+
  84
+
  85
+properties
  86
+----------
  87
+
  88
+A dict of properties, similar to info-labels. See
  89
+http://mirrors.xbmc.org/docs/python-docs/xbmcgui.html#ListItem-setProperty for
  90
+more information.
  91
+
  92
+
  93
+context_menu
  94
+------------
  95
+
  96
+A list of tuples, where each tuple is of length 2. The tuple should be (label,
  97
+action) where action is a string representing a built-in XBMC function. See the
  98
+`XBMC documentation
  99
+<http://mirrors.xbmc.org/docs/python-docs/xbmcgui.html#ListItem-addContextMenuItems>`_
  100
+for more details and `Using the Context Menu` for some example code.
  101
+
  102
+
  103
+replace_context_menu
  104
+--------------------
  105
+
  106
+Used in conjunction with `context_menu`. A boolean indicating whether to
  107
+replace the existing context menu with the passed context menu items. Defaults
  108
+to False.
  109
+
  110
+
  111
+is_playable
  112
+-----------
  113
+
  114
+A boolean indicating whether the item dict is a playable item. False indicates
  115
+that the item is a directory item. Use True when the path is a direct media
  116
+URL, or a URL that calls back to your addon where set_resolved_url will be
  117
+used.
  118
+
  119
+
  120
+info_type
  121
+---------
  122
+
  123
+Used in conjunction with `info`. The default value is usually configured
  124
+automatically from your addon.xml. See
  125
+http://mirrors.xbmc.org/docs/python-docs/xbmcgui.html#ListItem-setInfo for
  126
+valid values.
  127
+
  128
+
  129
+stream_info
  130
+-----------
  131
+
  132
+A dict where each key is a stream type and each value is another dict of stream
  133
+values. See
  134
+http://mirrors.xbmc.org/docs/python-docs/xbmcgui.html#ListItem-addStreamInfo
  135
+for more information.
136  docs/patterns.rst
Source Rendered
@@ -68,7 +68,50 @@ dictionary, however it is automatically persisted to disk.
68 68
 Adding pagination
69 69
 -----------------
70 70
 
71  
-pagination
  71
+If you are scraping a website that uses pagination, it's possible to present
  72
+the same interface in XBMC without having to scrape all of the pages up front.
  73
+To accomplish this, we are going to create our own *Next* and *Previous* list
  74
+items which go the next and previous page of results respectively. We're also
  75
+going to take advantage of a parameter option that gets passed to XBMC,
  76
+`updateListing`. If we pass True for this parameter, then every time the use
  77
+clicks the Next item, the URL won't be added to history. This enables the ".."
  78
+list item to go to the correct parent directory, instead of the previous page.
  79
+
  80
+Some example code:
  81
+
  82
+.. sourcecode:: python
  83
+
  84
+    @plugin.route('/videos/<page>')
  85
+    def show_videos(page='1'):
  86
+        page = int(page)  # all url params are strings by default
  87
+        videos, next_page = get_videos(page)
  88
+        items = [make_item(video) for video in videos]
  89
+
  90
+        if next_page:
  91
+            items.insert(0, {
  92
+                'label': 'Next >>',
  93
+                'path': plugin.url_for('show_videos', page=str(page + 1))
  94
+            })
  95
+            
  96
+        if page > 1:
  97
+            items.insert(0, {
  98
+                'label': '<< Previous',
  99
+                'path': plugin.url_for('show_videos', page=str(page - 1))
  100
+            })
  101
+
  102
+        return plugin.finish(items, update_listing=True)
  103
+
  104
+The first thing to notice about our view, is that it takes a page number as a 
  105
+URL parameter. We then pass the page number to the API call, get_videos(), to
  106
+return the correct data based on the current page. Then we create our own
  107
+previous/next list items depending on the current page. Lastly, we are
  108
+returning the result of the call to plugin.finish(). By default, when you
  109
+normally return a list of dicts, plugin.finish() is called for you. However, in
  110
+this case we need to pass the update_listing=True parameter so we must call it
  111
+explictly.
  112
+
  113
+Setting update_listing to True, notifies XBMC that we are paginating, and that
  114
+every new page should *not* be a new entry in the history.
72 115
 
73 116
 
74 117
 Reusing views with multiple routes
@@ -96,7 +139,23 @@ can use python's default argument syntax.
96 139
 Adding sort methods
97 140
 -------------------
98 141
 
99  
-sort methods
  142
+Sort methods enable the user to sort a directory listing in different ways. You
  143
+can see the available sort methods `here
  144
+<http://mirrors.xbmc.org/docs/python-docs/xbmcplugin.html#-addSortMethod>`_, or
  145
+by doing ``dir(xbmcswift2.SortMethod)``. The simplest way to add sort methods to
  146
+your views is to call plugin.finish() with a sort_methods argument and return
  147
+the result from your view (this is what xbmcswift2 does behind the scenes
  148
+normally).
  149
+
  150
+.. sourcecode:: python
  151
+
  152
+    @plugin.route('/movies')
  153
+    def show_movies():
  154
+        movies = api.get_movies()
  155
+        items = [create_item(movie) for movie in movies]
  156
+        return plugin.finish(items, sort_methods=['playlist_order', 'title', 'date'])
  157
+
  158
+See :meth:`xbmcswift2.Plugin.finish` for more information.
100 159
 
101 160
 
102 161
 Playing RTMP urls
@@ -121,12 +180,72 @@ Using settings
121 180
 how to use settings
122 181
 
123 182
 
124  
-Using the context menu
  183
+Using the Context Menu
125 184
 ----------------------
126 185
 
  186
+XBMC allows plugin to authors to update the context menu on a per list item
  187
+basis. This allows you to add more functionality to your addons, as you can
  188
+allow users other actions for a given item. One popular use for this feature is
  189
+to create allow playable items to be added to custom playlists within the
  190
+addon. (See the itunes_ or reddit-music_ addons for implementations).
  191
+
  192
+.. _itunes: https://github.com/dersphere/plugin.video.itunes_podcasts
  193
+.. _reddit-music: https://github.com/jbeluch/xbmc-reddit-music
  194
+
  195
+In xbmcswift2, adding context menu items is accomplished by passing a value for
  196
+the *context_menu* key in an item dict. The value should be a list of 2-tuples.
  197
+Each tuple corresponds to a context menu item, and should be of the format
  198
+(display_string, action) where action is a string corresponding to one of
  199
+XBMC's `built-in functions`_. See `XBMC's documentation
  200
+<http://mirrors.xbmc.org/docs/python-docs/xbmcgui.html#ListItem-addContextMenuItems>`_
  201
+for more information.
  202
+
  203
+.. _`built-in functions`: http://wiki.xbmc.org/?title=List_of_Built_In_Functions
  204
+
  205
+The most common actions are `XBMC.RunPlugin()` and `XBMC.Container.Update()`.
  206
+RunPlugin takes a single argument, a URL for a plugin (you can create a URL
  207
+with :meth:`xbmcswift2.Plugin.url_for`). XBMC will then run your plugin in a
  208
+background thread, *it will not affect the current UI*. So, RunPlugin is good
  209
+for any sort of background task. Update(), however will change the current UI
  210
+directory, so is useful when data is updated and you need to refresh the
  211
+screen.
  212
+
  213
+If you are using one of the two above built-ins, there are convenience
  214
+functions in xbmcswift2 in the actions module.
  215
+
  216
+Here is a quick example of updating the context menu.
  217
+
  218
+.. sourcecode:: python
  219
+
  220
+    from xbmcswift2 import actions
  221
+
  222
+    @plugin.url('/favorites/add/<url>')
  223
+    def add_to_favs(url):
  224
+        # this is a background view
  225
+        ...
  226
+
  227
+    def make_favorite_ctx(url)
  228
+        label = 'Add to favorites'
  229
+        new_url = plugin.url_for('add_to_favorites', url=url)
  230
+        return (label, actions.background(new_url))
  231
+
  232
+
  233
+    @plugin.route('/movies')
  234
+    def show_movies()
  235
+        items = [{
  236
+            ...
  237
+            'context_menu': [
  238
+                make_favorite_ctx(movie['url']),
  239
+            ],
  240
+            'replace_context_menu': True,
  241
+        } for movie in movies]
  242
+        return items
127 243
 
128  
-Pickling parameters in URls
129  
----------------------------
  244
+Sometimes the context_menu value can become very nested, so we've pulled out
  245
+the logic into the ``make_favorite_ctx`` function. Notice also the use of the
  246
+*replace_context_menu* key and the True value. This instructs XBMC to clear the
  247
+context menu prior to adding your context menu items. By default, your context
  248
+menu items are mixed in with the built in options.
130 249
 
131 250
 
132 251
 Using extra parameters in the query string
@@ -140,15 +259,14 @@ A dict of query string parameters can be accessed from ``plugin.request.args``.
140 259
 
141 260
 Any arguments that are not instances of basestring will attempt to be preserved
142 261
 by pickling them before being encoded into the query string. This functionality
143  
-isn't fully tested however, and XBMC does finitely limit the length of URLs. If
144  
-you need to preserve python objects between function calls, see the Caching_
145  
-patterns.
  262
+isn't fully tested however, and XBMC does limit the length of URLs. If you need
  263
+to preserve python objects between function calls, see the Caching_ patterns.
146 264
 
147 265
 
148 266
 Using Modules
149 267
 -------------
150 268
 
151  
-Modules are meant to be mini-plugins. They have some basic functionality that
  269
+Modules are meant to be mini-addons. They have some basic functionality that
152 270
 is separate from the main plugin. In order to be used, they must be registered
153 271
 with a plugin.
154 272
 
109  docs/poweredby.rst
Source Rendered
@@ -7,87 +7,28 @@ Addons Powered by xbmcswift2
7 7
 Want your addon included here? Send me an email at web@jonathanbeluch.com with
8 8
 your addon name and a link to a repository (XBMC's git repo is fine).
9 9
 
10  
-Academic Earth
11  
---------------
12  
-
13  
-Watch lectures from Academic Earth within the XBMC interface.
14  
-
15  
-* `info <http://xbmcaddonbrowser.com/addons/eden/plugin.video.academicearth/>`_
16  
-* `view source <https://github.com/jbeluch/xbmc-academic-earth>`_
17  
-
18  
-Documentary.net
19  
----------------
20  
-
21  
-Watch documentaries from http://documentary.net.
22  
-
23  
-* `info <http://xbmcaddonbrowser.com/addons/eden/plugin.video.documentary.net/>`_
24  
-* `view source <https://github.com/jbeluch/plugin.video.documentary.net>`_
25  
-
26  
-VimCasts
27  
---------
28  
-
29  
-Watch screencasts from http://vimcasts.org.
30  
-
31  
-* `info <http://xbmcaddonbrowser.com/addons/eden/plugin.video.vimcasts/>`_
32  
-* `view source <https://github.com/jbeluch/xbmc-vimcasts>`_
33  
-
34  
-
35  
-Radio
36  
------
37  
-
38  
-Music plugin to access over 4000 international radio broadcasts from rad.io, radio.de and radio.fr.
39  
-
40  
-* `info <http://xbmcaddonbrowser.com/addons/eden/plugin.audio.radio_de/>`_
41  
-* `view source <https://github.com/dersphere/plugin.audio.radio_de>`_
42  
-
43  
-
44  
-Shoutcast 2
45  
------------
46  
-
47  
-Browse more than 50.000 free internet radio stations.
48  
-
49  
-* `info <http://xbmcaddonbrowser.com/addons/eden/plugin.audio.shoutcast>`_
50  
-* `view source <https://github.com/dersphere/plugin.audio.shoutcast>`_
51  
-
52  
-
53  
-Cheezburger Network
54  
--------------------
55  
-
56  
-Cheezburger: All your funny in one place.
57  
-
58  
-* `info <http://xbmcaddonbrowser.com/addons/eden/plugin.image.cheezburger_network>`_
59  
-* `view source <https://github.com/dersphere/plugin.image.cheezburger_network>`_
60  
-
61  
-
62  
-Apple Itunes Podcasts
63  
----------------------
64  
-
65  
-Browse and search for thousands of free audio and video podcasts.
66  
-
67  
-* `info <http://xbmcaddonbrowser.com/addons/eden/plugin.video.itunes_podcasts>`_
68  
-* `view source <https://github.com/dersphere/plugin.video.itunes_podcasts>`_
69  
-
70  
-
71  
-MyZen.tv
72  
---------
73  
-
74  
-Watch the Free Exercises from the well-being website www.myzen.tv.
75  
-
76  
-* `info <http://xbmcaddonbrowser.com/addons/eden/plugin.video.myzen_tv>`_
77  
-* `view source <https://github.com/dersphere/plugin.video.myzen_tv>`_
78  
-
79  
-Rofl.to
80  
--------
81  
-
82  
-Daily updated funny videos + clips. Your #1 resource for fun videos.
83  
-
84  
-* `info <http://xbmcaddonbrowser.com/addons/eden/plugin.video.rofl_to>`_
85  
-* `view source <https://github.com/dersphere/plugin.video.rofl_to>`_
86  
-
87  
-Wimp
88  
-----
89  
-
90  
-A collection of the best online videos. Our editors make sure clips added are family friendly.
91  
-
92  
-* `info <http://xbmcaddonbrowser.com/addons/eden/plugin.video.wimp>`_
93  
-* `view source <https://github.com/dersphere/plugin.video.wimp>`_
  10
+========================    =============================================================
  11
+Addon                       Source
  12
+========================    =============================================================
  13
+`Academic Earth`_           https://github.com/jbeluch/xbmc-academic-earth
  14
+`Documentary.net`_          https://github.com/jbeluch/plugin.video.documentary.net
  15
+`VimCasts`_                 https://github.com/jbeluch/xbmc-vimcasts
  16
+`Radio`_                    https://github.com/dersphere/plugin.audio.radio_de
  17
+`Shoutcast 2`_              https://github.com/dersphere/plugin.audio.shoutcast
  18
+`Cheezburger Network`_      https://github.com/dersphere/plugin.image.cheezburger_network
  19
+`Apple Itunes Podcasts`_    https://github.com/dersphere/plugin.video.itunes_podcasts
  20
+`MyZen.tv`_                 https://github.com/dersphere/plugin.video.myzen_tv
  21
+`Rofl.to`_                  https://github.com/dersphere/plugin.video.rofl_to
  22
+`Wimp`_                     https://github.com/dersphere/plugin.video.wimp
  23
+========================    =============================================================
  24
+
  25
+.. _Academic Earth: http://xbmcaddonbrowser.com/addons/frodo/plugin.video.academicearth/
  26
+.. _Documentary.net: http://xbmcaddonbrowser.com/addons/frodo/plugin.video.documentary.net/
  27
+.. _VimCasts: http://xbmcaddonbrowser.com/addons/frodo/plugin.video.vimcasts/
  28
+.. _Radio: http://xbmcaddonbrowser.com/addons/frodo/plugin.audio.radio_de/
  29
+.. _Shoutcast 2: http://xbmcaddonbrowser.com/addons/frodo/plugin.audio.shoutcast
  30
+.. _Cheezburger Network: http://xbmcaddonbrowser.com/addons/frodo/plugin.image.cheezburger_network
  31
+.. _Apple Itunes Podcasts: http://xbmcaddonbrowser.com/addons/frodo/plugin.video.itunes_podcasts
  32
+.. _MyZen.tv: http://xbmcaddonbrowser.com/addons/frodo/plugin.video.myzen_tv
  33
+.. _Rofl.to: http://xbmcaddonbrowser.com/addons/frodo/plugin.video.rofl_to
  34
+.. _Wimp: http://xbmcaddonbrowser.com/addons/frodo/plugin.video.wimp
33  fabfile.py
@@ -27,7 +27,8 @@
27 27
 BRANCHES = {
28 28
     # <xbmc_version>: <git_branch>
29 29
     'DHARMA': 'dharma',
30  
-    'EDEN': 'master',
  30
+    'EDEN': 'eden',
  31
+    'FRODO': 'master',
31 32
 }
32 33
 
33 34
 
@@ -66,9 +67,9 @@ def push(self, branch):
66 67
         with lcd(self.path):
67 68
             local('git push --tags origin %s' % branch)
68 69
 
69  
-    def tag(self, version, xbmc_version):
  70
+    def tag(self, tag, message):
70 71
         with lcd(self.path):
71  
-            local('git tag -a %s -m "%s v%s"' % (version, xbmc_version, version))
  72
+            local('git tag -a %s -m "%s"' % (tag, message))
72 73
 
73 74
     def commit(self, version):
74 75
         with lcd(self.path):
@@ -149,8 +150,9 @@ def local_release(xbmc_version=None):
149 150
     if xbmc_version not in BRANCHES.keys():
150 151
         abort('Invalid XBMC version, [dharma, eden]')
151 152
 
152  
-    path = release_prepare(xbmc_version)
153  
-    print 'Development release created at %s' % path
  153
+    local_repo, dist_repo = release_prepare(xbmc_version)
  154
+    print 'Development release created at %s' % dist_repo.path
  155
+
154 156
 
155 157
 @task
156 158
 def release(xbmc_version=None):
@@ -160,8 +162,8 @@ def release(xbmc_version=None):
160 162
     if xbmc_version not in BRANCHES.keys():
161 163
         abort('Invalid XBMC version, [dharma, eden]')
162 164
 
163  
-    dist_path = release_prepare(xbmc_version)
164  
-    release_perform(xbmc_version, dist_path)
  165
+    local_repo, dist_repo = release_prepare(xbmc_version)
  166
+    release_perform(xbmc_version, local_repo, dist_repo)
165 167
 
166 168
 
167 169
 def release_prepare(xbmc_version):
@@ -212,24 +214,25 @@ def release_prepare(xbmc_version):
212 214
     # if user doesn't want to continue they shouldu be able to :cq
213 215
     returncode = subprocess.check_call([EDITOR, changelog])
214 216
 
215  
-    # return path to new dist repo
216  
-    return dist_path
217  
-
  217
+    # return both repos
  218
+    return local_repo, GitRepo(path=dist_path)
218 219
 
219  
-def release_perform(xbmc_version, dist_path):
220  
-    dist_repo = GitRepo(path=dist_path)
221 220
 
  221
+def release_perform(xbmc_version, local_repo, dist_repo):
222 222
     # Stage everything in the repo
223 223
     log('Staging all modified files in the distribution repo...')
224 224
     dist_repo.stage_all()
225 225
 
226 226
     # Get the current XBMC version
227  
-    version = get_addon_version(dist_path)
  227
+    version = get_addon_version(dist_repo.path)
228 228
 
229 229
     # Commit all staged changes and tag
230 230
     log('Commiting changes and tagging the release...')
231 231
     dist_repo.commit(version)
232  
-    dist_repo.tag(version, xbmc_version.lower())
  232
+    dist_repo.tag(version, '%s v%s' % (xbmc_version, version))
  233
+
  234
+    # Tag the local repo as well
  235
+    local_repo.tag('xbmc-%s' % version, 'XBMC distribution v%s' % version)
233 236
 
234 237
     # Push everything
235 238
     log('Pushing changes to remote...')
@@ -238,5 +241,5 @@ def release_perform(xbmc_version, dist_path):
238 241
     puts(colors.green('Release performed.'))
239 242
 
240 243
     # write the email
241  
-    addon_id = get_addon_id(dist_path)
  244
+    addon_id = get_addon_id(dist_repo.path)
242 245
     print_email(addon_id, version, REPO_PUBLIC_URL, version, xbmc_version.lower())
2  setup.py
@@ -46,7 +46,7 @@
46 46
 
47 47
 setup(
48 48
     name = 'xbmcswift2',
49  
-    version = '0.2.2',
  49
+    version = '0.3.0',
50 50
     author = 'Jonathan Beluch',
51 51
     author_email = 'web@jonathanbeluch.com',
52 52
     description = 'A micro framework for rapid development of XBMC plugins.',
10  tests/cli/test_console.py
... ...
@@ -0,0 +1,10 @@
  1
+import unittest
  2
+from xbmcswift2.cli import console
  3
+
  4
+
  5
+class TestConsole(unittest.TestCase):
  6
+
  7
+    def test_get_max_len(self):
  8
+        items = ['a', 'bb', 'ccc', 'dddd']
  9
+        self.assertEqual(console.get_max_len(items), 4)
  10
+        self.assertEqual(console.get_max_len([]), 0)
37  tests/test_listitem.py
@@ -21,7 +21,7 @@ def test_label(self):
21 21
         self.assertEqual(item.label, 'bar')
22 22
         item.set_label('baz')
23 23
         self.assertEqual(item.get_label(), 'baz')
24  
-        
  24
+
25 25
     def test_label2(self):
26 26
         item = ListItem('foo')
27 27
         self.assertIsNone(item.label2)
@@ -41,7 +41,7 @@ def test_icon(self):
41 41
         item.set_icon('baz')
42 42
         self.assertEqual(item.icon, 'baz')
43 43
         self.assertEqual(item.get_icon(), 'baz')
44  
-        
  44
+
45 45
     def test_thumbnail(self):
46 46
         item = ListItem()
47 47
         self.assertIsNone(item.thumbnail)
@@ -83,6 +83,14 @@ def test_set_info(self):
83 83
             item.set_info('video', {'title': '300'})
84 84
         mock_setInfo.assert_called_with('video', {'title': '300'})
85 85
 
  86
+    def test_stream_info(self):
  87
+        with patch.object(xbmcgui.ListItem, 'addStreamInfo') as mock_stream_info:
  88
+            item = ListItem()
  89
+            item.add_stream_info('video', {'duration': 185})
  90
+            mock_stream_info.assert_called_with('video', {'duration': 185})
  91
+            item.add_stream_info('audio', {'languange': 'en'})
  92
+            mock_stream_info.assert_called_with('audio', {'languange': 'en'})
  93
+
86 94
     def test_selected(self):
87 95
         item = ListItem()
88 96
         self.assertEqual(item.selected, False)
@@ -136,7 +144,7 @@ def test_get_property(self, mock_getProperty):
136 144
     @patch('xbmcswift2.xbmcgui.ListItem.setProperty')
137 145
     def test_set_property(self, mock_setProperty):
138 146
         item = ListItem()
139  
-        item.set_property('foo', 'bar') 
  147
+        item.set_property('foo', 'bar')
140 148
         mock_setProperty.assert_called_with('foo', 'bar')
141 149
 
142 150
     def test_as_tuple(self):
@@ -160,6 +168,20 @@ def test_non_basestring_val(self):
160 168
 
161 169
 
162 170
 class TestFromDict(TestCase):
  171
+
  172
+    def test_from_dict_props(self):
  173
+        dct = {
  174
+            'properties': {'StartOffset': '256.4'},
  175
+        }
  176
+        item = ListItem.from_dict(**dct)
  177
+        self.assertEqual(item.get_property('StartOffset'), '256.4')
  178
+
  179
+        dct = {
  180
+            'properties': [('StartOffset', '256.4')],
  181
+        }
  182
+        item = ListItem.from_dict(**dct)
  183
+        self.assertEqual(item.get_property('StartOffset'), '256.4')
  184
+
163 185
     def test_from_dict(self):
164 186
         dct = {
165 187
             'label': 'foo',
@@ -171,10 +193,14 @@ def test_from_dict(self):
171 193
             'info': {'title': 'My title'},
172 194
             'info_type': 'pictures',
173 195
             'properties': [('StartOffset', '256.4')],
  196
+            'stream_info': {
  197
+                'video': {'duration': 185}
  198
+            },
174 199
             'context_menu': [('label', 'action')],
175 200
             'is_playable': True}
176 201
         with patch.object(ListItem, 'set_info', spec=True) as mock_set_info:
177  
-            item = ListItem.from_dict(**dct)
  202
+            with patch.object(ListItem, 'add_stream_info', spec=True) as mock_set_stream_info:
  203
+                item = ListItem.from_dict(**dct)
178 204
         self.assertEqual(item.label, 'foo')
179 205
         self.assertEqual(item.label2, 'bar')
180 206
         self.assertEqual(item.icon, 'icon')
@@ -182,8 +208,9 @@ def test_from_dict(self):
182 208
         self.assertEqual(item.path, 'plugin://my.plugin.id/')
183 209
         self.assertEqual(item.selected, True)
184 210
         mock_set_info.assert_called_with('pictures', {'title': 'My title'})
  211
+        mock_set_stream_info.assert_called_with('video', {'duration': 185})
185 212
         self.assertEqual(item.get_property('StartOffset'), '256.4')
186  
-        self.assertEqual(item.get_context_menu_items(), [('label', 'action')]) 
  213
+        self.assertEqual(item.get_context_menu_items(), [('label', 'action')])
187 214
         self.assertEqual(item.get_property('isPlayable'), 'true')
188 215
         self.assertEqual(item.is_folder, False)
189 216
 
13  tests/test_plugin.py
@@ -161,6 +161,15 @@ def run(relative_url, handle=0, qs='?'):
161 161
 
162 162
 class TestBasicRouting(TestCase):
163 163
 
  164
+    def test_url_for_func(self):
  165
+        plugin = NewPlugin()
  166
+        @plugin.route('/', name='another_name')
  167
+        def main_menu():
  168
+            return [{'label': 'Hello XBMC'}]
  169
+        self.assertEqual(plugin.url_for(main_menu), 'plugin://plugin.video.helloxbmc/')
  170
+        self.assertEqual(plugin.url_for(main_menu, foo='bar'), 'plugin://plugin.video.helloxbmc/?foo=bar')
  171
+        self.assertEqual(plugin.url_for(main_menu, foo=3), 'plugin://plugin.video.helloxbmc/?foo=3')
  172
+
164 173
     def test_url_for(self):
165 174
         plugin = NewPlugin()
166 175
         @plugin.route('/')
@@ -168,7 +177,7 @@ def main_menu():
168 177
             return [{'label': 'Hello XBMC'}]
169 178
         self.assertEqual(plugin.url_for('main_menu'), 'plugin://plugin.video.helloxbmc/')
170 179
         self.assertEqual(plugin.url_for('main_menu', foo='bar'), 'plugin://plugin.video.helloxbmc/?foo=bar')
171  
-        self.assertEqual(plugin.url_for('main_menu', foo=3), 'plugin://plugin.video.helloxbmc/?foo=I3%0A.&_pickled=foo')
  180
+        self.assertEqual(plugin.url_for('main_menu', foo=3), 'plugin://plugin.video.helloxbmc/?foo=3')
172 181
 
173 182
     def test_url_for_multiple_routes(self):
174 183
         plugin = NewPlugin()
@@ -178,7 +187,7 @@ def main_menu():
178 187
             return [{'label': 'Hello XBMC'}]
179 188
         self.assertEqual(plugin.url_for('main_menu'), 'plugin://plugin.video.helloxbmc/')
180 189
         self.assertEqual(plugin.url_for('main_menu', foo='bar'), 'plugin://plugin.video.helloxbmc/?foo=bar')
181  
-        self.assertEqual(plugin.url_for('main_menu', foo=3), 'plugin://plugin.video.helloxbmc/?foo=I3%0A.&_pickled=foo')
  190
+        self.assertEqual(plugin.url_for('main_menu', foo=3), 'plugin://plugin.video.helloxbmc/?foo=3')
182 191
         self.assertEqual(plugin.url_for('videos'), 'plugin://plugin.video.helloxbmc/videos/')
183 192
 
184 193
     def test_options(self):
25  tests/test_storage.py
@@ -33,7 +33,7 @@ def test_pickle(self):
33 33
         self.assertEqual(42, storage2['answer'])
34 34
 
35 35
         remove(filename)
36  
-        
  36
+
37 37
     def test_csv(self):
38 38
         filename = '/tmp/testdict.csv'
39 39
         remove(filename)
@@ -52,7 +52,7 @@ def test_csv(self):
52 52
         self.assertEqual('42', storage2['answer'])
53 53
 
54 54
         remove(filename)
55  
-    
  55
+
56 56
     def test_json(self):
57 57
         filename = '/tmp/testdict.json'
58 58
         remove(filename)
@@ -71,9 +71,10 @@ def test_json(self):
71 71
         self.assertEqual('42', storage2['answer'])
72 72
 
73 73
         remove(filename)
74  
-    
  74
+
75 75
 
76 76
 class TestTimedStorage(TestCase):
  77
+
77 78
     def test_pickle(self):
78 79
         filename = '/tmp/testdict.pickle'
79 80
         remove(filename)
@@ -95,3 +96,21 @@ def test_pickle(self):
95 96
         # Ensure the expired dict was synced
96 97
         storage4 = TimedStorage(filename, file_format='pickle', TTL=timedelta(hours=1))
97 98
         self.assertEqual(sorted(storage3.items()), sorted(storage4.items()))
  99
+
  100
+
  101
+class Test_Storage(TestCase):
  102
+
  103
+    def test_clear(self):
  104
+        filename = '/tmp/testclear.json'
  105
+        storage = _Storage(filename, file_format='json')
  106
+        storage['name'] = 'jon'
  107
+        storage.sync()
  108
+
  109
+        # dict with single value is now saved to disk
  110
+        with open(filename) as inp:
  111
+            self.assertEqual(inp.read(), '{"name":"jon"}')
  112
+
  113
+        # now clear the dict, it should sync to disk.
  114
+        storage.clear()
  115
+        with open(filename) as inp:
  116
+            self.assertEqual(inp.read(), '{}')
47  tests/test_urls.py
... ...
@@ -0,0 +1,47 @@
  1
+import unittest
  2
+from xbmcswift2 import UrlRule
  3
+
  4
+
  5
+class TestUrls(unittest.TestCase):
  6
+
  7
+    def test_make_path_qs(self):
  8
+        def view(video_id):
  9
+            pass
  10
+
  11
+        rule = UrlRule('/videos/<video_id>', view, view.__name__, {})
  12
+
  13
+        path_qs = rule.make_path_qs({'video_id': '24'})
  14
+        self.assertEqual(path_qs, '/videos/24')
  15
+
  16
+        # allow ints
  17
+        path_qs = rule.make_path_qs({'video_id': 24})
  18
+        self.assertEqual(path_qs, '/videos/24')
  19
+
  20
+    def test_make_qs(self):
  21
+        def view(video_id):
  22
+            pass
  23
+
  24
+        rule = UrlRule('/videos', view, view.__name__, {})
  25
+
  26
+        path_qs = rule.make_path_qs({'video_id': '24'})
  27
+        self.assertEqual(path_qs, '/videos?video_id=24')
  28
+
  29
+        # allow ints
  30
+        path_qs = rule.make_path_qs({'video_id': 24})
  31
+        self.assertEqual(path_qs, '/videos?video_id=24')
  32
+
  33
+class TestUrlRule(unittest.TestCase):
  34
+
  35
+    def test_match_without_trailing_slash(self):
  36
+        def view():
  37
+            pass
  38
+        rule = UrlRule('/videos/', view, view.__name__, {})
  39
+        self.assertEqual((view, {}), rule.match('/videos'))
  40
+
  41
+    def test_match_with_trailing_slash(self):
  42
+        def view():
  43
+            pass
  44
+        rule = UrlRule('/videos', view, view.__name__, {})
  45
+        self.assertEqual((view, {}), rule.match('/videos/'))
  46
+
  47
+
60  tests/test_xbmcmixin.py
@@ -29,6 +29,8 @@ class TestMixedIn(XBMCMixin):
29 29
 
30 30
 class MixedIn(XBMCMixin):
31 31
 
  32
+    storage_path = '/tmp'