2021-02-20 22:33:07 +00:00
|
|
|
#!/usr/bin/env dub
|
|
|
|
/+ dub.sdl:
|
|
|
|
name "openapigenerator.d"
|
|
|
|
dependency "dyaml" version="~>0.8.0"
|
2021-03-19 22:01:29 +00:00
|
|
|
dependency "handlebars" version="~>0.2.2"
|
|
|
|
stringImportPaths "codegen"
|
2021-02-20 22:33:07 +00:00
|
|
|
+/
|
2021-02-20 23:26:18 +00:00
|
|
|
|
|
|
|
// The following copyright string also applies to this file.
|
|
|
|
string COPYRIGHT = q"EOL
|
|
|
|
/*
|
|
|
|
* Sailfin: a Jellyfin client written using Qt
|
|
|
|
* Copyright (C) 2021 Chris Josten and the Sailfin Contributors.
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*/
|
|
|
|
EOL";
|
2021-02-20 22:33:07 +00:00
|
|
|
import std.algorithm;
|
|
|
|
import std.array;
|
|
|
|
import std.file : mkdirRecurse;
|
|
|
|
import std.exception;
|
|
|
|
import std.path : buildPath, dirSeparator;
|
|
|
|
import std.range;
|
|
|
|
import std.string;
|
|
|
|
import std.stdio;
|
|
|
|
import std.uni;
|
|
|
|
|
|
|
|
import dyaml;
|
2021-03-19 22:01:29 +00:00
|
|
|
import handlebars.tpl;
|
2021-02-20 22:33:07 +00:00
|
|
|
|
2021-02-20 23:26:18 +00:00
|
|
|
static this() {
|
|
|
|
COPYRIGHT ~= q"EOS
|
|
|
|
/*
|
|
|
|
* WARNING: THIS IS AN AUTOMATICALLY GENERATED FILE! PLEASE DO NOT EDIT THIS, AS YOUR EDITS WILL GET
|
|
|
|
* OVERWRITTEN AT SOME POINT!
|
|
|
|
*
|
|
|
|
* If there is a bug in this file, please fix the code generator used to generate this file found in
|
|
|
|
* core/openapigenerator.d.
|
|
|
|
*
|
|
|
|
* This file is generated based on Jellyfin's OpenAPI description, "openapi.json". Please update that
|
|
|
|
* file with a newer file if needed instead of manually updating the files.
|
|
|
|
*/
|
|
|
|
EOS";
|
|
|
|
}
|
|
|
|
|
2021-02-20 22:33:07 +00:00
|
|
|
// CODE GENERATION SETTINGS
|
|
|
|
|
2021-02-20 23:26:18 +00:00
|
|
|
// File name of the CMake file this generated should generate.
|
|
|
|
string CMAKE_INCLUDE_FILE = "GeneratedSources.cmake";
|
|
|
|
string CMAKE_VAR_PREFIX = "openapi";
|
|
|
|
|
2021-03-05 14:34:10 +00:00
|
|
|
|
|
|
|
string INCLUDE_PREFIX = "JellyfinQt";
|
|
|
|
string SRC_PREFIX = "";
|
|
|
|
|
2021-03-20 02:30:50 +00:00
|
|
|
string MODEL_FOLDER = "DTO";
|
2021-03-05 14:34:10 +00:00
|
|
|
string SUPPORT_FOLDER = "support";
|
2021-02-20 23:26:18 +00:00
|
|
|
|
2021-02-21 04:02:05 +00:00
|
|
|
string[string] compatAliases;
|
2021-03-05 14:34:10 +00:00
|
|
|
string[string] memberAliases;
|
2021-02-21 04:02:05 +00:00
|
|
|
|
|
|
|
static this() {
|
|
|
|
compatAliases["BaseItemDto"] = "Item";
|
|
|
|
compatAliases["UserDto"] = "User";
|
|
|
|
compatAliases["UserItemDataDto"] = "UserData";
|
2021-03-05 14:34:10 +00:00
|
|
|
|
|
|
|
memberAliases["id"] = "jellyfinId";
|
2021-02-21 04:02:05 +00:00
|
|
|
}
|
|
|
|
|
2021-02-20 23:26:18 +00:00
|
|
|
CasePolicy OPENAPI_CASING = CasePolicy.PASCAL;
|
2021-03-20 02:30:50 +00:00
|
|
|
static immutable string[1] CPP_NAMESPACE = ["Jellyfin"];
|
|
|
|
static immutable string[2] CPP_NAMESPACE_DTO = ["Jellyfin", "DTO"];
|
|
|
|
static immutable string[2] CPP_NAMESPACE_SUPPORT = ["Jellyfin", "Support"];
|
|
|
|
|
2021-02-20 22:33:07 +00:00
|
|
|
CasePolicy CPP_FILENAME_CASING = CasePolicy.LOWER;
|
|
|
|
CasePolicy CPP_CLASS_CASING = CasePolicy.PASCAL;
|
|
|
|
CasePolicy CPP_CLASS_MEMBER_CASING = CasePolicy.CAMEL;
|
|
|
|
// Prefix for class members.
|
|
|
|
string CPP_CLASS_MEMBER_PREFIX = "m_";
|
|
|
|
CasePolicy CPP_CLASS_METHOD_CASING = CasePolicy.CAMEL;
|
|
|
|
bool GENERATE_PROPERTIES = true;
|
|
|
|
|
|
|
|
string outputDirectory = "generated";
|
2021-02-20 23:26:18 +00:00
|
|
|
// END CODE GENERATION SETTINGS.
|
|
|
|
|
|
|
|
// Implementation
|
2021-02-20 22:33:07 +00:00
|
|
|
|
|
|
|
enum CasePolicy {
|
|
|
|
KEEP, // Do not modify
|
|
|
|
PASCAL, // PascalCase
|
|
|
|
CAMEL, // camelCase
|
|
|
|
SNAKE, // snake_case
|
|
|
|
SCREAMING_SNAKE, // SCREAMING_SNAKE_CASE
|
|
|
|
LOWER, // lowercase
|
|
|
|
UPPER // UPPERCASE
|
|
|
|
}
|
|
|
|
|
|
|
|
string USAGE = "USAGE: %s <openapi scheme>";
|
|
|
|
|
|
|
|
class CLIArgumentException : Exception {
|
|
|
|
mixin basicExceptionCtors;
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(string[] args) {
|
|
|
|
try {
|
|
|
|
realMain(args);
|
|
|
|
} catch (CLIArgumentException e) {
|
|
|
|
stderr.writeln(e.message);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void realMain(string[] args) {
|
|
|
|
enforce!CLIArgumentException(args.length >= 2, USAGE.format(args[0]));
|
|
|
|
string schemeFile = args[1];
|
|
|
|
|
|
|
|
if (args.length >= 3) outputDirectory = args[2];
|
2021-03-05 14:34:10 +00:00
|
|
|
mkdirRecurse(buildPath(outputDirectory, "include", INCLUDE_PREFIX, MODEL_FOLDER));
|
|
|
|
mkdirRecurse(buildPath(outputDirectory, "src", SRC_PREFIX, MODEL_FOLDER));
|
2021-02-20 22:33:07 +00:00
|
|
|
|
|
|
|
Node root = Loader.fromFile(schemeFile).load();
|
2021-02-20 23:26:18 +00:00
|
|
|
Appender!(string[]) headerFiles, implementationFiles;
|
2021-02-21 04:02:05 +00:00
|
|
|
foreach(string key, ref const Node scheme; root["components"]["schemas"]) {
|
2021-02-20 22:33:07 +00:00
|
|
|
generateFileForSchema(key, scheme, root["components"]["schemas"]);
|
2021-02-20 23:26:18 +00:00
|
|
|
|
|
|
|
string fileBase = key.applyCasePolicy(OPENAPI_CASING, CPP_FILENAME_CASING);
|
2021-03-05 14:34:10 +00:00
|
|
|
headerFiles ~= [buildPath(outputDirectory, "include", INCLUDE_PREFIX, MODEL_FOLDER, fileBase ~ ".h")];
|
|
|
|
implementationFiles ~= [buildPath(outputDirectory, "src", SRC_PREFIX, MODEL_FOLDER, fileBase ~ ".cpp")];
|
2021-02-20 23:26:18 +00:00
|
|
|
}
|
2021-02-21 04:02:05 +00:00
|
|
|
foreach(string original, string compatAlias; compatAliases) {
|
|
|
|
writeCompatAliasFile(original, compatAlias);
|
|
|
|
}
|
2021-02-20 23:26:18 +00:00
|
|
|
writeCMakeFile(headerFiles[], implementationFiles[]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void writeCMakeFile(string[] headerFiles, string[] implementationFiles) {
|
2021-02-21 04:02:05 +00:00
|
|
|
File output = File(buildPath(outputDirectory, CMAKE_INCLUDE_FILE), "w+");
|
2021-02-20 23:26:18 +00:00
|
|
|
output.writeln("cmake_minimum_required(VERSION 3.0)");
|
|
|
|
// Peek laziness: wrapping a C++ comment inside a CMake block comment because I couldn't be
|
|
|
|
// donkey'd to do otherwise.
|
|
|
|
output.writeln("#[[");
|
|
|
|
output.writeln(COPYRIGHT);
|
|
|
|
output.writeln("]]");
|
|
|
|
|
|
|
|
output.writef("set(%s_HEADERS", CMAKE_VAR_PREFIX);
|
|
|
|
foreach (headerFile; headerFiles) {
|
|
|
|
output.writeln();
|
|
|
|
output.writef("\t%s", headerFile);
|
2021-02-20 22:33:07 +00:00
|
|
|
}
|
2021-02-21 04:02:05 +00:00
|
|
|
output.writeln(")");
|
2021-02-20 23:26:18 +00:00
|
|
|
output.writeln();
|
|
|
|
|
|
|
|
output.writef("set(%s_SOURCES", CMAKE_VAR_PREFIX);
|
|
|
|
foreach (implementationFile; implementationFiles) {
|
|
|
|
output.writeln();
|
|
|
|
output.writef("\t%s", implementationFile);
|
|
|
|
}
|
2021-02-21 04:02:05 +00:00
|
|
|
output.writeln(")");
|
|
|
|
}
|
|
|
|
|
|
|
|
void writeCompatAliasFile(ref const string original, ref const string compatAlias) {
|
|
|
|
string fileBase = compatAlias.applyCasePolicy(OPENAPI_CASING, CPP_FILENAME_CASING);
|
2021-03-05 14:34:10 +00:00
|
|
|
File headerFile = File(buildPath(outputDirectory, "include", INCLUDE_PREFIX, MODEL_FOLDER, fileBase ~ ".h"), "w+");
|
|
|
|
File implementationFile = File(buildPath(outputDirectory, "src", SRC_PREFIX, MODEL_FOLDER, fileBase ~ ".cpp"), "w+");
|
2021-02-21 04:02:05 +00:00
|
|
|
|
2021-03-20 02:30:50 +00:00
|
|
|
writeHeaderPreamble(headerFile, CPP_NAMESPACE_DTO, compatAlias, [], [original]);
|
2021-02-21 04:02:05 +00:00
|
|
|
headerFile.writefln("using %s = %s;", compatAlias, original);
|
2021-03-20 02:30:50 +00:00
|
|
|
writeHeaderPostamble(headerFile, CPP_NAMESPACE_DTO, compatAlias);
|
2021-02-20 22:33:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void generateFileForSchema(ref string name, ref const Node scheme, Node allSchemas) {
|
|
|
|
string fileBase = name.applyCasePolicy(OPENAPI_CASING, CPP_FILENAME_CASING);
|
2021-03-05 14:34:10 +00:00
|
|
|
File headerFile = File(buildPath(outputDirectory, "include", INCLUDE_PREFIX, MODEL_FOLDER, fileBase ~ ".h"), "w+");
|
|
|
|
File implementationFile = File(buildPath(outputDirectory, "src", SRC_PREFIX, MODEL_FOLDER, fileBase ~ ".cpp"), "w+");
|
2021-02-20 22:33:07 +00:00
|
|
|
|
|
|
|
if ("enum" in scheme) {
|
2021-03-05 14:34:10 +00:00
|
|
|
string[3] imports = ["QJsonValue", "QObject", "QString"];
|
2021-03-20 02:30:50 +00:00
|
|
|
string[1] userImports = [buildPath(SUPPORT_FOLDER, "jsonconv.h")];
|
|
|
|
writeHeaderPreamble(headerFile, CPP_NAMESPACE_DTO, name, imports, userImports);
|
2021-02-20 22:33:07 +00:00
|
|
|
|
|
|
|
Appender!(string[]) values;
|
|
|
|
foreach (string value; scheme["enum"]) {
|
|
|
|
values ~= value;
|
|
|
|
}
|
|
|
|
writeEnumHeader(headerFile, name, values[]);
|
2021-03-20 02:30:50 +00:00
|
|
|
writeHeaderPostamble(headerFile, CPP_NAMESPACE_DTO, name);
|
2021-02-20 22:33:07 +00:00
|
|
|
|
2021-03-20 02:30:50 +00:00
|
|
|
writeImplementationPreamble(implementationFile, CPP_NAMESPACE_DTO, name);
|
2021-02-20 22:33:07 +00:00
|
|
|
writeEnumImplementation(implementationFile, name);
|
2021-03-20 02:30:50 +00:00
|
|
|
writeImplementationPostamble(implementationFile, CPP_NAMESPACE_DTO, name);
|
2021-02-20 22:33:07 +00:00
|
|
|
}
|
|
|
|
if (scheme["type"].as!string == "object" && "properties" in scheme) {
|
|
|
|
// Determine all imports
|
|
|
|
Appender!(string[]) systemImports, userImports;
|
2021-02-21 04:02:05 +00:00
|
|
|
Appender!(string[]) forwardDeclarations;
|
2021-03-20 02:30:50 +00:00
|
|
|
systemImports ~= ["optional", "QJsonObject", "QJsonValue"];
|
2021-03-05 14:34:10 +00:00
|
|
|
userImports ~= [buildPath(SUPPORT_FOLDER, "jsonconv.h")];
|
2021-02-20 22:33:07 +00:00
|
|
|
|
|
|
|
MetaTypeInfo[] usedTypes = collectTypeInfo(scheme["properties"], allSchemas);
|
|
|
|
bool importedContainers = false;
|
|
|
|
void collectImports(MetaTypeInfo type) {
|
2021-03-20 02:30:50 +00:00
|
|
|
if (type.needsPointer && !systemImports[].canFind("QSharedPointer")) {
|
|
|
|
systemImports ~= ["QSharedPointer"];
|
|
|
|
}
|
|
|
|
|
2021-02-20 22:33:07 +00:00
|
|
|
if (type.needsSystemImport && !systemImports[].canFind(type.typeName)) {
|
|
|
|
if (type.isContainer) {
|
|
|
|
if (!importedContainers) {
|
|
|
|
systemImports ~= ["QList", "QStringList"];
|
|
|
|
importedContainers = true;
|
|
|
|
}
|
|
|
|
if (type.containerType !is null) {
|
|
|
|
collectImports(type.containerType);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
systemImports ~= [type.typeName];
|
|
|
|
}
|
2021-03-20 02:30:50 +00:00
|
|
|
} else {
|
|
|
|
string userImport = buildPath(MODEL_FOLDER, type.typeName.applyCasePolicy(OPENAPI_CASING, CasePolicy.LOWER) ~ ".h");
|
|
|
|
if (type.needsLocalImport && !userImports[].canFind(userImport) && type.typeName != name) {
|
|
|
|
userImports ~= userImport;
|
2021-02-21 04:02:05 +00:00
|
|
|
}
|
2021-02-20 22:33:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
foreach (type; usedTypes) {
|
|
|
|
collectImports(type);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sort them for nicer reading
|
|
|
|
string[] sortedSystemImports = sort(systemImports[]).array;
|
|
|
|
string[] sortedUserImports = sort(userImports[]).array;
|
2021-02-21 04:02:05 +00:00
|
|
|
string[] sortedForwardDeclarations = sort(forwardDeclarations[]).array;
|
2021-02-20 22:33:07 +00:00
|
|
|
|
|
|
|
// Write implementation files
|
2021-03-20 02:30:50 +00:00
|
|
|
writeHeaderPreamble(headerFile, CPP_NAMESPACE_DTO, name, sortedSystemImports, sortedUserImports);
|
2021-02-21 04:02:05 +00:00
|
|
|
writeObjectHeader(headerFile, name, usedTypes, sortedForwardDeclarations);
|
2021-03-20 02:30:50 +00:00
|
|
|
writeHeaderPostamble(headerFile, CPP_NAMESPACE_DTO, name);
|
2021-02-20 22:33:07 +00:00
|
|
|
|
2021-03-20 02:30:50 +00:00
|
|
|
writeImplementationPreamble(implementationFile, CPP_NAMESPACE_DTO, name);
|
2021-02-20 22:33:07 +00:00
|
|
|
writeObjectImplementation(implementationFile, name, usedTypes);
|
2021-03-20 02:30:50 +00:00
|
|
|
writeImplementationPostamble(implementationFile, CPP_NAMESPACE_DTO, name);
|
2021-02-20 22:33:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Object
|
|
|
|
MetaTypeInfo[] collectTypeInfo(Node properties, Node allSchemas) {
|
|
|
|
|
|
|
|
// We need to recurse (sometimes)
|
|
|
|
MetaTypeInfo getType(string name, Node node) {
|
|
|
|
MetaTypeInfo info = new MetaTypeInfo();
|
2021-03-05 14:34:10 +00:00
|
|
|
info.originalName = name;
|
2021-02-20 22:33:07 +00:00
|
|
|
info.name = name.applyCasePolicy(OPENAPI_CASING, CPP_CLASS_MEMBER_CASING);
|
|
|
|
if ("description" in node) {
|
|
|
|
info.description = node["description"].as!string;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Special case for QML
|
2021-03-05 14:34:10 +00:00
|
|
|
info.name = memberAliases.get(info.name.toLower(), info.name);
|
2021-02-20 22:33:07 +00:00
|
|
|
|
|
|
|
if ("$ref" in node) {
|
|
|
|
string type = node["$ref"].as!string()["#/components/schemas/".length..$];
|
|
|
|
if (type in allSchemas&& "type" in allSchemas[type]
|
|
|
|
&& allSchemas[type]["type"].as!string == "object") {
|
|
|
|
info.needsPointer = true;
|
2021-03-20 02:30:50 +00:00
|
|
|
info.isTypeNullable = true;
|
|
|
|
info.typeNullableCheck = ".isNull()";
|
|
|
|
info.typeNullableSetter = ".clear()";
|
|
|
|
}
|
|
|
|
|
2021-02-20 22:33:07 +00:00
|
|
|
info.needsLocalImport = true;
|
2021-02-21 04:02:05 +00:00
|
|
|
info.typeName = type;
|
2021-02-20 22:33:07 +00:00
|
|
|
return info;
|
|
|
|
}
|
|
|
|
if (!("type" in node)) {
|
|
|
|
info.typeName = "QVariant";
|
2021-03-20 02:30:50 +00:00
|
|
|
info.isTypeNullable = true;
|
2021-02-20 22:33:07 +00:00
|
|
|
info.needsSystemImport = true;
|
2021-03-20 02:30:50 +00:00
|
|
|
info.typeNullableCheck = ".isNull()";
|
|
|
|
info.typeNullableSetter = ".clear()";
|
2021-02-20 22:33:07 +00:00
|
|
|
return info;
|
|
|
|
}
|
|
|
|
switch(node["type"].as!string) {
|
|
|
|
case "boolean":
|
|
|
|
info.typeName = "bool";
|
|
|
|
return info;
|
|
|
|
case "string":
|
|
|
|
if ("format" in node) {
|
|
|
|
switch(node["format"].as!string) {
|
|
|
|
case "date-time":
|
|
|
|
info.typeName= "QDateTime";
|
|
|
|
info.needsSystemImport = true;
|
2021-03-20 02:30:50 +00:00
|
|
|
info.isTypeNullable = true;
|
|
|
|
info.typeNullableCheck = ".isNull()";
|
|
|
|
info.typeNullableSetter = "= QDateTime()";
|
|
|
|
return info;
|
|
|
|
case "uuid":
|
|
|
|
info.typeName = "QUuid";
|
|
|
|
info.needsSystemImport = true;
|
|
|
|
info.isTypeNullable = true;
|
|
|
|
info.typeNullableCheck = ".isNull()";
|
|
|
|
info.typeNullableSetter = "= QGuid()";
|
2021-02-20 22:33:07 +00:00
|
|
|
return info;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-03-20 02:30:50 +00:00
|
|
|
info.isTypeNullable = true;
|
2021-02-20 22:33:07 +00:00
|
|
|
info.typeName= "QString";
|
|
|
|
info.needsSystemImport = true;
|
2021-03-20 02:30:50 +00:00
|
|
|
info.typeNullableCheck = ".isNull()";
|
|
|
|
info.typeNullableSetter = ".clear()";
|
2021-02-20 22:33:07 +00:00
|
|
|
return info;
|
|
|
|
case "integer":
|
|
|
|
if ("format" in node) {
|
|
|
|
info.typeName= "q" ~ node["format"].as!string;
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
goto default;
|
|
|
|
case "number":
|
|
|
|
if ("format" in node) {
|
|
|
|
switch(node["format"].as!string) {
|
|
|
|
case "float":
|
|
|
|
case "double":
|
|
|
|
info.typeName = node["format"].as!string;
|
|
|
|
return info;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
goto default;
|
|
|
|
case "object":
|
|
|
|
info.typeName = "QJsonObject"; // This'll do for now
|
|
|
|
return info;
|
|
|
|
case "array":
|
|
|
|
MetaTypeInfo containedType = getType("arrayItem", node["items"]);
|
|
|
|
info.needsLocalImport = containedType.needsLocalImport;
|
|
|
|
info.needsSystemImport = true;
|
|
|
|
info.isContainer = true;
|
|
|
|
info.containerType = containedType;
|
2021-03-20 02:30:50 +00:00
|
|
|
info.isTypeNullable = true;
|
|
|
|
info.typeNullableCheck = ".size() == 0";
|
|
|
|
info.typeNullableSetter = ".clear()";
|
2021-02-20 22:33:07 +00:00
|
|
|
if (containedType.typeName == "QString") {
|
|
|
|
info.typeName = "QStringList";
|
|
|
|
} else {
|
2021-02-21 04:02:05 +00:00
|
|
|
info.typeName = "QList<" ~ containedType.typeNameWithQualifiers ~ ">";
|
2021-02-20 22:33:07 +00:00
|
|
|
}
|
|
|
|
return info;
|
|
|
|
default:
|
|
|
|
info.typeName = "UNIMPLEMENTED";
|
|
|
|
return info;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Appender!(MetaTypeInfo[]) result;
|
|
|
|
|
|
|
|
foreach(const ref string name, const ref Node node; properties) {
|
|
|
|
result ~= getType(name, node);
|
|
|
|
}
|
|
|
|
return result[];
|
|
|
|
}
|
|
|
|
|
|
|
|
void writeObjectHeader(File output, string name, MetaTypeInfo[] properties, string[] userImports) {
|
2021-03-19 22:01:29 +00:00
|
|
|
class Controller {
|
|
|
|
string className;
|
|
|
|
MetaTypeInfo[] properties;
|
|
|
|
string[] userImports;
|
2021-03-20 02:30:50 +00:00
|
|
|
string supportNamespace = namespaceString!CPP_NAMESPACE_SUPPORT;
|
2021-02-20 22:33:07 +00:00
|
|
|
}
|
2021-03-19 22:01:29 +00:00
|
|
|
Controller controller = new Controller();
|
|
|
|
controller.className = name.applyCasePolicy(OPENAPI_CASING, CPP_CLASS_CASING);
|
|
|
|
controller.properties = properties;
|
|
|
|
controller.userImports = userImports;
|
2021-02-20 22:33:07 +00:00
|
|
|
|
2021-03-19 22:01:29 +00:00
|
|
|
output.writeln(render!(import("object_header.hbs"), Controller)(controller));
|
2021-02-20 22:33:07 +00:00
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void writeObjectImplementation(File output, string name, MetaTypeInfo[] properties) {
|
2021-03-19 22:01:29 +00:00
|
|
|
class Controller {
|
|
|
|
string className;
|
|
|
|
MetaTypeInfo[] properties;
|
2021-03-20 02:30:50 +00:00
|
|
|
string supportNamespace = namespaceString!CPP_NAMESPACE_SUPPORT;
|
2021-02-20 22:33:07 +00:00
|
|
|
}
|
2021-03-19 22:01:29 +00:00
|
|
|
Controller controller = new Controller();
|
|
|
|
controller.className = name.applyCasePolicy(OPENAPI_CASING, CPP_CLASS_CASING);
|
|
|
|
controller.properties = properties;
|
|
|
|
output.writeln(render!(import("object_implementation.hbs"), Controller)(controller));
|
2021-02-20 22:33:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Enum
|
|
|
|
void writeEnumHeader(File output, string name, string[] values, string doc = "") {
|
2021-03-19 22:01:29 +00:00
|
|
|
class Controller {
|
|
|
|
string className;
|
|
|
|
string[] values;
|
2021-03-20 02:30:50 +00:00
|
|
|
string supportNamespace = namespaceString!CPP_NAMESPACE_SUPPORT;
|
2021-02-20 22:33:07 +00:00
|
|
|
}
|
2021-03-19 22:01:29 +00:00
|
|
|
Controller controller = new Controller();
|
|
|
|
controller.className = name.applyCasePolicy(OPENAPI_CASING, CPP_CLASS_CASING);
|
|
|
|
controller.values = values;
|
|
|
|
output.writeln(render!(import("enum_header.hbs"), Controller)(controller));
|
2021-02-20 22:33:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void writeEnumImplementation(File output, string name) {
|
|
|
|
string className = name.applyCasePolicy(OPENAPI_CASING, CPP_CLASS_CASING);
|
2021-02-21 04:02:05 +00:00
|
|
|
output.writefln("%sClass::%sClass() {}", name, name);
|
2021-02-20 22:33:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Common
|
2021-03-20 02:30:50 +00:00
|
|
|
void writeHeaderPreamble(File output, immutable string[] fileNamespace, string className, string[] imports = [], string[] userImports = []) {
|
2021-02-20 23:26:18 +00:00
|
|
|
output.writeln(COPYRIGHT);
|
2021-03-20 02:30:50 +00:00
|
|
|
string guard = guardName(fileNamespace, className);
|
2021-02-20 22:33:07 +00:00
|
|
|
output.writefln("#ifndef %s", guard);
|
|
|
|
output.writefln("#define %s", guard);
|
|
|
|
output.writeln();
|
|
|
|
|
|
|
|
foreach(file; imports) {
|
|
|
|
output.writefln("#include <%s>", file);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (imports.length > 0) output.writeln();
|
|
|
|
|
2021-02-21 04:02:05 +00:00
|
|
|
foreach(file; userImports) {
|
2021-03-05 14:34:10 +00:00
|
|
|
output.writefln("#include \"%s\"", buildPath(INCLUDE_PREFIX, file));
|
2021-02-21 04:02:05 +00:00
|
|
|
}
|
|
|
|
if (userImports.length > 0) output.writeln();
|
|
|
|
|
2021-03-20 02:30:50 +00:00
|
|
|
foreach (namespace; fileNamespace) {
|
2021-02-20 22:33:07 +00:00
|
|
|
output.writefln("namespace %s {", namespace);
|
|
|
|
}
|
|
|
|
output.writeln();
|
|
|
|
}
|
|
|
|
|
2021-03-20 02:30:50 +00:00
|
|
|
void writeHeaderPostamble(File output, immutable string[] fileNamespace, string className) {
|
2021-02-20 22:33:07 +00:00
|
|
|
output.writeln();
|
2021-03-20 02:30:50 +00:00
|
|
|
foreach(namespace; fileNamespace) {
|
2021-02-20 22:33:07 +00:00
|
|
|
output.writefln("} // NS %s", namespace);
|
|
|
|
}
|
|
|
|
output.writeln();
|
2021-03-20 02:30:50 +00:00
|
|
|
output.writefln("#endif // %s", guardName(fileNamespace, className));
|
2021-02-20 22:33:07 +00:00
|
|
|
}
|
|
|
|
|
2021-03-20 02:30:50 +00:00
|
|
|
void writeImplementationPreamble(File output, immutable string[] fileNamespace, string className, string[] imports = []) {
|
2021-02-20 23:26:18 +00:00
|
|
|
output.writeln(COPYRIGHT);
|
2021-03-05 14:34:10 +00:00
|
|
|
output.writefln("#include <%s>", buildPath(INCLUDE_PREFIX, MODEL_FOLDER, className.applyCasePolicy(OPENAPI_CASING, CasePolicy.LOWER) ~ ".h"));
|
2021-02-20 22:33:07 +00:00
|
|
|
output.writeln();
|
|
|
|
|
|
|
|
foreach(file; imports) {
|
2021-03-05 14:34:10 +00:00
|
|
|
output.writefln("#include <%s>", buildPath(INCLUDE_PREFIX, file));
|
2021-02-20 22:33:07 +00:00
|
|
|
}
|
|
|
|
if (imports.length > 0) output.writeln();
|
|
|
|
|
2021-03-20 02:30:50 +00:00
|
|
|
foreach (namespace; fileNamespace) {
|
2021-02-20 22:33:07 +00:00
|
|
|
output.writefln("namespace %s {", namespace);
|
|
|
|
}
|
|
|
|
output.writeln();
|
|
|
|
}
|
|
|
|
|
2021-03-20 02:30:50 +00:00
|
|
|
void writeImplementationPostamble(File output, immutable string[] fileNamespace, string className) {
|
2021-02-20 22:33:07 +00:00
|
|
|
output.writeln();
|
2021-03-20 02:30:50 +00:00
|
|
|
foreach(namespace; fileNamespace) {
|
2021-02-20 22:33:07 +00:00
|
|
|
output.writefln("} // NS %s", namespace);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Helper functions
|
|
|
|
string applyCasePolicy(string source, CasePolicy input, CasePolicy output) {
|
|
|
|
if (input == output) return source;
|
|
|
|
switch(output) {
|
|
|
|
case CasePolicy.KEEP:
|
|
|
|
return source;
|
|
|
|
case CasePolicy.PASCAL:
|
|
|
|
if (input == CasePolicy.CAMEL) {
|
|
|
|
char[] result = source.dup;
|
|
|
|
result[0] = cast(char) toUpper(result[0]);
|
|
|
|
return cast(string) result;
|
|
|
|
} else {
|
|
|
|
throw new Exception("Not implemented");
|
|
|
|
}
|
|
|
|
case CasePolicy.CAMEL:
|
|
|
|
if (input == CasePolicy.PASCAL) {
|
|
|
|
char[] result = source.dup;
|
|
|
|
result[0] = cast(char) toLower(result[0]);
|
|
|
|
return cast(string) result;
|
|
|
|
} else {
|
|
|
|
throw new Exception("Not implemented");
|
|
|
|
}
|
|
|
|
case CasePolicy.LOWER:
|
|
|
|
if (input == CasePolicy.CAMEL || input == CasePolicy.PASCAL) {
|
|
|
|
return source.toLower();
|
|
|
|
}
|
|
|
|
throw new Exception("Not implemented");
|
|
|
|
case CasePolicy.UPPER:
|
|
|
|
if (input == CasePolicy.CAMEL || input == CasePolicy.PASCAL) {
|
|
|
|
return source.toUpper();
|
|
|
|
}
|
|
|
|
throw new Exception("Not implemented");
|
|
|
|
case CasePolicy.SCREAMING_SNAKE:
|
|
|
|
if (input == CasePolicy.CAMEL || input == CasePolicy.PASCAL) {
|
|
|
|
char[] mutableSource = source.dup;
|
|
|
|
Appender!(char[]) result;
|
|
|
|
foreach(window; source.slide!(Yes.withPartial)(2)) {
|
|
|
|
dchar c = window.front;
|
|
|
|
window.popFront();
|
|
|
|
dchar n = window.front;
|
|
|
|
if (isLower(c) && !isLower(n)) {
|
|
|
|
result ~= toUpper(c);
|
|
|
|
result ~= '_';
|
|
|
|
} else if (!isLower(c) && !isLower(n)) {
|
|
|
|
result ~= toUpper(c);
|
|
|
|
} else {
|
|
|
|
result ~= toUpper(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result ~= toUpper(source[$ - 1]);
|
|
|
|
return cast(string) result[];
|
|
|
|
}
|
|
|
|
throw new Exception("Not implemented");
|
|
|
|
default:
|
|
|
|
throw new Exception("Not implemented");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class MetaTypeInfo {
|
|
|
|
public:
|
2021-03-05 14:34:10 +00:00
|
|
|
string originalName = "";
|
2021-02-20 22:33:07 +00:00
|
|
|
string name = "";
|
|
|
|
string typeName = "";
|
2021-03-20 02:30:50 +00:00
|
|
|
/// Description of this property.
|
2021-02-20 22:33:07 +00:00
|
|
|
string description = "";
|
2021-03-20 02:30:50 +00:00
|
|
|
/// If this property is nullable according to the OpenAPI spec.
|
|
|
|
bool isNullable = false;
|
2021-02-20 22:33:07 +00:00
|
|
|
bool needsPointer = false;
|
2021-03-20 02:30:50 +00:00
|
|
|
/// If the type needs a system import (Such as Qt types)
|
2021-02-20 22:33:07 +00:00
|
|
|
bool needsSystemImport = false;
|
2021-03-20 02:30:50 +00:00
|
|
|
/// If the type needs a local import (such as types elsewhere in this project).
|
2021-02-20 22:33:07 +00:00
|
|
|
bool needsLocalImport = false;
|
2021-03-20 02:30:50 +00:00
|
|
|
/// If the type is a container type
|
2021-02-20 22:33:07 +00:00
|
|
|
bool isContainer = false;
|
2021-03-20 02:30:50 +00:00
|
|
|
/// If this type has a non-ambigious null state.
|
|
|
|
bool isTypeNullable = false;
|
|
|
|
/// If `isContainer` is true, the type of the container.
|
2021-02-20 22:33:07 +00:00
|
|
|
MetaTypeInfo containerType = null;
|
|
|
|
|
|
|
|
string writeName() {
|
|
|
|
return name.applyCasePolicy(CPP_CLASS_MEMBER_CASING, CasePolicy.PASCAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
string memberName() {
|
|
|
|
return CPP_CLASS_MEMBER_PREFIX ~ name;
|
|
|
|
}
|
|
|
|
|
|
|
|
string typeNameWithQualifiers() {
|
|
|
|
if (needsPointer) {
|
2021-03-20 02:30:50 +00:00
|
|
|
return "QSharedPointer<" ~ typeName~ ">";
|
2021-02-20 22:33:07 +00:00
|
|
|
} else {
|
|
|
|
return typeName;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-20 02:30:50 +00:00
|
|
|
bool needsOptional() {
|
|
|
|
return isNullable && !isTypeNullable;
|
|
|
|
}
|
|
|
|
|
|
|
|
string typeNullableCheck;
|
|
|
|
string nullableCheck() {
|
|
|
|
if (typeNullableCheck.length > 0) {
|
|
|
|
return memberName ~ "." ~ typeNullableCheck;
|
|
|
|
}
|
|
|
|
if (needsOptional) {
|
|
|
|
return "!" ~ memberName ~ ".has_value()";
|
|
|
|
}
|
|
|
|
return "Q_ASSERT(false)";
|
|
|
|
}
|
|
|
|
|
|
|
|
string typeNullableSetter = "";
|
|
|
|
string nullableSetter() {
|
|
|
|
if (needsOptional) {
|
|
|
|
return memberName ~ "= std::nullopt";
|
|
|
|
}
|
|
|
|
if (typeNullableSetter.startsWith("=")) {
|
|
|
|
return memberName ~ typeNullableSetter;
|
|
|
|
} else {
|
|
|
|
return typeNullableSetter;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-20 22:33:07 +00:00
|
|
|
string defaultInitializer() {
|
|
|
|
if (needsPointer) return "nullptr";
|
2021-03-20 02:30:50 +00:00
|
|
|
if (needsOptional) return "std::nullopt";
|
2021-02-20 22:33:07 +00:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-20 02:30:50 +00:00
|
|
|
string guardName(immutable string[] namespace, string className) {
|
2021-02-20 22:33:07 +00:00
|
|
|
return namespace.map!toUpper().join("_") ~ "_"
|
|
|
|
~ className.applyCasePolicy(OPENAPI_CASING, CasePolicy.UPPER)
|
|
|
|
~ "_H";
|
|
|
|
}
|
2021-03-20 02:30:50 +00:00
|
|
|
|
|
|
|
string namespaceString(string[] name)() {
|
|
|
|
string result;
|
|
|
|
static foreach(idx, part; name) {
|
|
|
|
static if (idx != 0) {
|
|
|
|
result ~= "::";
|
|
|
|
}
|
|
|
|
result ~= part;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|