Skip to content

Commit

Permalink
Add a global --ignore/-i option for file/directory exclusion (#87)
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Cruz Viotti <[email protected]>
  • Loading branch information
jviotti authored Jun 13, 2024
1 parent a07dc9a commit 14ae34c
Show file tree
Hide file tree
Showing 14 changed files with 212 additions and 27 deletions.
8 changes: 8 additions & 0 deletions docs/bundle.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Bundling
```sh
jsonschema bundle <schema.json>
[--http/-h] [--verbose/-v] [--resolve/-r <schemas-or-directories> ...]
[--ignore/-i <schemas-or-directories>]
```

A schema may contain references to remote schemas outside the scope of the
Expand Down Expand Up @@ -84,6 +85,13 @@ jsonschema bundle path/to/my/schema.json \
--resolve path/to/schemas --extension schema.json
```

### Bundle a JSON Schema importing a directory of schemas while ignoring another

```sh
jsonschema bundle path/to/my/schema.json \
--resolve path/to/schemas --ignore path/to/schemas/nested
```

### Bundle a JSON Schema while enabling HTTP resolution

```sh
Expand Down
7 changes: 7 additions & 0 deletions docs/format.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Formatting
```sh
jsonschema fmt [schemas-or-directories...]
[--check/-c] [--verbose/-v] [--extension/-e <extension>]
[--ignore/-i <schemas-or-directories>]
```

Schemas are code. As such, they are expected follow consistent stylistic
Expand Down Expand Up @@ -57,6 +58,12 @@ jsonschema fmt path/to/schemas/
jsonschema fmt
```

### Format every `.json` file in a given directory while ignoring another

```sh
jsonschema fmt path/to/schemas/ --ignore path/to/schemas/nested
```

### Format every `.schema.json` file in the current directory (recursively)

```sh
Expand Down
7 changes: 7 additions & 0 deletions docs/lint.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Linting
```sh
jsonschema lint [schemas-or-directories...]
[--fix/-f] [--verbose/-v] [--extension/-e <extension>]
[--ignore/-i <schemas-or-directories>]
```

JSON Schema is a surprisingly expressive schema language. Like with traditional
Expand Down Expand Up @@ -56,6 +57,12 @@ jsonschema lint path/to/my/schema_1.json path/to/my/schema_2.json
jsonschema lint path/to/schemas/
```

### Lint every `.json` file in a given directory while ignoring another

```sh
jsonschema lint path/to/schemas/ --ignore path/to/schemas/nested
```

### Lint every `.json` file in the current directory (recursively)

```sh
Expand Down
7 changes: 7 additions & 0 deletions docs/test.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Testing
jsonschema test [schemas-or-directories...]
[--http/-h] [--metaschema/-m] [--verbose/-v]
[--resolve/-r <schemas-or-directories> ...] [--extension/-e <extension>]
[--ignore/-i <schemas-or-directories>]
```

Schemas are code. As such, you should run an automated unit testing suite
Expand Down Expand Up @@ -69,6 +70,12 @@ jsonschema test path/to/tests/
jsonschema test
```

### Run every `.json` test definition in the current directory while ignoring another

```sh
jsonschema test --ignore dist
```

### Run every `.test.json` test definition in the current directory (recursively)

```sh
Expand Down
4 changes: 2 additions & 2 deletions src/command_fmt.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ auto intelligence::jsonschema::cli::fmt(
const std::span<const std::string> &arguments) -> int {
const auto options{parse_options(arguments, {"c", "check"})};

for (const auto &entry :
for_each_json(options.at(""), parse_extensions(options))) {
for (const auto &entry : for_each_json(options.at(""), parse_ignore(options),
parse_extensions(options))) {
if (options.contains("c") || options.contains("check")) {
log_verbose(options) << "Checking: " << entry.first.string() << "\n";
std::ifstream input{entry.first};
Expand Down
6 changes: 4 additions & 2 deletions src/command_lint.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ auto intelligence::jsonschema::cli::lint(

if (options.contains("f") || options.contains("fix")) {
for (const auto &entry :
for_each_json(options.at(""), parse_extensions(options))) {
for_each_json(options.at(""), parse_ignore(options),
parse_extensions(options))) {
log_verbose(options) << "Linting: " << entry.first.string() << "\n";
auto copy = entry.second;
bundle.apply(copy, sourcemeta::jsontoolkit::default_schema_walker,
Expand All @@ -34,7 +35,8 @@ auto intelligence::jsonschema::cli::lint(
}
} else {
for (const auto &entry :
for_each_json(options.at(""), parse_extensions(options))) {
for_each_json(options.at(""), parse_ignore(options),
parse_extensions(options))) {
log_verbose(options) << "Linting: " << entry.first.string() << "\n";
const bool subresult = bundle.check(
entry.second, sourcemeta::jsontoolkit::default_schema_walker,
Expand Down
4 changes: 2 additions & 2 deletions src/command_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ auto intelligence::jsonschema::cli::test(
const auto test_resolver{
resolver(options, options.contains("h") || options.contains("http"))};

for (const auto &entry :
for_each_json(options.at(""), parse_extensions(options))) {
for (const auto &entry : for_each_json(options.at(""), parse_ignore(options),
parse_extensions(options))) {
const sourcemeta::jsontoolkit::JSON test{
sourcemeta::jsontoolkit::from_file(entry.first)};
CLI_ENSURE(test.is_object(), "The test document must be an object")
Expand Down
16 changes: 13 additions & 3 deletions src/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,34 +31,44 @@ Global Options:
was passed.
test [schemas-or-directories...] [--http/-h] [--metaschema/-m]
[--extension/-e <extension>]
[--extension/-e <extension>] [--ignore/-i <schemas-or-directories>]
A schema test runner inspired by the official JSON Schema test suite.
Passing directories as input will run every `.json` file in such
directory (recursively) as a test. If no argument is passed, run every
`.json` file in the current working directory (recursively) as a test.
The `--ignore/-i` option can be set to files or directories to ignore.
The `--http/-h` option enables resolving remote schemas over the HTTP
protocol. The `--metaschema/-m` option checks that the given schema is
valid with respects to its dialect metaschema. When scanning
directories, the `--extension/-e` option is used to prefer a file
extension other than `.json`. This option can be set multiple times.
fmt [schemas-or-directories...] [--check/-c] [--extension/-e <extension>]
[--ignore/-i <schemas-or-directories>]
Format the input schemas in-place. Passing directories as input means
to format every `.json` file in such directory (recursively). If no
argument is passed, format every `.json` file in the current working
directory (recursively). The `--check/-c` option will check if the given
directory (recursively). The `--ignore/-i` option can be set to files
or directories to ignore.
The `--check/-c` option will check if the given
schemas adhere to the desired formatting without modifying them. When
scanning directories, the `--extension/-e` option is used to prefer a
file extension other than `.json`. This option can be set multiple times.
lint [schemas-or-directories...] [--fix/-f] [--extension/-e <extension>]
[--ignore/-i <schemas-or-directories>]
Lint the input schemas. Passing directories as input means to lint
every `.json` file in such directory (recursively). If no argument is
passed, lint every `.json` file in the current working directory
(recursively). The `--fix/-f` option will attempt to automatically
(recursively). The `--ignore/-i` option can be set to files or
directories to ignore.
The `--fix/-f` option will attempt to automatically
fix the linter errors. When scanning directories, the `--extension/-e`
option is used to prefer a file extension other than `.json`. This option
can be set multiple times.
Expand Down
91 changes: 74 additions & 17 deletions src/utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

#include "utils.h"

#include <algorithm> // std::any_of
#include <algorithm> // std::any_of, std::none_of
#include <cassert> // assert
#include <fstream> // std::ofstream
#include <iostream> // std::cerr
Expand All @@ -16,36 +16,66 @@

namespace {

bool path_starts_with(const std::filesystem::path &path,
const std::filesystem::path &prefix) {
auto path_iterator = path.begin();
auto prefix_iterator = prefix.begin();

while (prefix_iterator != prefix.end()) {
if (path_iterator == path.end() || *path_iterator != *prefix_iterator) {
return false;
}

++path_iterator;
++prefix_iterator;
}

return true;
}

auto handle_json_entry(
const std::filesystem::path &entry_path,
const std::set<std::filesystem::path> &blacklist,
const std::set<std::string> &extensions,
std::vector<std::pair<std::filesystem::path, sourcemeta::jsontoolkit::JSON>>
&result) -> void {
if (std::filesystem::is_directory(entry_path)) {
for (auto const &entry :
std::filesystem::recursive_directory_iterator{entry_path}) {
const auto canonical{std::filesystem::canonical(entry.path())};
if (!std::filesystem::is_directory(entry) &&
std::any_of(extensions.cbegin(), extensions.cend(),
[&entry](const auto &extension) {
return entry.path().string().ends_with(extension);
})) {
result.emplace_back(entry.path(),
sourcemeta::jsontoolkit::from_file(entry.path()));
[&canonical](const auto &extension) {
return canonical.string().ends_with(extension);
}) &&
std::none_of(blacklist.cbegin(), blacklist.cend(),
[&canonical](const auto &prefix) {
return prefix == canonical ||
path_starts_with(canonical, prefix);
})) {
result.emplace_back(canonical,
sourcemeta::jsontoolkit::from_file(canonical));
}
}
} else {
if (!std::filesystem::exists(entry_path)) {
const auto canonical{std::filesystem::canonical(entry_path)};
if (!std::filesystem::exists(canonical)) {
std::ostringstream error;
error << "No such file or directory: " << entry_path.string();
error << "No such file or directory: " << canonical.string();
throw std::runtime_error(error.str());
}

if (std::any_of(extensions.cbegin(), extensions.cend(),
[&entry_path](const auto &extension) {
return entry_path.string().ends_with(extension);
})) {
result.emplace_back(entry_path,
sourcemeta::jsontoolkit::from_file(entry_path));
[&canonical](const auto &extension) {
return canonical.string().ends_with(extension);
}) &&
std::none_of(blacklist.cbegin(), blacklist.cend(),
[&canonical](const auto &prefix) {
return prefix == canonical ||
path_starts_with(canonical, prefix);
})) {
result.emplace_back(canonical,
sourcemeta::jsontoolkit::from_file(canonical));
}
}
}
Expand All @@ -65,17 +95,19 @@ auto normalize_extension(const std::string &extension) -> std::string {
namespace intelligence::jsonschema::cli {

auto for_each_json(const std::vector<std::string> &arguments,
const std::set<std::filesystem::path> &blacklist,
const std::set<std::string> &extensions)
-> std::vector<
std::pair<std::filesystem::path, sourcemeta::jsontoolkit::JSON>> {
std::vector<std::pair<std::filesystem::path, sourcemeta::jsontoolkit::JSON>>
result;

if (arguments.empty()) {
handle_json_entry(std::filesystem::current_path(), extensions, result);
handle_json_entry(std::filesystem::current_path(), blacklist, extensions,
result);
} else {
for (const auto &entry : arguments) {
handle_json_entry(entry, extensions, result);
handle_json_entry(entry, blacklist, extensions, result);
}
}

Expand Down Expand Up @@ -207,15 +239,17 @@ auto resolver(const std::map<std::string, std::vector<std::string>> &options,

if (options.contains("resolve")) {
for (const auto &entry :
for_each_json(options.at("resolve"), parse_extensions(options))) {
for_each_json(options.at("resolve"), parse_ignore(options),
parse_extensions(options))) {
log_verbose(options) << "Loading schema: " << entry.first << "\n";
dynamic_resolver.add(entry.second);
}
}

if (options.contains("r")) {
for (const auto &entry :
for_each_json(options.at("r"), parse_extensions(options))) {
for_each_json(options.at("r"), parse_ignore(options),
parse_extensions(options))) {
log_verbose(options) << "Loading schema: " << entry.first << "\n";
dynamic_resolver.add(entry.second);
}
Expand Down Expand Up @@ -259,4 +293,27 @@ auto parse_extensions(const std::map<std::string, std::vector<std::string>>
return result;
}

auto parse_ignore(const std::map<std::string, std::vector<std::string>>
&options) -> std::set<std::filesystem::path> {
std::set<std::filesystem::path> result;

if (options.contains("ignore")) {
for (const auto &ignore : options.at("ignore")) {
const auto canonical{std::filesystem::weakly_canonical(ignore)};
log_verbose(options) << "Ignoring path: " << canonical << "\n";
result.insert(canonical);
}
}

if (options.contains("i")) {
for (const auto &ignore : options.at("e")) {
const auto canonical{std::filesystem::weakly_canonical(ignore)};
log_verbose(options) << "Ignoring path: " << canonical << "\n";
result.insert(canonical);
}
}

return result;
}

} // namespace intelligence::jsonschema::cli
4 changes: 4 additions & 0 deletions src/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ auto parse_options(const std::span<const std::string> &arguments,
-> std::map<std::string, std::vector<std::string>>;

auto for_each_json(const std::vector<std::string> &arguments,
const std::set<std::filesystem::path> &blacklist,
const std::set<std::string> &extensions)
-> std::vector<
std::pair<std::filesystem::path, sourcemeta::jsontoolkit::JSON>>;
Expand All @@ -45,6 +46,9 @@ auto log_verbose(const std::map<std::string, std::vector<std::string>> &options)
auto parse_extensions(const std::map<std::string, std::vector<std::string>>
&options) -> std::set<std::string>;

auto parse_ignore(const std::map<std::string, std::vector<std::string>>
&options) -> std::set<std::filesystem::path>;

} // namespace intelligence::jsonschema::cli

#endif
2 changes: 2 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ add_jsonschema_test_unix(format_cwd)
add_jsonschema_test_unix(format_multi_extension)
add_jsonschema_test_unix(format_check_single_fail)
add_jsonschema_test_unix(format_check_single_pass)
add_jsonschema_test_unix(format_directory_ignore_directory)
add_jsonschema_test_unix(format_directory_ignore_file)
add_jsonschema_test_unix(frame)
add_jsonschema_test_unix(validate_pass_draft4)
add_jsonschema_test_unix(validate_fail_draft4)
Expand Down
Loading

0 comments on commit 14ae34c

Please sign in to comment.