Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/website-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jobs:
-DBLAZE_ALTERSCHEMA:BOOL=OFF
-DBLAZE_CODEGEN:BOOL=OFF
-DBLAZE_DOCUMENTATION:BOOL=OFF
-DBLAZE_EDITOR:BOOL=OFF
-DBLAZE_TESTS:BOOL=OFF
-DBLAZE_DOCS:BOOL=ON
- run: cmake --build ./build --config Release --target doxygen
1 change: 1 addition & 0 deletions .github/workflows/website-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jobs:
-DBLAZE_ALTERSCHEMA:BOOL=OFF
-DBLAZE_CODEGEN:BOOL=OFF
-DBLAZE_DOCUMENTATION:BOOL=OFF
-DBLAZE_EDITOR:BOOL=OFF
-DBLAZE_TESTS:BOOL=OFF
-DBLAZE_DOCS:BOOL=ON
- run: cmake --build ./build --config Release --target doxygen
Expand Down
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ option(BLAZE_CONFIGURATION "Build the Blaze configuration file library" ON)
option(BLAZE_ALTERSCHEMA "Build the Blaze alterschema rule library" ON)
option(BLAZE_CODEGEN "Build the Blaze codegen library" ON)
option(BLAZE_DOCUMENTATION "Build the Blaze documentation generator library" ON)
option(BLAZE_EDITOR "Build the Blaze editor schema compatibility library" ON)
option(BLAZE_TESTS "Build the Blaze tests" OFF)
option(BLAZE_BENCHMARK "Build the Blaze benchmarks" OFF)
option(BLAZE_CONTRIB "Build the Blaze contrib programs" OFF)
Expand Down Expand Up @@ -77,6 +78,10 @@ if(BLAZE_DOCUMENTATION)
add_subdirectory(src/documentation)
endif()

if(BLAZE_EDITOR)
add_subdirectory(src/editor)
endif()

if(BLAZE_CONTRIB)
add_subdirectory(contrib)
endif()
Expand Down Expand Up @@ -155,6 +160,10 @@ if(BLAZE_TESTS)
add_subdirectory(test/documentation)
endif()

if(BLAZE_EDITOR)
add_subdirectory(test/editor)
endif()

if(PROJECT_IS_TOP_LEVEL)
# Otherwise we need the child project to link
# against the sanitizers too.
Expand Down
2 changes: 1 addition & 1 deletion DEPENDENCIES
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
vendorpull https://github.com/sourcemeta/vendorpull 1dcbac42809cf87cb5b045106b863e17ad84ba02
core https://github.com/sourcemeta/core 3ea0eb1b54627ba11579ac73e3a44fae4fd191a7
core https://github.com/sourcemeta/core 7b51d3cbb4e1ea56ddc910c0d315880113afd1cc
jsonschema-test-suite https://github.com/json-schema-org/JSON-Schema-Test-Suite c7257e92580678a086f0b9243a1903ed88bd27f7
3 changes: 3 additions & 0 deletions config.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ if(NOT BLAZE_COMPONENTS)
list(APPEND BLAZE_COMPONENTS alterschema)
list(APPEND BLAZE_COMPONENTS codegen)
list(APPEND BLAZE_COMPONENTS documentation)
list(APPEND BLAZE_COMPONENTS editor)
Comment thread
jviotti marked this conversation as resolved.
endif()

include(CMakeFindDependencyMacro)
Expand Down Expand Up @@ -45,6 +46,8 @@ foreach(component ${BLAZE_COMPONENTS})
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_codegen.cmake")
elseif(component STREQUAL "documentation")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_documentation.cmake")
elseif(component STREQUAL "editor")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_editor.cmake")
else()
message(FATAL_ERROR "Unknown Blaze component: ${component}")
endif()
Expand Down
1 change: 1 addition & 0 deletions doxygen/index.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ CMake
| `BLAZE_TEST` | Boolean | `ON` | Build the Blaze test runner library |
| `BLAZE_ALTERSCHEMA` | Boolean | `ON` | Build the Blaze alterschema rule library|
| `BLAZE_DOCUMENTATION` | Boolean | `ON` | Build the Blaze documentation library |
| `BLAZE_EDITOR` | Boolean | `ON` | Build the Blaze editor library |
| `BLAZE_TESTS` | Boolean | `OFF` | Build the Blaze tests |
| `BLAZE_BENCHMARK` | Boolean | `OFF` | Build the Blaze benchmarks |
| `BLAZE_CONTRIB` | Boolean | `OFF` | Build the Blaze contrib programs |
Expand Down
12 changes: 12 additions & 0 deletions src/editor/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
sourcemeta_library(NAMESPACE sourcemeta PROJECT blaze NAME editor
FOLDER "Blaze/Editor"
SOURCES editor.cc)

if(BLAZE_INSTALL)
sourcemeta_library_install(NAMESPACE sourcemeta PROJECT blaze NAME editor)
endif()

target_link_libraries(sourcemeta_blaze_editor PUBLIC
sourcemeta::core::json)
target_link_libraries(sourcemeta_blaze_editor PUBLIC
sourcemeta::core::jsonschema)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include <sourcemeta/core/editorschema.h>
#include <sourcemeta/blaze/editor.h>

#include <cassert> // assert
#include <map> // std::map
Expand Down Expand Up @@ -53,42 +53,45 @@ auto top_dynamic_anchor_location(

} // namespace

namespace sourcemeta::core {
namespace sourcemeta::blaze {

// Collected information about a reference to modify
struct ReferenceChange {
Pointer pointer;
JSON::String new_value;
JSON::String keyword;
sourcemeta::core::Pointer pointer;
sourcemeta::core::JSON::String new_value;
sourcemeta::core::JSON::String keyword;
bool rename_to_ref;
};

// Collected information about a subschema to modify
struct SubschemaChange {
Pointer pointer;
SchemaBaseDialect base_dialect;
sourcemeta::core::Pointer pointer;
sourcemeta::core::SchemaBaseDialect base_dialect;
bool add_schema_declaration;
bool erase_2020_12_keywords;
bool erase_2019_09_keywords;
};

auto for_editor(JSON &schema, const SchemaWalker &walker,
const SchemaResolver &resolver,
auto for_editor(sourcemeta::core::JSON &schema,
const sourcemeta::core::SchemaWalker &walker,
const sourcemeta::core::SchemaResolver &resolver,
std::string_view default_dialect) -> void {
// (1) Frame the schema and collect all changes we need to make
std::vector<ReferenceChange> reference_changes;
std::vector<SubschemaChange> subschema_changes;

{
SchemaFrame frame{SchemaFrame::Mode::References};
sourcemeta::core::SchemaFrame frame{
sourcemeta::core::SchemaFrame::Mode::References};
frame.analyse(schema, walker, resolver, default_dialect);

// Otherwise the input is not bundled
assert(frame.standalone());
Comment thread
jviotti marked this conversation as resolved.

// Note that `std::unordered_map` is slower here due to high collision rates
// from the simple pointer hashes
std::map<WeakPointer, std::reference_wrapper<const JSON::String>>
std::map<sourcemeta::core::WeakPointer,
std::reference_wrapper<const sourcemeta::core::JSON::String>>
pointer_to_uri;
for (const auto &entry : frame.locations()) {
pointer_to_uri.emplace(entry.second.pointer,
Expand All @@ -101,7 +104,7 @@ auto for_editor(JSON &schema, const SchemaWalker &walker,
assert(key.second.back().is_property());
const auto &keyword{key.second.back().to_property()};

if (key.first == SchemaReferenceType::Dynamic) {
if (key.first == sourcemeta::core::SchemaReferenceType::Dynamic) {
if (reference.fragment.has_value()) {
const auto destination{top_dynamic_anchor_location(
frame, key.second, reference.fragment.value(),
Expand All @@ -111,11 +114,12 @@ auto for_editor(JSON &schema, const SchemaWalker &walker,
}

reference_changes.push_back(
{to_pointer(key.second),
to_uri(destination.value().get()).recompose(), keyword, true});
{sourcemeta::core::to_pointer(key.second),
sourcemeta::core::to_uri(destination.value().get()).recompose(),
keyword, true});
} else {
reference_changes.push_back(
{to_pointer(key.second), "", keyword, true});
{sourcemeta::core::to_pointer(key.second), "", keyword, true});
}
} else {
if (keyword == "$schema") {
Expand All @@ -125,8 +129,9 @@ auto for_editor(JSON &schema, const SchemaWalker &walker,
const auto origin{frame.traverse(uri_it->second.get())};
assert(origin.has_value());
reference_changes.push_back(
{to_pointer(key.second),
JSON::String{to_string(origin.value().get().base_dialect)},
{sourcemeta::core::to_pointer(key.second),
sourcemeta::core::JSON::String{sourcemeta::core::to_string(
origin.value().get().base_dialect)},
keyword, false});
continue;
}
Expand All @@ -136,24 +141,28 @@ auto for_editor(JSON &schema, const SchemaWalker &walker,
const bool should_rename =
keyword == "$dynamicRef" || keyword == "$recursiveRef";
reference_changes.push_back(
{to_pointer(key.second),
to_uri(result.value().get().pointer).recompose(), keyword,
should_rename});
{sourcemeta::core::to_pointer(key.second),
sourcemeta::core::to_uri(result.value().get().pointer)
.recompose(),
keyword, should_rename});
} else {
reference_changes.push_back(
{to_pointer(key.second), reference.destination, keyword, false});
reference_changes.push_back({sourcemeta::core::to_pointer(key.second),
reference.destination, keyword, false});
}
}
}

// Collect subschema changes
for (const auto &entry : frame.locations()) {
if (entry.second.type != SchemaFrame::LocationType::Resource &&
entry.second.type != SchemaFrame::LocationType::Subschema) {
if (entry.second.type !=
sourcemeta::core::SchemaFrame::LocationType::Resource &&
entry.second.type !=
sourcemeta::core::SchemaFrame::LocationType::Subschema) {
continue;
}

const auto &subschema{get(schema, entry.second.pointer)};
const auto &subschema{
sourcemeta::core::get(schema, entry.second.pointer)};
if (subschema.is_boolean()) {
continue;
}
Expand All @@ -163,34 +172,38 @@ auto for_editor(JSON &schema, const SchemaWalker &walker,
const auto vocabularies{frame.vocabularies(entry.second, resolver)};

subschema_changes.push_back(
{to_pointer(entry.second.pointer), entry.second.base_dialect,
add_schema,
vocabularies.contains(Vocabularies::Known::JSON_Schema_2020_12_Core),
{sourcemeta::core::to_pointer(entry.second.pointer),
entry.second.base_dialect, add_schema,
vocabularies.contains(
Vocabularies::Known::JSON_Schema_2019_09_Core)});
sourcemeta::core::Vocabularies::Known::JSON_Schema_2020_12_Core),
vocabularies.contains(sourcemeta::core::Vocabularies::Known::
JSON_Schema_2019_09_Core)});
}
}

// (2) Apply reference changes
for (const auto &change : reference_changes) {
if (!change.new_value.empty()) {
set(schema, change.pointer, JSON{change.new_value});
sourcemeta::core::set(schema, change.pointer,
sourcemeta::core::JSON{change.new_value});
}
if (change.rename_to_ref) {
get(schema, change.pointer.initial()).rename(change.keyword, "$ref");
sourcemeta::core::get(schema, change.pointer.initial())
.rename(change.keyword, "$ref");
}
}

// (3) Apply subschema changes
for (const auto &change : subschema_changes) {
auto &subschema{get(schema, change.pointer)};
auto &subschema{sourcemeta::core::get(schema, change.pointer)};

if (change.add_schema_declaration) {
subschema.assign_assume_new(
"$schema", JSON{JSON::String{to_string(change.base_dialect)}});
"$schema", sourcemeta::core::JSON{sourcemeta::core::JSON::String{
sourcemeta::core::to_string(change.base_dialect)}});
}

anonymize(subschema, change.base_dialect);
sourcemeta::core::anonymize(subschema, change.base_dialect);

if (change.erase_2020_12_keywords) {
subschema.erase_keys({"$vocabulary", "$anchor", "$dynamicAnchor"});
Expand All @@ -200,4 +213,4 @@ auto for_editor(JSON &schema, const SchemaWalker &walker,
}
}

} // namespace sourcemeta::core
} // namespace sourcemeta::blaze
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
#ifndef SOURCEMETA_CORE_EXTENSION_EDITORSCHEMA_H_
#define SOURCEMETA_CORE_EXTENSION_EDITORSCHEMA_H_
#ifndef SOURCEMETA_BLAZE_EDITOR_H_
#define SOURCEMETA_BLAZE_EDITOR_H_

/// @defgroup editorschema EditorSchema
/// @defgroup editor Editor
/// @brief A JSON Schema compatibility layer for code editors
///
/// This functionality is included as follows:
///
/// ```cpp
/// #include <sourcemeta/core/editorschema.h>
/// #include <sourcemeta/blaze/editor.h>
/// ```

#ifndef SOURCEMETA_CORE_EDITORSCHEMA_EXPORT
#include <sourcemeta/core/editorschema_export.h>
#ifndef SOURCEMETA_BLAZE_EDITOR_EXPORT
#include <sourcemeta/blaze/editor_export.h>
#endif

#include <sourcemeta/core/json.h>
#include <sourcemeta/core/jsonschema.h>

#include <string_view> // std::string_view

namespace sourcemeta::core {
namespace sourcemeta::blaze {

/// @ingroup editorschema
/// @ingroup editor
///
/// This function aims to transform the schema (in potentially non-strictly
/// compliant manners) to workaround JSON Schema limitations of popular code
Expand All @@ -36,6 +36,7 @@ namespace sourcemeta::core {
/// ```cpp
/// #include <sourcemeta/core/json.h>
/// #include <sourcemeta/core/jsonschema.h>
/// #include <sourcemeta/blaze/editor.h>
///
/// auto schema = sourcemeta::core::parse_json(R"JSON({
/// "$id": "https://www.example.com/schema",
Expand All @@ -46,15 +47,16 @@ namespace sourcemeta::core {
/// sourcemeta::core::bundle(schema,
/// sourcemeta::core::schema_walker,
/// sourcemeta::core::schema_resolver);
/// sourcemeta::core::for_editor(schema,
/// sourcemeta::blaze::for_editor(schema,
/// sourcemeta::core::schema_walker,
/// sourcemeta::core::schema_resolver);
/// ```
SOURCEMETA_CORE_EDITORSCHEMA_EXPORT
auto for_editor(JSON &schema, const SchemaWalker &walker,
const SchemaResolver &resolver,
SOURCEMETA_BLAZE_EDITOR_EXPORT
auto for_editor(sourcemeta::core::JSON &schema,
const sourcemeta::core::SchemaWalker &walker,
const sourcemeta::core::SchemaResolver &resolver,
std::string_view default_dialect = "") -> void;

} // namespace sourcemeta::core
} // namespace sourcemeta::blaze

#endif
10 changes: 10 additions & 0 deletions test/editor/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
sourcemeta_googletest(NAMESPACE sourcemeta PROJECT blaze NAME editor
FOLDER "Blaze/Editor"
SOURCES editor_test.cc)

target_link_libraries(sourcemeta_blaze_editor_unit
PRIVATE sourcemeta::core::json)
target_link_libraries(sourcemeta_blaze_editor_unit
PRIVATE sourcemeta::core::jsonschema)
target_link_libraries(sourcemeta_blaze_editor_unit
PRIVATE sourcemeta::blaze::editor)
Loading
Loading