Skip to content

Commit f76dc85

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

12 files changed

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

0 commit comments

Comments
 (0)