This commit is contained in:
Tim Bentley 2018-08-04 21:58:13 +01:00
commit 3a0d0ff2ea
41 changed files with 681 additions and 113 deletions

View File

@ -115,7 +115,7 @@ def display_thumbnails(request, controller_name, log, dimensions, file_name, sli
height = -1
image = None
if dimensions:
match = re.search('(\d+)x(\d+)', dimensions)
match = re.search(r'(\d+)x(\d+)', dimensions)
if match:
# let's make sure that the dimensions are within reason
width = sorted([10, int(match.group(1)), 1000])[1]

View File

@ -39,7 +39,7 @@ log = logging.getLogger(__name__)
def _route_to_regex(route):
"""
r"""
Convert a route to a regular expression
For example:

View File

@ -54,7 +54,7 @@ class ApiTab(SettingsTab):
self.address_label.setObjectName('address_label')
self.address_edit = QtWidgets.QLineEdit(self.server_settings_group_box)
self.address_edit.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
self.address_edit.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'),
self.address_edit.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'),
self))
self.address_edit.setObjectName('address_edit')
self.server_settings_layout.addRow(self.address_label, self.address_edit)

View File

@ -64,13 +64,17 @@ def get_local_ip4():
log.debug('Getting local IPv4 interface(es) information')
my_ip4 = {}
for iface in QNetworkInterface.allInterfaces():
log.debug('Checking for isValid and flags == IsUP | IsRunning')
if not iface.isValid() or not (iface.flags() & (QNetworkInterface.IsUp | QNetworkInterface.IsRunning)):
continue
log.debug('Checking address(es) protocol')
for address in iface.addressEntries():
ip = address.ip()
# NOTE: Next line will skip if interface is localhost - keep for now until we decide about it later
# if (ip.protocol() == QAbstractSocket.IPv4Protocol) and (ip != QHostAddress.LocalHost):
log.debug('Checking for protocol == IPv4Protocol')
if ip.protocol() == QAbstractSocket.IPv4Protocol:
log.debug('Getting interface information')
my_ip4[iface.name()] = {'ip': ip.toString(),
'broadcast': address.broadcast().toString(),
'netmask': address.netmask().toString(),
@ -79,14 +83,21 @@ def get_local_ip4():
ip.toIPv4Address()).toString()
}
log.debug('Adding {iface} to active list'.format(iface=iface.name()))
if 'localhost' in my_ip4:
log.debug('Renaming windows localhost to lo')
my_ip4['lo'] = my_ip4['localhost']
my_ip4.pop('localhost')
if len(my_ip4) == 0:
log.warning('No active IPv4 network interfaces detected')
if len(my_ip4) == 1:
if 'lo' in my_ip4:
# No active interfaces - so leave localhost in there
log.warning('No active IPv4 interfaces found except localhost')
else:
# Since we have a valid IP4 interface, remove localhost
log.debug('Found at least one IPv4 interface, removing localhost')
my_ip4.pop('lo')
if 'lo' in my_ip4:
log.debug('Found at least one IPv4 interface, removing localhost')
my_ip4.pop('lo')
return my_ip4

View File

@ -32,6 +32,7 @@ import requests
from openlp.core.common import trace_error_handler
from openlp.core.common.registry import Registry
from openlp.core.common.settings import ProxyMode, Settings
log = logging.getLogger(__name__ + '.__init__')
@ -64,6 +65,39 @@ CONNECTION_TIMEOUT = 30
CONNECTION_RETRIES = 2
def get_proxy_settings(mode=None):
"""
Create a dictionary containing the proxy settings.
:param ProxyMode | None mode: Specify the source of the proxy settings
:return: A dict using the format expected by the requests library.
:rtype: dict | None
"""
settings = Settings()
if mode is None:
mode = settings.value('advanced/proxy mode')
if mode == ProxyMode.NO_PROXY:
return {'http': None, 'https': None}
elif mode == ProxyMode.SYSTEM_PROXY:
# The requests library defaults to using the proxy settings in the environment variables
return
elif mode == ProxyMode.MANUAL_PROXY:
http_addr = settings.value('advanced/proxy http')
https_addr = settings.value('advanced/proxy https')
username = settings.value('advanced/proxy username')
password = settings.value('advanced/proxy password')
basic_auth = ''
if username:
basic_auth = '{username}:{password}@'.format(username=username, password=password)
http_value = None
https_value = None
if http_addr:
http_value = 'http://{basic_auth}{http_addr}'.format(basic_auth=basic_auth, http_addr=http_addr)
if https_addr:
https_value = 'https://{basic_auth}{https_addr}'.format(basic_auth=basic_auth, https_addr=https_addr)
return {'http': http_value, 'https': https_value}
def get_user_agent():
"""
Return a user agent customised for the platform the user is on.
@ -75,14 +109,15 @@ def get_user_agent():
return browser_list[random_index]
def get_web_page(url, headers=None, update_openlp=False, proxies=None):
def get_web_page(url, headers=None, update_openlp=False, proxy=None):
"""
Attempts to download the webpage at url and returns that page or None.
:param url: The URL to be downloaded.
:param header: An optional HTTP header to pass in the request to the web server.
:param update_openlp: Tells OpenLP to update itself if the page is successfully downloaded.
Defaults to False.
:param dict | None headers: An optional HTTP header to pass in the request to the web server.
:param update_openlp: Tells OpenLP to update itself if the page is successfully downloaded. Defaults to False.
:param dict | ProxyMode | None proxy: ProxyMode enum or a dictionary containing the proxy servers, with their types
as the key e.g. {'http': 'http://proxyserver:port', 'https': 'https://proxyserver:port'}
"""
if not url:
return None
@ -90,11 +125,13 @@ def get_web_page(url, headers=None, update_openlp=False, proxies=None):
headers = {}
if 'user-agent' not in [key.lower() for key in headers.keys()]:
headers['User-Agent'] = get_user_agent()
if not isinstance(proxy, dict):
proxy = get_proxy_settings(mode=proxy)
log.debug('Downloading URL = %s' % url)
retries = 0
while retries < CONNECTION_RETRIES:
try:
response = requests.get(url, headers=headers, proxies=proxies, timeout=float(CONNECTION_TIMEOUT))
response = requests.get(url, headers=headers, proxies=proxy, timeout=float(CONNECTION_TIMEOUT))
log.debug('Downloaded page {url}'.format(url=response.url))
break
except OSError:

View File

@ -26,6 +26,7 @@ import datetime
import json
import logging
import os
from enum import IntEnum
from tempfile import gettempdir
from PyQt5 import QtCore, QtGui
@ -38,6 +39,13 @@ log = logging.getLogger(__name__)
__version__ = 2
class ProxyMode(IntEnum):
NO_PROXY = 1
SYSTEM_PROXY = 2
MANUAL_PROXY = 3
# Fix for bug #1014422.
X11_BYPASS_DEFAULT = True
if is_linux(): # pragma: no cover
@ -116,6 +124,11 @@ class Settings(QtCore.QSettings):
'advanced/print file meta data': False,
'advanced/print notes': False,
'advanced/print slide text': False,
'advanced/proxy mode': ProxyMode.SYSTEM_PROXY,
'advanced/proxy http': '',
'advanced/proxy https': '',
'advanced/proxy username': '',
'advanced/proxy password': '',
'advanced/recent file count': 4,
'advanced/save current plugin': False,
'advanced/slide limits': SlideLimits.End,

View File

@ -415,7 +415,7 @@ def expand_chords(text):
chords_on_prev_line = True
# Matches a chord, a tail, a remainder and a line end. See expand_and_align_chords_in_line() for more info.
new_line += re.sub(r'\[(.*?)\]([\u0080-\uFFFF,\w]*)'
'([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(\Z)?',
r'([\u0080-\uFFFF,\w,\s,\.,\,,\!,\?,\;,\:,\|,\",\',\-,\_]*)(\Z)?',
expand_and_align_chords_in_line, line)
new_line += '</span>'
expanded_text_lines.append(new_line)

View File

@ -19,7 +19,7 @@
# with this program; if not, write to the Free Software Foundation, Inc., 59 #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA #
###############################################################################
"""
r"""
This module is responsible for generating the HTML for :class:`~openlp.core.ui.maindisplay`. The ``build_html`` function
is the function which has to be called from outside. The generated and returned HTML will look similar to this::
@ -416,7 +416,7 @@ from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, Vertic
log = logging.getLogger(__name__)
HTML_SRC = Template("""
HTML_SRC = Template(r"""
<!DOCTYPE html>
<html>
<head>

View File

@ -338,7 +338,7 @@ class PJLinkCommands(object):
# Due to stupid projectors not following standards (Optoma, BenQ comes to mind),
# AND the different responses that can be received, the semi-permanent way to
# fix the class reply is to just remove all non-digit characters.
chk = re.findall('\d', data)
chk = re.findall(r'\d', data)
if len(chk) < 1:
log.error('({ip}) No numbers found in class version reply "{data}" - '
'defaulting to class "1"'.format(ip=self.entry.name, data=data))

View File

@ -36,6 +36,7 @@ from openlp.core.ui.style import HAS_DARK_STYLE
from openlp.core.ui.icons import UiIcons
from openlp.core.widgets.edits import PathEdit
from openlp.core.widgets.enums import PathEditType
from openlp.core.widgets.widgets import ProxyWidget
log = logging.getLogger(__name__)
@ -77,6 +78,9 @@ class AdvancedTab(SettingsTab):
self.media_plugin_check_box = QtWidgets.QCheckBox(self.ui_group_box)
self.media_plugin_check_box.setObjectName('media_plugin_check_box')
self.ui_layout.addRow(self.media_plugin_check_box)
self.hide_mouse_check_box = QtWidgets.QCheckBox(self.ui_group_box)
self.hide_mouse_check_box.setObjectName('hide_mouse_check_box')
self.ui_layout.addWidget(self.hide_mouse_check_box)
self.double_click_live_check_box = QtWidgets.QCheckBox(self.ui_group_box)
self.double_click_live_check_box.setObjectName('double_click_live_check_box')
self.ui_layout.addRow(self.double_click_live_check_box)
@ -117,6 +121,24 @@ class AdvancedTab(SettingsTab):
self.use_dark_style_checkbox = QtWidgets.QCheckBox(self.ui_group_box)
self.use_dark_style_checkbox.setObjectName('use_dark_style_checkbox')
self.ui_layout.addRow(self.use_dark_style_checkbox)
# Service Item Slide Limits
self.slide_group_box = QtWidgets.QGroupBox(self.left_column)
self.slide_group_box.setObjectName('slide_group_box')
self.slide_layout = QtWidgets.QVBoxLayout(self.slide_group_box)
self.slide_layout.setObjectName('slide_layout')
self.slide_label = QtWidgets.QLabel(self.slide_group_box)
self.slide_label.setWordWrap(True)
self.slide_layout.addWidget(self.slide_label)
self.end_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
self.end_slide_radio_button.setObjectName('end_slide_radio_button')
self.slide_layout.addWidget(self.end_slide_radio_button)
self.wrap_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
self.wrap_slide_radio_button.setObjectName('wrap_slide_radio_button')
self.slide_layout.addWidget(self.wrap_slide_radio_button)
self.next_item_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
self.next_item_radio_button.setObjectName('next_item_radio_button')
self.slide_layout.addWidget(self.next_item_radio_button)
self.left_layout.addWidget(self.slide_group_box)
# Data Directory
self.data_directory_group_box = QtWidgets.QGroupBox(self.left_column)
self.data_directory_group_box.setObjectName('data_directory_group_box')
@ -143,33 +165,6 @@ class AdvancedTab(SettingsTab):
self.data_directory_layout.addRow(self.data_directory_copy_check_layout)
self.data_directory_layout.addRow(self.new_data_directory_has_files_label)
self.left_layout.addWidget(self.data_directory_group_box)
# Hide mouse
self.hide_mouse_group_box = QtWidgets.QGroupBox(self.right_column)
self.hide_mouse_group_box.setObjectName('hide_mouse_group_box')
self.hide_mouse_layout = QtWidgets.QVBoxLayout(self.hide_mouse_group_box)
self.hide_mouse_layout.setObjectName('hide_mouse_layout')
self.hide_mouse_check_box = QtWidgets.QCheckBox(self.hide_mouse_group_box)
self.hide_mouse_check_box.setObjectName('hide_mouse_check_box')
self.hide_mouse_layout.addWidget(self.hide_mouse_check_box)
self.right_layout.addWidget(self.hide_mouse_group_box)
# Service Item Slide Limits
self.slide_group_box = QtWidgets.QGroupBox(self.right_column)
self.slide_group_box.setObjectName('slide_group_box')
self.slide_layout = QtWidgets.QVBoxLayout(self.slide_group_box)
self.slide_layout.setObjectName('slide_layout')
self.slide_label = QtWidgets.QLabel(self.slide_group_box)
self.slide_label.setWordWrap(True)
self.slide_layout.addWidget(self.slide_label)
self.end_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
self.end_slide_radio_button.setObjectName('end_slide_radio_button')
self.slide_layout.addWidget(self.end_slide_radio_button)
self.wrap_slide_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
self.wrap_slide_radio_button.setObjectName('wrap_slide_radio_button')
self.slide_layout.addWidget(self.wrap_slide_radio_button)
self.next_item_radio_button = QtWidgets.QRadioButton(self.slide_group_box)
self.next_item_radio_button.setObjectName('next_item_radio_button')
self.slide_layout.addWidget(self.next_item_radio_button)
self.right_layout.addWidget(self.slide_group_box)
# Display Workarounds
self.display_workaround_group_box = QtWidgets.QGroupBox(self.right_column)
self.display_workaround_group_box.setObjectName('display_workaround_group_box')
@ -224,6 +219,9 @@ class AdvancedTab(SettingsTab):
self.service_name_example.setObjectName('service_name_example')
self.service_name_layout.addRow(self.service_name_example_label, self.service_name_example)
self.right_layout.addWidget(self.service_name_group_box)
# Proxies
self.proxy_widget = ProxyWidget(self.right_column)
self.right_layout.addWidget(self.proxy_widget)
# After the last item on each side, add some spacing
self.left_layout.addStretch()
self.right_layout.addStretch()
@ -312,7 +310,6 @@ class AdvancedTab(SettingsTab):
translate('OpenLP.AdvancedTab',
'Revert to the default service name "{name}".').format(name=UiStrings().DefaultServiceName))
self.service_name_example_label.setText(translate('OpenLP.AdvancedTab', 'Example:'))
self.hide_mouse_group_box.setTitle(translate('OpenLP.AdvancedTab', 'Mouse Cursor'))
self.hide_mouse_check_box.setText(translate('OpenLP.AdvancedTab', 'Hide mouse cursor when over display window'))
self.data_directory_new_label.setText(translate('OpenLP.AdvancedTab', 'Path:'))
self.data_directory_cancel_button.setText(translate('OpenLP.AdvancedTab', 'Cancel'))
@ -335,6 +332,7 @@ class AdvancedTab(SettingsTab):
self.wrap_slide_radio_button.setText(translate('OpenLP.GeneralTab', '&Wrap around'))
self.next_item_radio_button.setText(translate('OpenLP.GeneralTab', '&Move to next/previous service item'))
self.search_as_type_check_box.setText(translate('SongsPlugin.GeneralTab', 'Enable search as you type'))
self.proxy_widget.retranslate_ui()
def load(self):
"""
@ -437,6 +435,7 @@ class AdvancedTab(SettingsTab):
if HAS_DARK_STYLE:
settings.setValue('use_dark_style', self.use_dark_style_checkbox.isChecked())
settings.endGroup()
self.proxy_widget.save()
def on_search_as_type_check_box_changed(self, check_state):
self.is_search_as_you_type_enabled = (check_state == QtCore.Qt.Checked)

View File

@ -80,7 +80,7 @@ def get_media_players():
"""
log.debug('get_media_players')
saved_players = Settings().value('media/players')
reg_ex = QtCore.QRegExp(".*\[(.*)\].*")
reg_ex = QtCore.QRegExp(r'.*\[(.*)\].*')
if Settings().value('media/override player') == QtCore.Qt.Checked:
if reg_ex.exactMatch(saved_players):
overridden_player = '{text}'.format(text=reg_ex.cap(1))

View File

@ -1127,7 +1127,7 @@ class SlideController(DisplayController, LogMixin, RegistryProperties):
# done by the thread holding the lock. If it is a "start" slide, we must wait for the lock, but only for 0.2
# seconds, since we don't want to cause a deadlock
timeout = 0.2 if start else -1
if not self.slide_selected_lock.acquire(start, timeout):
if not self.slide_selected_lock.acquire(start, timeout): # pylint: disable=too-many-function-args
if start:
self.log_debug('Could not get lock in slide_selected after waiting %f, skip to avoid deadlock.'
% timeout)

View File

@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2018 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 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:`~openlp.core.widgets.widgets` module contains custom widgets used in OpenLP
"""
from PyQt5 import QtWidgets
from openlp.core.common.i18n import translate
from openlp.core.common.settings import ProxyMode, Settings
class ProxyWidget(QtWidgets.QGroupBox):
"""
A proxy settings widget that implements loading and saving its settings.
"""
def __init__(self, parent=None):
"""
Initialise the widget.
:param QtWidgets.QWidget | None parent: The widgets parent
"""
super().__init__(parent)
self._setup()
def _setup(self):
"""
A setup method seperate from __init__ to allow easier testing
"""
self.setup_ui()
self.load()
def setup_ui(self):
"""
Create the widget layout and sub widgets
"""
self.layout = QtWidgets.QFormLayout(self)
self.radio_group = QtWidgets.QButtonGroup(self)
self.no_proxy_radio = QtWidgets.QRadioButton('', self)
self.radio_group.addButton(self.no_proxy_radio, ProxyMode.NO_PROXY)
self.layout.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.no_proxy_radio)
self.use_sysem_proxy_radio = QtWidgets.QRadioButton('', self)
self.radio_group.addButton(self.use_sysem_proxy_radio, ProxyMode.SYSTEM_PROXY)
self.layout.setWidget(1, QtWidgets.QFormLayout.SpanningRole, self.use_sysem_proxy_radio)
self.manual_proxy_radio = QtWidgets.QRadioButton('', self)
self.radio_group.addButton(self.manual_proxy_radio, ProxyMode.MANUAL_PROXY)
self.layout.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.manual_proxy_radio)
self.http_edit = QtWidgets.QLineEdit(self)
self.layout.addRow('HTTP:', self.http_edit)
self.https_edit = QtWidgets.QLineEdit(self)
self.layout.addRow('HTTPS:', self.https_edit)
self.username_edit = QtWidgets.QLineEdit(self)
self.layout.addRow('Username:', self.username_edit)
self.password_edit = QtWidgets.QLineEdit(self)
self.password_edit.setEchoMode(QtWidgets.QLineEdit.Password)
self.layout.addRow('Password:', self.password_edit)
# Signal / Slots
self.radio_group.buttonToggled.connect(self.on_radio_group_button_toggled)
def on_radio_group_button_toggled(self, button, checked):
"""
Handles the toggled signal on the radio buttons. The signal is emitted twice if a radio butting being toggled on
causes another radio button in the group to be toggled off.
En/Disables the `Manual Proxy` line edits depending on the currently selected radio button
:param QtWidgets.QRadioButton button: The button that has toggled
:param bool checked: The buttons new state
"""
id = self.radio_group.id(button) # The work around (see above comment)
enable_manual_edits = id == ProxyMode.MANUAL_PROXY and checked
self.http_edit.setEnabled(enable_manual_edits)
self.https_edit.setEnabled(enable_manual_edits)
self.username_edit.setEnabled(enable_manual_edits)
self.password_edit.setEnabled(enable_manual_edits)
def retranslate_ui(self):
"""
Translate the Ui
"""
self.setTitle(translate('OpenLP.ProxyWidget', 'Proxy Server Settings'))
self.no_proxy_radio.setText(translate('OpenLP.ProxyWidget', 'No prox&y'))
self.use_sysem_proxy_radio.setText(translate('OpenLP.ProxyWidget', '&Use system proxy'))
self.manual_proxy_radio.setText(translate('OpenLP.ProxyWidget', '&Manual proxy configuration'))
proxy_example = translate('OpenLP.ProxyWidget', 'e.g. proxy_server_address:port_no')
self.layout.labelForField(self.http_edit).setText(translate('OpenLP.ProxyWidget', 'HTTP:'))
self.http_edit.setPlaceholderText(proxy_example)
self.layout.labelForField(self.https_edit).setText(translate('OpenLP.ProxyWidget', 'HTTPS:'))
self.https_edit.setPlaceholderText(proxy_example)
self.layout.labelForField(self.username_edit).setText(translate('OpenLP.ProxyWidget', 'Username:'))
self.layout.labelForField(self.password_edit).setText(translate('OpenLP.ProxyWidget', 'Password:'))
def load(self):
"""
Load the data from the settings to the widget.
"""
settings = Settings()
checked_radio = self.radio_group.button(settings.value('advanced/proxy mode'))
checked_radio.setChecked(True)
self.http_edit.setText(settings.value('advanced/proxy http'))
self.https_edit.setText(settings.value('advanced/proxy https'))
self.username_edit.setText(settings.value('advanced/proxy username'))
self.password_edit.setText(settings.value('advanced/proxy password'))
def save(self):
"""
Save the widget data to the settings
"""
settings = Settings() # TODO: Migrate from old system
settings.setValue('advanced/proxy mode', self.radio_group.checkedId())
settings.setValue('advanced/proxy http', self.http_edit.text())
settings.setValue('advanced/proxy https', self.https_edit.text())
settings.setValue('advanced/proxy username', self.username_edit.text())
settings.setValue('advanced/proxy password', self.password_edit.text())

View File

@ -183,7 +183,7 @@ class EditBibleForm(QtWidgets.QDialog, Ui_EditBibleDialog, RegistryProperties):
"""
Validate a book.
"""
book_regex = re.compile('[\d]*[^\d]+$')
book_regex = re.compile(r'[\d]*[^\d]+$')
if not new_book_name:
self.book_name_edit[abbreviation].setFocus()
critical_error_message_box(

View File

@ -217,7 +217,7 @@ def update_reference_separators():
# add various Unicode alternatives
source_string = source_string.replace('-', '(?:[-\u00AD\u2010\u2011\u2012\u2014\u2014\u2212\uFE63\uFF0D])')
source_string = source_string.replace(',', '(?:[,\u201A])')
REFERENCE_SEPARATORS['sep_{role}'.format(role=role)] = '\s*(?:{source})\s*'.format(source=source_string)
REFERENCE_SEPARATORS['sep_{role}'.format(role=role)] = r'\s*(?:{source})\s*'.format(source=source_string)
REFERENCE_SEPARATORS['sep_{role}_default'.format(role=role)] = default_separators[index]
# verse range match: (<chapter>:)?<verse>(-((<chapter>:)?<verse>|end)?)?
range_regex = '(?:(?P<from_chapter>[0-9]+){sep_v})?' \
@ -255,7 +255,7 @@ def get_reference_match(match_type):
def parse_reference(reference, bible, language_selection, book_ref_id=False):
"""
r"""
This is the next generation über-awesome function that takes a person's typed in string and converts it to a list
of references to be queried from the Bible database files.

View File

@ -42,7 +42,7 @@ from openlp.plugins.bibles.lib import DisplayStyle, LayoutStyle, VerseReferenceL
log = logging.getLogger(__name__)
VALID_TEXT_SEARCH = re.compile('\w\w\w')
VALID_TEXT_SEARCH = re.compile(r'\w\w\w')
def get_reference_separators():

View File

@ -207,7 +207,7 @@ class MediaClipSelectorForm(QtWidgets.QDialog, Ui_MediaClipSelector, RegistryPro
# detect if we're dealing with a DVD or CD, so we use different loading approaches depending on the OS.
if is_win():
# If the given path is in the format "D:\" or "D:", prefix it with "/" to make VLC happy
pattern = re.compile('^\w:\\\\*$')
pattern = re.compile(r'^\w:\\\\*$')
if pattern.match(path):
path = '/' + path
self.vlc_media = self.vlc_instance.media_new_location('dvd://' + path)

View File

@ -105,7 +105,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
self.topics_list_view.setSortingEnabled(False)
self.topics_list_view.setAlternatingRowColors(True)
self.audio_list_widget.setAlternatingRowColors(True)
self.find_verse_split = re.compile('---\[\]---\n')
self.find_verse_split = re.compile(r'---\[\]---\n')
self.whitespace = re.compile(r'\W+')
self.find_tags = re.compile(r'\{/?\w+\}')
@ -316,7 +316,7 @@ class EditSongForm(QtWidgets.QDialog, Ui_EditSongDialog, RegistryProperties):
multiple.append(verse_tag)
self.song.lyrics = str(sxml.extract_xml(), 'utf-8')
for verse in multiple:
self.song.verse_order = re.sub('([' + verse.upper() + verse.lower() + '])(\W|$)',
self.song.verse_order = re.sub(r'([' + verse.upper() + verse.lower() + r'])(\W|$)',
r'\g<1>1\2', self.song.verse_order)
except:
log.exception('Problem processing song Lyrics \n{xml}'.format(xml=sxml.dump_xml()))

View File

@ -557,7 +557,7 @@ def transpose_lyrics(lyrics, transpose_value):
:return: The transposed lyrics
"""
# Split text by verse delimiter - both normal and optional
verse_list = re.split('(---\[.+?:.+?\]---|\[---\])', lyrics)
verse_list = re.split(r'(---\[.+?:.+?\]---|\[---\])', lyrics)
transposed_lyrics = ''
notation = Settings().value('songs/chord notation')
for verse in verse_list:
@ -580,7 +580,7 @@ def transpose_verse(verse_text, transpose_value, notation):
if '[' not in verse_text:
return verse_text
# Split the lyrics based on chord tags
lyric_list = re.split('(\[|\]|/)', verse_text)
lyric_list = re.split(r'(\[|\]|/)', verse_text)
transposed_lyrics = ''
in_tag = False
for word in lyric_list:

View File

@ -73,7 +73,7 @@ class DreamBeamImport(SongImport):
Valid extensions for a DreamBeam song file are:
* \*.xml
* .xml
"""
def do_import(self):

View File

@ -37,7 +37,7 @@ class EasySlidesImport(SongImport):
Import songs exported from EasySlides
The format example is here:
http://wiki.openlp.org/Development:EasySlides\_-_Song_Data_Format
http://wiki.openlp.org/Development:EasySlides_-_Song_Data_Format
"""
def __init__(self, manager, **kwargs):
"""
@ -210,7 +210,7 @@ class EasySlidesImport(SongImport):
vn = '1'
# have we got any digits?
# If so, versenumber is everything from the digits to the end
match = re.match('(.*)(\d+.*)', marker)
match = re.match(r'(.*)(\d+.*)', marker)
if match:
marker = match.group(1).strip()
vn = match.group(2)

View File

@ -273,15 +273,15 @@ class FoilPresenter(object):
elif copyright.find('C,)') != -1:
temp = copyright.partition('C,)')
copyright = temp[0]
copyright = re.compile('\\n').sub(' ', copyright)
copyright = re.compile('\(.*\)').sub('', copyright)
copyright = re.compile(r'\\n').sub(' ', copyright)
copyright = re.compile(r'\(.*\)').sub('', copyright)
if copyright.find('Rechte') != -1:
temp = copyright.partition('Rechte')
copyright = temp[0]
markers = ['Text +u\.?n?d? +Melodie[\w\,\. ]*:',
'Text +u\.?n?d? +Musik', 'T & M', 'Melodie und Satz',
'Text[\w\,\. ]*:', 'Melodie', 'Musik', 'Satz',
'Weise', '[dD]eutsch', '[dD]t[\.\:]', 'Englisch',
markers = [r'Text +u\.?n?d? +Melodie[\w\,\. ]*:',
r'Text +u\.?n?d? +Musik', 'T & M', 'Melodie und Satz',
r'Text[\w\,\. ]*:', 'Melodie', 'Musik', 'Satz',
'Weise', '[dD]eutsch', r'[dD]t[\.\:]', 'Englisch',
'[oO]riginal', 'Bearbeitung', '[R|r]efrain']
for marker in markers:
copyright = re.compile(marker).sub('<marker>', copyright, re.U)
@ -301,17 +301,17 @@ class FoilPresenter(object):
break
author_temp = []
for author in strings:
temp = re.split(',(?=\D{2})|(?<=\D),|\/(?=\D{3,})|(?<=\D);', author)
temp = re.split(r',(?=\D{2})|(?<=\D),|\/(?=\D{3,})|(?<=\D);', author)
for tempx in temp:
author_temp.append(tempx)
for author in author_temp:
regex = '^[\/,;\-\s\.]+|[\/,;\-\s\.]+$|\s*[0-9]{4}\s*[\-\/]?\s*([0-9]{4})?[\/,;\-\s\.]*$'
regex = r'^[\/,;\-\s\.]+|[\/,;\-\s\.]+$|\s*[0-9]{4}\s*[\-\/]?\s*([0-9]{4})?[\/,;\-\s\.]*$'
author = re.compile(regex).sub('', author)
author = re.compile('[0-9]{1,2}\.\s?J(ahr)?h\.|um\s*$|vor\s*$').sub('', author)
author = re.compile('[N|n]ach.*$').sub('', author)
author = re.compile(r'[0-9]{1,2}\.\s?J(ahr)?h\.|um\s*$|vor\s*$').sub('', author)
author = re.compile(r'[N|n]ach.*$').sub('', author)
author = author.strip()
if re.search('\w+\.?\s+\w{3,}\s+[a|u]nd\s|\w+\.?\s+\w{3,}\s+&\s', author, re.U):
temp = re.split('\s[a|u]nd\s|\s&\s', author)
if re.search(r'\w+\.?\s+\w{3,}\s+[a|u]nd\s|\w+\.?\s+\w{3,}\s+&\s', author, re.U):
temp = re.split(r'\s[a|u]nd\s|\s&\s', author)
for tempx in temp:
tempx = tempx.strip()
authors.append(tempx)

View File

@ -80,7 +80,7 @@ class LyrixImport(SongImport):
continue
# Detect and get CCLI number
if line.lower().startswith('ccli'):
ccli = re.findall('\d+', line)[0]
ccli = re.findall(r'\d+', line)[0]
try:
# If the CCLI was found, we are near the end
# Find author

View File

@ -156,7 +156,7 @@ class OpenSongImport(SongImport):
ustring = str(root.__getattr__(attr))
if isinstance(fn_or_string, str):
if attr in ['ccli']:
ustring = ''.join(re.findall('\d+', ustring))
ustring = ''.join(re.findall(r'\d+', ustring))
if ustring:
setattr(self, fn_or_string, int(ustring))
else:
@ -231,7 +231,7 @@ class OpenSongImport(SongImport):
content = this_line[1:right_bracket].lower()
# have we got any digits? If so, verse number is everything from the digits to the end (openlp does not
# have concept of part verses, so just ignore any non integers on the end (including floats))
match = re.match('(\D*)(\d+)', content)
match = re.match(r'(\D*)(\d+)', content)
if match is not None:
verse_tag = match.group(1)
verse_num = match.group(2)
@ -303,7 +303,7 @@ class OpenSongImport(SongImport):
# whitespace.
order = order.lower().split()
for verse_def in order:
match = re.match('(\D*)(\d+.*)', verse_def)
match = re.match(r'(\D*)(\d+.*)', verse_def)
if match is not None:
verse_tag = match.group(1)
verse_num = match.group(2)

View File

@ -122,7 +122,7 @@ class OPSProImport(SongImport):
# Try to split lyrics based on various rules
if lyrics:
lyrics_text = lyrics.Lyrics
verses = re.split('\r\n\s*?\r\n', lyrics_text)
verses = re.split(r'\r\n\s*?\r\n', lyrics_text)
verse_tag_defs = {}
verse_tag_texts = {}
for verse_text in verses:
@ -130,13 +130,13 @@ class OPSProImport(SongImport):
continue
verse_def = 'v'
# Detect verse number
verse_number = re.match('^(\d+)\r\n', verse_text)
verse_number = re.match(r'^(\d+)\r\n', verse_text)
if verse_number:
verse_text = re.sub('^\d+\r\n', '', verse_text)
verse_text = re.sub(r'^\d+\r\n', '', verse_text)
verse_def = 'v' + verse_number.group(1)
# Detect verse tags
elif re.match('^.+?\:\r\n', verse_text):
tag_match = re.match('^(.+?)\:\r\n(.*)', verse_text, flags=re.DOTALL)
elif re.match(r'^.+?\:\r\n', verse_text):
tag_match = re.match(r'^(.+?)\:\r\n(.*)', verse_text, flags=re.DOTALL)
tag = tag_match.group(1).lower()
tag = tag.split(' ')[0]
verse_text = tag_match.group(2)
@ -147,25 +147,25 @@ class OPSProImport(SongImport):
verse_tag_defs[tag] = verse_def
verse_tag_texts[tag] = verse_text
# Detect tag reference
elif re.match('^\(.*?\)$', verse_text):
tag_match = re.match('^\((.*?)\)$', verse_text)
elif re.match(r'^\(.*?\)$', verse_text):
tag_match = re.match(r'^\((.*?)\)$', verse_text)
tag = tag_match.group(1).lower()
if tag in verse_tag_defs:
verse_text = verse_tag_texts[tag]
verse_def = verse_tag_defs[tag]
# Detect end tag
elif re.match('^\[slot\]\r\n', verse_text, re.IGNORECASE):
elif re.match(r'^\[slot\]\r\n', verse_text, re.IGNORECASE):
verse_def = 'e'
verse_text = re.sub('^\[slot\]\r\n', '', verse_text, flags=re.IGNORECASE)
verse_text = re.sub(r'^\[slot\]\r\n', '', verse_text, flags=re.IGNORECASE)
# Replace the join tag with line breaks
verse_text = verse_text.replace('[join]', '')
# Replace the split tag with line breaks and an optional split
verse_text = re.sub('\[splits?\]', '\r\n[---]', verse_text)
verse_text = re.sub(r'\[splits?\]', '\r\n[---]', verse_text)
# Handle translations
if lyrics.IsDualLanguage:
verse_text = self.handle_translation(verse_text)
# Remove comments
verse_text = re.sub('\(.*?\)\r\n', '', verse_text, flags=re.IGNORECASE)
verse_text = re.sub(r'\(.*?\)\r\n', '', verse_text, flags=re.IGNORECASE)
self.add_verse(verse_text, verse_def)
self.finish()

View File

@ -70,7 +70,7 @@ class PowerSongImport(SongImport):
"""
Checks if source is a PowerSong 1.0 folder:
* is a directory
* contains at least one \*.song file
* contains at least one * .song file
:param openlp.core.common.path.Path import_source: Should be a Path object that fulfills the above criteria
:return: If the source is valid

View File

@ -51,7 +51,7 @@ class PresentationManagerImport(SongImport):
encoding = get_file_encoding(file_path)['encoding']
# Open file with detected encoding and remove encoding declaration
text = file_path.read_text(encoding=encoding)
text = re.sub('.+\?>\n', '', text)
text = re.sub(r'.+\?>\n', '', text)
try:
tree = etree.fromstring(text, parser=etree.XMLParser(recover=True))
except ValueError:

View File

@ -333,7 +333,7 @@ class SongsOfFellowshipImport(OpenOfficeImport):
There is a complicated word "One", which is sometimes lower and
sometimes upper depending on context. Never mind, keep it lower.
"""
text_arr = re.split('(\W+)', text)
text_arr = re.split(r'(\W+)', text)
text_arr[0] = text_arr[0].capitalize()
for i in range(1, len(text_arr)):
# Do not translate these. Fixed strings in SOF song file

View File

@ -142,7 +142,7 @@ class WorshipAssistantImport(SongImport):
# drop the square brackets
right_bracket = line.find(']')
content = line[1:right_bracket].lower()
match = re.match('(\D*)(\d+)', content)
match = re.match(r'(\D*)(\d+)', content)
if match is not None:
verse_tag = match.group(1)
verse_num = match.group(2)

View File

@ -81,7 +81,7 @@ def get_version():
latest = output.decode('utf-8').split(':')[0]
version_string = latest == revision and tag or 'r%s' % latest
# Save decimal version in case we need to do a portable build.
version = latest == revision and tag or '%s.%s' % (tag, latest)
version = latest == revision and tag or '%s-bzr%s' % (tag, latest)
return version_string, version
@ -92,21 +92,24 @@ def get_yml(branch, build_type):
f = open('appveyor.yml')
yml_text = f.read()
f.close()
yml_text = yml_text.replace('BRANCHNAME', branch)
version_string, version = get_version()
yml_text = yml_text.replace('TAG', version)
if build_type in ['openlp', 'trunk']:
yml_text = yml_text.replace('BRANCHPATH', '~openlp-core/openlp/trunk')
yml_text = yml_text.replace('BUILD_DOCS', '$TRUE')
else:
yml_text = yml_text.replace('BRANCHPATH', branch.split(':')[1])
yml_text = yml_text.replace('BUILD_DOCS', '$FALSE')
return yml_text
return yml_text, version_string
def hook(webhook_url, yml):
def hook(webhook_url, branch, build_type):
"""
Activate the webhook to start the build
"""
yml, version_string = get_yml(branch, build_type)
webhook_element['config'] = yml
webhook_element['commit']['message'] = 'Building ' + branch
version_string, version = get_version()
webhook_element['commit']['id'] = version_string
request = urllib.request.Request(webhook_url)
request.add_header('Content-Type', 'application/json;charset=utf-8')
@ -137,7 +140,7 @@ else:
if build_type not in ['dev', 'trunk', 'openlp']:
print('Invalid build type\nUsage: %s <webhook-url> <branch> <dev|trunk|openlp>' % sys.argv[0])
exit()
hook(webhook_url, get_yml(branch, build_type))
hook(webhook_url, branch, build_type)
# Wait 5 seconds to make sure the hook has been triggered
time.sleep(5)
get_appveyor_build_url(build_type)

View File

@ -1,18 +1,17 @@
version: OpenLP-win-ci-b{build}
init:
- choco install -y --force bzr
- set PATH=C:\Program Files (x86)\Bazaar;%PATH%
clone_script:
- bzr checkout --lightweight BRANCHNAME openlp-branch
- curl -L https://bazaar.launchpad.net/BRANCHPATH/tarball -o sourcecode.tar.gz
- 7z e sourcecode.tar.gz
- 7z x sourcecode.tar
- mv BRANCHPATH openlp-branch
environment:
PYTHON: C:\\Python34
install:
# Install dependencies from pypi
- "%PYTHON%\\python.exe -m pip install sqlalchemy alembic chardet beautifulsoup4 Mako nose mock pyodbc==4.0.8 psycopg2 pypiwin32 pyenchant websockets asyncio waitress six webob requests"
- "%PYTHON%\\python.exe -m pip install sqlalchemy alembic chardet beautifulsoup4 Mako nose mock pyodbc==4.0.8 psycopg2 pypiwin32==219 pyenchant websockets asyncio waitress six webob requests"
# Install mysql dependency
- "%PYTHON%\\python.exe -m pip install http://cdn.mysql.com/Downloads/Connector-Python/mysql-connector-python-2.0.4.zip#md5=3df394d89300db95163f17c843ef49df"
# Download and install lxml and pyicu (originally from http://www.lfd.uci.edu/~gohlke/pythonlibs/)
@ -74,10 +73,10 @@ after_test:
7z x documentation.tar
mv ~openlp-core/openlp/documentation documentation
cd packaging
&"$env:PYTHON\python.exe" builders/windows-builder.py --skip-update -c windows/config-appveyor.ini -b ../openlp-branch -d ../documentation --portable
&"$env:PYTHON\python.exe" builders/windows-builder.py --skip-update -c windows/config-appveyor.ini -b ../openlp-branch -d ../documentation --portable --tag-override TAG
} else {
cd packaging
&"$env:PYTHON\python.exe" builders/windows-builder.py --skip-update --skip-translations -c windows/config-appveyor.ini -b ../openlp-branch --portable
&"$env:PYTHON\python.exe" builders/windows-builder.py --skip-update --skip-translations -c windows/config-appveyor.ini -b ../openlp-branch --portable --tag-override TAG
}
artifacts:

View File

@ -124,7 +124,7 @@ def get_merge_info(url):
script_tag = soup.find('script', attrs={"id": "codereview-script"})
content = script_tag.contents[0]
start_pos = content.find('source_revid') + 16
pattern = re.compile('.*\w-\d\d\d\d\d+')
pattern = re.compile(r'.*\w-\d\d\d\d\d+')
match = pattern.match(content[start_pos:])
merge_info['author_email'] = match.group()[:-15]
# Launchpad doesn't supply the author's true name, so we'll just grab whatever they use for display on LP

View File

@ -15,5 +15,10 @@ ignore = E402
[pycodestyle]
exclude = resources.py,vlc.py
max-line-length = 120
ignore = E402
# Ignoring:
# E402...
# E722 do not use bare 'except'
# W503 line break before binary operator
# W504 line break after binary operator
ignore = E402,E722,W503,W504

View File

@ -85,7 +85,7 @@ class TestApiTab(TestCase, TestMixin):
ip_address = self.form.get_ip_address(ZERO_URL)
# THEN: the default ip address will be returned
assert re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), \
assert re.match(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), \
'The return value should be a valid ip address'
assert ip_address in ip4_list, 'The return address should be in the list of local IP addresses'

View File

@ -27,13 +27,14 @@ import tempfile
from unittest import TestCase
from unittest.mock import MagicMock, patch
from openlp.core.common.httputils import get_user_agent, get_web_page, get_url_file_size, download_file
from openlp.core.common.httputils import ProxyMode, download_file, get_proxy_settings, get_url_file_size, \
get_user_agent, get_web_page
from openlp.core.common.path import Path
from openlp.core.common.settings import Settings
from tests.helpers.testmixin import TestMixin
class TestHttpUtils(TestCase, TestMixin):
"""
A test suite to test out various http helper functions.
"""
@ -240,3 +241,119 @@ class TestHttpUtils(TestCase, TestMixin):
# THEN: socket.timeout should have been caught
# NOTE: Test is if $tmpdir/tempfile is still there, then test fails since ftw deletes bad downloaded files
assert os.path.exists(self.tempfile) is False, 'tempfile should have been deleted'
class TestGetProxySettings(TestCase, TestMixin):
def setUp(self):
self.build_settings()
self.addCleanup(self.destroy_settings)
@patch('openlp.core.common.httputils.Settings')
def test_mode_arg_specified(self, MockSettings):
"""
Test that the argument is used rather than reading the 'advanced/proxy mode' setting
"""
# GIVEN: Mocked settings
mocked_settings = MagicMock()
MockSettings.return_value = mocked_settings
# WHEN: Calling `get_proxy_settings` with the mode arg specified
get_proxy_settings(mode=ProxyMode.NO_PROXY)
# THEN: The mode arg should have been used rather than looking it up in the settings
mocked_settings.value.assert_not_called()
@patch('openlp.core.common.httputils.Settings')
def test_mode_incorrect_arg_specified(self, MockSettings):
"""
Test that the system settings are used when the mode arg specieied is invalid
"""
# GIVEN: Mocked settings
mocked_settings = MagicMock()
MockSettings.return_value = mocked_settings
# WHEN: Calling `get_proxy_settings` with an invalid mode arg specified
result = get_proxy_settings(mode='qwerty')
# THEN: An None should be returned
mocked_settings.value.assert_not_called()
assert result is None
def test_no_proxy_mode(self):
"""
Test that a dictionary with http and https values are set to None is returned, when `NO_PROXY` mode is specified
"""
# GIVEN: A `proxy mode` setting of NO_PROXY
Settings().setValue('advanced/proxy mode', ProxyMode.NO_PROXY)
# WHEN: Calling `get_proxy_settings`
result = get_proxy_settings()
# THEN: The returned value should be a dictionary with http and https values set to None
assert result == {'http': None, 'https': None}
def test_system_proxy_mode(self):
"""
Test that None is returned, when `SYSTEM_PROXY` mode is specified
"""
# GIVEN: A `proxy mode` setting of SYSTEM_PROXY
Settings().setValue('advanced/proxy mode', ProxyMode.SYSTEM_PROXY)
# WHEN: Calling `get_proxy_settings`
result = get_proxy_settings()
# THEN: The returned value should be None
assert result is None
def test_manual_proxy_mode_no_auth(self):
"""
Test that the correct proxy addresses are returned when basic authentication is not used
"""
# GIVEN: A `proxy mode` setting of MANUAL_PROXY with proxy servers, but no auth credentials are supplied
Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY)
Settings().setValue('advanced/proxy http', 'testhttp.server:port')
Settings().setValue('advanced/proxy https', 'testhttps.server:port')
Settings().setValue('advanced/proxy username', '')
Settings().setValue('advanced/proxy password', '')
# WHEN: Calling `get_proxy_settings`
result = get_proxy_settings()
# THEN: The returned value should be the proxy servers without authentication
assert result == {'http': 'http://testhttp.server:port', 'https': 'https://testhttps.server:port'}
def test_manual_proxy_mode_auth(self):
"""
Test that the correct proxy addresses are returned when basic authentication is used
"""
# GIVEN: A `proxy mode` setting of MANUAL_PROXY with proxy servers and auth credentials supplied
Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY)
Settings().setValue('advanced/proxy http', 'testhttp.server:port')
Settings().setValue('advanced/proxy https', 'testhttps.server:port')
Settings().setValue('advanced/proxy username', 'user')
Settings().setValue('advanced/proxy password', 'pass')
# WHEN: Calling `get_proxy_settings`
result = get_proxy_settings()
# THEN: The returned value should be the proxy servers with the authentication credentials
assert result == {'http': 'http://user:pass@testhttp.server:port',
'https': 'https://user:pass@testhttps.server:port'}
def test_manual_proxy_mode_no_servers(self):
"""
Test that the system proxies are overidden when the MANUAL_PROXY mode is specified, but no server addresses are
supplied
"""
# GIVEN: A `proxy mode` setting of MANUAL_PROXY with no servers specified
Settings().setValue('advanced/proxy mode', ProxyMode.MANUAL_PROXY)
Settings().setValue('advanced/proxy http', '')
Settings().setValue('advanced/proxy https', '')
Settings().setValue('advanced/proxy username', 'user')
Settings().setValue('advanced/proxy password', 'pass')
# WHEN: Calling `get_proxy_settings`
result = get_proxy_settings()
# THEN: The returned value should be the proxy servers set to None
assert result == {'http': None, 'https': None}

View File

@ -238,8 +238,8 @@ class TestInit(TestCase, TestMixin):
Test the clean_filename() function
"""
# GIVEN: A invalid file name and the valid file name.
invalid_name = 'A_file_with_invalid_characters_[\\/:\*\?"<>\|\+\[\]%].py'
wanted_name = 'A_file_with_invalid_characters______________________.py'
invalid_name = 'A_file_with_invalid_characters_[\\/:*?"<>|+[]%].py'
wanted_name = 'A_file_with_invalid_characters________________.py'
# WHEN: Clean the name.
result = clean_filename(invalid_name)

View File

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2017 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 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 #
###############################################################################
"""
Functional tests to test calls for network interfaces.
"""
from unittest import TestCase
from unittest.mock import MagicMock, call, patch
import openlp.core.common
from openlp.core.common import get_local_ip4
from tests.helpers.testmixin import TestMixin
lo_address_attrs = {'isValid.return_value': True,
'flags.return_value': True,
'InterfaceFlags.return_value': True,
'name.return_value': 'lo',
'broadcast.toString.return_value': '127.0.0.255',
'netmask.toString.return_value': '255.0.0.0',
'prefixLength.return_value': 8,
'ip.protocol.return_value': True}
class TestInterfaces(TestCase, TestMixin):
"""
A test suite to test out functions/methods that use network interface(s).
"""
def setUp(self):
"""
Create an instance and a few example actions.
"""
self.build_settings()
self.ip4_lo_address = MagicMock()
self.ip4_lo_address.configure_mock(**lo_address_attrs)
def tearDown(self):
"""
Clean up
"""
self.destroy_settings()
@patch.object(openlp.core.common, 'log')
def test_ip4_no_interfaces(self, mock_log):
"""
Test no interfaces available
"""
# GIVEN: Test environment
call_warning = [call('No active IPv4 network interfaces detected')]
with patch('openlp.core.common.QNetworkInterface') as mock_newtork_interface:
mock_newtork_interface.allInterfaces.return_value = []
# WHEN: get_local_ip4 is called
ifaces = get_local_ip4()
# THEN: There should not be any interfaces detected
assert not ifaces, 'There should have been no active interfaces'
mock_log.warning.assert_has_calls(call_warning)

View File

@ -12,7 +12,7 @@ from openlp.core.lib.htmlbuilder import build_html, build_background_css, build_
from openlp.core.lib.theme import HorizontalType, VerticalType
from tests.helpers.testmixin import TestMixin
HTML = """
HTML = r"""
<!DOCTYPE html>
<html>
<head>
@ -121,7 +121,7 @@ HTML = """
}
function show_text(new_text){
var match = /-webkit-text-fill-color:[^;"]+/gi;
var match = /-webkit-text-fill-color:[^;\"]+/gi;
if(timer != null)
clearTimeout(timer);
/*

View File

@ -99,7 +99,7 @@ class TestStartFileRenameForm(TestCase, TestMixin):
# GIVEN: QLineEdit with a validator set with illegal file name characters.
# WHEN: 'Typing' a string containing invalid file characters.
QtTest.QTest.keyClicks(self.form.file_name_edit, 'I/n\\v?a*l|i<d> \F[i\l]e" :N+a%me')
QtTest.QTest.keyClicks(self.form.file_name_edit, r'I/n\\v?a*l|i<d> \F[i\l]e" :N+a%me')
# THEN: The text in the QLineEdit should be the same as the input string with the invalid characters filtered
# out.

View File

@ -0,0 +1,174 @@
# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4
###############################################################################
# OpenLP - Open Source Lyrics Projection #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2018 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 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 #
###############################################################################
"""
Module to test the custom widgets.
"""
from unittest import TestCase
from unittest.mock import MagicMock, call, patch
from openlp.core.common.registry import Registry
from openlp.core.common.settings import ProxyMode
from openlp.core.widgets.widgets import ProxyWidget
from tests.helpers.testmixin import TestMixin
class TestProxyWidget(TestCase, TestMixin):
"""
Test the EditCustomForm.
"""
def setUp(self):
"""
Create the UI
"""
Registry.create()
self.setup_application()
def test_radio_button_exclusivity_no_proxy(self):
"""
Test that only one radio button can be checked at a time, and that the line edits are only enabled when the
`manual_proxy_radio` is checked
"""
# GIVEN: An instance of the `openlp.core.common.widgets.widgets.ProxyWidget` with a radio already checked
proxy_widget = ProxyWidget()
proxy_widget.manual_proxy_radio.setChecked(True)
# WHEN: 'Checking' the `no_proxy_radio` button
proxy_widget.no_proxy_radio.setChecked(True)
# THEN: The other radio buttons should not be checked and the line edits should not be enabled
assert proxy_widget.use_sysem_proxy_radio.isChecked() is False
assert proxy_widget.manual_proxy_radio.isChecked() is False
assert proxy_widget.http_edit.isEnabled() is False
assert proxy_widget.https_edit.isEnabled() is False
assert proxy_widget.username_edit.isEnabled() is False
assert proxy_widget.password_edit.isEnabled() is False
def test_radio_button_exclusivity_system_proxy(self):
"""
Test that only one radio button can be checked at a time, and that the line edits are only enabled when the
`manual_proxy_radio` is checked
"""
# GIVEN: An instance of the `openlp.core.common.widgets.widgets.ProxyWidget` with a radio already checked
proxy_widget = ProxyWidget()
proxy_widget.manual_proxy_radio.setChecked(True)
# WHEN: 'Checking' the `use_sysem_proxy_radio` button
proxy_widget.use_sysem_proxy_radio.setChecked(True)
# THEN: The other radio buttons should not be checked and the line edits should not be enabled
assert proxy_widget.no_proxy_radio.isChecked() is False
assert proxy_widget.manual_proxy_radio.isChecked() is False
assert proxy_widget.http_edit.isEnabled() is False
assert proxy_widget.https_edit.isEnabled() is False
assert proxy_widget.username_edit.isEnabled() is False
assert proxy_widget.password_edit.isEnabled() is False
def test_radio_button_exclusivity_manual_proxy(self):
"""
Test that only one radio button can be checked at a time, and that the line edits are only enabled when the
`manual_proxy_radio` is checked
"""
# GIVEN: An instance of the `openlp.core.common.widgets.widgets.ProxyWidget` with a radio already checked
proxy_widget = ProxyWidget()
proxy_widget.no_proxy_radio.setChecked(True)
# WHEN: 'Checking' the `manual_proxy_radio` button
proxy_widget.manual_proxy_radio.setChecked(True)
# THEN: The other radio buttons should not be checked and the line edits should be enabled
assert proxy_widget.no_proxy_radio.isChecked() is False
assert proxy_widget.use_sysem_proxy_radio.isChecked() is False
assert proxy_widget.http_edit.isEnabled() is True
assert proxy_widget.https_edit.isEnabled() is True
assert proxy_widget.username_edit.isEnabled() is True
assert proxy_widget.password_edit.isEnabled() is True
def test_proxy_widget_load_default_settings(self):
"""
Test that the default settings are loaded from the config correctly
"""
# GIVEN: And instance of the widget with default settings
proxy_widget = ProxyWidget()
# WHEN: Calling the `load` method
proxy_widget.load()
# THEN: The widget should be in its default state
assert proxy_widget.use_sysem_proxy_radio.isChecked() is True
assert proxy_widget.http_edit.text() == ''
assert proxy_widget.https_edit.text() == ''
assert proxy_widget.username_edit.text() == ''
assert proxy_widget.password_edit.text() == ''
@patch.object(ProxyWidget, 'load')
@patch('openlp.core.widgets.widgets.Settings')
def test_proxy_widget_save_no_proxy_settings(self, settings_patcher, proxy_widget_load_patcher):
"""
Test that the settings are saved correctly
"""
# GIVEN: A Mocked settings instance of the proxy widget with some known values set
settings_instance = MagicMock()
settings_patcher.return_value = settings_instance
proxy_widget = ProxyWidget()
proxy_widget.no_proxy_radio.setChecked(True)
proxy_widget.http_edit.setText('')
proxy_widget.https_edit.setText('')
proxy_widget.username_edit.setText('')
proxy_widget.password_edit.setText('')
# WHEN: Calling save
proxy_widget.save()
# THEN: The settings should be set as expected
settings_instance.setValue.assert_has_calls(
[call('advanced/proxy mode', ProxyMode.NO_PROXY),
call('advanced/proxy http', ''),
call('advanced/proxy https', ''),
call('advanced/proxy username', ''),
call('advanced/proxy password', '')])
@patch.object(ProxyWidget, 'load')
@patch('openlp.core.widgets.widgets.Settings')
def test_proxy_widget_save_manual_settings(self, settings_patcher, proxy_widget_load_patcher):
"""
Test that the settings are saved correctly
"""
# GIVEN: A Mocked and instance of the proxy widget with some known values set
settings_instance = MagicMock()
settings_patcher.return_value = settings_instance
proxy_widget = ProxyWidget()
proxy_widget.manual_proxy_radio.setChecked(True)
proxy_widget.http_edit.setText('http_proxy_server:port')
proxy_widget.https_edit.setText('https_proxy_server:port')
proxy_widget.username_edit.setText('username')
proxy_widget.password_edit.setText('password')
# WHEN: Calling save
proxy_widget.save()
# THEN: The settings should be set as expected
settings_instance.setValue.assert_has_calls(
[call('advanced/proxy mode', ProxyMode.MANUAL_PROXY),
call('advanced/proxy http', 'http_proxy_server:port'),
call('advanced/proxy https', 'https_proxy_server:port'),
call('advanced/proxy username', 'username'),
call('advanced/proxy password', 'password')])

View File

@ -68,7 +68,7 @@ class TestPylint(TestCase):
# WHEN: Running pylint
(pylint_stdout, pylint_stderr) = \
lint.py_run('openlp --errors-only --disable={disabled} --enable={enabled} '
lint.py_run('openlp --errors-only -j 4 --disable={disabled} --enable={enabled} '
'--reports=no --output-format=parseable'.format(disabled=disabled_checks,
enabled=enabled_checks),
**pylint_kwargs)
@ -88,7 +88,7 @@ class TestPylint(TestCase):
filtered_output = ''
for line in pylint_output.splitlines():
# Filter out module info lines
if line.startswith('**'):
if '***' in line:
continue
# Filter out undefined-variable error releated to WindowsError
elif 'undefined-variable' in line and 'WindowsError' in line: