Skip to content

Commit 95af392

Browse files
committed
[WIP] Kickstart a documentation module
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent fcdf00a commit 95af392

12 files changed

Lines changed: 618 additions & 0 deletions

File tree

.github/workflows/website-build.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ jobs:
2222
-DBLAZE_TEST:BOOL=OFF
2323
-DBLAZE_CONFIGURATION:BOOL=OFF
2424
-DBLAZE_ALTERSCHEMA:BOOL=OFF
25+
-DBLAZE_DOCUMENTATION:BOOL=OFF
2526
-DBLAZE_TESTS:BOOL=OFF
2627
-DBLAZE_DOCS:BOOL=ON
2728
- run: cmake --build ./build --config Release --target doxygen

.github/workflows/website-deploy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ jobs:
3333
-DBLAZE_TEST:BOOL=OFF
3434
-DBLAZE_CONFIGURATION:BOOL=OFF
3535
-DBLAZE_ALTERSCHEMA:BOOL=OFF
36+
-DBLAZE_DOCUMENTATION:BOOL=OFF
3637
-DBLAZE_TESTS:BOOL=OFF
3738
-DBLAZE_DOCS:BOOL=ON
3839
- run: cmake --build ./build --config Release --target doxygen

CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ option(BLAZE_OUTPUT "Build the Blaze output formats library" ON)
1111
option(BLAZE_TEST "Build the Blaze test runner library" ON)
1212
option(BLAZE_CONFIGURATION "Build the Blaze configuration file library" ON)
1313
option(BLAZE_ALTERSCHEMA "Build the Blaze alterschema rule library" ON)
14+
option(BLAZE_DOCUMENTATION "Build the Blaze documentation generator library" ON)
1415
option(BLAZE_TESTS "Build the Blaze tests" OFF)
1516
option(BLAZE_BENCHMARK "Build the Blaze benchmarks" OFF)
1617
option(BLAZE_CONTRIB "Build the Blaze contrib programs" OFF)
@@ -67,6 +68,10 @@ if(BLAZE_ALTERSCHEMA)
6768
add_subdirectory(src/alterschema)
6869
endif()
6970

71+
if(BLAZE_DOCUMENTATION)
72+
add_subdirectory(src/documentation)
73+
endif()
74+
7075
if(BLAZE_CONTRIB)
7176
add_subdirectory(contrib)
7277
endif()
@@ -118,6 +123,10 @@ if(BLAZE_TESTS)
118123
add_subdirectory(test/alterschema)
119124
endif()
120125

126+
if(BLAZE_DOCUMENTATION)
127+
add_subdirectory(test/documentation)
128+
endif()
129+
121130
if(PROJECT_IS_TOP_LEVEL)
122131
# Otherwise we need the child project to link
123132
# against the sanitizers too.

config.cmake.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ if(NOT BLAZE_COMPONENTS)
1010
list(APPEND BLAZE_COMPONENTS test)
1111
list(APPEND BLAZE_COMPONENTS configuration)
1212
list(APPEND BLAZE_COMPONENTS alterschema)
13+
list(APPEND BLAZE_COMPONENTS documentation)
1314
endif()
1415

1516
include(CMakeFindDependencyMacro)
@@ -35,6 +36,8 @@ foreach(component ${BLAZE_COMPONENTS})
3536
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_compiler.cmake")
3637
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_output.cmake")
3738
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_alterschema.cmake")
39+
elseif(component STREQUAL "documentation")
40+
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_documentation.cmake")
3841
else()
3942
message(FATAL_ERROR "Unknown Blaze component: ${component}")
4043
endif()

doxygen/index.markdown

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ CMake
7171
| `BLAZE_EVALUATOR` | Boolean | `ON` | Build the Blaze evaluator library |
7272
| `BLAZE_TEST` | Boolean | `ON` | Build the Blaze test runner library |
7373
| `BLAZE_ALTERSCHEMA` | Boolean | `ON` | Build the Blaze alterschema rule library|
74+
| `BLAZE_DOCUMENTATION` | Boolean | `ON` | Build the Blaze documentation library |
7475
| `BLAZE_TESTS` | Boolean | `OFF` | Build the Blaze tests |
7576
| `BLAZE_BENCHMARK` | Boolean | `OFF` | Build the Blaze benchmarks |
7677
| `BLAZE_CONTRIB` | Boolean | `OFF` | Build the Blaze contrib programs |

src/documentation/CMakeLists.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
sourcemeta_library(NAMESPACE sourcemeta PROJECT blaze NAME documentation
2+
FOLDER "Blaze/Documentation"
3+
SOURCES documentation.cc)
4+
5+
if(BLAZE_INSTALL)
6+
sourcemeta_library_install(NAMESPACE sourcemeta PROJECT blaze NAME documentation)
7+
endif()
8+
9+
target_link_libraries(sourcemeta_blaze_documentation PUBLIC
10+
sourcemeta::core::json)
11+
target_link_libraries(sourcemeta_blaze_documentation PUBLIC
12+
sourcemeta::core::jsonschema)
13+
target_link_libraries(sourcemeta_blaze_documentation PRIVATE
14+
sourcemeta::blaze::alterschema)

src/documentation/documentation.cc

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#include <sourcemeta/blaze/documentation.h>
2+
3+
#include <sourcemeta/blaze/alterschema.h>
4+
5+
#include <sourcemeta/core/jsonschema.h>
6+
7+
#include <cassert> // assert
8+
#include <utility> // std::move
9+
10+
namespace sourcemeta::blaze {
11+
12+
namespace {
13+
14+
auto type_expression_of(const sourcemeta::core::JSON &schema)
15+
-> Documentation::Type::Expression {
16+
Documentation::Type::Expression expression;
17+
18+
if (!schema.is_object()) {
19+
return expression;
20+
}
21+
22+
if (schema.defines("enum") && schema.at("enum").is_array()) {
23+
Documentation::Type::Expression::Enumeration enumeration;
24+
for (const auto &value : schema.at("enum").as_array()) {
25+
enumeration.values.push_back(value);
26+
}
27+
expression.value = std::move(enumeration);
28+
return expression;
29+
}
30+
31+
if (schema.defines("type") && schema.at("type").is_string()) {
32+
const auto &type{schema.at("type").to_string()};
33+
if (type == "object") {
34+
expression.value = Documentation::Type::Expression::Object{};
35+
} else if (type == "string") {
36+
expression.value =
37+
Documentation::Type::Expression::Primitive::String;
38+
} else if (type == "integer") {
39+
expression.value =
40+
Documentation::Type::Expression::Primitive::Integer;
41+
} else if (type == "number") {
42+
expression.value =
43+
Documentation::Type::Expression::Primitive::Number;
44+
}
45+
}
46+
47+
return expression;
48+
}
49+
50+
auto notes_of(const sourcemeta::core::JSON &schema) -> Documentation::Notes {
51+
Documentation::Notes notes;
52+
if (!schema.is_object()) {
53+
return notes;
54+
}
55+
if (schema.defines("title") && schema.at("title").is_string()) {
56+
notes.title = schema.at("title").to_string();
57+
}
58+
if (schema.defines("description") && schema.at("description").is_string()) {
59+
notes.description = schema.at("description").to_string();
60+
}
61+
if (schema.defines("default")) {
62+
notes.default_value = schema.at("default");
63+
}
64+
return notes;
65+
}
66+
67+
auto is_required_property(const sourcemeta::core::JSON &schema,
68+
const sourcemeta::core::JSON::String &property)
69+
-> bool {
70+
if (!schema.is_object() || !schema.defines("required") ||
71+
!schema.at("required").is_array()) {
72+
return false;
73+
}
74+
for (const auto &item : schema.at("required").as_array()) {
75+
if (item.is_string() && item.to_string() == property) {
76+
return true;
77+
}
78+
}
79+
return false;
80+
}
81+
82+
auto walk_schema(const sourcemeta::core::JSON &schema, const bool include_root)
83+
-> Documentation;
84+
85+
auto walk_properties(const sourcemeta::core::JSON &schema,
86+
std::vector<Documentation::Row> &rows) -> void {
87+
if (!schema.is_object() || !schema.defines("properties") ||
88+
!schema.at("properties").is_object()) {
89+
return;
90+
}
91+
92+
for (const auto &entry : schema.at("properties").as_object()) {
93+
Documentation::Row row;
94+
row.path = "/" + entry.first;
95+
row.type.expression = type_expression_of(entry.second);
96+
row.notes = notes_of(entry.second);
97+
row.required = is_required_property(schema, entry.first);
98+
rows.push_back(std::move(row));
99+
}
100+
}
101+
102+
auto walk_any_of(const sourcemeta::core::JSON &schema,
103+
std::vector<Documentation::Section> &children) -> void {
104+
if (!schema.is_object() || !schema.defines("anyOf") ||
105+
!schema.at("anyOf").is_array()) {
106+
return;
107+
}
108+
109+
Documentation::Section section;
110+
section.label = "Any of";
111+
for (const auto &branch : schema.at("anyOf").as_array()) {
112+
section.children.push_back(walk_schema(branch, false));
113+
}
114+
children.push_back(std::move(section));
115+
}
116+
117+
auto walk_schema(const sourcemeta::core::JSON &schema, const bool include_root)
118+
-> Documentation {
119+
Documentation documentation;
120+
121+
if (include_root) {
122+
Documentation::Row root;
123+
root.path = "(root)";
124+
root.type.expression = type_expression_of(schema);
125+
root.notes = notes_of(schema);
126+
documentation.rows.push_back(std::move(root));
127+
}
128+
129+
walk_properties(schema, documentation.rows);
130+
walk_any_of(schema, documentation.children);
131+
132+
return documentation;
133+
}
134+
135+
} // namespace
136+
137+
auto to_documentation(const sourcemeta::core::JSON &schema,
138+
const sourcemeta::core::SchemaWalker &walker,
139+
const sourcemeta::core::SchemaResolver &resolver)
140+
-> Documentation {
141+
// Canonicalize the schema for easier analysis
142+
sourcemeta::core::SchemaTransformer canonicalizer;
143+
sourcemeta::blaze::add(canonicalizer,
144+
sourcemeta::blaze::AlterSchemaMode::Canonicalizer);
145+
sourcemeta::core::JSON canonical{schema};
146+
[[maybe_unused]] const auto canonicalized{canonicalizer.apply(
147+
canonical, walker, resolver,
148+
[](const auto &, const auto, const auto, const auto &,
149+
[[maybe_unused]] const auto applied) { assert(applied); })};
150+
assert(canonicalized.first);
151+
152+
// Frame the canonicalized schema with reference information
153+
sourcemeta::core::SchemaFrame frame{
154+
sourcemeta::core::SchemaFrame::Mode::References};
155+
frame.analyse(canonical, walker, resolver);
156+
157+
return walk_schema(canonical, true);
158+
}
159+
160+
} // namespace sourcemeta::blaze

0 commit comments

Comments
 (0)