Flake8 Compatibility
This patch makes the feedgen flake8 compatible, fixing some minor issues along the way. Most noticeable, this switches from tabs to spaces. Signed-off-by: Lars Kiesow <lkiesow@uos.de>
This commit is contained in:
parent
ccf18502bc
commit
444855a248
19 changed files with 3553 additions and 3572 deletions
1
Makefile
1
Makefile
|
@ -53,3 +53,4 @@ test:
|
||||||
python -m unittest tests.test_entry
|
python -m unittest tests.test_entry
|
||||||
python -m unittest tests.test_extension
|
python -m unittest tests.test_extension
|
||||||
@rm -f tmp_Atomfeed.xml tmp_Rssfeed.xml
|
@rm -f tmp_Atomfeed.xml tmp_Rssfeed.xml
|
||||||
|
flake8 $$(find setup.py tests feedgen -name '*.py')
|
||||||
|
|
94
doc/conf.py
94
doc/conf.py
|
@ -3,7 +3,10 @@
|
||||||
# All configuration values have a default; values that are commented out
|
# All configuration values have a default; values that are commented out
|
||||||
# serve to show the default.
|
# serve to show the default.
|
||||||
|
|
||||||
import sys, os, time, codecs, re
|
import sys
|
||||||
|
import os
|
||||||
|
import codecs
|
||||||
|
import re
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
@ -13,18 +16,18 @@ sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
import feedgen.version
|
import feedgen.version
|
||||||
|
|
||||||
# -- General configuration -----------------------------------------------------
|
# -- General configuration ----------------------------------------------------
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
#needs_sphinx = '1.0'
|
#needs_sphinx = '1.0'
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
extensions = [
|
extensions = [
|
||||||
'sphinx.ext.intersphinx',
|
'sphinx.ext.intersphinx',
|
||||||
'sphinx.ext.coverage',
|
'sphinx.ext.coverage',
|
||||||
'sphinx.ext.autodoc'
|
'sphinx.ext.autodoc'
|
||||||
]
|
]
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
|
@ -65,7 +68,8 @@ release = feedgen.version.version_full_str
|
||||||
# directories to ignore when looking for source files.
|
# directories to ignore when looking for source files.
|
||||||
exclude_patterns = ['_build']
|
exclude_patterns = ['_build']
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
# The reST default role (used for this markup: `text`) to use for all
|
||||||
|
# documents.
|
||||||
#default_role = None
|
#default_role = None
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
|
@ -86,7 +90,7 @@ pygments_style = 'sphinx'
|
||||||
#modindex_common_prefix = []
|
#modindex_common_prefix = []
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output ---------------------------------------------------
|
# -- Options for HTML output --------------------------------------------------
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
# a list of builtin themes.
|
# a list of builtin themes.
|
||||||
|
@ -169,24 +173,24 @@ html_static_path = ['_static']
|
||||||
htmlhelp_basename = 'pyFeedGen'
|
htmlhelp_basename = 'pyFeedGen'
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output --------------------------------------------------
|
# -- Options for LaTeX output -------------------------------------------------
|
||||||
|
|
||||||
latex_elements = {
|
latex_elements = {
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
#'papersize': 'letterpaper',
|
#'papersize': 'letterpaper',
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
#'pointsize': '10pt',
|
#'pointsize': '10pt',
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
# Additional stuff for the LaTeX preamble.
|
||||||
#'preamble': '',
|
#'preamble': '',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
# Grouping the document tree into LaTeX files. List of tuples (source start
|
||||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
# file, target name, title, author, documentclass [howto/manual]).
|
||||||
latex_documents = [
|
latex_documents = [
|
||||||
('index', 'pyFeedGen.tex', u'pyFeedGen Documentation',
|
('index', 'pyFeedGen.tex', u'pyFeedGen Documentation', u'Lars Kiesow',
|
||||||
u'Lars Kiesow', 'manual'),
|
'manual'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
@ -210,28 +214,28 @@ latex_documents = [
|
||||||
#latex_domain_indices = True
|
#latex_domain_indices = True
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output --------------------------------------------
|
# -- Options for manual page output -------------------------------------------
|
||||||
|
|
||||||
# One entry per manual page. List of tuples
|
# One entry per manual page. List of tuples
|
||||||
# (source start file, name, description, authors, manual section).
|
# (source start file, name, description, authors, manual section).
|
||||||
man_pages = [
|
man_pages = [
|
||||||
('index', 'pyFeedGen.tex', u'pyFeedGen Documentation',
|
('index', 'pyFeedGen.tex', u'pyFeedGen Documentation',
|
||||||
[u'Lars Kiesow'], 1)
|
[u'Lars Kiesow'], 1)
|
||||||
]
|
]
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
# If true, show URL addresses after external links.
|
||||||
#man_show_urls = False
|
#man_show_urls = False
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Texinfo output ------------------------------------------------
|
# -- Options for Texinfo output -----------------------------------------------
|
||||||
|
|
||||||
# Grouping the document tree into Texinfo files. List of tuples
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
# (source start file, target name, title, author,
|
# (source start file, target name, title, author,
|
||||||
# dir menu entry, description, category)
|
# dir menu entry, description, category)
|
||||||
texinfo_documents = [
|
texinfo_documents = [
|
||||||
('index', 'pyFeedGen.tex', u'pyFeedGen Documentation',
|
('index', 'pyFeedGen.tex', u'pyFeedGen Documentation',
|
||||||
u'Lars Kiesow', 'Lernfunk3', 'One line description of project.',
|
u'Lars Kiesow', 'Lernfunk3', 'One line description of project.',
|
||||||
'Miscellaneous'),
|
'Miscellaneous'),
|
||||||
]
|
]
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
# Documents to append as an appendix to all manuals.
|
||||||
|
@ -248,36 +252,18 @@ texinfo_documents = [
|
||||||
intersphinx_mapping = {'http://docs.python.org/': None}
|
intersphinx_mapping = {'http://docs.python.org/': None}
|
||||||
|
|
||||||
|
|
||||||
# Ugly way of setting tabsize
|
|
||||||
import re
|
|
||||||
|
|
||||||
def process_docstring(app, what, name, obj, options, lines):
|
|
||||||
'''
|
|
||||||
spaces_pat = re.compile(r"(?<= )( {8})")
|
|
||||||
ll = []
|
|
||||||
for l in lines:
|
|
||||||
ll.append(spaces_pat.sub(" ",l))
|
|
||||||
'''
|
|
||||||
spaces_pat = re.compile(r"^ *")
|
|
||||||
ll = []
|
|
||||||
for l in lines:
|
|
||||||
spacelen = len(spaces_pat.search(l).group(0))
|
|
||||||
newlen = (spacelen % 8) + (spacelen / 8 * 4)
|
|
||||||
ll.append( (' '*newlen) + l.lstrip(' ') )
|
|
||||||
lines[:] = ll
|
|
||||||
|
|
||||||
|
|
||||||
# Include the GitHub readme file in index.rst
|
# Include the GitHub readme file in index.rst
|
||||||
r = re.compile(r'\[`*([^\]`]+)`*\]\(([^\)]+)\)')
|
r = re.compile(r'\[`*([^\]`]+)`*\]\(([^\)]+)\)')
|
||||||
r2 = re.compile(r'.. include-github-readme')
|
r2 = re.compile(r'.. include-github-readme')
|
||||||
|
|
||||||
|
|
||||||
def substitute_link(app, docname, text):
|
def substitute_link(app, docname, text):
|
||||||
if docname == 'index':
|
if docname == 'index':
|
||||||
readme_text = ''
|
readme_text = ''
|
||||||
with codecs.open(os.path.abspath('../readme.md'), 'r', 'utf-8') as f:
|
with codecs.open(os.path.abspath('../readme.md'), 'r', 'utf-8') as f:
|
||||||
readme_text = r.sub(r'`\1 <\2>`_', f.read())
|
readme_text = r.sub(r'`\1 <\2>`_', f.read())
|
||||||
text[0] = r2.sub(readme_text, text[0])
|
text[0] = r2.sub(readme_text, text[0])
|
||||||
|
|
||||||
|
|
||||||
def setup(app):
|
def setup(app):
|
||||||
app.connect('autodoc-process-docstring', process_docstring)
|
app.connect('source-read', substitute_link)
|
||||||
app.connect('source-read', substitute_link)
|
|
||||||
|
|
|
@ -1,136 +1,137 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
=======
|
=======
|
||||||
feedgen
|
feedgen
|
||||||
=======
|
=======
|
||||||
|
|
||||||
This module can be used to generate web feeds in both ATOM and RSS format.
|
This module can be used to generate web feeds in both ATOM and RSS format.
|
||||||
It has support for extensions. Included is for example an extension to
|
It has support for extensions. Included is for example an extension to
|
||||||
produce Podcasts.
|
produce Podcasts.
|
||||||
|
|
||||||
:copyright: 2013 by Lars Kiesow
|
:copyright: 2013 by Lars Kiesow
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
|
|
||||||
|
|
||||||
-------------
|
-------------
|
||||||
Create a Feed
|
Create a Feed
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
To create a feed simply instanciate the FeedGenerator class and insert some
|
To create a feed simply instanciate the FeedGenerator class and insert some
|
||||||
data::
|
data::
|
||||||
|
|
||||||
>>> from feedgen.feed import FeedGenerator
|
>>> from feedgen.feed import FeedGenerator
|
||||||
>>> fg = FeedGenerator()
|
>>> fg = FeedGenerator()
|
||||||
>>> fg.id('http://lernfunk.de/media/654321')
|
>>> fg.id('http://lernfunk.de/media/654321')
|
||||||
>>> fg.title('Some Testfeed')
|
>>> fg.title('Some Testfeed')
|
||||||
>>> fg.author( {'name':'John Doe','email':'john@example.de'} )
|
>>> fg.author( {'name':'John Doe','email':'john@example.de'} )
|
||||||
>>> fg.link( href='http://example.com', rel='alternate' )
|
>>> fg.link( href='http://example.com', rel='alternate' )
|
||||||
>>> fg.logo('http://ex.com/logo.jpg')
|
>>> fg.logo('http://ex.com/logo.jpg')
|
||||||
>>> fg.subtitle('This is a cool feed!')
|
>>> fg.subtitle('This is a cool feed!')
|
||||||
>>> fg.link( href='http://larskiesow.de/test.atom', rel='self' )
|
>>> fg.link( href='http://larskiesow.de/test.atom', rel='self' )
|
||||||
>>> fg.language('en')
|
>>> fg.language('en')
|
||||||
|
|
||||||
Note that for the methods which set fields that can occur more than once in
|
Note that for the methods which set fields that can occur more than once in
|
||||||
a feed you can use all of the following ways to provide data:
|
a feed you can use all of the following ways to provide data:
|
||||||
|
|
||||||
- Provide the data for that element as keyword arguments
|
- Provide the data for that element as keyword arguments
|
||||||
- Provide the data for that element as dictionary
|
- Provide the data for that element as dictionary
|
||||||
- Provide a list of dictionaries with the data for several elements
|
- Provide a list of dictionaries with the data for several elements
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
|
||||||
>>> fg.contributor( name='John Doe', email='jdoe@example.com' )
|
>>> fg.contributor(name='John Doe', email='jdoe@example.com' )
|
||||||
>>> fg.contributor({'name':'John Doe', 'email':'jdoe@example.com'})
|
>>> fg.contributor({'name':'John Doe', 'email':'jdoe@example.com'})
|
||||||
>>> fg.contributor([{'name':'John Doe', 'email':'jdoe@example.com'}, ...])
|
>>> fg.contributor([{'name':'John', 'email':'jdoe@example.com'}, …])
|
||||||
|
|
||||||
-----------------
|
-----------------
|
||||||
Generate the Feed
|
Generate the Feed
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
After that you can generate both RSS or ATOM by calling the respective method::
|
After that you can generate both RSS or ATOM by calling the respective
|
||||||
|
method::
|
||||||
|
|
||||||
>>> atomfeed = fg.atom_str(pretty=True) # Get the ATOM feed as string
|
>>> atomfeed = fg.atom_str(pretty=True) # Get the ATOM feed as string
|
||||||
>>> rssfeed = fg.rss_str(pretty=True) # Get the RSS feed as string
|
>>> rssfeed = fg.rss_str(pretty=True) # Get the RSS feed as string
|
||||||
>>> fg.atom_file('atom.xml') # Write the ATOM feed to a file
|
>>> fg.atom_file('atom.xml') # Write the ATOM feed to a file
|
||||||
>>> fg.rss_file('rss.xml') # Write the RSS feed to a file
|
>>> fg.rss_file('rss.xml') # Write the RSS feed to a file
|
||||||
|
|
||||||
|
|
||||||
----------------
|
----------------
|
||||||
Add Feed Entries
|
Add Feed Entries
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
To add entries (items) to a feed you need to create new FeedEntry objects
|
To add entries (items) to a feed you need to create new FeedEntry objects
|
||||||
and append them to the list of entries in the FeedGenerator. The most
|
and append them to the list of entries in the FeedGenerator. The most
|
||||||
convenient way to go is to use the FeedGenerator itself for the
|
convenient way to go is to use the FeedGenerator itself for the
|
||||||
instantiation of the FeedEntry object::
|
instantiation of the FeedEntry object::
|
||||||
|
|
||||||
>>> fe = fg.add_entry()
|
>>> fe = fg.add_entry()
|
||||||
>>> fe.id('http://lernfunk.de/media/654321/1')
|
>>> fe.id('http://lernfunk.de/media/654321/1')
|
||||||
>>> fe.title('The First Episode')
|
>>> fe.title('The First Episode')
|
||||||
|
|
||||||
The FeedGenerators method add_entry(...) without argument provides will
|
The FeedGenerators method add_entry(...) without argument provides will
|
||||||
automatically generate a new FeedEntry object, append it to the feeds
|
automatically generate a new FeedEntry object, append it to the feeds
|
||||||
internal list of entries and return it, so that additional data can be
|
internal list of entries and return it, so that additional data can be
|
||||||
added.
|
added.
|
||||||
|
|
||||||
----------
|
----------
|
||||||
Extensions
|
Extensions
|
||||||
----------
|
----------
|
||||||
|
|
||||||
The FeedGenerator supports extension to include additional data into the XML
|
The FeedGenerator supports extension to include additional data into the
|
||||||
structure of the feeds. Extensions can be loaded like this::
|
XML structure of the feeds. Extensions can be loaded like this::
|
||||||
|
|
||||||
>>> fg.load_extension('someext', atom=True, rss=True)
|
>>> fg.load_extension('someext', atom=True, rss=True)
|
||||||
|
|
||||||
This will try to load the extension “someext” from the file
|
This will try to load the extension “someext” from the file
|
||||||
`ext/someext.py`. It is required that `someext.py` contains a class named
|
`ext/someext.py`. It is required that `someext.py` contains a class named
|
||||||
“SomextExtension” which is required to have at least the two methods
|
“SomextExtension” which is required to have at least the two methods
|
||||||
`extend_rss(...)` and `extend_atom(...)`. Although not required, it is
|
`extend_rss(...)` and `extend_atom(...)`. Although not required, it is
|
||||||
strongly suggested to use BaseExtension from `ext/base.py` as superclass.
|
strongly suggested to use BaseExtension from `ext/base.py` as superclass.
|
||||||
|
|
||||||
`load_extension('someext', ...)` will also try to load a class named
|
`load_extension('someext', ...)` will also try to load a class named
|
||||||
“SomextEntryExtension” for every entry of the feed. This class can be
|
“SomextEntryExtension” for every entry of the feed. This class can be
|
||||||
located either in the same file as SomextExtension or in
|
located either in the same file as SomextExtension or in
|
||||||
`ext/someext_entry.py` which is suggested especially for large extensions.
|
`ext/someext_entry.py` which is suggested especially for large extensions.
|
||||||
|
|
||||||
The parameters `atom` and `rss` tell the FeedGenerator if the extensions
|
The parameters `atom` and `rss` tell the FeedGenerator if the extensions
|
||||||
should only be used for either ATOM or RSS feeds. The default value for both
|
should only be used for either ATOM or RSS feeds. The default value for
|
||||||
parameters is true which means that the extension would be used for both
|
both parameters is true which means that the extension would be used for
|
||||||
kinds of feeds.
|
both kinds of feeds.
|
||||||
|
|
||||||
**Example: Produceing a Podcast**
|
**Example: Produceing a Podcast**
|
||||||
|
|
||||||
One extension already provided is the podcast extension. A podcast is an RSS
|
One extension already provided is the podcast extension. A podcast is an
|
||||||
feed with some additional elements for ITunes.
|
RSS feed with some additional elements for ITunes.
|
||||||
|
|
||||||
To produce a podcast simply load the `podcast` extension::
|
To produce a podcast simply load the `podcast` extension::
|
||||||
|
|
||||||
>>> from feedgen.feed import FeedGenerator
|
>>> from feedgen.feed import FeedGenerator
|
||||||
>>> fg = FeedGenerator()
|
>>> fg = FeedGenerator()
|
||||||
>>> fg.load_extension('podcast')
|
>>> fg.load_extension('podcast')
|
||||||
...
|
...
|
||||||
>>> fg.podcast.itunes_category('Technology', 'Podcasting')
|
>>> fg.podcast.itunes_category('Technology', 'Podcasting')
|
||||||
...
|
...
|
||||||
>>> fg.rss_str(pretty=True)
|
>>> fg.rss_str(pretty=True)
|
||||||
>>> fg.rss_file('podcast.xml')
|
>>> fg.rss_file('podcast.xml')
|
||||||
|
|
||||||
Of cause the extension has to be loaded for the FeedEntry objects as well
|
Of cause the extension has to be loaded for the FeedEntry objects as well
|
||||||
but this is done automatically by the FeedGenerator for every feed entry if
|
but this is done automatically by the FeedGenerator for every feed entry if
|
||||||
the extension is loaded for the whole feed. You can, however, load an
|
the extension is loaded for the whole feed. You can, however, load an
|
||||||
extension for a specific FeedEntry by calling `load_extension(...)` on that
|
extension for a specific FeedEntry by calling `load_extension(...)` on that
|
||||||
entry. But this is a rather uncommon use.
|
entry. But this is a rather uncommon use.
|
||||||
|
|
||||||
Of cause you can still produce a normal ATOM or RSS feed, even if you have
|
Of cause you can still produce a normal ATOM or RSS feed, even if you have
|
||||||
loaded some plugins by temporary disabling them during the feed generation.
|
loaded some plugins by temporary disabling them during the feed generation.
|
||||||
This can be done by calling the generating method with the keyword argument
|
This can be done by calling the generating method with the keyword argument
|
||||||
`extensions` set to `False`.
|
`extensions` set to `False`.
|
||||||
|
|
||||||
---------------------
|
---------------------
|
||||||
Testing the Generator
|
Testing the Generator
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
You can test the module by simply executing::
|
You can test the module by simply executing::
|
||||||
|
|
||||||
$ python -m feedgen
|
$ python -m feedgen
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,129 +1,140 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''
|
'''
|
||||||
feedgen
|
feedgen
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
|
||||||
:copyright: 2013-2016, Lars Kiesow <lkiesow@uos.de>
|
:copyright: 2013-2016, Lars Kiesow <lkiesow@uos.de>
|
||||||
|
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from feedgen.feed import FeedGenerator
|
from feedgen.feed import FeedGenerator
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def print_enc(s):
|
|
||||||
'''Print function compatible with both python2 and python3 accepting strings
|
|
||||||
and byte arrays.
|
|
||||||
'''
|
|
||||||
if sys.version_info[0] >= 3:
|
|
||||||
print(s.decode('utf-8') if type(s) == type(b'') else s)
|
|
||||||
else:
|
|
||||||
print(s)
|
|
||||||
|
|
||||||
|
USAGE = '''
|
||||||
|
Usage: python -m feedgen [OPTION]
|
||||||
|
|
||||||
|
Use one of the following options:
|
||||||
|
|
||||||
|
File options:
|
||||||
|
<file>.atom -- Generate ATOM test feed
|
||||||
|
<file>.rss -- Generate RSS test teed
|
||||||
|
|
||||||
|
Stdout options:
|
||||||
|
atom -- Generate ATOM test output
|
||||||
|
rss -- Generate RSS test output
|
||||||
|
podcast -- Generate Podcast test output
|
||||||
|
dc.atom -- Generate DC extension test output (atom format)
|
||||||
|
dc.rss -- Generate DC extension test output (rss format)
|
||||||
|
syndication.atom -- Generate syndication extension test output (atom format)
|
||||||
|
syndication.rss -- Generate syndication extension test output (rss format)
|
||||||
|
torrent -- Generate Torrent test output
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def print_enc(s):
|
||||||
|
'''Print function compatible with both python2 and python3 accepting strings
|
||||||
|
and byte arrays.
|
||||||
|
'''
|
||||||
|
if sys.version_info[0] >= 3:
|
||||||
|
print(s.decode('utf-8') if isinstance(s, bytes) else s)
|
||||||
|
else:
|
||||||
|
print(s)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if len(sys.argv) != 2 or not (
|
if len(sys.argv) != 2 or not (
|
||||||
sys.argv[1].endswith('rss') \
|
sys.argv[1].endswith('rss') or
|
||||||
or sys.argv[1].endswith('atom') \
|
sys.argv[1].endswith('atom') or
|
||||||
or sys.argv[1].endswith('torrent') \
|
sys.argv[1] == 'torrent' or
|
||||||
or sys.argv[1].endswith('podcast') ):
|
sys.argv[1] == 'podcast'):
|
||||||
print_enc ('Usage: %s ( <file>.atom | atom | <file>.rss | rss | podcast | torrent )' % \
|
print(USAGE)
|
||||||
'python -m feedgen')
|
exit()
|
||||||
print_enc ('')
|
|
||||||
print_enc (' atom -- Generate ATOM test output and print it to stdout.')
|
|
||||||
print_enc (' rss -- Generate RSS test output and print it to stdout.')
|
|
||||||
print_enc (' <file>.atom -- Generate ATOM test feed and write it to file.atom.')
|
|
||||||
print_enc (' <file>.rss -- Generate RSS test teed and write it to file.rss.')
|
|
||||||
print_enc (' podcast -- Generate Podcast test output and print it to stdout.')
|
|
||||||
print_enc (' dc.atom -- Generate DC extension test output (atom format) and print it to stdout.')
|
|
||||||
print_enc (' dc.rss -- Generate DC extension test output (rss format) and print it to stdout.')
|
|
||||||
print_enc (' syndication.atom -- Generate syndication extension test output (atom format) and print it to stdout.')
|
|
||||||
print_enc (' syndication.rss -- Generate syndication extension test output (rss format) and print it to stdout.')
|
|
||||||
print_enc (' torrent -- Generate Torrent test output and print it to stdout.')
|
|
||||||
print_enc ('')
|
|
||||||
exit()
|
|
||||||
|
|
||||||
arg = sys.argv[1]
|
arg = sys.argv[1]
|
||||||
|
|
||||||
fg = FeedGenerator()
|
fg = FeedGenerator()
|
||||||
fg.id('http://lernfunk.de/_MEDIAID_123')
|
fg.id('http://lernfunk.de/_MEDIAID_123')
|
||||||
fg.title('Testfeed')
|
fg.title('Testfeed')
|
||||||
fg.author( {'name':'Lars Kiesow','email':'lkiesow@uos.de'} )
|
fg.author({'name': 'Lars Kiesow', 'email': 'lkiesow@uos.de'})
|
||||||
fg.link( href='http://example.com', rel='alternate' )
|
fg.link(href='http://example.com', rel='alternate')
|
||||||
fg.category(term='test')
|
fg.category(term='test')
|
||||||
fg.contributor( name='Lars Kiesow', email='lkiesow@uos.de' )
|
fg.contributor(name='Lars Kiesow', email='lkiesow@uos.de')
|
||||||
fg.contributor( name='John Doe', email='jdoe@example.com' )
|
fg.contributor(name='John Doe', email='jdoe@example.com')
|
||||||
fg.icon('http://ex.com/icon.jpg')
|
fg.icon('http://ex.com/icon.jpg')
|
||||||
fg.logo('http://ex.com/logo.jpg')
|
fg.logo('http://ex.com/logo.jpg')
|
||||||
fg.rights('cc-by')
|
fg.rights('cc-by')
|
||||||
fg.subtitle('This is a cool feed!')
|
fg.subtitle('This is a cool feed!')
|
||||||
fg.link( href='http://larskiesow.de/test.atom', rel='self' )
|
fg.link(href='http://larskiesow.de/test.atom', rel='self')
|
||||||
fg.language('de')
|
fg.language('de')
|
||||||
fe = fg.add_entry()
|
fe = fg.add_entry()
|
||||||
fe.id('http://lernfunk.de/_MEDIAID_123#1')
|
fe.id('http://lernfunk.de/_MEDIAID_123#1')
|
||||||
fe.title('First Element')
|
fe.title('First Element')
|
||||||
fe.content('''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Tamen
|
fe.content('''Lorem ipsum dolor sit amet, consectetur adipiscing elit. Tamen
|
||||||
aberramus a proposito, et, ne longius, prorsus, inquam, Piso, si ista
|
aberramus a proposito, et, ne longius, prorsus, inquam, Piso, si
|
||||||
mala sunt, placet. Aut etiam, ut vestitum, sic sententiam habeas aliam
|
ista mala sunt, placet. Aut etiam, ut vestitum, sic sententiam
|
||||||
domesticam, aliam forensem, ut in fronte ostentatio sit, intus veritas
|
habeas aliam domesticam, aliam forensem, ut in fronte ostentatio
|
||||||
occultetur? Cum id fugiunt, re eadem defendunt, quae Peripatetici,
|
sit, intus veritas occultetur? Cum id fugiunt, re eadem defendunt,
|
||||||
verba.''')
|
quae Peripatetici, verba.''')
|
||||||
fe.summary(u'Lorem ipsum dolor sit amet, consectetur adipiscing elit…')
|
fe.summary(u'Lorem ipsum dolor sit amet, consectetur adipiscing elit…')
|
||||||
fe.link( href='http://example.com', rel='alternate' )
|
fe.link(href='http://example.com', rel='alternate')
|
||||||
fe.author( name='Lars Kiesow', email='lkiesow@uos.de' )
|
fe.author(name='Lars Kiesow', email='lkiesow@uos.de')
|
||||||
|
|
||||||
if arg == 'atom':
|
if arg == 'atom':
|
||||||
print_enc (fg.atom_str(pretty=True))
|
print_enc(fg.atom_str(pretty=True))
|
||||||
elif arg == 'rss':
|
elif arg == 'rss':
|
||||||
print_enc (fg.rss_str(pretty=True))
|
print_enc(fg.rss_str(pretty=True))
|
||||||
elif arg == 'podcast':
|
elif arg == 'podcast':
|
||||||
# Load the podcast extension. It will automatically be loaded for all
|
# Load the podcast extension. It will automatically be loaded for all
|
||||||
# entries in the feed, too. Thus also for our “fe”.
|
# entries in the feed, too. Thus also for our “fe”.
|
||||||
fg.load_extension('podcast')
|
fg.load_extension('podcast')
|
||||||
fg.podcast.itunes_author('Lars Kiesow')
|
fg.podcast.itunes_author('Lars Kiesow')
|
||||||
fg.podcast.itunes_category('Technology', 'Podcasting')
|
fg.podcast.itunes_category('Technology', 'Podcasting')
|
||||||
fg.podcast.itunes_explicit('no')
|
fg.podcast.itunes_explicit('no')
|
||||||
fg.podcast.itunes_complete('no')
|
fg.podcast.itunes_complete('no')
|
||||||
fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss')
|
fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss')
|
||||||
fg.podcast.itunes_owner('John Doe', 'john@example.com')
|
fg.podcast.itunes_owner('John Doe', 'john@example.com')
|
||||||
fg.podcast.itunes_summary('Lorem ipsum dolor sit amet, ' + \
|
fg.podcast.itunes_summary('Lorem ipsum dolor sit amet, consectetur ' +
|
||||||
'consectetur adipiscing elit. ' + \
|
'adipiscing elit. Verba tu fingas et ea ' +
|
||||||
'Verba tu fingas et ea dicas, quae non sentias?')
|
'dicas, quae non sentias?')
|
||||||
fe.podcast.itunes_author('Lars Kiesow')
|
fe.podcast.itunes_author('Lars Kiesow')
|
||||||
print_enc (fg.rss_str(pretty=True))
|
print_enc(fg.rss_str(pretty=True))
|
||||||
|
|
||||||
elif arg == 'torrent':
|
elif arg == 'torrent':
|
||||||
fg.load_extension('torrent')
|
fg.load_extension('torrent')
|
||||||
fe.link( href='http://somewhere.behind.the.sea/torrent/debian-8.4.0-i386-netint.iso.torrent', rel='alternate', type='application/x-bittorrent, length=1000' )
|
fe.link(href='http://example.com/torrent/debian-8-netint.iso.torrent',
|
||||||
fe.torrent.filename('debian-8.4.0-i386-netint.iso.torrent')
|
rel='alternate',
|
||||||
fe.torrent.infohash('7661229811ef32014879ceedcdf4a48f256c88ba')
|
type='application/x-bittorrent, length=1000')
|
||||||
fe.torrent.contentlength('331350016')
|
fe.torrent.filename('debian-8.4.0-i386-netint.iso.torrent')
|
||||||
fe.torrent.seeds('789')
|
fe.torrent.infohash('7661229811ef32014879ceedcdf4a48f256c88ba')
|
||||||
fe.torrent.peers('456')
|
fe.torrent.contentlength('331350016')
|
||||||
fe.torrent.verified('123')
|
fe.torrent.seeds('789')
|
||||||
print_enc (fg.rss_str(pretty=True))
|
fe.torrent.peers('456')
|
||||||
|
fe.torrent.verified('123')
|
||||||
|
print_enc(fg.rss_str(pretty=True))
|
||||||
|
|
||||||
elif arg.startswith('dc.'):
|
elif arg.startswith('dc.'):
|
||||||
fg.load_extension('dc')
|
fg.load_extension('dc')
|
||||||
fg.dc.dc_contributor('Lars Kiesow')
|
fg.dc.dc_contributor('Lars Kiesow')
|
||||||
if arg.endswith('.atom'):
|
if arg.endswith('.atom'):
|
||||||
print_enc (fg.atom_str(pretty=True))
|
print_enc(fg.atom_str(pretty=True))
|
||||||
else:
|
else:
|
||||||
print_enc (fg.rss_str(pretty=True))
|
print_enc(fg.rss_str(pretty=True))
|
||||||
|
|
||||||
elif arg.startswith('syndication'):
|
elif arg.startswith('syndication'):
|
||||||
fg.load_extension('syndication')
|
fg.load_extension('syndication')
|
||||||
fg.syndication.update_period('daily')
|
fg.syndication.update_period('daily')
|
||||||
fg.syndication.update_frequency(2)
|
fg.syndication.update_frequency(2)
|
||||||
fg.syndication.update_base('2000-01-01T12:00+00:00')
|
fg.syndication.update_base('2000-01-01T12:00+00:00')
|
||||||
if arg.endswith('.rss'):
|
if arg.endswith('.rss'):
|
||||||
print_enc (fg.rss_str(pretty=True))
|
print_enc(fg.rss_str(pretty=True))
|
||||||
else:
|
else:
|
||||||
print_enc (fg.atom_str(pretty=True))
|
print_enc(fg.atom_str(pretty=True))
|
||||||
|
|
||||||
elif arg.endswith('atom'):
|
elif arg.endswith('atom'):
|
||||||
fg.atom_file(arg)
|
fg.atom_file(arg)
|
||||||
|
|
||||||
elif arg.endswith('rss'):
|
elif arg.endswith('rss'):
|
||||||
fg.rss_file(arg)
|
fg.rss_file(arg)
|
||||||
|
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
if sys.version_info[0] >= 3:
|
if sys.version_info[0] >= 3:
|
||||||
string_types = str
|
string_types = str
|
||||||
else:
|
else:
|
||||||
string_types = basestring
|
string_types = basestring # noqa: F821
|
||||||
|
|
1325
feedgen/entry.py
1325
feedgen/entry.py
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
===========
|
===========
|
||||||
feedgen.ext
|
feedgen.ext
|
||||||
===========
|
===========
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,43 +1,43 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''
|
'''
|
||||||
feedgen.ext.base
|
feedgen.ext.base
|
||||||
~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Basic FeedGenerator extension which does nothing but provides all necessary
|
Basic FeedGenerator extension which does nothing but provides all necessary
|
||||||
methods.
|
methods.
|
||||||
|
|
||||||
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
|
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
|
||||||
|
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
class BaseExtension(object):
|
class BaseExtension(object):
|
||||||
'''Basic FeedGenerator extension.
|
'''Basic FeedGenerator extension.
|
||||||
'''
|
'''
|
||||||
def extend_ns(self):
|
def extend_ns(self):
|
||||||
'''Returns a dict that will be used in the namespace map for the feed.'''
|
'''Returns a dict that will be used in the namespace map for the feed.
|
||||||
return dict()
|
'''
|
||||||
|
return dict()
|
||||||
|
|
||||||
def extend_rss(self, feed):
|
def extend_rss(self, feed):
|
||||||
'''Extend a RSS feed xml structure containing all previously set fields.
|
'''Extend a RSS feed xml structure containing all previously set fields.
|
||||||
|
|
||||||
:param feed: The feed xml root element.
|
:param feed: The feed xml root element.
|
||||||
:returns: The feed root element.
|
:returns: The feed root element.
|
||||||
'''
|
'''
|
||||||
return feed
|
return feed
|
||||||
|
|
||||||
|
def extend_atom(self, feed):
|
||||||
|
'''Extend an ATOM feed xml structure containing all previously set
|
||||||
|
fields.
|
||||||
|
|
||||||
def extend_atom(self, feed):
|
:param feed: The feed xml root element.
|
||||||
'''Extend an ATOM feed xml structure containing all previously set
|
:returns: The feed root element.
|
||||||
fields.
|
'''
|
||||||
|
return feed
|
||||||
:param feed: The feed xml root element.
|
|
||||||
:returns: The feed root element.
|
|
||||||
'''
|
|
||||||
return feed
|
|
||||||
|
|
||||||
|
|
||||||
class BaseEntryExtension(BaseExtension):
|
class BaseEntryExtension(BaseExtension):
|
||||||
'''Basic FeedEntry extension.
|
'''Basic FeedEntry extension.
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -1,419 +1,406 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''
|
'''
|
||||||
feedgen.ext.dc
|
feedgen.ext.dc
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Extends the FeedGenerator to add Dubline Core Elements to the feeds.
|
Extends the FeedGenerator to add Dubline Core Elements to the feeds.
|
||||||
|
|
||||||
Descriptions partly taken from
|
Descriptions partly taken from
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-coverage
|
http://dublincore.org/documents/dcmi-terms/#elements-coverage
|
||||||
|
|
||||||
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
|
:copyright: 2013-2016, Lars Kiesow <lkiesow@uos.de>
|
||||||
|
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from feedgen.ext.base import BaseExtension, BaseEntryExtension
|
from feedgen.ext.base import BaseExtension
|
||||||
|
|
||||||
|
|
||||||
class DcBaseExtension(BaseExtension):
|
class DcBaseExtension(BaseExtension):
|
||||||
'''Dublin Core Elements extension for podcasts.
|
'''Dublin Core Elements extension for podcasts.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
def __init__(self):
|
# http://dublincore.org/documents/usageguide/elements.shtml
|
||||||
# http://dublincore.org/documents/usageguide/elements.shtml
|
# http://dublincore.org/documents/dces/
|
||||||
# http://dublincore.org/documents/dces/
|
# http://dublincore.org/documents/dcmi-terms/
|
||||||
# http://dublincore.org/documents/dcmi-terms/
|
self._dcelem_contributor = None
|
||||||
self._dcelem_contributor = None
|
self._dcelem_coverage = None
|
||||||
self._dcelem_coverage = None
|
self._dcelem_creator = None
|
||||||
self._dcelem_creator = None
|
self._dcelem_date = None
|
||||||
self._dcelem_date = None
|
self._dcelem_description = None
|
||||||
self._dcelem_description = None
|
self._dcelem_format = None
|
||||||
self._dcelem_format = None
|
self._dcelem_identifier = None
|
||||||
self._dcelem_identifier = None
|
self._dcelem_language = None
|
||||||
self._dcelem_language = None
|
self._dcelem_publisher = None
|
||||||
self._dcelem_publisher = None
|
self._dcelem_relation = None
|
||||||
self._dcelem_relation = None
|
self._dcelem_rights = None
|
||||||
self._dcelem_rights = None
|
self._dcelem_source = None
|
||||||
self._dcelem_source = None
|
self._dcelem_subject = None
|
||||||
self._dcelem_subject = None
|
self._dcelem_title = None
|
||||||
self._dcelem_title = None
|
self._dcelem_type = None
|
||||||
self._dcelem_type = None
|
|
||||||
|
def extend_ns(self):
|
||||||
def extend_ns(self):
|
return {'dc': 'http://purl.org/dc/elements/1.1/'}
|
||||||
return {'dc' : 'http://purl.org/dc/elements/1.1/'}
|
|
||||||
|
def _extend_xml(self, xml_elem):
|
||||||
def _extend_xml(self, xml_elem):
|
'''Extend xml_elem with set DC fields.
|
||||||
'''Extend xml_elem with set DC fields.
|
|
||||||
|
:param xml_elem: etree element
|
||||||
:param xml_elem: etree element
|
'''
|
||||||
'''
|
DCELEMENTS_NS = 'http://purl.org/dc/elements/1.1/'
|
||||||
DCELEMENTS_NS = 'http://purl.org/dc/elements/1.1/'
|
|
||||||
|
for elem in ['contributor', 'coverage', 'creator', 'date',
|
||||||
for elem in ['contributor', 'coverage', 'creator', 'date', 'description',
|
'description', 'language', 'publisher', 'relation',
|
||||||
'language', 'publisher', 'relation', 'rights', 'source', 'subject',
|
'rights', 'source', 'subject', 'title', 'type', 'format',
|
||||||
'title', 'type', 'format', 'identifier']:
|
'identifier']:
|
||||||
if hasattr(self, '_dcelem_%s' % elem):
|
if hasattr(self, '_dcelem_%s' % elem):
|
||||||
for val in getattr(self, '_dcelem_%s' % elem) or []:
|
for val in getattr(self, '_dcelem_%s' % elem) or []:
|
||||||
node = etree.SubElement(xml_elem, '{%s}%s' % (DCELEMENTS_NS, elem))
|
node = etree.SubElement(xml_elem,
|
||||||
node.text = val
|
'{%s}%s' % (DCELEMENTS_NS, elem))
|
||||||
|
node.text = val
|
||||||
|
|
||||||
def extend_atom(self, atom_feed):
|
def extend_atom(self, atom_feed):
|
||||||
'''Extend an Atom feed with the set DC fields.
|
'''Extend an Atom feed with the set DC fields.
|
||||||
|
|
||||||
:param atom_feed: The feed root element
|
:param atom_feed: The feed root element
|
||||||
:returns: The feed root element
|
:returns: The feed root element
|
||||||
'''
|
'''
|
||||||
|
|
||||||
self._extend_xml(atom_feed)
|
self._extend_xml(atom_feed)
|
||||||
|
|
||||||
return atom_feed
|
return atom_feed
|
||||||
|
|
||||||
|
def extend_rss(self, rss_feed):
|
||||||
|
'''Extend a RSS feed with the set DC fields.
|
||||||
def extend_rss(self, rss_feed):
|
|
||||||
'''Extend a RSS feed with the set DC fields.
|
:param rss_feed: The feed root element
|
||||||
|
:returns: The feed root element.
|
||||||
:param rss_feed: The feed root element
|
'''
|
||||||
:returns: The feed root element.
|
channel = rss_feed[0]
|
||||||
'''
|
self._extend_xml(channel)
|
||||||
channel = rss_feed[0]
|
|
||||||
self._extend_xml(channel)
|
return rss_feed
|
||||||
|
|
||||||
return rss_feed
|
def dc_contributor(self, contributor=None, replace=False):
|
||||||
|
'''Get or set the dc:contributor which is an entity responsible for
|
||||||
|
making contributions to the resource.
|
||||||
def dc_contributor(self, contributor=None, replace=False):
|
|
||||||
'''Get or set the dc:contributor which is an entity responsible for
|
For more information see:
|
||||||
making contributions to the resource.
|
http://dublincore.org/documents/dcmi-terms/#elements-contributor
|
||||||
|
|
||||||
For more information see:
|
:param contributor: Contributor or list of contributors.
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-contributor
|
:param replace: Replace alredy set contributors (deault: False).
|
||||||
|
:returns: List of contributors.
|
||||||
:param contributor: Contributor or list of contributors.
|
'''
|
||||||
:param replace: Replace alredy set contributors (deault: False).
|
if contributor is not None:
|
||||||
:returns: List of contributors.
|
if not isinstance(contributor, list):
|
||||||
'''
|
contributor = [contributor]
|
||||||
if not contributor is None:
|
if replace or not self._dcelem_contributor:
|
||||||
if not isinstance(contributor, list):
|
self._dcelem_contributor = []
|
||||||
contributor = [contributor]
|
self._dcelem_contributor += contributor
|
||||||
if replace or not self._dcelem_contributor:
|
return self._dcelem_contributor
|
||||||
self._dcelem_contributor = []
|
|
||||||
self._dcelem_contributor += contributor
|
def dc_coverage(self, coverage=None, replace=True):
|
||||||
return self._dcelem_contributor
|
'''Get or set the dc:coverage which indicated the spatial or temporal
|
||||||
|
topic of the resource, the spatial applicability of the resource, or
|
||||||
|
the jurisdiction under which the resource is relevant.
|
||||||
def dc_coverage(self, coverage=None, replace=True):
|
|
||||||
'''Get or set the dc:coverage which indicated the spatial or temporal
|
Spatial topic and spatial applicability may be a named place or a
|
||||||
topic of the resource, the spatial applicability of the resource, or the
|
location specified by its geographic coordinates. Temporal topic may be
|
||||||
jurisdiction under which the resource is relevant.
|
a named period, date, or date range. A jurisdiction may be a named
|
||||||
|
administrative entity or a geographic place to which the resource
|
||||||
Spatial topic and spatial applicability may be a named place or a
|
applies. Recommended best practice is to use a controlled vocabulary
|
||||||
location specified by its geographic coordinates. Temporal topic may be a
|
such as the Thesaurus of Geographic Names [TGN]. Where appropriate,
|
||||||
named period, date, or date range. A jurisdiction may be a named
|
named places or time periods can be used in preference to numeric
|
||||||
administrative entity or a geographic place to which the resource
|
identifiers such as sets of coordinates or date ranges.
|
||||||
applies. Recommended best practice is to use a controlled vocabulary such
|
|
||||||
as the Thesaurus of Geographic Names [TGN]. Where appropriate, named
|
References:
|
||||||
places or time periods can be used in preference to numeric identifiers
|
[TGN] http://www.getty.edu/research/tools/vocabulary/tgn/index.html
|
||||||
such as sets of coordinates or date ranges.
|
|
||||||
|
:param coverage: Coverage of the feed.
|
||||||
References: [TGN] http://www.getty.edu/research/tools/vocabulary/tgn/index.html
|
:param replace: Replace already set coverage (default: True).
|
||||||
|
:returns: Coverage of the feed.
|
||||||
:param coverage: Coverage of the feed.
|
'''
|
||||||
:param replace: Replace already set coverage (default: True).
|
if coverage is not None:
|
||||||
:returns: Coverage of the feed.
|
if not isinstance(coverage, list):
|
||||||
'''
|
coverage = [coverage]
|
||||||
if not coverage is None:
|
if replace or not self._dcelem_coverage:
|
||||||
if not isinstance(coverage, list):
|
self._dcelem_coverage = []
|
||||||
coverage = [coverage]
|
self._dcelem_coverage = coverage
|
||||||
if replace or not self._dcelem_coverage:
|
return self._dcelem_coverage
|
||||||
self._dcelem_coverage = []
|
|
||||||
self._dcelem_coverage = coverage
|
def dc_creator(self, creator=None, replace=False):
|
||||||
return self._dcelem_coverage
|
'''Get or set the dc:creator which is an entity primarily responsible
|
||||||
|
for making the resource.
|
||||||
|
|
||||||
def dc_creator(self, creator=None, replace=False):
|
For more information see:
|
||||||
'''Get or set the dc:creator which is an entity primarily responsible for
|
http://dublincore.org/documents/dcmi-terms/#elements-creator
|
||||||
making the resource.
|
|
||||||
|
:param creator: Creator or list of creators.
|
||||||
For more information see:
|
:param replace: Replace alredy set creators (deault: False).
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-creator
|
:returns: List of creators.
|
||||||
|
'''
|
||||||
:param creator: Creator or list of creators.
|
if creator is not None:
|
||||||
:param replace: Replace alredy set creators (deault: False).
|
if not isinstance(creator, list):
|
||||||
:returns: List of creators.
|
creator = [creator]
|
||||||
'''
|
if replace or not self._dcelem_creator:
|
||||||
if not creator is None:
|
self._dcelem_creator = []
|
||||||
if not isinstance(creator, list):
|
self._dcelem_creator += creator
|
||||||
creator = [creator]
|
return self._dcelem_creator
|
||||||
if replace or not self._dcelem_creator:
|
|
||||||
self._dcelem_creator = []
|
def dc_date(self, date=None, replace=True):
|
||||||
self._dcelem_creator += creator
|
'''Get or set the dc:date which describes a point or period of time
|
||||||
return self._dcelem_creator
|
associated with an event in the lifecycle of the resource.
|
||||||
|
|
||||||
|
For more information see:
|
||||||
def dc_date(self, date=None, replace=True):
|
http://dublincore.org/documents/dcmi-terms/#elements-date
|
||||||
'''Get or set the dc:date which describes a point or period of time
|
|
||||||
associated with an event in the lifecycle of the resource.
|
:param date: Date or list of dates.
|
||||||
|
:param replace: Replace alredy set dates (deault: True).
|
||||||
For more information see:
|
:returns: List of dates.
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-date
|
'''
|
||||||
|
if date is not None:
|
||||||
:param date: Date or list of dates.
|
if not isinstance(date, list):
|
||||||
:param replace: Replace alredy set dates (deault: True).
|
date = [date]
|
||||||
:returns: List of dates.
|
if replace or not self._dcelem_date:
|
||||||
'''
|
self._dcelem_date = []
|
||||||
if not date is None:
|
self._dcelem_date += date
|
||||||
if not isinstance(date, list):
|
return self._dcelem_date
|
||||||
date = [date]
|
|
||||||
if replace or not self._dcelem_date:
|
def dc_description(self, description=None, replace=True):
|
||||||
self._dcelem_date = []
|
'''Get or set the dc:description which is an account of the resource.
|
||||||
self._dcelem_date += date
|
|
||||||
return self._dcelem_date
|
For more information see:
|
||||||
|
http://dublincore.org/documents/dcmi-terms/#elements-description
|
||||||
|
|
||||||
def dc_description(self, description=None, replace=True):
|
:param description: Description or list of descriptions.
|
||||||
'''Get or set the dc:description which is an account of the resource.
|
:param replace: Replace alredy set descriptions (deault: True).
|
||||||
|
:returns: List of descriptions.
|
||||||
For more information see:
|
'''
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-description
|
if description is not None:
|
||||||
|
if not isinstance(description, list):
|
||||||
:param description: Description or list of descriptions.
|
description = [description]
|
||||||
:param replace: Replace alredy set descriptions (deault: True).
|
if replace or not self._dcelem_description:
|
||||||
:returns: List of descriptions.
|
self._dcelem_description = []
|
||||||
'''
|
self._dcelem_description += description
|
||||||
if not description is None:
|
return self._dcelem_description
|
||||||
if not isinstance(description, list):
|
|
||||||
description = [description]
|
def dc_format(self, format=None, replace=True):
|
||||||
if replace or not self._dcelem_description:
|
'''Get or set the dc:format which describes the file format, physical
|
||||||
self._dcelem_description = []
|
medium, or dimensions of the resource.
|
||||||
self._dcelem_description += description
|
|
||||||
return self._dcelem_description
|
For more information see:
|
||||||
|
http://dublincore.org/documents/dcmi-terms/#elements-format
|
||||||
|
|
||||||
def dc_format(self, format=None, replace=True):
|
:param format: Format of the resource or list of formats.
|
||||||
'''Get or set the dc:format which describes the file format, physical
|
:param replace: Replace alredy set format (deault: True).
|
||||||
medium, or dimensions of the resource.
|
:returns: Format of the resource.
|
||||||
|
'''
|
||||||
For more information see:
|
if format is not None:
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-format
|
if not isinstance(format, list):
|
||||||
|
format = [format]
|
||||||
:param format: Format of the resource or list of formats.
|
if replace or not self._dcelem_format:
|
||||||
:param replace: Replace alredy set format (deault: True).
|
self._dcelem_format = []
|
||||||
:returns: Format of the resource.
|
self._dcelem_format += format
|
||||||
'''
|
return self._dcelem_format
|
||||||
if not format is None:
|
|
||||||
if not isinstance(format, list):
|
def dc_identifier(self, identifier=None, replace=True):
|
||||||
format = [format]
|
'''Get or set the dc:identifier which should be an unambiguous
|
||||||
if replace or not self._dcelem_format:
|
reference to the resource within a given context.
|
||||||
self._dcelem_format = []
|
|
||||||
self._dcelem_format += format
|
For more inidentifierion see:
|
||||||
return self._dcelem_format
|
http://dublincore.org/documents/dcmi-terms/#elements-identifier
|
||||||
|
|
||||||
|
:param identifier: Identifier of the resource or list of identifiers.
|
||||||
def dc_identifier(self, identifier=None, replace=True):
|
:param replace: Replace alredy set identifier (deault: True).
|
||||||
'''Get or set the dc:identifier which should be an unambiguous reference
|
:returns: Identifiers of the resource.
|
||||||
to the resource within a given context.
|
'''
|
||||||
|
if identifier is not None:
|
||||||
For more inidentifierion see:
|
if not isinstance(identifier, list):
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-identifier
|
identifier = [identifier]
|
||||||
|
if replace or not self._dcelem_identifier:
|
||||||
:param identifier: Identifier of the resource or list of identifiers.
|
self._dcelem_identifier = []
|
||||||
:param replace: Replace alredy set identifier (deault: True).
|
self._dcelem_identifier += identifier
|
||||||
:returns: Identifiers of the resource.
|
|
||||||
'''
|
def dc_language(self, language=None, replace=True):
|
||||||
if not identifier is None:
|
'''Get or set the dc:language which describes a language of the
|
||||||
if not isinstance(identifier, list):
|
resource.
|
||||||
identifier = [identifier]
|
|
||||||
if replace or not self._dcelem_identifier:
|
For more information see:
|
||||||
self._dcelem_identifier = []
|
http://dublincore.org/documents/dcmi-terms/#elements-language
|
||||||
self._dcelem_identifier += identifier
|
|
||||||
|
:param language: Language or list of languages.
|
||||||
|
:param replace: Replace alredy set languages (deault: True).
|
||||||
def dc_language(self, language=None, replace=True):
|
:returns: List of languages.
|
||||||
'''Get or set the dc:language which describes a language of the resource.
|
'''
|
||||||
|
if language is not None:
|
||||||
For more information see:
|
if not isinstance(language, list):
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-language
|
language = [language]
|
||||||
|
if replace or not self._dcelem_language:
|
||||||
:param language: Language or list of languages.
|
self._dcelem_language = []
|
||||||
:param replace: Replace alredy set languages (deault: True).
|
self._dcelem_language += language
|
||||||
:returns: List of languages.
|
return self._dcelem_language
|
||||||
'''
|
|
||||||
if not language is None:
|
def dc_publisher(self, publisher=None, replace=False):
|
||||||
if not isinstance(language, list):
|
'''Get or set the dc:publisher which is an entity responsible for
|
||||||
language = [language]
|
making the resource available.
|
||||||
if replace or not self._dcelem_language:
|
|
||||||
self._dcelem_language = []
|
For more information see:
|
||||||
self._dcelem_language += language
|
http://dublincore.org/documents/dcmi-terms/#elements-publisher
|
||||||
return self._dcelem_language
|
|
||||||
|
:param publisher: Publisher or list of publishers.
|
||||||
|
:param replace: Replace alredy set publishers (deault: False).
|
||||||
def dc_publisher(self, publisher=None, replace=False):
|
:returns: List of publishers.
|
||||||
'''Get or set the dc:publisher which is an entity responsible for making
|
'''
|
||||||
the resource available.
|
if publisher is not None:
|
||||||
|
if not isinstance(publisher, list):
|
||||||
For more information see:
|
publisher = [publisher]
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-publisher
|
if replace or not self._dcelem_publisher:
|
||||||
|
self._dcelem_publisher = []
|
||||||
:param publisher: Publisher or list of publishers.
|
self._dcelem_publisher += publisher
|
||||||
:param replace: Replace alredy set publishers (deault: False).
|
return self._dcelem_publisher
|
||||||
:returns: List of publishers.
|
|
||||||
'''
|
def dc_relation(self, relation=None, replace=False):
|
||||||
if not publisher is None:
|
'''Get or set the dc:relation which describes a related ressource.
|
||||||
if not isinstance(publisher, list):
|
|
||||||
publisher = [publisher]
|
For more information see:
|
||||||
if replace or not self._dcelem_publisher:
|
http://dublincore.org/documents/dcmi-terms/#elements-relation
|
||||||
self._dcelem_publisher = []
|
|
||||||
self._dcelem_publisher += publisher
|
:param relation: Relation or list of relations.
|
||||||
return self._dcelem_publisher
|
:param replace: Replace alredy set relations (deault: False).
|
||||||
|
:returns: List of relations.
|
||||||
|
'''
|
||||||
def dc_relation(self, relation=None, replace=False):
|
if relation is not None:
|
||||||
'''Get or set the dc:relation which describes a related ressource.
|
if not isinstance(relation, list):
|
||||||
|
relation = [relation]
|
||||||
For more information see:
|
if replace or not self._dcelem_relation:
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-relation
|
self._dcelem_relation = []
|
||||||
|
self._dcelem_relation += relation
|
||||||
:param relation: Relation or list of relations.
|
return self._dcelem_relation
|
||||||
:param replace: Replace alredy set relations (deault: False).
|
|
||||||
:returns: List of relations.
|
def dc_rights(self, rights=None, replace=False):
|
||||||
'''
|
'''Get or set the dc:rights which may contain information about rights
|
||||||
if not relation is None:
|
held in and over the resource.
|
||||||
if not isinstance(relation, list):
|
|
||||||
relation = [relation]
|
For more information see:
|
||||||
if replace or not self._dcelem_relation:
|
http://dublincore.org/documents/dcmi-terms/#elements-rights
|
||||||
self._dcelem_relation = []
|
|
||||||
self._dcelem_relation += relation
|
:param rights: Rights information or list of rights information.
|
||||||
return self._dcelem_relation
|
:param replace: Replace alredy set rightss (deault: False).
|
||||||
|
:returns: List of rights information.
|
||||||
|
'''
|
||||||
def dc_rights(self, rights=None, replace=False):
|
if rights is not None:
|
||||||
'''Get or set the dc:rights which may contain information about rights
|
if not isinstance(rights, list):
|
||||||
held in and over the resource.
|
rights = [rights]
|
||||||
|
if replace or not self._dcelem_rights:
|
||||||
For more information see:
|
self._dcelem_rights = []
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-rights
|
self._dcelem_rights += rights
|
||||||
|
return self._dcelem_rights
|
||||||
:param rights: Rights information or list of rights information.
|
|
||||||
:param replace: Replace alredy set rightss (deault: False).
|
def dc_source(self, source=None, replace=False):
|
||||||
:returns: List of rights information.
|
'''Get or set the dc:source which is a related resource from which the
|
||||||
'''
|
described resource is derived.
|
||||||
if not rights is None:
|
|
||||||
if not isinstance(rights, list):
|
The described resource may be derived from the related resource in
|
||||||
rights = [rights]
|
whole or in part. Recommended best practice is to identify the related
|
||||||
if replace or not self._dcelem_rights:
|
resource by means of a string conforming to a formal identification
|
||||||
self._dcelem_rights = []
|
system.
|
||||||
self._dcelem_rights += rights
|
|
||||||
return self._dcelem_rights
|
For more information see:
|
||||||
|
http://dublincore.org/documents/dcmi-terms/#elements-source
|
||||||
|
|
||||||
def dc_source(self, source=None, replace=False):
|
:param source: Source or list of sources.
|
||||||
'''Get or set the dc:source which is a related resource from which the
|
:param replace: Replace alredy set sources (deault: False).
|
||||||
described resource is derived.
|
:returns: List of sources.
|
||||||
|
'''
|
||||||
The described resource may be derived from the related resource in whole
|
if source is not None:
|
||||||
or in part. Recommended best practice is to identify the related resource
|
if not isinstance(source, list):
|
||||||
by means of a string conforming to a formal identification system.
|
source = [source]
|
||||||
|
if replace or not self._dcelem_source:
|
||||||
|
self._dcelem_source = []
|
||||||
For more information see:
|
self._dcelem_source += source
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-source
|
return self._dcelem_source
|
||||||
|
|
||||||
:param source: Source or list of sources.
|
def dc_subject(self, subject=None, replace=False):
|
||||||
:param replace: Replace alredy set sources (deault: False).
|
'''Get or set the dc:subject which describes the topic of the resource.
|
||||||
:returns: List of sources.
|
|
||||||
'''
|
For more information see:
|
||||||
if not source is None:
|
http://dublincore.org/documents/dcmi-terms/#elements-subject
|
||||||
if not isinstance(source, list):
|
|
||||||
source = [source]
|
:param subject: Subject or list of subjects.
|
||||||
if replace or not self._dcelem_source:
|
:param replace: Replace alredy set subjects (deault: False).
|
||||||
self._dcelem_source = []
|
:returns: List of subjects.
|
||||||
self._dcelem_source += source
|
'''
|
||||||
return self._dcelem_source
|
if subject is not None:
|
||||||
|
if not isinstance(subject, list):
|
||||||
|
subject = [subject]
|
||||||
def dc_subject(self, subject=None, replace=False):
|
if replace or not self._dcelem_subject:
|
||||||
'''Get or set the dc:subject which describes the topic of the resource.
|
self._dcelem_subject = []
|
||||||
|
self._dcelem_subject += subject
|
||||||
For more information see:
|
return self._dcelem_subject
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-subject
|
|
||||||
|
def dc_title(self, title=None, replace=True):
|
||||||
:param subject: Subject or list of subjects.
|
'''Get or set the dc:title which is a name given to the resource.
|
||||||
:param replace: Replace alredy set subjects (deault: False).
|
|
||||||
:returns: List of subjects.
|
For more information see:
|
||||||
'''
|
http://dublincore.org/documents/dcmi-terms/#elements-title
|
||||||
if not subject is None:
|
|
||||||
if not isinstance(subject, list):
|
:param title: Title or list of titles.
|
||||||
subject = [subject]
|
:param replace: Replace alredy set titles (deault: False).
|
||||||
if replace or not self._dcelem_subject:
|
:returns: List of titles.
|
||||||
self._dcelem_subject = []
|
'''
|
||||||
self._dcelem_subject += subject
|
if title is not None:
|
||||||
return self._dcelem_subject
|
if not isinstance(title, list):
|
||||||
|
title = [title]
|
||||||
|
if replace or not self._dcelem_title:
|
||||||
def dc_title(self, title=None, replace=True):
|
self._dcelem_title = []
|
||||||
'''Get or set the dc:title which is a name given to the resource.
|
self._dcelem_title += title
|
||||||
|
return self._dcelem_title
|
||||||
For more information see:
|
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-title
|
def dc_type(self, type=None, replace=False):
|
||||||
|
'''Get or set the dc:type which describes the nature or genre of the
|
||||||
:param title: Title or list of titles.
|
resource.
|
||||||
:param replace: Replace alredy set titles (deault: False).
|
|
||||||
:returns: List of titles.
|
For more information see:
|
||||||
'''
|
http://dublincore.org/documents/dcmi-terms/#elements-type
|
||||||
if not title is None:
|
|
||||||
if not isinstance(title, list):
|
:param type: Type or list of types.
|
||||||
title = [title]
|
:param replace: Replace alredy set types (deault: False).
|
||||||
if replace or not self._dcelem_title:
|
:returns: List of types.
|
||||||
self._dcelem_title = []
|
'''
|
||||||
self._dcelem_title += title
|
if type is not None:
|
||||||
return self._dcelem_title
|
if not isinstance(type, list):
|
||||||
|
type = [type]
|
||||||
|
if replace or not self._dcelem_type:
|
||||||
def dc_type(self, type=None, replace=False):
|
self._dcelem_type = []
|
||||||
'''Get or set the dc:type which describes the nature or genre of the
|
self._dcelem_type += type
|
||||||
resource.
|
return self._dcelem_type
|
||||||
|
|
||||||
For more information see:
|
|
||||||
http://dublincore.org/documents/dcmi-terms/#elements-type
|
|
||||||
|
|
||||||
:param type: Type or list of types.
|
|
||||||
:param replace: Replace alredy set types (deault: False).
|
|
||||||
:returns: List of types.
|
|
||||||
'''
|
|
||||||
if not type is None:
|
|
||||||
if not isinstance(type, list):
|
|
||||||
type = [type]
|
|
||||||
if replace or not self._dcelem_type:
|
|
||||||
self._dcelem_type = []
|
|
||||||
self._dcelem_type += type
|
|
||||||
return self._dcelem_type
|
|
||||||
|
|
||||||
class DcExtension(DcBaseExtension):
|
class DcExtension(DcBaseExtension):
|
||||||
'''Dublin Core Elements extension for podcasts.
|
'''Dublin Core Elements extension for podcasts.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
class DcEntryExtension(DcBaseExtension):
|
class DcEntryExtension(DcBaseExtension):
|
||||||
'''Dublin Core Elements extension for podcasts.
|
'''Dublin Core Elements extension for podcasts.
|
||||||
'''
|
'''
|
||||||
def extend_atom(self, entry):
|
def extend_atom(self, entry):
|
||||||
'''Add dc elements to an atom item. Alters the item itself.
|
'''Add dc elements to an atom item. Alters the item itself.
|
||||||
|
|
||||||
:param entry: An atom entry element.
|
:param entry: An atom entry element.
|
||||||
:returns: The entry element.
|
:returns: The entry element.
|
||||||
'''
|
'''
|
||||||
self._extend_xml(entry)
|
self._extend_xml(entry)
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
def extend_rss(self, item):
|
def extend_rss(self, item):
|
||||||
'''Add dc elements to a RSS item. Alters the item itself.
|
'''Add dc elements to a RSS item. Alters the item itself.
|
||||||
|
|
||||||
:param item: A RSS item element.
|
:param item: A RSS item element.
|
||||||
:returns: The item element.
|
:returns: The item element.
|
||||||
'''
|
'''
|
||||||
self._extend_xml(item)
|
self._extend_xml(item)
|
||||||
return item
|
return item
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''
|
'''
|
||||||
feedgen.ext.podcast
|
feedgen.ext.podcast
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Extends the FeedGenerator to produce podcasts.
|
Extends the FeedGenerator to produce podcasts.
|
||||||
|
|
||||||
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
|
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
|
||||||
|
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
@ -17,343 +17,343 @@ from feedgen.compat import string_types
|
||||||
|
|
||||||
|
|
||||||
class PodcastExtension(BaseExtension):
|
class PodcastExtension(BaseExtension):
|
||||||
'''FeedGenerator extension for podcasts.
|
'''FeedGenerator extension for podcasts.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
def __init__(self):
|
# ITunes tags
|
||||||
## ITunes tags
|
# http://www.apple.com/itunes/podcasts/specs.html#rss
|
||||||
# http://www.apple.com/itunes/podcasts/specs.html#rss
|
self.__itunes_author = None
|
||||||
self.__itunes_author = None
|
self.__itunes_block = None
|
||||||
self.__itunes_block = None
|
self.__itunes_category = None
|
||||||
self.__itunes_category = None
|
self.__itunes_image = None
|
||||||
self.__itunes_image = None
|
self.__itunes_explicit = None
|
||||||
self.__itunes_explicit = None
|
self.__itunes_complete = None
|
||||||
self.__itunes_complete = None
|
self.__itunes_new_feed_url = None
|
||||||
self.__itunes_new_feed_url = None
|
self.__itunes_owner = None
|
||||||
self.__itunes_owner = None
|
self.__itunes_subtitle = None
|
||||||
self.__itunes_subtitle = None
|
self.__itunes_summary = None
|
||||||
self.__itunes_summary = None
|
|
||||||
|
def extend_ns(self):
|
||||||
|
return {'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd'}
|
||||||
def extend_ns(self):
|
|
||||||
return {'itunes' : 'http://www.itunes.com/dtds/podcast-1.0.dtd'}
|
def extend_rss(self, rss_feed):
|
||||||
|
'''Extend an RSS feed root with set itunes fields.
|
||||||
|
|
||||||
def extend_rss(self, rss_feed):
|
:returns: The feed root element.
|
||||||
'''Extend an RSS feed root with set itunes fields.
|
'''
|
||||||
|
ITUNES_NS = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
|
||||||
:returns: The feed root element.
|
channel = rss_feed[0]
|
||||||
'''
|
|
||||||
ITUNES_NS = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
|
if self.__itunes_author:
|
||||||
channel = rss_feed[0]
|
author = etree.SubElement(channel, '{%s}author' % ITUNES_NS)
|
||||||
|
author.text = self.__itunes_author
|
||||||
if self.__itunes_author:
|
|
||||||
author = etree.SubElement(channel, '{%s}author' % ITUNES_NS)
|
if self.__itunes_block is not None:
|
||||||
author.text = self.__itunes_author
|
block = etree.SubElement(channel, '{%s}block' % ITUNES_NS)
|
||||||
|
block.text = 'yes' if self.__itunes_block else 'no'
|
||||||
if not self.__itunes_block is None:
|
|
||||||
block = etree.SubElement(channel, '{%s}block' % ITUNES_NS)
|
for c in self.__itunes_category or []:
|
||||||
block.text = 'yes' if self.__itunes_block else 'no'
|
if not c.get('cat'):
|
||||||
|
continue
|
||||||
for c in self.__itunes_category or []:
|
category = channel.find(
|
||||||
if not c.get('cat'):
|
'{%s}category[@text="%s"]' % (ITUNES_NS, c.get('cat')))
|
||||||
continue
|
if category is None:
|
||||||
category = channel.find('{%s}category[@text="%s"]' % (ITUNES_NS,c.get('cat')))
|
category = etree.SubElement(channel,
|
||||||
if category == None:
|
'{%s}category' % ITUNES_NS)
|
||||||
category = etree.SubElement(channel, '{%s}category' % ITUNES_NS)
|
category.attrib['text'] = c.get('cat')
|
||||||
category.attrib['text'] = c.get('cat')
|
|
||||||
|
if c.get('sub'):
|
||||||
if c.get('sub'):
|
subcategory = etree.SubElement(category,
|
||||||
subcategory = etree.SubElement(category, '{%s}category' % ITUNES_NS)
|
'{%s}category' % ITUNES_NS)
|
||||||
subcategory.attrib['text'] = c.get('sub')
|
subcategory.attrib['text'] = c.get('sub')
|
||||||
|
|
||||||
if self.__itunes_image:
|
if self.__itunes_image:
|
||||||
image = etree.SubElement(channel, '{%s}image' % ITUNES_NS)
|
image = etree.SubElement(channel, '{%s}image' % ITUNES_NS)
|
||||||
image.attrib['href'] = self.__itunes_image
|
image.attrib['href'] = self.__itunes_image
|
||||||
|
|
||||||
if self.__itunes_explicit in ('yes', 'no', 'clean'):
|
if self.__itunes_explicit in ('yes', 'no', 'clean'):
|
||||||
explicit = etree.SubElement(channel, '{%s}explicit' % ITUNES_NS)
|
explicit = etree.SubElement(channel, '{%s}explicit' % ITUNES_NS)
|
||||||
explicit.text = self.__itunes_explicit
|
explicit.text = self.__itunes_explicit
|
||||||
|
|
||||||
if self.__itunes_complete in ('yes', 'no'):
|
if self.__itunes_complete in ('yes', 'no'):
|
||||||
complete = etree.SubElement(channel, '{%s}complete' % ITUNES_NS)
|
complete = etree.SubElement(channel, '{%s}complete' % ITUNES_NS)
|
||||||
complete.text = self.__itunes_complete
|
complete.text = self.__itunes_complete
|
||||||
|
|
||||||
if self.__itunes_new_feed_url:
|
if self.__itunes_new_feed_url:
|
||||||
new_feed_url = etree.SubElement(channel, '{%s}new-feed-url' % ITUNES_NS)
|
new_feed_url = etree.SubElement(channel,
|
||||||
new_feed_url.text = self.__itunes_new_feed_url
|
'{%s}new-feed-url' % ITUNES_NS)
|
||||||
|
new_feed_url.text = self.__itunes_new_feed_url
|
||||||
if self.__itunes_owner:
|
|
||||||
owner = etree.SubElement(channel, '{%s}owner' % ITUNES_NS)
|
if self.__itunes_owner:
|
||||||
owner_name = etree.SubElement(owner, '{%s}name' % ITUNES_NS)
|
owner = etree.SubElement(channel, '{%s}owner' % ITUNES_NS)
|
||||||
owner_name.text = self.__itunes_owner.get('name')
|
owner_name = etree.SubElement(owner, '{%s}name' % ITUNES_NS)
|
||||||
owner_email = etree.SubElement(owner, '{%s}email' % ITUNES_NS)
|
owner_name.text = self.__itunes_owner.get('name')
|
||||||
owner_email.text = self.__itunes_owner.get('email')
|
owner_email = etree.SubElement(owner, '{%s}email' % ITUNES_NS)
|
||||||
|
owner_email.text = self.__itunes_owner.get('email')
|
||||||
if self.__itunes_subtitle:
|
|
||||||
subtitle = etree.SubElement(channel, '{%s}subtitle' % ITUNES_NS)
|
if self.__itunes_subtitle:
|
||||||
subtitle.text = self.__itunes_subtitle
|
subtitle = etree.SubElement(channel, '{%s}subtitle' % ITUNES_NS)
|
||||||
|
subtitle.text = self.__itunes_subtitle
|
||||||
if self.__itunes_summary:
|
|
||||||
summary = etree.SubElement(channel, '{%s}summary' % ITUNES_NS)
|
if self.__itunes_summary:
|
||||||
summary.text = self.__itunes_summary
|
summary = etree.SubElement(channel, '{%s}summary' % ITUNES_NS)
|
||||||
|
summary.text = self.__itunes_summary
|
||||||
return rss_feed
|
|
||||||
|
return rss_feed
|
||||||
|
|
||||||
def itunes_author(self, itunes_author=None):
|
def itunes_author(self, itunes_author=None):
|
||||||
'''Get or set the itunes:author. The content of this tag is shown in the
|
'''Get or set the itunes:author. The content of this tag is shown in
|
||||||
Artist column in iTunes. If the tag is not present, iTunes uses the
|
the Artist column in iTunes. If the tag is not present, iTunes uses the
|
||||||
contents of the <author> tag. If <itunes:author> is not present at the
|
contents of the <author> tag. If <itunes:author> is not present at the
|
||||||
feed level, iTunes will use the contents of <managingEditor>.
|
feed level, iTunes will use the contents of <managingEditor>.
|
||||||
|
|
||||||
:param itunes_author: The author of the podcast.
|
:param itunes_author: The author of the podcast.
|
||||||
:returns: The author of the podcast.
|
:returns: The author of the podcast.
|
||||||
'''
|
'''
|
||||||
if not itunes_author is None:
|
if itunes_author is not None:
|
||||||
self.__itunes_author = itunes_author
|
self.__itunes_author = itunes_author
|
||||||
return self.__itunes_author
|
return self.__itunes_author
|
||||||
|
|
||||||
|
def itunes_block(self, itunes_block=None):
|
||||||
def itunes_block(self, itunes_block=None):
|
'''Get or set the ITunes block attribute. Use this to prevent the
|
||||||
'''Get or set the ITunes block attribute. Use this to prevent the entire
|
entire podcast from appearing in the iTunes podcast directory.
|
||||||
podcast from appearing in the iTunes podcast directory.
|
|
||||||
|
:param itunes_block: Block the podcast.
|
||||||
:param itunes_block: Block the podcast.
|
:returns: If the podcast is blocked.
|
||||||
:returns: If the podcast is blocked.
|
'''
|
||||||
'''
|
if itunes_block is not None:
|
||||||
if not itunes_block is None:
|
self.__itunes_block = itunes_block
|
||||||
self.__itunes_block = itunes_block
|
return self.__itunes_block
|
||||||
return self.__itunes_block
|
|
||||||
|
def itunes_category(self, itunes_category=None, replace=False, **kwargs):
|
||||||
def itunes_category(self, itunes_category=None, replace=False, **kwargs):
|
'''Get or set the ITunes category which appears in the category column
|
||||||
'''Get or set the ITunes category which appears in the category column
|
and in iTunes Store Browser.
|
||||||
and in iTunes Store Browser.
|
|
||||||
|
The (sub-)category has to be one from the values defined at
|
||||||
The (sub-)category has to be one from the values defined at
|
http://www.apple.com/itunes/podcasts/specs.html#categories
|
||||||
http://www.apple.com/itunes/podcasts/specs.html#categories
|
|
||||||
|
This method can be called with:
|
||||||
This method can be called with:
|
|
||||||
|
- the fields of an itunes_category as keyword arguments
|
||||||
- the fields of an itunes_category as keyword arguments
|
- the fields of an itunes_category as a dictionary
|
||||||
- the fields of an itunes_category as a dictionary
|
- a list of dictionaries containing the itunes_category fields
|
||||||
- a list of dictionaries containing the itunes_category fields
|
|
||||||
|
An itunes_category has the following fields:
|
||||||
An itunes_category has the following fields:
|
|
||||||
|
- *cat* name for a category.
|
||||||
- *cat* name for a category.
|
- *sub* name for a subcategory, child of category
|
||||||
- *sub* name for a subcategory, child of category
|
|
||||||
|
If a podcast has more than one subcategory from the same category, the
|
||||||
If a podcast has more than one subcategory from the same category, the
|
category is called more than once.
|
||||||
category is called more than once.
|
|
||||||
|
Likei the parameter::
|
||||||
Likei the parameter::
|
|
||||||
|
[{"cat":"Arts","sub":"Design"},{"cat":"Arts","sub":"Food"}]
|
||||||
[{"cat":"Arts","sub":"Design"},{"cat":"Arts","sub":"Food"}]
|
|
||||||
|
…would become::
|
||||||
…would become::
|
|
||||||
|
<itunes:category text="Arts">
|
||||||
<itunes:category text="Arts">
|
<itunes:category text="Design"/>
|
||||||
<itunes:category text="Design"/>
|
<itunes:category text="Food"/>
|
||||||
<itunes:category text="Food"/>
|
</itunes:category>
|
||||||
</itunes:category>
|
|
||||||
|
|
||||||
|
:param itunes_category: Dictionary or list of dictionaries with
|
||||||
:param itunes_category: Dictionary or list of dictionaries with
|
itunes_category data.
|
||||||
itunes_category data.
|
:param replace: Add or replace old data.
|
||||||
:param replace: Add or replace old data.
|
:returns: List of itunes_categories as dictionaries.
|
||||||
:returns: List of itunes_categories as dictionaries.
|
|
||||||
|
---
|
||||||
---
|
|
||||||
|
**Important note about deprecated parameter syntax:** Old version of
|
||||||
**Important note about deprecated parameter syntax:** Old version of the
|
the feedgen did only support one category plus one subcategory which
|
||||||
feedgen did only support one category plus one subcategory which would be
|
would be passed to this ducntion as first two parameters. For
|
||||||
passed to this ducntion as first two parameters. For compatibility
|
compatibility reasons, this still works but should not be used any may
|
||||||
reasons, this still works but should not be used any may be removed at
|
be removed at any time.
|
||||||
any time.
|
'''
|
||||||
'''
|
# Ensure old API still works for now. Note that the API is deprecated
|
||||||
# Ensure old API still works for now. Note that the API is deprecated and
|
# and this fallback may be removed at any time.
|
||||||
# this fallback may be removed at any time.
|
if isinstance(itunes_category, string_types):
|
||||||
if isinstance(itunes_category, string_types):
|
itunes_category = {'cat': itunes_category}
|
||||||
itunes_category = {'cat':itunes_category}
|
if replace:
|
||||||
if replace:
|
itunes_category['sub'] = replace
|
||||||
itunes_category['sub'] = replace
|
replace = True
|
||||||
replace=True
|
if itunes_category is None and kwargs:
|
||||||
if itunes_category is None and kwargs:
|
itunes_category = kwargs
|
||||||
itunes_category = kwargs
|
if itunes_category is not None:
|
||||||
if not itunes_category is None:
|
if replace or self.__itunes_category is None:
|
||||||
if replace or self.__itunes_category is None:
|
self.__itunes_category = []
|
||||||
self.__itunes_category = []
|
self.__itunes_category += ensure_format(itunes_category,
|
||||||
self.__itunes_category += ensure_format( itunes_category,
|
set(['cat', 'sub']),
|
||||||
set(['cat', 'sub']), set(['cat']))
|
set(['cat']))
|
||||||
return self.__itunes_category
|
return self.__itunes_category
|
||||||
|
|
||||||
|
def itunes_image(self, itunes_image=None):
|
||||||
def itunes_image(self, itunes_image=None):
|
'''Get or set the image for the podcast. This tag specifies the artwork
|
||||||
'''Get or set the image for the podcast. This tag specifies the artwork
|
for your podcast. Put the URL to the image in the href attribute.
|
||||||
for your podcast. Put the URL to the image in the href attribute. iTunes
|
iTunes prefers square .jpg images that are at least 1400x1400 pixels,
|
||||||
prefers square .jpg images that are at least 1400x1400 pixels, which is
|
which is different from what is specified for the standard RSS image
|
||||||
different from what is specified for the standard RSS image tag. In order
|
tag. In order for a podcast to be eligible for an iTunes Store feature,
|
||||||
for a podcast to be eligible for an iTunes Store feature, the
|
the accompanying image must be at least 1400x1400 pixels.
|
||||||
accompanying image must be at least 1400x1400 pixels.
|
|
||||||
|
iTunes supports images in JPEG and PNG formats with an RGB color space
|
||||||
iTunes supports images in JPEG and PNG formats with an RGB color space
|
(CMYK is not supported). The URL must end in ".jpg" or ".png". If the
|
||||||
(CMYK is not supported). The URL must end in ".jpg" or ".png". If the
|
<itunes:image> tag is not present, iTunes will use the contents of the
|
||||||
<itunes:image> tag is not present, iTunes will use the contents of the
|
RSS image tag.
|
||||||
RSS image tag.
|
|
||||||
|
If you change your podcast’s image, also change the file’s name. iTunes
|
||||||
If you change your podcast’s image, also change the file’s name. iTunes
|
may not change the image if it checks your feed and the image URL is
|
||||||
may not change the image if it checks your feed and the image URL is the
|
the same. The server hosting your cover art image must allow HTTP head
|
||||||
same. The server hosting your cover art image must allow HTTP head
|
requests for iTS to be able to automatically update your cover art.
|
||||||
requests for iTS to be able to automatically update your cover art.
|
|
||||||
|
:param itunes_image: Image of the podcast.
|
||||||
:param itunes_image: Image of the podcast.
|
:returns: Image of the podcast.
|
||||||
:returns: Image of the podcast.
|
'''
|
||||||
'''
|
if itunes_image is not None:
|
||||||
if not itunes_image is None:
|
if itunes_image.endswith('.jpg') or itunes_image.endswith('.png'):
|
||||||
if not ( itunes_image.endswith('.jpg') or itunes_image.endswith('.png') ):
|
self.__itunes_image = itunes_image
|
||||||
ValueError('Image file must be png or jpg')
|
else:
|
||||||
self.__itunes_image = itunes_image
|
ValueError('Image file must be png or jpg')
|
||||||
return self.__itunes_image
|
return self.__itunes_image
|
||||||
|
|
||||||
|
def itunes_explicit(self, itunes_explicit=None):
|
||||||
def itunes_explicit(self, itunes_explicit=None):
|
'''Get or the the itunes:explicit value of the podcast. This tag should
|
||||||
'''Get or the the itunes:explicit value of the podcast. This tag should
|
be used to indicate whether your podcast contains explicit material.
|
||||||
be used to indicate whether your podcast contains explicit material. The
|
The three values for this tag are "yes", "no", and "clean".
|
||||||
three values for this tag are "yes", "no", and "clean".
|
|
||||||
|
If you populate this tag with "yes", an "explicit" parental advisory
|
||||||
If you populate this tag with "yes", an "explicit" parental advisory
|
graphic will appear next to your podcast artwork on the iTunes Store
|
||||||
graphic will appear next to your podcast artwork on the iTunes Store and
|
and in the Name column in iTunes. If the value is "clean", the parental
|
||||||
in the Name column in iTunes. If the value is "clean", the parental
|
advisory type is considered Clean, meaning that no explicit language or
|
||||||
advisory type is considered Clean, meaning that no explicit language or
|
adult content is included anywhere in the episodes, and a "clean"
|
||||||
adult content is included anywhere in the episodes, and a "clean" graphic
|
graphic will appear. If the explicit tag is present and has any other
|
||||||
will appear. If the explicit tag is present and has any other value
|
value (e.g., "no"), you see no indicator — blank is the default
|
||||||
(e.g., "no"), you see no indicator — blank is the default advisory type.
|
advisory type.
|
||||||
|
|
||||||
:param itunes_explicit: If the podcast contains explicit material.
|
:param itunes_explicit: If the podcast contains explicit material.
|
||||||
:returns: If the podcast contains explicit material.
|
:returns: If the podcast contains explicit material.
|
||||||
'''
|
'''
|
||||||
if not itunes_explicit is None:
|
if itunes_explicit is not None:
|
||||||
if not itunes_explicit in ('', 'yes', 'no', 'clean'):
|
if itunes_explicit not in ('', 'yes', 'no', 'clean'):
|
||||||
raise ValueError('Invalid value for explicit tag')
|
raise ValueError('Invalid value for explicit tag')
|
||||||
self.__itunes_explicit = itunes_explicit
|
self.__itunes_explicit = itunes_explicit
|
||||||
return self.__itunes_explicit
|
return self.__itunes_explicit
|
||||||
|
|
||||||
|
def itunes_complete(self, itunes_complete=None):
|
||||||
def itunes_complete(self, itunes_complete=None):
|
'''Get or set the itunes:complete value of the podcast. This tag can be
|
||||||
'''Get or set the itunes:complete value of the podcast. This tag can be
|
used to indicate the completion of a podcast.
|
||||||
used to indicate the completion of a podcast.
|
|
||||||
|
If you populate this tag with "yes", you are indicating that no more
|
||||||
If you populate this tag with "yes", you are indicating that no more
|
episodes will be added to the podcast. If the <itunes:complete> tag is
|
||||||
episodes will be added to the podcast. If the <itunes:complete> tag is
|
present and has any other value (e.g. “no”), it will have no effect on
|
||||||
present and has any other value (e.g. “no”), it will have no effect on
|
the podcast.
|
||||||
the podcast.
|
|
||||||
|
:param itunes_complete: If the podcast is complete.
|
||||||
:param itunes_complete: If the podcast is complete.
|
:returns: If the podcast is complete.
|
||||||
:returns: If the podcast is complete.
|
'''
|
||||||
'''
|
if itunes_complete is not None:
|
||||||
if not itunes_complete is None:
|
if itunes_complete not in ('yes', 'no', '', True, False):
|
||||||
if not itunes_complete in ('yes', 'no', '', True, False):
|
raise ValueError('Invalid value for complete tag')
|
||||||
raise ValueError('Invalid value for complete tag')
|
if itunes_complete is True:
|
||||||
if itunes_complete == True:
|
itunes_complete = 'yes'
|
||||||
itunes_complete = 'yes'
|
if itunes_complete is False:
|
||||||
if itunes_complete == False:
|
itunes_complete = 'no'
|
||||||
itunes_complete = 'no'
|
self.__itunes_complete = itunes_complete
|
||||||
self.__itunes_complete = itunes_complete
|
return self.__itunes_complete
|
||||||
return self.__itunes_complete
|
|
||||||
|
def itunes_new_feed_url(self, itunes_new_feed_url=None):
|
||||||
|
'''Get or set the new-feed-url property of the podcast. This tag allows
|
||||||
def itunes_new_feed_url(self, itunes_new_feed_url=None):
|
you to change the URL where the podcast feed is located
|
||||||
'''Get or set the new-feed-url property of the podcast. This tag allows
|
|
||||||
you to change the URL where the podcast feed is located
|
After adding the tag to your old feed, you should maintain the old feed
|
||||||
|
for 48 hours before retiring it. At that point, iTunes will have
|
||||||
After adding the tag to your old feed, you should maintain the old feed
|
updated the directory with the new feed URL.
|
||||||
for 48 hours before retiring it. At that point, iTunes will have updated
|
|
||||||
the directory with the new feed URL.
|
:param itunes_new_feed_url: New feed URL.
|
||||||
|
:returns: New feed URL.
|
||||||
:param itunes_new_feed_url: New feed URL.
|
'''
|
||||||
:returns: New feed URL.
|
if itunes_new_feed_url is not None:
|
||||||
'''
|
self.__itunes_new_feed_url = itunes_new_feed_url
|
||||||
if not itunes_new_feed_url is None:
|
return self.__itunes_new_feed_url
|
||||||
self.__itunes_new_feed_url = itunes_new_feed_url
|
|
||||||
return self.__itunes_new_feed_url
|
def itunes_owner(self, name=None, email=None):
|
||||||
|
'''Get or set the itunes:owner of the podcast. This tag contains
|
||||||
|
information that will be used to contact the owner of the podcast for
|
||||||
def itunes_owner(self, name=None, email=None):
|
communication specifically about the podcast. It will not be publicly
|
||||||
'''Get or set the itunes:owner of the podcast. This tag contains
|
displayed.
|
||||||
information that will be used to contact the owner of the podcast for
|
|
||||||
communication specifically about the podcast. It will not be publicly
|
:param itunes_owner: The owner of the feed.
|
||||||
displayed.
|
:returns: Data of the owner of the feed.
|
||||||
|
'''
|
||||||
:param itunes_owner: The owner of the feed.
|
if name is not None:
|
||||||
:returns: Data of the owner of the feed.
|
if name and email:
|
||||||
'''
|
self.__itunes_owner = {'name': name, 'email': email}
|
||||||
if not name is None:
|
elif not name and not email:
|
||||||
if name and email:
|
self.__itunes_owner = None
|
||||||
self.__itunes_owner = {'name':name, 'email':email}
|
else:
|
||||||
elif not name and not email:
|
raise ValueError('Both name and email have to be set.')
|
||||||
self.__itunes_owner = None
|
return self.__itunes_owner
|
||||||
else:
|
|
||||||
raise ValueError('Both name and email have to be set.')
|
def itunes_subtitle(self, itunes_subtitle=None):
|
||||||
return self.__itunes_owner
|
'''Get or set the itunes:subtitle value for the podcast. The contents of
|
||||||
|
this tag are shown in the Description column in iTunes. The subtitle
|
||||||
|
displays best if it is only a few words long.
|
||||||
def itunes_subtitle(self, itunes_subtitle=None):
|
|
||||||
'''Get or set the itunes:subtitle value for the podcast. The contents of
|
:param itunes_subtitle: Subtitle of the podcast.
|
||||||
this tag are shown in the Description column in iTunes. The subtitle
|
:returns: Subtitle of the podcast.
|
||||||
displays best if it is only a few words long.
|
'''
|
||||||
|
if itunes_subtitle is not None:
|
||||||
:param itunes_subtitle: Subtitle of the podcast.
|
self.__itunes_subtitle = itunes_subtitle
|
||||||
:returns: Subtitle of the podcast.
|
return self.__itunes_subtitle
|
||||||
'''
|
|
||||||
if not itunes_subtitle is None:
|
def itunes_summary(self, itunes_summary=None):
|
||||||
self.__itunes_subtitle = itunes_subtitle
|
'''Get or set the itunes:summary value for the podcast. The contents of
|
||||||
return self.__itunes_subtitle
|
this tag are shown in a separate window that appears when the "circled
|
||||||
|
i" in the Description column is clicked. It also appears on the iTunes
|
||||||
|
page for your podcast. This field can be up to 4000 characters. If
|
||||||
def itunes_summary(self, itunes_summary=None):
|
`<itunes:summary>` is not included, the contents of the <description>
|
||||||
'''Get or set the itunes:summary value for the podcast. The contents of
|
tag are used.
|
||||||
this tag are shown in a separate window that appears when the "circled i"
|
|
||||||
in the Description column is clicked. It also appears on the iTunes page
|
:param itunes_summary: Summary of the podcast.
|
||||||
for your podcast. This field can be up to 4000 characters. If
|
:returns: Summary of the podcast.
|
||||||
<itunes:summary> is not included, the contents of the <description> tag
|
'''
|
||||||
are used.
|
if itunes_summary is not None:
|
||||||
|
self.__itunes_summary = itunes_summary
|
||||||
:param itunes_summary: Summary of the podcast.
|
return self.__itunes_summary
|
||||||
:returns: Summary of the podcast.
|
|
||||||
'''
|
_itunes_categories = {
|
||||||
if not itunes_summary is None:
|
'Arts': [
|
||||||
self.__itunes_summary = itunes_summary
|
'Design', 'Fashion & Beauty', 'Food', 'Literature',
|
||||||
return self.__itunes_summary
|
'Performing Arts', 'Visual Arts'],
|
||||||
|
'Business': [
|
||||||
|
'Business News', 'Careers', 'Investing',
|
||||||
_itunes_categories = {
|
'Management & Marketing', 'Shopping'],
|
||||||
'Arts': [ 'Design', 'Fashion & Beauty', 'Food', 'Literature',
|
'Comedy': [],
|
||||||
'Performing Arts', 'Visual Arts' ],
|
'Education': [
|
||||||
'Business' : [ 'Business News', 'Careers', 'Investing',
|
'Education', 'Education Technology', 'Higher Education',
|
||||||
'Management & Marketing', 'Shopping' ],
|
'K-12', 'Language Courses', 'Training'],
|
||||||
'Comedy' : [],
|
'Games & Hobbies': [
|
||||||
'Education' : [ 'Education', 'Education Technology',
|
'Automotive', 'Aviation', 'Hobbies', 'Other Games',
|
||||||
'Higher Education', 'K-12', 'Language Courses', 'Training' ],
|
'Video Games'],
|
||||||
'Games & Hobbies' : [ 'Automotive', 'Aviation', 'Hobbies',
|
'Government & Organizations': [
|
||||||
'Other Games', 'Video Games' ],
|
'Local', 'National', 'Non-Profit', 'Regional'],
|
||||||
'Government & Organizations' : [ 'Local', 'National', 'Non-Profit',
|
'Health': [
|
||||||
'Regional' ],
|
'Alternative Health', 'Fitness & Nutrition', 'Self-Help',
|
||||||
'Health' : [ 'Alternative Health', 'Fitness & Nutrition', 'Self-Help',
|
'Sexuality'],
|
||||||
'Sexuality' ],
|
'Kids & Family': [],
|
||||||
'Kids & Family' : [],
|
'Music': [],
|
||||||
'Music' : [],
|
'News & Politics': [],
|
||||||
'News & Politics' : [],
|
'Religion & Spirituality': [
|
||||||
'Religion & Spirituality' : [ 'Buddhism', 'Christianity', 'Hinduism',
|
'Buddhism', 'Christianity', 'Hinduism', 'Islam', 'Judaism',
|
||||||
'Islam', 'Judaism', 'Other', 'Spirituality' ],
|
'Other', 'Spirituality'],
|
||||||
'Science & Medicine' : [ 'Medicine', 'Natural Sciences',
|
'Science & Medicine': [
|
||||||
'Social Sciences' ],
|
'Medicine', 'Natural Sciences', 'Social Sciences'],
|
||||||
'Society & Culture' : [ 'History', 'Personal Journals', 'Philosophy',
|
'Society & Culture': [
|
||||||
'Places & Travel' ],
|
'History', 'Personal Journals', 'Philosophy',
|
||||||
'Sports & Recreation' : [ 'Amateur', 'College & High School',
|
'Places & Travel'],
|
||||||
'Outdoor', 'Professional' ],
|
'Sports & Recreation': [
|
||||||
'Technology' : [ 'Gadgets', 'Tech News', 'Podcasting',
|
'Amateur', 'College & High School', 'Outdoor', 'Professional'],
|
||||||
'Software How-To' ],
|
'Technology': [
|
||||||
'TV & Film' : []
|
'Gadgets', 'Tech News', 'Podcasting', 'Software How-To'],
|
||||||
}
|
'TV & Film': []}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''
|
'''
|
||||||
feedgen.ext.podcast_entry
|
feedgen.ext.podcast_entry
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Extends the feedgen to produce podcasts.
|
Extends the feedgen to produce podcasts.
|
||||||
|
|
||||||
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
|
:copyright: 2013-2016, Lars Kiesow <lkiesow@uos.de>
|
||||||
|
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
@ -15,229 +15,230 @@ from feedgen.ext.base import BaseEntryExtension
|
||||||
|
|
||||||
|
|
||||||
class PodcastEntryExtension(BaseEntryExtension):
|
class PodcastEntryExtension(BaseEntryExtension):
|
||||||
'''FeedEntry extension for podcasts.
|
'''FeedEntry extension for podcasts.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# ITunes tags
|
||||||
|
# http://www.apple.com/itunes/podcasts/specs.html#rss
|
||||||
|
self.__itunes_author = None
|
||||||
|
self.__itunes_block = None
|
||||||
|
self.__itunes_image = None
|
||||||
|
self.__itunes_duration = None
|
||||||
|
self.__itunes_explicit = None
|
||||||
|
self.__itunes_is_closed_captioned = None
|
||||||
|
self.__itunes_order = None
|
||||||
|
self.__itunes_subtitle = None
|
||||||
|
self.__itunes_summary = None
|
||||||
|
|
||||||
def __init__(self):
|
def extend_rss(self, entry):
|
||||||
## ITunes tags
|
'''Add additional fields to an RSS item.
|
||||||
# http://www.apple.com/itunes/podcasts/specs.html#rss
|
|
||||||
self.__itunes_author = None
|
|
||||||
self.__itunes_block = None
|
|
||||||
self.__itunes_image = None
|
|
||||||
self.__itunes_duration = None
|
|
||||||
self.__itunes_explicit = None
|
|
||||||
self.__itunes_is_closed_captioned = None
|
|
||||||
self.__itunes_order = None
|
|
||||||
self.__itunes_subtitle = None
|
|
||||||
self.__itunes_summary = None
|
|
||||||
|
|
||||||
|
:param feed: The RSS item XML element to use.
|
||||||
|
'''
|
||||||
|
ITUNES_NS = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
|
||||||
|
|
||||||
def extend_rss(self, entry):
|
if self.__itunes_author:
|
||||||
'''Add additional fields to an RSS item.
|
author = etree.SubElement(entry, '{%s}author' % ITUNES_NS)
|
||||||
|
author.text = self.__itunes_author
|
||||||
|
|
||||||
:param feed: The RSS item XML element to use.
|
if self.__itunes_block is not None:
|
||||||
'''
|
block = etree.SubElement(entry, '{%s}block' % ITUNES_NS)
|
||||||
ITUNES_NS = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
|
block.text = 'yes' if self.__itunes_block else 'no'
|
||||||
|
|
||||||
if self.__itunes_author:
|
if self.__itunes_image:
|
||||||
author = etree.SubElement(entry, '{%s}author' % ITUNES_NS)
|
image = etree.SubElement(entry, '{%s}image' % ITUNES_NS)
|
||||||
author.text = self.__itunes_author
|
image.attrib['href'] = self.__itunes_image
|
||||||
|
|
||||||
if not self.__itunes_block is None:
|
if self.__itunes_duration:
|
||||||
block = etree.SubElement(entry, '{%s}block' % ITUNES_NS)
|
duration = etree.SubElement(entry, '{%s}duration' % ITUNES_NS)
|
||||||
block.text = 'yes' if self.__itunes_block else 'no'
|
duration.text = self.__itunes_duration
|
||||||
|
|
||||||
if self.__itunes_image:
|
if self.__itunes_explicit in ('yes', 'no', 'clean'):
|
||||||
image = etree.SubElement(entry, '{%s}image' % ITUNES_NS)
|
explicit = etree.SubElement(entry, '{%s}explicit' % ITUNES_NS)
|
||||||
image.attrib['href'] = self.__itunes_image
|
explicit.text = self.__itunes_explicit
|
||||||
|
|
||||||
if self.__itunes_duration:
|
if self.__itunes_is_closed_captioned is not None:
|
||||||
duration = etree.SubElement(entry, '{%s}duration' % ITUNES_NS)
|
is_closed_captioned = etree.SubElement(
|
||||||
duration.text = self.__itunes_duration
|
entry, '{%s}isClosedCaptioned' % ITUNES_NS)
|
||||||
|
if self.__itunes_is_closed_captioned:
|
||||||
|
is_closed_captioned.text = 'yes'
|
||||||
|
else:
|
||||||
|
is_closed_captioned.text = 'no'
|
||||||
|
|
||||||
if self.__itunes_explicit in ('yes', 'no', 'clean'):
|
if self.__itunes_order is not None and self.__itunes_order >= 0:
|
||||||
explicit = etree.SubElement(entry, '{%s}explicit' % ITUNES_NS)
|
order = etree.SubElement(entry, '{%s}order' % ITUNES_NS)
|
||||||
explicit.text = self.__itunes_explicit
|
order.text = str(self.__itunes_order)
|
||||||
|
|
||||||
if not self.__itunes_is_closed_captioned is None:
|
if self.__itunes_subtitle:
|
||||||
is_closed_captioned = etree.SubElement(entry, '{%s}isClosedCaptioned' % ITUNES_NS)
|
subtitle = etree.SubElement(entry, '{%s}subtitle' % ITUNES_NS)
|
||||||
is_closed_captioned.text = 'yes' if self.__itunes_is_closed_captioned else 'no'
|
subtitle.text = self.__itunes_subtitle
|
||||||
|
|
||||||
if not self.__itunes_order is None and self.__itunes_order >= 0:
|
if self.__itunes_summary:
|
||||||
order = etree.SubElement(entry, '{%s}order' % ITUNES_NS)
|
summary = etree.SubElement(entry, '{%s}summary' % ITUNES_NS)
|
||||||
order.text = str(self.__itunes_order)
|
summary.text = self.__itunes_summary
|
||||||
|
return entry
|
||||||
|
|
||||||
if self.__itunes_subtitle:
|
def itunes_author(self, itunes_author=None):
|
||||||
subtitle = etree.SubElement(entry, '{%s}subtitle' % ITUNES_NS)
|
'''Get or set the itunes:author of the podcast episode. The content of
|
||||||
subtitle.text = self.__itunes_subtitle
|
this tag is shown in the Artist column in iTunes. If the tag is not
|
||||||
|
present, iTunes uses the contents of the <author> tag. If
|
||||||
|
<itunes:author> is not present at the feed level, iTunes will use the
|
||||||
|
contents of <managingEditor>.
|
||||||
|
|
||||||
if self.__itunes_summary:
|
:param itunes_author: The author of the podcast.
|
||||||
summary = etree.SubElement(entry, '{%s}summary' % ITUNES_NS)
|
:returns: The author of the podcast.
|
||||||
summary.text = self.__itunes_summary
|
'''
|
||||||
return entry
|
if itunes_author is not None:
|
||||||
|
self.__itunes_author = itunes_author
|
||||||
|
return self.__itunes_author
|
||||||
|
|
||||||
|
def itunes_block(self, itunes_block=None):
|
||||||
|
'''Get or set the ITunes block attribute. Use this to prevent episodes
|
||||||
|
from appearing in the iTunes podcast directory.
|
||||||
|
|
||||||
def itunes_author(self, itunes_author=None):
|
:param itunes_block: Block podcast episodes.
|
||||||
'''Get or set the itunes:author of the podcast episode. The content of
|
:returns: If the podcast episode is blocked.
|
||||||
this tag is shown in the Artist column in iTunes. If the tag is not
|
'''
|
||||||
present, iTunes uses the contents of the <author> tag. If <itunes:author>
|
if itunes_block is not None:
|
||||||
is not present at the feed level, iTunes will use the contents of
|
self.__itunes_block = itunes_block
|
||||||
<managingEditor>.
|
return self.__itunes_block
|
||||||
|
|
||||||
:param itunes_author: The author of the podcast.
|
def itunes_image(self, itunes_image=None):
|
||||||
:returns: The author of the podcast.
|
'''Get or set the image for the podcast episode. This tag specifies the
|
||||||
'''
|
artwork for your podcast. Put the URL to the image in the href
|
||||||
if not itunes_author is None:
|
attribute. iTunes prefers square .jpg images that are at least
|
||||||
self.__itunes_author = itunes_author
|
1400x1400 pixels, which is different from what is specified for the
|
||||||
return self.__itunes_author
|
standard RSS image tag. In order for a podcast to be eligible for an
|
||||||
|
iTunes Store feature, the accompanying image must be at least 1400x1400
|
||||||
|
pixels.
|
||||||
|
|
||||||
|
iTunes supports images in JPEG and PNG formats with an RGB color space
|
||||||
|
(CMYK is not supported). The URL must end in ".jpg" or ".png". If the
|
||||||
|
<itunes:image> tag is not present, iTunes will use the contents of the
|
||||||
|
RSS image tag.
|
||||||
|
|
||||||
def itunes_block(self, itunes_block=None):
|
If you change your podcast’s image, also change the file’s name. iTunes
|
||||||
'''Get or set the ITunes block attribute. Use this to prevent episodes
|
may not change the image if it checks your feed and the image URL is
|
||||||
from appearing in the iTunes podcast directory.
|
the same. The server hosting your cover art image must allow HTTP head
|
||||||
|
requests for iTS to be able to automatically update your cover art.
|
||||||
|
|
||||||
:param itunes_block: Block podcast episodes.
|
:param itunes_image: Image of the podcast.
|
||||||
:returns: If the podcast episode is blocked.
|
:returns: Image of the podcast.
|
||||||
'''
|
'''
|
||||||
if not itunes_block is None:
|
if itunes_image is not None:
|
||||||
self.__itunes_block = itunes_block
|
if itunes_image.endswith('.jpg') or itunes_image.endswith('.png'):
|
||||||
return self.__itunes_block
|
self.__itunes_image = itunes_image
|
||||||
|
else:
|
||||||
|
ValueError('Image file must be png or jpg')
|
||||||
|
return self.__itunes_image
|
||||||
|
|
||||||
|
def itunes_duration(self, itunes_duration=None):
|
||||||
|
'''Get or set the duration of the podcast episode. The content of this
|
||||||
|
tag is shown in the Time column in iTunes.
|
||||||
|
|
||||||
def itunes_image(self, itunes_image=None):
|
The tag can be formatted HH:MM:SS, H:MM:SS, MM:SS, or M:SS (H = hours,
|
||||||
'''Get or set the image for the podcast episode. This tag specifies the
|
M = minutes, S = seconds). If an integer is provided (no colon
|
||||||
artwork for your podcast. Put the URL to the image in the href attribute.
|
present), the value is assumed to be in seconds. If one colon is
|
||||||
iTunes prefers square .jpg images that are at least 1400x1400 pixels,
|
present, the number to the left is assumed to be minutes, and the
|
||||||
which is different from what is specified for the standard RSS image tag.
|
number to the right is assumed to be seconds. If more than two colons
|
||||||
In order for a podcast to be eligible for an iTunes Store feature, the
|
are present, the numbers farthest to the right are ignored.
|
||||||
accompanying image must be at least 1400x1400 pixels.
|
|
||||||
|
|
||||||
iTunes supports images in JPEG and PNG formats with an RGB color space
|
:param itunes_duration: Duration of the podcast episode.
|
||||||
(CMYK is not supported). The URL must end in ".jpg" or ".png". If the
|
:returns: Duration of the podcast episode.
|
||||||
<itunes:image> tag is not present, iTunes will use the contents of the
|
'''
|
||||||
RSS image tag.
|
if itunes_duration is not None:
|
||||||
|
itunes_duration = str(itunes_duration)
|
||||||
|
if len(itunes_duration.split(':')) > 3 or \
|
||||||
|
itunes_duration.lstrip('0123456789:') != '':
|
||||||
|
ValueError('Invalid duration format')
|
||||||
|
self.__itunes_duration = itunes_duration
|
||||||
|
return self.itunes_duration
|
||||||
|
|
||||||
If you change your podcast’s image, also change the file’s name. iTunes
|
def itunes_explicit(self, itunes_explicit=None):
|
||||||
may not change the image if it checks your feed and the image URL is the
|
'''Get or the the itunes:explicit value of the podcast episode. This
|
||||||
same. The server hosting your cover art image must allow HTTP head
|
tag should be used to indicate whether your podcast episode contains
|
||||||
requests for iTS to be able to automatically update your cover art.
|
explicit material. The three values for this tag are "yes", "no", and
|
||||||
|
"clean".
|
||||||
|
|
||||||
:param itunes_image: Image of the podcast.
|
If you populate this tag with "yes", an "explicit" parental advisory
|
||||||
:returns: Image of the podcast.
|
graphic will appear next to your podcast artwork on the iTunes Store
|
||||||
'''
|
and in the Name column in iTunes. If the value is "clean", the parental
|
||||||
if not itunes_image is None:
|
advisory type is considered Clean, meaning that no explicit language or
|
||||||
if not ( itunes_image.endswith('.jpg') or itunes_image.endswith('.png') ):
|
adult content is included anywhere in the episodes, and a "clean"
|
||||||
ValueError('Image file must be png or jpg')
|
graphic will appear. If the explicit tag is present and has any other
|
||||||
self.__itunes_image = itunes_image
|
value (e.g., "no"), you see no indicator — blank is the default
|
||||||
return self.__itunes_image
|
advisory type.
|
||||||
|
|
||||||
|
:param itunes_explicit: If the podcast episode contains explicit
|
||||||
|
material.
|
||||||
|
:returns: If the podcast episode contains explicit material.
|
||||||
|
'''
|
||||||
|
if itunes_explicit is not None:
|
||||||
|
if itunes_explicit not in ('', 'yes', 'no', 'clean'):
|
||||||
|
raise ValueError('Invalid value for explicit tag')
|
||||||
|
self.__itunes_explicit = itunes_explicit
|
||||||
|
return self.__itunes_explicit
|
||||||
|
|
||||||
def itunes_duration(self, itunes_duration=None):
|
def itunes_is_closed_captioned(self, itunes_is_closed_captioned=None):
|
||||||
'''Get or set the duration of the podcast episode. The content of this
|
'''Get or set the is_closed_captioned value of the podcast episode.
|
||||||
tag is shown in the Time column in iTunes.
|
This tag should be used if your podcast includes a video episode with
|
||||||
|
embedded closed captioning support. The two values for this tag are
|
||||||
|
"yes" and "no”.
|
||||||
|
|
||||||
The tag can be formatted HH:MM:SS, H:MM:SS, MM:SS, or M:SS (H = hours,
|
:param is_closed_captioned: If the episode has closed captioning
|
||||||
M = minutes, S = seconds). If an integer is provided (no colon present),
|
support.
|
||||||
the value is assumed to be in seconds. If one colon is present, the
|
:returns: If the episode has closed captioning support.
|
||||||
number to the left is assumed to be minutes, and the number to the right
|
'''
|
||||||
is assumed to be seconds. If more than two colons are present, the
|
if itunes_is_closed_captioned is not None:
|
||||||
numbers farthest to the right are ignored.
|
self.__itunes_is_closed_captioned = \
|
||||||
|
itunes_is_closed_captioned in ('yes', True)
|
||||||
|
return self.__itunes_is_closed_captioned
|
||||||
|
|
||||||
:param itunes_duration: Duration of the podcast episode.
|
def itunes_order(self, itunes_order=None):
|
||||||
:returns: Duration of the podcast episode.
|
'''Get or set the itunes:order value of the podcast episode. This tag
|
||||||
'''
|
can be used to override the default ordering of episodes on the store.
|
||||||
if not itunes_duration is None:
|
|
||||||
itunes_duration = str(itunes_duration)
|
|
||||||
if len(itunes_duration.split(':')) > 3 or \
|
|
||||||
itunes_duration.lstrip('0123456789:') != '':
|
|
||||||
ValueError('Invalid duration format')
|
|
||||||
self.__itunes_duration = itunes_duration
|
|
||||||
return self.itunes_duration
|
|
||||||
|
|
||||||
|
This tag is used at an <item> level by populating with the number value
|
||||||
|
in which you would like the episode to appear on the store. For
|
||||||
|
example, if you would like an <item> to appear as the first episode in
|
||||||
|
the podcast, you would populate the <itunes:order> tag with “1”. If
|
||||||
|
conflicting order values are present in multiple episodes, the store
|
||||||
|
will use default ordering (pubDate).
|
||||||
|
|
||||||
def itunes_explicit(self, itunes_explicit=None):
|
To remove the order from the episode set the order to a value below
|
||||||
'''Get or the the itunes:explicit value of the podcast episode. This tag
|
zero.
|
||||||
should be used to indicate whether your podcast episode contains explicit
|
|
||||||
material. The three values for this tag are "yes", "no", and "clean".
|
|
||||||
|
|
||||||
If you populate this tag with "yes", an "explicit" parental advisory
|
:param itunes_order: The order of the episode.
|
||||||
graphic will appear next to your podcast artwork on the iTunes Store and
|
:returns: The order of the episode.
|
||||||
in the Name column in iTunes. If the value is "clean", the parental
|
'''
|
||||||
advisory type is considered Clean, meaning that no explicit language or
|
if itunes_order is not None:
|
||||||
adult content is included anywhere in the episodes, and a "clean" graphic
|
self.__itunes_order = int(itunes_order)
|
||||||
will appear. If the explicit tag is present and has any other value
|
return self.__itunes_order
|
||||||
(e.g., "no"), you see no indicator — blank is the default advisory type.
|
|
||||||
|
|
||||||
:param itunes_explicit: If the podcast episode contains explicit material.
|
def itunes_subtitle(self, itunes_subtitle=None):
|
||||||
:returns: If the podcast episode contains explicit material.
|
'''Get or set the itunes:subtitle value for the podcast episode. The
|
||||||
'''
|
contents of this tag are shown in the Description column in iTunes. The
|
||||||
if not itunes_explicit is None:
|
subtitle displays best if it is only a few words long.
|
||||||
if not itunes_explicit in ('', 'yes', 'no', 'clean'):
|
|
||||||
raise ValueError('Invalid value for explicit tag')
|
|
||||||
self.__itunes_explicit = itunes_explicit
|
|
||||||
return self.__itunes_explicit
|
|
||||||
|
|
||||||
|
:param itunes_subtitle: Subtitle of the podcast episode.
|
||||||
|
:returns: Subtitle of the podcast episode.
|
||||||
|
'''
|
||||||
|
if itunes_subtitle is not None:
|
||||||
|
self.__itunes_subtitle = itunes_subtitle
|
||||||
|
return self.__itunes_subtitle
|
||||||
|
|
||||||
def itunes_is_closed_captioned(self, itunes_is_closed_captioned=None):
|
def itunes_summary(self, itunes_summary=None):
|
||||||
'''Get or set the is_closed_captioned value of the podcast episode. This
|
'''Get or set the itunes:summary value for the podcast episode. The
|
||||||
tag should be used if your podcast includes a video episode with embedded
|
contents of this tag are shown in a separate window that appears when
|
||||||
closed captioning support. The two values for this tag are "yes" and
|
the "circled i" in the Description column is clicked. It also appears
|
||||||
"no”.
|
on the iTunes page for your podcast. This field can be up to 4000
|
||||||
|
characters. If <itunes:summary> is not included, the contents of the
|
||||||
|
<description> tag are used.
|
||||||
|
|
||||||
:param is_closed_captioned: If the episode has closed captioning support.
|
:param itunes_summary: Summary of the podcast episode.
|
||||||
:returns: If the episode has closed captioning support.
|
:returns: Summary of the podcast episode.
|
||||||
'''
|
'''
|
||||||
if not itunes_is_closed_captioned is None:
|
if itunes_summary is not None:
|
||||||
self.__itunes_is_closed_captioned = itunes_is_closed_captioned in ('yes', True)
|
self.__itunes_summary = itunes_summary
|
||||||
return self.__itunes_is_closed_captioned
|
return self.__itunes_summary
|
||||||
|
|
||||||
|
|
||||||
def itunes_order(self, itunes_order=None):
|
|
||||||
'''Get or set the itunes:order value of the podcast episode. This tag can
|
|
||||||
be used to override the default ordering of episodes on the store.
|
|
||||||
|
|
||||||
This tag is used at an <item> level by populating with the number value
|
|
||||||
in which you would like the episode to appear on the store. For example,
|
|
||||||
if you would like an <item> to appear as the first episode in the
|
|
||||||
podcast, you would populate the <itunes:order> tag with “1”. If
|
|
||||||
conflicting order values are present in multiple episodes, the store will
|
|
||||||
use default ordering (pubDate).
|
|
||||||
|
|
||||||
To remove the order from the episode set the order to a value below zero.
|
|
||||||
|
|
||||||
:param itunes_order: The order of the episode.
|
|
||||||
:returns: The order of the episode.
|
|
||||||
'''
|
|
||||||
if not itunes_order is None:
|
|
||||||
self.__itunes_order = int(itunes_order)
|
|
||||||
return self.__itunes_order
|
|
||||||
|
|
||||||
|
|
||||||
def itunes_subtitle(self, itunes_subtitle=None):
|
|
||||||
'''Get or set the itunes:subtitle value for the podcast episode. The
|
|
||||||
contents of this tag are shown in the Description column in iTunes. The
|
|
||||||
subtitle displays best if it is only a few words long.
|
|
||||||
|
|
||||||
:param itunes_subtitle: Subtitle of the podcast episode.
|
|
||||||
:returns: Subtitle of the podcast episode.
|
|
||||||
'''
|
|
||||||
if not itunes_subtitle is None:
|
|
||||||
self.__itunes_subtitle = itunes_subtitle
|
|
||||||
return self.__itunes_subtitle
|
|
||||||
|
|
||||||
|
|
||||||
def itunes_summary(self, itunes_summary=None):
|
|
||||||
'''Get or set the itunes:summary value for the podcast episode. The
|
|
||||||
contents of this tag are shown in a separate window that appears when the
|
|
||||||
"circled i" in the Description column is clicked. It also appears on the
|
|
||||||
iTunes page for your podcast. This field can be up to 4000 characters. If
|
|
||||||
<itunes:summary> is not included, the contents of the <description> tag
|
|
||||||
are used.
|
|
||||||
|
|
||||||
:param itunes_summary: Summary of the podcast episode.
|
|
||||||
:returns: Summary of the podcast episode.
|
|
||||||
'''
|
|
||||||
if not itunes_summary is None:
|
|
||||||
self.__itunes_summary = itunes_summary
|
|
||||||
return self.__itunes_summary
|
|
||||||
|
|
|
@ -1,127 +1,126 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''
|
'''
|
||||||
feedgen.ext.torrent
|
feedgen.ext.torrent
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Extends the FeedGenerator to produce torrent feeds.
|
Extends the FeedGenerator to produce torrent feeds.
|
||||||
|
|
||||||
:copyright: 2016, Raspbeguy <raspbeguy@hashtagueule.fr>
|
:copyright: 2016, Raspbeguy <raspbeguy@hashtagueule.fr>
|
||||||
|
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from feedgen.ext.base import BaseExtension,BaseEntryExtension
|
from feedgen.ext.base import BaseExtension, BaseEntryExtension
|
||||||
|
|
||||||
TORRENT_NS = 'http://xmlns.ezrss.it/0.1/dtd/'
|
TORRENT_NS = 'http://xmlns.ezrss.it/0.1/dtd/'
|
||||||
|
|
||||||
|
|
||||||
class TorrentExtension(BaseExtension):
|
class TorrentExtension(BaseExtension):
|
||||||
'''FeedGenerator extension for torrent feeds.
|
'''FeedGenerator extension for torrent feeds.
|
||||||
'''
|
'''
|
||||||
def extend_ns(self):
|
def extend_ns(self):
|
||||||
return {'torrent' : TORRENT_NS}
|
return {'torrent': TORRENT_NS}
|
||||||
|
|
||||||
|
|
||||||
class TorrentEntryExtension(BaseEntryExtension):
|
class TorrentEntryExtension(BaseEntryExtension):
|
||||||
'''FeedEntry extention for torrent feeds
|
'''FeedEntry extention for torrent feeds
|
||||||
'''
|
'''
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.__torrent_filename = None
|
self.__torrent_filename = None
|
||||||
self.__torrent_infohash = None
|
self.__torrent_infohash = None
|
||||||
self.__torrent_contentlength = None
|
self.__torrent_contentlength = None
|
||||||
self.__torrent_seeds = None
|
self.__torrent_seeds = None
|
||||||
self.__torrent_peers = None
|
self.__torrent_peers = None
|
||||||
self.__torrent=verified = None
|
|
||||||
|
|
||||||
|
def extend_rss(self, entry):
|
||||||
|
'''Add additional fields to an RSS item.
|
||||||
|
|
||||||
def extend_rss(self, entry):
|
:param feed: The RSS item XML element to use.
|
||||||
'''Add additional fields to an RSS item.
|
'''
|
||||||
|
if self.__torrent_filename:
|
||||||
|
filename = etree.SubElement(entry, '{%s}filename' % TORRENT_NS)
|
||||||
|
filename.text = self.__torrent_filename
|
||||||
|
|
||||||
:param feed: The RSS item XML element to use.
|
if self.__torrent_contentlength:
|
||||||
'''
|
contentlength = etree.SubElement(entry,
|
||||||
if self.__torrent_filename:
|
'{%s}contentlength' % TORRENT_NS)
|
||||||
filename = etree.SubElement(entry, '{%s}filename' % TORRENT_NS)
|
contentlength.text = self.__torrent_contentlength
|
||||||
filename.text = self.__torrent_filename
|
|
||||||
|
|
||||||
if self.__torrent_contentlength:
|
if self.__torrent_infohash:
|
||||||
contentlength = etree.SubElement(entry, '{%s}contentlength' % TORRENT_NS)
|
infohash = etree.SubElement(entry, '{%s}infohash' % TORRENT_NS)
|
||||||
contentlength.text = self.__torrent_contentlength
|
infohash.text = self.__torrent_infohash
|
||||||
|
magnet = etree.SubElement(entry, '{%s}magneturi' % TORRENT_NS)
|
||||||
|
magnet.text = 'magnet:?xt=urn:btih:' + self.__torrent_infohash
|
||||||
|
|
||||||
if self.__torrent_infohash:
|
if self.__torrent_seeds:
|
||||||
infohash = etree.SubElement(entry, '{%s}infohash' % TORRENT_NS)
|
seeds = etree.SubElement(entry, '{%s}seed' % TORRENT_NS)
|
||||||
infohash.text = self.__torrent_infohash
|
seeds.text = self.__torrent_seeds
|
||||||
magnet = etree.SubElement(entry, '{%s}magneturi' % TORRENT_NS)
|
|
||||||
magnet.text = 'magnet:?xt=urn:btih:' + self.__torrent_infohash
|
|
||||||
|
|
||||||
if self.__torrent_seeds:
|
if self.__torrent_peers:
|
||||||
seeds = etree.SubElement(entry, '{%s}seed' % TORRENT_NS)
|
peers = etree.SubElement(entry, '{%s}peers' % TORRENT_NS)
|
||||||
seeds.text = self.__torrent_seeds
|
peers.text = self.__torrent_peers
|
||||||
|
|
||||||
if self.__torrent_peers:
|
if self.__torrent_seeds:
|
||||||
peers = etree.SubElement(entry, '{%s}peers' % TORRENT_NS)
|
verified = etree.SubElement(entry, '{%s}verified' % TORRENT_NS)
|
||||||
peers.text = self.__torrent_peers
|
verified.text = self.__torrent_verified
|
||||||
|
|
||||||
if self.__torrent_seeds:
|
def filename(self, torrent_filename=None):
|
||||||
verified = etree.SubElement(entry, '{%s}verified' % TORRENT_NS)
|
'''Get or set the name of the torrent file.
|
||||||
verified.text = self.__torrent_verified
|
|
||||||
|
|
||||||
|
:param torrent_filename: The name of the torrent file.
|
||||||
|
:returns: The name of the torrent file.
|
||||||
|
'''
|
||||||
|
if torrent_filename is not None:
|
||||||
|
self.__torrent_filename = torrent_filename
|
||||||
|
return self.__torrent_filename
|
||||||
|
|
||||||
def filename(self, torrent_filename=None):
|
def infohash(self, torrent_infohash=None):
|
||||||
'''Get or set the name of the torrent file.
|
'''Get or set the hash of the target file.
|
||||||
|
|
||||||
:param torrent_filename: The name of the torrent file.
|
:param torrent_infohash: The target file hash.
|
||||||
:returns: The name of the torrent file.
|
:returns: The target hash file.
|
||||||
'''
|
'''
|
||||||
if not torrent_filename is None:
|
if torrent_infohash is not None:
|
||||||
self.__torrent_filename = torrent_filename
|
self.__torrent_infohash = torrent_infohash
|
||||||
return self.__torrent_filename
|
return self.__torrent_infohash
|
||||||
|
|
||||||
def infohash (self, torrent_infohash=None):
|
def contentlength(self, torrent_contentlength=None):
|
||||||
'''Get or set the hash of the target file.
|
'''Get or set the size of the target file.
|
||||||
|
|
||||||
:param torrent_infohash: The target file hash.
|
:param torrent_contentlength: The target file size.
|
||||||
:returns: The target hash file.
|
:returns: The target file size.
|
||||||
'''
|
'''
|
||||||
if not torrent_infohash is None:
|
if torrent_contentlength is not None:
|
||||||
self.__torrent_infohash = torrent_infohash
|
self.__torrent_contentlength = torrent_contentlength
|
||||||
return self.__torrent_infohash
|
return self.__torrent_contentlength
|
||||||
|
|
||||||
def contentlength (self, torrent_contentlength=None):
|
def seeds(self, torrent_seeds=None):
|
||||||
'''Get or set the size of the target file.
|
'''Get or set the number of seeds.
|
||||||
|
|
||||||
:param torrent_contentlength: The target file size.
|
:param torrent_seeds: The seeds number.
|
||||||
:returns: The target file size.
|
:returns: The seeds number.
|
||||||
'''
|
'''
|
||||||
if not torrent_contentlength is None:
|
if torrent_seeds is not None:
|
||||||
self.__torrent_contentlength = torrent_contentlength
|
self.__torrent_seeds = torrent_seeds
|
||||||
return self.__torrent_contentlength
|
return self.__torrent_seeds
|
||||||
|
|
||||||
def seeds (self, torrent_seeds=None):
|
def peers(self, torrent_peers=None):
|
||||||
'''Get or set the number of seeds.
|
'''Get or set the number od peers
|
||||||
|
|
||||||
:param torrent_seeds: The seeds number.
|
:param torrent_infohash: The peers number.
|
||||||
:returns: The seeds number.
|
:returns: The peers number.
|
||||||
'''
|
'''
|
||||||
if not torrent_seeds is None:
|
if torrent_peers is not None:
|
||||||
self.__torrent_seeds = torrent_seeds
|
self.__torrent_peers = torrent_peers
|
||||||
return self.__torrent_seeds
|
return self.__torrent_peers
|
||||||
|
|
||||||
def peers (self, torrent_peers=None):
|
def verified(self, torrent_verified=None):
|
||||||
'''Get or set the number od peers
|
'''Get or set the number of verified peers.
|
||||||
|
|
||||||
:param torrent_infohash: The peers number.
|
:param torrent_infohash: The verified peers number.
|
||||||
:returns: The peers number.
|
:returns: The verified peers number.
|
||||||
'''
|
'''
|
||||||
if not torrent_peers is None:
|
if torrent_verified is not None:
|
||||||
self.__torrent_peers = torrent_peers
|
self.__torrent_verified = torrent_verified
|
||||||
return self.__torrent_peers
|
return self.__torrent_verified
|
||||||
|
|
||||||
def verified (self, torrent_verified=None):
|
|
||||||
'''Get or set the number of verified peers.
|
|
||||||
|
|
||||||
:param torrent_infohash: The verified peers number.
|
|
||||||
:returns: The verified peers number.
|
|
||||||
'''
|
|
||||||
if not torrent_verified is None:
|
|
||||||
self.__torrent_verified = torrent_verified
|
|
||||||
return self.__torrent_verified
|
|
||||||
|
|
2310
feedgen/feed.py
2310
feedgen/feed.py
File diff suppressed because it is too large
Load diff
112
feedgen/util.py
112
feedgen/util.py
|
@ -1,72 +1,74 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''
|
'''
|
||||||
feedgen.util
|
feedgen.util
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
This file contains helper functions for the feed generator module.
|
This file contains helper functions for the feed generator module.
|
||||||
|
|
||||||
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
|
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
import sys, locale
|
import locale
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
def ensure_format(val, allowed, required, allowed_values=None, defaults=None):
|
def ensure_format(val, allowed, required, allowed_values=None, defaults=None):
|
||||||
'''Takes a dictionary or a list of dictionaries and check if all keys are in
|
'''Takes a dictionary or a list of dictionaries and check if all keys are in
|
||||||
the set of allowed keys, if all required keys are present and if the values
|
the set of allowed keys, if all required keys are present and if the values
|
||||||
of a specific key are ok.
|
of a specific key are ok.
|
||||||
|
|
||||||
:param val: Dictionaries to check.
|
:param val: Dictionaries to check.
|
||||||
:param allowed: Set of allowed keys.
|
:param allowed: Set of allowed keys.
|
||||||
:param required: Set of required keys.
|
:param required: Set of required keys.
|
||||||
:param allowed_values: Dictionary with keys and sets of their allowed values.
|
:param allowed_values: Dictionary with keys and sets of their allowed
|
||||||
:param defaults: Dictionary with default values.
|
values.
|
||||||
:returns: List of checked dictionaries.
|
:param defaults: Dictionary with default values.
|
||||||
'''
|
:returns: List of checked dictionaries.
|
||||||
if not val:
|
'''
|
||||||
return None
|
if not val:
|
||||||
if allowed_values is None:
|
return None
|
||||||
allowed_values = {}
|
if allowed_values is None:
|
||||||
if defaults is None:
|
allowed_values = {}
|
||||||
defaults = {}
|
if defaults is None:
|
||||||
# Make shure that we have a list of dicts. Even if there is only one.
|
defaults = {}
|
||||||
if not isinstance(val, list):
|
# Make shure that we have a list of dicts. Even if there is only one.
|
||||||
val = [val]
|
if not isinstance(val, list):
|
||||||
for elem in val:
|
val = [val]
|
||||||
if not isinstance(elem, dict):
|
for elem in val:
|
||||||
raise ValueError('Invalid data (value is no dictionary)')
|
if not isinstance(elem, dict):
|
||||||
# Set default values
|
raise ValueError('Invalid data (value is no dictionary)')
|
||||||
|
# Set default values
|
||||||
|
|
||||||
version = sys.version_info[0]
|
version = sys.version_info[0]
|
||||||
|
|
||||||
if version == 2:
|
if version == 2:
|
||||||
items = defaults.iteritems()
|
items = defaults.iteritems()
|
||||||
else:
|
else:
|
||||||
items = defaults.items()
|
items = defaults.items()
|
||||||
|
|
||||||
for k,v in items:
|
for k, v in items:
|
||||||
elem[k] = elem.get(k, v)
|
elem[k] = elem.get(k, v)
|
||||||
if not set(elem.keys()) <= allowed:
|
if not set(elem.keys()) <= allowed:
|
||||||
raise ValueError('Data contains invalid keys')
|
raise ValueError('Data contains invalid keys')
|
||||||
if not set(elem.keys()) >= required:
|
if not set(elem.keys()) >= required:
|
||||||
raise ValueError('Data contains not all required keys')
|
raise ValueError('Data contains not all required keys')
|
||||||
|
|
||||||
if version == 2:
|
if version == 2:
|
||||||
values = allowed_values.iteritems()
|
values = allowed_values.iteritems()
|
||||||
else:
|
else:
|
||||||
values = allowed_values.items()
|
values = allowed_values.items()
|
||||||
|
|
||||||
for k,v in values:
|
for k, v in values:
|
||||||
if elem.get(k) and not elem[k] in v:
|
if elem.get(k) and not elem[k] in v:
|
||||||
raise ValueError('Invalid value for %s' % k )
|
raise ValueError('Invalid value for %s' % k)
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
def formatRFC2822(d):
|
def formatRFC2822(d):
|
||||||
'''Make sure the locale setting do not interfere with the time format.
|
'''Make sure the locale setting do not interfere with the time format.
|
||||||
'''
|
'''
|
||||||
l = locale.setlocale(locale.LC_ALL)
|
l = locale.setlocale(locale.LC_ALL)
|
||||||
locale.setlocale(locale.LC_ALL, 'C')
|
locale.setlocale(locale.LC_ALL, 'C')
|
||||||
d = d.strftime('%a, %d %b %Y %H:%M:%S %z')
|
d = d.strftime('%a, %d %b %Y %H:%M:%S %z')
|
||||||
locale.setlocale(locale.LC_ALL, l)
|
locale.setlocale(locale.LC_ALL, l)
|
||||||
return d
|
return d
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
'''
|
'''
|
||||||
feedgen.version
|
feedgen.version
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
:copyright: 2013-2017, Lars Kiesow <lkiesow@uos.de>
|
:copyright: 2013-2017, Lars Kiesow <lkiesow@uos.de>
|
||||||
|
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
@ -18,8 +18,8 @@ version_str = '.'.join([str(x) for x in version])
|
||||||
|
|
||||||
version_major = version[:1]
|
version_major = version[:1]
|
||||||
version_minor = version[:2]
|
version_minor = version[:2]
|
||||||
version_full = version
|
version_full = version
|
||||||
|
|
||||||
version_major_str = '.'.join([str(x) for x in version_major])
|
version_major_str = '.'.join([str(x) for x in version_major])
|
||||||
version_minor_str = '.'.join([str(x) for x in version_minor])
|
version_minor_str = '.'.join([str(x) for x in version_minor])
|
||||||
version_full_str = '.'.join([str(x) for x in version_full])
|
version_full_str = '.'.join([str(x) for x in version_full])
|
||||||
|
|
63
setup.py
63
setup.py
|
@ -6,36 +6,36 @@ import feedgen.version
|
||||||
|
|
||||||
packages = ['feedgen', 'feedgen/ext']
|
packages = ['feedgen', 'feedgen/ext']
|
||||||
|
|
||||||
setup(
|
setup(name='feedgen',
|
||||||
name = 'feedgen',
|
packages=packages,
|
||||||
packages = packages,
|
version=feedgen.version.version_full_str,
|
||||||
version = feedgen.version.version_full_str,
|
description='Feed Generator (ATOM, RSS, Podcasts)',
|
||||||
description = 'Feed Generator (ATOM, RSS, Podcasts)',
|
author='Lars Kiesow',
|
||||||
author = 'Lars Kiesow',
|
author_email='lkiesow@uos.de',
|
||||||
author_email = 'lkiesow@uos.de',
|
url='http://lkiesow.github.io/python-feedgen',
|
||||||
url = 'http://lkiesow.github.io/python-feedgen',
|
keywords=['feed', 'ATOM', 'RSS', 'podcast'],
|
||||||
keywords = ['feed','ATOM','RSS','podcast'],
|
license='FreeBSD and LGPLv3+',
|
||||||
license = 'FreeBSD and LGPLv3+',
|
install_requires=['lxml', 'python-dateutil'],
|
||||||
install_requires = ['lxml', 'python-dateutil'],
|
classifiers=[
|
||||||
classifiers = [
|
'Development Status :: 5 - Production/Stable',
|
||||||
'Development Status :: 5 - Production/Stable',
|
'Intended Audience :: Developers',
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Information Technology',
|
||||||
'Intended Audience :: Information Technology',
|
'Intended Audience :: Science/Research',
|
||||||
'Intended Audience :: Science/Research',
|
'License :: OSI Approved :: BSD License',
|
||||||
'License :: OSI Approved :: BSD License',
|
'License :: OSI Approved :: GNU Lesser General Public License v3 ' +
|
||||||
'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)',
|
'or later (LGPLv3+)',
|
||||||
'Natural Language :: English',
|
'Natural Language :: English',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
'Programming Language :: Python :: 2',
|
'Programming Language :: Python :: 2',
|
||||||
'Programming Language :: Python :: 3',
|
'Programming Language :: Python :: 3',
|
||||||
'Topic :: Communications',
|
'Topic :: Communications',
|
||||||
'Topic :: Internet',
|
'Topic :: Internet',
|
||||||
'Topic :: Text Processing',
|
'Topic :: Text Processing',
|
||||||
'Topic :: Text Processing :: Markup',
|
'Topic :: Text Processing :: Markup',
|
||||||
'Topic :: Text Processing :: Markup :: XML'
|
'Topic :: Text Processing :: Markup :: XML'
|
||||||
],
|
],
|
||||||
long_description = '''\
|
long_description='''\
|
||||||
Feedgenerator
|
Feedgenerator
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
@ -46,5 +46,4 @@ Podcasts.
|
||||||
It is licensed under the terms of both, the FreeBSD license and the LGPLv3+.
|
It is licensed under the terms of both, the FreeBSD license and the LGPLv3+.
|
||||||
Choose the one which is more convenient for you. For more details have a look
|
Choose the one which is more convenient for you. For more details have a look
|
||||||
at license.bsd and license.lgpl.
|
at license.bsd and license.lgpl.
|
||||||
'''
|
''')
|
||||||
)
|
|
||||||
|
|
|
@ -7,100 +7,99 @@ These are test cases for a basic entry.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
from lxml import etree
|
|
||||||
from feedgen.feed import FeedGenerator
|
from feedgen.feed import FeedGenerator
|
||||||
|
|
||||||
|
|
||||||
class TestSequenceFunctions(unittest.TestCase):
|
class TestSequenceFunctions(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
||||||
fg = FeedGenerator()
|
fg = FeedGenerator()
|
||||||
self.feedId = 'http://example.com'
|
self.feedId = 'http://example.com'
|
||||||
self.title = 'Some Testfeed'
|
self.title = 'Some Testfeed'
|
||||||
|
|
||||||
fg.id(self.feedId)
|
fg.id(self.feedId)
|
||||||
fg.title(self.title)
|
fg.title(self.title)
|
||||||
|
|
||||||
fe = fg.add_entry()
|
fe = fg.add_entry()
|
||||||
fe.id('http://lernfunk.de/media/654321/1')
|
fe.id('http://lernfunk.de/media/654321/1')
|
||||||
fe.title('The First Episode')
|
fe.title('The First Episode')
|
||||||
|
|
||||||
#Use also the different name add_item
|
# Use also the different name add_item
|
||||||
fe = fg.add_item()
|
fe = fg.add_item()
|
||||||
fe.id('http://lernfunk.de/media/654321/1')
|
fe.id('http://lernfunk.de/media/654321/1')
|
||||||
fe.title('The Second Episode')
|
fe.title('The Second Episode')
|
||||||
|
|
||||||
fe = fg.add_entry()
|
fe = fg.add_entry()
|
||||||
fe.id('http://lernfunk.de/media/654321/1')
|
fe.id('http://lernfunk.de/media/654321/1')
|
||||||
fe.title('The Third Episode')
|
fe.title('The Third Episode')
|
||||||
|
|
||||||
self.fg = fg
|
self.fg = fg
|
||||||
|
|
||||||
def test_checkEntryNumbers(self):
|
def test_checkEntryNumbers(self):
|
||||||
|
|
||||||
fg = self.fg
|
fg = self.fg
|
||||||
assert len(fg.entry()) == 3
|
assert len(fg.entry()) == 3
|
||||||
|
|
||||||
def test_checkItemNumbers(self):
|
def test_checkItemNumbers(self):
|
||||||
|
|
||||||
fg = self.fg
|
fg = self.fg
|
||||||
assert len(fg.item()) == 3
|
assert len(fg.item()) == 3
|
||||||
|
|
||||||
def test_checkEntryContent(self):
|
def test_checkEntryContent(self):
|
||||||
|
|
||||||
fg = self.fg
|
fg = self.fg
|
||||||
assert len(fg.entry()) != None
|
assert fg.entry()
|
||||||
|
|
||||||
def test_removeEntryByIndex(self):
|
def test_removeEntryByIndex(self):
|
||||||
fg = FeedGenerator()
|
fg = FeedGenerator()
|
||||||
self.feedId = 'http://example.com'
|
self.feedId = 'http://example.com'
|
||||||
self.title = 'Some Testfeed'
|
self.title = 'Some Testfeed'
|
||||||
|
|
||||||
fe = fg.add_entry()
|
fe = fg.add_entry()
|
||||||
fe.id('http://lernfunk.de/media/654321/1')
|
fe.id('http://lernfunk.de/media/654321/1')
|
||||||
fe.title('The Third Episode')
|
fe.title('The Third Episode')
|
||||||
assert len(fg.entry()) == 1
|
assert len(fg.entry()) == 1
|
||||||
fg.remove_entry(0)
|
fg.remove_entry(0)
|
||||||
assert len(fg.entry()) == 0
|
assert len(fg.entry()) == 0
|
||||||
|
|
||||||
def test_removeEntryByEntry(self):
|
def test_removeEntryByEntry(self):
|
||||||
fg = FeedGenerator()
|
fg = FeedGenerator()
|
||||||
self.feedId = 'http://example.com'
|
self.feedId = 'http://example.com'
|
||||||
self.title = 'Some Testfeed'
|
self.title = 'Some Testfeed'
|
||||||
|
|
||||||
fe = fg.add_entry()
|
fe = fg.add_entry()
|
||||||
fe.id('http://lernfunk.de/media/654321/1')
|
fe.id('http://lernfunk.de/media/654321/1')
|
||||||
fe.title('The Third Episode')
|
fe.title('The Third Episode')
|
||||||
|
|
||||||
assert len(fg.entry()) == 1
|
assert len(fg.entry()) == 1
|
||||||
fg.remove_entry(fe)
|
fg.remove_entry(fe)
|
||||||
assert len(fg.entry()) == 0
|
assert len(fg.entry()) == 0
|
||||||
|
|
||||||
def test_categoryHasDomain(self):
|
def test_categoryHasDomain(self):
|
||||||
fg = FeedGenerator()
|
fg = FeedGenerator()
|
||||||
fg.title('some title')
|
fg.title('some title')
|
||||||
fg.link( href='http://www.dontcare.com', rel='alternate' )
|
fg.link(href='http://www.dontcare.com', rel='alternate')
|
||||||
fg.description('description')
|
fg.description('description')
|
||||||
fe = fg.add_entry()
|
fe = fg.add_entry()
|
||||||
fe.id('http://lernfunk.de/media/654321/1')
|
fe.id('http://lernfunk.de/media/654321/1')
|
||||||
fe.title('some title')
|
fe.title('some title')
|
||||||
fe.category([
|
fe.category([
|
||||||
{'term' : 'category',
|
{'term': 'category',
|
||||||
'scheme': 'http://www.somedomain.com/category',
|
'scheme': 'http://www.somedomain.com/category',
|
||||||
'label' : 'Category',
|
'label': 'Category',
|
||||||
}])
|
}])
|
||||||
|
|
||||||
result = fg.rss_str()
|
result = fg.rss_str()
|
||||||
assert b'domain="http://www.somedomain.com/category"' in result
|
assert b'domain="http://www.somedomain.com/category"' in result
|
||||||
|
|
||||||
def test_content_cdata_type(self):
|
|
||||||
fg = FeedGenerator()
|
|
||||||
fg.title('some title')
|
|
||||||
fg.id('http://lernfunk.de/media/654322/1')
|
|
||||||
fe = fg.add_entry()
|
|
||||||
fe.id('http://lernfunk.de/media/654322/1')
|
|
||||||
fe.title('some title')
|
|
||||||
fe.content('content', type='CDATA')
|
|
||||||
result = fg.atom_str()
|
|
||||||
assert b'<content type="CDATA"><![CDATA[content]]></content>' in result
|
|
||||||
|
|
||||||
|
def test_content_cdata_type(self):
|
||||||
|
fg = FeedGenerator()
|
||||||
|
fg.title('some title')
|
||||||
|
fg.id('http://lernfunk.de/media/654322/1')
|
||||||
|
fe = fg.add_entry()
|
||||||
|
fe.id('http://lernfunk.de/media/654322/1')
|
||||||
|
fe.title('some title')
|
||||||
|
fe.content('content', type='CDATA')
|
||||||
|
result = fg.atom_str()
|
||||||
|
assert b'<content type="CDATA"><![CDATA[content]]></content>' in result
|
||||||
|
|
|
@ -12,6 +12,8 @@ from lxml import etree
|
||||||
|
|
||||||
class TestExtensionSyndication(unittest.TestCase):
|
class TestExtensionSyndication(unittest.TestCase):
|
||||||
|
|
||||||
|
SYN_NS = {'sy': 'http://purl.org/rss/1.0/modules/syndication/'}
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.fg = FeedGenerator()
|
self.fg = FeedGenerator()
|
||||||
self.fg.load_extension('syndication')
|
self.fg.load_extension('syndication')
|
||||||
|
@ -20,14 +22,11 @@ class TestExtensionSyndication(unittest.TestCase):
|
||||||
self.fg.description('description')
|
self.fg.description('description')
|
||||||
|
|
||||||
def test_update_period(self):
|
def test_update_period(self):
|
||||||
for period_type in ('hourly', 'daily', 'weekly',
|
for period_type in ('hourly', 'daily', 'weekly', 'monthly', 'yearly'):
|
||||||
'monthly', 'yearly'):
|
|
||||||
self.fg.syndication.update_period(period_type)
|
self.fg.syndication.update_period(period_type)
|
||||||
root = etree.fromstring(self.fg.rss_str())
|
root = etree.fromstring(self.fg.rss_str())
|
||||||
a = root.xpath('/rss/channel/sy:UpdatePeriod',
|
a = root.xpath('/rss/channel/sy:UpdatePeriod',
|
||||||
namespaces={
|
namespaces=self.SYN_NS)
|
||||||
'sy':'http://purl.org/rss/1.0/modules/syndication/'
|
|
||||||
})
|
|
||||||
assert a[0].text == period_type
|
assert a[0].text == period_type
|
||||||
|
|
||||||
def test_update_frequency(self):
|
def test_update_frequency(self):
|
||||||
|
@ -35,19 +34,14 @@ class TestExtensionSyndication(unittest.TestCase):
|
||||||
self.fg.syndication.update_frequency(frequency)
|
self.fg.syndication.update_frequency(frequency)
|
||||||
root = etree.fromstring(self.fg.rss_str())
|
root = etree.fromstring(self.fg.rss_str())
|
||||||
a = root.xpath('/rss/channel/sy:UpdateFrequency',
|
a = root.xpath('/rss/channel/sy:UpdateFrequency',
|
||||||
namespaces={
|
namespaces=self.SYN_NS)
|
||||||
'sy':'http://purl.org/rss/1.0/modules/syndication/'
|
|
||||||
})
|
|
||||||
assert a[0].text == str(frequency)
|
assert a[0].text == str(frequency)
|
||||||
|
|
||||||
def test_update_base(self):
|
def test_update_base(self):
|
||||||
base = '2000-01-01T12:00+00:00'
|
base = '2000-01-01T12:00+00:00'
|
||||||
self.fg.syndication.update_base(base)
|
self.fg.syndication.update_base(base)
|
||||||
root = etree.fromstring(self.fg.rss_str())
|
root = etree.fromstring(self.fg.rss_str())
|
||||||
a = root.xpath('/rss/channel/sy:UpdateBase',
|
a = root.xpath('/rss/channel/sy:UpdateBase', namespaces=self.SYN_NS)
|
||||||
namespaces={
|
|
||||||
'sy':'http://purl.org/rss/1.0/modules/syndication/'
|
|
||||||
})
|
|
||||||
assert a[0].text == base
|
assert a[0].text == base
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,17 +55,17 @@ class TestExtensionPodcast(unittest.TestCase):
|
||||||
self.fg.description('description')
|
self.fg.description('description')
|
||||||
|
|
||||||
def test_category_new(self):
|
def test_category_new(self):
|
||||||
self.fg.podcast.itunes_category([{'cat':'Technology',
|
self.fg.podcast.itunes_category([{'cat': 'Technology',
|
||||||
'sub':'Podcasting'}])
|
'sub': 'Podcasting'}])
|
||||||
self.fg.podcast.itunes_explicit('no')
|
self.fg.podcast.itunes_explicit('no')
|
||||||
self.fg.podcast.itunes_complete('no')
|
self.fg.podcast.itunes_complete('no')
|
||||||
self.fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss')
|
self.fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss')
|
||||||
self.fg.podcast.itunes_owner('John Doe', 'john@example.com')
|
self.fg.podcast.itunes_owner('John Doe', 'john@example.com')
|
||||||
ns = {'itunes':'http://www.itunes.com/dtds/podcast-1.0.dtd'}
|
ns = {'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd'}
|
||||||
root = etree.fromstring(self.fg.rss_str())
|
root = etree.fromstring(self.fg.rss_str())
|
||||||
cat = root.xpath('/rss/channel/itunes:category/@text', namespaces=ns)
|
cat = root.xpath('/rss/channel/itunes:category/@text', namespaces=ns)
|
||||||
scat = root.xpath('/rss/channel/itunes:category/itunes:category/@text',
|
scat = root.xpath('/rss/channel/itunes:category/itunes:category/@text',
|
||||||
namespaces=ns)
|
namespaces=ns)
|
||||||
assert cat[0] == 'Technology'
|
assert cat[0] == 'Technology'
|
||||||
assert scat[0] == 'Podcasting'
|
assert scat[0] == 'Podcasting'
|
||||||
|
|
||||||
|
@ -81,10 +75,10 @@ class TestExtensionPodcast(unittest.TestCase):
|
||||||
self.fg.podcast.itunes_complete('no')
|
self.fg.podcast.itunes_complete('no')
|
||||||
self.fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss')
|
self.fg.podcast.itunes_new_feed_url('http://example.com/new-feed.rss')
|
||||||
self.fg.podcast.itunes_owner('John Doe', 'john@example.com')
|
self.fg.podcast.itunes_owner('John Doe', 'john@example.com')
|
||||||
ns = {'itunes':'http://www.itunes.com/dtds/podcast-1.0.dtd'}
|
ns = {'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd'}
|
||||||
root = etree.fromstring(self.fg.rss_str())
|
root = etree.fromstring(self.fg.rss_str())
|
||||||
cat = root.xpath('/rss/channel/itunes:category/@text', namespaces=ns)
|
cat = root.xpath('/rss/channel/itunes:category/@text', namespaces=ns)
|
||||||
scat = root.xpath('/rss/channel/itunes:category/itunes:category/@text',
|
scat = root.xpath('/rss/channel/itunes:category/itunes:category/@text',
|
||||||
namespaces=ns)
|
namespaces=ns)
|
||||||
assert cat[0] == 'Technology'
|
assert cat[0] == 'Technology'
|
||||||
assert scat[0] == 'Podcasting'
|
assert scat[0] == 'Podcasting'
|
||||||
|
|
|
@ -12,288 +12,314 @@ from lxml import etree
|
||||||
from feedgen.feed import FeedGenerator
|
from feedgen.feed import FeedGenerator
|
||||||
from feedgen.ext.dc import DcExtension, DcEntryExtension
|
from feedgen.ext.dc import DcExtension, DcEntryExtension
|
||||||
|
|
||||||
|
|
||||||
class TestSequenceFunctions(unittest.TestCase):
|
class TestSequenceFunctions(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
||||||
fg = FeedGenerator()
|
fg = FeedGenerator()
|
||||||
|
|
||||||
self.nsAtom = "http://www.w3.org/2005/Atom"
|
self.nsAtom = "http://www.w3.org/2005/Atom"
|
||||||
self.nsRss = "http://purl.org/rss/1.0/modules/content/"
|
self.nsRss = "http://purl.org/rss/1.0/modules/content/"
|
||||||
|
|
||||||
self.feedId = 'http://lernfunk.de/media/654321'
|
self.feedId = 'http://lernfunk.de/media/654321'
|
||||||
self.title = 'Some Testfeed'
|
self.title = 'Some Testfeed'
|
||||||
|
|
||||||
self.authorName = 'John Doe'
|
self.authorName = 'John Doe'
|
||||||
self.authorMail = 'john@example.de'
|
self.authorMail = 'john@example.de'
|
||||||
self.author = {'name': self.authorName,'email': self.authorMail}
|
self.author = {'name': self.authorName, 'email': self.authorMail}
|
||||||
|
|
||||||
self.linkHref = 'http://example.com'
|
self.linkHref = 'http://example.com'
|
||||||
self.linkRel = 'alternate'
|
self.linkRel = 'alternate'
|
||||||
|
|
||||||
self.logo = 'http://ex.com/logo.jpg'
|
self.logo = 'http://ex.com/logo.jpg'
|
||||||
self.subtitle = 'This is a cool feed!'
|
self.subtitle = 'This is a cool feed!'
|
||||||
|
|
||||||
self.link2Href = 'http://larskiesow.de/test.atom'
|
self.link2Href = 'http://larskiesow.de/test.atom'
|
||||||
self.link2Rel = 'self'
|
self.link2Rel = 'self'
|
||||||
|
|
||||||
self.language = 'en'
|
self.language = 'en'
|
||||||
|
|
||||||
self.categoryTerm = 'This category term'
|
self.categoryTerm = 'This category term'
|
||||||
self.categoryScheme = 'This category scheme'
|
self.categoryScheme = 'This category scheme'
|
||||||
self.categoryLabel = 'This category label'
|
self.categoryLabel = 'This category label'
|
||||||
|
|
||||||
self.cloudDomain = 'example.com'
|
self.cloudDomain = 'example.com'
|
||||||
self.cloudPort = '4711'
|
self.cloudPort = '4711'
|
||||||
self.cloudPath = '/ws/example'
|
self.cloudPath = '/ws/example'
|
||||||
self.cloudRegisterProcedure = 'registerProcedure'
|
self.cloudRegisterProcedure = 'registerProcedure'
|
||||||
self.cloudProtocol = 'SOAP 1.1'
|
self.cloudProtocol = 'SOAP 1.1'
|
||||||
|
|
||||||
self.icon = "http://example.com/icon.png"
|
self.icon = "http://example.com/icon.png"
|
||||||
self.contributor = {'name':"Contributor Name", 'uri':"Contributor Uri",
|
self.contributor = {'name': "Contributor Name",
|
||||||
'email': 'Contributor email'}
|
'uri': "Contributor Uri",
|
||||||
self.copyright = "The copyright notice"
|
'email': 'Contributor email'}
|
||||||
self.docs = 'http://www.rssboard.org/rss-specification'
|
self.copyright = "The copyright notice"
|
||||||
self.managingEditor = 'mail@example.com'
|
self.docs = 'http://www.rssboard.org/rss-specification'
|
||||||
self.rating = '(PICS-1.1 "http://www.classify.org/safesurf/" 1 r (SS~~000 1))'
|
self.managingEditor = 'mail@example.com'
|
||||||
self.skipDays = 'Tuesday'
|
self.rating = '(PICS-1.1 "http://www.classify.org/safesurf/" ' + \
|
||||||
self.skipHours = 23
|
'1 r (SS~~000 1))'
|
||||||
|
self.skipDays = 'Tuesday'
|
||||||
|
self.skipHours = 23
|
||||||
|
|
||||||
self.textInputTitle = "Text input title"
|
self.textInputTitle = "Text input title"
|
||||||
self.textInputDescription = "Text input description"
|
self.textInputDescription = "Text input description"
|
||||||
self.textInputName = "Text input name"
|
self.textInputName = "Text input name"
|
||||||
self.textInputLink = "Text input link"
|
self.textInputLink = "Text input link"
|
||||||
|
|
||||||
self.ttl = 900
|
self.ttl = 900
|
||||||
|
|
||||||
self.webMaster = 'webmaster@example.com'
|
self.webMaster = 'webmaster@example.com'
|
||||||
|
|
||||||
fg.id(self.feedId)
|
fg.id(self.feedId)
|
||||||
fg.title(self.title)
|
fg.title(self.title)
|
||||||
fg.author(self.author)
|
fg.author(self.author)
|
||||||
fg.link( href=self.linkHref, rel=self.linkRel )
|
fg.link(href=self.linkHref, rel=self.linkRel)
|
||||||
fg.logo(self.logo)
|
fg.logo(self.logo)
|
||||||
fg.subtitle(self.subtitle)
|
fg.subtitle(self.subtitle)
|
||||||
fg.link( href=self.link2Href, rel=self.link2Rel )
|
fg.link(href=self.link2Href, rel=self.link2Rel)
|
||||||
fg.language(self.language)
|
fg.language(self.language)
|
||||||
fg.cloud(domain=self.cloudDomain, port=self.cloudPort,
|
fg.cloud(domain=self.cloudDomain, port=self.cloudPort,
|
||||||
path=self.cloudPath, registerProcedure=self.cloudRegisterProcedure,
|
path=self.cloudPath,
|
||||||
protocol=self.cloudProtocol)
|
registerProcedure=self.cloudRegisterProcedure,
|
||||||
fg.icon(self.icon)
|
protocol=self.cloudProtocol)
|
||||||
fg.category(term=self.categoryTerm, scheme=self.categoryScheme,
|
fg.icon(self.icon)
|
||||||
label=self.categoryLabel)
|
fg.category(term=self.categoryTerm, scheme=self.categoryScheme,
|
||||||
fg.contributor(self.contributor)
|
label=self.categoryLabel)
|
||||||
fg.copyright(self.copyright)
|
fg.contributor(self.contributor)
|
||||||
fg.docs(docs=self.docs)
|
fg.copyright(self.copyright)
|
||||||
fg.managingEditor(self.managingEditor)
|
fg.docs(docs=self.docs)
|
||||||
fg.rating(self.rating)
|
fg.managingEditor(self.managingEditor)
|
||||||
fg.skipDays(self.skipDays)
|
fg.rating(self.rating)
|
||||||
fg.skipHours(self.skipHours)
|
fg.skipDays(self.skipDays)
|
||||||
fg.textInput(title=self.textInputTitle,
|
fg.skipHours(self.skipHours)
|
||||||
description=self.textInputDescription, name=self.textInputName,
|
fg.textInput(title=self.textInputTitle,
|
||||||
link=self.textInputLink)
|
description=self.textInputDescription,
|
||||||
fg.ttl(self.ttl)
|
name=self.textInputName, link=self.textInputLink)
|
||||||
fg.webMaster(self.webMaster)
|
fg.ttl(self.ttl)
|
||||||
|
fg.webMaster(self.webMaster)
|
||||||
|
|
||||||
self.fg = fg
|
self.fg = fg
|
||||||
|
|
||||||
|
def test_baseFeed(self):
|
||||||
|
fg = self.fg
|
||||||
|
|
||||||
def test_baseFeed(self):
|
assert fg.id() == self.feedId
|
||||||
fg = self.fg
|
assert fg.title() == self.title
|
||||||
|
|
||||||
assert fg.id() == self.feedId
|
assert fg.author()[0]['name'] == self.authorName
|
||||||
assert fg.title() == self.title
|
assert fg.author()[0]['email'] == self.authorMail
|
||||||
|
|
||||||
assert fg.author()[0]['name'] == self.authorName
|
assert fg.link()[0]['href'] == self.linkHref
|
||||||
assert fg.author()[0]['email'] == self.authorMail
|
assert fg.link()[0]['rel'] == self.linkRel
|
||||||
|
|
||||||
assert fg.link()[0]['href'] == self.linkHref
|
assert fg.logo() == self.logo
|
||||||
assert fg.link()[0]['rel'] == self.linkRel
|
assert fg.subtitle() == self.subtitle
|
||||||
|
|
||||||
assert fg.logo() == self.logo
|
assert fg.link()[1]['href'] == self.link2Href
|
||||||
assert fg.subtitle() == self.subtitle
|
assert fg.link()[1]['rel'] == self.link2Rel
|
||||||
|
|
||||||
assert fg.link()[1]['href'] == self.link2Href
|
assert fg.language() == self.language
|
||||||
assert fg.link()[1]['rel'] == self.link2Rel
|
|
||||||
|
|
||||||
assert fg.language() == self.language
|
def test_atomFeedFile(self):
|
||||||
|
fg = self.fg
|
||||||
|
filename = 'tmp_Atomfeed.xml'
|
||||||
|
fg.atom_file(filename=filename, pretty=True, xml_declaration=False)
|
||||||
|
|
||||||
def test_atomFeedFile(self):
|
with open(filename, "r") as myfile:
|
||||||
fg = self.fg
|
atomString = myfile.read().replace('\n', '')
|
||||||
filename = 'tmp_Atomfeed.xml'
|
|
||||||
fg.atom_file(filename=filename, pretty=True, xml_declaration=False)
|
|
||||||
|
|
||||||
with open (filename, "r") as myfile:
|
self.checkAtomString(atomString)
|
||||||
atomString=myfile.read().replace('\n', '')
|
|
||||||
|
|
||||||
self.checkAtomString(atomString)
|
def test_atomFeedString(self):
|
||||||
|
fg = self.fg
|
||||||
|
|
||||||
def test_atomFeedString(self):
|
atomString = fg.atom_str(pretty=True, xml_declaration=False)
|
||||||
fg = self.fg
|
self.checkAtomString(atomString)
|
||||||
|
|
||||||
atomString = fg.atom_str(pretty=True, xml_declaration=False)
|
def test_rel_values_for_atom(self):
|
||||||
self.checkAtomString(atomString)
|
values_for_rel = [
|
||||||
|
'about', 'alternate', 'appendix', 'archives', 'author', 'bookmark',
|
||||||
|
'canonical', 'chapter', 'collection', 'contents', 'copyright',
|
||||||
|
'create-form', 'current', 'derivedfrom', 'describedby',
|
||||||
|
'describes', 'disclosure', 'duplicate', 'edit', 'edit-form',
|
||||||
|
'edit-media', 'enclosure', 'first', 'glossary', 'help', 'hosts',
|
||||||
|
'hub', 'icon', 'index', 'item', 'last', 'latest-version',
|
||||||
|
'license', 'lrdd', 'memento', 'monitor', 'monitor-group', 'next',
|
||||||
|
'next-archive', 'nofollow', 'noreferrer', 'original', 'payment',
|
||||||
|
'predecessor-version', 'prefetch', 'prev', 'preview', 'previous',
|
||||||
|
'prev-archive', 'privacy-policy', 'profile', 'related', 'replies',
|
||||||
|
'search', 'section', 'self', 'service', 'start', 'stylesheet',
|
||||||
|
'subsection', 'successor-version', 'tag', 'terms-of-service',
|
||||||
|
'timegate', 'timemap', 'type', 'up', 'version-history', 'via',
|
||||||
|
'working-copy', 'working-copy-of']
|
||||||
|
links = [{'href': '%s/%s' % (self.linkHref,
|
||||||
|
val.replace('-', '_')), 'rel': val}
|
||||||
|
for val in values_for_rel]
|
||||||
|
fg = self.fg
|
||||||
|
fg.link(links, replace=True)
|
||||||
|
atomString = fg.atom_str(pretty=True, xml_declaration=False)
|
||||||
|
feed = etree.fromstring(atomString)
|
||||||
|
nsAtom = self.nsAtom
|
||||||
|
feed_links = feed.findall("{%s}link" % nsAtom)
|
||||||
|
idx = 0
|
||||||
|
assert len(links) == len(feed_links)
|
||||||
|
while idx < len(values_for_rel):
|
||||||
|
assert feed_links[idx].get('href') == links[idx]['href']
|
||||||
|
assert feed_links[idx].get('rel') == links[idx]['rel']
|
||||||
|
idx += 1
|
||||||
|
|
||||||
def test_rel_values_for_atom(self):
|
def test_rel_values_for_rss(self):
|
||||||
values_for_rel = [
|
values_for_rel = [
|
||||||
'about', 'alternate', 'appendix', 'archives', 'author', 'bookmark',
|
'about', 'alternate', 'appendix', 'archives', 'author', 'bookmark',
|
||||||
'canonical', 'chapter', 'collection', 'contents', 'copyright', 'create-form',
|
'canonical', 'chapter', 'collection', 'contents', 'copyright',
|
||||||
'current', 'derivedfrom', 'describedby', 'describes', 'disclosure',
|
'create-form', 'current', 'derivedfrom', 'describedby',
|
||||||
'duplicate', 'edit', 'edit-form', 'edit-media', 'enclosure', 'first', 'glossary',
|
'describes', 'disclosure', 'duplicate', 'edit', 'edit-form',
|
||||||
'help', 'hosts', 'hub', 'icon', 'index', 'item', 'last', 'latest-version', 'license',
|
'edit-media', 'enclosure', 'first', 'glossary', 'help', 'hosts',
|
||||||
'lrdd', 'memento', 'monitor', 'monitor-group', 'next', 'next-archive', 'nofollow',
|
'hub', 'icon', 'index', 'item', 'last', 'latest-version',
|
||||||
'noreferrer', 'original', 'payment', 'predecessor-version', 'prefetch', 'prev', 'preview',
|
'license', 'lrdd', 'memento', 'monitor', 'monitor-group', 'next',
|
||||||
'previous', 'prev-archive', 'privacy-policy', 'profile', 'related', 'replies', 'search',
|
'next-archive', 'nofollow', 'noreferrer', 'original', 'payment',
|
||||||
'section', 'self', 'service', 'start', 'stylesheet', 'subsection', 'successor-version',
|
'predecessor-version', 'prefetch', 'prev', 'preview', 'previous',
|
||||||
'tag', 'terms-of-service', 'timegate', 'timemap', 'type', 'up', 'version-history', 'via',
|
'prev-archive', 'privacy-policy', 'profile', 'related', 'replies',
|
||||||
'working-copy', 'working-copy-of'
|
'search', 'section', 'self', 'service', 'start', 'stylesheet',
|
||||||
]
|
'subsection', 'successor-version', 'tag', 'terms-of-service',
|
||||||
links = [{'href': '%s/%s' % (self.linkHref, val.replace('-', '_')), 'rel': val} for val in values_for_rel]
|
'timegate', 'timemap', 'type', 'up', 'version-history', 'via',
|
||||||
fg = self.fg
|
'working-copy', 'working-copy-of']
|
||||||
fg.link(links, replace=True)
|
links = [{'href': '%s/%s' % (self.linkHref,
|
||||||
atomString = fg.atom_str(pretty=True, xml_declaration=False)
|
val.replace('-', '_')), 'rel': val}
|
||||||
feed = etree.fromstring(atomString)
|
for val in values_for_rel]
|
||||||
nsAtom = self.nsAtom
|
fg = self.fg
|
||||||
feed_links = feed.findall("{%s}link" % nsAtom)
|
fg.link(links, replace=True)
|
||||||
idx = 0
|
rssString = fg.rss_str(pretty=True, xml_declaration=False)
|
||||||
assert len(links) == len(feed_links)
|
feed = etree.fromstring(rssString)
|
||||||
while idx < len(values_for_rel):
|
channel = feed.find("channel")
|
||||||
assert feed_links[idx].get('href') == links[idx]['href']
|
nsAtom = self.nsAtom
|
||||||
assert feed_links[idx].get('rel') == links[idx]['rel']
|
|
||||||
idx += 1
|
|
||||||
|
|
||||||
def test_rel_values_for_rss(self):
|
atom_links = channel.findall("{%s}link" % nsAtom)
|
||||||
values_for_rel = [
|
# rss feed only implements atom's 'self' link
|
||||||
'about', 'alternate', 'appendix', 'archives', 'author', 'bookmark',
|
assert len(atom_links) == 1
|
||||||
'canonical', 'chapter', 'collection', 'contents', 'copyright', 'create-form',
|
assert atom_links[0].get('href') == '%s/%s' % (self.linkHref, 'self')
|
||||||
'current', 'derivedfrom', 'describedby', 'describes', 'disclosure',
|
assert atom_links[0].get('rel') == 'self'
|
||||||
'duplicate', 'edit', 'edit-form', 'edit-media', 'enclosure', 'first', 'glossary',
|
|
||||||
'help', 'hosts', 'hub', 'icon', 'index', 'item', 'last', 'latest-version', 'license',
|
|
||||||
'lrdd', 'memento', 'monitor', 'monitor-group', 'next', 'next-archive', 'nofollow',
|
|
||||||
'noreferrer', 'original', 'payment', 'predecessor-version', 'prefetch', 'prev', 'preview',
|
|
||||||
'previous', 'prev-archive', 'privacy-policy', 'profile', 'related', 'replies', 'search',
|
|
||||||
'section', 'self', 'service', 'start', 'stylesheet', 'subsection', 'successor-version',
|
|
||||||
'tag', 'terms-of-service', 'timegate', 'timemap', 'type', 'up', 'version-history', 'via',
|
|
||||||
'working-copy', 'working-copy-of'
|
|
||||||
]
|
|
||||||
links = [{'href': '%s/%s' % (self.linkHref, val.replace('-', '_')), 'rel': val} for val in values_for_rel]
|
|
||||||
fg = self.fg
|
|
||||||
fg.link(links, replace=True)
|
|
||||||
rssString = fg.rss_str(pretty=True, xml_declaration=False)
|
|
||||||
feed = etree.fromstring(rssString)
|
|
||||||
channel = feed.find("channel")
|
|
||||||
nsAtom = self.nsAtom
|
|
||||||
|
|
||||||
atom_links = channel.findall("{%s}link" % nsAtom)
|
rss_links = channel.findall('link')
|
||||||
assert len(atom_links) == 1 # rss feed only implements atom's 'self' link
|
# RSS only needs one URL. We use the first link for RSS:
|
||||||
assert atom_links[0].get('href') == '%s/%s' % (self.linkHref, 'self')
|
assert len(rss_links) == 1
|
||||||
assert atom_links[0].get('rel') == 'self'
|
assert rss_links[0].text == '%s/%s' % \
|
||||||
|
(self.linkHref, 'working-copy-of'.replace('-', '_'))
|
||||||
|
|
||||||
rss_links = channel.findall('link')
|
def checkAtomString(self, atomString):
|
||||||
assert len(rss_links) == 1 # RSS only needs one URL. We use the first link for RSS:
|
|
||||||
assert rss_links[0].text == '%s/%s' % (self.linkHref, 'working-copy-of'.replace('-', '_'))
|
|
||||||
|
|
||||||
def checkAtomString(self, atomString):
|
feed = etree.fromstring(atomString)
|
||||||
|
|
||||||
feed = etree.fromstring(atomString)
|
nsAtom = self.nsAtom
|
||||||
|
|
||||||
nsAtom = self.nsAtom
|
assert feed.find("{%s}title" % nsAtom).text == self.title
|
||||||
|
assert feed.find("{%s}updated" % nsAtom).text is not None
|
||||||
|
assert feed.find("{%s}id" % nsAtom).text == self.feedId
|
||||||
|
assert feed.find("{%s}category" % nsAtom)\
|
||||||
|
.get('term') == self.categoryTerm
|
||||||
|
assert feed.find("{%s}category" % nsAtom)\
|
||||||
|
.get('label') == self.categoryLabel
|
||||||
|
assert feed.find("{%s}author" % nsAtom)\
|
||||||
|
.find("{%s}name" % nsAtom).text == self.authorName
|
||||||
|
assert feed.find("{%s}author" % nsAtom)\
|
||||||
|
.find("{%s}email" % nsAtom).text == self.authorMail
|
||||||
|
assert feed.findall("{%s}link" % nsAtom)[0]\
|
||||||
|
.get('href') == self.linkHref
|
||||||
|
assert feed.findall("{%s}link" % nsAtom)[0].get('rel') == self.linkRel
|
||||||
|
assert feed.findall("{%s}link" % nsAtom)[1]\
|
||||||
|
.get('href') == self.link2Href
|
||||||
|
assert feed.findall("{%s}link" % nsAtom)[1].get('rel') == self.link2Rel
|
||||||
|
assert feed.find("{%s}logo" % nsAtom).text == self.logo
|
||||||
|
assert feed.find("{%s}icon" % nsAtom).text == self.icon
|
||||||
|
assert feed.find("{%s}subtitle" % nsAtom).text == self.subtitle
|
||||||
|
assert feed.find("{%s}contributor" % nsAtom)\
|
||||||
|
.find("{%s}name" % nsAtom).text == self.contributor['name']
|
||||||
|
assert feed.find("{%s}contributor" % nsAtom)\
|
||||||
|
.find("{%s}email" % nsAtom).text == self.contributor['email']
|
||||||
|
assert feed.find("{%s}contributor" % nsAtom)\
|
||||||
|
.find("{%s}uri" % nsAtom).text == self.contributor['uri']
|
||||||
|
assert feed.find("{%s}rights" % nsAtom).text == self.copyright
|
||||||
|
|
||||||
assert feed.find("{%s}title" % nsAtom).text == self.title
|
def test_rssFeedFile(self):
|
||||||
assert feed.find("{%s}updated" % nsAtom).text != None
|
fg = self.fg
|
||||||
assert feed.find("{%s}id" % nsAtom).text == self.feedId
|
filename = 'tmp_Rssfeed.xml'
|
||||||
assert feed.find("{%s}category" % nsAtom).get('term') == self.categoryTerm
|
fg.rss_file(filename=filename, pretty=True, xml_declaration=False)
|
||||||
assert feed.find("{%s}category" % nsAtom).get('label') == self.categoryLabel
|
|
||||||
assert feed.find("{%s}author" % nsAtom).find("{%s}name" % nsAtom).text == self.authorName
|
|
||||||
assert feed.find("{%s}author" % nsAtom).find("{%s}email" % nsAtom).text == self.authorMail
|
|
||||||
assert feed.findall("{%s}link" % nsAtom)[0].get('href') == self.linkHref
|
|
||||||
assert feed.findall("{%s}link" % nsAtom)[0].get('rel') == self.linkRel
|
|
||||||
assert feed.findall("{%s}link" % nsAtom)[1].get('href') == self.link2Href
|
|
||||||
assert feed.findall("{%s}link" % nsAtom)[1].get('rel') == self.link2Rel
|
|
||||||
assert feed.find("{%s}logo" % nsAtom).text == self.logo
|
|
||||||
assert feed.find("{%s}icon" % nsAtom).text == self.icon
|
|
||||||
assert feed.find("{%s}subtitle" % nsAtom).text == self.subtitle
|
|
||||||
assert feed.find("{%s}contributor" % nsAtom).find("{%s}name" % nsAtom).text == self.contributor['name']
|
|
||||||
assert feed.find("{%s}contributor" % nsAtom).find("{%s}email" % nsAtom).text == self.contributor['email']
|
|
||||||
assert feed.find("{%s}contributor" % nsAtom).find("{%s}uri" % nsAtom).text == self.contributor['uri']
|
|
||||||
assert feed.find("{%s}rights" % nsAtom).text == self.copyright
|
|
||||||
|
|
||||||
def test_rssFeedFile(self):
|
with open(filename, "r") as myfile:
|
||||||
fg = self.fg
|
rssString = myfile.read().replace('\n', '')
|
||||||
filename = 'tmp_Rssfeed.xml'
|
|
||||||
fg.rss_file(filename=filename, pretty=True, xml_declaration=False)
|
|
||||||
|
|
||||||
with open (filename, "r") as myfile:
|
self.checkRssString(rssString)
|
||||||
rssString=myfile.read().replace('\n', '')
|
|
||||||
|
|
||||||
self.checkRssString(rssString)
|
def test_rssFeedString(self):
|
||||||
|
fg = self.fg
|
||||||
|
rssString = fg.rss_str(pretty=True, xml_declaration=False)
|
||||||
|
self.checkRssString(rssString)
|
||||||
|
|
||||||
def test_rssFeedString(self):
|
def test_loadPodcastExtension(self):
|
||||||
fg = self.fg
|
fg = self.fg
|
||||||
rssString = fg.rss_str(pretty=True, xml_declaration=False)
|
fg.add_entry()
|
||||||
self.checkRssString(rssString)
|
fg.load_extension('podcast', atom=True, rss=True)
|
||||||
|
fg.add_entry()
|
||||||
|
|
||||||
def test_loadPodcastExtension(self):
|
def test_loadDcExtension(self):
|
||||||
fg = self.fg
|
fg = self.fg
|
||||||
fg.add_entry()
|
fg.add_entry()
|
||||||
fg.load_extension('podcast', atom=True, rss=True)
|
fg.load_extension('dc', atom=True, rss=True)
|
||||||
fg.add_entry()
|
fg.add_entry()
|
||||||
|
|
||||||
def test_loadDcExtension(self):
|
def test_extensionAlreadyLoaded(self):
|
||||||
fg = self.fg
|
fg = self.fg
|
||||||
fg.add_entry()
|
fg.load_extension('dc', atom=True, rss=True)
|
||||||
fg.load_extension('dc', atom=True, rss=True)
|
with self.assertRaises(ImportError):
|
||||||
fg.add_entry()
|
fg.load_extension('dc')
|
||||||
|
|
||||||
def test_extensionAlreadyLoaded(self):
|
def test_registerCustomExtension(self):
|
||||||
fg = self.fg
|
fg = self.fg
|
||||||
fg.load_extension('dc', atom=True, rss=True)
|
fg.add_entry()
|
||||||
with self.assertRaises(ImportError) as context:
|
fg.register_extension('dc', DcExtension, DcEntryExtension)
|
||||||
fg.load_extension('dc')
|
fg.add_entry()
|
||||||
|
|
||||||
def test_registerCustomExtension(self):
|
def checkRssString(self, rssString):
|
||||||
fg = self.fg
|
|
||||||
fg.add_entry()
|
|
||||||
fg.register_extension('dc', DcExtension, DcEntryExtension)
|
|
||||||
fg.add_entry()
|
|
||||||
|
|
||||||
def checkRssString(self, rssString):
|
feed = etree.fromstring(rssString)
|
||||||
|
nsAtom = self.nsAtom
|
||||||
|
|
||||||
feed = etree.fromstring(rssString)
|
ch = feed.find("channel")
|
||||||
nsAtom = self.nsAtom
|
assert ch is not None
|
||||||
nsRss = self.nsRss
|
|
||||||
|
|
||||||
channel = feed.find("channel")
|
assert ch.find("title").text == self.title
|
||||||
assert channel != None
|
assert ch.find("description").text == self.subtitle
|
||||||
|
assert ch.find("lastBuildDate").text is not None
|
||||||
|
docs = "http://www.rssboard.org/rss-specification"
|
||||||
|
assert ch.find("docs").text == docs
|
||||||
|
assert ch.find("generator").text == "python-feedgen"
|
||||||
|
assert ch.findall("{%s}link" % nsAtom)[0].get('href') == self.link2Href
|
||||||
|
assert ch.findall("{%s}link" % nsAtom)[0].get('rel') == self.link2Rel
|
||||||
|
assert ch.find("image").find("url").text == self.logo
|
||||||
|
assert ch.find("image").find("title").text == self.title
|
||||||
|
assert ch.find("image").find("link").text == self.link2Href
|
||||||
|
assert ch.find("category").text == self.categoryLabel
|
||||||
|
assert ch.find("cloud").get('domain') == self.cloudDomain
|
||||||
|
assert ch.find("cloud").get('port') == self.cloudPort
|
||||||
|
assert ch.find("cloud").get('path') == self.cloudPath
|
||||||
|
assert ch.find("cloud").get('registerProcedure') == \
|
||||||
|
self.cloudRegisterProcedure
|
||||||
|
assert ch.find("cloud").get('protocol') == self.cloudProtocol
|
||||||
|
assert ch.find("copyright").text == self.copyright
|
||||||
|
assert ch.find("docs").text == self.docs
|
||||||
|
assert ch.find("managingEditor").text == self.managingEditor
|
||||||
|
assert ch.find("rating").text == self.rating
|
||||||
|
assert ch.find("skipDays").find("day").text == self.skipDays
|
||||||
|
assert int(ch.find("skipHours").find("hour").text) == self.skipHours
|
||||||
|
assert ch.find("textInput").get('title') == self.textInputTitle
|
||||||
|
assert ch.find("textInput").get('description') == \
|
||||||
|
self.textInputDescription
|
||||||
|
assert ch.find("textInput").get('name') == self.textInputName
|
||||||
|
assert ch.find("textInput").get('link') == self.textInputLink
|
||||||
|
assert int(ch.find("ttl").text) == self.ttl
|
||||||
|
assert ch.find("webMaster").text == self.webMaster
|
||||||
|
|
||||||
assert channel.find("title").text == self.title
|
|
||||||
assert channel.find("description").text == self.subtitle
|
|
||||||
assert channel.find("lastBuildDate").text != None
|
|
||||||
assert channel.find("docs").text == "http://www.rssboard.org/rss-specification"
|
|
||||||
assert channel.find("generator").text == "python-feedgen"
|
|
||||||
assert channel.findall("{%s}link" % nsAtom)[0].get('href') == self.link2Href
|
|
||||||
assert channel.findall("{%s}link" % nsAtom)[0].get('rel') == self.link2Rel
|
|
||||||
assert channel.find("image").find("url").text == self.logo
|
|
||||||
assert channel.find("image").find("title").text == self.title
|
|
||||||
assert channel.find("image").find("link").text == self.link2Href
|
|
||||||
assert channel.find("category").text == self.categoryLabel
|
|
||||||
assert channel.find("cloud").get('domain') == self.cloudDomain
|
|
||||||
assert channel.find("cloud").get('port') == self.cloudPort
|
|
||||||
assert channel.find("cloud").get('path') == self.cloudPath
|
|
||||||
assert channel.find("cloud").get('registerProcedure') == self.cloudRegisterProcedure
|
|
||||||
assert channel.find("cloud").get('protocol') == self.cloudProtocol
|
|
||||||
assert channel.find("copyright").text == self.copyright
|
|
||||||
assert channel.find("docs").text == self.docs
|
|
||||||
assert channel.find("managingEditor").text == self.managingEditor
|
|
||||||
assert channel.find("rating").text == self.rating
|
|
||||||
assert channel.find("skipDays").find("day").text == self.skipDays
|
|
||||||
assert int(channel.find("skipHours").find("hour").text) == self.skipHours
|
|
||||||
assert channel.find("textInput").get('title') == self.textInputTitle
|
|
||||||
assert channel.find("textInput").get('description') == self.textInputDescription
|
|
||||||
assert channel.find("textInput").get('name') == self.textInputName
|
|
||||||
assert channel.find("textInput").get('link') == self.textInputLink
|
|
||||||
assert int(channel.find("ttl").text) == self.ttl
|
|
||||||
assert channel.find("webMaster").text == self.webMaster
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
Loading…
Reference in a new issue