|
1 | 1 | #include <sourcemeta/blaze/compiler.h> |
2 | 2 | #include <sourcemeta/core/json.h> |
| 3 | +#include <sourcemeta/core/jsonpointer.h> |
| 4 | +#include <sourcemeta/core/options.h> |
3 | 5 |
|
4 | | -#include <chrono> // std::chrono |
5 | | -#include <cstdlib> // EXIT_SUCCESS, EXIT_FAILURE |
6 | | -#include <iostream> // std::cerr |
| 6 | +#include <chrono> // std::chrono |
| 7 | +#include <cstdlib> // EXIT_SUCCESS, EXIT_FAILURE |
| 8 | +#include <filesystem> // std::filesystem |
| 9 | +#include <iostream> // std::cerr, std::cout |
| 10 | +#include <optional> // std::optional, std::nullopt |
| 11 | +#include <string> // std::string |
| 12 | +#include <string_view> // std::string_view |
| 13 | +#include <utility> // std::pair |
| 14 | +#include <vector> // std::vector |
| 15 | + |
| 16 | +using ResolveDirectories = |
| 17 | + std::vector<std::pair<std::string, std::filesystem::path>>; |
| 18 | + |
| 19 | +static auto parse_resolve_directory(const std::string_view value) |
| 20 | + -> std::optional<std::pair<std::string, std::filesystem::path>> { |
| 21 | + const auto comma{value.find(',')}; |
| 22 | + if (comma == std::string_view::npos || comma == 0 || |
| 23 | + comma == value.size() - 1) { |
| 24 | + return std::nullopt; |
| 25 | + } |
| 26 | + |
| 27 | + return std::pair{std::string{value.substr(0, comma)}, |
| 28 | + std::filesystem::path{value.substr(comma + 1)}}; |
| 29 | +} |
| 30 | + |
| 31 | +static auto resolve_schema(const std::string_view identifier, |
| 32 | + const ResolveDirectories &resolve_directories) |
| 33 | + -> std::optional<sourcemeta::core::JSON> { |
| 34 | + for (const auto &[prefix, directory] : resolve_directories) { |
| 35 | + if (!identifier.starts_with(prefix)) { |
| 36 | + continue; |
| 37 | + } |
| 38 | + |
| 39 | + auto suffix{identifier.substr(prefix.size())}; |
| 40 | + if (!suffix.empty() && suffix.front() == '/') { |
| 41 | + suffix.remove_prefix(1); |
| 42 | + } |
| 43 | + |
| 44 | + const auto file_path{std::filesystem::weakly_canonical( |
| 45 | + directory / std::filesystem::path{suffix})}; |
| 46 | + const auto directory_string{directory.string()}; |
| 47 | + const auto file_string{file_path.string()}; |
| 48 | + if (!file_string.starts_with(directory_string)) { |
| 49 | + continue; |
| 50 | + } |
| 51 | + |
| 52 | + if (std::filesystem::exists(file_path)) { |
| 53 | + return sourcemeta::core::read_json(file_path); |
| 54 | + } |
| 55 | + } |
| 56 | + |
| 57 | + return sourcemeta::core::schema_resolver(identifier); |
| 58 | +} |
7 | 59 |
|
8 | 60 | auto main(int argc, char **argv) noexcept -> int { |
9 | | - if (argc < 3) { |
10 | | - std::cerr << "Usage: " << argv[0] << " <fast|exhaustive> <schema.json>\n"; |
| 61 | + sourcemeta::core::Options options; |
| 62 | + options.flag("fast", {"f"}); |
| 63 | + options.option("default-dialect", {"d"}); |
| 64 | + options.option("resolve-directory", {"r"}); |
| 65 | + options.option("path", {"p"}); |
| 66 | + |
| 67 | + try { |
| 68 | + options.parse(argc, argv); |
| 69 | + } catch (const sourcemeta::core::OptionsError &error) { |
| 70 | + std::cerr << "error: " << error.what() << "\n"; |
11 | 71 | return EXIT_FAILURE; |
12 | 72 | } |
13 | 73 |
|
14 | | - const std::string mode_string{argv[1]}; |
15 | | - auto mode{sourcemeta::blaze::Mode::FastValidation}; |
16 | | - if (mode_string == "fast") { |
17 | | - std::cerr << "Choosing fast mode\n"; |
18 | | - } else if (mode_string == "exhaustive") { |
19 | | - std::cerr << "Choosing exhaustive mode\n"; |
20 | | - mode = sourcemeta::blaze::Mode::Exhaustive; |
21 | | - } else { |
22 | | - std::cerr << "Invalid mode: " << mode_string << "\n"; |
| 74 | + const auto &positional{options.positional()}; |
| 75 | + if (positional.empty()) { |
| 76 | + std::cerr << "Usage: " << argv[0] |
| 77 | + << " [--fast] [--default-dialect <uri>] " |
| 78 | + "[--resolve-directory <uri-prefix>,<dir>]... " |
| 79 | + "[--path <pointer>] <schema.json>\n"; |
23 | 80 | return EXIT_FAILURE; |
24 | 81 | } |
25 | 82 |
|
26 | | - const auto schema{sourcemeta::core::read_json(argv[2])}; |
27 | | - std::cerr << "Compiling schema: " << argv[2] << "\n"; |
28 | | - const auto compile_start{std::chrono::high_resolution_clock::now()}; |
29 | | - const auto schema_template{sourcemeta::blaze::compile( |
30 | | - schema, sourcemeta::core::schema_walker, |
31 | | - sourcemeta::core::schema_resolver, |
32 | | - sourcemeta::blaze::default_schema_compiler, mode)}; |
33 | | - const auto compile_end{std::chrono::high_resolution_clock::now()}; |
34 | | - const auto compile_duration{ |
35 | | - std::chrono::duration_cast<std::chrono::milliseconds>(compile_end - |
36 | | - compile_start)}; |
37 | | - std::cerr << "Took: " << compile_duration.count() << "ms\n"; |
38 | | - std::cerr << "Number of generated instructions: " |
39 | | - << schema_template.targets[0].size() << "\n"; |
40 | | - |
41 | | - sourcemeta::core::prettify(sourcemeta::blaze::to_json(schema_template), |
42 | | - std::cout); |
43 | | - std::cout << "\n"; |
| 83 | + const auto mode{options.contains("fast") |
| 84 | + ? sourcemeta::blaze::Mode::FastValidation |
| 85 | + : sourcemeta::blaze::Mode::Exhaustive}; |
| 86 | + std::cerr << "Mode: " |
| 87 | + << (mode == sourcemeta::blaze::Mode::FastValidation ? "fast" |
| 88 | + : "exhaustive") |
| 89 | + << "\n"; |
| 90 | + |
| 91 | + std::string default_dialect; |
| 92 | + if (options.contains("default-dialect")) { |
| 93 | + default_dialect = options.at("default-dialect").front(); |
| 94 | + std::cerr << "Default dialect: " << default_dialect << "\n"; |
| 95 | + } |
| 96 | + |
| 97 | + try { |
| 98 | + std::vector<std::pair<std::string, std::filesystem::path>> |
| 99 | + resolve_directories; |
| 100 | + if (options.contains("resolve-directory")) { |
| 101 | + for (const auto &value : options.at("resolve-directory")) { |
| 102 | + auto entry{parse_resolve_directory(value)}; |
| 103 | + if (!entry.has_value()) { |
| 104 | + std::cerr << "error: --resolve-directory value must be " |
| 105 | + "<uri-prefix>,<directory>\n"; |
| 106 | + return EXIT_FAILURE; |
| 107 | + } |
| 108 | + |
| 109 | + const auto canonical{ |
| 110 | + std::filesystem::weakly_canonical(entry.value().second)}; |
| 111 | + if (!std::filesystem::is_directory(canonical)) { |
| 112 | + std::cerr << "error: not a directory: " << entry.value().second |
| 113 | + << "\n"; |
| 114 | + return EXIT_FAILURE; |
| 115 | + } |
| 116 | + |
| 117 | + entry.value().second = canonical; |
| 118 | + std::cerr << "Resolve: " << entry.value().first << " -> " << canonical |
| 119 | + << "\n"; |
| 120 | + resolve_directories.push_back(std::move(entry.value())); |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + const auto resolver{ |
| 125 | + [&resolve_directories](const std::string_view identifier) |
| 126 | + -> std::optional<sourcemeta::core::JSON> { |
| 127 | + return resolve_schema(identifier, resolve_directories); |
| 128 | + }}; |
| 129 | + |
| 130 | + auto document{ |
| 131 | + sourcemeta::core::read_json(std::filesystem::path{positional.front()})}; |
| 132 | + std::cerr << "Input: " << positional.front() << "\n"; |
| 133 | + |
| 134 | + if (options.contains("path")) { |
| 135 | + const auto pointer{sourcemeta::core::to_pointer( |
| 136 | + std::string{options.at("path").front()})}; |
| 137 | + const auto *result{sourcemeta::core::try_get(document, pointer)}; |
| 138 | + if (result == nullptr) { |
| 139 | + std::cerr << "error: path not found in input: " |
| 140 | + << options.at("path").front() << "\n"; |
| 141 | + return EXIT_FAILURE; |
| 142 | + } |
| 143 | + |
| 144 | + auto extracted{*result}; |
| 145 | + document = std::move(extracted); |
| 146 | + } |
| 147 | + |
| 148 | + const auto compile_start{std::chrono::high_resolution_clock::now()}; |
| 149 | + const auto schema_template{sourcemeta::blaze::compile( |
| 150 | + document, sourcemeta::core::schema_walker, resolver, |
| 151 | + sourcemeta::blaze::default_schema_compiler, mode, default_dialect)}; |
| 152 | + const auto compile_end{std::chrono::high_resolution_clock::now()}; |
| 153 | + const auto compile_duration{ |
| 154 | + std::chrono::duration_cast<std::chrono::milliseconds>(compile_end - |
| 155 | + compile_start)}; |
| 156 | + std::cerr << "Compiled in " << compile_duration.count() << "ms (" |
| 157 | + << schema_template.targets[0].size() << " instructions)\n"; |
| 158 | + |
| 159 | + sourcemeta::core::prettify(sourcemeta::blaze::to_json(schema_template), |
| 160 | + std::cout); |
| 161 | + std::cout << "\n"; |
| 162 | + } catch (const std::exception &error) { |
| 163 | + std::cerr << "error: " << error.what() << "\n"; |
| 164 | + return EXIT_FAILURE; |
| 165 | + } |
44 | 166 |
|
45 | 167 | return EXIT_SUCCESS; |
46 | 168 | } |
0 commit comments