-
Notifications
You must be signed in to change notification settings - Fork 126
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #37813 from robertapplin/37730-tool-to-create-mvp-…
…template-files Add tool for generating example MVP files from a template
- Loading branch information
Showing
14 changed files
with
524 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
# Mantid Repository : https://github.com/mantidproject/mantid | ||
# | ||
# Copyright © 2024 ISIS Rutherford Appleton Laboratory UKRI, | ||
# NScD Oak Ridge National Laboratory, European Spallation Source, | ||
# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS | ||
# SPDX - License - Identifier: GPL - 3.0 + | ||
from os.path import abspath, dirname, join | ||
from typing import Callable, Union | ||
|
||
TEMPLATE_DIRECTORY = join(dirname(abspath(__file__)), "templates") | ||
|
||
|
||
def _python_filename(file_type: str, name: Union[str, None] = None) -> str: | ||
"""Create a filename string with a Python filename convention.""" | ||
return f"{name.lower()}_{file_type.lower()}" if name is not None else file_type.lower() | ||
|
||
|
||
def _cpp_filename(file_type: str, name: Union[str, None] = None) -> str: | ||
"""Create a filename string with a C++ filename convention.""" | ||
return f"{name}{file_type}" if name is not None else file_type | ||
|
||
|
||
def _generate_setup_file(name: str, filename: Callable, setup_filename: str, extension: str, output_directory: str) -> None: | ||
"""Generates a file which is used to setup or launch the generated MVP widget.""" | ||
template_filepath = join(TEMPLATE_DIRECTORY, f"{setup_filename}.{extension}.in") | ||
with open(template_filepath, mode="r") as file: | ||
content = file.read() | ||
|
||
content = content.replace("Model", f"{name}Model") | ||
content = content.replace("View", f"{name}View") | ||
content = content.replace("Presenter", f"{name}Presenter") | ||
# Only required for python launch file | ||
content = content.replace("from model", "from " + filename("Model", name)) | ||
content = content.replace("from view", "from " + filename("View", name)) | ||
content = content.replace("from presenter", "from " + filename("Presenter", name)) | ||
|
||
output_filepath = join(output_directory, f"{setup_filename}.{extension}") | ||
with open(output_filepath, mode="w") as file: | ||
file.write(content) | ||
|
||
|
||
def _generate_mvp_file(name: str, filename: Callable, file_type: str, extension: str, output_directory: str) -> None: | ||
"""Generates a file using the corresponding template in the template directory.""" | ||
template_filepath = join(TEMPLATE_DIRECTORY, f"{filename(file_type)}.{extension}.in") | ||
with open(template_filepath, mode="r") as file: | ||
content = file.read() | ||
|
||
for mvp_type in ["Model", "View", "Presenter"]: | ||
content = content.replace(mvp_type, f"{name}{mvp_type}") | ||
|
||
output_filepath = join(output_directory, f"{filename(file_type, name)}.{extension}") | ||
with open(output_filepath, mode="w") as file: | ||
file.write(content) | ||
|
||
|
||
def _generate_python_files(name: str, include_setup: bool, output_directory: str) -> None: | ||
"""Generate MVP files for a Python use case.""" | ||
print("Generating Python files with an MVP pattern...") | ||
|
||
_generate_mvp_file(name, _python_filename, "View", "py", output_directory) | ||
_generate_mvp_file(name, _python_filename, "Presenter", "py", output_directory) | ||
_generate_mvp_file(name, _python_filename, "Model", "py", output_directory) | ||
if include_setup: | ||
_generate_setup_file(name, _python_filename, "launch", "py", output_directory) | ||
|
||
print(f"Output directory: {output_directory}") | ||
print("Done!") | ||
|
||
|
||
def _generate_cpp_files(name: str, include_setup: bool, output_directory: str) -> None: | ||
"""Generate MVP files for a C++ use case.""" | ||
print("Generating C++ files with an MVP pattern...") | ||
|
||
_generate_mvp_file(name, _cpp_filename, "View", "cpp", output_directory) | ||
_generate_mvp_file(name, _cpp_filename, "Presenter", "cpp", output_directory) | ||
_generate_mvp_file(name, _cpp_filename, "Model", "cpp", output_directory) | ||
_generate_mvp_file(name, _cpp_filename, "View", "h", output_directory) | ||
_generate_mvp_file(name, _cpp_filename, "Presenter", "h", output_directory) | ||
_generate_mvp_file(name, _cpp_filename, "Model", "h", output_directory) | ||
if include_setup: | ||
_generate_setup_file(name, _cpp_filename, "main", "cpp", output_directory) | ||
_generate_setup_file(name, _cpp_filename, "CMakeLists", "txt", output_directory) | ||
|
||
print(f"Output directory: {output_directory}") | ||
print("Done!") | ||
|
||
|
||
def _generate_files(name: str, language: str, include_setup: bool, output_directory: str) -> None: | ||
"""Generate MVP files for a specific programming language.""" | ||
match language.lower(): | ||
case "python": | ||
_generate_python_files(name, include_setup, output_directory) | ||
case "c++": | ||
_generate_cpp_files(name, include_setup, output_directory) | ||
case _: | ||
raise ValueError(f"An unsupported language '{language}' has been provided. Choose one: [Python, C++].") | ||
|
||
|
||
if __name__ == "__main__": | ||
from argparse import ArgumentParser, BooleanOptionalAction | ||
|
||
parser = ArgumentParser(description="Generates files which can be used as an initial Model-View-Presenter template.") | ||
parser.add_argument("-n", "--name", required=True, help="The base name to use for the files and classes.") | ||
parser.add_argument("-l", "--language", required=True, help="The language to generate template MVP files for [Python or C++].") | ||
parser.add_argument( | ||
"-s", | ||
"--include-setup", | ||
action=BooleanOptionalAction, | ||
help="Whether to include setup files such as a launch script (and CMakeLists.txt for C++).", | ||
) | ||
parser.add_argument("-o", "--output-dir", required=True, help="The absolute path to output the generated files to.") | ||
args = parser.parse_args() | ||
|
||
_generate_files(args.name.capitalize(), args.language, args.include_setup, args.output_dir) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
cmake_minimum_required(VERSION 3.21) | ||
|
||
project(MVPWidget) | ||
|
||
set(CMAKE_CXX_STANDARD 17) | ||
|
||
find_package(Qt5 COMPONENTS Core Gui Widgets REQUIRED) | ||
|
||
qt5_wrap_cpp(MOC_FILES View.h) | ||
|
||
add_executable(launch | ||
main.cpp | ||
Model.cpp | ||
View.cpp | ||
Presenter.cpp | ||
${MOC_FILES} | ||
) | ||
|
||
# Link with the Release version of the standard library. This is required because | ||
# Conda does not provide packages with Debug symbols. | ||
if(WIN32) | ||
set_property(TARGET launch PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") | ||
endif() | ||
|
||
target_link_libraries(launch | ||
Qt5::Core | ||
Qt5::Gui | ||
Qt5::Widgets | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// Mantid Repository : https://github.com/mantidproject/mantid | ||
// | ||
// Copyright © 2024 ISIS Rutherford Appleton Laboratory UKRI, | ||
// NScD Oak Ridge National Laboratory, European Spallation Source, | ||
// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS | ||
// SPDX - License - Identifier: GPL - 3.0 + | ||
#include "Model.h" | ||
|
||
|
||
Model::Model() : m_count(0u) {} | ||
|
||
std::size_t Model::count() const { | ||
return m_count; | ||
} | ||
|
||
void Model::setCount(std::size_t const value) { | ||
m_count = value; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// Mantid Repository : https://github.com/mantidproject/mantid | ||
// | ||
// Copyright © 2024 ISIS Rutherford Appleton Laboratory UKRI, | ||
// NScD Oak Ridge National Laboratory, European Spallation Source, | ||
// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS | ||
// SPDX - License - Identifier: GPL - 3.0 + | ||
#pragma once | ||
|
||
#include <string> | ||
#include <cstddef> | ||
|
||
|
||
class IModel { | ||
public: | ||
virtual ~IModel() = default; | ||
|
||
virtual std::size_t count() const = 0; | ||
|
||
virtual void setCount(std::size_t const value) = 0; | ||
}; | ||
|
||
class Model final : public IModel { | ||
|
||
public: | ||
Model(); | ||
~Model() override = default; | ||
|
||
std::size_t count() const override; | ||
|
||
void setCount(std::size_t const value) override; | ||
|
||
private: | ||
std::size_t m_count; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Mantid Repository : https://github.com/mantidproject/mantid | ||
// | ||
// Copyright © 2024 ISIS Rutherford Appleton Laboratory UKRI, | ||
// NScD Oak Ridge National Laboratory, European Spallation Source, | ||
// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS | ||
// SPDX - License - Identifier: GPL - 3.0 + | ||
#include "Presenter.h" | ||
|
||
#include "Model.h" | ||
#include "View.h" | ||
|
||
|
||
Presenter::Presenter(std::unique_ptr<IModel> model, IView *view) : m_model(std::move(model)), m_view(view) { | ||
// Use a subscriber to avoid Qt connections in the presenter | ||
m_view->subscribe(this); | ||
} | ||
|
||
void Presenter::handleButtonClicked() { | ||
// An example method to handle a view event | ||
m_model->setCount(m_model->count() + 1u); | ||
m_view->setLabel(std::to_string(m_model->count())); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// Mantid Repository : https://github.com/mantidproject/mantid | ||
// | ||
// Copyright © 2024 ISIS Rutherford Appleton Laboratory UKRI, | ||
// NScD Oak Ridge National Laboratory, European Spallation Source, | ||
// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS | ||
// SPDX - License - Identifier: GPL - 3.0 + | ||
#pragma once | ||
|
||
#include "Model.h" | ||
|
||
#include <string> | ||
#include <memory> | ||
|
||
|
||
class IView; | ||
|
||
class IPresenter { | ||
public: | ||
virtual ~IPresenter() = default; | ||
|
||
virtual void handleButtonClicked() = 0; | ||
}; | ||
|
||
class Presenter final : public IPresenter { | ||
|
||
public: | ||
Presenter(std::unique_ptr<IModel> model, IView *view); | ||
~Presenter() override = default; | ||
|
||
void handleButtonClicked() override; | ||
|
||
private: | ||
std::unique_ptr<IModel> m_model; | ||
IView *m_view; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// Mantid Repository : https://github.com/mantidproject/mantid | ||
// | ||
// Copyright © 2024 ISIS Rutherford Appleton Laboratory UKRI, | ||
// NScD Oak Ridge National Laboratory, European Spallation Source, | ||
// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS | ||
// SPDX - License - Identifier: GPL - 3.0 + | ||
#include "View.h" | ||
|
||
#include "Presenter.h" | ||
|
||
#include <string> | ||
|
||
#include <QVBoxLayout> | ||
#include <QLabel> | ||
#include <QPushButton> | ||
#include <QString> | ||
|
||
|
||
View::View(QWidget *parent) : QWidget(parent), m_presenter() { | ||
auto layout = new QVBoxLayout(); | ||
auto button = new QPushButton("Increment", this); | ||
m_label = new QLabel("0", this); | ||
|
||
layout->addWidget(button); | ||
layout->addWidget(m_label); | ||
|
||
setLayout(layout); | ||
|
||
connect(button, &QPushButton::clicked, this, &View::notifyButtonClicked); | ||
} | ||
|
||
void View::subscribe(IPresenter *presenter) { | ||
m_presenter = presenter; | ||
} | ||
|
||
void View::notifyButtonClicked() { | ||
// An example event slot which notifies the presenter | ||
m_presenter->handleButtonClicked(); | ||
} | ||
|
||
void View::setLabel(std::string const &text) { | ||
m_label->setText(QString::fromStdString(text)); | ||
} |
Oops, something went wrong.