diff --git a/.bzrignore b/.bzrignore deleted file mode 100644 index d907fb5..0000000 --- a/.bzrignore +++ /dev/null @@ -1,13 +0,0 @@ -.git -.git-backup -.gitignore -__pycache__ -MANIFEST -build -doc/sphinx/build -side-view_ui.py -test-repository/bzr-repo/ -test-repository/git-repo/repo-branches/ -test-repository/git-repo/repo-twisted-branches/ -test-repository/git-repo/repo-two-branches/ -test-repository/git-repo/repo1/ diff --git a/.gitignore b/.gitignore index a588905..e7c50d1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,33 +1,75 @@ *~ +.pycache __pycache__ -build -doc/sphinx/build -# -gui-prototypes/side-view/side-view_ui.py -CodeReview/PatienceDiff/_patiencediff_c.cpython-34m.so -bzr-backup/ -gui-prototypes/qlog.ui -links/ -notes/bzr-qlog1.png -notes/diffview.py -notes/github1.png -notes/github2.png -notes/github3.png -notes/github4.png -notes/github5.png -notes/github6.png -notes/qbzr-qdiff.png -notes/qbzr-qlog.png -notes/qgit1.png -notes/qgit2.png -notes/qgit3.png -notes/qgit4.png -notes/qgit5.png -notes/qgit6.png -test-repository/ -test/Model.py -test/github3-test.py -test/swalign-fabrice/ -test/swalign-mbreese/ -test/token.txt -tools/rsync-vps.sh +.directory + +du.log +nogit/ +__TRASH__/ +review.json + +CodeReview.egg-info/ +MANIFEST +build/ +dist/ + +doc/sphinx/build/ +doc/sphinx/source/api/ + +src/CodeReview/GitHub/code/ +src/CodeReview/QmlApplication/rcc/CodeReviewRessource.py +src/CodeReview/QmlApplication/rcc/code-review.rcc +src/CodeReview/TextDistance/buggy.c +src/CodeReview/ovh.txt +src/CodeReview/share/icons/copy-icons.sh + +src/CodeReview/PatienceDiff/_patiencediff_c.cpython-314-x86_64-linux-gnu.so +src/CodeReview/TextDistance/levenshtein_distance_c.cpython-314-x86_64-linux-gnu.so +src/code_review.egg-info/ + +doc/IDEA.md +doc/git-gui.md +doc/gitkraken/ + +old/RELEASE-TEST.rst +old/__BUGS__/ +old/gitkraken-id.txt +old/gui-prototypes/qlog.ui +old/gui-prototypes/side-view/side-view_ui.py +old/notes/bzr-qlog1.png +old/notes/codereview.png +old/notes/diffview-link.txt +old/notes/git-man-pages/ +old/notes/github1.png +old/notes/github2.png +old/notes/github3.png +old/notes/github4.png +old/notes/github5.png +old/notes/github6.png +old/notes/qbzr-qdiff.png +old/notes/qbzr-qlog.png +old/notes/qgit1.png +old/notes/qgit2.png +old/notes/qgit3.png +old/notes/qgit4.png +old/notes/qgit5.png +old/notes/qgit6.png +old/profile-pyqgit.sh +old/test-failure.txt +old/test-repository/bzr-repo/ +old/test-repository/git-repo/repo-branches/ +old/test-repository/git-repo/repo-twisted-branches/ +old/test-repository/git-repo/repo-two-branches/ +old/test-repository/git-repo/repo1/ +old/test/Model.py +old/test/github3-test.py +old/test/init-stage-test-repo.sh +old/test/stage-test +old/test/swalign-fabrice/ +old/test/swalign-mbreese/ +old/test/test-diff.py +old/test/test-levenshtein-distance.py +old/test/test-repo/ +old/test/token.txt +old/tools/rsync-vps.sh +old/tools/upload-www diff --git a/CodeReview/Config/ConfigInstall.py b/CodeReview/Config/ConfigInstall.py deleted file mode 100644 index e99a8b6..0000000 --- a/CodeReview/Config/ConfigInstall.py +++ /dev/null @@ -1,67 +0,0 @@ -#################################################################################################### - -import os -import sys - -#################################################################################################### - -import CodeReview.Tools.Path as PathTools # due to Path class - -#################################################################################################### - -_this_file = PathTools.to_absolute_path(__file__) - -class Path(object): - - CodeReview_module_directory = PathTools.parent_directory_of(_this_file, step=2) - config_directory = os.path.dirname(_this_file) - - ############################################## - - @staticmethod - def share_directory(): - - path = os.path.dirname(Path.CodeReview_module_directory) - if path.startswith(sys.exec_prefix): - return os.path.join(sys.exec_prefix, 'share', 'CodeReview') - else: - return os.path.join(path, 'share') - -#################################################################################################### - -class Logging(object): - - default_config_file = 'logging.yml' - directories = (Path.config_directory,) - - ############################################## - - @staticmethod - def find(config_file): - - return PathTools.find(config_file, Logging.directories) - -#################################################################################################### - -class Icon(object): - - icon_directory = os.path.join(Path.share_directory(), 'icons') - - ############################################## - - @staticmethod - def find(file_name, icon_size): - - if icon_size == 'svg': - size_directory = icon_size - else: - size_directory = '{0}x{0}'.format(icon_size) - - icon_directory = os.path.join(Icon.icon_directory, size_directory) - return PathTools.find(file_name, (icon_directory,)) - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/CodeReview/Diff/__init__.py b/CodeReview/Diff/__init__.py deleted file mode 100644 index 90d9e46..0000000 --- a/CodeReview/Diff/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -#################################################################################################### -# -# CodeReview - A Code Review GUI -# Copyright (C) 2015 Fabrice Salvaire -# -# This program is free software: you can redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. If -# not, see . -# -#################################################################################################### - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/CodeReview/GUI/Forms/__init__.py b/CodeReview/GUI/Forms/__init__.py deleted file mode 100644 index 395dbe6..0000000 --- a/CodeReview/GUI/Forms/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -#################################################################################################### -# -# CodeReview - A Code Review GUI -# Copyright (C) 2015 Fabrice Salvaire -# -# This program is free software: you can redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. If -# not, see . -# -#################################################################################################### - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/CodeReview/GUI/LogBrowser/LogBrowserApplication.py b/CodeReview/GUI/LogBrowser/LogBrowserApplication.py deleted file mode 100644 index 2e2b84b..0000000 --- a/CodeReview/GUI/LogBrowser/LogBrowserApplication.py +++ /dev/null @@ -1,134 +0,0 @@ -#################################################################################################### -# -# CodeReview - A Code Review GUI -# Copyright (C) 2015 Fabrice Salvaire -# -# This program is free software: you can redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. If -# not, see . -# -#################################################################################################### - -################################################################################################### - -import logging -import os - -from PyQt5 import QtCore, QtWidgets - -#################################################################################################### - -from .CommitTableModel import CommitTableModel -from .LogTableModel import LogTableModel -from CodeReview.Application.ApplicationBase import ApplicationBase -from CodeReview.GUI.Base.GuiApplicationBase import GuiApplicationBase -from CodeReview.Repository.Git import RepositoryNotFound, GitRepository - -#################################################################################################### - -class LogBrowserApplication(GuiApplicationBase, ApplicationBase): - - _logger = logging.getLogger(__name__) - - ############################################### - - def __init__(self, args): - - super(LogBrowserApplication, self).__init__(args=args) - self._logger.debug(str(args)) - - from .LogBrowserMainWindow import LogBrowserMainWindow - self._main_window = LogBrowserMainWindow() - self._main_window.showMaximized() - - self.post_init() - - ############################################## - - def _init_actions(self): - - super(LogBrowserApplication, self)._init_actions() - - ############################################## - - def post_init(self): - - super(LogBrowserApplication, self).post_init() - self._init_repository() - - ############################################## - - def show_message(self, message=None, timeout=0, warn=False): - - """ Hides the normal status indications and displays the given message for the specified - number of milli-seconds (timeout). If timeout is 0 (default), the message remains displayed - until the clearMessage() slot is called or until the showMessage() slot is called again to - change the message. - - Note that showMessage() is called to show temporary explanations of tool tip texts, so - passing a timeout of 0 is not sufficient to display a permanent message. - """ - - self._main_window.show_message(message, timeout, warn) - - ############################################## - - def _init_repository(self): - - if self._args.path is None: - path = os.getcwd() - else: - path = self._args.path - try: - self._repository = GitRepository(path) - except RepositoryNotFound: - self.show_message("Any Git repository was found in path {}".format(path), warn=True) - self._repository = None - return - - self._log_table_model = LogTableModel(self._repository) - log_table = self._main_window._log_table - log_table.setModel(self._log_table_model) - # Set the column widths - column_enum = self._log_table_model.column_enum - width = 0 - for column in ( - column_enum.revision, - # column_enum.message, - column_enum.date, - column_enum.comitter, - ): - log_table.resizeColumnToContents(int(column)) - width += log_table.columnWidth(int(column)) - width = log_table.width() - width - width *= .9 # Fixme: subtract spaces ... - log_table.setColumnWidth(int(column_enum.message), width) - - self._commit_table_model = CommitTableModel() - commit_table = self._main_window._commit_table - commit_table.setModel(self._commit_table_model) - - ############################################## - - def reload_repository(self): - - self._init_repository() - - ############################################## - - @property - def repository(self): - return self._repository - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/CodeReview/GUI/LogBrowser/LogBrowserMainWindow.py b/CodeReview/GUI/LogBrowser/LogBrowserMainWindow.py deleted file mode 100644 index bd7942a..0000000 --- a/CodeReview/GUI/LogBrowser/LogBrowserMainWindow.py +++ /dev/null @@ -1,324 +0,0 @@ -#################################################################################################### -# -# CodeReview - A Code Review GUI -# Copyright (C) 2015 Fabrice Salvaire -# -# This program is free software: you can redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. If -# not, see . -# -#################################################################################################### - -#################################################################################################### - -import logging - -from PyQt5 import QtWidgets -from PyQt5.QtCore import Qt - -import pygit2 as git - -#################################################################################################### - -from CodeReview.GUI.Base.MainWindowBase import MainWindowBase -from CodeReview.GUI.Widgets.IconLoader import IconLoader -from CodeReview.GUI.Widgets.MessageBox import MessageBox - -#################################################################################################### - -_module_logger = logging.getLogger(__name__) - -#################################################################################################### - -class LogBrowserMainWindow(MainWindowBase): - - _logger = _module_logger.getChild('LogBrowserMainWindow') - - ############################################## - - def __init__(self, parent=None): - - super(LogBrowserMainWindow, self).__init__(title='CodeReview Log Browser', parent=parent) - - self._current_revision = None - self._diff = None - self._current_patch = None - self._diff_window = None - - self._init_ui() - self._create_actions() - self._create_toolbar() - - icon_loader = IconLoader() - self.setWindowIcon(icon_loader['code-review@svg']) - - ############################################## - - def _init_ui(self): - - self._central_widget = QtWidgets.QWidget(self) - self.setCentralWidget(self._central_widget) - - self._vertical_layout = QtWidgets.QVBoxLayout(self._central_widget) - self._message_box = MessageBox(self) - splitter = QtWidgets.QSplitter() - splitter.setOrientation(Qt.Vertical) - self._log_table = QtWidgets.QTableView() - self._commit_table = QtWidgets.QTableView() - - for widget in (self._message_box, splitter): - self._vertical_layout.addWidget(widget) - for widget in (self._log_table, self._commit_table): - splitter.addWidget(widget) - - table = self._log_table - table.setSelectionMode(QtWidgets.QTableView.SingleSelection) - table.setSelectionBehavior(QtWidgets.QTableView.SelectRows) - table.verticalHeader().setVisible(False) - table.setShowGrid(False) - # table.setSortingEnabled(True) - table.clicked.connect(self._update_commit_table) - - table = self._commit_table - table.setSelectionMode(QtWidgets.QTableView.SingleSelection) - table.setSelectionBehavior(QtWidgets.QTableView.SelectRows) - table.verticalHeader().setVisible(False) - table.setShowGrid(False) - table.setSortingEnabled(True) - table.clicked.connect(self._show_patch) - - # horizontal_header = table_view.horizontalHeader() - # horizontal_header.setMovable(True) - - ############################################## - - def _create_actions(self): - - # icon_loader = IconLoader() - - self._stagged_mode_action = \ - QtWidgets.QAction('Stagged', - self, - toolTip='Stagged Mode', - shortcut='Ctrl+1', - checkable=True, - ) - - self._not_stagged_mode_action = \ - QtWidgets.QAction('Not Stagged', - self, - toolTip='Not Stagged Mode', - shortcut='Ctrl+2', - checkable=True, - ) - - self._all_change_mode_action = \ - QtWidgets.QAction('All', - self, - toolTip='All Mode', - shortcut='Ctrl+3', - checkable=True, - ) - - self._action_group = QtWidgets.QActionGroup(self) - self._action_group.triggered.connect(self._update_working_tree_diff) - for action in (self._all_change_mode_action, - self._stagged_mode_action, - self._not_stagged_mode_action, - ): - self._action_group.addAction(action) - self._all_change_mode_action.setChecked(True) - - self._reload_action = \ - QtWidgets.QAction('Reload', - self, - toolTip='Reload', - shortcut='Ctrl+4' - ) - self._reload_action.triggered.connect(self._reload_repository) - - ############################################## - - def _create_toolbar(self): - - self._tool_bar = self.addToolBar('Diff on Working Tree') - for item in self._action_group.actions(): - self._tool_bar.addAction(item) - for item in (self._reload_action,): - self._tool_bar.addAction(item) - - ############################################## - - def init_menu(self): - - super(LogBrowserMainWindow, self).init_menu() - - ############################################## - - def show_message(self, message=None, timeout=0, warn=False): - - """ Hides the normal status indications and displays the given message for the specified - number of milli-seconds (timeout). If timeout is 0 (default), the message remains displayed - until the clearMessage() slot is called or until the showMessage() slot is called again to - change the message. - - Note that showMessage() is called to show temporary explanations of tool tip texts, so - passing a timeout of 0 is not sufficient to display a permanent message. - """ - - if warn: - self._message_box.push_message(message) - else: - status_bar = self.statusBar() - if message is None: - status_bar.clearMessage() - else: - status_bar.showMessage(message, timeout) - - ############################################## - - def _reload_repository(self): - - self._application.reload_repository() - self._update_working_tree_diff() - - ############################################## - - def _update_working_tree_diff(self): - - if self._log_table.currentIndex().row() == 0: - self._update_commit_table() - - ############################################## - - def _update_commit_table(self, index=None): - - if index is not None: - index = index.row() - else: - index = 0 - - if index: - self._current_revision = index - log_table_model = self._log_table.model() - commit1 = log_table_model[index] - try: - commit2 = log_table_model[index +1] - kwargs = dict(a=commit2, b=commit1) # Fixme: - except IndexError: - kwargs = dict(a=commit1) - else: # working directory - self._current_revision = None - if self._stagged_mode_action.isChecked(): - # Changes between the index and your last commit - kwargs = dict(a='HEAD', cached=True) - elif self._not_stagged_mode_action.isChecked(): - # Changes in the working tree not yet staged for the next commit - kwargs = {} - elif self._all_change_mode_action.isChecked(): - # Changes in the working tree since your last commit - kwargs = dict(a='HEAD') - - self._diff = self._application.repository.diff(**kwargs) - - commit_table_model = self._commit_table.model() - commit_table_model.update(self._diff) - self._commit_table.resizeColumnsToContents() - - ############################################## - - def _show_patch(self, index): - - self._current_patch = index.row() - self._show_current_patch() - - ############################################## - - def _on_diff_window_closed(self): - - self._diff_window = None - - ############################################## - - @property - def current_patch(self): - return self._current_patch - - ############################################## - - @property - def number_of_patches(self): - return len(self._diff) - - ############################################## - - def _show_current_patch(self): - - if self._diff_window is None: - from CodeReview.GUI.DiffViewer.DiffViewerMainWindow import DiffViewerMainWindow - self._diff_window = DiffViewerMainWindow(self) - self._diff_window.closed.connect(self._on_diff_window_closed) - self._diff_window.showMaximized() - - patch = self._diff[self._current_patch] - delta = patch.delta - if not delta.is_binary: - self._logger.info('revision {} '.format(self._current_revision) + delta.new_file.path) - # print(delta.status, delta.similarity, delta.additions, delta.deletions, delta.is_binary) - # for hunk in delta.hunks: - # print(hunk.old_start, hunk.old_lines, hunk.new_start, hunk.new_lines, hunk.lines) - repository = self._application.repository - if delta.status in (git.GIT_DELTA_MODIFIED, git.GIT_DELTA_RENAMED): - paths = (delta.old_file.path, delta.new_file.path) - elif delta.status == git.GIT_DELTA_ADDED: - paths = (None, delta.new_file.path) - elif delta.status == git.GIT_DELTA_DELETED: - paths = (delta.old_file.path, None) - texts = [repository.file_content(blob_id) - for blob_id in (delta.old_file.id, delta.new_file.id)] - metadatas = [dict(path=delta.old_file.path, document_type='file', last_modification_date=None), - dict(path=delta.new_file.path, document_type='file', last_modification_date=None)] - self._diff_window.diff_documents(paths, texts, metadatas, workdir=repository.workdir) - else: - self._logger.info('revision {} Binary '.format(self._current_revision) + delta.new_file.path) - # Fixme: show image pdf ... - - ############################################## - - @property - def _last_path_index(self): - - return len(self._diff) -1 - - ############################################## - - def previous_patch(self): - - if self._current_patch >= 1: - self._current_patch -= 1 - else: - self._current_patch = self._last_path_index - self._show_current_patch() - - ############################################## - - def next_patch(self): - - if self._current_patch < self._last_path_index: - self._current_patch += 1 - else: - self._current_patch = 0 - self._show_current_patch() - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/CodeReview/GUI/LogBrowser/__init__.py b/CodeReview/GUI/LogBrowser/__init__.py deleted file mode 100644 index 90d9e46..0000000 --- a/CodeReview/GUI/LogBrowser/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -#################################################################################################### -# -# CodeReview - A Code Review GUI -# Copyright (C) 2015 Fabrice Salvaire -# -# This program is free software: you can redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. If -# not, see . -# -#################################################################################################### - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/CodeReview/GUI/Widgets/__init__.py b/CodeReview/GUI/Widgets/__init__.py deleted file mode 100644 index 395dbe6..0000000 --- a/CodeReview/GUI/Widgets/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -#################################################################################################### -# -# CodeReview - A Code Review GUI -# Copyright (C) 2015 Fabrice Salvaire -# -# This program is free software: you can redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. If -# not, see . -# -#################################################################################################### - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/CodeReview/GUI/__init__.py b/CodeReview/GUI/__init__.py deleted file mode 100644 index 90d9e46..0000000 --- a/CodeReview/GUI/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -#################################################################################################### -# -# CodeReview - A Code Review GUI -# Copyright (C) 2015 Fabrice Salvaire -# -# This program is free software: you can redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. If -# not, see . -# -#################################################################################################### - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/CodeReview/Logging/__init__.py b/CodeReview/Logging/__init__.py deleted file mode 100644 index 395dbe6..0000000 --- a/CodeReview/Logging/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -#################################################################################################### -# -# CodeReview - A Code Review GUI -# Copyright (C) 2015 Fabrice Salvaire -# -# This program is free software: you can redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. If -# not, see . -# -#################################################################################################### - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/CodeReview/Math/__init__.py b/CodeReview/Math/__init__.py deleted file mode 100644 index 395dbe6..0000000 --- a/CodeReview/Math/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -#################################################################################################### -# -# CodeReview - A Code Review GUI -# Copyright (C) 2015 Fabrice Salvaire -# -# This program is free software: you can redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. If -# not, see . -# -#################################################################################################### - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/CodeReview/Repository/Git.py b/CodeReview/Repository/Git.py deleted file mode 100644 index 28ee654..0000000 --- a/CodeReview/Repository/Git.py +++ /dev/null @@ -1,136 +0,0 @@ -#################################################################################################### -# -# CodeReview - A Code Review GUI -# Copyright (C) 2015 Fabrice Salvaire -# -# This program is free software: you can redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. If -# not, see . -# -#################################################################################################### - -#################################################################################################### - -import logging -import os - -import pygit2 as git - -#################################################################################################### - -_module_logger = logging.getLogger(__name__) - -#################################################################################################### - -class RepositoryNotFound(Exception): - pass - -#################################################################################################### - -class GitRepository(object): - - _logger = _module_logger.getChild('GitRepository') - - ############################################## - - def __init__(self, path): - - path = os.path.realpath(path) - - try: - repository_path = git.discover_repository(path) - self._repository = git.Repository(repository_path) - workdir = self._repository.workdir - if os.path.isdir(path) and not path.endswith(os.sep): - path += os.sep - self._path_filter = path.replace(workdir, '') - self._logger.info('\nPath %s\nWork Dir: %s\nPath Filter: %s', - path, workdir, self._path_filter) - except KeyError: - raise RepositoryNotFound - - ############################################## - - @property - def workdir(self): - return self._repository.workdir - - ############################################## - - def commits(self): - - head = self._repository.head - head_commit = self._repository[head.target] - commits = [commit - for commit in self._repository.walk(head_commit.id, git.GIT_SORT_TIME)] - - return commits - - ############################################## - - def diff(self, a=None, b=None, cached=False, path_filter=None): - - if path_filter is None: - path_filter = self._path_filter - - patches = [] - - # GIT_DIFF_PATIENCE - diff = self._repository.diff(a=a, b=b, cached=cached) - diff.find_similar() - # flags, rename_threshold, copy_threshold, rename_from_rewrite_threshold, break_rewrite_threshold, rename_limit - for patch in diff: - delta = patch.delta - if path_filter and not delta.old_file.path.startswith(path_filter): - # self._logger.info('Skip ' + delta.old_file.path) - continue - patches.append(patch) - - return Diff(diff, patches) - - ############################################## - - def file_content(self, oid): - try: - return self._repository[oid].data.decode('utf-8') - except KeyError: - return None - -#################################################################################################### - -class Diff(object): - - ############################################## - - def __init__(self, diff, patches): - - self.diff = diff - self._patches = patches - - ############################################## - - def __len__(self): - return len(self._patches) - - ############################################## - - def __iter__(self): - return iter(self._patches) - - ############################################## - - def __getitem__(self, i): - return self._patches[i] - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/CodeReview/Repository/__init__.py b/CodeReview/Repository/__init__.py deleted file mode 100644 index 395dbe6..0000000 --- a/CodeReview/Repository/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -#################################################################################################### -# -# CodeReview - A Code Review GUI -# Copyright (C) 2015 Fabrice Salvaire -# -# This program is free software: you can redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. If -# not, see . -# -#################################################################################################### - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/CodeReview/Tools/__init__.py b/CodeReview/Tools/__init__.py deleted file mode 100644 index 395dbe6..0000000 --- a/CodeReview/Tools/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -#################################################################################################### -# -# CodeReview - A Code Review GUI -# Copyright (C) 2015 Fabrice Salvaire -# -# This program is free software: you can redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. If -# not, see . -# -#################################################################################################### - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/CodeReview/Version.py b/CodeReview/Version.py deleted file mode 100644 index c30d37f..0000000 --- a/CodeReview/Version.py +++ /dev/null @@ -1,39 +0,0 @@ -#################################################################################################### -# -# CodeReview - A Code Review GUI -# Copyright (C) 2015 Fabrice Salvaire -# -# This program is free software: you can redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. If -# not, see . -# -#################################################################################################### - -#################################################################################################### - -# cf. http://en.wikipedia.org/wiki/Software_versioning - -#################################################################################################### - -from CodeReview.Tools.RevisionVersion import RevisionVersion - -#################################################################################################### - -CodeReview = RevisionVersion({'major':0, - 'minor':1, - 'revision':0, - 'suffix':'', - }) - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/CodeReview/__init__.py b/CodeReview/__init__.py deleted file mode 100644 index 395dbe6..0000000 --- a/CodeReview/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -#################################################################################################### -# -# CodeReview - A Code Review GUI -# Copyright (C) 2015 Fabrice Salvaire -# -# This program is free software: you can redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. If -# not, see . -# -#################################################################################################### - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/MANIFEST.in b/MANIFEST.in index 3e5c4ba..3550c33 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,32 +1,23 @@ -include .bzrignore include .gitignore include .pyflymakerc include .travis.yml -include CHANGES.txt include GPL-V3.0.txt -include LICENSE.txt include MANIFEST.in include README.html include README.rst include README.txt -include make-release.sh -include pylintrc.ini include requirements.txt -include setenv.sh start.sh include setup_data.py -include test-repository/git-repo/*.sh -recursive-include CodeReview *.py *.yml *.c *.h *.ui Makefile.pyqt .in -recursive-include bin +recursive-include CodeReview *.py *.yml *.c *.h *.ui Makefile.pyqt *.png *.svg recursive-include doc * recursive-include doc/sphinx/source/_static * recursive-include gh-pages * -recursive-include share *.png *.svg recursive-include spec *.spec *.desktop -recursive-include tools * recursive-include unit-test *.py recursive-include unit-test/data *.txt -prune *~ *.pyc +prune *~ +prune *.pyc prune build prune doc/sphinx/build -prune test-repository/bzr-repo/ -prune test-repository/git-repo/ +prune tasks +prune QtShim diff --git a/README.html b/README.html index f147dfd..222b9f1 100644 --- a/README.html +++ b/README.html @@ -3,13 +3,13 @@ - - + +README.rst \ No newline at end of file diff --git a/src/CodeReview/share/icons/32x32/go-next.svg b/src/CodeReview/share/icons/32x32/go-next.svg new file mode 100644 index 0000000..ec1545d --- /dev/null +++ b/src/CodeReview/share/icons/32x32/go-next.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/src/CodeReview/share/icons/32x32/go-previous.svg b/src/CodeReview/share/icons/32x32/go-previous.svg new file mode 100644 index 0000000..fbdfb7a --- /dev/null +++ b/src/CodeReview/share/icons/32x32/go-previous.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/src/CodeReview/share/icons/32x32/media-playlist-repeat.svg b/src/CodeReview/share/icons/32x32/media-playlist-repeat.svg new file mode 100644 index 0000000..0e3071b --- /dev/null +++ b/src/CodeReview/share/icons/32x32/media-playlist-repeat.svg @@ -0,0 +1,17 @@ + + + + + + diff --git a/src/CodeReview/share/icons/32x32/view-refresh.svg b/src/CodeReview/share/icons/32x32/view-refresh.svg new file mode 100644 index 0000000..c60eef3 --- /dev/null +++ b/src/CodeReview/share/icons/32x32/view-refresh.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/share/icons/48x48/dialog-warning.png b/src/CodeReview/share/icons/48x48/dialog-warning.png similarity index 100% rename from share/icons/48x48/dialog-warning.png rename to src/CodeReview/share/icons/48x48/dialog-warning.png diff --git a/share/icons/code-review-250.png b/src/CodeReview/share/icons/code-review-250.png similarity index 100% rename from share/icons/code-review-250.png rename to src/CodeReview/share/icons/code-review-250.png diff --git a/share/icons/svg/align-mode.svg b/src/CodeReview/share/icons/svg/align-mode.svg similarity index 70% rename from share/icons/svg/align-mode.svg rename to src/CodeReview/share/icons/svg/align-mode.svg index 5d08521..587cb44 100644 --- a/share/icons/svg/align-mode.svg +++ b/src/CodeReview/share/icons/svg/align-mode.svg @@ -9,12 +9,12 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="1000" - height="1000" + width="1066.6666" + height="1066.6666" viewBox="0 0 999.99996 999.99996" id="svg4136" version="1.1" - inkscape:version="0.91 r13725" + inkscape:version="0.92.1 r" sodipodi:docname="align-mode.svg"> @@ -63,7 +63,7 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="0.626" - inkscape:cx="500" + inkscape:cx="40.734824" inkscape:cy="500" inkscape:document-units="px" inkscape:current-layer="layer1" @@ -71,10 +71,10 @@ units="px" width="500mm" inkscape:snap-grids="true" - inkscape:window-width="1600" - inkscape:window-height="853" - inkscape:window-x="-2" - inkscape:window-y="-3" + inkscape:window-width="1920" + inkscape:window-height="1007" + inkscape:window-x="0" + inkscape:window-y="0" inkscape:window-maximized="1" inkscape:snap-text-baseline="true" objecttolerance="10000" @@ -87,7 +87,9 @@ id="grid4823" spacingx="4.9999998" spacingy="4.9999998" - empspacing="1" /> + empspacing="1" + originx="0" + originy="0" /> @@ -97,7 +99,7 @@ image/svg+xml - + @@ -107,7 +109,7 @@ id="layer1" transform="translate(0,-52.36226)"> + y="-156.09781" + style="font-size:20px;line-height:1.25">  A + y="542.39453" + style="font-size:474.11590576px;line-height:1.25">A A + y="542.39447" + style="font-size:474.11590576px;line-height:1.25">A B + y="981.72424" + style="font-size:498.26498413px;line-height:1.20000005">B diff --git a/share/icons/svg/code-review.svg b/src/CodeReview/share/icons/svg/code-review.svg similarity index 100% rename from share/icons/svg/code-review.svg rename to src/CodeReview/share/icons/svg/code-review.svg diff --git a/share/icons/svg/complete-mode.svg b/src/CodeReview/share/icons/svg/complete-mode.svg similarity index 52% rename from share/icons/svg/complete-mode.svg rename to src/CodeReview/share/icons/svg/complete-mode.svg index 73c55d4..45155fe 100644 --- a/share/icons/svg/complete-mode.svg +++ b/src/CodeReview/share/icons/svg/complete-mode.svg @@ -9,12 +9,12 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="1000" - height="1000" + width="1066.6666" + height="1066.6666" viewBox="0 0 999.99996 999.99996" id="svg4136" version="1.1" - inkscape:version="0.91 r13725" + inkscape:version="0.92.1 r" sodipodi:docname="complete-mode.svg"> @@ -46,19 +46,19 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="0.626" - inkscape:cx="492.8115" - inkscape:cy="500" + inkscape:zoom="0.74343753" + inkscape:cx="533.33331" + inkscape:cy="533.33331" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" units="px" width="500mm" inkscape:snap-grids="true" - inkscape:window-width="1600" - inkscape:window-height="853" - inkscape:window-x="-2" - inkscape:window-y="-3" + inkscape:window-width="1920" + inkscape:window-height="1007" + inkscape:window-x="0" + inkscape:window-y="0" inkscape:window-maximized="1" inkscape:snap-text-baseline="true" objecttolerance="10000" @@ -70,7 +70,9 @@ id="grid4823" spacingx="9.9999996" spacingy="9.9999996" - empspacing="1" /> + empspacing="1" + originx="0" + originy="0" /> @@ -80,7 +82,7 @@ image/svg+xml - + @@ -90,56 +92,59 @@ id="layer1" transform="translate(0,-52.36226)"> + width="419.99997" + height="480" + x="40" + y="112.36226" + ry="0" /> + y="-156.09781" + style="font-size:20px;line-height:1.25">  C + id="tspan4908" + x="83.626762" + y="553.81818" + style="font-size:552.68743896px;line-height:1.25">C + width="419.42871" + height="479.99997" + x="540" + y="512.36224" /> C + id="tspan4910" + x="583.62671" + y="953.81812" + style="font-size:552.68743896px;line-height:1.25">C diff --git a/src/CodeReview/share/icons/svg/edit-delete.svg b/src/CodeReview/share/icons/svg/edit-delete.svg new file mode 120000 index 0000000..39d3d8a --- /dev/null +++ b/src/CodeReview/share/icons/svg/edit-delete.svg @@ -0,0 +1 @@ +../32x32/edit-delete.svg \ No newline at end of file diff --git a/src/CodeReview/share/icons/svg/go-next.svg b/src/CodeReview/share/icons/svg/go-next.svg new file mode 120000 index 0000000..04c9bfa --- /dev/null +++ b/src/CodeReview/share/icons/svg/go-next.svg @@ -0,0 +1 @@ +../32x32/go-next.svg \ No newline at end of file diff --git a/src/CodeReview/share/icons/svg/go-previous.svg b/src/CodeReview/share/icons/svg/go-previous.svg new file mode 120000 index 0000000..b13fdba --- /dev/null +++ b/src/CodeReview/share/icons/svg/go-previous.svg @@ -0,0 +1 @@ +../32x32/go-previous.svg \ No newline at end of file diff --git a/share/icons/svg/highlight.svg b/src/CodeReview/share/icons/svg/highlight.svg similarity index 100% rename from share/icons/svg/highlight.svg rename to src/CodeReview/share/icons/svg/highlight.svg diff --git a/share/icons/svg/line-number-mode.svg b/src/CodeReview/share/icons/svg/line-number-mode.svg similarity index 62% rename from share/icons/svg/line-number-mode.svg rename to src/CodeReview/share/icons/svg/line-number-mode.svg index 63bcade..79a5239 100644 --- a/share/icons/svg/line-number-mode.svg +++ b/src/CodeReview/share/icons/svg/line-number-mode.svg @@ -9,13 +9,13 @@ xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="1000" - height="1000" + width="1066.6666" + height="1066.6666" viewBox="0 0 999.99996 999.99996" id="svg4136" version="1.1" - inkscape:version="0.91 r13725" - sodipodi:docname="line-number.svg"> + inkscape:version="0.92.1 r" + sodipodi:docname="line-number-mode.svg"> + empspacing="1" + originx="0" + originy="0" /> @@ -107,7 +109,7 @@ id="layer1" transform="translate(0,-52.36226)"> + y="-156.09781" + style="font-size:20px;line-height:1.25">  1 + id="tspan5538" + x="87.017891" + y="542.39453" + style="font-size:474.11245728px;line-height:1.25">1 A + y="542.39447" + style="font-size:474.11590576px;line-height:1.25">A B + y="981.72424" + style="font-size:498.26498413px;line-height:1.20000005">B 2 + id="tspan5558" + x="100.16022" + y="934.14233" + style="font-size:474.11245728px;line-height:1.25">2 diff --git a/src/CodeReview/share/icons/svg/media-playlist-repeat.svg b/src/CodeReview/share/icons/svg/media-playlist-repeat.svg new file mode 120000 index 0000000..47a4244 --- /dev/null +++ b/src/CodeReview/share/icons/svg/media-playlist-repeat.svg @@ -0,0 +1 @@ +../32x32/media-playlist-repeat.svg \ No newline at end of file diff --git a/src/CodeReview/share/icons/svg/view-refresh.svg b/src/CodeReview/share/icons/svg/view-refresh.svg new file mode 120000 index 0000000..7fa865d --- /dev/null +++ b/src/CodeReview/share/icons/svg/view-refresh.svg @@ -0,0 +1 @@ +../32x32/view-refresh.svg \ No newline at end of file diff --git a/src/QtShim/QtConfig.py b/src/QtShim/QtConfig.py new file mode 100644 index 0000000..e1b09db --- /dev/null +++ b/src/QtShim/QtConfig.py @@ -0,0 +1,745 @@ +#################################################################################################### + +"""Common members of all bindings + +This is where each member of Qt.py is explicitly defined. +It is based on a 'lowest common denominator' of all bindings; +including members found in each of the 4 bindings. + +The '_common_members' dictionary is generated using the +build_membership.sh script. + +""" + +_common_members = { + 'QtCore': [ + 'QAbstractAnimation', + 'QAbstractEventDispatcher', + 'QAbstractItemModel', + 'QAbstractListModel', + 'QAbstractState', + 'QAbstractTableModel', + 'QAbstractTransition', + 'QAnimationGroup', + 'QBasicTimer', + 'QBitArray', + 'QBuffer', + 'QByteArray', + 'QByteArrayMatcher', + 'QChildEvent', + 'QCoreApplication', + 'QCryptographicHash', + 'QDataStream', + 'QDate', + 'QDateTime', + 'QDir', + 'QDirIterator', + 'QDynamicPropertyChangeEvent', + 'QEasingCurve', + 'QElapsedTimer', + 'QEvent', + 'QEventLoop', + 'QEventTransition', + 'QFile', + 'QFileInfo', + 'QFileSystemWatcher', + 'QFinalState', + 'QGenericArgument', + 'QGenericReturnArgument', + 'QHistoryState', + 'QItemSelectionRange', + 'QIODevice', + 'QLibraryInfo', + 'QLine', + 'QLineF', + 'QLocale', + 'QMargins', + 'QMetaClassInfo', + 'QMetaEnum', + 'QMetaMethod', + 'QMetaObject', + 'QMetaProperty', + 'QMimeData', + 'QModelIndex', + 'QMutex', + 'QMutexLocker', + 'QObject', + 'QParallelAnimationGroup', + 'QPauseAnimation', + 'QPersistentModelIndex', + 'QPluginLoader', + 'QPoint', + 'QPointF', + 'QProcess', + 'QProcessEnvironment', + 'QPropertyAnimation', + 'QReadLocker', + 'QReadWriteLock', + 'QRect', + 'QRectF', + 'QRegExp', + 'QResource', + 'QRunnable', + 'QSemaphore', + 'QSequentialAnimationGroup', + 'QSettings', + 'QSignalMapper', + 'QSignalTransition', + 'QSize', + 'QSizeF', + 'QSocketNotifier', + 'QState', + 'QStateMachine', + 'QSysInfo', + 'QSystemSemaphore', + 'QT_TRANSLATE_NOOP', + 'QT_TR_NOOP', + 'QT_TR_NOOP_UTF8', + 'QTemporaryFile', + 'QTextBoundaryFinder', + 'QTextCodec', + 'QTextDecoder', + 'QTextEncoder', + 'QTextStream', + 'QTextStreamManipulator', + 'QThread', + 'QThreadPool', + 'QTime', + 'QTimeLine', + 'QTimer', + 'QTimerEvent', + 'QTranslator', + 'QUrl', + 'QVariantAnimation', + 'QWaitCondition', + 'QWriteLocker', + 'QXmlStreamAttribute', + 'QXmlStreamAttributes', + 'QXmlStreamEntityDeclaration', + 'QXmlStreamEntityResolver', + 'QXmlStreamNamespaceDeclaration', + 'QXmlStreamNotationDeclaration', + 'QXmlStreamReader', + 'QXmlStreamWriter', + 'Qt', + 'QtCriticalMsg', + 'QtDebugMsg', + 'QtFatalMsg', + 'QtMsgType', + 'QtSystemMsg', + 'QtWarningMsg', + 'qAbs', + 'qAddPostRoutine', + 'qChecksum', + 'qCritical', + 'qDebug', + 'qFatal', + 'qFuzzyCompare', + 'qIsFinite', + 'qIsInf', + 'qIsNaN', + 'qIsNull', + 'qRegisterResourceData', + 'qUnregisterResourceData', + 'qVersion', + 'qWarning', + 'qrand', + 'qsrand' + ], + 'QtGui': [ + 'QAbstractTextDocumentLayout', + 'QActionEvent', + 'QBitmap', + 'QBrush', + 'QClipboard', + 'QCloseEvent', + 'QColor', + 'QConicalGradient', + 'QContextMenuEvent', + 'QCursor', + 'QDesktopServices', + 'QDoubleValidator', + 'QDrag', + 'QDragEnterEvent', + 'QDragLeaveEvent', + 'QDragMoveEvent', + 'QDropEvent', + 'QFileOpenEvent', + 'QFocusEvent', + 'QFont', + 'QFontDatabase', + 'QFontInfo', + 'QFontMetrics', + 'QFontMetricsF', + 'QGradient', + 'QGuiApplication', + 'QHelpEvent', + 'QHideEvent', + 'QHoverEvent', + 'QIcon', + 'QIconDragEvent', + 'QIconEngine', + 'QImage', + 'QImageIOHandler', + 'QImageReader', + 'QImageWriter', + 'QInputEvent', + 'QInputMethodEvent', + 'QIntValidator', + 'QKeyEvent', + 'QKeySequence', + 'QLinearGradient', + 'QMatrix2x2', + 'QMatrix2x3', + 'QMatrix2x4', + 'QMatrix3x2', + 'QMatrix3x3', + 'QMatrix3x4', + 'QMatrix4x2', + 'QMatrix4x3', + 'QMatrix4x4', + 'QMouseEvent', + 'QMoveEvent', + 'QMovie', + 'QPaintDevice', + 'QPaintEngine', + 'QPaintEngineState', + 'QPaintEvent', + 'QPainter', + 'QPainterPath', + 'QPainterPathStroker', + 'QPalette', + 'QPen', + 'QPicture', + 'QPictureIO', + 'QPixmap', + 'QPixmapCache', + 'QPolygon', + 'QPolygonF', + 'QQuaternion', + 'QRadialGradient', + 'QRegExpValidator', + 'QRegion', + 'QResizeEvent', + 'QSessionManager', + 'QShortcutEvent', + 'QShowEvent', + 'QStandardItem', + 'QStandardItemModel', + 'QStatusTipEvent', + 'QSyntaxHighlighter', + 'QTabletEvent', + 'QTextBlock', + 'QTextBlockFormat', + 'QTextBlockGroup', + 'QTextBlockUserData', + 'QTextCharFormat', + 'QTextCursor', + 'QTextDocument', + 'QTextDocumentFragment', + 'QTextFormat', + 'QTextFragment', + 'QTextFrame', + 'QTextFrameFormat', + 'QTextImageFormat', + 'QTextInlineObject', + 'QTextItem', + 'QTextLayout', + 'QTextLength', + 'QTextLine', + 'QTextList', + 'QTextListFormat', + 'QTextObject', + 'QTextObjectInterface', + 'QTextOption', + 'QTextTable', + 'QTextTableCell', + 'QTextTableCellFormat', + 'QTextTableFormat', + 'QTouchEvent', + 'QTransform', + 'QValidator', + 'QVector2D', + 'QVector3D', + 'QVector4D', + 'QWhatsThisClickedEvent', + 'QWheelEvent', + 'QWindowStateChangeEvent', + 'qAlpha', + 'qBlue', + 'qGray', + 'qGreen', + 'qIsGray', + 'qRed', + 'qRgb', + 'qRgba' + ], + # 'QtHelp': [ + # 'QHelpContentItem', + # 'QHelpContentModel', + # 'QHelpContentWidget', + # 'QHelpEngine', + # 'QHelpEngineCore', + # 'QHelpIndexModel', + # 'QHelpIndexWidget', + # 'QHelpSearchEngine', + # 'QHelpSearchQuery', + # 'QHelpSearchQueryWidget', + # 'QHelpSearchResultWidget' + # ], + # 'QtMultimedia': [ + # 'QAbstractVideoBuffer', + # 'QAbstractVideoSurface', + # 'QAudio', + # 'QAudioDeviceInfo', + # 'QAudioFormat', + # 'QAudioInput', + # 'QAudioOutput', + # 'QVideoFrame', + # 'QVideoSurfaceFormat' + # ], + # 'QtNetwork': [ + # 'QAbstractNetworkCache', + # 'QAbstractSocket', + # 'QAuthenticator', + # 'QHostAddress', + # 'QHostInfo', + # 'QLocalServer', + # 'QLocalSocket', + # 'QNetworkAccessManager', + # 'QNetworkAddressEntry', + # 'QNetworkCacheMetaData', + # 'QNetworkConfiguration', + # 'QNetworkConfigurationManager', + # 'QNetworkCookie', + # 'QNetworkCookieJar', + # 'QNetworkDiskCache', + # 'QNetworkInterface', + # 'QNetworkProxy', + # 'QNetworkProxyFactory', + # 'QNetworkProxyQuery', + # 'QNetworkReply', + # 'QNetworkRequest', + # 'QNetworkSession', + # 'QSsl', + # 'QTcpServer', + # 'QTcpSocket', + # 'QUdpSocket' + # ], + # 'QtOpenGL': [ + # 'QGL', + # 'QGLContext', + # 'QGLFormat', + # 'QGLWidget' + # ], + # 'QtPrintSupport': [ + # 'QAbstractPrintDialog', + # 'QPageSetupDialog', + # 'QPrintDialog', + # 'QPrintEngine', + # 'QPrintPreviewDialog', + # 'QPrintPreviewWidget', + # 'QPrinter', + # 'QPrinterInfo' + # ], + # 'QtSql': [ + # 'QSql', + # 'QSqlDatabase', + # 'QSqlDriver', + # 'QSqlDriverCreatorBase', + # 'QSqlError', + # 'QSqlField', + # 'QSqlIndex', + # 'QSqlQuery', + # 'QSqlQueryModel', + # 'QSqlRecord', + # 'QSqlRelation', + # 'QSqlRelationalDelegate', + # 'QSqlRelationalTableModel', + # 'QSqlResult', + # 'QSqlTableModel' + # ], + 'QtSvg': [ + 'QGraphicsSvgItem', + 'QSvgGenerator', + 'QSvgRenderer', + 'QSvgWidget' + ], + # 'QtTest': [ + # 'QTest' + # ], + 'QtWidgets': [ + 'QAbstractButton', + 'QAbstractGraphicsShapeItem', + 'QAbstractItemDelegate', + 'QAbstractItemView', + 'QAbstractScrollArea', + 'QAbstractSlider', + 'QAbstractSpinBox', + 'QAction', + 'QActionGroup', + 'QApplication', + 'QBoxLayout', + 'QButtonGroup', + 'QCalendarWidget', + 'QCheckBox', + 'QColorDialog', + 'QColumnView', + 'QComboBox', + 'QCommandLinkButton', + 'QCommonStyle', + 'QCompleter', + 'QDataWidgetMapper', + 'QDateEdit', + 'QDateTimeEdit', + 'QDesktopWidget', + 'QDial', + 'QDialog', + 'QDialogButtonBox', + 'QDirModel', + 'QDockWidget', + 'QDoubleSpinBox', + 'QErrorMessage', + 'QFileDialog', + 'QFileIconProvider', + 'QFileSystemModel', + 'QFocusFrame', + 'QFontComboBox', + 'QFontDialog', + 'QFormLayout', + 'QFrame', + 'QGesture', + 'QGestureEvent', + 'QGestureRecognizer', + 'QGraphicsAnchor', + 'QGraphicsAnchorLayout', + 'QGraphicsBlurEffect', + 'QGraphicsColorizeEffect', + 'QGraphicsDropShadowEffect', + 'QGraphicsEffect', + 'QGraphicsEllipseItem', + 'QGraphicsGridLayout', + 'QGraphicsItem', + 'QGraphicsItemGroup', + 'QGraphicsLayout', + 'QGraphicsLayoutItem', + 'QGraphicsLineItem', + 'QGraphicsLinearLayout', + 'QGraphicsObject', + 'QGraphicsOpacityEffect', + 'QGraphicsPathItem', + 'QGraphicsPixmapItem', + 'QGraphicsPolygonItem', + 'QGraphicsProxyWidget', + 'QGraphicsRectItem', + 'QGraphicsRotation', + 'QGraphicsScale', + 'QGraphicsScene', + 'QGraphicsSceneContextMenuEvent', + 'QGraphicsSceneDragDropEvent', + 'QGraphicsSceneEvent', + 'QGraphicsSceneHelpEvent', + 'QGraphicsSceneHoverEvent', + 'QGraphicsSceneMouseEvent', + 'QGraphicsSceneMoveEvent', + 'QGraphicsSceneResizeEvent', + 'QGraphicsSceneWheelEvent', + 'QGraphicsSimpleTextItem', + 'QGraphicsTextItem', + 'QGraphicsTransform', + 'QGraphicsView', + 'QGraphicsWidget', + 'QGridLayout', + 'QGroupBox', + 'QHBoxLayout', + 'QHeaderView', + 'QInputDialog', + 'QItemDelegate', + 'QItemEditorCreatorBase', + 'QItemEditorFactory', + 'QKeyEventTransition', + 'QLCDNumber', + 'QLabel', + 'QLayout', + 'QLayoutItem', + 'QLineEdit', + 'QListView', + 'QListWidget', + 'QListWidgetItem', + 'QMainWindow', + 'QMdiArea', + 'QMdiSubWindow', + 'QMenu', + 'QMenuBar', + 'QMessageBox', + 'QMouseEventTransition', + 'QPanGesture', + 'QPinchGesture', + 'QPlainTextDocumentLayout', + 'QPlainTextEdit', + 'QProgressBar', + 'QProgressDialog', + 'QPushButton', + 'QRadioButton', + 'QRubberBand', + 'QScrollArea', + 'QScrollBar', + 'QShortcut', + 'QSizeGrip', + 'QSizePolicy', + 'QSlider', + 'QSpacerItem', + 'QSpinBox', + 'QSplashScreen', + 'QSplitter', + 'QSplitterHandle', + 'QStackedLayout', + 'QStackedWidget', + 'QStatusBar', + 'QStyle', + 'QStyleFactory', + 'QStyleHintReturn', + 'QStyleHintReturnMask', + 'QStyleHintReturnVariant', + 'QStyleOption', + 'QStyleOptionButton', + 'QStyleOptionComboBox', + 'QStyleOptionComplex', + 'QStyleOptionDockWidget', + 'QStyleOptionFocusRect', + 'QStyleOptionFrame', + 'QStyleOptionGraphicsItem', + 'QStyleOptionGroupBox', + 'QStyleOptionHeader', + 'QStyleOptionMenuItem', + 'QStyleOptionProgressBar', + 'QStyleOptionRubberBand', + 'QStyleOptionSizeGrip', + 'QStyleOptionSlider', + 'QStyleOptionSpinBox', + 'QStyleOptionTab', + 'QStyleOptionTabBarBase', + 'QStyleOptionTabWidgetFrame', + 'QStyleOptionTitleBar', + 'QStyleOptionToolBar', + 'QStyleOptionToolBox', + 'QStyleOptionToolButton', + 'QStyleOptionViewItem', + 'QStylePainter', + 'QStyledItemDelegate', + 'QSwipeGesture', + 'QSystemTrayIcon', + 'QTabBar', + 'QTabWidget', + 'QTableView', + 'QTableWidget', + 'QTableWidgetItem', + 'QTableWidgetSelectionRange', + 'QTapAndHoldGesture', + 'QTapGesture', + 'QTextBrowser', + 'QTextEdit', + 'QTimeEdit', + 'QToolBar', + 'QToolBox', + 'QToolButton', + 'QToolTip', + 'QTreeView', + 'QTreeWidget', + 'QTreeWidgetItem', + 'QTreeWidgetItemIterator', + 'QUndoCommand', + 'QUndoGroup', + 'QUndoStack', + 'QUndoView', + 'QVBoxLayout', + 'QWhatsThis', + 'QWidget', + 'QWidgetAction', + 'QWidgetItem', + 'QWizard', + 'QWizardPage' + ], + # 'QtX11Extras': [ + # 'QX11Info' + # ], + # 'QtXml': [ + # 'QDomAttr', + # 'QDomCDATASection', + # 'QDomCharacterData', + # 'QDomComment', + # 'QDomDocument', + # 'QDomDocumentFragment', + # 'QDomDocumentType', + # 'QDomElement', + # 'QDomEntity', + # 'QDomEntityReference', + # 'QDomImplementation', + # 'QDomNamedNodeMap', + # 'QDomNode', + # 'QDomNodeList', + # 'QDomNotation', + # 'QDomProcessingInstruction', + # 'QDomText', + # 'QXmlAttributes', + # 'QXmlContentHandler', + # 'QXmlDTDHandler', + # 'QXmlDeclHandler', + # 'QXmlDefaultHandler', + # 'QXmlEntityResolver', + # 'QXmlErrorHandler', + # 'QXmlInputSource', + # 'QXmlLexicalHandler', + # 'QXmlLocator', + # 'QXmlNamespaceSupport', + # 'QXmlParseException', + # 'QXmlReader', + # 'QXmlSimpleReader' + # ], + # 'QtXmlPatterns': [ + # 'QAbstractMessageHandler', + # 'QAbstractUriResolver', + # 'QAbstractXmlNodeModel', + # 'QAbstractXmlReceiver', + # 'QSourceLocation', + # 'QXmlFormatter', + # 'QXmlItem', + # 'QXmlName', + # 'QXmlNamePool', + # 'QXmlNodeModelIndex', + # 'QXmlQuery', + # 'QXmlResultItems', + # 'QXmlSchema', + # 'QXmlSchemaValidator', + # 'QXmlSerializer' + # ] + 'QtQml': [ + 'qmlRegisterType', + 'qmlRegisterUncreatableType', + 'QQmlApplicationEngine', + ], + 'QtQuick': [ + 'QQuickPaintedItem', + 'QQuickView', + ], +} + +#################################################################################################### + +"""Misplaced members + +These members from the original submodule are misplaced relative PySide2 + +""" + +_misplaced_members = { + 'PySide2': { + 'QtCore.QStringListModel': 'QtCore.QStringListModel', + 'QtGui.QStringListModel': 'QtCore.QStringListModel', + 'QtCore.Property': 'QtCore.Property', + 'QtCore.Signal': 'QtCore.Signal', + 'QtCore.Slot': 'QtCore.Slot', + 'QtCore.QAbstractProxyModel': 'QtCore.QAbstractProxyModel', + 'QtCore.QSortFilterProxyModel': 'QtCore.QSortFilterProxyModel', + 'QtCore.QItemSelection': 'QtCore.QItemSelection', + 'QtCore.QItemSelectionModel': 'QtCore.QItemSelectionModel', + 'QtCore.QItemSelectionRange': 'QtCore.QItemSelectionRange', + # 'QtUiTools.QUiLoader': ['QtCompat.loadUi', _loadUi], + # 'shiboken2.wrapInstance': ['QtCompat.wrapInstance', _wrapinstance], + # 'shiboken2.getCppPointer': ['QtCompat.getCppPointer', _getcpppointer], + 'QtWidgets.qApp': 'QtWidgets.QApplication.instance()', + # 'QtCore.QCoreApplication.translate': [ + # 'QtCompat.translate', _translate + # ], + # 'QtWidgets.QApplication.translate': [ + # 'QtCompat.translate', _translate + # ], + # 'QtCore.qInstallMessageHandler': [ + # 'QtCompat.qInstallMessageHandler', _qInstallMessageHandler + # ], + }, + 'PyQt5': { + 'QtCore.pyqtProperty': 'QtCore.Property', + 'QtCore.pyqtSignal': 'QtCore.Signal', + 'QtCore.pyqtSlot': 'QtCore.Slot', + 'QtCore.QAbstractProxyModel': 'QtCore.QAbstractProxyModel', + 'QtCore.QSortFilterProxyModel': 'QtCore.QSortFilterProxyModel', + 'QtCore.QStringListModel': 'QtCore.QStringListModel', + 'QtCore.QItemSelection': 'QtCore.QItemSelection', + 'QtCore.QItemSelectionModel': 'QtCore.QItemSelectionModel', + 'QtCore.QItemSelectionRange': 'QtCore.QItemSelectionRange', + # 'uic.loadUi': ['QtCompat.loadUi', _loadUi], + # 'sip.wrapinstance': ['QtCompat.wrapInstance', _wrapinstance], + # 'sip.unwrapinstance': ['QtCompat.getCppPointer', _getcpppointer], + 'QtWidgets.qApp': 'QtWidgets.QApplication.instance()', + # 'QtCore.QCoreApplication.translate': [ + # 'QtCompat.translate', _translate + # ], + # 'QtWidgets.QApplication.translate': [ + # 'QtCompat.translate', _translate + # ], + # 'QtCore.qInstallMessageHandler': [ + # 'QtCompat.qInstallMessageHandler', _qInstallMessageHandler + # ], + }, +} + +#################################################################################################### + +"""Compatibility Members + +This dictionary is used to build Qt.QtCompat objects that provide a consistent +interface for obsolete members, and differences in binding return values. + +{ + 'binding': { + 'classname': { + 'targetname': 'binding_namespace', + } + } +} + +""" + +_compatibility_members = { + 'PySide2': { + # 'QWidget': { + # 'grab': 'QtWidgets.QWidget.grab', + # }, + # 'QHeaderView': { + # 'sectionsClickable': 'QtWidgets.QHeaderView.sectionsClickable', + # 'setSectionsClickable': + # 'QtWidgets.QHeaderView.setSectionsClickable', + # 'sectionResizeMode': 'QtWidgets.QHeaderView.sectionResizeMode', + # 'setSectionResizeMode': + # 'QtWidgets.QHeaderView.setSectionResizeMode', + # 'sectionsMovable': 'QtWidgets.QHeaderView.sectionsMovable', + # 'setSectionsMovable': 'QtWidgets.QHeaderView.setSectionsMovable', + # }, + # 'QFileDialog': { + # 'getOpenFileName': 'QtWidgets.QFileDialog.getOpenFileName', + # 'getOpenFileNames': 'QtWidgets.QFileDialog.getOpenFileNames', + # 'getSaveFileName': 'QtWidgets.QFileDialog.getSaveFileName', + # }, + }, + 'PyQt5': { + # 'QWidget': { + # 'grab': 'QtWidgets.QWidget.grab', + # }, + # 'QHeaderView': { + # 'sectionsClickable': 'QtWidgets.QHeaderView.sectionsClickable', + # 'setSectionsClickable': + # 'QtWidgets.QHeaderView.setSectionsClickable', + # 'sectionResizeMode': 'QtWidgets.QHeaderView.sectionResizeMode', + # 'setSectionResizeMode': + # 'QtWidgets.QHeaderView.setSectionResizeMode', + # 'sectionsMovable': 'QtWidgets.QHeaderView.sectionsMovable', + # 'setSectionsMovable': 'QtWidgets.QHeaderView.setSectionsMovable', + # }, + # 'QFileDialog': { + # 'getOpenFileName': 'QtWidgets.QFileDialog.getOpenFileName', + # 'getOpenFileNames': 'QtWidgets.QFileDialog.getOpenFileNames', + # 'getSaveFileName': 'QtWidgets.QFileDialog.getSaveFileName', + # }, + }, +} diff --git a/src/QtShim/Wrapper.py b/src/QtShim/Wrapper.py new file mode 100644 index 0000000..a398f00 --- /dev/null +++ b/src/QtShim/Wrapper.py @@ -0,0 +1,34 @@ +#################################################################################################### + +def _qInstallMessageHandler(handler): + '''Install a message handler that works in all bindings + + Args: + handler: A function that takes 3 arguments, or None + ''' + + def messageOutputHandler(*args): + # In Qt5 bindings, message handlers are passed 3 arguments + # The first argument is a QtMsgType + # The last argument is the message to be printed + # The Middle argument (if passed) is a QMessageLogContext + if len(args) == 3: + msgType, logContext, msg = args + elif len(args) == 2: + msgType, msg = args + logContext = None + else: + raise TypeError( + 'handler expected 2 or 3 arguments, got {0}'.format(len(args))) + + if isinstance(msg, bytes): + # In python 3, some bindings pass a bytestring, which cannot be + # used elsewhere. Decoding a python 2 or 3 bytestring object will + # consistently return a unicode object. + msg = msg.decode() + + handler(msgType, logContext, msg) + + if not handler: + handler = messageOutputHandler + return Qt._QtCore.qInstallMessageHandler(handler) diff --git a/src/QtShim/__init__.py b/src/QtShim/__init__.py new file mode 100644 index 0000000..0438cc0 --- /dev/null +++ b/src/QtShim/__init__.py @@ -0,0 +1,389 @@ +"""Minimal Python 3 shim around PyQt5 and Pyside2 Qt bindings for QML applications. + +Forked from https://github.com/mottosso/Qt.py under MIT License. +Copyright (c) 2016 Marcus Ottosson + +Changes + +* Dropped Python2 and Qt4 support +* Focus on last Python 3 release +* Focus on last Qt API : QML + +Requirements + +* make use of lazy loading to speed up startup time ! +""" + +# Fixme: ressource file CodeReview/QtApplication/rcc/CodeReviewRessource.py + +#################################################################################################### + +# Enable support for `from Qt import *` +__all__ = [] + +#################################################################################################### + +import importlib +import logging +import os +import sys +import types + +from .QtConfig import _common_members, _misplaced_members, _compatibility_members + +#################################################################################################### + +# _module_logger = logging.getLogger(__name__) + +#################################################################################################### + +# Flags from environment variables +QT_VERBOSE = bool(os.getenv('QT_VERBOSE')) + +QT_PREFERRED_BINDING = os.getenv('QT_PREFERRED_BINDING', '') +if QT_PREFERRED_BINDING: + QT_PREFERRED_BINDING = list(x for x in QT_PREFERRED_BINDING.split(',') if x) +else: + # on dec 2018, PySide2 is still not fully operational + QT_PREFERRED_BINDING = ('PyQt5', 'PySide2') + +#################################################################################################### + +def _new_module(name): + return types.ModuleType(__name__ + '.' + name) + +#################################################################################################### + +# Reference to Qt.py +Qt = sys.modules[__name__] +Qt.QtCompat = _new_module('QtCompat') + +#################################################################################################### + +def _log(text): + if QT_VERBOSE: + # _logger print + sys.stdout.write(text + '\n') + +#################################################################################################### + +def _import_sub_module(module, name): + """import a submodule""" + _log('_import_sub_module {} {}'.format(module, name)) + module_name = module.__name__ + '.' + name # e.g. PyQt5.QtCore + module = importlib.import_module(module_name) + return module + +#################################################################################################### + +def _setup(module, extras): + """Install common submodules""" + + Qt.__binding__ = module.__name__ + + for name in list(_common_members) + extras: + try: + submodule = _import_sub_module(module, name) + except ImportError: + try: + # For extra modules like sip and shiboken that may not be + # children of the binding. + submodule = __import__(name) + except ImportError: + continue + + setattr(Qt, '_' + name, submodule) + + if name not in extras: + # Store reference to original binding + setattr(Qt, name, _new_module(name)) # Qt.QtCore = module(so module) + +#################################################################################################### + +def _reassign_misplaced_members(binding): + """Apply misplaced members from `binding` to Qt.py + + Arguments: + binding (dict): Misplaced members + + """ + + for src, dst in _misplaced_members[binding].items(): + # print() + dst_value = None + + # Fixme: to func + src_parts = src.split('.') + src_module = src_parts[0] + if len(src_parts): + src_member = src_parts[1:] + else: + src_member = None + + if isinstance(dst, (list, tuple)): + dst, dst_value = dst + # print(src, '->', dst, dst_value) + # print(src_module, src_member) + + dst_parts = dst.split('.') + dst_module = dst_parts[0] + if len(dst_parts): + dst_member = dst_parts[1] + else: + dst_member = None + # print(dst_module, dst_member) + + # Get the member we want to store in the namesapce. + if not dst_value: + try: + _part = getattr(Qt, '_' + src_module) + while src_member: + member = src_member.pop(0) + _part = getattr(_part, member) + dst_value = _part + except AttributeError: + # If the member we want to store in the namespace does not + # exist, there is no need to continue. This can happen if a + # request was made to rename a member that didn't exist, for + # example if QtWidgets isn't available on the target platform. + _log('Misplaced member has no source: {0}'.format(src)) + continue + # print(dst_value) + + try: + # Fixme: src_object ??? + src_object = getattr(Qt, dst_module) + except AttributeError: + # print('Failed to get src_object') + if dst_module not in _common_members: + # Only create the Qt parent module if its listed in + # _common_members. Without this check, if you remove QtCore + # from _common_members, the default _misplaced_members will add + # Qt.QtCore so it can add Signal, Slot, etc. + msg = "Not creating missing member module '{m}' for '{c}'" + _log(msg.format(m=dst_module, c=dst_member)) + continue + # If the dst is valid but the Qt parent module does not exist + # then go ahead and create a new module to contain the member. + setattr(Qt, dst_module, _new_module(dst_module)) + src_object = getattr(Qt, dst_module) + # Enable direct import of the new module + sys.modules[__name__ + '.' + dst_module] = src_object + + if not dst_value: + dst_value = getattr(Qt, '_' + src_module) + if src_member: + dst_value = getattr(dst_value, src_member) + + setattr( + src_object, + dst_member or dst_module, + dst_value + ) + +#################################################################################################### + +def _build_compatibility_members(binding, decorators=None): + """Apply `binding` to QtCompat + + Arguments: + binding (str): Top level binding in _compatibility_members. + decorators (dict, optional): Provides the ability to decorate the + original Qt methods when needed by a binding. This can be used + to change the returned value to a standard value. The key should + be the classname, the value is a dict where the keys are the + target method names, and the values are the decorator functions. + + """ + + decorators = decorators or dict() + + # Allow optional site-level customization of the compatibility members. + # This method does not need to be implemented in QtSiteConfig. + try: + import QtSiteConfig + except ImportError: + pass + else: + if hasattr(QtSiteConfig, 'update_compatibility_decorators'): + QtSiteConfig.update_compatibility_decorators(binding, decorators) + + _QtCompat = type('QtCompat', (object,), {}) + + for classname, bindings in _compatibility_members[binding].items(): + attrs = {} + for target, binding in bindings.items(): + namespaces = binding.split('.') + try: + src_object = getattr(Qt, '_' + namespaces[0]) + except AttributeError as e: + _log('QtCompat: AttributeError: %s' % e) + # Skip reassignment of non-existing members. + # This can happen if a request was made to + # rename a member that didn't exist, for example + # if QtWidgets isn't available on the target platform. + continue + + # Walk down any remaining namespace getting the object assuming + # that if the first namespace exists the rest will exist. + for namespace in namespaces[1:]: + src_object = getattr(src_object, namespace) + + # decorate the Qt method if a decorator was provided. + if target in decorators.get(classname, []): + # staticmethod must be called on the decorated method to + # prevent a TypeError being raised when the decorated method + # is called. + src_object = staticmethod( + decorators[classname][target](src_object)) + + attrs[target] = src_object + + # Create the QtCompat class and install it into the namespace + compat_class = type(classname, (_QtCompat,), attrs) + setattr(Qt.QtCompat, classname, compat_class) + +#################################################################################################### + +def _pyside2(): + """Initialise PySide2 + + These functions serve to test the existence of a binding + along with set it up in such a way that it aligns with + the final step; adding members from the original binding + to Qt.py + + """ + + import PySide2 as module + extras = [] + # try: + # from PySide2 import shiboken2 + # extras.append('shiboken2') + # except ImportError: + # pass + + _setup(module, extras) + Qt.__binding_version__ = module.__version__ + + # if hasattr(Qt, '_shiboken2'): + # Qt.QtCompat.wrapInstance = _wrapinstance + # Qt.QtCompat.getCppPointer = _getcpppointer + # Qt.QtCompat.delete = shiboken2.delete + + if hasattr(Qt, '_QtCore'): + Qt.__qt_version__ = Qt._QtCore.qVersion() + + # if hasattr(Qt, '_QtWidgets'): + # Qt.QtCompat.setSectionResizeMode = \ + # Qt._QtWidgets.QHeaderView.setSectionResizeMode + + _reassign_misplaced_members('PySide2') + # _build_compatibility_members('PySide2') + +#################################################################################################### + +def _pyqt5(): + """Initialise PyQt5""" + + import PyQt5 as module + extras = [] + # try: + # import sip + # extras.append(sip.__name__) + # except ImportError: + # sip = None + + _setup(module, extras) + # if hasattr(Qt, '_sip'): + # Qt.QtCompat.wrapInstance = _wrapinstance + # Qt.QtCompat.getCppPointer = _getcpppointer + # Qt.QtCompat.delete = sip.delete + + if hasattr(Qt, '_QtCore'): + Qt.__binding_version__ = Qt._QtCore.PYQT_VERSION_STR + Qt.__qt_version__ = Qt._QtCore.QT_VERSION_STR + + # if hasattr(Qt, '_QtWidgets'): + # Qt.QtCompat.setSectionResizeMode = \ + # Qt._QtWidgets.QHeaderView.setSectionResizeMode + + _reassign_misplaced_members('PyQt5') + # _build_compatibility_members('PyQt5') + +#################################################################################################### + +def _install(): + + # Default order (customise order and content via QT_PREFERRED_BINDING) + order = QT_PREFERRED_BINDING + + available = { + 'PySide2': _pyside2, + 'PyQt5': _pyqt5, + } + + _log("Order: {}".format(' '.join(order))) + + found_binding = False + for name in order: + _log('Trying %s' % name) + + try: + available[name]() + found_binding = True + break + + except ImportError as e: + _log('ImportError: %s' % e) + + except KeyError: + _log("ImportError: Preferred binding '%s' not found." % name) + + if not found_binding: + # If not binding were found, throw this error + raise ImportError('No Qt binding were found.') + + # Install individual members + for name, members in _common_members.items(): + try: + their_submodule = getattr(Qt, '_' + name) + except AttributeError: + continue + + our_submodule = getattr(Qt, name) + + # Enable import * + __all__.append(name) + + # Enable direct import of submodule, + # e.g. import Qt.QtCore + sys.modules[__name__ + '.' + name] = our_submodule + + for member in members: + # Accept that a submodule may miss certain members. + try: + their_member = getattr(their_submodule, member) + except AttributeError: + _log("'%s.%s' was missing." % (name, member)) + continue + setattr(our_submodule, member, their_member) + + # Enable direct import of QtCompat + sys.modules['Qt.QtCompat'] = Qt.QtCompat + +#################################################################################################### + +_install() + +#################################################################################################### + +# Fixme: Python 3.7 +# def __getattr__(name): +# print('__getattr__', name) + +#################################################################################################### + +# Setup Binding Enum states +Qt.IsPySide2 = Qt.__binding__ == 'PySide2' +Qt.IsPyQt5 = not Qt.IsPySide2 diff --git a/src/QtShim/not-implemented.py b/src/QtShim/not-implemented.py new file mode 100644 index 0000000..be022e1 --- /dev/null +++ b/src/QtShim/not-implemented.py @@ -0,0 +1,116 @@ +#################################################################################################### + +def _getcpppointer(object): + if hasattr(Qt, '_shiboken2'): + return getattr(Qt, '_shiboken2').getCppPointer(object)[0] + elif hasattr(Qt, '_sip'): + return getattr(Qt, '_sip').unwrapinstance(object) + raise AttributeError("'module' has no attribute 'getCppPointer'") + +#################################################################################################### + +def _wrapinstance(ptr, base=None): + '''Enable implicit cast of pointer to most suitable class + + This behaviour is available in sip per default. + + Based on http://nathanhorne.com/pyqtpyside-wrap-instance + + Usage: + This mechanism kicks in under these circumstances. + 1. Qt.py is using PySide 1 or 2. + 2. A `base` argument is not provided. + + See :func:`QtCompat.wrapInstance()` + + Arguments: + ptr (int): Pointer to QObject in memory + base (QObject, optional): Base class to wrap with. Defaults to QObject, + which should handle anything. + + ''' + + assert isinstance(ptr, int), "Argument 'ptr' must be of type " + assert (base is None) or issubclass(base, Qt.QtCore.QObject), ( + "Argument 'base' must be of type ") + + if Qt.IsPyQt4 or Qt.IsPyQt5: + func = getattr(Qt, '_sip').wrapinstance + elif Qt.IsPySide2: + func = getattr(Qt, '_shiboken2').wrapInstance + elif Qt.IsPySide: + func = getattr(Qt, '_shiboken').wrapInstance + else: + raise AttributeError("'module' has no attribute 'wrapInstance'") + + if base is None: + q_object = func(int(ptr), Qt.QtCore.QObject) + meta_object = q_object.metaObject() + class_name = meta_object.className() + super_class_name = meta_object.superClass().className() + + if hasattr(Qt.QtWidgets, class_name): + base = getattr(Qt.QtWidgets, class_name) + + elif hasattr(Qt.QtWidgets, super_class_name): + base = getattr(Qt.QtWidgets, super_class_name) + + else: + base = Qt.QtCore.QObject + + return func(int(ptr), base) + +#################################################################################################### + +def _translate(context, sourceText, *args): + # In Qt4 bindings, translate can be passed 2 or 3 arguments + # In Qt5 bindings, translate can be passed 2 arguments + # The first argument is disambiguation[str] + # The last argument is n[int] + # The middle argument can be encoding[QtCore.QCoreApplication.Encoding] + if len(args) == 3: + disambiguation, encoding, n = args + elif len(args) == 2: + disambiguation, n = args + encoding = None + else: + raise TypeError( + 'Expected 4 or 5 arguments, got {0}.'.format(len(args) + 2)) + + if hasattr(Qt.QtCore, 'QCoreApplication'): + app = getattr(Qt.QtCore, 'QCoreApplication') + else: + raise NotImplementedError( + 'Missing QCoreApplication implementation for {binding}'.format( + binding=Qt.__binding__, + ) + ) + if Qt.__binding__ in ('PySide2', 'PyQt5'): + sanitized_args = [context, sourceText, disambiguation, n] + else: + sanitized_args = [ + context, + sourceText, + disambiguation, + encoding or app.CodecForTr, + n + ] + return app.translate(*sanitized_args) + +#################################################################################################### +#################################################################################################### + +def _none(): + '''Internal option (used in installer)''' + + Mock = type('Mock', (), {'__getattr__': lambda Qt, attr: None}) + + Qt.__binding__ = 'None' + Qt.__qt_version__ = '0.0.0' + Qt.__binding_version__ = '0.0.0' + Qt.QtCompat.loadUi = lambda uifile, baseinstance=None: None + Qt.QtCompat.setSectionResizeMode = lambda *args, **kwargs: None + + for submodule in _common_members.keys(): + setattr(Qt, submodule, Mock()) + setattr(Qt, '_' + submodule, Mock()) diff --git a/tasks/__init__.py b/tasks/__init__.py new file mode 100644 index 0000000..ce165f0 --- /dev/null +++ b/tasks/__init__.py @@ -0,0 +1,37 @@ +#################################################################################################### +# +# CodeReview - A Code Review GUI +# Copyright (C) 2019 Fabrice Salvaire +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +#################################################################################################### + +# http://www.pyinvoke.org + +#################################################################################################### + +from invoke import task, Collection + # import sys + +#################################################################################################### + +from . import clean +from . import doc +from . import release + +ns = Collection() +ns.add_collection(Collection.from_module(clean)) +ns.add_collection(Collection.from_module(release)) +ns.add_collection(Collection.from_module(doc)) diff --git a/tasks/clean.py b/tasks/clean.py new file mode 100644 index 0000000..2ef1b71 --- /dev/null +++ b/tasks/clean.py @@ -0,0 +1,38 @@ +#################################################################################################### +# +# CodeReview - A Code Review GUI +# Copyright (C) 2019 Fabrice Salvaire +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +#################################################################################################### + +#################################################################################################### + +from invoke import task + +#################################################################################################### + +@task +def clean_flycheck(ctx): + with ctx.cd(ctx.Package): + ctx.run('find . -name "flycheck*.py" -exec rm {} \;') + +@task +def clean_emacs_backup(ctx): + ctx.run('find . -name "*~" -type f -exec rm -f {} \;') + +@task(clean_flycheck, clean_emacs_backup) +def clean(ctx): + pass diff --git a/tasks/data/license-template.py b/tasks/data/license-template.py new file mode 100644 index 0000000..1de9fa0 --- /dev/null +++ b/tasks/data/license-template.py @@ -0,0 +1,19 @@ +#################################################################################################### +# +# CodeReview - A Code Review GUI +# Copyright (C) 2019 Fabrice Salvaire +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +#################################################################################################### diff --git a/tasks/data/license-template.qml b/tasks/data/license-template.qml new file mode 100644 index 0000000..63f63df --- /dev/null +++ b/tasks/data/license-template.qml @@ -0,0 +1,19 @@ +/*************************************************************************************************** + * + * CodeReview - A Code Review GUI + * Copyright (C) 2019 Fabrice Salvaire + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + ***************************************************************************************************/ diff --git a/tasks/doc.py b/tasks/doc.py new file mode 100644 index 0000000..7e6af7d --- /dev/null +++ b/tasks/doc.py @@ -0,0 +1,90 @@ +#################################################################################################### +# +# CodeReview - A Code Review GUI +# Copyright (C) 2019 Fabrice Salvaire +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +#################################################################################################### + +#################################################################################################### + +from pathlib import Path + +from invoke import task + +#################################################################################################### + +from .clean import clean_flycheck as clean_flycheck + +#################################################################################################### + +CODEREVIEW_SOURCE_PATH = Path(__file__).resolve().parents[1] + +SPHINX_PATH = CODEREVIEW_SOURCE_PATH.joinpath('doc', 'sphinx') +BUILD_PATH = SPHINX_PATH.joinpath('build') +RST_SOURCE_PATH = SPHINX_PATH.joinpath('source') +RST_API_PATH = RST_SOURCE_PATH.joinpath('api') +RST_EXAMPLES_PATH = RST_SOURCE_PATH.joinpath('examples') + +#################################################################################################### + +@task +def clean_api(ctx): + ctx.run('rm -rf doc/sphinx/source/api') + +#################################################################################################### + +@task(clean_flycheck, clean_api) +def make_api(ctx): + print('\nGenerate RST API files') + ctx.run('pyterate-rst-api {0.Package}'.format(ctx)) + print('\nRun Sphinx') + with ctx.cd('doc/sphinx/'): + ctx.run('./make-html') #--clean + +#################################################################################################### + +@task +def run_sphinx(ctx): + print() + print('Run Sphinx') + working_path = SPHINX_PATH + # subprocess.run(('make-html'), cwd=working_path) + # --clean + with ctx.cd(str(working_path)): + ctx.run('make-html') + +@task(run_sphinx) +def publish(ctx): + with ctx.cd(str(CODEREVIEW_SOURCE_PATH.joinpath('gh-pages'))): + ctx.run('update-gh-pages') + +#################################################################################################### + +@task() +def make_readme(ctx): + from setup_data import long_description + with open('README.rst', 'w') as fh: + fh.write(long_description) + # import subprocess + # subprocess.call(('rst2html', 'README.rst', 'README.html')) + ctx.run('rst2html README.rst README.html') + +#################################################################################################### + +@task +def update_authors(ctx): + # Keep authors in the order of appearance and use awk to filter out dupes + ctx.run("git log --format='- %aN <%aE>' --reverse | awk '!x[$0]++' > AUTHORS") diff --git a/tasks/release.py b/tasks/release.py new file mode 100644 index 0000000..f25e113 --- /dev/null +++ b/tasks/release.py @@ -0,0 +1,200 @@ +#################################################################################################### +# +# CodeReview - A Code Review GUI +# Copyright (C) 2019 Fabrice Salvaire +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +#################################################################################################### + +#################################################################################################### + +from pathlib import Path +import re +import shutil + +from invoke import task + +#################################################################################################### + +STANDARD_PACKAGES = ( + 'argparse', + 'atexit', + 'datetime', + 'glob', + 'hashlib', + 'importlib', + 'json', + 'logging', + 'operator', + 'os', + 'pathlib', + 'shutil', + 'signal', + 'stat', + 'subprocess', + 'sys', + 'time', + 'traceback', +) + +@task() +def show_import(ctx): + package = ctx.Package + with ctx.cd(package): + result = ctx.run("grep -r -h -E '^(import|from) [a-zA-Z]' . | sort | uniq", hide='out') + imports = set() + for line in result.stdout.split('\n'): + if line.startswith('from'): + position = line.find('import') + line = line[5:position] + elif line.startswith('import'): + line = line[7:] + # print('|{}|'.format(line)) + position = line.find('.') + if position != -1: + line = line[:position] + line = line.strip() + if line: + imports.add(line) + imports -= set(STANDARD_PACKAGES) + imports -= set((package,)) + for item in sorted(imports): + print(item) + +@task +def find_package(ctx, name): + ctx.run('pip freeze | grep -i {}'.format(name)) + +#################################################################################################### + +@task() +def update_git_sha(ctx): + # Fixme: wrong workflow, must tag the last commit + result = ctx.run('git describe --tags --abbrev=0 --always', hide='out') + tag = result.stdout.strip() + print(f"Tag is {tag}") + if tag.startswith('v'): + version = tag[1:] + version = version.replace('-branched', '') + else: + version = tag + if not re.match('\d+(\.\d+(\.\d+)?)?', version): + raise ValueError('Invalid version {}'.format(version)) + result = ctx.run('git rev-parse HEAD', hide='out') + sha = result.stdout.strip() + print(f"SHA {sha}") + print(f"Tag {tag}") + print(f"Version {version}") + filename = Path(ctx.Package, '__init__.py') + with open(str(filename), 'r') as fh: + lines = fh.readlines() + with open(filename, 'w') as fh: + for line in lines: + if line.startswith('__version__'): + line = "__version__ = '{}'\n".format(version) + if line.startswith('GIT_TAG'): + line = "GIT_TAG = '{}'\n".format(tag) + if line.startswith('GIT_SHA'): + line = "GIT_SHA = '{}'\n".format(sha) + fh.write(line) + +#################################################################################################### + +@task() +def clean(ctx): + for directory in ('build', 'dist', 'PySpice.egg-info'): + shutil.rmtree(directory, ignore_errors=True) + +@task() +def show_python_site(ctx): + ctx.run('python3 -m site') + +@task(update_git_sha) +def build(ctx): + ctx.run('python3 setup.py build') + +@task(build) +def install(ctx): + ctx.run('python3 setup.py install') + +# @task(clean, build) +# def sdist(ctx): +# ctx.run('python3 setup.py sdist') + +# @task(clean, build) +# def bdist(ctx): +# ctx.run('python3 setup.py bdist') + +@task(clean, build) +def wheel(ctx): + ctx.run('python3 setup.py sdist') + ctx.run('python3 setup.py bdist_wheel') + +@task(wheel) +def upload(ctx): + # Twine documentation: https://pypi.org/project/twine/ + # ctx.run('twine register dist/*whl') + # Sign using + # BA24CE0F65CB8C67 Fabrice SALVAIRE + # registered on key servers: hkps://hkps.pool.sks-keyservers.net hkp://pgp.mit.edu + ctx.run('gpg --detach-sign --armor dist/*tar.gz') + ctx.run('gpg --detach-sign --armor dist/*whl') + # Binary wheel 'CodeReview-1.1.0-cp310-cp310-linux_x86_64.whl' has an unsupported platform tag 'linux_x86_64'. + # see https://github.com/pypa/manylinux + # see https://github.com/pypa/auditwheel + ctx.run('twine upload dist/*tar.gz') + +def _get_pipy_json(): + import requests + response = requests.get('https://pypi.org/pypi/PySpice/json') + assert(response.status_code == requests.codes.ok) + return response.json() + +@task() +def get_pypi_json(ctx): + import json + print(json.dumps(_get_pipy_json(), sort_keys=True, indent=4)) + +@task() +def get_wheel(ctx): + # https://dzone.com/articles/package-signing-in-pip-it-works-in-a-roundabout-so + data = _get_pipy_json() + version = data['info']['version'] + filename = data['urls'][0]['filename'] + wheel_url = data['urls'][0]['url'] + print('Get version {}'.format(version)) + ctx.run('curl --output {} {}'.format(filename, wheel_url)) + asc_suffix = '.asc' + filename_asc = filename + asc_suffix + ctx.run('curl --output {} {}'.format(filename_asc, wheel_url + asc_suffix)) + # ctx.run('gpg --keyserver pgp.mit.edu --search-keys {}'.format(key_id)) + ctx.run('gpg --verify {} {}'.format(filename_asc, filename)) + +#################################################################################################### + +@task() +def get_github_tar_sha(ctx): + # Fixme: check git sha + result = ctx.run('git describe --tags --abbrev=0 --always', hide='out') + tag = result.stdout.strip() + url = 'https://github.com/FabriceSalvaire/PySpice/archive/{}.tar.gz'.format(tag) + print('Get', url) + import hashlib + import requests + response = requests.get(url, allow_redirects=True) + assert(response.status_code == requests.codes.ok) + sha = hashlib.sha256(response.content) + print(sha.hexdigest()) + diff --git a/tasks/scripts/Makefile b/tasks/scripts/Makefile new file mode 100644 index 0000000..45e869a --- /dev/null +++ b/tasks/scripts/Makefile @@ -0,0 +1,18 @@ +# -*- Makefile -*- + +#################################################################################################### + +all: code-review.rcc CodeReviewRessource.py + +#################################################################################################### + +%.rcc : %.qrc + rcc-qt5 -binary $< -o $@ + +CodeReviewRessource.py : code-review.qrc + pyrcc5 -o $@ $< + +#################################################################################################### + +clean: + rm *.py *.rcc diff --git a/tasks/scripts/get-material-icon b/tasks/scripts/get-material-icon new file mode 100755 index 0000000..61673a4 --- /dev/null +++ b/tasks/scripts/get-material-icon @@ -0,0 +1,184 @@ +#! /usr/bin/env python3 + +#################################################################################################### + +# https://material.io/tools/icons/static/icons/baseline-save-24px.svg +# https://material.io/tools/icons/static/icons/baseline-save-black-36.zip + +# https://material.io/tools/icons/static/icons/outline-save-black-36.zip +# https://material.io/tools/icons/static/icons/round-save-black-36.zip +# https://material.io/tools/icons/static/icons/sharp-save-black-36.zip +# https://material.io/tools/icons/static/icons/twotone-save-black-36.zip + +# https://material.io/tools/icons/static/icons/baseline-save-white-36.zip + +# 1x/baseline_save_black_18dp.png: PNG image data, 18 x 18, 8-bit gray+alpha, non-interlaced +# 1x/baseline_save_black_24dp.png: PNG image data, 24 x 24, 8-bit gray+alpha, non-interlaced +# 1x/baseline_save_black_36dp.png: PNG image data, 36 x 36, 8-bit gray+alpha, non-interlaced +# 1x/baseline_save_black_48dp.png: PNG image data, 48 x 48, 8-bit gray+alpha, non-interlaced + +# 2x/baseline_save_black_18dp.png: PNG image data, 36 x 36, 8-bit gray+alpha, non-interlaced +# 2x/baseline_save_black_24dp.png: PNG image data, 48 x 48, 8-bit gray+alpha, non-interlaced +# 2x/baseline_save_black_36dp.png: PNG image data, 72 x 72, 8-bit gray+alpha, non-interlaced +# 2x/baseline_save_black_48dp.png: PNG image data, 96 x 96, 8-bit gray+alpha, non-interlaced + +#################################################################################################### + +from pathlib import Path +from zipfile import ZipFile +import argparse +import os +import shutil +import tempfile +import urllib3 + +#################################################################################################### + +parser = argparse.ArgumentParser(description='Fetch material icon.') + +parser.add_argument( + 'src_name', metavar='NAME', + help='icon name', +) + +parser.add_argument( + '--dst-name', + default=None, + help='dst name, default is same name', +) + +parser.add_argument( + '--style', + default='baseline', + help='style: [baseline], outline, round, twotone, sharp', +) + +parser.add_argument( + '--color', + default='black', + help='color: [black], white', +) + +args = parser.parse_args() + +#################################################################################################### + +urllib3.disable_warnings() + +#################################################################################################### + +class MaterialIconFetcher: + + SCALE = (1, 2) + DP = (18, 24, 36, 48) + + ############################################## + + def __init__(self, icons_path, theme): + + self._icons_path = Path(str(icons_path)).resolve() + self._theme = str(theme) + self._theme_path = self._icons_path.joinpath(self._theme) + + if not self._icons_path.exists(): + os.mkdir(self._icons_path) + if not self._theme_path.exists(): + os.mkdir(self._theme_path) + + self._http = urllib3.PoolManager() + + # with tempfile.TemporaryDirectory() as tmp_directory: + self._tmp_directory = tempfile.TemporaryDirectory() + self._tmp_directory_path = Path(self._tmp_directory.name) + print('tmp_directory', self._tmp_directory_path) + + ############################################## + + def _fetch_ressource(self, url): + print('Fetch', url, '...') + request = self._http.request('GET', url) + return request.data + + ############################################## + + def _fetch_png_icon(self, **kwargs): + + # https://material.io/tools/icons/static/icons/baseline-save-black-36.zip + root = 'https://material.io/tools/icons/static/icons/' + url_pattern = '{style}-{name}-{color}-{dp}.zip' + filename = url_pattern.format(**kwargs) + url = root + filename + + data = self._fetch_ressource(url) + + return filename, data + + ############################################## + + def _extract_png_archive(self, **kwargs): + + filename, data = self._fetch_png_icon(**kwargs) + zip_path = self._tmp_directory_path.joinpath(filename) + with open(zip_path, 'wb') as fh: + fh.write(data) + with ZipFile(zip_path, 'r') as zip_archive: + zip_archive.extractall(self._tmp_directory_path) + + ############################################## + + def fetch_icon(self, src_name, dst_name, style, color): + + kwargs = dict(src_name=src_name, dst_name=dst_name, style=style, color=color) + + for dp in self.DP: + self._extract_png_archive(name=src_name, dp=dp, **kwargs) + + print() + for scale in self.SCALE: + for dp in self.DP: + dst_kwargs = dict(kwargs) + dst_kwargs.update(dict(scale=scale, dp=dp)) + + # 1x/baseline_save_black_18dp.png + filename_pattern = '{style}_{src_name}_{color}_{dp}dp.png' + src_path = self._tmp_directory_path.joinpath( + '{scale}x'.format(**dst_kwargs), + filename_pattern.format(**dst_kwargs), + ) + + if scale > 1: + size_directory = '{dp}x{dp}@{scale}'.format(**dst_kwargs) + else: + size_directory = '{dp}x{dp}'.format(**dst_kwargs) + size_path = self._theme_path.joinpath(size_directory) + if not size_path.exists(): + os.mkdir(size_path) + filename_pattern = '{dst_name}-{color}.png' + filename = filename_pattern.format(**dst_kwargs) + dst_path = size_path.joinpath(filename) + + # print('Copy', src_path, dst_path) + shutil.copyfile(src_path, dst_path) + + rcc_pattern = 'icons/{}/{}/{}' + rcc_line = rcc_pattern.format(self._theme, size_directory, filename) + if dp != 36: + rcc_line = ''.format(rcc_line) + rcc_line = ' '*8 + rcc_line + print(rcc_line) + +#################################################################################################### + +root_path = Path(__file__).resolve().parents[1] +icons_path = root_path.joinpath('share', 'icons') +theme = 'material' +print('Icons path:', icons_path, theme) + +fetcher = MaterialIconFetcher(icons_path, theme) + +fetcher.fetch_icon( + args.src_name, + args.dst_name or args.src_name.replace('_', '-'), + args.style, + args.color, +) diff --git a/tasks/scripts/make-icon-png b/tasks/scripts/make-icon-png new file mode 100755 index 0000000..b982a8d --- /dev/null +++ b/tasks/scripts/make-icon-png @@ -0,0 +1,80 @@ +#! /usr/bin/env python3 + +#################################################################################################### +# +# CodeReview - A Code Review GUI +# Copyright (C) 2019 Fabrice Salvaire +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +#################################################################################################### + +#################################################################################################### + +from pathlib import Path +import argparse +import subprocess + +#################################################################################################### + +parser = argparse.ArgumentParser(description='Generate PNG files from a SVG.') +parser.add_argument( + 'svg_path', metavar='file.svg', + help='SVG PATH', +) +args = parser.parse_args() + +#################################################################################################### + +theme = 'material' +DP = (36, 48) +SCALE = (1, 2) + +svg_path = Path(args.svg_path).resolve() +theme_path = svg_path.parents[1].joinpath(theme) +print('Theme path:', theme_path) + +#################################################################################################### + +inkscape_options = [ + '--export-area-page', + '--export-background=white', + '--export-background-opacity=0', +] + +#################################################################################################### + +def run_inkscape(svg_path, dp, scale): + filename = svg_path.name.replace('.svg', '.png') + if scale > 1: + size_directory = '{dp}x{dp}@{scale}'.format(dp=dp, scale=scale) + else: + size_directory = '{dp}x{dp}'.format(dp=dp) + png_path = theme_path.joinpath(size_directory, filename) + command = ( + 'inkscape', + *inkscape_options, + '--export-png={}'.format(png_path), + # --export-width= + '--export-height={}'.format(dp*scale), + str(svg_path), + ) + print('>', ' '.join(command)) + subprocess.check_call(command) + +#################################################################################################### + +for dp in DP: + for scale in SCALE: + run_inkscape(svg_path, dp, scale) diff --git a/tasks/scripts/make-logo-png b/tasks/scripts/make-logo-png new file mode 100755 index 0000000..d5dd866 --- /dev/null +++ b/tasks/scripts/make-logo-png @@ -0,0 +1,67 @@ +#! /usr/bin/env python3 + +#################################################################################################### +# +# CodeReview - A Code Review GUI +# Copyright (C) 2019 Fabrice Salvaire +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +#################################################################################################### + +#################################################################################################### + +from pathlib import Path +import argparse +import subprocess + +#################################################################################################### + +parser = argparse.ArgumentParser(description='Generate PNG files from a SVG.') +parser.add_argument( + 'svg_path', metavar='file.svg', + help='SVG PATH', +) +args = parser.parse_args() + +svg_path = Path(args.svg_path).resolve() + +#################################################################################################### + +sizes = (32, 64, 96, 128, 256, 512) + +inkscape_options = [ + '--export-area-page', + '--export-background=white', + '--export-background-opacity=0', +] + +#################################################################################################### + +def run_inkscape(svg_path, size): + command = ( + 'inkscape', + *inkscape_options, + '--export-png=logo-{}.png'.format(size), + # --export-width= + '--export-height={}'.format(size), + str(svg_path), + ) + print('>', ' '.join(command)) + subprocess.check_call(command) + +#################################################################################################### + +for size in sizes: + run_inkscape(svg_path, size) diff --git a/tasks/scripts/translate b/tasks/scripts/translate new file mode 100755 index 0000000..d1f0d4c --- /dev/null +++ b/tasks/scripts/translate @@ -0,0 +1,534 @@ +#! /usr/bin/env python3 + +#################################################################################################### +# +# CodeReview - A Code Review GUI +# Copyright (C) 2019 Fabrice Salvaire +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +#################################################################################################### + +"""Program to manage and translate Qt translation files. + +It features: + +* lupdate, linguist, lrelease +* conversion to/from .po file +* poedit +* translation using Google Translation + +Actions are run in the right order. + +""" + +#################################################################################################### + +from pathlib import Path +import argparse +import os +import subprocess + +from googletrans import Translator +# https://py-googletrans.readthedocs.io/en/latest/ + +#################################################################################################### + +DEFAULT_LANGUAGES = ( + 'fr_FR', +) + +# Fixme: project dependent +source_path = Path(__file__).resolve().parents[1] +qml_path = source_path.joinpath('qml') +py_path = source_path.joinpath('CodeReview') +translation_path = source_path.joinpath('share', 'translations') +base_name = 'code-review' + +#################################################################################################### + +parser = argparse.ArgumentParser(description='Translate Qt Application.') + +parser.add_argument( + '--language', + default=None, + help='languages, default {}'.format(DEFAULT_LANGUAGES), +) + +parser.add_argument( + '--update', + default=False, + action='store_true', + help='run lupdate', +) + +parser.add_argument( + '--translate', + default=False, + action='store_true', + help='translate using Google Translate', +) + +parser.add_argument( + '--convert-to-po', + default=False, + action='store_true', + help='convert to .po file', +) + +parser.add_argument( + '--convert-from-po', + default=False, + action='store_true', + help='convert from .po file', +) + +parser.add_argument( + '--poedit', + default=False, + action='store_true', + help='run poedit', +) + +parser.add_argument( + '--linguist', + default=False, + action='store_true', + help='run linguist', +) + +parser.add_argument( + '--release', + default=False, + action='store_true', + help='run lrelease', +) + +args = parser.parse_args() + +#################################################################################################### + +def _clean_path(path): + return Path(str(path)).resolve() + +#################################################################################################### + +class TanslationManager: + + ############################################## + + def __init__(self, translation_path, base_name, py_path, qml_path): + + self._translation_path = _clean_path(translation_path) + self._base_name = base_name + self._py_path = _clean_path(py_path) + self._qml_path = _clean_path(qml_path) + + if not self._translation_path.exists(): + os.mkdir(self._translation_path) + + ############################################## + + def ts_path(self, scope, language): + if scope: + filename = '{}.{}.{}.ts'.format(self._base_name, scope, language) + else: + filename = '{}.{}.ts'.format(self._base_name, language) + return self._translation_path.joinpath(filename) + + def po_path(self, scope, language): + filename = '{}.{}.{}.po'.format(self._base_name, scope, language) + return self._translation_path.joinpath(filename) + + ############################################## + + def run_lupdate(self, language): + + # Usage: + # lupdate [options] [project-file]... + # lupdate [options] [source-file|path|@lst-file]... -ts ts-files|@lst-file + # + # lupdate is part of Qt's Linguist tool chain. It extracts translatable + # messages from Qt UI files, C++, Java and JavaScript/QtScript source code. + # Extracted messages are stored in textual translation source files (typically + # Qt TS XML). New and modified messages can be merged into existing TS files. + # + # Options: + # -help Display this information and exit. + # -no-obsolete + # Drop all obsolete and vanished strings. + # -extensions [,]... + # Process files with the given extensions only. + # The extension list must be separated with commas, not with whitespace. + # Default: 'java,jui,ui,c,c++,cc,cpp,cxx,ch,h,h++,hh,hpp,hxx,js,qs,qml,qrc'. + # -pluralonly + # Only include plural form messages. + # -silent + # Do not explain what is being done. + # -no-sort + # Do not sort contexts in TS files. + # -no-recursive + # Do not recursively scan the following directories. + # -recursive + # Recursively scan the following directories (default). + # -I or -I + # Additional location to look for include files. + # May be specified multiple times. + # -locations {absolute|relative|none} + # Specify/override how source code references are saved in TS files. + # Guessed from existing TS files if not specified. + # Default is absolute for new files. + # -no-ui-lines + # Do not record line numbers in references to UI files. + # -disable-heuristic {sametext|similartext|number} + # Disable the named merge heuristic. Can be specified multiple times. + # -pro + # Name of a .pro file. Useful for files with .pro file syntax but + # different file suffix. Projects are recursed into and merged. + # -pro-out + # Virtual output directory for processing subsequent .pro files. + # -pro-debug + # Trace processing .pro files. Specify twice for more verbosity. + # -source-language [_] + # Specify the language of the source strings for new files. + # Defaults to POSIX if not specified. + # -target-language [_] + # Specify the language of the translations for new files. + # Guessed from the file name if not specified. + # -tr-function-alias {+=,=}[,{+=,=}]... + # With +=, recognize as an alternative spelling of . + # With =, recognize as the only spelling of . + # Available s (with their currently defined aliases) are: + # Q_DECLARE_TR_FUNCTIONS (=Q_DECLARE_TR_FUNCTIONS) + # QT_TR_N_NOOP (=QT_TR_N_NOOP) + # QT_TRID_N_NOOP (=QT_TRID_N_NOOP) + # QT_TRANSLATE_N_NOOP (=QT_TRANSLATE_N_NOOP) + # QT_TRANSLATE_N_NOOP3 (=QT_TRANSLATE_N_NOOP3) + # QT_TR_NOOP (=QT_TR_NOOP) + # QT_TRID_NOOP (=QT_TRID_NOOP) + # QT_TRANSLATE_NOOP (=QT_TRANSLATE_NOOP) + # QT_TRANSLATE_NOOP3 (=QT_TRANSLATE_NOOP3) + # QT_TR_NOOP_UTF8 (=QT_TR_NOOP_UTF8) + # QT_TRANSLATE_NOOP_UTF8 (=QT_TRANSLATE_NOOP_UTF8) + # QT_TRANSLATE_NOOP3_UTF8 (=QT_TRANSLATE_NOOP3_UTF8) + # findMessage (=findMessage) + # qtTrId (=qtTrId) + # tr (=tr) + # trUtf8 (=trUtf8) + # translate (=translate) + # qsTr (=qsTr) + # qsTrId (=qsTrId) + # qsTranslate (=qsTranslate) + # -ts ... + # Specify the output file(s). This will override the TRANSLATIONS. + # -version + # Display the version of lupdate and exit. + # @lst-file + # Read additional file names (one per line) or includepaths (one per + # line, and prefixed with -I) from lst-file. + + command = ( + 'lupdate-qt5', + '-extensions', 'qml,js', + '-source-language', 'en_GB', + '-target-language', language, + str(self._qml_path), + '-ts', str(self.ts_path('qml', language)), + ) + print('>', ' '.join(command)) + subprocess.check_call(command) + + py_filenames = [] + for root, _, filenames in os.walk(self._py_path): + root = Path(root) + for filename in filenames: + filename = Path(filename) + if filename.suffix == '.py': + filename = str(root.joinpath(filename)) + py_filenames.append(filename) + + command = ( + 'pylupdate5', + '-verbose', + *py_filenames, + '-ts', str(self.ts_path('py', language)), + ) + print('>', ' '.join(command)) + subprocess.check_call(command) + + # Fixme: pylupdate5 don't support qml and folder !!! + + # print('Fix obsolete') + # with open(ts_path, 'r') as fh: + # content = fh.readlines() + # with open(ts_path, 'w') as fh: + # for line in content: + # line = line.replace('type="obsolete"', '') + # line = line.replace('', '') + # fh.write(line) + + ############################################## + + def run_linguist(self, language): + + for scope in ('qml', 'py'): + command = ( + 'linguist-qt5', + str(self.ts_path(scope, language)), + ) + print('>', ' '.join(command)) + subprocess.check_call(command) + + ############################################## + + def run_poedit(self, language): + + command = ( + 'poedit', + str(self.po_path(language)), + ) + print('>', ' '.join(command)) + subprocess.check_call(command) + + ############################################## + + def run_lrelease(self, language): + + # Usage: + # lrelease [options] project-file + # lrelease [options] ts-files [-qm qm-file] + # + # lrelease is part of Qt's Linguist tool chain. It can be used as a + # stand-alone tool to convert XML-based translations files in the TS + # format into the 'compiled' QM format used by QTranslator objects. + # + # Options: + # -help Display this information and exit + # -idbased + # Use IDs instead of source strings for message keying + # -compress + # Compress the QM files + # -nounfinished + # Do not include unfinished translations + # -removeidentical + # If the translated text is the same as + # the source text, do not include the message + # -markuntranslated + # If a message has no real translation, use the source text + # prefixed with the given string instead + # -silent + # Do not explain what is being done + # -version + # Display the version of lrelease and exit + + command = ( + 'lconvert-qt5', + '-i', + str(self.ts_path('qml', language)), + str(self.ts_path('py', language)), + '-o', str(self.ts_path(None, language)), + ) + print('>', ' '.join(command)) + subprocess.check_call(command) + + command = ( + 'lrelease-qt5', + str(self.ts_path(None, language)), + ) + print('>', ' '.join(command)) + subprocess.check_call(command) + + ############################################## + + def run_lconvert(self, language, format): + + # Usage: + # lconvert [options] [...] + # + # lconvert is part of Qt's Linguist tool chain. It can be used as a + # stand-alone tool to convert and filter translation data files. + # The following file formats are supported: + # + # qm - Traductions Qt compilées + # pot - Fichiers de modèle de localisation GNU Gettext + # qph - Qt Linguist "livre de phrases" + # ts - Sources de traduction Qt + # po - Fichiers de localisation GNU Gettext + # xlf - Fichiers de localisation XLIFF + # + # If multiple input files are specified, they are merged with + # translations from later files taking precedence. + # + # Options: + # -h + # -help Display this information and exit. + # + # -i + # -input-file + # Specify input file. Use if might start with a dash. + # This option can be used several times to merge inputs. + # May be '-' (standard input) for use in a pipe. + # + # -o + # -output-file + # Specify output file. Default is '-' (standard output). + # + # -if + # -input-format + # Specify input format for subsequent s. + # The format is auto-detected from the file name and defaults to 'ts'. + # + # -of + # -output-format + # Specify output format. See -if. + # + # -drop-tags + # Drop named extra tags when writing TS or XLIFF files. + # May be specified repeatedly. + # + # -drop-translations + # Drop existing translations and reset the status to 'unfinished'. + # Note: this implies --no-obsolete. + # + # -source-language [_] + # Specify/override the language of the source strings. Defaults to + # POSIX if not specified and the file does not name it yet. + # + # -target-language [_] + # Specify/override the language of the translation. + # The target language is guessed from the file name if this option + # is not specified and the file contents name no language yet. + # + # -no-obsolete + # Drop obsolete messages. + # + # -no-finished + # Drop finished messages. + # + # -no-untranslated + # Drop untranslated messages. + # + # -sort-contexts + # Sort contexts in output TS file alphabetically. + # + # -locations {absolute|relative|none} + # Override how source code references are saved in TS files. + # Default is absolute. + # + # -no-ui-lines + # Drop line numbers from references to UI files. + # + # -verbose + # be a bit more verbose + # + # Long options can be specified with only one leading dash, too. + # + # Return value: + # 0 on success + # 1 on command line parse failures + # 2 on read failures + # 3 on write failures + + is_po = format == 'po' + + for scope in ('qml', 'py'): + command = ( + 'lconvert-qt5', + '-i' if is_po else '-o', str(self.ts_path(scope, language)), + '-o' if is_po else '-i', str(self.po_path(language)), + ) + print('>', ' '.join(command)) + subprocess.check_call(command) + + ############################################## + + def translate(self, language): + for scope in ('qml', 'py'): + self.translate_scope(scope, language) + + ############################################## + + def translate_scope(self, scope, language): + + print('Translate {} {} ...'.format(scope, language)) + + language_code = language[:2] + if language_code == 'en': + return + + translator = Translator( + service_urls=[ + 'translate.google.' + language_code, + 'translate.google.com', + ], + ) + + def translate_string(source): + return translator.translate(source, src='en', dest=language_code).text + + ts_path = self.ts_path(scope, language) + with open(ts_path, 'r') as fh: + lines = list(fh.readlines()) + + with open(ts_path, 'w') as fh: + source = None + for line in lines: + striped_line = line.strip() + if striped_line.startswith(''): + source = striped_line[striped_line.find('>')+1:striped_line.rfind('<')] + elif striped_line.startswith(''): + old_translation = striped_line[striped_line.find('>')+1:striped_line.rfind('<')] + if not old_translation: + translation = translate_string(source) + print() + print(source) + print(translation) + line = line.replace(' type="unfinished">', '>' + translation) + fh.write(line) + +#################################################################################################### + +manager = TanslationManager( + translation_path, + base_name, + py_path, + qml_path, +) + +if not args.language: + languages = DEFAULT_LANGUAGES +else: + languages = [x for x in [x.strip() for x in args.languages.split(',')] if x] + +if args.update: + for language in languages: + manager.run_lupdate(language) +if args.translate: + for language in languages: + manager.translate(language) +if args.convert_to_po: + for language in languages: + manager.run_lconvert(language, 'po') +if args.poedit: + for language in languages: + manager.run_poedit(language) +if args.convert_from_po: + for language in languages: + manager.run_lconvert(language, 'ts') +if args.linguist: + for language in languages: + manager.run_linguist(language) +if args.release: + for language in languages: + manager.run_lrelease(language) diff --git a/tasks/scripts/upload-to-pypi b/tasks/scripts/upload-to-pypi new file mode 100755 index 0000000..36398ab --- /dev/null +++ b/tasks/scripts/upload-to-pypi @@ -0,0 +1,11 @@ +#! /bin/bash + +# bdist +# python setup.py check --verbose --metadata --restructuredtext --strict && \ +# python setup.py register sdist upload + +python setup.py bdist_wheel + +twine register dist/*whl +gpg --detach-sign -a dist/*whl +twine upload dist/* diff --git a/tools/RstFactory.py b/tools/RstFactory.py deleted file mode 100644 index 0957398..0000000 --- a/tools/RstFactory.py +++ /dev/null @@ -1,242 +0,0 @@ -#################################################################################################### -# -# CodeReview - A Code Review GUI -# Copyright (C) 2015 Fabrice Salvaire -# -# This program is free software: you can redistribute it and/or modify it under the terms of the GNU -# General Public License as published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without -# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License along with this program. If -# not, see . -# -#################################################################################################### - -""" This module provides tools to build a Sphinx API documentation. """ - -#################################################################################################### - -import os - -#################################################################################################### - -class RstFactory(object): - - """ This class build recursively a Sphinx API documentation from a Python root module. """ - - init_file_name = '__init__.py' - - end_marker = """ -.. End -""" - - ############################################## - - def __init__(self, module_path, rst_directory, excluded_directory): - - self._rst_directory = os.path.realpath(rst_directory) - self._root_module_path = os.path.realpath(module_path) - - self._excluded_directory = [os.path.join(self._root_module_path, x) for x in excluded_directory] - self._root_module_name = os.path.basename(self._root_module_path) - - print("RST API Path: ", self._rst_directory) - print("Root Module Path:", self._root_module_path) - print("Root Module Name:", self._root_module_name) - print('Exclude:', '\n '.join(self._excluded_directory)) - - if not os.path.exists(self._rst_directory): - os.mkdir(self._rst_directory) - - self._process_recursively() - - ############################################## - - def _process_recursively(self): - - """ Process recursively the inner Python files and directories. """ - - for module_path, sub_directories, files in os.walk(self._root_module_path, followlinks=True): - for sub_directory in list(sub_directories): - if os.path.join(module_path, sub_directory) in self._excluded_directory: - del sub_directories[sub_directories.index(sub_directory)] - if self.is_python_directory_module(module_path): - python_files = [file_name - for file_name in files - if self.is_python_file(file_name)] - sub_modules = [sub_directory - for sub_directory in sub_directories - if self.is_python_directory_module(os.path.join(module_path, sub_directory))] - if python_files or sub_modules: - self._process_directory_module(module_path, sorted(python_files), sorted(sub_modules)) - - ############################################## - - def _process_directory_module(self, module_path, python_files, sub_modules): - - directory_module_name = os.path.basename(module_path) - directory_module_python_path = self.module_path_to_python_path(module_path) - dst_directory = self.join_rst_path(self.python_path_to_path(directory_module_python_path)) - print() - print("Directory Module Name:", directory_module_name) - print("Directory Module Python Path:", directory_module_python_path) - print("Dest Path:", dst_directory) - - if not os.path.exists(dst_directory): - os.mkdir(dst_directory) - - # Generate a RST file per module - module_names = [] - for file_name in python_files: - module_name = self.filename_to_module(file_name) - module_names.append(module_name) - print(" Module:", module_name) - rst = self._generate_rst_module(directory_module_python_path, module_name) - rst_file_name = os.path.join(dst_directory, module_name + '.rst') - with open(rst_file_name, 'w') as f: - f.write(rst) - - # Generate the TOC RST file - rst = self._generate_toc(directory_module_name, sorted(module_names + sub_modules)) - rst += '\n' - rst += self._generate_automodule(directory_module_python_path) - rst += self.end_marker - rst_file_name = os.path.join(os.path.dirname(dst_directory), directory_module_name + '.rst') - with open(rst_file_name, 'w') as f: - f.write(rst) - - ############################################## - - @staticmethod - def is_python_directory_module(path): - - return os.path.exists(os.path.join(path, RstFactory.init_file_name)) - - ############################################## - - @staticmethod - def is_python_file(file_name): - - return (file_name.endswith('.py') and - file_name != RstFactory.init_file_name and - 'flymake'not in file_name) - - ############################################## - - @staticmethod - def path_to_python_path(path): - - return path.replace(os.path.sep, '.') - - ############################################## - - @staticmethod - def python_path_to_path(python_path): - - return python_path.replace('.', os.path.sep) - - ############################################## - - @staticmethod - def join_python_path(*args): - - return '.'.join(args) - - ############################################## - - @staticmethod - def filename_to_module(file_name): - - return file_name[:-3] # suppress '.py' - - ############################################## - - def module_path_to_python_path(self, path): - - return self.path_to_python_path(self._root_module_name + path[len(self._root_module_path):]) - - ############################################## - - def join_rst_path(self, path): - - return os.path.join(self._rst_directory, path) - - ############################################## - - def _generate_title(self, module_name): - - mod_rst = ' :mod:`' - - template = """ -%(header_line)s -%(mod)s%(module_name)s` -%(header_line)s""" - - rst = template.lstrip() % dict( - module_name=module_name, - mod=mod_rst, - header_line='*'*(len(module_name) + len(mod_rst) +2), - ) - - return rst - - ############################################## - - def _generate_toc(self, directory_module_name, module_names): - - template = """%(title)s - -.. toctree:: -""" - - rst = template.lstrip() % dict( - title=self._generate_title(directory_module_name), - ) - - for module_name in module_names: - rst += ' '*2 + os.path.join(directory_module_name, module_name) + '\n' - - return rst - - ############################################## - - def _generate_automodule(self, module_path): - - template = """ -.. automodule:: %(module_path)s - :members: - :show-inheritance: -""" - - rst = template.lstrip() % dict( - module_path=module_path, - ) - - return rst - - ############################################## - - def _generate_rst_module(self, module_path, module_name): - - template = """%(title)s - -%(automodule)s -""" - - rst = template.lstrip() % dict( - title=self._generate_title(module_name), - automodule=self._generate_automodule(RstFactory.join_python_path(module_path, module_name)) - ) - rst += self.end_marker - - return rst - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/tools/generate-rst-api.sh b/tools/generate-rst-api.sh deleted file mode 100755 index 9c772e7..0000000 --- a/tools/generate-rst-api.sh +++ /dev/null @@ -1,27 +0,0 @@ -#################################################################################################### - -api=doc/sphinx/source/api -##old_api=doc/sphinx/old-api - -##mkdir -p ${old_api} -#mv --backup=numbered $api ${old_api} - -echo -echo Generate RST API files -./tools/generate-rst-api - -echo -echo Run Sphinx -pushd doc/sphinx/ -./make-html #--clean -popd - -##echo -##echo Old api directory moved to -##ls -l -h ${old_api} - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/tools/perl-grep b/tools/perl-grep deleted file mode 100755 index d31cf01..0000000 --- a/tools/perl-grep +++ /dev/null @@ -1,20 +0,0 @@ -#! /usr/bin/perl -# -*- perl -*- - -exit(1)if (@ARGV < 2); - -my $pattern = $ARGV[0]; -my $filename = $ARGV[1]; - -# print "$pattern $filename"; - -open(INPUT, $filename); - -while () -{ - exit(0) if (/$pattern/); -} - -exit(1); - -# End diff --git a/tools/replace b/tools/replace deleted file mode 100755 index 50bf4b0..0000000 --- a/tools/replace +++ /dev/null @@ -1,148 +0,0 @@ -#! /usr/bin/env python -# -*- python -*- - -#################################################################################################### -# -# CodeReview - Diff Viewer -# Copyright (C) 2014 Salvaire Fabrice -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -#################################################################################################### - -#################################################################################################### - -import os -import re -import subprocess -import sys - -from optparse import OptionParser - -#################################################################################################### - -default_excluded_extension = ','.join(('~', - '#', - '.diff', - '.pdf', - '.pyc', - '.tex', - '.xml', - '.txt', - )) - -default_exclusion_pattern = '\.(bzr|git)' - -#################################################################################################### -# -# Options -# - -usage = 'usage: %prog [options]' - -parser = OptionParser(usage) - -parser.add_option('--root-tree-path', - dest='root_tree_path', - type='string', default='.', - help='root path') - -parser.add_option('--exclude', - dest='exclusion_pattern', - type='string', default=default_exclusion_pattern, - help='exclusion regexp [%s]' % (default_exclusion_pattern)) - -parser.add_option('--exclude-extension', - dest='excluded_extension', - type='string', default=default_excluded_extension, - help='exclude extension [%s]' % (default_excluded_extension)) - -parser.add_option('--pattern', - dest='pattern', - type='string', default=None, - help='pattern') - -parser.add_option('--new-pattern', - dest='new_pattern', - type='string', default=None, - help='new pattern') - -opt, args = parser.parse_args() - -#################################################################################################### - -def to_absolute_path(path): - - return os.path.abspath(os.path.expanduser(path)) - -#################################################################################################### - -program_path = os.path.dirname(os.path.abspath(__file__)) -perl_grep = os.path.join(program_path, 'perl-grep') - -root_tree_path = to_absolute_path(opt.root_tree_path) - -if opt.pattern is None: - sys.exit(1) - -excluded_extension = opt.excluded_extension.split(',') - -if opt.exclusion_pattern is not None: - exclude_re = re.compile(opt.exclusion_pattern) -else: - exclude_re = None - -#################################################################################################### - -def process_file(absolut_file_name): - - return_code = subprocess.call([perl_grep, opt.pattern, absolut_file_name]) - if return_code == 0: - print(absolut_file_name) - - if opt.new_pattern is not None: - subprocess.call(['sed', - '--in-place=~', - 's/%s/%s/g' % (opt.pattern, opt.new_pattern), - absolut_file_name, - ]) - -#################################################################################################### - -for root, dirs, files in os.walk(root_tree_path): - for file_name in files: - - absolut_file_name = os.path.join(root, file_name) - - skipped = False - for extension in excluded_extension: - if file_name.endswith(extension): - # print 'Exclude for extension', extension, file_name - skipped = True - break - - if not skipped and exclude_re.search(absolut_file_name) is not None: - # print 'Exclude for regexp', file_name - skipped = True - - if not skipped: - process_file(absolut_file_name) - -sys.exit(0) - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..a0b9b4d --- /dev/null +++ b/tox.ini @@ -0,0 +1,11 @@ +# tox (https://tox.readthedocs.io/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +envlist = py36 + +[testenv] +commands = pytest unit-test +deps = pytest diff --git a/unit-test/test_AttributeDictionaryInterface.py b/unit-test-broken/test_AttributeDictionaryInterface.py similarity index 91% rename from unit-test/test_AttributeDictionaryInterface.py rename to unit-test-broken/test_AttributeDictionaryInterface.py index 42843e1..be2b71a 100644 --- a/unit-test/test_AttributeDictionaryInterface.py +++ b/unit-test-broken/test_AttributeDictionaryInterface.py @@ -22,10 +22,11 @@ #################################################################################################### -from Babel.Tools.AttributeDictionaryInterface import (ExtendedDictionaryInterface, - ReadOnlyAttributeDictionaryInterface, - AttributeDictionaryInterface, - AttributeDictionaryInterfaceDescriptor) +from CodeReview.Tools.AttributeDictionaryInterface import ( + ExtendedDictionaryInterface, + ReadOnlyAttributeDictionaryInterface, + AttributeDictionaryInterface, + AttributeDictionaryInterfaceDescriptor) #################################################################################################### @@ -195,9 +196,3 @@ def test_iter(self): if __name__ == '__main__': unittest.main() - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/unit-test/test_RawTextDocumentDiff.py b/unit-test-broken/test_RawTextDocumentDiff.py similarity index 90% rename from unit-test/test_RawTextDocumentDiff.py rename to unit-test-broken/test_RawTextDocumentDiff.py index e428a72..4d85952 100644 --- a/unit-test/test_RawTextDocumentDiff.py +++ b/unit-test-broken/test_RawTextDocumentDiff.py @@ -32,13 +32,13 @@ def test(self): raw_text_document1 = RawTextDocument(text1) raw_text_document2 = RawTextDocument(text2) - + two_way_file_diff_factory = TwoWayFileDiffFactory() file_diff = two_way_file_diff_factory.process(raw_text_document1, raw_text_document2) file_diff.pretty_print() file_diff.print_unidiff() - + print('='*10) replace_group = file_diff[1] print(replace_group) @@ -52,13 +52,13 @@ def test(self): def test_empty(self): text1 = '' - + with open(self._join_data_path('test_file2.py')) as f: text2 = f.read() raw_text_document1 = RawTextDocument(text1) raw_text_document2 = RawTextDocument(text2) - + two_way_file_diff_factory = TwoWayFileDiffFactory() file_diff = two_way_file_diff_factory.process(raw_text_document1, raw_text_document2) @@ -70,9 +70,3 @@ def test_empty(self): if __name__ == '__main__': unittest.main() - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/unit-test/test_TextDocumentDiffModel.py b/unit-test-broken/test_TextDocumentDiffModel.py similarity index 92% rename from unit-test/test_TextDocumentDiffModel.py rename to unit-test-broken/test_TextDocumentDiffModel.py index 488b6e0..dc5e29b 100644 --- a/unit-test/test_TextDocumentDiffModel.py +++ b/unit-test-broken/test_TextDocumentDiffModel.py @@ -41,14 +41,14 @@ def test(self): text2 = f.read() lexer = get_lexer_for_filename('data/test_file1.py', stripnl=False) - + raw_text_document1 = RawTextDocument(text1) raw_text_document2 = RawTextDocument(text2) file_diff = TwoWayFileDiffFactory().process(raw_text_document1, raw_text_document2) document_model1, document_model2 = TextDocumentDiffModelFactory().process(file_diff) highlighted_text1 = HighlightedText(raw_text_document1, lexer) - + print('Document 1:') self._pretty_print(document_model1) print('\nHighlighted Document 1:') @@ -60,7 +60,7 @@ def test(self): ############################################## def _pretty_print(self, document_model): - + for text_block in document_model: print('='*100) print(text_block) @@ -79,9 +79,3 @@ def _pretty_print(self, document_model): if __name__ == '__main__': unittest.main() - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/unit-test/data/test_file1.py b/unit-test/data/test_file1.py index 02943cd..68e6457 100644 --- a/unit-test/data/test_file1.py +++ b/unit-test/data/test_file1.py @@ -76,9 +76,3 @@ def function3(x): adminiculi indigenis viribus a fundamento ipso carinae ad supremos usque carbasos aedificet onerariam navem omnibusque armamentis instructam mari committat. """ - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/unit-test/data/test_file2.py b/unit-test/data/test_file2.py index 80cb90e..4bc1b06 100644 --- a/unit-test/data/test_file2.py +++ b/unit-test/data/test_file2.py @@ -78,9 +78,3 @@ def function3(x): adminiculi indigenis viribus a fundamento ipso carinae ad supremos usque carbasos aedificet onerariam navem omnibusque armamentis instructam mari committat. """ - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/unit-test/data/test_file_template.py b/unit-test/data/test_file_template.py index 7f78334..34a7ea3 100644 --- a/unit-test/data/test_file_template.py +++ b/unit-test/data/test_file_template.py @@ -25,5 +25,5 @@ def function2(x): var1 = function2(10) var2 = function2(20) - + # this is the End diff --git a/unit-test/test_EnumFactory.py b/unit-test/test_EnumFactory.py index 714870e..c0c2508 100644 --- a/unit-test/test_EnumFactory.py +++ b/unit-test/test_EnumFactory.py @@ -13,7 +13,7 @@ class TestEnumFactory(unittest.TestCase): def test(self): enum1 = EnumFactory('Enum1', ('cst1', 'cst2')) - + self.assertEqual(enum1.cst1, 0) self.assertEqual(repr(enum1.cst1), 'cst1') self.assertEqual(str(enum1.cst1), 'cst1') @@ -21,12 +21,12 @@ def test(self): self.assertEqual(repr(enum1.cst2), 'cst2') self.assertEqual(str(enum1.cst2), 'cst2') self.assertEqual(len(enum1), 2) - + enum2 = ExplicitEnumFactory('Enum2', {'cst1':1, 'cst2':3}) - + self.assertEqual(enum2.cst1, 1) self.assertEqual(enum2.cst2, 3) - + self.assertTrue(enum2.cst2 in enum2) #################################################################################################### @@ -34,9 +34,3 @@ def test(self): if __name__ == '__main__': unittest.main() - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/unit-test/test_RawTextDocument.py b/unit-test/test_RawTextDocument.py index 31de0d0..431ab1b 100644 --- a/unit-test/test_RawTextDocument.py +++ b/unit-test/test_RawTextDocument.py @@ -85,9 +85,3 @@ def test_empty(self): if __name__ == '__main__': unittest.main() - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/unit-test/test_Slice.py b/unit-test/test_Slice.py index 88d9410..12783a1 100644 --- a/unit-test/test_Slice.py +++ b/unit-test/test_Slice.py @@ -41,9 +41,3 @@ def test(self): if __name__ == '__main__': unittest.main() - -#################################################################################################### -# -# End -# -#################################################################################################### diff --git a/unit-test/test_SyntaxHighlighter.py b/unit-test/test_SyntaxHighlighter.py index 6c4bf33..711f60a 100644 --- a/unit-test/test_SyntaxHighlighter.py +++ b/unit-test/test_SyntaxHighlighter.py @@ -23,7 +23,7 @@ def test(self): text = f.read() raw_text_document = RawTextDocument(text) - + lexer = get_lexer_for_filename(test_file_path, stripnl=False) highlighted_text = HighlightedText(raw_text_document, lexer) @@ -35,9 +35,3 @@ def test(self): if __name__ == '__main__': unittest.main() - -#################################################################################################### -# -# End -# -####################################################################################################