forked from openlp/openlp
Head
This commit is contained in:
commit
2e82d92ebc
@ -278,7 +278,7 @@ class VerseType(object):
|
||||
if verse_index is None:
|
||||
verse_index = VerseType.from_string(verse_name, default)
|
||||
elif len(verse_name) == 1:
|
||||
verse_index = VerseType.from_translated_tag(verse_name, None)
|
||||
verse_index = VerseType.from_translated_tag(verse_name, default)
|
||||
if verse_index is None:
|
||||
verse_index = VerseType.from_tag(verse_name, default)
|
||||
else:
|
||||
|
@ -527,15 +527,7 @@ class SongMediaItem(MediaManagerItem):
|
||||
add_song = True
|
||||
if search_results:
|
||||
for song in search_results:
|
||||
author_list = item.data_string['authors']
|
||||
same_authors = True
|
||||
for author in song.authors:
|
||||
if author.display_name in author_list:
|
||||
author_list = author_list.replace(author.display_name, '', 1)
|
||||
else:
|
||||
same_authors = False
|
||||
break
|
||||
if same_authors and author_list.strip(', ') == '':
|
||||
if self._authors_match(song, item.data_string['authors']):
|
||||
add_song = False
|
||||
edit_id = song.id
|
||||
break
|
||||
@ -561,6 +553,23 @@ class SongMediaItem(MediaManagerItem):
|
||||
self.generate_footer(item, song)
|
||||
return item
|
||||
|
||||
def _authors_match(self, song, authors):
|
||||
"""
|
||||
Checks whether authors from a song in the database match the authors of the song to be imported.
|
||||
|
||||
:param song: A list of authors from the song in the database
|
||||
:param authors: A string with authors from the song to be imported
|
||||
:return: True when Authors do match, else False.
|
||||
"""
|
||||
author_list = authors.split(', ')
|
||||
for author in song.authors:
|
||||
if author.display_name in author_list:
|
||||
author_list.remove(author.display_name)
|
||||
else:
|
||||
return False
|
||||
# List must be empty at the end
|
||||
return not author_list
|
||||
|
||||
def search(self, string, show_error):
|
||||
"""
|
||||
Search for some songs
|
||||
|
@ -45,11 +45,11 @@ class OpenSongImport(SongImport):
|
||||
"""
|
||||
Import songs exported from OpenSong
|
||||
|
||||
The format is described loosly on the `OpenSong File Format Specification
|
||||
The format is described loosely on the `OpenSong File Format Specification
|
||||
<http://www.opensong.org/d/manual/song_file_format_specification>`_ page on the OpenSong web site. However, it
|
||||
doesn't describe the <lyrics> section, so here's an attempt:
|
||||
|
||||
If the first charachter of a line is a space, then the rest of that line is lyrics. If it is not a space the
|
||||
If the first character of a line is a space, then the rest of that line is lyrics. If it is not a space the
|
||||
following applies.
|
||||
|
||||
Verses can be expressed in one of 2 ways, either in complete verses, or by line grouping, i.e. grouping all line 1's
|
||||
@ -93,12 +93,19 @@ class OpenSongImport(SongImport):
|
||||
|
||||
All verses are imported and tagged appropriately.
|
||||
|
||||
Guitar chords can be provided "above" the lyrics (the line is preceeded by a period "."), and one or more "_" can
|
||||
Guitar chords can be provided "above" the lyrics (the line is preceded by a period "."), and one or more "_" can
|
||||
be used to signify long-drawn-out words. Chords and "_" are removed by this importer. For example::
|
||||
|
||||
. A7 Bm
|
||||
1 Some____ Words
|
||||
|
||||
Lines that contain only whitespace are ignored.
|
||||
| indicates a blank line, and || a new slide.
|
||||
|
||||
Slide 1 Line 1|Slide 1 Line 2||Slide 2 Line 1|Slide 2 Line 2
|
||||
|
||||
Lines beginning with ; are comments
|
||||
|
||||
The <presentation> tag is used to populate the OpenLP verse display order field. The Author and Copyright tags are
|
||||
also imported to the appropriate places.
|
||||
"""
|
||||
@ -107,9 +114,14 @@ class OpenSongImport(SongImport):
|
||||
"""
|
||||
Initialise the class.
|
||||
"""
|
||||
SongImport.__init__(self, manager, **kwargs)
|
||||
super(OpenSongImport, self).__init__(manager, **kwargs)
|
||||
|
||||
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 filename in self.import_source:
|
||||
if self.stop_import_flag:
|
||||
@ -141,19 +153,41 @@ class OpenSongImport(SongImport):
|
||||
'author': self.parse_author,
|
||||
'title': 'title',
|
||||
'aka': 'alternate_title',
|
||||
'hymn_number': 'song_number'
|
||||
'hymn_number': self.parse_song_book_name_and_number,
|
||||
'user1': self.add_comment,
|
||||
'user2': self.add_comment,
|
||||
'user3': self.add_comment
|
||||
}
|
||||
for attr, fn_or_string in list(decode.items()):
|
||||
if attr in fields:
|
||||
ustring = str(root.__getattr__(attr))
|
||||
if isinstance(fn_or_string, str):
|
||||
if attr in ['ccli']:
|
||||
if ustring:
|
||||
setattr(self, fn_or_string, int(ustring))
|
||||
else:
|
||||
setattr(self, fn_or_string, None)
|
||||
else:
|
||||
setattr(self, fn_or_string, ustring)
|
||||
else:
|
||||
fn_or_string(ustring)
|
||||
if 'theme' in fields and str(root.theme) not in self.topics:
|
||||
self.topics.append(str(root.theme))
|
||||
if 'alttheme' in fields and str(root.alttheme) not in self.topics:
|
||||
self.topics.append(str(root.alttheme))
|
||||
# Themes look like "God: Awe/Wonder", but we just want
|
||||
# "Awe" and "Wonder". We use a set to ensure each topic
|
||||
# is only added once, in case it is already there, which
|
||||
# is actually quite likely if the alttheme is set
|
||||
topics = set(self.topics)
|
||||
if 'theme' in fields:
|
||||
theme = str(root.theme)
|
||||
subthemes = theme[theme.find(':')+1:].split('/')
|
||||
for topic in subthemes:
|
||||
topics.add(topic.strip())
|
||||
if 'alttheme' in fields:
|
||||
theme = str(root.alttheme)
|
||||
subthemes = theme[theme.find(':')+1:].split('/')
|
||||
for topic in subthemes:
|
||||
topics.add(topic.strip())
|
||||
self.topics = list(topics)
|
||||
self.topics.sort()
|
||||
# data storage while importing
|
||||
verses = {}
|
||||
# keep track of verses appearance order
|
||||
@ -168,7 +202,7 @@ class OpenSongImport(SongImport):
|
||||
else:
|
||||
lyrics = ''
|
||||
for this_line in lyrics.split('\n'):
|
||||
if not this_line:
|
||||
if not this_line.strip():
|
||||
continue
|
||||
# skip this line if it is a comment
|
||||
if this_line.startswith(';'):
|
||||
@ -209,8 +243,14 @@ class OpenSongImport(SongImport):
|
||||
# Tidy text and remove the ____s from extended words
|
||||
this_line = self.tidy_text(this_line)
|
||||
this_line = this_line.replace('_', '')
|
||||
this_line = this_line.replace('|', '\n')
|
||||
this_line = this_line.replace('||', '\n[---]\n')
|
||||
this_line = this_line.strip()
|
||||
# If the line consists solely of a '|', then just use the implicit newline
|
||||
# Otherwise, add a newline for each '|'
|
||||
if this_line == '|':
|
||||
this_line = ''
|
||||
else:
|
||||
this_line = this_line.replace('|', '\n')
|
||||
verses[verse_tag][verse_num][inst].append(this_line)
|
||||
# done parsing
|
||||
# add verses in original order
|
||||
@ -223,7 +263,14 @@ class OpenSongImport(SongImport):
|
||||
verse_def = '%s%s' % (verse_tag, verse_num[:length])
|
||||
verse_joints[verse_def] = '%s\n[---]\n%s' % (verse_joints[verse_def], lines) \
|
||||
if verse_def in verse_joints else lines
|
||||
for verse_def, lines in verse_joints.items():
|
||||
# Parsing the dictionary produces the elements in a non-intuitive order. While it "works", it's not a
|
||||
# natural layout should the user come back to edit the song. Instead we sort by the verse type, so that we
|
||||
# get all the verses in order (v1, v2, ...), then the chorus(es), bridge(s), pre-chorus(es) etc. We use a
|
||||
# tuple for the key, since tuples naturally sort in this manner.
|
||||
verse_defs = sorted(verse_joints.keys(),
|
||||
key=lambda verse_def: (VerseType.from_tag(verse_def[0]), int(verse_def[1:])))
|
||||
for verse_def in verse_defs:
|
||||
lines = verse_joints[verse_def]
|
||||
self.add_verse(lines, verse_def)
|
||||
if not self.verses:
|
||||
self.add_verse('')
|
||||
@ -244,6 +291,8 @@ class OpenSongImport(SongImport):
|
||||
# Assume it's no.1 if there are no digits
|
||||
verse_tag = verse_def
|
||||
verse_num = '1'
|
||||
verse_index = VerseType.from_loose_input(verse_tag)
|
||||
verse_tag = VerseType.tags[verse_index]
|
||||
verse_def = '%s%s' % (verse_tag, verse_num)
|
||||
if verse_num in verses.get(verse_tag, {}):
|
||||
self.verse_order_list.append(verse_def)
|
||||
|
@ -188,13 +188,61 @@ class SongImport(QtCore.QObject):
|
||||
self.title = lines[0]
|
||||
self.add_verse(text)
|
||||
|
||||
def parse_song_book_name_and_number(self, book_and_number):
|
||||
"""
|
||||
Build the book name and song number from a single string
|
||||
"""
|
||||
# Turn 'Spring Harvest 1997 No. 34' or
|
||||
# 'Spring Harvest 1997 (34)' or
|
||||
# 'Spring Harvest 1997 34' into
|
||||
# Book name:'Spring Harvest 1997' and
|
||||
# Song number: 34
|
||||
#
|
||||
# Also, turn 'NRH231.' into
|
||||
# Book name:'NRH' and
|
||||
# Song number: 231
|
||||
book_and_number = book_and_number.strip()
|
||||
if not book_and_number:
|
||||
return
|
||||
book_and_number = book_and_number.replace('No.', ' ')
|
||||
if ' ' in book_and_number:
|
||||
parts = book_and_number.split(' ')
|
||||
self.song_book_name = ' '.join(parts[:-1])
|
||||
self.song_number = parts[-1].strip('()')
|
||||
else:
|
||||
# Something like 'ABC123.'
|
||||
match = re.match(r'(.*\D)(\d+)', book_and_number)
|
||||
match_num = re.match(r'(\d+)', book_and_number)
|
||||
if match:
|
||||
# Name and number
|
||||
self.song_book_name = match.group(1)
|
||||
self.song_number = match.group(2)
|
||||
# These last two cases aren't tested yet, but
|
||||
# are here in an attempt to do something vaguely
|
||||
# sensible if we get a string in a different format
|
||||
elif match_num:
|
||||
# Number only
|
||||
self.song_number = match_num.group(1)
|
||||
else:
|
||||
# Name only
|
||||
self.song_book_name = book_and_number
|
||||
|
||||
def add_comment(self, comment):
|
||||
"""
|
||||
Build the comments field
|
||||
"""
|
||||
if self.comments.find(comment) >= 0:
|
||||
return
|
||||
if comment:
|
||||
self.comments += comment.strip() + '\n'
|
||||
|
||||
def add_copyright(self, copyright):
|
||||
"""
|
||||
Build the copyright field
|
||||
"""
|
||||
if self.copyright.find(copyright) >= 0:
|
||||
return
|
||||
if self.copyright != '':
|
||||
if self.copyright:
|
||||
self.copyright += ' '
|
||||
self.copyright += copyright
|
||||
|
||||
|
@ -153,3 +153,52 @@ class TestMediaItem(TestCase, TestMixin):
|
||||
|
||||
# THEN: The songbook should be in the footer
|
||||
self.assertEqual(service_item.raw_footer, ['My Song', 'My copyright', 'My songbook #12'])
|
||||
|
||||
def authors_match_test(self):
|
||||
"""
|
||||
Test the author matching when importing a song from a service
|
||||
"""
|
||||
# GIVEN: A song and a string with authors
|
||||
song = MagicMock()
|
||||
song.authors = []
|
||||
author = MagicMock()
|
||||
author.display_name = "Hans Wurst"
|
||||
song.authors.append(author)
|
||||
author2 = MagicMock()
|
||||
author2.display_name = "Max Mustermann"
|
||||
song.authors.append(author2)
|
||||
# There are occasions where an author appears twice in a song (with different types).
|
||||
# We need to make sure that this case works (lp#1313538)
|
||||
author3 = MagicMock()
|
||||
author3.display_name = "Max Mustermann"
|
||||
song.authors.append(author3)
|
||||
authors_str = "Hans Wurst, Max Mustermann, Max Mustermann"
|
||||
|
||||
# WHEN: Checking for matching
|
||||
result = self.media_item._authors_match(song, authors_str)
|
||||
|
||||
# THEN: They should match
|
||||
self.assertTrue(result, "Authors should match")
|
||||
|
||||
def authors_dont_match_test(self):
|
||||
# GIVEN: A song and a string with authors
|
||||
song = MagicMock()
|
||||
song.authors = []
|
||||
author = MagicMock()
|
||||
author.display_name = "Hans Wurst"
|
||||
song.authors.append(author)
|
||||
author2 = MagicMock()
|
||||
author2.display_name = "Max Mustermann"
|
||||
song.authors.append(author2)
|
||||
# There are occasions where an author appears twice in a song (with different types).
|
||||
# We need to make sure that this case works (lp#1313538)
|
||||
author3 = MagicMock()
|
||||
author3.display_name = "Max Mustermann"
|
||||
song.authors.append(author3)
|
||||
|
||||
# WHEN: An author is missing in the string
|
||||
authors_str = "Hans Wurst, Max Mustermann"
|
||||
result = self.media_item._authors_match(song, authors_str)
|
||||
|
||||
# THEN: They should not match
|
||||
self.assertFalse(result, "Authors should not match")
|
||||
|
121
tests/functional/openlp_plugins/songs/test_opensongimport.py
Normal file
121
tests/functional/openlp_plugins/songs/test_opensongimport.py
Normal file
@ -0,0 +1,121 @@
|
||||
# -*- 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 #
|
||||
###############################################################################
|
||||
"""
|
||||
This module contains tests for the OpenSong song importer.
|
||||
"""
|
||||
|
||||
import os
|
||||
from unittest import TestCase
|
||||
|
||||
from tests.helpers.songfileimport import SongImportTestHelper
|
||||
from openlp.plugins.songs.lib.opensongimport import OpenSongImport
|
||||
from tests.functional import patch, MagicMock
|
||||
|
||||
TEST_PATH = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources', 'opensongsongs'))
|
||||
|
||||
|
||||
class TestOpenSongFileImport(SongImportTestHelper):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.importer_class_name = 'OpenSongImport'
|
||||
self.importer_module_name = 'opensongimport'
|
||||
super(TestOpenSongFileImport, self).__init__(*args, **kwargs)
|
||||
|
||||
def test_song_import(self):
|
||||
"""
|
||||
Test that loading an OpenSong file works correctly on various files
|
||||
"""
|
||||
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.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.load_external_result_data(os.path.join(TEST_PATH, 'One, Two, Three, Four, Five.json')))
|
||||
|
||||
|
||||
class TestOpenSongImport(TestCase):
|
||||
"""
|
||||
Test the functions in the :mod:`opensongimport` module.
|
||||
"""
|
||||
def create_importer_test(self):
|
||||
"""
|
||||
Test creating an instance of the OpenSong file importer
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.opensongimport.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
|
||||
# WHEN: An importer object is created
|
||||
importer = OpenSongImport(mocked_manager, filenames=[])
|
||||
|
||||
# THEN: The importer object should not be None
|
||||
self.assertIsNotNone(importer, 'Import should not be none')
|
||||
|
||||
def invalid_import_source_test(self):
|
||||
"""
|
||||
Test OpenSongImport.do_import handles different invalid import_source values
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.opensongimport.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
mocked_import_wizard = MagicMock()
|
||||
importer = OpenSongImport(mocked_manager, filenames=[])
|
||||
importer.import_wizard = mocked_import_wizard
|
||||
importer.stop_import_flag = True
|
||||
|
||||
# WHEN: Import source is not a list
|
||||
for source in ['not a list', 0]:
|
||||
importer.import_source = source
|
||||
|
||||
# THEN: do_import should return none and the progress bar maximum should not be set.
|
||||
self.assertIsNone(importer.do_import(), 'do_import should return None when import_source is not a list')
|
||||
self.assertEqual(mocked_import_wizard.progress_bar.setMaximum.called, False,
|
||||
'setMaximum on import_wizard.progress_bar should not have been called')
|
||||
|
||||
def valid_import_source_test(self):
|
||||
"""
|
||||
Test OpenSongImport.do_import handles different invalid import_source values
|
||||
"""
|
||||
# GIVEN: A mocked out SongImport class, and a mocked out "manager"
|
||||
with patch('openlp.plugins.songs.lib.opensongimport.SongImport'):
|
||||
mocked_manager = MagicMock()
|
||||
mocked_import_wizard = MagicMock()
|
||||
importer = OpenSongImport(mocked_manager, filenames=[])
|
||||
importer.import_wizard = mocked_import_wizard
|
||||
importer.stop_import_flag = True
|
||||
|
||||
# WHEN: Import source is a list
|
||||
importer.import_source = ['List', 'of', 'files']
|
||||
|
||||
# THEN: do_import should return none and the progress bar setMaximum should be called with the length of
|
||||
# import_source.
|
||||
self.assertIsNone(importer.do_import(), 'do_import should return None when import_source is a list '
|
||||
'and stop_import_flag is True')
|
||||
mocked_import_wizard.progress_bar.setMaximum.assert_called_with(len(importer.import_source))
|
@ -33,7 +33,7 @@ song files from third party applications.
|
||||
import json
|
||||
from unittest import TestCase
|
||||
|
||||
from tests.functional import patch, MagicMock
|
||||
from tests.functional import patch, MagicMock, call
|
||||
|
||||
|
||||
class SongImportTestHelper(TestCase):
|
||||
@ -56,13 +56,13 @@ class SongImportTestHelper(TestCase):
|
||||
'openlp.plugins.songs.lib.%s.%s.add_verse' % (self.importer_module_name, self.importer_class_name))
|
||||
self.finish_patcher = patch(
|
||||
'openlp.plugins.songs.lib.%s.%s.finish' % (self.importer_module_name, self.importer_class_name))
|
||||
self.parse_author_patcher = patch(
|
||||
'openlp.plugins.songs.lib.%s.%s.parse_author' % (self.importer_module_name, self.importer_class_name))
|
||||
self.add_author_patcher = patch(
|
||||
'openlp.plugins.songs.lib.%s.%s.add_author' % (self.importer_module_name, self.importer_class_name))
|
||||
self.song_import_patcher = patch('openlp.plugins.songs.lib.%s.SongImport' % self.importer_module_name)
|
||||
self.mocked_add_copyright = self.add_copyright_patcher.start()
|
||||
self.mocked_add_verse = self.add_verse_patcher.start()
|
||||
self.mocked_finish = self.finish_patcher.start()
|
||||
self.mocked_parse_author = self.parse_author_patcher.start()
|
||||
self.mocked_add_author = self.add_author_patcher.start()
|
||||
self.mocked_song_importer = self.song_import_patcher.start()
|
||||
self.mocked_manager = MagicMock()
|
||||
self.mocked_import_wizard = MagicMock()
|
||||
@ -75,7 +75,7 @@ class SongImportTestHelper(TestCase):
|
||||
self.add_copyright_patcher.stop()
|
||||
self.add_verse_patcher.stop()
|
||||
self.finish_patcher.stop()
|
||||
self.parse_author_patcher.stop()
|
||||
self.add_author_patcher.stop()
|
||||
self.song_import_patcher.stop()
|
||||
|
||||
def load_external_result_data(self, file_name):
|
||||
@ -112,14 +112,17 @@ class SongImportTestHelper(TestCase):
|
||||
self.assertIsNone(importer.do_import(), 'do_import should return None when it has completed')
|
||||
self.assertEqual(importer.title, title, 'title for %s should be "%s"' % (source_file_name, title))
|
||||
for author in author_calls:
|
||||
self.mocked_parse_author.assert_any_call(author)
|
||||
self.mocked_add_author.assert_any_call(author)
|
||||
if song_copyright:
|
||||
self.mocked_add_copyright.assert_called_with(song_copyright)
|
||||
if ccli_number:
|
||||
self.assertEqual(importer.ccli_number, ccli_number,
|
||||
'ccli_number for %s should be %s' % (source_file_name, ccli_number))
|
||||
expected_calls = []
|
||||
for verse_text, verse_tag in add_verse_calls:
|
||||
self.mocked_add_verse.assert_any_call(verse_text, verse_tag)
|
||||
expected_calls.append(call(verse_text, verse_tag))
|
||||
self.mocked_add_verse.assert_has_calls(expected_calls, any_order=False)
|
||||
if topics:
|
||||
self.assertEqual(importer.topics, topics, 'topics for %s should be %s' % (source_file_name, topics))
|
||||
if comments:
|
||||
@ -132,7 +135,7 @@ class SongImportTestHelper(TestCase):
|
||||
self.assertEqual(importer.song_number, song_number,
|
||||
'song_number for %s should be %s' % (source_file_name, song_number))
|
||||
if verse_order_list:
|
||||
self.assertEqual(importer.verse_order_list, [],
|
||||
self.assertEqual(importer.verse_order_list, verse_order_list,
|
||||
'verse_order_list for %s should be %s' % (source_file_name, verse_order_list))
|
||||
self.mocked_finish.assert_called_with()
|
||||
|
||||
|
56
tests/resources/opensongsongs/Amazing Grace
Normal file
56
tests/resources/opensongsongs/Amazing Grace
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<song>
|
||||
<title>Amazing Grace (Demonstration)</title>
|
||||
<author>John Newton, Edwin Excell & John P. Rees</author>
|
||||
<copyright>Public Domain </copyright>
|
||||
<presentation>V1 V2 V3 V4 V5</presentation>
|
||||
<capo print="false"></capo>
|
||||
<tempo></tempo>
|
||||
<ccli>22025</ccli>
|
||||
<theme>God: Assurance/Grace/Salvation</theme>
|
||||
<alttheme>Worship: Praise</alttheme>
|
||||
<user1> </user1>
|
||||
<user2> </user2>
|
||||
<user3> </user3>
|
||||
<lyrics>[V]
|
||||
;Test the chords format
|
||||
;Chords beging with .
|
||||
;Verses begin with their verse number
|
||||
;Link words with _
|
||||
;Comments begin with ;
|
||||
. D D7 G D
|
||||
1A______ma________zing grace! How sweet the sound!
|
||||
2'Twas grace that taught my heart to fear,
|
||||
3The Lord has pro____mised good to me,
|
||||
4Thro' ma________ny dan____gers, toils and snares
|
||||
5When we've been there ten thou__sand years,
|
||||
|
||||
. Bm E A A7
|
||||
1That saved a wretch like me!
|
||||
2And grace my fears re___lieved.
|
||||
3His Word my hope se___cures.
|
||||
4I have al___rea____dy come.
|
||||
5Bright shi___ning as the sun,
|
||||
|
||||
. D D7 G D
|
||||
1I once was lost, but now am found;
|
||||
2How pre___cious did that grace ap____pear,
|
||||
3He will my shield and por___tion be
|
||||
4'Tis grace that brought me safe thus far,
|
||||
5We've no less days to sing God's praise,
|
||||
|
||||
. Bm A G D
|
||||
1Was blind, but now I see.
|
||||
2The hour I first be_lieved.
|
||||
3As long as life en_dures.
|
||||
4And grace will lead me home.
|
||||
5Than when we first be_gun.
|
||||
|
||||
</lyrics>
|
||||
<hymn_number>Demonstration Songs 0</hymn_number>
|
||||
<key></key>
|
||||
<aka></aka>
|
||||
<key_line></key_line>
|
||||
<time_sig></time_sig>
|
||||
<style index="default_style"></style>
|
||||
</song>
|
42
tests/resources/opensongsongs/Amazing Grace.json
Normal file
42
tests/resources/opensongsongs/Amazing Grace.json
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
"authors": [
|
||||
"John Newton",
|
||||
"Edwin Excell",
|
||||
"John P. Rees"
|
||||
],
|
||||
"ccli_number": 22025,
|
||||
"comments": "\n\n\n",
|
||||
"copyright": "Public Domain ",
|
||||
"song_book_name": "Demonstration Songs",
|
||||
"song_number": 0,
|
||||
"title": "Amazing Grace (Demonstration)",
|
||||
"topics": [
|
||||
"Assurance",
|
||||
"Grace",
|
||||
"Praise",
|
||||
"Salvation"
|
||||
],
|
||||
"verse_order_list": [],
|
||||
"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.",
|
||||
"v1"
|
||||
],
|
||||
[
|
||||
"'Twas grace that taught my heart to fear,\nAnd grace my fears relieved.\nHow precious did that grace appear,\nThe hour I first believed.",
|
||||
"v2"
|
||||
],
|
||||
[
|
||||
"The Lord has promised good to me,\nHis Word my hope secures.\nHe will my shield and portion be\nAs long as life endures.",
|
||||
"v3"
|
||||
],
|
||||
[
|
||||
"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.",
|
||||
"v4"
|
||||
],
|
||||
[
|
||||
"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.",
|
||||
"v5"
|
||||
]
|
||||
]
|
||||
}
|
56
tests/resources/opensongsongs/Beautiful Garden Of Prayer
Normal file
56
tests/resources/opensongsongs/Beautiful Garden Of Prayer
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<song>
|
||||
<title>Beautiful Garden Of Prayer (Demonstration)</title>
|
||||
<author>Eleanor Allen Schroll & James H. Fillmore</author>
|
||||
<copyright>Public Domain </copyright>
|
||||
<presentation>V1 C V2 C V3 C</presentation>
|
||||
<capo print="false"></capo>
|
||||
<tempo></tempo>
|
||||
<ccli>60252</ccli>
|
||||
<theme>God: Prayer/Devotion</theme>
|
||||
<alttheme>Prayer: Prayer/Devotion</alttheme>
|
||||
<user1></user1>
|
||||
<user2></user2>
|
||||
<user3></user3>
|
||||
<lyrics>
|
||||
;Test breaks and newlines
|
||||
;A single | on the end of a line adds an extra \n
|
||||
;Blank lines are ignored, even with a space prefix
|
||||
;We also check that the chorus is added after the verses, despite the order in the file
|
||||
[V1]
|
||||
There's a garden where Jesus is waiting,
|
||||
|
||||
There's a place that is wondrously fair.
|
||||
For it glows with the light of His presence,|
|
||||
'Tis the beautiful garden of prayer.
|
||||
|
||||
;A double || on a line adds a new slide
|
||||
[C]
|
||||
O the beautiful garden, the garden of prayer,
|
||||
O the beautiful garden of prayer.
|
||||
There my Savior awaits, and He opens the gates
|
||||
||
|
||||
To the beautiful garden of prayer.
|
||||
|
||||
;A double || on the end of a line adds a new slide
|
||||
[V2]
|
||||
There's a garden where Jesus is waiting,
|
||||
And I go with my burden and care.
|
||||
Just to learn from His lips, words of comfort,||
|
||||
In the beautiful garden of prayer.
|
||||
|
||||
;A single | on a line adds just one line break
|
||||
[V3]
|
||||
There's a garden where Jesus is waiting,
|
||||
And He bids you to come meet Him there,
|
||||
Just to bow and receive a new blessing,
|
||||
|
|
||||
In the beautiful garden of prayer.
|
||||
</lyrics>
|
||||
<hymn_number>DS0</hymn_number>
|
||||
<key></key>
|
||||
<aka></aka>
|
||||
<key_line></key_line>
|
||||
<time_sig></time_sig>
|
||||
<style index="default_style"></style>
|
||||
</song>
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"authors": [
|
||||
"Eleanor Allen Schroll",
|
||||
"James H. Fillmore"
|
||||
],
|
||||
"ccli_number": 60252,
|
||||
"comments": "",
|
||||
"copyright": "Public Domain ",
|
||||
"song_book_name": "DS",
|
||||
"song_number": 0,
|
||||
"title": "Beautiful Garden Of Prayer (Demonstration)",
|
||||
"topics": [
|
||||
"Devotion",
|
||||
"Prayer"
|
||||
],
|
||||
"verse_order_list": ["v1", "c1", "v2", "c1", "v3", "c1"],
|
||||
"verses": [
|
||||
[
|
||||
"There's a garden where Jesus is waiting,\nThere's a place that is wondrously fair.\nFor it glows with the light of His presence,\n\n'Tis the beautiful garden of prayer.",
|
||||
"v1"
|
||||
],
|
||||
[
|
||||
"There's a garden where Jesus is waiting,\nAnd I go with my burden and care.\nJust to learn from His lips, words of comfort,\n[---]\nIn the beautiful garden of prayer.",
|
||||
"v2"
|
||||
],
|
||||
[
|
||||
"There's a garden where Jesus is waiting,\nAnd He bids you to come meet Him there,\nJust to bow and receive a new blessing,\n\nIn the beautiful garden of prayer.",
|
||||
"v3"
|
||||
],
|
||||
[
|
||||
"O the beautiful garden, the garden of prayer,\nO the beautiful garden of prayer.\nThere my Savior awaits, and He opens the gates\n[---]\nTo the beautiful garden of prayer.",
|
||||
"c1"
|
||||
]
|
||||
]
|
||||
}
|
38
tests/resources/opensongsongs/One, Two, Three, Four, Five
Normal file
38
tests/resources/opensongsongs/One, Two, Three, Four, Five
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<song>
|
||||
<title>12345</title>
|
||||
<author>Traditional</author>
|
||||
<copyright>Public Domain </copyright>
|
||||
<presentation>T</presentation>
|
||||
<capo print="false"></capo>
|
||||
<ccli></ccli>
|
||||
<tempo></tempo>
|
||||
<theme></theme>
|
||||
<alttheme></alttheme>
|
||||
<user1></user1>
|
||||
<user2></user2>
|
||||
<user3></user3>
|
||||
<lyrics>
|
||||
;Test [T]ag element - should be turned into [o]ther
|
||||
;And lines beginning with numbers
|
||||
;And a title that contains only numeric characters
|
||||
;That isdiffernt to the filename
|
||||
;And most elements are empty
|
||||
[T]
|
||||
1, 2, 3, 4, 5,
|
||||
Once I caught a fish alive.
|
||||
6, 7, 8, 9, 10,
|
||||
Then I let it go again.
|
||||
|
||||
Why did you let it go?
|
||||
Because it bit my finger so.
|
||||
Which finger did it bite?
|
||||
This little finger on my right.
|
||||
</lyrics>
|
||||
<hymn_number></hymn_number>
|
||||
<key></key>
|
||||
<aka></aka>
|
||||
<key_line></key_line>
|
||||
<time_sig></time_sig>
|
||||
<style index="default_style"></style>
|
||||
</song>
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"authors": [
|
||||
"Traditional"
|
||||
],
|
||||
"comments": "",
|
||||
"copyright": "Public Domain ",
|
||||
"title": "12345",
|
||||
"topics": [
|
||||
],
|
||||
"verse_order_list": ["o1"],
|
||||
"verses": [
|
||||
[
|
||||
"1, 2, 3, 4, 5,\nOnce I caught a fish alive.\n6, 7, 8, 9, 10,\nThen I let it go again.\nWhy did you let it go?\nBecause it bit my finger so.\nWhich finger did it bite?\nThis little finger on my right.",
|
||||
"o1"
|
||||
]
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user