diff --git a/pywikitools/test/data/Heart-32.png b/pywikitools/test/data/Heart-32.png
new file mode 100644
index 0000000..f5d6169
Binary files /dev/null and b/pywikitools/test/data/Heart-32.png differ
diff --git a/pywikitools/test/data/example.html b/pywikitools/test/data/example.html
new file mode 100644
index 0000000..dd26d4b
--- /dev/null
+++ b/pywikitools/test/data/example.html
@@ -0,0 +1,74 @@
+
+
Бог дал нам Свое Слово, чтобы мы знали, кто Он и чего хочет. Мы читаем Библию, чтобы лучше понять Бога и делать то, что Он говорит. Это самое важное. Но возникает вопрос: с чего начать чтение? В зависимости от вашей жизненной ситуации и прошлого опыта мы предлагаем вам разные варианты того, с чего можно начать. Во всех случаях основа остается одной и той же, но при этом для чтения предлагаются чуть разные книги. Ниже вы найдете "стандартный" план, который предлагает начать чтение с Луки и деяний, а также план, в котором в начале читается история Сотворения и вся книга Бытия, а затем Новый Завет.
+
+
+
- Включает в себя подборку из семи историй из Нового Завета
+- Хорошо для людей с "современным западным " бэкграундом.
+
+
- Предлагает начать чтение с Книги Бытия, Евангелия от Матфея и Деяний Апостолов
+- Хорошо для людей, например, из мусульманского общества.
+
Подсказки для чтения Библии
+
Когда вы только приступаете к чтению Библии, начните с тех книг, которые приведены ниже:
+
Евангелие от Луки
+Деяния апостолов
+
Каждый раз, перед тем как читать Писание, попросите Бога помочь вам понять, то что вы будете читать.
+
Ответьте на несколько вопросов (см.разворот) чтобы лучше понять, о чем говорит текст.
+
Поделитесь с другими тем что вы прочитали, а также запишите свои мысли, вопросы и то что говорит вам Бог через прочитанное.
+
+
Читая Библию вместе (в группе)
+
План встречи:
+
- 1. Как дела?
+- 2. Подотчетность
+
+- Как мы применили то, что узнали в прошлый раз?
+- 3. Благодарение и молитва
+
+- Что хорошего произошло с нами на прошлой неделе? Поблагодарите Бога.
+
+
+
- 4. Чтение
+- Прочитаем отрывок вместе. Попросить у Бога помощи чтобы понять стихи, которые мы прочитаем.
+- 5. Повторение
+- Вместе, не смотря в книгу, повторяем пройденный материал.
+- 6. Понимание
+- Ответим на следующие вопросы:
+
| Разум: Что мы здесь узнаем из прочитанного? (О Боге / о людях) |
+ |
+Сердце: Что касается моего сердца? (Как они чувствовали себя? Что я чувствую относительно ... ?) |
+ |
+Руки: Как я могу это применить? (Пример для подражания? С кем мы могли бы этим поделиться?) |
+
- 7. Цели
+
+- Какая у нас цель до следующего собрания?
+- 8. Молитва
+
+- Время помолиться друг за друга.
+
Важно:
+
- Придерживаемся Библейского отрывка
+- Все принимают участие
+- Поощряем друг друга
+
\ No newline at end of file
diff --git "a/pywikitools/test/data/htmlexport/ru/files/\320\230\321\201\321\206\320\265\320\273\320\265\320\275\320\270\320\265.html" "b/pywikitools/test/data/htmlexport/ru/files/\320\230\321\201\321\206\320\265\320\273\320\265\320\275\320\270\320\265.html"
new file mode 100644
index 0000000..de81a9d
--- /dev/null
+++ "b/pywikitools/test/data/htmlexport/ru/files/\320\230\321\201\321\206\320\265\320\273\320\265\320\275\320\270\320\265.html"
@@ -0,0 +1,49 @@
+Исцеление
+Бог дал нам Свое Слово, чтобы мы знали, кто Он и чего хочет. Мы читаем Библию, чтобы лучше понять Бога и делать то, что Он говорит. Это самое важное. Но возникает вопрос: с чего начать чтение? В зависимости от вашей жизненной ситуации и прошлого опыта мы предлагаем вам разные варианты того, с чего можно начать. Во всех случаях основа остается одной и той же, но при этом для чтения предлагаются чуть разные книги. Ниже вы найдете "стандартный" план, который предлагает начать чтение с Луки и деяний, а также план, в котором в начале читается история Сотворения и вся книга Бытия, а затем Новый Завет.
+
+
+- Включает в себя подборку из семи историй из Нового Завета
+- Хорошо для людей с "современным западным " бэкграундом.
+
+- Предлагает начать чтение с Книги Бытия, Евангелия от Матфея и Деяний Апостолов
+- Хорошо для людей, например, из мусульманского общества.
+Подсказки для чтения Библии
+Когда вы только приступаете к чтению Библии, начните с тех книг, которые приведены ниже:
+
Евангелие от Луки
+Деяния апостолов
+
Каждый раз, перед тем как читать Писание, попросите Бога помочь вам понять, то что вы будете читать.
+
Ответьте на несколько вопросов (см.разворот) чтобы лучше понять, о чем говорит текст.
+
Поделитесь с другими тем что вы прочитали, а также запишите свои мысли, вопросы и то что говорит вам Бог через прочитанное.
+
+Читая Библию вместе (в группе)
+План встречи:
+- 1. Как дела?
+- 2. Подотчетность
+
+- Как мы применили то, что узнали в прошлый раз?
+- 3. Благодарение и молитва
+
+- Что хорошего произошло с нами на прошлой неделе? Поблагодарите Бога.
+
+
+- 4. Чтение
+- Прочитаем отрывок вместе. Попросить у Бога помощи чтобы понять стихи, которые мы прочитаем.
+- 5. Повторение
+- Вместе, не смотря в книгу, повторяем пройденный материал.
+- 6. Понимание
+- Ответим на следующие вопросы:
+ | Разум: Что мы здесь узнаем из прочитанного? (О Боге / о людях) |
+ |
+Сердце: Что касается моего сердца? (Как они чувствовали себя? Что я чувствую относительно ... ?) |
+ |
+Руки: Как я могу это применить? (Пример для подражания? С кем мы могли бы этим поделиться?) |
+
- 7. Цели
+
+- Какая у нас цель до следующего собрания?
+- 8. Молитва
+
+- Время помолиться друг за друга.
+Важно:
+- Придерживаемся Библейского отрывка
+- Все принимают участие
+- Поощряем друг друга
diff --git a/pywikitools/test/data/htmlexport/ru/structure/content.json b/pywikitools/test/data/htmlexport/ru/structure/content.json
new file mode 100644
index 0000000..7a99795
--- /dev/null
+++ b/pywikitools/test/data/htmlexport/ru/structure/content.json
@@ -0,0 +1,48 @@
+{
+ "language_code": "ru",
+ "english_name": "Russian",
+ "worksheets": [
+ {
+ "page": "Healing",
+ "title": "\u0418\u0441\u0446\u0435\u043b\u0435\u043d\u0438\u0435",
+ "filename": "\u0418\u0441\u0446\u0435\u043b\u0435\u043d\u0438\u0435.html",
+ "version": "1.0",
+ "pdf": "\u0418\u0441\u0446\u0435\u043b\u0435\u043d\u0438\u0435.pdf"
+ },
+ {
+ "page": "My_Story_with_God",
+ "title": "\u041c\u043e\u0451 \u0441\u0432\u0438\u0434\u0435\u0442\u0435\u043b\u044c\u0441\u0442\u0432\u043e",
+ "filename": "\u041c\u043e\u0451_\u0441\u0432\u0438\u0434\u0435\u0442\u0435\u043b\u044c\u0441\u0442\u0432\u043e.html",
+ "version": "1.1",
+ "pdf": "\u041c\u043e\u0451_\u0441\u0432\u0438\u0434\u0435\u0442\u0435\u043b\u044c\u0441\u0442\u0432\u043e.pdf"
+ },
+ {
+ "page": "Bible_Reading_Hints",
+ "title": "\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438 \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0411\u0438\u0431\u043b\u0438\u0438",
+ "filename": "\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438_\u0434\u043b\u044f_\u0447\u0442\u0435\u043d\u0438\u044f_\u0411\u0438\u0431\u043b\u0438\u0438.html",
+ "version": "2.0",
+ "pdf": "\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438_\u0434\u043b\u044f_\u0447\u0442\u0435\u043d\u0438\u044f_\u0411\u0438\u0431\u043b\u0438\u0438.pdf"
+ },
+ {
+ "page": "Bible_Reading_Hints_(Seven_Stories_full_of_Hope)",
+ "title": "\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438 \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0411\u0438\u0431\u043b\u0438\u0438 (\u0421\u0435\u043c\u044c \u0438\u0441\u0442\u043e\u0440\u0438\u0439, \u043f\u043e\u043b\u043d\u044b\u0445 \u043d\u0430\u0434\u0435\u0436\u0434\u044b)",
+ "filename": "\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438_\u0434\u043b\u044f_\u0447\u0442\u0435\u043d\u0438\u044f_\u0411\u0438\u0431\u043b\u0438\u0438_(\u0421\u0435\u043c\u044c_\u0438\u0441\u0442\u043e\u0440\u0438\u0439,_\u043f\u043e\u043b\u043d\u044b\u0445_\u043d\u0430\u0434\u0435\u0436\u0434\u044b).html",
+ "version": "2.0",
+ "pdf": "\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438_\u0434\u043b\u044f_\u0447\u0442\u0435\u043d\u0438\u044f_\u0411\u0438\u0431\u043b\u0438\u0438_(\u0421\u0435\u043c\u044c_\u0438\u0441\u0442\u043e\u0440\u0438\u0439,_\u043f\u043e\u043b\u043d\u044b\u0445_\u043d\u0430\u0434\u0435\u0436\u0434\u044b).pdf"
+ },
+ {
+ "page": "Bible_Reading_Hints_(Starting_with_the_Creation)",
+ "title": "\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438 \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0411\u0438\u0431\u043b\u0438\u0438 (\u041d\u0430\u0447\u0438\u043d\u0430\u044f \u0441 \u0421\u043e\u0442\u0432\u043e\u0440\u0435\u043d\u0438\u044f)",
+ "filename": "\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438_\u0434\u043b\u044f_\u0447\u0442\u0435\u043d\u0438\u044f_\u0411\u0438\u0431\u043b\u0438\u0438_(\u041d\u0430\u0447\u0438\u043d\u0430\u044f_\u0441_\u0421\u043e\u0442\u0432\u043e\u0440\u0435\u043d\u0438\u044f).html",
+ "version": "2.0",
+ "pdf": "\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0438_\u0434\u043b\u044f_\u0447\u0442\u0435\u043d\u0438\u044f_\u0411\u0438\u0431\u043b\u0438\u0438_(\u041d\u0430\u0447\u0438\u043d\u0430\u044f_\u0441_\u0421\u043e\u0442\u0432\u043e\u0440\u0435\u043d\u0438\u044f).pdf"
+ },
+ {
+ "page": "Four_Kinds_of_Disciples",
+ "title": "\u0427\u0435\u0442\u044b\u0440\u0435 \u0432\u0438\u0434\u0430 \u0443\u0447\u0435\u043d\u0438\u043a\u043e\u0432",
+ "filename": "\u0427\u0435\u0442\u044b\u0440\u0435_\u0432\u0438\u0434\u0430_\u0443\u0447\u0435\u043d\u0438\u043a\u043e\u0432.html",
+ "version": "1.1",
+ "pdf": "\u0427\u0435\u0442\u044b\u0440\u0435_\u0432\u0438\u0434\u0430_\u0443\u0447\u0435\u043d\u0438\u043a\u043e\u0432.pdf"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/pywikitools/test/test_export_html.py b/pywikitools/test/test_export_html.py
new file mode 100644
index 0000000..2817635
--- /dev/null
+++ b/pywikitools/test/test_export_html.py
@@ -0,0 +1,158 @@
+"""
+Test all the functionalities of export_html.py
+- creating folders if necessary
+- get html contents for worksheets from API
+- Export htmls into local directory
+- download image files into local directory
+- export content.json (with current content)
+
+Run tests:
+ python3 pywikitools/test/test_export_html.py
+"""
+
+from os.path import abspath, dirname, join, exists
+import tempfile
+import json
+import unittest
+
+import requests
+from unittest.mock import Mock, patch
+
+from pywikitools.fortraininglib import ForTrainingLib
+from pywikitools.resourcesbot.changes import ChangeLog, ChangeType
+from pywikitools.resourcesbot.data_structures import LanguageInfo, json_decode
+from pywikitools.resourcesbot.export_html import ExportHTML
+
+
+class TestExportHTML(unittest.TestCase):
+ @classmethod
+ def setUpClass(self):
+ with open(join(dirname(abspath(__file__)), "data", "ru.json"), 'r') as f:
+ self.language_info: LanguageInfo = json.load(f, object_hook=json_decode)
+ # Create a pseudo English LanguageInfo - enough for our testing purposes (version is always the same)
+ self.english_info = LanguageInfo("en", "English")
+ for worksheet, info in self.language_info.worksheets.items():
+ self.english_info.add_worksheet_info(worksheet, info)
+ self.fortraininglib = ForTrainingLib("https://test.4training.net")
+
+ @patch("os.makedirs")
+ def test_run_with_empty_base_folder(self, mock_makedirs):
+ with self.assertLogs('pywikitools.resourcesbot.export_html', level='WARNING'):
+ export_html = ExportHTML(self.fortraininglib, "", force_rewrite=False)
+
+ # run() should return without doing anything because of empty base folder
+ export_html.run(self.language_info, self.english_info, ChangeLog(), ChangeLog())
+ mock_makedirs.assert_not_called()
+
+ def test_run_filters_unfinished_worksheets(self):
+ fortraininglib_mock = Mock()
+ with tempfile.TemporaryDirectory() as temp_dir:
+ export_html = ExportHTML(fortraininglib_mock, temp_dir, force_rewrite=False)
+ with patch.object(export_html, 'has_relevant_change', return_value=False) as mock_has_relevant_change:
+ export_html.run(self.language_info, self.english_info, ChangeLog(), ChangeLog())
+ calls = [call[0][0] for call in mock_has_relevant_change.call_args_list]
+ # Healing is finished, Church is an unfinished worksheet
+ self.assertIn('Healing', calls)
+ self.assertNotIn('Church', calls)
+
+ fortraininglib_mock.get_page_html.assert_not_called()
+ # Verify that `language_info` remains unchanged
+ self.assertIsNotNone(self.language_info.get_worksheet('Church'))
+
+ def test_directory_structure_creation(self):
+ with tempfile.TemporaryDirectory() as temp_dir:
+ # Create target paths to check later
+ base_folder = join(temp_dir, "not_existing_yet")
+
+ # Base folder should be created directly when initializing the class
+ export_html = ExportHTML(self.fortraininglib, base_folder, force_rewrite=False)
+ self.assertTrue(exists(base_folder))
+ export_html.run(self.language_info, self.english_info, ChangeLog(), ChangeLog())
+
+ # Assert that the right directories were created
+ self.assertTrue(exists(join(base_folder, "ru")))
+ self.assertTrue(exists(join(base_folder, "ru", "files/")))
+ self.assertTrue(exists(join(base_folder, "ru", "structure/")))
+
+ # assert that the method still works if the folders are already there
+ with self.assertNoLogs(level='WARNING'):
+ export_html.run(self.language_info, self.english_info, ChangeLog(), ChangeLog())
+
+ @patch('pywikitools.fortraininglib.ForTrainingLib.get_page_html')
+ def test_download_and_save_transformed_html_and_images(self, mock_get_page_html):
+ with open(join(dirname(abspath(__file__)), "data", "example.html"), 'r') as f:
+ mock_get_page_html.return_value = f.read()
+ changelog = ChangeLog()
+ changelog.add_change('Healing', ChangeType.UPDATED_WORKSHEET)
+ changelog.add_change("Church", ChangeType.NEW_WORKSHEET)
+
+ # Mock the response for the image download
+ response = requests.Response()
+ response.status_code = 200
+ with open(join(dirname(abspath(__file__)), "data", "Heart-32.png"), 'rb') as f:
+ response._content = f.read()
+
+ # Initialize the ExportHTML class with a valid base folder
+ with tempfile.TemporaryDirectory() as temp_dir:
+ export_html = ExportHTML(self.fortraininglib, temp_dir, force_rewrite=False)
+
+ with patch('requests.get', return_value=response):
+ export_html.run(self.language_info, self.english_info, changelog, ChangeLog())
+
+ # Assert the file was created correctly
+ path_to_transformed_html = join(temp_dir, 'ru', 'Исцеление.html')
+ self.assertTrue(exists(path_to_transformed_html))
+
+ # Assert the content is correct
+ expected_html = join(dirname(abspath(__file__)), "data", "htmlexport", "ru", "files", "Исцеление.html")
+ with open(path_to_transformed_html, 'r', encoding='utf-8') as test_file:
+ with open(expected_html, 'r', encoding='utf-8') as expected_file:
+ self.assertEqual(test_file.read(), expected_file.read())
+
+ self.assertTrue(exists(join(temp_dir, 'ru', 'files', 'Heart-32.png')))
+
+ path_to_contents = join(temp_dir, 'ru', 'structure', 'contents.json')
+ self.assertTrue(exists(path_to_contents))
+ with open(path_to_contents, 'r') as test_file:
+ with open(join(dirname(abspath(__file__)),
+ "data", "htmlexport", "ru", "structure", "content.json"), 'r') as expected_file:
+ self.assertEqual(expected_file.read(), test_file.read())
+
+ def test_complex_export_html(self):
+ with tempfile.TemporaryDirectory() as temp_dir:
+ ar_changelog = ChangeLog()
+ # normal worksheet
+ ar_changelog.add_change('Hearing_from_God', ChangeType.UPDATED_WORKSHEET)
+ expected_path_hearing = join(temp_dir, 'ar', 'الاستماع_من_الله.html')
+ # worksheet with images
+ ar_changelog.add_change('Time_with_God', ChangeType.UPDATED_WORKSHEET)
+ expected_path_time = join(temp_dir, 'ar', 'قضاء_وقت_مع_الله.html')
+ # unfinished worksheet -> shouldn't be exported
+ ar_changelog.add_change("Church", ChangeType.NEW_WORKSHEET)
+ expected_path_church = join(temp_dir, 'ar', 'كنيسة.html')
+ # normal worksheet -> will only be created with force rewrite
+ expected_path_prayer = join(temp_dir, 'ar', 'الصلاة.html')
+
+ with open(join(dirname(abspath(__file__)), "data", "ar.json"), 'r') as f:
+ ar_language_info: LanguageInfo = json.load(f, object_hook=json_decode)
+ with open(join(dirname(abspath(__file__)), "data", "en.json"), 'r') as f:
+ en_language_info: LanguageInfo = json.load(f, object_hook=json_decode)
+
+ export_html = ExportHTML(self.fortraininglib, temp_dir, force_rewrite=False)
+ export_html.run(ar_language_info, en_language_info, ar_changelog, ChangeLog())
+
+ self.assertTrue(exists(join(temp_dir, 'ar', 'files', 'Head-32.png')))
+ self.assertTrue(exists(expected_path_hearing))
+ self.assertTrue(exists(expected_path_time))
+ self.assertFalse(exists(expected_path_church))
+
+ # run with force rewrite
+ self.assertFalse(exists(expected_path_prayer))
+ export_html = ExportHTML(self.fortraininglib, temp_dir, force_rewrite=True)
+ with self.assertLogs():
+ export_html.run(ar_language_info, en_language_info, ar_changelog, ChangeLog())
+ self.assertTrue(exists(expected_path_prayer))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/setup.py b/setup.py
index 3090d48..0d371b2 100644
--- a/setup.py
+++ b/setup.py
@@ -19,17 +19,17 @@
setup(
author="4training",
author_email='pywikitools@4training.net',
- python_requires='>=3.5',
+ python_requires='>=3.10',
classifiers=[
'Development Status :: 2 - Pre-Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Natural Language :: English',
'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.5',
- 'Programming Language :: Python :: 3.6',
- 'Programming Language :: Python :: 3.7',
- 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.10',
+ 'Programming Language :: Python :: 3.11',
+ 'Programming Language :: Python :: 3.12',
+ 'Programming Language :: Python :: 3.13',
],
description="Python tools for mediawiki with the Translate plugin (some based on pywikibot)",
entry_points={