Plan document file saves in app core
This commit is contained in:
@@ -394,6 +394,9 @@ Known local toolchain state:
|
|||||||
- `pano_cli classify-open` exposes the `pp_app_core` document-open route
|
- `pano_cli classify-open` exposes the `pp_app_core` document-open route
|
||||||
contract as JSON and is covered for project files, ABR imports, PPBR
|
contract as JSON and is covered for project files, ABR imports, PPBR
|
||||||
imports, and malformed path rejection.
|
imports, and malformed path rejection.
|
||||||
|
- `pano_cli plan-document-file` exposes `pp_app_core` document-name
|
||||||
|
validation, legacy `.ppi` path construction, and overwrite-prompt decisions
|
||||||
|
as JSON and is covered for save-now and existing-target overwrite states.
|
||||||
- `pano_cli simulate-app-session` exposes `pp_app_core` project-open,
|
- `pano_cli simulate-app-session` exposes `pp_app_core` project-open,
|
||||||
app-close, save, save-as, save-version, and save-before-workflow decisions
|
app-close, save, save-as, save-version, and save-before-workflow decisions
|
||||||
as JSON and is covered for clean, dirty, already-prompting, missing-canvas,
|
as JSON and is covered for clean, dirty, already-prompting, missing-canvas,
|
||||||
@@ -402,9 +405,9 @@ Known local toolchain state:
|
|||||||
contract for PPI/project files, ABR imports, PPBR imports, inner-dot names,
|
contract for PPI/project files, ABR imports, PPBR imports, inner-dot names,
|
||||||
and malformed paths before the live `App::open_document` performs UI or
|
and malformed paths before the live `App::open_document` performs UI or
|
||||||
legacy canvas work.
|
legacy canvas work.
|
||||||
- `pp_app_core_document_session_tests` covers clean and dirty app session plus
|
- `pp_app_core_document_session_tests` covers clean and dirty app session,
|
||||||
save-request and save-before-workflow decisions without requiring a window,
|
save-request, save-before-workflow, document file target, and overwrite
|
||||||
canvas, or message box.
|
decisions without requiring a window, canvas, or message box.
|
||||||
- `pp_ui_core` consumes vcpkg tinyxml2 only when `PP_USE_VCPKG_TINYXML2=ON`
|
- `pp_ui_core` consumes vcpkg tinyxml2 only when `PP_USE_VCPKG_TINYXML2=ON`
|
||||||
through the vcpkg preset; default and Android validation still use the
|
through the vcpkg preset; default and Android validation still use the
|
||||||
retained vendored fallback tracked by DEBT-0012.
|
retained vendored fallback tracked by DEBT-0012.
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ and validation command.
|
|||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| PPI open/save | `Canvas`, serializer, dialogs | `pp_document`, `pp_assets`, `pano_cli` | Round-trip tiny project, old-version fixture, corrupt/truncated fixture |
|
| PPI open/save | `Canvas`, serializer, dialogs | `pp_document`, `pp_assets`, `pano_cli` | Round-trip tiny project, old-version fixture, corrupt/truncated fixture |
|
||||||
| Open-document routing | `App::open_document` | `pp_app_core`, `pano_cli`, `pp_panopainter_ui`, `pp_document`, `pp_assets` | Project/ABR/PPBR route tests, malformed path tests, CLI route smoke, app open smoke |
|
| Open-document routing | `App::open_document` | `pp_app_core`, `pano_cli`, `pp_panopainter_ui`, `pp_document`, `pp_assets` | Project/ABR/PPBR route tests, malformed path tests, CLI route smoke, app open smoke |
|
||||||
| Document session decisions | `App::open_document`, `App::request_close`, save hotkeys, file menu, dialogs | `pp_app_core`, `pano_cli`, `pp_panopainter_ui` | Clean/dirty/prompt-open/save/save-as/save-version/save-before-workflow decision tests, CLI session smoke, app close/open/save/new/browse smoke |
|
| Document session decisions | `App::open_document`, `App::request_close`, save hotkeys, file menu, dialogs | `pp_app_core`, `pano_cli`, `pp_panopainter_ui` | Clean/dirty/prompt-open/save/save-as/save-version/save-before-workflow/name/overwrite decision tests, CLI session and document-file smoke, app close/open/save/new/browse smoke |
|
||||||
| Version metadata | `scripts/pre-build.py`, `version.*` | build system, `pp_foundation` | Generated header smoke test, missing-tag behavior |
|
| Version metadata | `scripts/pre-build.py`, `version.*` | build system, `pp_foundation` | Generated header smoke test, missing-tag behavior |
|
||||||
| Thumbnail generation/read | `Canvas`, `Image` | `pp_assets`, `pp_paint_renderer` | Golden thumbnail, corrupt input |
|
| Thumbnail generation/read | `Canvas`, `Image` | `pp_assets`, `pp_paint_renderer` | Golden thumbnail, corrupt input |
|
||||||
| Save-as, overwrite prompts | App/dialogs | `pp_app_core`, `pp_panopainter_ui`, `pp_platform_*` | Decision tests, UI automation, and platform smoke |
|
| Save-as, overwrite prompts | App/dialogs | `pp_app_core`, `pp_panopainter_ui`, `pp_platform_*` | Decision tests, UI automation, and platform smoke |
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
|||||||
| --- | --- | --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- | --- | --- |
|
||||||
| DEBT-0001 | Open | Modernization | Existing platform build files remain alongside new CMake | Required for incremental migration without losing platform coverage | Existing platform builds plus new CMake configure | Remove after all platform builds consume shared CMake targets |
|
| DEBT-0001 | Open | Modernization | Existing platform build files remain alongside new CMake | Required for incremental migration without losing platform coverage | Existing platform builds plus new CMake configure | Remove after all platform builds consume shared CMake targets |
|
||||||
| DEBT-0002 | Open | Modernization | Vendored SDK and patched libraries retained initially | Some dependencies are SDK-only, patched, or have platform-specific binaries | Dependency inventory and platform build smoke tests | Replace with vcpkg packages or document permanent vendored status after triplet evaluation |
|
| DEBT-0002 | Open | Modernization | Vendored SDK and patched libraries retained initially | Some dependencies are SDK-only, patched, or have platform-specific binaries | Dependency inventory and platform build smoke tests | Replace with vcpkg packages or document permanent vendored status after triplet evaluation |
|
||||||
| DEBT-0003 | Open | Modernization | Existing singletons remain during initial split; `App::open_document`, `App::request_close`, file-menu save actions, `NodeCanvas` save hotkeys, new/open/browse dirty-document workflow prompts, `pano_cli classify-open`, and `pano_cli simulate-app-session` now consume pure `pp_app_core` route/session contracts, but document loading and saving still reach legacy `Canvas::I` and UI singletons | Avoid behavior changes while introducing component boundaries | App launch and component tests; `pp_app_core_document_route_tests`; `pp_app_core_document_session_tests`; `pano_cli classify-open --path D:/Paint/demo.ppi`; `pano_cli simulate-app-session --unsaved --save-intent save-dirty-version`; `pano_cli simulate-app-session --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Replace singleton reaches with context/service injection at component boundaries |
|
| DEBT-0003 | Open | Modernization | Existing singletons remain during initial split; `App::open_document`, `App::request_close`, file-menu save actions, `NodeCanvas` save hotkeys, new/open/browse dirty-document workflow prompts, new/save-as document file naming and overwrite decisions, `pano_cli classify-open`, `pano_cli plan-document-file`, and `pano_cli simulate-app-session` now consume pure `pp_app_core` route/session contracts, but document loading and saving still reach legacy `Canvas::I` and UI singletons | Avoid behavior changes while introducing component boundaries | App launch and component tests; `pp_app_core_document_route_tests`; `pp_app_core_document_session_tests`; `pano_cli classify-open --path D:/Paint/demo.ppi`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli simulate-app-session --unsaved --save-intent save-dirty-version`; `pano_cli simulate-app-session --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Replace singleton reaches with context/service injection at component boundaries |
|
||||||
| DEBT-0004 | Open | Modernization | Android, Linux, WebGL, Apple, and AppX build files remain platform-specific until root CMake alignment reaches them | Prevent platform regressions during incremental migration; raw Windows `.sln/.vcxproj` files were removed on 2026-05-31 by user decision | `cmake --preset windows-msvc-default`; platform-specific configure/build smoke checks as each platform is migrated | Root CMake owns every platform source list and package path |
|
| DEBT-0004 | Open | Modernization | Android, Linux, WebGL, Apple, and AppX build files remain platform-specific until root CMake alignment reaches them | Prevent platform regressions during incremental migration; raw Windows `.sln/.vcxproj` files were removed on 2026-05-31 by user decision | `cmake --preset windows-msvc-default`; platform-specific configure/build smoke checks as each platform is migrated | Root CMake owns every platform source list and package path |
|
||||||
| DEBT-0005 | Open | Modernization | Temporary local CTest harness is used before Catch2 is wired through vcpkg | `vcpkg` is not currently on PATH, but headless tests need to run now | `ctest --preset desktop-fast --build-config Debug` | Replace `tests/test_harness.h` tests with Catch2 tests once vcpkg toolchain/presets are validated |
|
| DEBT-0005 | Open | Modernization | Temporary local CTest harness is used before Catch2 is wired through vcpkg | `vcpkg` is not currently on PATH, but headless tests need to run now | `ctest --preset desktop-fast --build-config Debug` | Replace `tests/test_harness.h` tests with Catch2 tests once vcpkg toolchain/presets are validated |
|
||||||
| DEBT-0007 | Open | Modernization | `vcpkg.json` and `windows-msvc-vcpkg-headless` are validated for the headless Windows component matrix, but app targets still use vendored libraries and Android/Apple triplets are not proven | Dependency migration must stay incremental while SDK/patched/vendor dependencies remain in use | `$env:VCPKG_ROOT="C:\Program Files\Microsoft Visual Studio\2022\Community\VC\vcpkg"; cmake --preset windows-msvc-vcpkg-headless`; `ctest --preset desktop-fast-vcpkg --build-config Debug` | Component targets consume vcpkg packages where reliable and desktop app, Android, and Apple triplets are validated or explicitly documented as permanent vendor exceptions |
|
| DEBT-0007 | Open | Modernization | `vcpkg.json` and `windows-msvc-vcpkg-headless` are validated for the headless Windows component matrix, but app targets still use vendored libraries and Android/Apple triplets are not proven | Dependency migration must stay incremental while SDK/patched/vendor dependencies remain in use | `$env:VCPKG_ROOT="C:\Program Files\Microsoft Visual Studio\2022\Community\VC\vcpkg"; cmake --preset windows-msvc-vcpkg-headless`; `ctest --preset desktop-fast-vcpkg --build-config Debug` | Component targets consume vcpkg packages where reliable and desktop app, Android, and Apple triplets are validated or explicitly documented as permanent vendor exceptions |
|
||||||
|
|||||||
@@ -426,7 +426,9 @@ contract for project files, ABR imports, PPBR imports, and malformed path
|
|||||||
rejection. `pano_cli simulate-app-session` exposes the pure `pp_app_core`
|
rejection. `pano_cli simulate-app-session` exposes the pure `pp_app_core`
|
||||||
session decisions used by project-open, app-close, save, save-as, and
|
session decisions used by project-open, app-close, save, save-as, and
|
||||||
save-version flows, plus the save-before-continue workflow gate used by
|
save-version flows, plus the save-before-continue workflow gate used by
|
||||||
new-document/open/browse dialogs.
|
new-document/open/browse dialogs. `pano_cli plan-document-file` exposes the
|
||||||
|
same app-core document-name validation, legacy `.ppi` path construction, and
|
||||||
|
overwrite prompt decision used by new-document and save-as dialogs.
|
||||||
`pano_cli parse-layout` exercises the XML layout path. Continue expanding
|
`pano_cli parse-layout` exercises the XML layout path. Continue expanding
|
||||||
document behavior toward legacy Canvas parity and then port OpenGL classes
|
document behavior toward legacy Canvas parity and then port OpenGL classes
|
||||||
behind the renderer boundary.
|
behind the renderer boundary.
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "foundation/result.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace pp::app {
|
namespace pp::app {
|
||||||
|
|
||||||
enum class ProjectOpenDecision {
|
enum class ProjectOpenDecision {
|
||||||
@@ -33,6 +39,17 @@ enum class DocumentWorkflowDecision {
|
|||||||
prompt_save_before_continue,
|
prompt_save_before_continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class DocumentFileWriteDecision {
|
||||||
|
save_now,
|
||||||
|
prompt_overwrite,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DocumentFileTarget {
|
||||||
|
std::string name;
|
||||||
|
std::string directory;
|
||||||
|
std::string path;
|
||||||
|
};
|
||||||
|
|
||||||
[[nodiscard]] constexpr ProjectOpenDecision plan_project_open(bool has_unsaved_changes) noexcept
|
[[nodiscard]] constexpr ProjectOpenDecision plan_project_open(bool has_unsaved_changes) noexcept
|
||||||
{
|
{
|
||||||
return has_unsaved_changes
|
return has_unsaved_changes
|
||||||
@@ -97,4 +114,32 @@ enum class DocumentWorkflowDecision {
|
|||||||
: DocumentWorkflowDecision::continue_now;
|
: DocumentWorkflowDecision::continue_now;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline pp::foundation::Result<DocumentFileTarget> make_document_file_target(
|
||||||
|
std::string_view work_directory,
|
||||||
|
std::string_view document_name)
|
||||||
|
{
|
||||||
|
if (document_name.empty()) {
|
||||||
|
return pp::foundation::Result<DocumentFileTarget>::failure(
|
||||||
|
pp::foundation::Status::invalid_argument("document name must not be empty"));
|
||||||
|
}
|
||||||
|
|
||||||
|
DocumentFileTarget target;
|
||||||
|
target.name = std::string(document_name);
|
||||||
|
target.directory = std::string(work_directory);
|
||||||
|
target.path.reserve(target.directory.size() + target.name.size() + 5);
|
||||||
|
target.path += target.directory;
|
||||||
|
target.path += "/";
|
||||||
|
target.path += target.name;
|
||||||
|
target.path += ".ppi";
|
||||||
|
return pp::foundation::Result<DocumentFileTarget>::success(std::move(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] constexpr DocumentFileWriteDecision plan_document_file_write(
|
||||||
|
bool target_exists) noexcept
|
||||||
|
{
|
||||||
|
return target_exists
|
||||||
|
? DocumentFileWriteDecision::prompt_overwrite
|
||||||
|
: DocumentFileWriteDecision::save_now;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,21 +157,20 @@ void App::dialog_newdoc()
|
|||||||
dialog->btn_ok->on_click = [this, dialog](Node*)
|
dialog->btn_ok->on_click = [this, dialog](Node*)
|
||||||
{
|
{
|
||||||
std::string name = dialog->input->m_text;
|
std::string name = dialog->input->m_text;
|
||||||
std::string path = work_path + "/" + name + ".ppi";
|
const auto target = pp::app::make_document_file_target(work_path, name);
|
||||||
|
if (!target)
|
||||||
if (name.empty())
|
|
||||||
{
|
{
|
||||||
message_box("Warning", "You need to specify a name to file.");
|
message_box("Warning", "You need to specify a name to file.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto action = [this, dialog, name, path] {
|
auto action = [this, dialog, target = target.value()] {
|
||||||
std::array<int, 6> resolutions{ 512, 1024, 1536, 2048, 4096, 8192 };
|
std::array<int, 6> resolutions{ 512, 1024, 1536, 2048, 4096, 8192 };
|
||||||
int res = resolutions[dialog->m_resolution->m_current_index];
|
int res = resolutions[dialog->m_resolution->m_current_index];
|
||||||
doc_name = name;
|
doc_name = target.name;
|
||||||
doc_path = path;
|
doc_path = target.path;
|
||||||
doc_filename = name + ".ppi";
|
doc_filename = target.name + ".ppi";
|
||||||
doc_dir = work_path;
|
doc_dir = target.directory;
|
||||||
|
|
||||||
layers->clear();
|
layers->clear();
|
||||||
canvas->m_canvas->m_layers.clear();
|
canvas->m_canvas->m_layers.clear();
|
||||||
@@ -189,7 +188,8 @@ void App::dialog_newdoc()
|
|||||||
App::I->hideKeyboard();
|
App::I->hideKeyboard();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Asset::exist(path))
|
const auto write_decision = pp::app::plan_document_file_write(Asset::exist(target.value().path));
|
||||||
|
if (write_decision == pp::app::DocumentFileWriteDecision::prompt_overwrite)
|
||||||
{
|
{
|
||||||
// ask confirm is file already exist
|
// ask confirm is file already exist
|
||||||
auto msgbox = new NodeMessageBox();
|
auto msgbox = new NodeMessageBox();
|
||||||
@@ -363,32 +363,32 @@ void App::dialog_save()
|
|||||||
dialog->btn_ok->on_click = [this, dialog](Node*)
|
dialog->btn_ok->on_click = [this, dialog](Node*)
|
||||||
{
|
{
|
||||||
std::string name = dialog->input->m_text;
|
std::string name = dialog->input->m_text;
|
||||||
std::string path = work_path + "/" + name + ".ppi";
|
const auto target = pp::app::make_document_file_target(work_path, name);
|
||||||
|
if (!target)
|
||||||
if (name.empty())
|
|
||||||
{
|
{
|
||||||
message_box("Warning", "You need to specify a name to file.");
|
message_box("Warning", "You need to specify a name to file.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto action = [this, dialog, name, path] {
|
auto action = [this, dialog, target = target.value()] {
|
||||||
canvas->m_canvas->project_save(path);
|
canvas->m_canvas->project_save(target.path);
|
||||||
doc_name = name;
|
doc_name = target.name;
|
||||||
doc_path = path;
|
doc_path = target.path;
|
||||||
doc_dir = work_path;
|
doc_dir = target.directory;
|
||||||
title_update();
|
title_update();
|
||||||
dialog->destroy();
|
dialog->destroy();
|
||||||
App::I->hideKeyboard();
|
App::I->hideKeyboard();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Asset::exist(path))
|
const auto write_decision = pp::app::plan_document_file_write(Asset::exist(target.value().path));
|
||||||
|
if (write_decision == pp::app::DocumentFileWriteDecision::prompt_overwrite)
|
||||||
{
|
{
|
||||||
// ask confirm is file already exist
|
// ask confirm is file already exist
|
||||||
auto msgbox = new NodeMessageBox();
|
auto msgbox = new NodeMessageBox();
|
||||||
msgbox->set_manager(&layout);
|
msgbox->set_manager(&layout);
|
||||||
msgbox->init();
|
msgbox->init();
|
||||||
msgbox->m_title->set_text("Warning");
|
msgbox->m_title->set_text("Warning");
|
||||||
msgbox->m_message->set_text(("Are you sure you want to overwrite " + name + "?").c_str());
|
msgbox->m_message->set_text(("Are you sure you want to overwrite " + target.value().name + "?").c_str());
|
||||||
msgbox->btn_ok->on_click = [this, msgbox, action](Node*) {
|
msgbox->btn_ok->on_click = [this, msgbox, action](Node*) {
|
||||||
action();
|
action();
|
||||||
msgbox->destroy();
|
msgbox->destroy();
|
||||||
|
|||||||
@@ -371,6 +371,18 @@ if(TARGET pano_cli)
|
|||||||
LABELS "app;integration;desktop-fast;fuzz"
|
LABELS "app;integration;desktop-fast;fuzz"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
add_test(NAME pano_cli_plan_document_file_save_now_smoke
|
||||||
|
COMMAND pano_cli plan-document-file --work-dir D:/Paint --name demo)
|
||||||
|
set_tests_properties(pano_cli_plan_document_file_save_now_smoke PROPERTIES
|
||||||
|
LABELS "app;integration;desktop-fast"
|
||||||
|
PASS_REGULAR_EXPRESSION "\"command\":\"plan-document-file\".*\"name\":\"demo\".*\"directory\":\"D:/Paint\".*\"path\":\"D:/Paint/demo.ppi\".*\"exists\":false.*\"decision\":\"save-now\"")
|
||||||
|
|
||||||
|
add_test(NAME pano_cli_plan_document_file_overwrite_smoke
|
||||||
|
COMMAND pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists)
|
||||||
|
set_tests_properties(pano_cli_plan_document_file_overwrite_smoke PROPERTIES
|
||||||
|
LABELS "app;integration;desktop-fast"
|
||||||
|
PASS_REGULAR_EXPRESSION "\"command\":\"plan-document-file\".*\"path\":\"D:/Paint/demo.ppi\".*\"exists\":true.*\"decision\":\"prompt-overwrite\"")
|
||||||
|
|
||||||
add_test(NAME pano_cli_simulate_app_session_clean_smoke
|
add_test(NAME pano_cli_simulate_app_session_clean_smoke
|
||||||
COMMAND pano_cli simulate-app-session)
|
COMMAND pano_cli simulate-app-session)
|
||||||
set_tests_properties(pano_cli_simulate_app_session_clean_smoke PROPERTIES
|
set_tests_properties(pano_cli_simulate_app_session_clean_smoke PROPERTIES
|
||||||
|
|||||||
@@ -115,6 +115,34 @@ void workflow_with_dirty_canvas_prompts_for_save(pp::tests::Harness& harness)
|
|||||||
== pp::app::DocumentWorkflowDecision::prompt_save_before_continue);
|
== pp::app::DocumentWorkflowDecision::prompt_save_before_continue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void document_file_target_rejects_empty_name(pp::tests::Harness& harness)
|
||||||
|
{
|
||||||
|
const auto target = pp::app::make_document_file_target("D:/Paint", "");
|
||||||
|
PP_EXPECT(harness, !target);
|
||||||
|
PP_EXPECT(harness, target.status().code == pp::foundation::StatusCode::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
void document_file_target_builds_legacy_ppi_path(pp::tests::Harness& harness)
|
||||||
|
{
|
||||||
|
const auto target = pp::app::make_document_file_target("D:/Paint", "demo");
|
||||||
|
PP_EXPECT(harness, target);
|
||||||
|
PP_EXPECT(harness, target.value().name == "demo");
|
||||||
|
PP_EXPECT(harness, target.value().directory == "D:/Paint");
|
||||||
|
PP_EXPECT(harness, target.value().path == "D:/Paint/demo.ppi");
|
||||||
|
}
|
||||||
|
|
||||||
|
void document_file_write_prompts_only_for_existing_targets(pp::tests::Harness& harness)
|
||||||
|
{
|
||||||
|
PP_EXPECT(
|
||||||
|
harness,
|
||||||
|
pp::app::plan_document_file_write(false)
|
||||||
|
== pp::app::DocumentFileWriteDecision::save_now);
|
||||||
|
PP_EXPECT(
|
||||||
|
harness,
|
||||||
|
pp::app::plan_document_file_write(true)
|
||||||
|
== pp::app::DocumentFileWriteDecision::prompt_overwrite);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
@@ -131,5 +159,8 @@ int main()
|
|||||||
harness.run("workflow without canvas is unavailable", workflow_without_canvas_is_unavailable);
|
harness.run("workflow without canvas is unavailable", workflow_without_canvas_is_unavailable);
|
||||||
harness.run("workflow with clean canvas continues now", workflow_with_clean_canvas_continues_now);
|
harness.run("workflow with clean canvas continues now", workflow_with_clean_canvas_continues_now);
|
||||||
harness.run("workflow with dirty canvas prompts for save", workflow_with_dirty_canvas_prompts_for_save);
|
harness.run("workflow with dirty canvas prompts for save", workflow_with_dirty_canvas_prompts_for_save);
|
||||||
|
harness.run("document file target rejects empty name", document_file_target_rejects_empty_name);
|
||||||
|
harness.run("document file target builds legacy ppi path", document_file_target_builds_legacy_ppi_path);
|
||||||
|
harness.run("document file write prompts only for existing targets", document_file_write_prompts_only_for_existing_targets);
|
||||||
return harness.finish();
|
return harness.finish();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,12 @@ struct ClassifyOpenArgs {
|
|||||||
std::string path;
|
std::string path;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct PlanDocumentFileArgs {
|
||||||
|
std::string work_directory;
|
||||||
|
std::string name;
|
||||||
|
bool target_exists = false;
|
||||||
|
};
|
||||||
|
|
||||||
struct SimulateAppSessionArgs {
|
struct SimulateAppSessionArgs {
|
||||||
bool has_canvas = true;
|
bool has_canvas = true;
|
||||||
bool new_document = false;
|
bool new_document = false;
|
||||||
@@ -313,6 +319,18 @@ const char* document_workflow_decision_name(pp::app::DocumentWorkflowDecision de
|
|||||||
return "unavailable";
|
return "unavailable";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* document_file_write_decision_name(pp::app::DocumentFileWriteDecision decision) noexcept
|
||||||
|
{
|
||||||
|
switch (decision) {
|
||||||
|
case pp::app::DocumentFileWriteDecision::save_now:
|
||||||
|
return "save-now";
|
||||||
|
case pp::app::DocumentFileWriteDecision::prompt_overwrite:
|
||||||
|
return "prompt-overwrite";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "save-now";
|
||||||
|
}
|
||||||
|
|
||||||
pp::foundation::Result<float> parse_float_arg(std::string_view text)
|
pp::foundation::Result<float> parse_float_arg(std::string_view text)
|
||||||
{
|
{
|
||||||
float value = 0.0F;
|
float value = 0.0F;
|
||||||
@@ -343,6 +361,7 @@ void print_help()
|
|||||||
<< " import-image --path FILE [--document-width N] [--document-height N] [--face N] [--x N] [--y N]\n"
|
<< " import-image --path FILE [--document-width N] [--document-height N] [--face N] [--x N] [--y N]\n"
|
||||||
<< " inspect-project --path FILE\n"
|
<< " inspect-project --path FILE\n"
|
||||||
<< " classify-open --path FILE\n"
|
<< " classify-open --path FILE\n"
|
||||||
|
<< " plan-document-file --work-dir DIR --name NAME [--target-exists]\n"
|
||||||
<< " load-project --path FILE\n"
|
<< " load-project --path FILE\n"
|
||||||
<< " parse-layout --path FILE\n"
|
<< " parse-layout --path FILE\n"
|
||||||
<< " record-render [--width N] [--height N] [--exercise-clear]\n"
|
<< " record-render [--width N] [--height N] [--exercise-clear]\n"
|
||||||
@@ -1184,6 +1203,64 @@ int classify_open(int argc, char** argv)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pp::foundation::Status parse_plan_document_file_args(
|
||||||
|
int argc,
|
||||||
|
char** argv,
|
||||||
|
PlanDocumentFileArgs& args)
|
||||||
|
{
|
||||||
|
for (int i = 2; i < argc; ++i) {
|
||||||
|
const std::string_view key(argv[i]);
|
||||||
|
if (key == "--work-dir") {
|
||||||
|
if (i + 1 >= argc) {
|
||||||
|
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||||
|
}
|
||||||
|
args.work_directory = argv[++i];
|
||||||
|
} else if (key == "--name") {
|
||||||
|
if (i + 1 >= argc) {
|
||||||
|
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||||
|
}
|
||||||
|
args.name = argv[++i];
|
||||||
|
} else if (key == "--target-exists") {
|
||||||
|
args.target_exists = true;
|
||||||
|
} else {
|
||||||
|
return pp::foundation::Status::invalid_argument("unknown option");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.work_directory.empty()) {
|
||||||
|
return pp::foundation::Status::invalid_argument("work directory must not be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
return pp::foundation::Status::success();
|
||||||
|
}
|
||||||
|
|
||||||
|
int plan_document_file(int argc, char** argv)
|
||||||
|
{
|
||||||
|
PlanDocumentFileArgs args;
|
||||||
|
const auto status = parse_plan_document_file_args(argc, argv, args);
|
||||||
|
if (!status.ok()) {
|
||||||
|
print_error("plan-document-file", status.message);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto target = pp::app::make_document_file_target(args.work_directory, args.name);
|
||||||
|
if (!target) {
|
||||||
|
print_error("plan-document-file", target.status().message);
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto write_decision = pp::app::plan_document_file_write(args.target_exists);
|
||||||
|
std::cout << "{\"ok\":true,\"command\":\"plan-document-file\""
|
||||||
|
<< ",\"target\":{\"name\":\"" << json_escape(target.value().name)
|
||||||
|
<< "\",\"directory\":\"" << json_escape(target.value().directory)
|
||||||
|
<< "\",\"path\":\"" << json_escape(target.value().path)
|
||||||
|
<< "\",\"exists\":" << json_bool(args.target_exists)
|
||||||
|
<< "},\"decision\":\""
|
||||||
|
<< document_file_write_decision_name(write_decision)
|
||||||
|
<< "\"}\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
pp::foundation::Status parse_simulate_app_session_args(
|
pp::foundation::Status parse_simulate_app_session_args(
|
||||||
int argc,
|
int argc,
|
||||||
char** argv,
|
char** argv,
|
||||||
@@ -3162,6 +3239,10 @@ int main(int argc, char** argv)
|
|||||||
return classify_open(argc, argv);
|
return classify_open(argc, argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (command == "plan-document-file") {
|
||||||
|
return plan_document_file(argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
if (command == "load-project") {
|
if (command == "load-project") {
|
||||||
return load_project(argc, argv);
|
return load_project(argc, argv);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user