← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~john+ubuntu-g/openlp/singingthefaith into lp:openlp

 

John Lines has proposed merging lp:~john+ubuntu-g/openlp/singingthefaith into lp:openlp.

Commit message:
Initial merge of SingingTheFaithImport, including update to importer.py

Requested reviews:
  Raoul Snyman (raoul-snyman)
  Tomas Groth (tomasgroth)
  Phill (phill-ridout)

For more details, see:
https://code.launchpad.net/~john+ubuntu-g/openlp/singingthefaith/+merge/372194

Singing The Faith is the new Authorized Hymn book for the Methodist Church of Great Britain.
There is an electronic version of the Hymn book, for Windows only, which can export Hymns as text files.

This import module smooths the process of converting these text files into OpenLP. The input format is messy and not intended for automatic processing so the importer uses a combination of heuristics and a hints file. This version has not been tested on all the hymns in Singing The Faith, but deals with most of the, more than 600, hymns it has been tested with.

Documentation for the source format and hints file is at https://wiki.openlp.org/Development:SingingTheFaith_Format

The change includes a test module, which works for the single verse case, and for a whole song - both without use of hints, and another couple of cases using hints.


-- 
Your team OpenLP Core is subscribed to branch lp:openlp.
=== modified file 'openlp/plugins/songs/lib/importer.py'
--- openlp/plugins/songs/lib/importer.py	2019-04-13 13:00:22 +0000
+++ openlp/plugins/songs/lib/importer.py	2019-09-03 12:48:40 +0000
@@ -42,6 +42,7 @@
 from .importers.powersong import PowerSongImport
 from .importers.presentationmanager import PresentationManagerImport
 from .importers.propresenter import ProPresenterImport
+from .importers.singingthefaith import SingingTheFaithImport
 from .importers.songbeamer import SongBeamerImport
 from .importers.songpro import SongProImport
 from .importers.songshowplus import SongShowPlusImport
@@ -173,16 +174,17 @@
     PowerSong = 16
     PresentationManager = 17
     ProPresenter = 18
-    SongBeamer = 19
-    SongPro = 20
-    SongShowPlus = 21
-    SongsOfFellowship = 22
-    SundayPlus = 23
-    VideoPsalm = 24
-    WordsOfWorship = 25
-    WorshipAssistant = 26
-    WorshipCenterPro = 27
-    ZionWorx = 28
+    SingingTheFaith = 19
+    SongBeamer = 20
+    SongPro = 21
+    SongShowPlus = 22
+    SongsOfFellowship = 23
+    SundayPlus = 24
+    VideoPsalm = 25
+    WordsOfWorship = 26
+    WorshipAssistant = 27
+    WorshipCenterPro = 28
+    ZionWorx = 29
 
     # Set optional attribute defaults
     __defaults__ = {
@@ -343,6 +345,16 @@
             'filter': '{text} (*.pro4 *.pro5 *.pro6)'.format(text=translate('SongsPlugin.ImportWizardForm',
                                                                             'ProPresenter Song Files'))
         },
+        SingingTheFaith: {
+            'class': SingingTheFaithImport,
+            'name': 'SingingTheFaith',
+            'prefix': 'singingTheFaith',
+            'filter': '{text} (*.txt)'.format(text=translate('SongsPlugin.ImportWizardForm',
+                                                             'Singing The Faith Exported Files')),
+            'descriptionText': translate('SongsPlugin.ImportWizardForm',
+                                         'First use Singing The Faith Electonic edition to export '
+                                         'the song(s) in Text format.')
+        },
         SongBeamer: {
             'class': SongBeamerImport,
             'name': 'SongBeamer',
@@ -462,6 +474,7 @@
             SongFormat.PowerSong,
             SongFormat.PresentationManager,
             SongFormat.ProPresenter,
+            SongFormat.SingingTheFaith,
             SongFormat.SongBeamer,
             SongFormat.SongPro,
             SongFormat.SongShowPlus,

=== added file 'openlp/plugins/songs/lib/importers/singingthefaith.py'
--- openlp/plugins/songs/lib/importers/singingthefaith.py	1970-01-01 00:00:00 +0000
+++ openlp/plugins/songs/lib/importers/singingthefaith.py	2019-09-03 12:48:40 +0000
@@ -0,0 +1,427 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+###############################################################################
+# OpenLP - Open Source Lyrics Projection                                      #
+# --------------------------------------------------------------------------- #
+# Copyright (c) 2008-2019 OpenLP Developers                                   #
+# --------------------------------------------------------------------------- #
+# 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 3 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:`singingthefaith` module provides the functionality for importing songs which are
+exported from Singing The Faith - an Authorised songbook for the Methodist Church of
+Great Britain."""
+
+import logging
+import re
+from pathlib import Path
+
+from openlp.core.common.i18n import translate
+from openlp.plugins.songs.lib.importers.songimport import SongImport
+
+log = logging.getLogger(__name__)
+
+
+class SingingTheFaithImport(SongImport):
+    """
+    Import songs exported from SingingTheFaith
+    """
+
+    def __init__(self, manager, **kwargs):
+        """
+        Initialise the class.
+        """
+        super(SingingTheFaithImport, self).__init__(manager, **kwargs)
+        self.hints_available = False
+        self.checks_needed = True
+        self.hint_line = {}
+        self.hint_file_version = '0'
+        self.hint_verse_order = ''
+        self.hint_song_title = ''
+        self.hint_comments = ''
+        self.hint_ccli = ''
+        self.hint_ignore_indent = False
+        self.hint_songbook_number_in_title = False
+
+    def do_import(self):
+        """
+        Receive a single file or a list of files to import.
+        """
+        if not isinstance(self.import_source, list):
+            return
+        self.import_wizard.progress_bar.setMaximum(len(self.import_source))
+        for file_path in self.import_source:
+            if self.stop_import_flag:
+                return
+            # If this is backported to version 2.4 then do_import is called with a filename
+            #  rather than a path object if called from the development version.
+            # Check here to minimise differences between versions.
+            if isinstance(file_path, str):
+                song_file = open(file_path, 'rt', encoding='cp1251')
+                self.do_import_file(song_file)
+                song_file.close()
+            else:
+                with file_path.open('rt', encoding='cp1251') as song_file:
+                    self.do_import_file(song_file)
+
+    def do_import_file(self, file):
+        """
+        Process the SingingTheFaith file - pass in a file-like object, not a file path.
+        """
+        singing_the_faith_version = 1
+        self.set_defaults()
+        # Setup variables
+        line_number = 0
+        old_indent = 0
+        # The chorus indent is how many spaces the chorus is indented - it might be 6,
+        # but we test for >= and I do not know how consistent to formatting of the
+        # exported songs is.
+        chorus_indent = 5
+        # Initialise the song title - the format of the title finally produced can be affected
+        #  by the SongbookNumberInTitle option in the hints file
+        song_title = 'STF000 -'
+        song_number = '0'
+        ccli = '0'
+        current_verse = ''
+        current_verse_type = 'v'
+        current_verse_number = 1
+        # Potentially we could try to track current chorus number to automatically handle
+        # more than 1 chorus, currently unused.
+        # current_chorus_number = 1
+        has_chorus = False
+        chorus_written = False
+        auto_verse_order_ok = False
+        copyright = ''
+        # the check_flag is prepended to the title, removed if the import should be OK
+        # all the songs which need manual editing should sort below all the OK songs
+        check_flag = 'z'
+
+        self.add_comment(
+            'Imported with Singing The Faith Importer  v{no}'.format(no=singing_the_faith_version))
+
+        # Get the file_song_number - so we can use it for hints
+        filename = Path(file.name)
+        song_number_file = filename.stem
+        song_number_match = re.search(r'\d+', song_number_file)
+        if song_number_match:
+            song_number_file = song_number_match.group()
+
+        # See if there is a hints file in the same location as the file
+        dir_path = filename.parent
+        hints_file_path = dir_path / 'hints.tag'
+        try:
+            with hints_file_path.open('r') as hints_file:
+                hints_available = self.read_hints(hints_file, song_number_file)
+        except FileNotFoundError:
+            hints_available = False
+
+        try:
+            for line in file:
+                line_number += 1
+                # Strip out leftover formatting (\i and \b)
+                line = line.replace('\\i', '')
+                line = line.replace('\\b', '')
+                if hints_available and str(line_number) in self.hint_line:
+                    hint = self.hint_line[str(line_number)]
+                    # Set to false if this hint does not replace the line
+                    line_replaced = True
+                    if hint == 'Comment':
+                        line.strip()
+                        self.add_comment(line)
+                        continue
+                    elif hint == 'Ignore':
+                        continue
+                    elif hint == 'Author':
+                        # add as a raw author - do not split
+                        line.strip()
+                        self.add_author(line)
+                        line_number += 1
+                        next(file)
+                        continue
+                    elif hint.startswith('VariantVerse'):
+                        vv, hintverse, replace = hint.split(' ', 2)
+                        this_verse = self.verses[int(hintverse) - 1]
+                        this_verse_str = this_verse[1]
+                        new_verse = this_verse_str
+                        # There might be multiple replace pairs separated by |
+                        replaces = replace.split('|')
+                        for rep in replaces:
+                            source_str, dest_str = rep.split('/')
+                            new_verse = new_verse.replace(source_str, dest_str)
+                        self.add_verse(new_verse, 'v')
+                        self.verse_order_list.append('v{}'.format(str(current_verse_number)))
+                        current_verse_number += 1
+                        line_number += 1
+                        next(file)
+                        continue
+                    elif hint == 'AddSpaceAfterSemi':
+                        line = line.replace(';', '; ')
+                        line_replaced = False
+                        # note - do not use contine here as the line should now be processed as normal.
+                    elif hint == 'AddSpaceAfterColon':
+                        line = line.replace(':', ': ')
+                        line_replaced = False
+                    elif hint == 'BlankLine':
+                        line = ' *Blank*'
+                        line_replaced = False
+                    elif hint == 'BoldLine':
+                        # processing of the hint is deferred, but pick it up as a known hint here
+                        line_replaced = False
+                    else:
+                        self.log_error(translate('SongsPlugin.SingingTheFaithImport',
+                                       'File {file})'.format(file=file.name)),
+                                       translate('SongsPlugin.SingingTheFaithImport',
+                                       'Unknown hint {hint}').format(hint=hint))
+                    if line_replaced:
+                        return
+                # STF exported lines have a leading verse number at the start of each verse.
+                #  remove them - note that we want to track the indent as that shows a chorus
+                # so will deal with that before stripping all leading spaces.
+                indent = 0
+                if line.strip():
+                    # One hymn has one line which starts '* 6' at the start of a verse
+                    # Strip this out
+                    if line.startswith('* 6'):
+                        line = line.lstrip('* ')
+                    verse_num_match = re.search(r'^\d+', line)
+                    if verse_num_match:
+                        # Could extract the verse number and check it against the calculated
+                        # verse number - TODO
+                        # verse_num = verse_num_match.group()
+                        line = line.lstrip('0123456789')
+                    indent_match = re.search(r'^\s+', line)
+                    if indent_match:
+                        indent = len(indent_match.group())
+                # Assuming we have sorted out what is verse and what is chorus, strip lines,
+                # unless ignoreIndent
+                if self.hint_ignore_indent:
+                    line = line.rstrip()
+                else:
+                    line = line.strip()
+                if line_number == 2:
+                    # note that songs seem to start with a blank line so the title is line 2
+                    # Also we strip blanks from the title, even if ignoring indent.
+                    song_title = line.strip()
+                # Process possible line formatting hints after the verse number has been removed
+                if hints_available and str(line_number) in self.hint_line and hint == 'BoldLine':
+                    line = '{{st}}{0}{{/st}}'.format(line)
+                # Detect the 'Reproduced from Singing the Faith Electronic Words Edition' line
+                if line.startswith('Reproduced from Singing the Faith Electronic Words Edition'):
+                    song_number_match = re.search(r'\d+', line)
+                    if song_number_match:
+                        song_number = song_number_match.group()
+                        continue
+                elif indent == 0:
+                    # If the indent is 0 and it contains '(c)' then it is a Copyright line
+                    if '(c)' in line:
+                        copyright = line
+                        continue
+                    elif (line.startswith('Liturgical ') or line.startswith('From The ') or
+                          line.startswith('From Common ') or line.startswith('Based on Psalm ')):
+                        self.add_comment(line)
+                        continue
+                    # If indent is 0 it may be the author, unless it was one of the cases covered above
+                    elif len(line) > 0:
+                        # May have more than one author, separated by ' and '
+                        authors = line.split(' and ')
+                        for a in authors:
+                            self.parse_author(a)
+                        continue
+                # If a blank line has bee replaced by *Blank* then put it back to being
+                # a simple space since this is past stripping blanks
+                if '*Blank*' in line:
+                    line = ' '
+                if line == '':
+                    if current_verse != '':
+                        self.add_verse(current_verse, current_verse_type)
+                        self.verse_order_list.append(current_verse_type + str(current_verse_number))
+                        if current_verse_type == 'c':
+                            chorus_written = True
+                        else:
+                            current_verse_number += 1
+                    current_verse = ''
+                    if chorus_written:
+                        current_verse_type = 'v'
+                else:
+                    # If the line is indented more than or equal chorus_indent then assume it is a chorus
+                    # If the indent has just changed then start a new verse just like hitting a blank line
+                    if not self.hint_ignore_indent and ((indent >= chorus_indent) and (old_indent < indent)):
+                        if current_verse != '':
+                            self.add_verse(current_verse, current_verse_type)
+                            self.verse_order_list.append(current_verse_type + str(current_verse_number))
+                            if current_verse_type == 'v':
+                                current_verse_number += 1
+                        current_verse = line
+                        current_verse_type = 'c'
+                        old_indent = indent
+                        chorus_written = False
+                        has_chorus = True
+                        continue
+                    if current_verse == '':
+                        current_verse += line
+                    else:
+                        current_verse += '\n' + line
+                old_indent = indent
+        except Exception as e:
+            self.log_error(translate('SongsPlugin.SingingTheFaithImport', 'File {file}').format(file=file.name),
+                           translate('SongsPlugin.SingingTheFaithImport', 'Error: {error}').format(error=e))
+            return
+
+        if self.hint_song_title:
+            song_title = self.hint_song_title
+        self.title = '{}STF{} - {title}'.format(check_flag, song_number.zfill(3), title=song_title)
+        self.song_book_name = 'Singing The Faith'
+        self.song_number = song_number
+        self.ccli_number = ccli
+        self.add_copyright(copyright)
+        # If we have a chorus then the generated Verse order can not be used directly, but we can generate
+        #  one for two special cases - Verse followed by one chorus (to be repeated after every verse)
+        #  of Chorus, followed by verses. If hints for ManualCheck or VerseOrder are supplied ignore this
+        if has_chorus and not self.hint_verse_order and not self.checks_needed:
+            auto_verse_order_ok = False
+            # Popular case V1 C2 V2 ...
+            if self.verse_order_list:         # protect against odd cases
+                if self.verse_order_list[0] == 'v1' and self.verse_order_list[1] == 'c2':
+                    new_verse_order_list = ['v1', 'c1']
+                    i = 2
+                    auto_verse_order_ok = True
+                elif self.verse_order_list[0] == 'c1' and self.verse_order_list[1] == 'v1':
+                    new_verse_order_list = ['c1', 'v1', 'c1']
+                    i = 2
+                    auto_verse_order_ok = True
+                # if we are in a case we can deal with
+                if auto_verse_order_ok:
+                    while i < len(self.verse_order_list):
+                        if self.verse_order_list[i].startswith('v'):
+                            new_verse_order_list.append(self.verse_order_list[i])
+                            new_verse_order_list.append('c1')
+                        else:
+                            auto_verse_order_ok = False
+                            self.add_comment('Importer detected unexpected verse order entry {}'.format(
+                                self.verse_order_list[i]))
+                        i += 1
+                    self.verse_order_list = new_verse_order_list
+            else:
+                if not auto_verse_order_ok:
+                    self.verse_order_list = []
+        if self.hint_verse_order:
+            self.verse_order_list = self.hint_verse_order.split(',')
+        if self.hint_comments:
+            self.add_comment(self.hint_comments)
+        if self.hint_ccli:
+            self.ccli_number = self.hint_ccli
+        # Write the title last as by now we will know if we need checks
+        if hints_available and not self.checks_needed:
+            check_flag = ''
+        elif not hints_available and not has_chorus:
+            check_flag = ''
+        elif not hints_available and has_chorus and auto_verse_order_ok:
+            check_flag = ''
+        if self.hint_songbook_number_in_title:
+            self.title = '{}STF{} - {title}'.format(check_flag, song_number.zfill(3), title=song_title)
+        else:
+            self.title = '{}{title}'.format(check_flag, title=song_title)
+        if not self.finish():
+            self.log_error(file.name)
+
+    def read_hints(self, file, song_number):
+        """
+        Read the hints used to transform a particular song into version which can be projected,
+        or improve the transformation process beyond the standard heuristics. Not every song will
+        have, or need, hints.
+        """
+        hintfound = False
+        self.hint_verse_order = ''
+        self.hint_line.clear()
+        self.hint_comments = ''
+        self.hint_song_title = ''
+        self.hint_ignore_indent = False
+        self.hint_ccli = ''
+        for tl in file:
+            if not tl.strip():
+                return hintfound
+            tagval = tl.split(':')
+            tag = tagval[0].strip()
+            val = tagval[1].strip()
+            if tag == 'Version':
+                self.hint_file_version = val
+                continue
+            elif tag == 'SongbookNumberInTitle':
+                if val == 'False':
+                    self.hint_songbook_number_in_title = False
+                else:
+                    self.hint_songbook_number_in_title = True
+                continue
+            elif tag == 'Comment':
+                continue
+            if (tag == 'Hymn') and (val == song_number):
+                self.add_comment('Using hints version {}'.format(str(self.hint_file_version)))
+                hintfound = True
+                # Assume, unless the hints has ManualCheck that if hinted all will be OK
+                self.checks_needed = False
+                for tl in file:
+                    tagval = tl.split(':')
+                    tag = tagval[0].strip()
+                    val = tagval[1].strip()
+                    if tag == 'End':
+                        return hintfound
+                    elif tag == 'CommentsLine':
+                        vals = val.split(',')
+                        for v in vals:
+                            self.hint_line[v] = 'Comment'
+                    elif tag == 'IgnoreLine':
+                        vals = val.split(',')
+                        for v in vals:
+                            self.hint_line[v] = 'Ignore'
+                    elif tag == 'AuthorLine':
+                        vals = val.split(',')
+                        for v in vals:
+                            self.hint_line[v] = 'Author'
+                    elif tag == 'AddSpaceAfterSemi':
+                        vals = val.split(',')
+                        for v in vals:
+                            self.hint_line[v] = 'AddSpaceAfterSemi'
+                    elif tag == 'AddSpaceAfterColon':
+                        vals = val.split(',')
+                        for v in vals:
+                            self.hint_line[v] = 'AddSpaceAfterColon'
+                    elif tag == 'BlankLine':
+                        vals = val.split(',')
+                        for v in vals:
+                            self.hint_line[v] = 'BlankLine'
+                    elif tag == 'BoldLine':
+                        vals = val.split(',')
+                        for v in vals:
+                            self.hint_line[v] = 'BoldLine'
+                    elif tag == 'VerseOrder':
+                        self.hint_verse_order = val
+                    elif tag == 'ManualCheck':
+                        self.checks_needed = True
+                    elif tag == 'IgnoreIndent':
+                        self.hint_ignore_indent = True
+                    elif tag == 'VariantVerse':
+                        vvline = val.split(' ', 1)
+                        self.hint_line[vvline[0].strip()] = 'VariantVerse {}'.format(vvline[1].strip())
+                    elif tag == 'SongTitle':
+                        self.hint_song_title = val
+                    elif tag == 'AddComment':
+                        self.hint_comments += '\n' + val
+                    elif tag == 'CCLI':
+                        self.hint_ccli = val
+                    elif tag == 'Hymn':
+                        self.log_error(file.name, 'Missing End tag in hint for Hymn: {}'.format(song_number))
+                    else:
+                        self.log_error(file.name, 'Unknown tag {} value {}'.format(tag, val))
+        return hintfound

=== added file 'resources/hints.tag'
--- resources/hints.tag	1970-01-01 00:00:00 +0000
+++ resources/hints.tag	2019-09-03 12:48:40 +0000
@@ -0,0 +1,666 @@
+Tag-STFHints-version: 1.0
+Version: 2
+SongbookNumberInTitle: True
+End:
+Hymn: 2
+VerseOrder: V1,C1,V2,C1,V3,C1
+End:
+Hymn: 8
+AddSpaceAfterColon: 2,11,20,33
+End:
+Hymn: 10
+CommentsLine: 17
+End:
+Hymn: 11
+CommentsLine: 24
+End:
+Hymn: 15
+VerseOrder: V1,C1,V2,C1,C2,C1
+End:
+Hymn: 18
+CommentsLine: 16
+End:
+Hymn: 19
+CommentsLine: 8
+End:
+Hymn: 22
+CommentsLine: 20
+End:
+Hymn: 24
+IgnoreLine: 13
+VerseOrder: V1,V2,V1
+End:
+Hymn: 26
+VerseOrder: V1,C1,V2,C1,V3,C1,V4,C1,V5,C1
+End:
+Hymn: 27
+AddComment: Verse 1 is original Shona
+SongTitle: Jesu, tawa pano
+IgnoreLine: 2
+CommentsLine: 31
+End:
+Hymn: 28
+CommentsLine: 41
+End:
+Hymn: 29
+CommentsLine: 18
+End:
+Hymn: 30
+CommentsLine: 26
+End:
+Hymn: 35
+VerseOrder: V1,C1,V2,C1,V3,C1
+End:
+Hymn: 37
+IgnoreLine: 42
+VerseOrder: V1,V2,C1,V3,V4,C1
+End:
+Hymn: 38
+ManualCheck: Yes
+AddComment: Make all and cantor words Bold tagged for readability
+SongTitle: Wa wa wa Emimimo
+End:
+Hymn: 40
+VariantVerse: 15 1 Blessed be the name of the Lord/Glory to the name of the Lord|blessed be the name/glory to the name
+VariantVerse: 17 1 Blessed be the name of the Lord/Holy is the name of the Lord|blessed be the name/holy is the name
+VerseOrder: V1,C1,V2,C1,V3,C1
+End:
+Hymn: 41
+IgnoreIndent: Yes
+IgnoreLine: 35,42
+VerseOrder: V1,V2,V3,V4,V2,V3,V5,V3
+End:
+Hymn: 43
+IgnoreIndent: Yes
+CommentsLine: 40
+End:
+Hymn: 45
+IgnoreIndent: Yes
+CommentsLine: 104
+End:
+Hymn: 46
+VerseOrder: V1,C1,V2,C1,C2,C1
+End:
+Hymn: 48
+VerseOrder: V1,C1,V2,C2
+End:
+Hymn: 51
+VerseOrder: V1,C1,V2,C1,V3,C1
+End:
+Hymn: 55
+AddSpaceAfterSemi: 15
+End:
+Hymn: 60
+CommentsLine: 22
+End:
+Hymn: 61
+VerseOrder: C1,V1,C1
+End:
+Hymn: 64
+IgnoreLine: 23,25
+VerseOrder: V1,C1,C2,V2,C1,C2,C3
+End:
+Hymn: 65
+VerseOrder: V1,C1,V2,C2,V3,C1,V4,C1
+End:
+Hymn: 68
+IgnoreLine: 15,31
+VerseOrder: C1,V1,C1,V2,C2,C1
+End:
+Hymn: 71
+IgnoreLine: 23
+VerseOrder: V1,C1,V2,C1,V3
+End:
+Hymn: 74
+IgnoreIndent: Yes
+End:
+Hymn: 77
+IgnoreLine: 32
+CommentsLine: 37
+VerseOrder: V1,V2,C1,V3,C1
+End:
+Hymn: 78
+VerseOrder: V1,V2,V1,V2,V3
+End:
+Hymn: 82
+VerseOrder: V1,C1,V2,C1,V3,C1,V4,C1
+End:
+Hymn: 84
+IgnoreIndent: Yes
+End:
+Hymn: 86
+CommentsLine: 86
+End:
+Hymn: 89
+VerseOrder: V1,V2,C1
+End:
+Hymn: 92
+IgnoreIndent: Yes
+IgnoreLine: 2,18,48
+SongTitle: Think of a world without any flowers
+End:
+Hymn: 93
+AddSpaceAfterSemi: 9,10,11,12
+IgnoreLine: 21,30,31,32,33
+VerseOrder: V1,C1,V2,C1,V3,C1
+End:
+Hymn: 94
+AuthorLine: 24
+VerseOrder: V1,C1,V2,C1,V3,C1
+End:
+Hymn: 95
+AddSpaceAfterSemi: 2
+End:
+Hymn: 98
+IgnoreLine: 17,19,24
+VerseOrder: V1,C1,V2,C1,C2,C1
+AddComment: C2 is an optional Bridge
+End:
+Hymn: 100
+VerseOrder: C1,V1,C1,V2,C1,V3,C1,V4,C1
+End:
+Hymn: 102
+VerseOrder: V1,C1,V2,C1,V3,C1,V4,C1,V5,C1
+End:
+Hymn: 103
+AddSpaceAfterColon: 2,3,11,20
+End:
+Hymn: 105
+VerseOrder: C1,V1,C1,V2,C1
+End:
+Hymn: 118
+IgnoreLine: 15,21
+CommentsLine: 28
+VerseOrder: V1,C1,C1,V2,C1,V3,C1,C1,C2
+End:
+Hymn: 123
+CommentsLine: 40
+End:
+Hymn: 140
+IgnoreLine: 14
+VerseOrder: V1,C1,V2,C1
+End:
+Hymn: 141
+AddSpaceAfterSemi: 2
+End:
+Hymn: 145
+ManualCheck: Yes
+AddComment: Make cantor and all bold, and add to all verses
+SongTitle: Night has fallen
+End:
+Hymn: 147
+AddSpaceAfterSemi: 22
+End:
+Hymn: 165
+IgnoreLine: 2,11,20,29,38
+CommentsLine: 50
+SongTitle: Advent candles tell their story
+End:
+Hymn: 166
+CommentsLine: 40
+VerseOrder: V1,C1,V2,C1,V3,C1,V4,C1,V5,C2
+End:
+Hymn: 168
+CommentsLine: 30
+End:
+Hymn: 170
+VerseOrder: V1,C1,V2,C1,V3,C1,V4,C2
+End:
+Hymn: 173
+VerseOrder: V1,C1,V2,C1,V3,C2
+End:
+Hymn: 174
+CommentsLine: 41
+End:
+Hymn: 175
+IgnoreLine: 22,29
+VerseOrder: V1,C1,V2,C1,C2,C1
+End:
+Hymn: 176
+CommentsLine: 26
+ManualCheck: Yes
+End:
+Hymn: 178
+VerseOrder: V1,C1,V2,C1,V3,C1,V4,C2
+End:
+Hymn: 186
+IgnoreIndent: Yes
+CommentsLine: 24
+End:
+Hymn: 194
+IgnoreIndent: Yes
+End:
+Hymn: 200
+AddSpaceAfterColon: 2
+CommentsLine: 24
+End:
+Hymn: 209
+IgnoreLine: 11,17,23
+VerseOrder: V1,C1,C1,V2,C1,V3,C1
+End:
+Hymn: 212
+CommentsLine: 46
+End:
+Hymn: 220
+AddSpaceAfterColon: 30
+End:
+Hymn: 227
+VerseOrder: V1,V2,V3,V4,V1
+End:
+Hymn: 228
+CommentsLine: 49
+End:
+Hymn: 234
+CommentsLine: 7
+End:
+Hymn: 235
+IgnoreIndent: Yes
+End:
+Hymn: 240
+CommentsLine: 24
+End:
+Hymn: 241
+IgnoreLine: 39
+VerseOrder: V1,C1,V2,C2,C3,C2
+AddComment: is the final chorus a repeat of C1 or C2 ?
+ManualCheck: Yes
+End:
+Hymn: 246
+VerseOrder: V1,C1,V2,C1,V3,C1,V4,C1
+End:
+Hymn: 247
+VerseOrder: V1,C1,V2,C1,V3,C1,V4,C1,V5,C1
+End:
+Hymn: 248
+AddSpaceAfterSemi: 13
+End:
+Hymn: 249
+VerseOrder: C1,V1,C1,V2,C1,V3,C1,V4,C1
+End:
+Hymn: 252
+AddSpaceAfterColon: 1,6,9,13,16,20,23,27,30,34
+End:
+Hymn: 254
+CommentsLine: 22
+End:
+Hymn: 256
+VerseOrder: V1,C1,V2,C1,V3,C1,V4,C1,V5,C1,V6,C2
+End:
+Hymn: 258
+IgnoreLine: 14
+VerseOrder: C1,V1,C1
+End:
+Hymn: 261
+AddSpaceAfterSemi: 4
+End:
+Hymn: 267
+CommentsLine: 33
+End:
+Hymn: 274
+IgnoreLine: 21,30
+VerseOrder: V1,C1,V2,C1,C2,C1
+End:
+Hymn: 279
+IgnoreLine: 35
+VerseOrder: V1,V2,C1,V3,V4,C1
+End:
+Hymn: 285
+CommentsLine: 25
+End:
+Hymn: 298
+IgnoreIndent: Yes
+AddSpaceAfterSemi: 8
+End:
+Hymn: 299
+IgnoreLine: 26
+CommentsLine: 32
+VerseOrder: V1,C1,V2,V3,V4,C1,C2
+End:
+Hymn: 300
+AddSpaceAfterSemi: 8,9,13,17,18
+AddSpaceAfterColon: 7,15,20,25,30
+End:
+Hymn: 302
+VerseOrder: C1,V1,C1,V2,C1,V3,C1,V4,C1,V5,C1
+End:
+Hymn: 316
+VerseOrder: V1,C1,V2,C1,V3,C1,V4,C1,V5,C1,V6,C1,V7,C2
+End:
+Hymn: 321
+CommentsLine: 15
+End:
+Hymn: 323
+AddSpaceAfterSemi: 11
+End:
+Hymn: 331
+VerseOrder: V1,C1,V2,C2
+End:
+Hymn: 335
+VerseOrder: V1,C1,V2,C1,V3,C1,V4,C1,V5,C2
+End:
+Hymn: 343
+IgnoreLine: 24
+VerseOrder: V1,V2,C1,V3,C1
+End:
+Hymn: 349
+AddSpaceAfterSemi: 20,24
+IgnoreLine: 17
+AddComment: The refrain is optional
+VerseOrder: V1,V2,V3,C1,V4,V3
+End:
+Hymn: 351
+CCLI: 3350395
+End:
+Hymn: 353
+AddSpaceAfterSemi: 15
+End:
+Hymn: 364
+AddSpaceAfterSemi: 22
+End:
+Hymn: 367
+IgnoreLine: 28
+VerseOrder: V1,C1,V2,C1
+End:
+Hymn: 373
+CommentsLine: 26
+End:
+Hymn: 374
+ManualCheck: Yes
+CommentsLine: 24
+End:
+Hymn: 377
+VerseOrder: V1,C1,V2,C1,V3,C1
+End:
+Hymn: 380
+VerseOrder: V1,C1,V2,C1
+End:
+Hymn: 386
+BlankLine: 6,16
+IgnoreLine: 19,26
+VerseOrder: V1,C1,V2,C1,C2,C1
+End:
+Hymn: 389
+AuthorLine: 21
+End:
+Hymn: 401
+AddSpaceAfterSemi: 27
+End:
+Hymn: 403
+AddSpaceAfterColon: 2
+End:
+Hymn: 404
+SongTitle: Go tell everyone
+VerseOrder: V1,C1,V2,C1,V3,C1,V4,C1
+End:
+Hymn: 405
+VerseOrder: V1,C1,V2,C1,V3,C1
+End:
+Hymn: 406
+IgnoreLine: 18,27
+End:
+Hymn: 407
+IgnoreLine: 31
+VerseOrder: V1,V2,C1,V3,C1
+End:
+Hymn: 408
+IgnoreIndent: Yes
+CommentsLine: 25
+End:
+Hymn: 410
+AddSpaceAfterSemi: 15,16
+End:
+Hymn: 419
+CommentsLine: 25
+End:
+Hymn: 420
+IgnoreIndent: Yes
+End:
+Hymn: 421
+IgnoreIndent: Yes
+CommentsLine: 55
+End:
+Hymn: 424
+IgnoreLine: 16
+VerseOrder: V1,C1,V2,C1
+End:
+Hymn: 428
+IgnoreLine: 25
+VerseOrder: V1,V2,C1,V3,C1
+End:
+Hymn: 432
+AddSpaceAfterSemi: 8
+End:
+Hymn: 433
+CommentsLine: 44
+End:
+Hymn: 447
+IgnoreLine: 19,21
+VerseOrder: V1,V2,C1,V3,C1,V1
+End:
+Hymn: 451
+IgnoreLine: 14,16
+VerseOrder: C1,V1,C1,V1,V2
+End:
+Hymn: 454
+CommentsLine: 2,42
+SongTitle: Where shall my wondering soul begin
+End:
+Hymn: 458
+CommentsLine: 2
+SongTitle: Away with our fears The glad morning appears
+End:
+Hymn: 469
+ManualCheck: Yes
+AddComment: Need a VariantChorus, Chorus2 and Chorus3 are Variants of Chorus1
+VerseOrder: V1,C1,V2,C2,V3,C2,V4,C3
+End:
+Hymn: 470
+IgnoreIndent: Yes
+End:
+Hymn: 476
+VerseOrder: V1,C1,V2,C1,V3,C1,V4,C1,V5,C1
+End:
+Hymn: 477
+VerseOrder: C1,V1,C1,V2,C1
+End:
+Hymn: 480
+CommentsLine: 28
+End:
+Hymn: 483
+AddComment: Verses 4 and 5 are the
+CommentsLine: 29
+End:
+Hymn: 488
+IgnoreLine: 25
+VerseOrder: V1,V2,C1,V3,C1
+End:
+Hymn: 492
+AddSpaceAfterSemi: 9,10,15
+End:
+Hymn: 499
+CommentsLine: 37
+End:
+Hymn: 509
+IgnoreIndent: Yes
+CommentsLine: 40
+End:
+Hymn: 517
+VerseOrder: V1,C1,V2,C1,V3,C1,V4,C2
+End:
+Hymn: 528
+CommentsLine: 32
+End:
+Hymn: 541
+IgnoreLine: 17,18,25
+VerseOrder: V1,V2,C1,V1,V2,V3,C1
+End:
+Hymn: 545
+CommentsLine: 28
+End:
+Hymn: 548
+AuthorLine: 22
+End:
+Hymn: 554
+IgnoreLine: 15,21
+VerseOrder: V1,C1,V2,C1,V3,C1,V4
+End:
+Hymn: 555
+IgnoreLine: 15
+VerseOrder: V1,V2,C1,V1
+End:
+Hymn: 559
+IgnoreIndent: Yes
+End:
+Hymn: 561
+CommentsLine: 45
+End:
+Hymn: 562
+CommentsLine: 38
+End:
+Hymn: 565
+IgnoreLine: 21
+VerseOrder: V1,V2,V1
+End:
+Hymn: 566
+AddSpaceAfterSemi: 27
+End:
+Hymn: 567
+ManualCheck: Yes
+AddComment: Check with musician what should be on the screen
+End:
+Hymn: 570
+VerseOrder: V1,C1,V2,C1,V3,C1
+End:
+Hymn: 575
+CommentsLine: 30
+End:
+Hymn: 578
+IgnoreLine: 11,18,25
+VerseOrder: C1,V1,C1,V2,C1,V3,C1
+End:
+Hymn: 586
+IgnoreLine: 22
+VerseOrder: V1,V2,C1,V3,C1
+End:
+Hymn: 587
+VerseOrder: V1,C1,V2,C2,V3
+End:
+Hymn: 582
+CommentsLine: 25
+End:
+Hymn: 594
+CommentsLine: 41
+End:
+Hymn: 601
+CommentsLine: 16
+End:
+Hymn: 603
+ManualCheck: Yes
+AddComment: Update Verse 4 for names of the couple
+CommentsLine: 22
+IgnoreIndent: Yes
+End:
+Hymn: 609
+CommentsLine: 37
+End:
+Hymn: 610
+CommentsLine: 29
+End:
+Hymn: 626
+IgnoreLine: 29,31,44
+VerseOrder: V1,C1,C2,V2,C1,C2,C3,C2
+End:
+Hymn: 631
+CommentsLine: 15
+End:
+Hymn: 627
+IgnoreLine: 25,36
+VerseOrder: V1,C1,V2,C1,C2,C1
+End:
+Hymn: 632
+IgnoreLine: 17,24
+VerseOrder: V1,C1,V2,C1,C2,C1
+End:
+Hymn: 635
+IgnoreLine: 23,25
+VerseOrder: V1,C1,C2,V2,C1,C2
+End:
+Hymn: 637
+CommentsLine: 41
+End:
+Hymn: 638
+CommentsLine: 34
+End:
+Hymn: 640
+CommentsLine: 19,20
+End:
+Hymn: 654
+IgnoreIndent: Yes
+End:
+Hymn: 657
+IgnoreLine: 20,21,23
+VerseOrder: V1,C1,C2,V2,C1,C2,C3
+End:
+Hymn: 662
+AddSpaceAfterSemi: 2,12
+End:
+Hymn: 670
+IgnoreLine: 27
+VerseOrder: V1,C1,V2,C1
+End:
+Hymn: 677
+CommentsLine: 31
+End:
+Hymn: 681
+IgnoreIndent: Yes
+End:
+Hymn: 684
+IgnoreIndent: Yes
+End:
+Hymn: 693
+IgnoreLine: 46,57
+VerseOrder: V1,V2,C1,V3,V4,C1,V5,C1
+End:
+Hymn: 697
+IgnoreIndent: Yes
+CommentsLine: 21
+End:
+Hymn: 699
+IgnoreLine: 23,32
+VerseOrder: V1,C1,V2,C1,C1,C2,C1
+End:
+Hymn: 700
+IgnoreIndent: Yes
+End:
+Hymn: 707
+IgnoreLine: 15
+VerseOrder: V1,C1,V2,C1,V3
+End:
+Hymn: 729
+IgnoreIndent: Yes
+End:
+Hymn: 741
+CommentsLine: 25
+End:
+Hymn: 753
+CommentsLine: 14
+IgnoreIndent: Yes
+End:
+Hymn: 754
+IgnoreIndent: Yes
+End:
+Hymn: 764
+IgnoreIndent: Yes
+CommentsLine: 31
+End:
+Hymn: 783
+BlankLine: 5
+End:
+Hymn: 819
+AddSpaceAfterSemi: 32
+End:
+Hymn: 820
+CommentsLine: 2
+BoldLine: 6,7,12,13,17,18,23,24,29,30,32,33,34,35
+SongTitle: Psalm 98 - O sing to the Lord a new song
+End:

=== added file 'tests/functional/openlp_plugins/songs/test_singingthefaithimport.py'
--- tests/functional/openlp_plugins/songs/test_singingthefaithimport.py	1970-01-01 00:00:00 +0000
+++ tests/functional/openlp_plugins/songs/test_singingthefaithimport.py	2019-09-03 12:48:40 +0000
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
+
+##########################################################################
+# OpenLP - Open Source Lyrics Projection                                 #
+# ---------------------------------------------------------------------- #
+# Copyright (c) 2008-2019 OpenLP Developers                              #
+# ---------------------------------------------------------------------- #
+# 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, either version 3 of the License, or      #
+# (at your option) any later version.                                    #
+#                                                                        #
+# 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, see <https://www.gnu.org/licenses/>. #
+##########################################################################
+"""
+This module contains tests for the SingingTheFaith song importer.
+"""
+from tests.helpers.songfileimport import SongImportTestHelper
+from tests.utils.constants import RESOURCE_PATH
+
+
+TEST_PATH = RESOURCE_PATH / 'songs' / 'singingthefaith'
+
+
+class TestSingingTheFaithFileImport(SongImportTestHelper):
+
+    def __init__(self, *args, **kwargs):
+        self.importer_class_name = 'SingingTheFaithImport'
+        self.importer_module_name = 'singingthefaith'
+        super(TestSingingTheFaithFileImport, self).__init__(*args, **kwargs)
+
+    def test_song_import(self):
+        """
+        Test that loading a Singing The Faith file works correctly on various files
+        """
+        # Single verse
+        self.file_import([TEST_PATH / 'H1.txt'],
+                         self.load_external_result_data(TEST_PATH / 'STF001.json'))
+        # Whole song
+        self.file_import([TEST_PATH / 'H2.txt'],
+                         self.load_external_result_data(TEST_PATH / 'STF002.json'))
+
+        # Tests with hints - note that the hints directory has a hints.tag which specifies
+        # SongbookNumberInTitle: True
+        # The default is false, so the unhinted tests will not have the title, but the hinted
+        # song tests will need it
+
+        # Single verse
+        self.file_import([TEST_PATH / 'hints' / 'H1.txt'],
+                         self.load_external_result_data(TEST_PATH / 'hints' / 'STF001.json'))
+        # Whole song
+        self.file_import([TEST_PATH / 'hints' / 'H2.txt'],
+                         self.load_external_result_data(TEST_PATH / 'hints' / 'STF002.json'))

=== modified file 'tests/helpers/songfileimport.py'
--- tests/helpers/songfileimport.py	2019-05-22 06:47:00 +0000
+++ tests/helpers/songfileimport.py	2019-09-03 12:48:40 +0000
@@ -123,7 +123,8 @@
         log.debug("Song copyright imported: %s" % importer.song_number)
         log.debug("Topics imported: %s" % importer.topics)
 
-        assert importer.title == title, 'title for %s should be "%s"' % (source_file_name, title)
+        assert importer.title == title, \
+            'title for %s should be "%s" and is "%s"' % (source_file_name, title, importer.title)
         for author in author_calls:
             if isinstance(author, str):
                 self.mocked_add_author.assert_any_call(author)

=== added directory 'tests/resources/songs/singingthefaith'
=== added file 'tests/resources/songs/singingthefaith/H1.txt'
--- tests/resources/songs/singingthefaith/H1.txt	1970-01-01 00:00:00 +0000
+++ tests/resources/songs/singingthefaith/H1.txt	2019-09-03 12:48:40 +0000
@@ -0,0 +1,9 @@
+
+1   Amazing Grace! how sweet the sound!
+    That saved a wretch like me!
+    I once was lost, but now am found;
+    Was blind, but now I see.
+
+John Newton (d. 1807)
+
+Reproduced from Singing the Faith Electronic Words Edition, number 1 - or not as this is a hand made test file

=== added file 'tests/resources/songs/singingthefaith/H2.txt'
--- tests/resources/songs/singingthefaith/H2.txt	1970-01-01 00:00:00 +0000
+++ tests/resources/songs/singingthefaith/H2.txt	2019-09-03 12:48:40 +0000
@@ -0,0 +1,30 @@
+
+1   Amazing Grace! how sweet the sound!
+    That saved a wretch like me!
+    I once was lost, but now am found;
+    Was blind, but now I see.
+
+2   'Twas grace that taught my heart to fear,
+    And grace my fears relieved.
+    How precious did that grace appear,
+    The hour I first believed.
+
+3   The Lord has promised good to me,
+    His Word my hope secures.
+    He will my shield and portion be
+    As long as life endures.
+
+4   Thro' many dangers, toils and snares
+    I have already come.
+    'Tis grace that brought me safe thus far,
+    And grace will lead me home.
+
+5   When we've been there ten thousand years,
+    Bright shining as the sun,
+    We've no less days to sing God's praise,
+    Than when we first begun.
+
+
+John Newton (d. 1807)
+
+Reproduced from Singing the Faith Electronic Words Edition, number 2 - or not as this is a hand made test file

=== added file 'tests/resources/songs/singingthefaith/STF001.json'
--- tests/resources/songs/singingthefaith/STF001.json	1970-01-01 00:00:00 +0000
+++ tests/resources/songs/singingthefaith/STF001.json	2019-09-03 12:48:40 +0000
@@ -0,0 +1,13 @@
+{
+    "title": "Amazing Grace! how sweet the sound!",
+    "authors": [
+        "John Newton (d. 1807)"
+    ],
+    "verse_order_list": ["v1"],
+    "verses": [
+        [
+            "Amazing Grace! how sweet the sound!\nThat saved a wretch like me!\nI once was lost, but now am found;\nWas blind, but now I see.",
+            "v"
+        ]
+    ]
+}

=== added file 'tests/resources/songs/singingthefaith/STF002.json'
--- tests/resources/songs/singingthefaith/STF002.json	1970-01-01 00:00:00 +0000
+++ tests/resources/songs/singingthefaith/STF002.json	2019-09-03 12:48:40 +0000
@@ -0,0 +1,29 @@
+{
+    "title": "Amazing Grace! how sweet the sound!",
+    "authors": [
+        "John Newton (d. 1807)"
+    ],
+    "verse_order_list": ["v1", "v2", "v3", "v4", "v5"],
+    "verses": [
+        [
+            "Amazing Grace! how sweet the sound!\nThat saved a wretch like me!\nI once was lost, but now am found;\nWas blind, but now I see.",
+            "v"
+        ],
+        [
+            "'Twas grace that taught my heart to fear,\nAnd grace my fears relieved.\nHow precious did that grace appear,\nThe hour I first believed.",
+            "v"
+        ],
+        [
+            "The Lord has promised good to me,\nHis Word my hope secures.\nHe will my shield and portion be\nAs long as life endures.",
+            "v"
+        ],
+        [
+            "Thro' many dangers, toils and snares\nI have already come.\n'Tis grace that brought me safe thus far,\nAnd grace will lead me home.",
+            "v"
+        ],
+        [
+            "When we've been there ten thousand years,\nBright shining as the sun,\nWe've no less days to sing God's praise,\nThan when we first begun.",
+            "v"
+        ]
+    ]
+}

=== added directory 'tests/resources/songs/singingthefaith/hints'
=== added file 'tests/resources/songs/singingthefaith/hints/H1.txt'
--- tests/resources/songs/singingthefaith/hints/H1.txt	1970-01-01 00:00:00 +0000
+++ tests/resources/songs/singingthefaith/hints/H1.txt	2019-09-03 12:48:40 +0000
@@ -0,0 +1,9 @@
+
+1   Amazing Grace! how sweet the sound!
+    That saved a wretch like me!
+    I once was lost, but now am found;
+    Was blind, but now I see.
+
+John Newton (d. 1807)
+
+Reproduced from Singing the Faith Electronic Words Edition, number 1 - or not as this is a hand made test file

=== added file 'tests/resources/songs/singingthefaith/hints/H2.txt'
--- tests/resources/songs/singingthefaith/hints/H2.txt	1970-01-01 00:00:00 +0000
+++ tests/resources/songs/singingthefaith/hints/H2.txt	2019-09-03 12:48:40 +0000
@@ -0,0 +1,30 @@
+
+1   Amazing Grace! how sweet the sound!
+    That saved a wretch like me!
+    I once was lost, but now am found;
+    Was blind, but now I see.
+
+2   'Twas grace that taught my heart to fear,
+    And grace my fears relieved.
+    How precious did that grace appear,
+    The hour I first believed.
+
+3   The Lord has promised good to me,
+    His Word my hope secures.
+    He will my shield and portion be
+    As long as life endures.
+
+4   Thro' many dangers, toils and snares
+    I have already come.
+    'Tis grace that brought me safe thus far,
+    And grace will lead me home.
+
+5   When we've been there ten thousand years,
+    Bright shining as the sun,
+    We've no less days to sing God's praise,
+    Than when we first begun.
+
+
+John Newton (d. 1807)
+
+Reproduced from Singing the Faith Electronic Words Edition, number 2 - or not as this is a hand made test file

=== added file 'tests/resources/songs/singingthefaith/hints/STF001.json'
--- tests/resources/songs/singingthefaith/hints/STF001.json	1970-01-01 00:00:00 +0000
+++ tests/resources/songs/singingthefaith/hints/STF001.json	2019-09-03 12:48:40 +0000
@@ -0,0 +1,13 @@
+{
+    "title": "STF001 - Amazing Grace! how sweet the sound!",
+    "authors": [
+        "John Newton (d. 1807)"
+    ],
+    "verse_order_list": ["v1"],
+    "verses": [
+        [
+            "Amazing Grace! how sweet the sound!\nThat saved a wretch like me!\nI once was lost, but now am found;\nWas blind, but now I see.",
+            "v"
+        ]
+    ]
+}

=== added file 'tests/resources/songs/singingthefaith/hints/STF002.json'
--- tests/resources/songs/singingthefaith/hints/STF002.json	1970-01-01 00:00:00 +0000
+++ tests/resources/songs/singingthefaith/hints/STF002.json	2019-09-03 12:48:40 +0000
@@ -0,0 +1,29 @@
+{
+    "title": "STF002 - Amazing Grace! how sweet the sound!",
+    "authors": [
+        "John Newton (d. 1807)"
+    ],
+    "verse_order_list": ["v1", "v2", "v3", "v4", "v5"],
+    "verses": [
+        [
+            "Amazing Grace! how sweet the sound!\nThat saved a wretch like me!\nI once was lost, but now am found;\nWas blind, but now I see.",
+            "v"
+        ],
+        [
+            "'Twas grace that taught my heart to fear,\nAnd grace my fears relieved.\nHow precious did that grace appear,\nThe hour I first believed.",
+            "v"
+        ],
+        [
+            "The Lord has promised good to me,\nHis Word my hope secures.\nHe will my shield and portion be\nAs long as life endures.",
+            "v"
+        ],
+        [
+            "Thro' many dangers, toils and snares\nI have already come.\n'Tis grace that brought me safe thus far,\nAnd grace will lead me home.",
+            "v"
+        ],
+        [
+            "When we've been there ten thousand years,\nBright shining as the sun,\nWe've no less days to sing God's praise,\nThan when we first begun.",
+            "v"
+        ]
+    ]
+}

=== added file 'tests/resources/songs/singingthefaith/hints/hints.tag'
--- tests/resources/songs/singingthefaith/hints/hints.tag	1970-01-01 00:00:00 +0000
+++ tests/resources/songs/singingthefaith/hints/hints.tag	2019-09-03 12:48:40 +0000
@@ -0,0 +1,5 @@
+Tag-STFHints-version: 1.0
+Version: 2
+SongbookNumberInTitle: True
+End:
+


Follow ups