← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~sam92/openlp/worshipassistant-import into lp:openlp

 

Samuel Mehrbrodt has proposed merging lp:~sam92/openlp/worshipassistant-import into lp:openlp.

Requested reviews:
  Tomas Groth (tomasgroth)
  Tim Bentley (trb143)
Related bugs:
  Bug #1334130 in OpenLP: "Import from Worship Assistant"
  https://bugs.launchpad.net/openlp/+bug/1334130

For more details, see:
https://code.launchpad.net/~sam92/openlp/worshipassistant-import/+merge/224956

Add an importer for Worship Assistant

lp:~sam92/openlp/worshipassistant-import (revision 2404)
[SUCCESS] http://ci.openlp.org/job/Branch-01-Pull/490/
[SUCCESS] http://ci.openlp.org/job/Branch-02-Functional-Tests/446/
[SUCCESS] http://ci.openlp.org/job/Branch-03-Interface-Tests/391/
[SUCCESS] http://ci.openlp.org/job/Branch-04-Windows_Tests/351/
[SUCCESS] http://ci.openlp.org/job/Branch-05a-Code_Analysis/236/
[SUCCESS] http://ci.openlp.org/job/Branch-05b-Test_Coverage/110/
-- 
https://code.launchpad.net/~sam92/openlp/worshipassistant-import/+merge/224956
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/plugins/bibles/lib/http.py'
--- openlp/plugins/bibles/lib/http.py	2014-05-02 06:42:17 +0000
+++ openlp/plugins/bibles/lib/http.py	2014-06-30 07:21:09 +0000
@@ -225,7 +225,7 @@
         url_book_name = urllib.parse.quote(book_name.encode("utf-8"))
         url_params = 'search=%s+%s&version=%s' % (url_book_name, chapter, version)
         soup = get_soup_for_bible_ref(
-            'http://www.biblegateway.com/passage/?%s' % url_params,
+            'http://legacy.biblegateway.com/passage/?%s' % url_params,
             pre_parse_regex=r'<meta name.*?/>', pre_parse_substitute='')
         if not soup:
             return None
@@ -252,7 +252,7 @@
         """
         log.debug('BGExtract.get_books_from_http("%s")', version)
         url_params = urllib.parse.urlencode({'action': 'getVersionInfo', 'vid': '%s' % version})
-        reference_url = 'http://www.biblegateway.com/versions/?%s#books' % url_params
+        reference_url = 'http://legacy.biblegateway.com/versions/?%s#books' % url_params
         page = get_web_page(reference_url)
         if not page:
             send_error_message('download')

=== modified file 'openlp/plugins/songs/lib/cclifileimport.py'
--- openlp/plugins/songs/lib/cclifileimport.py	2014-05-21 14:47:44 +0000
+++ openlp/plugins/songs/lib/cclifileimport.py	2014-06-30 07:21:09 +0000
@@ -64,7 +64,7 @@
             filename = str(filename)
             log.debug('Importing CCLI File: %s', filename)
             if os.path.isfile(filename):
-                detect_file = open(filename, 'r')
+                detect_file = open(filename, 'rb')
                 detect_content = detect_file.read(2048)
                 try:
                     str(detect_content, 'utf-8')

=== modified file 'openlp/plugins/songs/lib/importer.py'
--- openlp/plugins/songs/lib/importer.py	2014-06-09 10:27:17 +0000
+++ openlp/plugins/songs/lib/importer.py	2014-06-30 07:21:09 +0000
@@ -50,6 +50,7 @@
 from .foilpresenterimport import FoilPresenterImport
 from .zionworximport import ZionWorxImport
 from .propresenterimport import ProPresenterImport
+from .worshipassistantimport import WorshipAssistantImport
 # Imports that might fail
 
 
@@ -167,8 +168,9 @@
     SongsOfFellowship = 16
     SundayPlus = 17
     WordsOfWorship = 18
-    WorshipCenterPro = 19
-    ZionWorx = 20
+    WorshipAssistant = 19
+    WorshipCenterPro = 20
+    ZionWorx = 21
 
     # Set optional attribute defaults
     __defaults__ = {
@@ -321,6 +323,16 @@
             'prefix': 'wordsOfWorship',
             'filter': '%s (*.wsg *.wow-song)' % translate('SongsPlugin.ImportWizardForm', 'Words Of Worship Song Files')
         },
+        WorshipAssistant: {
+            'class': WorshipAssistantImport,
+            'name': 'Worship Assistant 0',
+            'prefix': 'worshipAssistant',
+            'selectMode': SongFormatSelect.SingleFile,
+            'filter': '%s (*.csv)' % translate('SongsPlugin.ImportWizardForm', 'Worship Assistant Files'),
+            'comboBoxText': translate('SongsPlugin.ImportWizardForm', 'Worship Assistant (CSV)'),
+            'descriptionText': translate('SongsPlugin.ImportWizardForm',
+                                         'In Worship Assistant, export your Database to a CSV file.')
+        },
         WorshipCenterPro: {
             'name': 'WorshipCenter Pro',
             'prefix': 'worshipCenterPro',
@@ -370,16 +382,17 @@
             SongFormat.SongsOfFellowship,
             SongFormat.SundayPlus,
             SongFormat.WordsOfWorship,
+            SongFormat.WorshipAssistant,
             SongFormat.WorshipCenterPro,
             SongFormat.ZionWorx
         ]
 
     @staticmethod
-    def get(format, *attributes):
+    def get(song_format, *attributes):
         """
         Return requested song format attribute(s).
 
-        :param format:  A song format from SongFormat.
+        :param song_format: A song format from SongFormat.
         :param attributes: Zero or more song format attributes from SongFormat.
 
         Return type depends on number of supplied attributes:
@@ -389,23 +402,23 @@
         :>1: Return tuple of requested attribute values.
         """
         if not attributes:
-            return SongFormat.__attributes__.get(format)
+            return SongFormat.__attributes__.get(song_format)
         elif len(attributes) == 1:
             default = SongFormat.__defaults__.get(attributes[0])
-            return SongFormat.__attributes__[format].get(attributes[0], default)
+            return SongFormat.__attributes__[song_format].get(attributes[0], default)
         else:
             values = []
             for attr in attributes:
                 default = SongFormat.__defaults__.get(attr)
-                values.append(SongFormat.__attributes__[format].get(attr, default))
+                values.append(SongFormat.__attributes__[song_format].get(attr, default))
             return tuple(values)
 
     @staticmethod
-    def set(format, attribute, value):
+    def set(song_format, attribute, value):
         """
         Set specified song format attribute to the supplied value.
         """
-        SongFormat.__attributes__[format][attribute] = value
+        SongFormat.__attributes__[song_format][attribute] = value
 
 
 SongFormat.set(SongFormat.SongsOfFellowship, 'availability', HAS_SOF)

=== added file 'openlp/plugins/songs/lib/worshipassistantimport.py'
--- openlp/plugins/songs/lib/worshipassistantimport.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/songs/lib/worshipassistantimport.py	2014-06-30 07:21:09 +0000
@@ -0,0 +1,171 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2014 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan      #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub,      #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer.   #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru,          #
+# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,             #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock,              #
+# Frode Woldsund, Martin Zibricky, Patrick Zimmermann                         #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+"""
+The :mod:`worshipassistantimport` module provides the functionality for importing
+Worship Assistant songs into the OpenLP database.
+"""
+import chardet
+import csv
+import logging
+import re
+
+from openlp.core.common import translate
+from openlp.plugins.songs.lib import VerseType
+from openlp.plugins.songs.lib.songimport import SongImport
+
+log = logging.getLogger(__name__)
+
+EMPTY_STR = 'NULL'
+
+
+class WorshipAssistantImport(SongImport):
+    """
+    The :class:`WorshipAssistantImport` class provides the ability to import songs
+    from Worship Assistant, via a dump of the database to a CSV file.
+
+    The following fields are in the exported CSV file:
+
+    * ``SONGNR`` Song ID (Discarded by importer)
+    * ``TITLE`` Song title
+    * ``AUTHOR`` Song author.
+    * ``COPYRIGHT`` Copyright information
+    * ``FIRSTLINE`` Unknown (Discarded by importer)
+    * ``PRIKEY`` Primary chord key (Discarded by importer)
+    * ``ALTKEY`` Alternate chord key (Discarded by importer)
+    * ``TEMPO`` Tempo (Discarded by importer)
+    * ``FOCUS`` Unknown (Discarded by importer)
+    * ``THEME`` Theme (Discarded by importer)
+    * ``SCRIPTURE`` Associated scripture (Discarded by importer)
+    * ``ACTIVE`` Boolean value (Discarded by importer)
+    * ``SONGBOOK`` Boolean value (Discarded by importer)
+    * ``TIMESIG`` Unknown (Discarded by importer)
+    * ``INTRODUCED`` Date the song was created (Discarded by importer)
+    * ``LASTUSED`` Date the song was last used (Discarded by importer)
+    * ``TIMESUSED`` How many times the song was used (Discarded by importer)
+    * ``CCLINR`` CCLI Number
+    * ``USER1`` User Field 1 (Discarded by importer)
+    * ``USER2`` User Field 2 (Discarded by importer)
+    * ``USER3`` User Field 3 (Discarded by importer)
+    * ``USER4`` User Field 4 (Discarded by importer)
+    * ``USER5`` User Field 5 (Discarded by importer)
+    * ``ROADMAP`` Verse order used for the presentation
+    * ``FILELINK1`` Associated file 1 (Discarded by importer)
+    * ``OVERMAP`` Verse order used for printing (Discarded by importer)
+    * ``FILELINK2`` Associated file 2 (Discarded by importer)
+    * ``LYRICS`` The song lyrics used for printing (Discarded by importer, LYRICS2 is used instead)
+    * ``INFO`` Unknown (Discarded by importer)
+    * ``LYRICS2`` The song lyrics used for the presentation
+    * ``BACKGROUND`` Custom background (Discarded by importer)
+    """
+    def do_import(self):
+        """
+        Receive a CSV file to import.
+        """
+        # Get encoding
+        detect_file = open(self.import_source, 'rb')
+        detect_content = detect_file.read()
+        details = chardet.detect(detect_content)
+        detect_file.close()
+        songs_file = open(self.import_source, 'r', encoding=details['encoding'])
+        songs_reader = csv.DictReader(songs_file)
+        try:
+            records = list(songs_reader)
+        except csv.Error as e:
+            self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Error reading CSV file.'),
+                           translate('SongsPlugin.WorshipAssistantImport', 'Line %d: %s') %
+                           (songs_reader.line_num, e))
+            return
+        num_records = len(records)
+        log.info('%s records found in CSV file' % num_records)
+        self.import_wizard.progress_bar.setMaximum(num_records)
+        for index, record in enumerate(records, 1):
+            if self.stop_import_flag:
+                return
+            # Ensure that all keys are uppercase
+            record = dict((field.upper(), value) for field, value in record.items())
+            # The CSV file has a line in the middle of the file where the headers are repeated.
+            #  We need to skip this line.
+            if record['TITLE'] == "TITLE" and record['AUTHOR'] == 'AUTHOR' and record['LYRICS2'] == 'LYRICS2':
+                continue
+            self.set_defaults()
+            verse_order_list = []
+            try:
+                self.title = record['TITLE']
+                if record['AUTHOR'] != EMPTY_STR:
+                    self.parse_author(record['AUTHOR'])
+                    print(record['AUTHOR'])
+                if record['COPYRIGHT'] != EMPTY_STR:
+                    self.add_copyright(record['COPYRIGHT'])
+                if record['CCLINR'] != EMPTY_STR:
+                    self.ccli_number = record['CCLINR']
+                if record['ROADMAP'] != EMPTY_STR:
+                    verse_order_list = record['ROADMAP'].split(',')
+                lyrics = record['LYRICS2']
+            except UnicodeDecodeError as e:
+                self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Record %d' % index),
+                               translate('SongsPlugin.WorshipAssistantImport', 'Decoding error: %s') % e)
+                continue
+            except TypeError as e:
+                self.log_error(translate('SongsPlugin.WorshipAssistantImport',
+                                         'File not valid WorshipAssistant CSV format.'), 'TypeError: %s' % e)
+                return
+            verse = ''
+            for line in lyrics.splitlines():
+                if line.startswith('['):  # verse marker
+                    # drop the square brackets
+                    right_bracket = line.find(']')
+                    content = line[1:right_bracket].lower()
+                    match = re.match('(\D*)(\d+)', content)
+                    if match is not None:
+                        verse_tag = match.group(1)
+                        verse_num = match.group(2)
+                    else:
+                        # otherwise we assume number 1 and take the whole prefix as the verse tag
+                        verse_tag = content
+                        verse_num = '1'
+                    verse_index = VerseType.from_loose_input(verse_tag) if verse_tag else 0
+                    verse_tag = VerseType.tags[verse_index]
+                    # Update verse order when the verse name has changed
+                    if content != verse_tag + verse_num:
+                        for i in range(len(verse_order_list)):
+                            if verse_order_list[i].lower() == content.lower():
+                                verse_order_list[i] = verse_tag + verse_num
+                elif line and not line.isspace():
+                    verse += line + '\n'
+                elif verse:
+                    self.add_verse(verse, verse_tag+verse_num)
+                    verse = ''
+            if verse:
+                self.add_verse(verse, verse_tag+verse_num)
+            if verse_order_list:
+                self.verse_order_list = verse_order_list
+            if not self.finish():
+                self.log_error(translate('SongsPlugin.WorshipAssistantImport', 'Record %d') % index
+                               + (': "' + self.title + '"' if self.title else ''))
+            songs_file.close()

=== modified file 'tests/functional/openlp_plugins/songs/test_opensongimport.py'
--- tests/functional/openlp_plugins/songs/test_opensongimport.py	2014-04-30 20:39:40 +0000
+++ tests/functional/openlp_plugins/songs/test_opensongimport.py	2014-06-30 07:21:09 +0000
@@ -52,11 +52,11 @@
         """
         Test that loading an OpenSong file works correctly on various files
         """
-        self.file_import(os.path.join(TEST_PATH, 'Amazing Grace'),
+        self.file_import([os.path.join(TEST_PATH, 'Amazing Grace')],
                          self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
-        self.file_import(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer'),
+        self.file_import([os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer')],
                          self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json')))
-        self.file_import(os.path.join(TEST_PATH, 'One, Two, Three, Four, Five'),
+        self.file_import([os.path.join(TEST_PATH, 'One, Two, Three, Four, Five')],
                          self.load_external_result_data(os.path.join(TEST_PATH, 'One, Two, Three, Four, Five.json')))
 
 

=== modified file 'tests/functional/openlp_plugins/songs/test_propresenterimport.py'
--- tests/functional/openlp_plugins/songs/test_propresenterimport.py	2014-06-09 10:27:17 +0000
+++ tests/functional/openlp_plugins/songs/test_propresenterimport.py	2014-06-30 07:21:09 +0000
@@ -50,5 +50,5 @@
         """
         Test that loading an ProPresenter file works correctly
         """
-        self.file_import(os.path.join(TEST_PATH, 'Amazing Grace.pro4'),
+        self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.pro4')],
                          self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))

=== modified file 'tests/functional/openlp_plugins/songs/test_songshowplusimport.py'
--- tests/functional/openlp_plugins/songs/test_songshowplusimport.py	2014-05-11 04:45:30 +0000
+++ tests/functional/openlp_plugins/songs/test_songshowplusimport.py	2014-06-30 07:21:09 +0000
@@ -53,11 +53,11 @@
         """
         Test that loading a SongShow Plus file works correctly on various files
         """
-        self.file_import(os.path.join(TEST_PATH, 'Amazing Grace.sbsong'),
+        self.file_import([os.path.join(TEST_PATH, 'Amazing Grace.sbsong')],
                          self.load_external_result_data(os.path.join(TEST_PATH, 'Amazing Grace.json')))
-        self.file_import(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.sbsong'),
+        self.file_import([os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.sbsong')],
                          self.load_external_result_data(os.path.join(TEST_PATH, 'Beautiful Garden Of Prayer.json')))
-        self.file_import(os.path.join(TEST_PATH, 'a mighty fortress is our god.sbsong'),
+        self.file_import([os.path.join(TEST_PATH, 'a mighty fortress is our god.sbsong')],
                          self.load_external_result_data(os.path.join(TEST_PATH, 'a mighty fortress is our god.json')))
 
 

=== added file 'tests/functional/openlp_plugins/songs/test_worshipassistantimport.py'
--- tests/functional/openlp_plugins/songs/test_worshipassistantimport.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_plugins/songs/test_worshipassistantimport.py	2014-06-30 07:21:09 +0000
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2013 Raoul Snyman                                        #
+# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan      #
+# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub,      #
+# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer.   #
+# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru,          #
+# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,             #
+# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock,              #
+# Frode Woldsund, Martin Zibricky, Patrick Zimmermann                         #
+# --------------------------------------------------------------------------- #
+# This program is free software; you can redistribute it and/or modify it     #
+# under the terms of the GNU General Public License as published by the Free  #
+# Software Foundation; version 2 of the License.                              #
+#                                                                             #
+# This program is distributed in the hope that it will be useful, but WITHOUT #
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
+# more details.                                                               #
+#                                                                             #
+# You should have received a copy of the GNU General Public License along     #
+# with this program; if not, write to the Free Software Foundation, Inc., 59  #
+# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
+###############################################################################
+"""
+The :mod:`worshipassistantimport` module provides the functionality for importing
+WorshipAssistant song files into the current installation database.
+"""
+
+import os
+
+from tests.helpers.songfileimport import SongImportTestHelper
+
+TEST_PATH = os.path.abspath(
+    os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'worshipassistantsongs'))
+
+
+class TestWorshipAssistantFileImport(SongImportTestHelper):
+
+    def __init__(self, *args, **kwargs):
+        self.importer_class_name = 'WorshipAssistantImport'
+        self.importer_module_name = 'worshipassistantimport'
+        super(TestWorshipAssistantFileImport, self).__init__(*args, **kwargs)
+
+    def test_song_import(self):
+        """
+        Test that loading an Worship Assistant file works correctly
+        """
+        self.file_import(os.path.join(TEST_PATH, 'du_herr.csv'),
+                         self.load_external_result_data(os.path.join(TEST_PATH, 'du_herr.json')))
+        self.file_import(os.path.join(TEST_PATH, 'would_you_be_free.csv'),
+                         self.load_external_result_data(os.path.join(TEST_PATH, 'would_you_be_free.json')))

=== modified file 'tests/helpers/songfileimport.py'
--- tests/helpers/songfileimport.py	2014-06-04 04:54:44 +0000
+++ tests/helpers/songfileimport.py	2014-06-30 07:21:09 +0000
@@ -95,7 +95,7 @@
         importer.topics = []
 
         # WHEN: Importing the source file
-        importer.import_source = [source_file_name]
+        importer.import_source = source_file_name
         add_verse_calls = self._get_data(result_data, 'verses')
         author_calls = self._get_data(result_data, 'authors')
         ccli_number = self._get_data(result_data, 'ccli_number')

=== added directory 'tests/resources/worshipassistantsongs'
=== added file 'tests/resources/worshipassistantsongs/du_herr.csv'
--- tests/resources/worshipassistantsongs/du_herr.csv	1970-01-01 00:00:00 +0000
+++ tests/resources/worshipassistantsongs/du_herr.csv	2014-06-30 07:21:09 +0000
@@ -0,0 +1,30 @@
+"SongID","SongNr","Title","Author","Copyright","FirstLine","PriKey","AltKey","Tempo","Focus","Theme","Scripture","Active","Songbook","TimeSig","Introduced","LastUsed","TimesUsed","CCLINr","User1","User2","User3","User4","User5","Roadmap","Overmap","FileLink1","FileLink2","Updated","Lyrics","Info","Lyrics2","Background"
+"4ee399dc-edda-4aa9-891e-a859ca093c78","NULL","Du, Herr, verläßt mich nicht","Carl Brockhaus / Johann Georg Bäßler 1806","NULL","NULL","NULL","NULL","NULL","NULL","NULL","NULL","1","1","NULL","NULL","NULL","NULL","NULL","NULL","NULL","NULL","NULL","NULL","NULL","NULL","NULL","NULL","2014-06-25 12:15:28.317","","NULL","[1]
+Du, Herr, verläßt mich nicht.
+Auf Dich mein Herz allein vertraut,
+Mein Auge glaubend auf Dich schaut.
+Du bist mein Heil, mein Licht,
+Mein Fels, mein sichrer Hort.
+Bin ich versucht, gibt's Not und Leid,
+Du bleibst mein Trost, mein Arm im Streit,
+Mein Licht am dunklen Ort.
+
+[2]
+Ich weiß, daß Du mich liebst.
+Bist mir in jeder Lage nah',
+Wohin ich gehe – Du bist da,
+Ja, Du mir alles gibst.
+Ich überlaß mich Dir;
+Denn Du, Herr, kennst mich ganz und gar
+Und führst mich sicher, wunderbar,
+Und bist selbst alles mir.
+
+[3]
+In dieser Wüste hier
+Find't nirgend meine Seele Ruh',
+Denn meine Ruh' bist, Jesu, Du.
+Wohl mir, ich geh' zu Dir!
+Bald werd' ich bei Dir sein,
+Bald mit den Deinen ewiglich
+Anbeten, loben, preisen Dich,
+Mich Deiner stets erfreun.","NULL"

=== added file 'tests/resources/worshipassistantsongs/du_herr.json'
--- tests/resources/worshipassistantsongs/du_herr.json	1970-01-01 00:00:00 +0000
+++ tests/resources/worshipassistantsongs/du_herr.json	2014-06-30 07:21:09 +0000
@@ -0,0 +1,21 @@
+{
+    "authors": [
+        "Carl Brockhaus / Johann Georg Bäßler 1806"
+    ],
+    "title": "Du, Herr, verläßt mich nicht",
+    "verse_order_list": [],
+    "verses": [
+        [
+            "Du, Herr, verläßt mich nicht.\nAuf Dich mein Herz allein vertraut,\nMein Auge glaubend auf Dich schaut.\nDu bist mein Heil, mein Licht,\nMein Fels, mein sichrer Hort.\nBin ich versucht, gibt's Not und Leid,\nDu bleibst mein Trost, mein Arm im Streit,\nMein Licht am dunklen Ort.\n",
+            "v1"
+        ],
+        [
+            "Ich weiß, daß Du mich liebst.\nBist mir in jeder Lage nah',\nWohin ich gehe – Du bist da,\nJa, Du mir alles gibst.\nIch überlaß mich Dir;\nDenn Du, Herr, kennst mich ganz und gar\nUnd führst mich sicher, wunderbar,\nUnd bist selbst alles mir.\n",
+            "v2"
+        ],
+        [
+            "In dieser Wüste hier\nFind't nirgend meine Seele Ruh',\nDenn meine Ruh' bist, Jesu, Du.\nWohl mir, ich geh' zu Dir!\nBald werd' ich bei Dir sein,\nBald mit den Deinen ewiglich\nAnbeten, loben, preisen Dich,\nMich Deiner stets erfreun.\n",
+            "v3"
+        ]
+    ]
+}

=== added file 'tests/resources/worshipassistantsongs/would_you_be_free.csv'
--- tests/resources/worshipassistantsongs/would_you_be_free.csv	1970-01-01 00:00:00 +0000
+++ tests/resources/worshipassistantsongs/would_you_be_free.csv	2014-06-30 07:21:09 +0000
@@ -0,0 +1,30 @@
+SONGNR,TITLE,AUTHOR,COPYRIGHT,FIRSTLINE,PRIKEY,ALTKEY,TEMPO,FOCUS,THEME,SCRIPTURE,ACTIVE,SONGBOOK,TIMESIG,INTRODUCED,LASTUSED,TIMESUSED,CCLINR,USER1,USER2,USER3,USER4,USER5,ROADMAP,FILELINK1,OVERMAP,FILELINK2,LYRICS,INFO,LYRICS2,Background
+"7","Would You Be Free","Jones, Lewis E.","Public Domain","Would you be free from your burden of sin?","G","","Moderate","Only To Others","","","N","Y","","1899-12-30","1899-12-30","","","","","","","","1,C,1","","","",".G                           C         G  
+ Would you be free from your burden of sin? 
+.        D            D7     G     
+ There's power in the blood, power in the blood 
+.                      C       G 
+ Would you o'er evil a victory win?  
+.        D         D7           G 
+ There's wonderful power in the blood 
+ 
+.G                      C              G 
+ There is power, power, wonder working power 
+.D            G 
+ In the blood of the Lamb 
+.                       C              G 
+ There is power, power, wonder working power 
+.       D                     G 
+ In the precious blood of the Lamb 
+","","[1] 
+Would you be free from your burden of sin? 
+There's power in the blood, power in the blood 
+Would you o'er evil a victory win?  
+There's wonderful power in the blood 
+ 
+[C] 
+There is power, power, wonder working power 
+In the blood of the Lamb 
+There is power, power, wonder working power 
+In the precious blood of the Lamb 
+",""

=== added file 'tests/resources/worshipassistantsongs/would_you_be_free.json'
--- tests/resources/worshipassistantsongs/would_you_be_free.json	1970-01-01 00:00:00 +0000
+++ tests/resources/worshipassistantsongs/would_you_be_free.json	2014-06-30 07:21:09 +0000
@@ -0,0 +1,19 @@
+{
+    "authors": [
+        "Jones",
+        "Lewis E"
+    ],
+    "title": "Would You Be Free",
+    "verse_order_list": ["v1", "c1", "v1"],
+    "copyright": "Public Domain",
+    "verses": [
+        [
+            "Would you be free from your burden of sin? \nThere's power in the blood, power in the blood \nWould you o'er evil a victory win?  \nThere's wonderful power in the blood \n",
+            "v1"
+        ],
+        [
+            "There is power, power, wonder working power \nIn the blood of the Lamb \nThere is power, power, wonder working power \nIn the precious blood of the Lamb \n",
+            "c1"
+        ]
+    ]
+}

=== modified file 'tests/utils/test_bzr_tags.py'
--- tests/utils/test_bzr_tags.py	2014-05-03 15:34:13 +0000
+++ tests/utils/test_bzr_tags.py	2014-06-30 07:21:09 +0000
@@ -50,10 +50,6 @@
     ['1.9.11', '2039'],
     ['1.9.12', '2063'],
     ['2.0', '2118'],
-    ['2.0.1', '?'],
-    ['2.0.2', '?'],
-    ['2.0.3', '?'],
-    ['2.0.4', '?'],
     ['2.1.0', '2119']
 ]
 


Follow ups