Step 644: self-hosting project config
This commit is contained in:
@@ -4675,4 +4675,13 @@ target_link_libraries(step643_test PRIVATE
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
add_executable(step644_test tests/step644_test.cpp)
|
||||
target_include_directories(step644_test PRIVATE src)
|
||||
target_link_libraries(step644_test PRIVATE
|
||||
nlohmann_json::nlohmann_json
|
||||
unofficial::tree-sitter::tree-sitter
|
||||
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)
|
||||
|
||||
53
editor/src/SelfHostingProjectConfig.h
Normal file
53
editor/src/SelfHostingProjectConfig.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
// Step 644: .whetstone.json for self-hosting editor project
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
struct SelfHostingProjectConfig {
|
||||
std::string projectName;
|
||||
std::string workspaceRoot;
|
||||
std::vector<std::string> sourceRoots;
|
||||
std::vector<std::string> testTargets;
|
||||
bool selfHosting = false;
|
||||
};
|
||||
|
||||
class SelfHostingProjectConfigModel {
|
||||
public:
|
||||
static json toJson(const SelfHostingProjectConfig& cfg) {
|
||||
return {
|
||||
{"projectName", cfg.projectName},
|
||||
{"workspaceRoot", cfg.workspaceRoot},
|
||||
{"sourceRoots", cfg.sourceRoots},
|
||||
{"testTargets", cfg.testTargets},
|
||||
{"selfHosting", cfg.selfHosting}
|
||||
};
|
||||
}
|
||||
|
||||
static bool fromJson(const json& j, SelfHostingProjectConfig* out, std::string* error) {
|
||||
if (!out || !error) return false;
|
||||
error->clear();
|
||||
if (!j.is_object()) {
|
||||
*error = "config_invalid";
|
||||
return false;
|
||||
}
|
||||
out->projectName = j.value("projectName", "");
|
||||
out->workspaceRoot = j.value("workspaceRoot", "");
|
||||
out->sourceRoots = j.value("sourceRoots", std::vector<std::string>{});
|
||||
out->testTargets = j.value("testTargets", std::vector<std::string>{});
|
||||
out->selfHosting = j.value("selfHosting", false);
|
||||
if (out->projectName.empty()) {
|
||||
*error = "project_name_missing";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool canSelfHost(const SelfHostingProjectConfig& cfg) {
|
||||
return cfg.selfHosting && !cfg.workspaceRoot.empty() && !cfg.sourceRoots.empty();
|
||||
}
|
||||
};
|
||||
135
editor/tests/step644_test.cpp
Normal file
135
editor/tests/step644_test.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
// Step 644: .whetstone.json for self-hosting editor project (12 tests)
|
||||
|
||||
#include "SelfHostingProjectConfig.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
static int passed = 0, failed = 0;
|
||||
#define TEST(name) { std::cout << " " << #name << "... "; }
|
||||
#define PASS() { std::cout << "PASS\n"; ++passed; }
|
||||
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
|
||||
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; }
|
||||
|
||||
static SelfHostingProjectConfig sample() {
|
||||
return {
|
||||
"whetstone_DSL",
|
||||
"/workspace/whetstone_DSL",
|
||||
{"editor/src", "editor/tests"},
|
||||
{"step644_test", "step645_test"},
|
||||
true
|
||||
};
|
||||
}
|
||||
|
||||
void test_to_json_emits_project_name() {
|
||||
TEST(to_json_emits_project_name);
|
||||
auto j = SelfHostingProjectConfigModel::toJson(sample());
|
||||
CHECK(j.value("projectName", "") == "whetstone_DSL", "project name mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_to_json_emits_workspace_root() {
|
||||
TEST(to_json_emits_workspace_root);
|
||||
auto j = SelfHostingProjectConfigModel::toJson(sample());
|
||||
CHECK(j.value("workspaceRoot", "") == "/workspace/whetstone_DSL", "workspace mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_to_json_emits_source_roots() {
|
||||
TEST(to_json_emits_source_roots);
|
||||
auto j = SelfHostingProjectConfigModel::toJson(sample());
|
||||
CHECK(j["sourceRoots"].size() == 2, "source root count mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_to_json_emits_test_targets() {
|
||||
TEST(to_json_emits_test_targets);
|
||||
auto j = SelfHostingProjectConfigModel::toJson(sample());
|
||||
CHECK(j["testTargets"].size() == 2, "test target count mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_from_json_rejects_non_object() {
|
||||
TEST(from_json_rejects_non_object);
|
||||
SelfHostingProjectConfig out;
|
||||
std::string err;
|
||||
CHECK(!SelfHostingProjectConfigModel::fromJson(json::array(), &out, &err), "array should fail");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_from_json_rejects_missing_project_name() {
|
||||
TEST(from_json_rejects_missing_project_name);
|
||||
SelfHostingProjectConfig out;
|
||||
std::string err;
|
||||
CHECK(!SelfHostingProjectConfigModel::fromJson({{"workspaceRoot", "/ws"}}, &out, &err), "missing name should fail");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_from_json_parses_valid_config() {
|
||||
TEST(from_json_parses_valid_config);
|
||||
SelfHostingProjectConfig out;
|
||||
std::string err;
|
||||
CHECK(SelfHostingProjectConfigModel::fromJson(SelfHostingProjectConfigModel::toJson(sample()), &out, &err),
|
||||
"valid config should parse");
|
||||
CHECK(out.projectName == "whetstone_DSL", "parsed name mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_from_json_parses_self_hosting_flag() {
|
||||
TEST(from_json_parses_self_hosting_flag);
|
||||
SelfHostingProjectConfig out;
|
||||
std::string err;
|
||||
CHECK(SelfHostingProjectConfigModel::fromJson(SelfHostingProjectConfigModel::toJson(sample()), &out, &err),
|
||||
"valid config should parse");
|
||||
CHECK(out.selfHosting, "selfHosting flag mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_can_self_host_true_for_valid_self_hosting_config() {
|
||||
TEST(can_self_host_true_for_valid_self_hosting_config);
|
||||
CHECK(SelfHostingProjectConfigModel::canSelfHost(sample()), "should self-host");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_can_self_host_false_when_flag_disabled() {
|
||||
TEST(can_self_host_false_when_flag_disabled);
|
||||
auto cfg = sample();
|
||||
cfg.selfHosting = false;
|
||||
CHECK(!SelfHostingProjectConfigModel::canSelfHost(cfg), "disabled flag should fail");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_can_self_host_false_when_workspace_missing() {
|
||||
TEST(can_self_host_false_when_workspace_missing);
|
||||
auto cfg = sample();
|
||||
cfg.workspaceRoot.clear();
|
||||
CHECK(!SelfHostingProjectConfigModel::canSelfHost(cfg), "missing workspace should fail");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_can_self_host_false_when_source_roots_empty() {
|
||||
TEST(can_self_host_false_when_source_roots_empty);
|
||||
auto cfg = sample();
|
||||
cfg.sourceRoots.clear();
|
||||
CHECK(!SelfHostingProjectConfigModel::canSelfHost(cfg), "missing sources should fail");
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 644: .whetstone.json self-hosting config\n";
|
||||
|
||||
test_to_json_emits_project_name();
|
||||
test_to_json_emits_workspace_root();
|
||||
test_to_json_emits_source_roots();
|
||||
test_to_json_emits_test_targets();
|
||||
test_from_json_rejects_non_object();
|
||||
test_from_json_rejects_missing_project_name();
|
||||
test_from_json_parses_valid_config();
|
||||
test_from_json_parses_self_hosting_flag();
|
||||
test_can_self_host_true_for_valid_self_hosting_config();
|
||||
test_can_self_host_false_when_flag_disabled();
|
||||
test_can_self_host_false_when_workspace_missing();
|
||||
test_can_self_host_false_when_source_roots_empty();
|
||||
|
||||
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
31
progress.md
31
progress.md
@@ -13172,3 +13172,34 @@ with size/function constraints and naming conventions.
|
||||
- `editor/src/Sprint38IntegrationSummary.h` (`60` <= `600`)
|
||||
- Sprint 38 test files remain within test-file size guidance.
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
### Step 644: `.whetstone.json` for the editor project itself
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Adds a self-hosting project-config model for Whetstone’s own workspace using
|
||||
`.whetstone.json` semantics, including JSON round-trip helpers and
|
||||
self-hosting eligibility checks.
|
||||
|
||||
**Files added:**
|
||||
- `editor/src/SelfHostingProjectConfig.h` - self-hosting config model:
|
||||
- JSON encode/decode for project/workspace/source/test metadata
|
||||
- validation with structured error signaling
|
||||
- `canSelfHost(...)` readiness helper
|
||||
- `editor/tests/step644_test.cpp` - 12 tests covering:
|
||||
- JSON emission and parse behavior
|
||||
- invalid-config edge rejection
|
||||
- self-hosting readiness decision paths
|
||||
|
||||
**Files modified:**
|
||||
- `editor/CMakeLists.txt` - `step644_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `cmake -S editor -B editor/build-native` - PASS
|
||||
- `cmake --build editor/build-native --target step644_test step643_test` - PASS
|
||||
- `./editor/build-native/step644_test` - PASS (12/12)
|
||||
- `./editor/build-native/step643_test` - PASS (8/8) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/SelfHostingProjectConfig.h` (`53` <= `600`)
|
||||
- `editor/tests/step644_test.cpp` within test-file size guidance (`135` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
Reference in New Issue
Block a user