Skip to content

Commit 3191eb0

Browse files
committed
Generate C/C++ headers in compile using --include (like xxd)
Signed-off-by: Juan Cruz Viotti <[email protected]>
1 parent fe07eda commit 3191eb0

File tree

7 files changed

+212
-5
lines changed

7 files changed

+212
-5
lines changed

docs/compile.markdown

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Compiling
88
jsonschema compile <schema.json|.yaml> [--http/-h] [--verbose/-v]
99
[--resolve/-r <schemas-or-directories> ...] [--extension/-e <extension>]
1010
[--ignore/-i <schemas-or-directories>] [--fast/-f] [--default-dialect/-d <uri>]
11-
[--minify/-m] [--json/-j]
11+
[--minify/-m] [--json/-j] [--include/-n <name>]
1212
```
1313

1414
The `validate` command will first compile the schema into an optimised
@@ -48,3 +48,9 @@ jsonschema compile path/to/my/schema.json --fast > template.json
4848
```sh
4949
jsonschema compile path/to/my/schema.json --resolve other.json > template.json
5050
```
51+
52+
### Compile a JSON Schema to a C/C++ header file
53+
54+
```sh
55+
jsonschema compile path/to/my/schema.json --include MY_SCHEMA > my_schema.h
56+
```

src/command_compile.cc

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55

66
#include <sourcemeta/blaze/compiler.h>
77

8-
#include <iostream> // std::cerr, std::cout
8+
#include <algorithm> // std::transform
9+
#include <cctype> // std::toupper
10+
#include <iomanip> // std::hex, std::setw, std::setfill
11+
#include <iostream> // std::cerr, std::cout
12+
#include <sstream> // std::ostringstream
913

1014
#include "command.h"
1115
#include "configuration.h"
@@ -62,11 +66,59 @@ auto sourcemeta::jsonschema::compile(const sourcemeta::core::Options &options)
6266
}
6367

6468
const auto template_json{sourcemeta::blaze::to_json(schema_template)};
65-
if (options.contains("minify")) {
69+
70+
if (options.contains("include") && !options.at("include").empty()) {
71+
std::string name{options.at("include").front()};
72+
std::transform(name.begin(), name.end(), name.begin(),
73+
[](unsigned char character) -> unsigned char {
74+
return static_cast<unsigned char>(std::toupper(character));
75+
});
76+
77+
std::ostringstream json_stream;
78+
sourcemeta::core::stringify(template_json, json_stream);
79+
const std::string json_data{json_stream.str()};
80+
81+
constexpr std::size_t BYTES_PER_LINE{16};
82+
83+
std::cout << "#ifndef SOURCEMETA_JSONSCHEMA_INCLUDE_" << name << "_H_\n";
84+
std::cout << "#define SOURCEMETA_JSONSCHEMA_INCLUDE_" << name << "_H_\n";
85+
std::cout << "\n";
86+
std::cout << "#ifdef __cplusplus\n";
87+
std::cout << "#include <string_view>\n";
88+
std::cout << "#endif\n";
89+
std::cout << "\n";
90+
std::cout << "const char " << name << "_DATA[] =";
91+
92+
for (std::size_t index = 0; index < json_data.size(); ++index) {
93+
if (index % BYTES_PER_LINE == 0) {
94+
std::cout << "\n \"";
95+
}
96+
97+
std::cout << "\\x" << std::hex << std::setw(2) << std::setfill('0')
98+
<< (static_cast<unsigned int>(
99+
static_cast<unsigned char>(json_data[index])));
100+
101+
if ((index + 1) % BYTES_PER_LINE == 0 || index + 1 == json_data.size()) {
102+
std::cout << "\"";
103+
}
104+
}
105+
106+
std::cout << ";\n";
107+
std::cout << std::dec;
108+
std::cout << "const unsigned int " << name
109+
<< "_LENGTH = " << json_data.size() << ";\n";
110+
std::cout << "\n";
111+
std::cout << "#ifdef __cplusplus\n";
112+
std::cout << "constexpr std::string_view " << name << "{" << name
113+
<< "_DATA, " << name << "_LENGTH};\n";
114+
std::cout << "#endif\n";
115+
std::cout << "\n";
116+
std::cout << "#endif\n";
117+
} else if (options.contains("minify")) {
66118
sourcemeta::core::stringify(template_json, std::cout);
119+
std::cout << "\n";
67120
} else {
68121
sourcemeta::core::prettify(template_json, std::cout);
122+
std::cout << "\n";
69123
}
70-
71-
std::cout << "\n";
72124
}

src/main.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@ Global Options:
5959
6060
compile <schema.json|.yaml> [--extension/-e <extension>]
6161
[--ignore/-i <schemas-or-directories>] [--fast/-f] [--minify/-m]
62+
[--include/-n <name>]
6263
6364
Compile the given schema into an internal optimised representation.
65+
Use --include/-n to output as a C/C++ header file.
6466
6567
test [schemas-or-directories...] [--extension/-e <extension>]
6668
[--ignore/-i <schemas-or-directories>]
@@ -168,6 +170,7 @@ auto jsonschema_main(const std::string &program, const std::string &command,
168170
} else if (command == "compile") {
169171
app.flag("fast", {"f"});
170172
app.flag("minify", {"m"});
173+
app.option("include", {"n"});
171174
app.parse(argc, argv, {.skip = 1});
172175
sourcemeta::jsonschema::compile(app);
173176
return EXIT_SUCCESS;

test/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,9 @@ add_jsonschema_test_unix(compile/pass_without_extension_json)
359359
add_jsonschema_test_unix(compile/pass_without_extension_yaml)
360360
add_jsonschema_test_unix(compile/pass_custom_extension_json)
361361
add_jsonschema_test_unix(compile/pass_custom_extension_yaml)
362+
add_jsonschema_test_unix(compile/pass_include)
363+
add_jsonschema_test_unix(compile/pass_include_lowercase)
364+
add_jsonschema_test_unix(compile/pass_include_short)
362365

363366
# Canonicalize
364367
add_jsonschema_test_unix(canonicalize/pass_input_unmodified)

test/compile/pass_include.sh

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/bin/sh
2+
3+
set -o errexit
4+
set -o nounset
5+
6+
TMP="$(mktemp -d)"
7+
clean() { rm -rf "$TMP"; }
8+
trap clean EXIT
9+
10+
cat << 'EOF' > "$TMP/schema.json"
11+
{
12+
"$id": "https://example.com",
13+
"$schema": "https://json-schema.org/draft/2020-12/schema",
14+
"type": "string"
15+
}
16+
EOF
17+
18+
"$1" compile "$TMP/schema.json" --include TEST_SCHEMA > "$TMP/output.h"
19+
20+
cat << 'EOF' > "$TMP/expected.h"
21+
#ifndef SOURCEMETA_JSONSCHEMA_INCLUDE_TEST_SCHEMA_H_
22+
#define SOURCEMETA_JSONSCHEMA_INCLUDE_TEST_SCHEMA_H_
23+
24+
#ifdef __cplusplus
25+
#include <string_view>
26+
#endif
27+
28+
const char TEST_SCHEMA_DATA[] =
29+
"\x5b\x66\x61\x6c\x73\x65\x2c\x74\x72\x75\x65\x2c\x5b\x22\x22\x2c"
30+
"\x22\x68\x74\x74\x70\x73\x3a\x2f\x2f\x65\x78\x61\x6d\x70\x6c\x65"
31+
"\x2e\x63\x6f\x6d\x22\x5d\x2c\x5b\x5b\x31\x31\x2c\x22\x2f\x74\x79"
32+
"\x70\x65\x22\x2c\x22\x22\x2c\x22\x23\x2f\x74\x79\x70\x65\x22\x2c"
33+
"\x32\x2c\x5b\x38\x2c\x34\x5d\x5d\x5d\x5d";
34+
const unsigned int TEST_SCHEMA_LENGTH = 74;
35+
36+
#ifdef __cplusplus
37+
constexpr std::string_view TEST_SCHEMA{TEST_SCHEMA_DATA, TEST_SCHEMA_LENGTH};
38+
#endif
39+
40+
#endif
41+
EOF
42+
43+
diff "$TMP/output.h" "$TMP/expected.h"
44+
45+
# Verify the header compiles with a C compiler
46+
cat << 'EOF' > "$TMP/test.c"
47+
#include "output.h"
48+
EOF
49+
cc -c "$TMP/test.c" -o "$TMP/test.o"
50+
51+
# Verify the header compiles with a C++ compiler
52+
cat << 'EOF' > "$TMP/test.cc"
53+
#include "output.h"
54+
EOF
55+
c++ -std=c++17 -c "$TMP/test.cc" -o "$TMP/test_cpp.o"
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/bin/sh
2+
3+
set -o errexit
4+
set -o nounset
5+
6+
TMP="$(mktemp -d)"
7+
clean() { rm -rf "$TMP"; }
8+
trap clean EXIT
9+
10+
cat << 'EOF' > "$TMP/schema.json"
11+
{
12+
"$id": "https://example.com",
13+
"$schema": "https://json-schema.org/draft/2020-12/schema",
14+
"type": "string"
15+
}
16+
EOF
17+
18+
# Pass lowercase name, should be uppercased in output
19+
"$1" compile "$TMP/schema.json" --include test_schema > "$TMP/output.h"
20+
21+
cat << 'EOF' > "$TMP/expected.h"
22+
#ifndef SOURCEMETA_JSONSCHEMA_INCLUDE_TEST_SCHEMA_H_
23+
#define SOURCEMETA_JSONSCHEMA_INCLUDE_TEST_SCHEMA_H_
24+
25+
#ifdef __cplusplus
26+
#include <string_view>
27+
#endif
28+
29+
const char TEST_SCHEMA_DATA[] =
30+
"\x5b\x66\x61\x6c\x73\x65\x2c\x74\x72\x75\x65\x2c\x5b\x22\x22\x2c"
31+
"\x22\x68\x74\x74\x70\x73\x3a\x2f\x2f\x65\x78\x61\x6d\x70\x6c\x65"
32+
"\x2e\x63\x6f\x6d\x22\x5d\x2c\x5b\x5b\x31\x31\x2c\x22\x2f\x74\x79"
33+
"\x70\x65\x22\x2c\x22\x22\x2c\x22\x23\x2f\x74\x79\x70\x65\x22\x2c"
34+
"\x32\x2c\x5b\x38\x2c\x34\x5d\x5d\x5d\x5d";
35+
const unsigned int TEST_SCHEMA_LENGTH = 74;
36+
37+
#ifdef __cplusplus
38+
constexpr std::string_view TEST_SCHEMA{TEST_SCHEMA_DATA, TEST_SCHEMA_LENGTH};
39+
#endif
40+
41+
#endif
42+
EOF
43+
44+
diff "$TMP/output.h" "$TMP/expected.h"

test/compile/pass_include_short.sh

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/bin/sh
2+
3+
set -o errexit
4+
set -o nounset
5+
6+
TMP="$(mktemp -d)"
7+
clean() { rm -rf "$TMP"; }
8+
trap clean EXIT
9+
10+
cat << 'EOF' > "$TMP/schema.json"
11+
{
12+
"$id": "https://example.com",
13+
"$schema": "https://json-schema.org/draft/2020-12/schema",
14+
"type": "string"
15+
}
16+
EOF
17+
18+
# Use short option -n
19+
"$1" compile "$TMP/schema.json" -n TEST_SCHEMA > "$TMP/output.h"
20+
21+
cat << 'EOF' > "$TMP/expected.h"
22+
#ifndef SOURCEMETA_JSONSCHEMA_INCLUDE_TEST_SCHEMA_H_
23+
#define SOURCEMETA_JSONSCHEMA_INCLUDE_TEST_SCHEMA_H_
24+
25+
#ifdef __cplusplus
26+
#include <string_view>
27+
#endif
28+
29+
const char TEST_SCHEMA_DATA[] =
30+
"\x5b\x66\x61\x6c\x73\x65\x2c\x74\x72\x75\x65\x2c\x5b\x22\x22\x2c"
31+
"\x22\x68\x74\x74\x70\x73\x3a\x2f\x2f\x65\x78\x61\x6d\x70\x6c\x65"
32+
"\x2e\x63\x6f\x6d\x22\x5d\x2c\x5b\x5b\x31\x31\x2c\x22\x2f\x74\x79"
33+
"\x70\x65\x22\x2c\x22\x22\x2c\x22\x23\x2f\x74\x79\x70\x65\x22\x2c"
34+
"\x32\x2c\x5b\x38\x2c\x34\x5d\x5d\x5d\x5d";
35+
const unsigned int TEST_SCHEMA_LENGTH = 74;
36+
37+
#ifdef __cplusplus
38+
constexpr std::string_view TEST_SCHEMA{TEST_SCHEMA_DATA, TEST_SCHEMA_LENGTH};
39+
#endif
40+
41+
#endif
42+
EOF
43+
44+
diff "$TMP/output.h" "$TMP/expected.h"

0 commit comments

Comments
 (0)