Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
@@ -0,0 +1,43 @@
# ModuleInfoLib
This is a c++17 library to parse and create the Steinberg moduleinfo.json files.
## Parsing
To parse a moduleinfo.json file you need to include the following files to your project:
* moduleinfoparser.cpp
* moduleinfoparser.h
* moduleinfo.h
* json.h
* jsoncxx.h
And add a header search path to the root folder of the VST SDK.
Now to parse a moduleinfo.json file in code you need to read the moduleinfo.json into a memory buffer and call
``` c++
auto moduleInfo = ModuleInfoLib::parseCompatibilityJson (std::string_view (buffer, bufferSize), &std::cerr);
```
Afterwards if parsing succeeded the moduleInfo optional has a value containing the ModuleInfo.
## Creating
The VST3 SDK contains the moduleinfotool utility that can create moduleinfo.json files from VST3 modules.
To add this capability to your own project you need to link to the sdk_hosting library from the SDK and include the following files to your project:
* moduleinfocreator.cpp
* moduleinfocreator.h
* moduleinfo.h
Additionally you need to add the module platform implementation from the hosting directory (module_win32.cpp, module_mac.mm or module_linux.cpp).
Now you can use the two methods in moduleinfocreator.h to create a moduleinfo.json file:
``` c++
auto moduleInfo = ModuleInfoLib::createModuleInfo (module, false);
ModuleInfoLib::outputJson (moduleInfo, std::cout);
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,407 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
// Flags : clang-format SMTGSequencer
//
// Category :
// Filename : public.sdk/source/vst/moduleinfo/jsoncxx.h
// Created by : Steinberg, 12/2021
// Description :
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "json.h"
#include <cassert>
#include <cstdlib>
#include <optional>
#include <string>
#include <string_view>
#include <variant>
#if defined(_MSC_VER) || __has_include(<charconv>)
#include <charconv>
#define SMTG_HAS_CHARCONV
#endif
//------------------------------------------------------------------------
namespace JSON {
namespace Detail {
//------------------------------------------------------------------------
template <typename JsonT>
struct Base
{
explicit Base (JsonT* o) : object_ (o) {}
explicit Base (const Base& o) : object_ (o.object_) {}
Base& operator= (const Base& o) = default;
operator JsonT* () const { return object_; }
JsonT* jsonValue () const { return object_; }
protected:
Base () : object_ (nullptr) {}
JsonT* object_;
};
//------------------------------------------------------------------------
template <typename JsonElement>
struct Iterator
{
explicit Iterator (JsonElement el) : el (el) {}
bool operator== (const Iterator& other) const { return other.el == el; }
bool operator!= (const Iterator& other) const { return other.el != el; }
const JsonElement& operator* () const { return el; }
const JsonElement& operator-> () const { return el; }
Iterator& operator++ ()
{
if (el)
el = el.next ();
return *this;
}
Iterator operator++ (int)
{
auto it = Iterator (el);
operator++ ();
return it;
}
private:
JsonElement el;
};
//------------------------------------------------------------------------
} // Detail
struct Object;
struct Array;
struct String;
struct Number;
struct Boolean;
//------------------------------------------------------------------------
enum class Type
{
Object,
Array,
String,
Number,
True,
False,
Null,
};
//------------------------------------------------------------------------
struct SourceLocation
{
size_t offset;
size_t line;
size_t row;
};
//------------------------------------------------------------------------
struct Value : Detail::Base<json_value_s>
{
using Detail::Base<json_value_s>::Base;
using VariantT = std::variant<Object, Array, String, Number, Boolean, std::nullptr_t>;
std::optional<Object> asObject () const;
std::optional<Array> asArray () const;
std::optional<String> asString () const;
std::optional<Number> asNumber () const;
std::optional<Boolean> asBoolean () const;
std::optional<std::nullptr_t> asNull () const;
VariantT asVariant () const;
Type type () const;
SourceLocation getSourceLocation () const;
};
//------------------------------------------------------------------------
struct Boolean
{
Boolean (size_t type) : value (type == json_type_true) {}
operator bool () const { return value; }
private:
bool value;
};
//------------------------------------------------------------------------
struct String : Detail::Base<json_string_s>
{
using Detail::Base<json_string_s>::Base;
std::string_view text () const { return {jsonValue ()->string, jsonValue ()->string_size}; }
SourceLocation getSourceLocation () const;
};
//------------------------------------------------------------------------
struct Number : Detail::Base<json_number_s>
{
using Detail::Base<json_number_s>::Base;
std::string_view text () const { return {jsonValue ()->number, jsonValue ()->number_size}; }
std::optional<int64_t> getInteger () const;
std::optional<double> getDouble () const;
};
//------------------------------------------------------------------------
struct ObjectElement : Detail::Base<json_object_element_s>
{
using Detail::Base<json_object_element_s>::Base;
String name () const { return String (jsonValue ()->name); }
Value value () const { return Value (jsonValue ()->value); }
ObjectElement next () const { return ObjectElement (jsonValue ()->next); }
};
//------------------------------------------------------------------------
struct Object : Detail::Base<json_object_s>
{
using Detail::Base<json_object_s>::Base;
using Iterator = Detail::Iterator<ObjectElement>;
size_t size () const { return jsonValue ()->length; }
Iterator begin () const { return Iterator (ObjectElement (jsonValue ()->start)); }
Iterator end () const { return Iterator (ObjectElement (nullptr)); }
};
//------------------------------------------------------------------------
struct ArrayElement : Detail::Base<json_array_element_s>
{
using Detail::Base<json_array_element_s>::Base;
Value value () const { return Value (jsonValue ()->value); }
ArrayElement next () const { return ArrayElement (jsonValue ()->next); }
};
//------------------------------------------------------------------------
struct Array : Detail::Base<json_array_s>
{
using Detail::Base<json_array_s>::Base;
using Iterator = Detail::Iterator<ArrayElement>;
size_t size () const { return jsonValue ()->length; }
Iterator begin () const { return Iterator (ArrayElement (jsonValue ()->start)); }
Iterator end () const { return Iterator (ArrayElement (nullptr)); }
};
//------------------------------------------------------------------------
struct Document : Value
{
static std::variant<Document, json_parse_result_s> parse (std::string_view data)
{
auto allocate = [] (void*, size_t allocSize) { return std::malloc (allocSize); };
json_parse_result_s parse_result {};
auto value = json_parse_ex (data.data (), data.size (),
json_parse_flags_allow_json5 |
json_parse_flags_allow_location_information,
allocate, nullptr, &parse_result);
if (value)
return Document (value);
return parse_result;
}
~Document () noexcept
{
if (object_)
std::free (object_);
}
Document (Document&& doc) noexcept { *this = std::move (doc); }
Document& operator= (Document&& doc) noexcept
{
std::swap (object_, doc.object_);
return *this;
}
private:
using Value::Value;
};
//------------------------------------------------------------------------
inline std::optional<Object> Value::asObject () const
{
if (type () != Type::Object)
return {};
return Object (json_value_as_object (jsonValue ()));
}
//------------------------------------------------------------------------
inline std::optional<Array> Value::asArray () const
{
if (type () != Type::Array)
return {};
return Array (json_value_as_array (jsonValue ()));
}
//------------------------------------------------------------------------
inline std::optional<String> Value::asString () const
{
if (type () != Type::String)
return {};
return String (json_value_as_string (jsonValue ()));
}
//------------------------------------------------------------------------
inline std::optional<Number> Value::asNumber () const
{
if (type () != Type::Number)
return {};
return Number (json_value_as_number (jsonValue ()));
}
//------------------------------------------------------------------------
inline std::optional<Boolean> Value::asBoolean () const
{
if (type () == Type::True || type () == Type::False)
return Boolean (jsonValue ()->type);
return {};
}
//------------------------------------------------------------------------
inline std::optional<std::nullptr_t> Value::asNull () const
{
if (type () != Type::Null)
return {};
return nullptr;
}
//------------------------------------------------------------------------
inline Type Value::type () const
{
switch (jsonValue ()->type)
{
case json_type_string: return Type::String;
case json_type_number: return Type::Number;
case json_type_object: return Type::Object;
case json_type_array: return Type::Array;
case json_type_true: return Type::True;
case json_type_false: return Type::False;
case json_type_null: return Type::Null;
}
assert (false);
return Type::Null;
}
//------------------------------------------------------------------------
inline Value::VariantT Value::asVariant () const
{
switch (type ())
{
case Type::String: return *asString ();
case Type::Number: return *asNumber ();
case Type::Object: return *asObject ();
case Type::Array: return *asArray ();
case Type::True: return *asBoolean ();
case Type::False: return *asBoolean ();
case Type::Null: return *asNull ();
}
assert (false);
return nullptr;
}
//------------------------------------------------------------------------
inline SourceLocation Value::getSourceLocation () const
{
auto exValue = reinterpret_cast<json_value_ex_s*> (jsonValue ());
return {exValue->offset, exValue->line_no, exValue->row_no};
}
//------------------------------------------------------------------------
inline SourceLocation String::getSourceLocation () const
{
auto exValue = reinterpret_cast<json_string_ex_s*> (jsonValue ());
return {exValue->offset, exValue->line_no, exValue->row_no};
}
//------------------------------------------------------------------------
inline std::optional<int64_t> Number::getInteger () const
{
#if defined(SMTG_HAS_CHARCONV)
int64_t result {0};
auto res = std::from_chars (jsonValue ()->number,
jsonValue ()->number + jsonValue ()->number_size, result);
if (res.ec == std::errc ())
return result;
return {};
#else
int64_t result {0};
std::string str (jsonValue ()->number, jsonValue ()->number + jsonValue ()->number_size);
if (std::sscanf (str.data (), "%lld", &result) != 1)
return {};
return result;
#endif
}
//------------------------------------------------------------------------
inline std::optional<double> Number::getDouble () const
{
#if 1 // clang still has no floting point from_chars version
size_t ctrl {0};
auto result = std::stod (std::string (jsonValue ()->number, jsonValue ()->number_size), &ctrl);
if (ctrl > 0)
return result;
#else
double result {0.};
auto res = std::from_chars (jsonValue ()->number,
jsonValue ()->number + jsonValue ()->number_size, result);
if (res.ec == std::errc ())
return result;
#endif
return {};
}
//------------------------------------------------------------------------
inline std::string_view errorToString (json_parse_error_e error)
{
switch (error)
{
case json_parse_error_e::json_parse_error_none: return {};
case json_parse_error_e::json_parse_error_expected_comma_or_closing_bracket:
return "json_parse_error_expected_comma_or_closing_bracket";
case json_parse_error_e::json_parse_error_expected_colon:
return "json_parse_error_expected_colon";
case json_parse_error_e::json_parse_error_expected_opening_quote:
return "json_parse_error_expected_opening_quote";
case json_parse_error_e::json_parse_error_invalid_string_escape_sequence:
return "json_parse_error_invalid_string_escape_sequence";
case json_parse_error_e::json_parse_error_invalid_number_format:
return "json_parse_error_invalid_number_format";
case json_parse_error_e::json_parse_error_invalid_value:
return "json_parse_error_invalid_value";
case json_parse_error_e::json_parse_error_premature_end_of_buffer:
return "json_parse_error_premature_end_of_buffer";
case json_parse_error_e::json_parse_error_invalid_string:
return "json_parse_error_invalid_string";
case json_parse_error_e::json_parse_error_allocator_failed:
return "json_parse_error_allocator_failed";
case json_parse_error_e::json_parse_error_unexpected_trailing_characters:
return "json_parse_error_unexpected_trailing_characters";
case json_parse_error_e::json_parse_error_unknown: return "json_parse_error_unknown";
}
return {};
}
//------------------------------------------------------------------------
} // JSON
@@ -0,0 +1,80 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
// Flags : clang-format SMTGSequencer
//
// Category : moduleinfo
// Filename : public.sdk/source/vst/moduleinfo/moduleinfo.h
// Created by : Steinberg, 12/2021
// Description :
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include <cstdint>
#include <string>
#include <vector>
//------------------------------------------------------------------------
namespace Steinberg {
//------------------------------------------------------------------------
struct ModuleInfo
{
//------------------------------------------------------------------------
struct FactoryInfo
{
std::string vendor;
std::string url;
std::string email;
int32_t flags {0};
};
//------------------------------------------------------------------------
struct Snapshot
{
double scaleFactor {1.};
std::string path;
};
using SnapshotList = std::vector<Snapshot>;
//------------------------------------------------------------------------
struct ClassInfo
{
std::string cid;
std::string category;
std::string name;
std::string vendor;
std::string version;
std::string sdkVersion;
std::vector<std::string> subCategories;
SnapshotList snapshots;
int32_t cardinality {0x7FFFFFFF};
uint32_t flags {0};
};
//------------------------------------------------------------------------
struct Compatibility
{
std::string newCID;
std::vector<std::string> oldCID;
};
using ClassList = std::vector<ClassInfo>;
using CompatibilityList = std::vector<Compatibility>;
std::string name;
std::string version;
FactoryInfo factoryInfo;
ClassList classes;
CompatibilityList compatibility;
};
//------------------------------------------------------------------------
} // Steinberg
@@ -0,0 +1,289 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
// Flags : clang-format SMTGSequencer
//
// Category : moduleinfo
// Filename : public.sdk/source/vst/moduleinfo/moduleinfocreator.cpp
// Created by : Steinberg, 12/2021
// Description : utility functions to create moduleinfo json files
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "moduleinfocreator.h"
#include "jsoncxx.h"
#include <algorithm>
#include <stdexcept>
#include <string>
//------------------------------------------------------------------------
namespace Steinberg::ModuleInfoLib {
using namespace VST3;
namespace {
//------------------------------------------------------------------------
struct JSON5Writer
{
private:
std::ostream& stream;
bool beautify;
bool lastIsComma {false};
int32_t intend {0};
void doBeautify ()
{
if (beautify)
{
stream << '\n';
for (int i = 0; i < intend; ++i)
stream << " ";
}
}
void writeComma ()
{
if (lastIsComma)
return;
stream << ",";
lastIsComma = true;
}
void startObject ()
{
stream << "{";
++intend;
lastIsComma = false;
}
void endObject ()
{
--intend;
doBeautify ();
stream << "}";
lastIsComma = false;
}
void startArray ()
{
stream << "[";
++intend;
lastIsComma = false;
}
void endArray ()
{
--intend;
doBeautify ();
stream << "]";
lastIsComma = false;
}
public:
JSON5Writer (std::ostream& stream, bool beautify = true) : stream (stream), beautify (beautify)
{
}
void string (std::string_view str)
{
stream << "\"" << str << "\"";
lastIsComma = false;
}
void boolean (bool val)
{
stream << (val ? "true" : "false");
lastIsComma = false;
}
template <typename ValueT>
void value (ValueT val)
{
stream << val;
lastIsComma = false;
}
template <typename Proc>
void object (Proc proc)
{
startObject ();
proc ();
endObject ();
}
template <typename Iterator, typename Proc>
void array (Iterator begin, Iterator end, Proc proc)
{
startArray ();
while (begin != end)
{
doBeautify ();
proc (begin);
++begin;
writeComma ();
}
endArray ();
}
template <typename Proc>
void keyValue (std::string_view key, Proc proc)
{
doBeautify ();
string (key);
stream << ": ";
proc ();
writeComma ();
}
};
//------------------------------------------------------------------------
void writeSnapshots (const ModuleInfo::SnapshotList& snapshots, JSON5Writer& w)
{
w.keyValue ("Snapshots", [&] () {
w.array (snapshots.begin (), snapshots.end (), [&] (const auto& el) {
w.object ([&] () {
w.keyValue ("Scale Factor", [&] () { w.value (el->scaleFactor); });
w.keyValue ("Path", [&] () { w.string (el->path); });
});
});
});
}
//------------------------------------------------------------------------
void writeClassInfo (const ModuleInfo::ClassInfo& cls, JSON5Writer& w)
{
w.keyValue ("CID", [&] () { w.string (cls.cid); });
w.keyValue ("Category", [&] () { w.string (cls.category); });
w.keyValue ("Name", [&] () { w.string (cls.name); });
w.keyValue ("Vendor", [&] () { w.string (cls.vendor); });
w.keyValue ("Version", [&] () { w.string (cls.version); });
w.keyValue ("SDKVersion", [&] () { w.string (cls.sdkVersion); });
const auto& sc = cls.subCategories;
if (!sc.empty ())
{
w.keyValue ("Sub Categories", [&] () {
w.array (sc.begin (), sc.end (), [&] (const auto& cat) { w.string (*cat); });
});
}
w.keyValue ("Class Flags", [&] () { w.value (cls.flags); });
w.keyValue ("Cardinality", [&] () { w.value (cls.cardinality); });
writeSnapshots (cls.snapshots, w);
}
//------------------------------------------------------------------------
void writePluginCompatibility (const ModuleInfo::CompatibilityList& compat, JSON5Writer& w)
{
if (compat.empty ())
return;
w.keyValue ("Compatibility", [&] () {
w.array (compat.begin (), compat.end (), [&] (auto& el) {
w.object ([&] () {
w.keyValue ("New", [&] () { w.string (el->newCID); });
w.keyValue ("Old", [&] () {
w.array (el->oldCID.begin (), el->oldCID.end (),
[&] (auto& oldEl) { w.string (*oldEl); });
});
});
});
});
}
//------------------------------------------------------------------------
void writeFactoryInfo (const ModuleInfo::FactoryInfo& fi, JSON5Writer& w)
{
w.keyValue ("Factory Info", [&] () {
w.object ([&] () {
w.keyValue ("Vendor", [&] () { w.string (fi.vendor); });
w.keyValue ("URL", [&] () { w.string (fi.url); });
w.keyValue ("E-Mail", [&] () { w.string (fi.email); });
w.keyValue ("Flags", [&] () {
w.object ([&] () {
w.keyValue ("Unicode",
[&] () { w.boolean (fi.flags & PFactoryInfo::kUnicode); });
w.keyValue ("Classes Discardable", [&] () {
w.boolean (fi.flags & PFactoryInfo::kClassesDiscardable);
});
w.keyValue ("Component Non Discardable", [&] () {
w.boolean (fi.flags & PFactoryInfo::kComponentNonDiscardable);
});
});
});
});
});
}
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
ModuleInfo createModuleInfo (const VST3::Hosting::Module& module, bool includeDiscardableClasses)
{
ModuleInfo info;
const auto& factory = module.getFactory ();
auto factoryInfo = factory.info ();
info.name = module.getName ();
auto pos = info.name.find_last_of ('.');
if (pos != std::string::npos)
info.name.erase (pos);
info.factoryInfo.vendor = factoryInfo.vendor ();
info.factoryInfo.url = factoryInfo.url ();
info.factoryInfo.email = factoryInfo.email ();
info.factoryInfo.flags = factoryInfo.flags ();
if (factoryInfo.classesDiscardable () == false ||
(factoryInfo.classesDiscardable () && includeDiscardableClasses))
{
auto snapshots = VST3::Hosting::Module::getSnapshots (module.getPath ());
for (const auto& ci : factory.classInfos ())
{
ModuleInfo::ClassInfo classInfo;
classInfo.cid = ci.ID ().toString ();
classInfo.category = ci.category ();
classInfo.name = ci.name ();
classInfo.vendor = ci.vendor ();
classInfo.version = ci.version ();
classInfo.sdkVersion = ci.sdkVersion ();
classInfo.subCategories = ci.subCategories ();
classInfo.cardinality = ci.cardinality ();
classInfo.flags = ci.classFlags ();
auto snapshotIt = std::find_if (snapshots.begin (), snapshots.end (),
[&] (const auto& el) { return el.uid == ci.ID (); });
if (snapshotIt != snapshots.end ())
{
for (auto& s : snapshotIt->images)
{
std::string_view path (s.path);
if (path.find (module.getPath ()) == 0)
path.remove_prefix (module.getPath ().size () + 1);
classInfo.snapshots.emplace_back (
ModuleInfo::Snapshot {s.scaleFactor, {path.data (), path.size ()}});
}
snapshots.erase (snapshotIt);
}
info.classes.emplace_back (std::move (classInfo));
}
}
return info;
}
//------------------------------------------------------------------------
void outputJson (const ModuleInfo& info, std::ostream& output)
{
JSON5Writer w (output);
w.object ([&] () {
w.keyValue ("Name", [&] () { w.string (info.name); });
w.keyValue ("Version", [&] () { w.string (info.version); });
writeFactoryInfo (info.factoryInfo, w);
writePluginCompatibility (info.compatibility, w);
w.keyValue ("Classes", [&] () {
w.array (info.classes.begin (), info.classes.end (),
[&] (const auto& cls) { w.object ([&] () { writeClassInfo (*cls, w); }); });
});
});
}
//------------------------------------------------------------------------
} // Steinberg::ModuleInfoLib
@@ -0,0 +1,47 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
// Flags : clang-format SMTGSequencer
//
// Category : moduleinfo
// Filename : public.sdk/source/vst/moduleinfo/moduleinfocreator.h
// Created by : Steinberg, 12/2021
// Description : utility functions to create moduleinfo json files
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "moduleinfo.h"
#include "public.sdk/source/vst/hosting/module.h"
#include <iostream>
#include <optional>
#include <string_view>
//------------------------------------------------------------------------
namespace Steinberg::ModuleInfoLib {
//------------------------------------------------------------------------
/** create a ModuleInfo from a module
*
* @param module module to create the module info from
* @param includeDiscardableClasses if true adds the current available classes to the module info
* @return a ModuleInfo struct with the classes and factory info of the module
*/
ModuleInfo createModuleInfo (const VST3::Hosting::Module& module, bool includeDiscardableClasses);
//------------------------------------------------------------------------
/** output the ModuleInfo as json to the stream
*
* @param info module info
* @param output output stream
*/
void outputJson (const ModuleInfo& info, std::ostream& output);
//------------------------------------------------------------------------
} // Steinberg::ModuelInfoLib
@@ -0,0 +1,517 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
// Flags : clang-format SMTGSequencer
//
// Category : moduleinfo
// Filename : public.sdk/source/vst/moduleinfo/moduleinfoparser.cpp
// Created by : Steinberg, 01/2022
// Description : utility functions to parse moduleinfo json files
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#include "moduleinfoparser.h"
#include "jsoncxx.h"
#include "pluginterfaces/base/ipluginbase.h"
#include <limits>
#include <stdexcept>
//------------------------------------------------------------------------
namespace Steinberg::ModuleInfoLib {
namespace {
//------------------------------------------------------------------------
void printJsonParseError (json_parse_result_s& parseResult, std::ostream& errorOut)
{
errorOut << "error : "
<< JSON::errorToString (static_cast<json_parse_error_e> (parseResult.error)) << '\n';
errorOut << "offset : " << parseResult.error_offset << '\n';
errorOut << "line no: " << parseResult.error_line_no << '\n';
errorOut << "row no : " << parseResult.error_row_no << '\n';
}
//------------------------------------------------------------------------
struct parse_error : std::exception
{
parse_error (const std::string& str, const JSON::Value& value)
: str (str), location (value.getSourceLocation ())
{
addLocation (location);
}
parse_error (const std::string& str, const JSON::String& value)
: str (str), location (value.getSourceLocation ())
{
addLocation (location);
}
const char* what () const noexcept override { return str.data (); }
private:
void addLocation (const JSON::SourceLocation& loc)
{
str += '\n';
str += "offset:";
str += std::to_string (loc.offset);
str += '\n';
str += "line:";
str += std::to_string (loc.line);
str += '\n';
str += "row:";
str += std::to_string (loc.row);
str += '\n';
}
std::string str;
JSON::SourceLocation location;
};
//------------------------------------------------------------------------
struct ModuleInfoJsonParser
{
ModuleInfoJsonParser () = default;
std::string_view getText (const JSON::Value& value) const
{
if (auto str = value.asString ())
return str->text ();
throw parse_error ("Expect a String here", value);
}
template <typename T>
T getInteger (const JSON::Value& value) const
{
if (auto number = value.asNumber ())
{
if (auto result = number->getInteger ())
{
if (result > static_cast<int64_t> (std::numeric_limits<T>::max ()) ||
result < static_cast<int64_t> (std::numeric_limits<T>::min ()))
throw parse_error ("Value is out of range here", value);
return static_cast<T> (*result);
}
throw parse_error ("Expect an Integer here", value);
}
throw parse_error ("Expect a Number here", value);
}
double getDouble (const JSON::Value& value) const
{
if (auto number = value.asNumber ())
{
if (auto result = number->getDouble ())
return *result;
throw parse_error ("Expect a Double here", value);
}
throw parse_error ("Expect a Number here", value);
}
void parseFactoryInfo (const JSON::Value& value)
{
enum ParsedBits
{
Vendor = 1 << 0,
URL = 1 << 1,
EMail = 1 << 2,
Flags = 1 << 3,
};
uint32_t parsed {0};
if (auto obj = value.asObject ())
{
for (const auto& el : *obj)
{
auto elementName = el.name ().text ();
if (elementName == "Vendor")
{
if (parsed & ParsedBits::Vendor)
throw parse_error ("Only one 'Vendor' key allowed", el.name ());
parsed |= ParsedBits::Vendor;
info.factoryInfo.vendor = getText (el.value ());
}
else if (elementName == "URL")
{
if (parsed & ParsedBits::URL)
throw parse_error ("Only one 'URL' key allowed", el.name ());
parsed |= ParsedBits::URL;
info.factoryInfo.url = getText (el.value ());
}
else if (elementName == "E-Mail")
{
if (parsed & ParsedBits::EMail)
throw parse_error ("Only one 'E-Mail' key allowed", el.name ());
parsed |= ParsedBits::EMail;
info.factoryInfo.email = getText (el.value ());
}
else if (elementName == "Flags")
{
if (parsed & ParsedBits::Flags)
throw parse_error ("Only one 'Flags' key allowed", el.name ());
auto flags = el.value ().asObject ();
if (!flags)
throw parse_error ("Expect 'Flags' to be a JSON Object", el.name ());
for (const auto& flag : *flags)
{
auto flagName = flag.name ().text ();
auto flagValue = flag.value ().asBoolean ();
if (!flagValue)
throw parse_error ("Flag must be a boolean", flag.value ());
if (flagName == "Classes Discardable")
{
if (*flagValue)
info.factoryInfo.flags |= PFactoryInfo::kClassesDiscardable;
}
else if (flagName == "Component Non Discardable")
{
if (*flagValue)
info.factoryInfo.flags |= PFactoryInfo::kComponentNonDiscardable;
}
else if (flagName == "Unicode")
{
if (*flagValue)
info.factoryInfo.flags |= PFactoryInfo::kUnicode;
}
else
throw parse_error ("Unknown flag", flag.name ());
}
parsed |= ParsedBits::Flags;
}
}
}
if (!(parsed & ParsedBits::Vendor))
throw std::logic_error ("Missing 'Vendor' in Factory Info");
if (!(parsed & ParsedBits::URL))
throw std::logic_error ("Missing 'URL' in Factory Info");
if (!(parsed & ParsedBits::EMail))
throw std::logic_error ("Missing 'EMail' in Factory Info");
if (!(parsed & ParsedBits::Flags))
throw std::logic_error ("Missing 'Flags' in Factory Info");
}
void parseClasses (const JSON::Value& value)
{
enum ParsedBits
{
CID = 1 << 0,
Category = 1 << 1,
Name = 1 << 2,
Vendor = 1 << 3,
Version = 1 << 4,
SDKVersion = 1 << 5,
SubCategories = 1 << 6,
ClassFlags = 1 << 7,
Snapshots = 1 << 8,
Cardinality = 1 << 9,
};
auto array = value.asArray ();
if (!array)
throw parse_error ("Expect Classes Array", value);
for (const auto& classInfoEl : *array)
{
auto classInfo = classInfoEl.value ().asObject ();
if (!classInfo)
throw parse_error ("Expect Class Object", classInfoEl.value ());
ModuleInfo::ClassInfo ci {};
uint32_t parsed {0};
for (const auto& el : *classInfo)
{
auto elementName = el.name ().text ();
if (elementName == "CID")
{
if (parsed & ParsedBits::CID)
throw parse_error ("Only one 'CID' key allowed", el.name ());
ci.cid = getText (el.value ());
parsed |= ParsedBits::CID;
}
else if (elementName == "Category")
{
if (parsed & ParsedBits::Category)
throw parse_error ("Only one 'Category' key allowed", el.name ());
ci.category = getText (el.value ());
parsed |= ParsedBits::Category;
}
else if (elementName == "Name")
{
if (parsed & ParsedBits::Name)
throw parse_error ("Only one 'Name' key allowed", el.name ());
ci.name = getText (el.value ());
parsed |= ParsedBits::Name;
}
else if (elementName == "Vendor")
{
if (parsed & ParsedBits::Vendor)
throw parse_error ("Only one 'Vendor' key allowed", el.name ());
ci.vendor = getText (el.value ());
parsed |= ParsedBits::Vendor;
}
else if (elementName == "Version")
{
if (parsed & ParsedBits::Version)
throw parse_error ("Only one 'Version' key allowed", el.name ());
ci.version = getText (el.value ());
parsed |= ParsedBits::Version;
}
else if (elementName == "SDKVersion")
{
if (parsed & ParsedBits::SDKVersion)
throw parse_error ("Only one 'SDKVersion' key allowed", el.name ());
ci.sdkVersion = getText (el.value ());
parsed |= ParsedBits::SDKVersion;
}
else if (elementName == "Sub Categories")
{
if (parsed & ParsedBits::SubCategories)
throw parse_error ("Only one 'Sub Categories' key allowed", el.name ());
auto subCatArr = el.value ().asArray ();
if (!subCatArr)
throw parse_error ("Expect Array here", el.value ());
for (const auto& catEl : *subCatArr)
{
auto cat = getText (catEl.value ());
ci.subCategories.emplace_back (cat);
}
parsed |= ParsedBits::SubCategories;
}
else if (elementName == "Class Flags")
{
if (parsed & ParsedBits::ClassFlags)
throw parse_error ("Only one 'Class Flags' key allowed", el.name ());
ci.flags = getInteger<uint32_t> (el.value ());
parsed |= ParsedBits::ClassFlags;
}
else if (elementName == "Cardinality")
{
if (parsed & ParsedBits::Cardinality)
throw parse_error ("Only one 'Cardinality' key allowed", el.name ());
ci.cardinality = getInteger<int32_t> (el.value ());
parsed |= ParsedBits::Cardinality;
}
else if (elementName == "Snapshots")
{
if (parsed & ParsedBits::Snapshots)
throw parse_error ("Only one 'Snapshots' key allowed", el.name ());
auto snapArr = el.value ().asArray ();
if (!snapArr)
throw parse_error ("Expect Array here", el.value ());
for (const auto& snapEl : *snapArr)
{
auto snap = snapEl.value ().asObject ();
if (!snap)
throw parse_error ("Expect Object here", snapEl.value ());
ModuleInfo::Snapshot snapshot;
for (const auto& spEl : *snap)
{
auto spElName = spEl.name ().text ();
if (spElName == "Path")
snapshot.path = getText (spEl.value ());
else if (spElName == "Scale Factor")
snapshot.scaleFactor = getDouble (spEl.value ());
else
throw parse_error ("Unexpected key", spEl.name ());
}
if (snapshot.scaleFactor == 0. || snapshot.path.empty ())
throw parse_error ("Missing Snapshot keys", snapEl.value ());
ci.snapshots.emplace_back (std::move (snapshot));
}
parsed |= ParsedBits::Snapshots;
}
else
throw parse_error ("Unexpected key", el.name ());
}
if (!(parsed & ParsedBits::CID))
throw parse_error ("'CID' key missing", classInfoEl.value ());
if (!(parsed & ParsedBits::Category))
throw parse_error ("'Category' key missing", classInfoEl.value ());
if (!(parsed & ParsedBits::Name))
throw parse_error ("'Name' key missing", classInfoEl.value ());
if (!(parsed & ParsedBits::Vendor))
throw parse_error ("'Vendor' key missing", classInfoEl.value ());
if (!(parsed & ParsedBits::Version))
throw parse_error ("'Version' key missing", classInfoEl.value ());
if (!(parsed & ParsedBits::SDKVersion))
throw parse_error ("'SDK Version' key missing", classInfoEl.value ());
if (!(parsed & ParsedBits::ClassFlags))
throw parse_error ("'Class Flags' key missing", classInfoEl.value ());
if (!(parsed & ParsedBits::Cardinality))
throw parse_error ("'Cardinality' key missing", classInfoEl.value ());
info.classes.emplace_back (std::move (ci));
}
}
void parseCompatibility (const JSON::Value& value)
{
auto arr = value.asArray ();
if (!arr)
throw parse_error ("Expect Array here", value);
for (const auto& el : *arr)
{
auto obj = el.value ().asObject ();
if (!obj)
throw parse_error ("Expect Object here", el.value ());
ModuleInfo::Compatibility compat;
for (const auto& objEl : *obj)
{
auto elementName = objEl.name ().text ();
if (elementName == "New")
compat.newCID = getText (objEl.value ());
else if (elementName == "Old")
{
auto oldElArr = objEl.value ().asArray ();
if (!oldElArr)
throw parse_error ("Expect Array here", objEl.value ());
for (const auto& old : *oldElArr)
{
compat.oldCID.emplace_back (getText (old.value ()));
}
}
}
if (compat.newCID.empty ())
throw parse_error ("Expect New CID here", el.value ());
if (compat.oldCID.empty ())
throw parse_error ("Expect Old CID here", el.value ());
info.compatibility.emplace_back (std::move (compat));
}
}
void parse (const JSON::Document& doc)
{
auto docObj = doc.asObject ();
if (!docObj)
throw parse_error ("Unexpected", doc);
enum ParsedBits
{
Name = 1 << 0,
Version = 1 << 1,
FactoryInfo = 1 << 2,
Compatibility = 1 << 3,
Classes = 1 << 4,
};
uint32_t parsed {0};
for (const auto& el : *docObj)
{
auto elementName = el.name ().text ();
if (elementName == "Name")
{
if (parsed & ParsedBits::Name)
throw parse_error ("Only one 'Name' key allowed", el.name ());
parsed |= ParsedBits::Name;
info.name = getText (el.value ());
}
else if (elementName == "Version")
{
if (parsed & ParsedBits::Version)
throw parse_error ("Only one 'Version' key allowed", el.name ());
parsed |= ParsedBits::Version;
info.version = getText (el.value ());
}
else if (elementName == "Factory Info")
{
if (parsed & ParsedBits::FactoryInfo)
throw parse_error ("Only one 'Factory Info' key allowed", el.name ());
parseFactoryInfo (el.value ());
parsed |= ParsedBits::FactoryInfo;
}
else if (elementName == "Compatibility")
{
if (parsed & ParsedBits::Compatibility)
throw parse_error ("Only one 'Compatibility' key allowed", el.name ());
parseCompatibility (el.value ());
parsed |= ParsedBits::Compatibility;
}
else if (elementName == "Classes")
{
if (parsed & ParsedBits::Classes)
throw parse_error ("Only one 'Classes' key allowed", el.name ());
parseClasses (el.value ());
parsed |= ParsedBits::Classes;
}
else
{
throw parse_error ("Unexpected JSON Token", el.name ());
}
}
if (!(parsed & ParsedBits::Name))
throw std::logic_error ("'Name' key missing");
if (!(parsed & ParsedBits::Version))
throw std::logic_error ("'Version' key missing");
if (!(parsed & ParsedBits::FactoryInfo))
throw std::logic_error ("'Factory Info' key missing");
if (!(parsed & ParsedBits::Classes))
throw std::logic_error ("'Classes' key missing");
}
ModuleInfo&& takeInfo () { return std::move (info); }
private:
ModuleInfo info;
};
//------------------------------------------------------------------------
} // anonymous
//------------------------------------------------------------------------
std::optional<ModuleInfo> parseJson (std::string_view jsonData, std::ostream* optErrorOutput)
{
auto docVar = JSON::Document::parse (jsonData);
if (auto res = std::get_if<json_parse_result_s> (&docVar))
{
if (optErrorOutput)
printJsonParseError (*res, *optErrorOutput);
return {};
}
auto doc = std::get_if<JSON::Document> (&docVar);
assert (doc);
try
{
ModuleInfoJsonParser parser;
parser.parse (*doc);
return parser.takeInfo ();
}
catch (std::exception& error)
{
if (optErrorOutput)
*optErrorOutput << error.what () << '\n';
return {};
}
// unreachable
}
//------------------------------------------------------------------------
std::optional<ModuleInfo::CompatibilityList> parseCompatibilityJson (std::string_view jsonData,
std::ostream* optErrorOutput)
{
auto docVar = JSON::Document::parse (jsonData);
if (auto res = std::get_if<json_parse_result_s> (&docVar))
{
if (optErrorOutput)
printJsonParseError (*res, *optErrorOutput);
return {};
}
auto doc = std::get_if<JSON::Document> (&docVar);
assert (doc);
try
{
ModuleInfoJsonParser parser;
parser.parseCompatibility (*doc);
return parser.takeInfo ().compatibility;
}
catch (std::exception& error)
{
if (optErrorOutput)
*optErrorOutput << error.what () << '\n';
return {};
}
// unreachable
}
//------------------------------------------------------------------------
} // Steinberg::ModuelInfoLib
@@ -0,0 +1,48 @@
//-----------------------------------------------------------------------------
// Project : VST SDK
// Flags : clang-format SMTGSequencer
//
// Category : moduleinfo
// Filename : public.sdk/source/vst/moduleinfo/moduleinfoparser.h
// Created by : Steinberg, 01/2022
// Description : utility functions to parse moduleinfo json files
//
//-----------------------------------------------------------------------------
// This file is part of a Steinberg SDK. It is subject to the license terms
// in the LICENSE file found in the top-level directory of this distribution
// and at www.steinberg.net/sdklicenses.
// No part of the SDK, including this file, may be copied, modified, propagated,
// or distributed except according to the terms contained in the LICENSE file.
//-----------------------------------------------------------------------------
#pragma once
#include "moduleinfo.h"
#include <iostream>
#include <optional>
#include <string_view>
//------------------------------------------------------------------------
namespace Steinberg::ModuleInfoLib {
//------------------------------------------------------------------------
/** parse a json formatted string to a ModuleInfo struct
*
* @param jsonData a string view to a json formatted string
* @param optErrorOutput optional error output stream where to print parse error
* @return ModuleInfo if parsing succeeded
*/
std::optional<ModuleInfo> parseJson (std::string_view jsonData, std::ostream* optErrorOutput);
//------------------------------------------------------------------------
/** parse a json formatted string to a ModuleInfo::CompatibilityList
*
* @param jsonData a string view to a json formatted string
* @param optErrorOutput optional error output stream where to print parse error
* @return ModuleInfo::CompatibilityList if parsing succeeded
*/
std::optional<ModuleInfo::CompatibilityList> parseCompatibilityJson (std::string_view jsonData,
std::ostream* optErrorOutput);
//------------------------------------------------------------------------
} // Steinberg::ModuelInfoLib