This commit is contained in:
Anton Maminov 2018-09-17 22:41:32 +03:00
Родитель 0cc77f9164
Коммит 9af65fa8bf
8 изменённых файлов: 470 добавлений и 1 удалений

Просмотреть файл

@ -22,7 +22,7 @@ ENV PATH="${workdir}/swift-4.1.3-RELEASE-ubuntu16.04/usr/bin:${PATH}"
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash -
# Add .NET core package sources
RUN curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
RUN curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
RUN mv microsoft.gpg /etc/apt/trusted.gpg.d/microsoft.gpg
RUN sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/microsoft-ubuntu-xenial-prod xenial main" > /etc/apt/sources.list.d/dotnetdev.list'
@ -65,6 +65,12 @@ RUN sh -c 'curl https://storage.googleapis.com/download.dartlang.org/linux/debia
RUN apt-get update
RUN apt-get install dart
# Crystal
RUN curl -sL "https://keybase.io/crystal/pgp_keys.asc" | apt-key add -
RUN echo "deb https://dist.crystal-lang.org/apt crystal main" | tee /etc/apt/sources.list.d/crystal.list
RUN apt-get update
RUN apt-get install crystal --assume-yes
ENV PATH="${workdir}/node_modules/.bin:${PATH}"
COPY . .

Просмотреть файл

@ -174,6 +174,7 @@ files, URLs, or add other options.
`quicktype` has many complex test dependencies:
- `crystal` compiler
- `dotnetcore` SDK
- Java, Maven
- `elm` tools

Просмотреть файл

@ -71,3 +71,4 @@ export { ElmTargetLanguage, ElmRenderer } from "./language/Elm";
export { JSONSchemaTargetLanguage, JSONSchemaRenderer } from "./language/JSONSchema";
export { RustTargetLanguage, RustRenderer } from "./language/Rust";
export { RubyTargetLanguage, RubyRenderer } from "./language/ruby";
export { CrystalTargetLanguage, CrystalRenderer } from "./language/Crystal";

Просмотреть файл

@ -14,6 +14,7 @@ import { KotlinTargetLanguage } from "./Kotlin";
import { ElmTargetLanguage } from "./Elm";
import { JSONSchemaTargetLanguage } from "./JSONSchema";
import { RustTargetLanguage } from "./Rust";
import { CrystalTargetLanguage } from "./Crystal";
import { RubyTargetLanguage } from "./ruby";
import { DartTargetLanguage } from "./Dart";
import { PythonTargetLanguage } from "./Python";
@ -22,6 +23,7 @@ export const all: TargetLanguage[] = [
new NewtonsoftCSharpTargetLanguage(),
new GoTargetLanguage(),
new RustTargetLanguage(),
new CrystalTargetLanguage(),
new CPlusPlusTargetLanguage(),
new ObjectiveCTargetLanguage(),
new JavaTargetLanguage(),

Просмотреть файл

@ -0,0 +1,417 @@
import { mapFirst } from "collection-utils";
import { TargetLanguage } from "../TargetLanguage";
import { ConvenienceRenderer, ForbiddenWordsInfo } from "../ConvenienceRenderer";
import {
legalizeCharacters,
splitIntoWords,
isLetterOrUnderscoreOrDigit,
combineWords,
allLowerWordStyle,
firstUpperWordStyle,
intToHex,
utf32ConcatMap,
escapeNonPrintableMapper,
isPrintable,
isAscii,
isLetterOrUnderscore
} from "../support/Strings";
import { Name, Namer, funPrefixNamer } from "../Naming";
import { UnionType, Type, ClassType, EnumType } from "../Type";
import { matchType, nullableFromUnion, removeNullFromUnion } from "../TypeUtils";
import { Sourcelike, maybeAnnotated } from "../Source";
import { anyTypeIssueAnnotation, nullTypeIssueAnnotation } from "../Annotation";
import { Option } from "../RendererOptions";
import { defined } from "../support/Support";
import { RenderContext } from "../Renderer";
export class CrystalTargetLanguage extends TargetLanguage {
protected makeRenderer(renderContext: RenderContext): CrystalRenderer {
return new CrystalRenderer(this, renderContext);
}
constructor() {
super("crystal", ["crystal", "cr", "crystallang"], "cr");
}
protected get defaultIndentation(): string {
return " ";
}
protected getOptions(): Option<any>[] {
return [];
}
}
const keywords = [
"Any",
"Array",
"Atomic",
"Bool",
"Channel",
"Char",
"Class",
"Enum",
"Enumerable",
"Event",
"Extern",
"Exception",
"File",
"Float",
"Float32",
"Float64",
"GC",
"GZip",
"Hash",
"HTML",
"HTTP",
"Int",
"Int128",
"Int16",
"Int32",
"Int64",
"Int8",
"Iterable",
"Link",
"Logger",
"Math",
"Mutex",
"Nil",
"Number",
"JSON",
"IO",
"Object",
"Pointer",
"Proc",
"Process",
"Range",
"Random",
"Regex",
"Reference",
"Set",
"Signal",
"Slice",
"Spec",
"StaticArray",
"String",
"Struct",
"Symbol",
"System",
"TCPServer",
"TCPSocket",
"Socket",
"Tempfile",
"Termios",
"Time",
"Tuple",
"ThreadLocal",
"UDPSocket",
"UInt128",
"UInt16",
"UInt32",
"UInt64",
"UInt8",
"Union",
"UNIXServer",
"UNIXSocket",
"UUID",
"URI",
"VaList",
"Value",
"Void",
"WeakRef",
"XML",
"YAML",
"Zip",
"Zlib",
"abstract",
"alias",
"as",
"as?",
"asm",
"begin",
"break",
"case",
"class",
"def",
"do",
"else",
"elsif",
"end",
"ensure",
"enum",
"extend",
"false",
"for",
"fun",
"if",
"in",
"include",
"instance_sizeof",
"is_a?",
"lib",
"macro",
"module",
"next",
"nil",
"nil?",
"of",
"out",
"pointerof",
"private",
"protected",
"require",
"rescue",
"return",
"select",
"self",
"sizeof",
"struct",
"super",
"then",
"true",
"type",
"typeof",
"uninitialized",
"union",
"unless",
"until",
"when",
"while",
"with",
"yield"
];
const isAsciiLetterOrUnderscoreOrDigit = (codePoint: number): boolean => {
if (!isAscii(codePoint)) {
return false;
}
return isLetterOrUnderscoreOrDigit(codePoint);
};
const isAsciiLetterOrUnderscore = (codePoint: number): boolean => {
if (!isAscii(codePoint)) {
return false;
}
return isLetterOrUnderscore(codePoint);
};
const legalizeName = legalizeCharacters(isAsciiLetterOrUnderscoreOrDigit);
function crystalStyle(original: string, isSnakeCase: boolean): string {
const words = splitIntoWords(original);
const wordStyle = isSnakeCase ? allLowerWordStyle : firstUpperWordStyle;
const combined = combineWords(
words,
legalizeName,
wordStyle,
wordStyle,
wordStyle,
wordStyle,
isSnakeCase ? "_" : "",
isAsciiLetterOrUnderscore
);
return combined === "_" ? "_underscore" : combined;
}
const snakeNamingFunction = funPrefixNamer("default", (original: string) => crystalStyle(original, true));
const camelNamingFunction = funPrefixNamer("camel", (original: string) => crystalStyle(original, false));
const standardUnicodeCrystalEscape = (codePoint: number): string => {
if (codePoint <= 0xffff) {
return "\\u{" + intToHex(codePoint, 4) + "}";
} else {
return "\\u{" + intToHex(codePoint, 6) + "}";
}
};
const crystalStringEscape = utf32ConcatMap(escapeNonPrintableMapper(isPrintable, standardUnicodeCrystalEscape));
export class CrystalRenderer extends ConvenienceRenderer {
constructor(targetLanguage: TargetLanguage, renderContext: RenderContext) {
super(targetLanguage, renderContext);
}
protected makeNamedTypeNamer(): Namer {
return camelNamingFunction;
}
protected namerForObjectProperty(): Namer | null {
return snakeNamingFunction;
}
protected makeUnionMemberNamer(): Namer | null {
return camelNamingFunction;
}
protected makeEnumCaseNamer(): Namer | null {
return camelNamingFunction;
}
protected forbiddenNamesForGlobalNamespace(): string[] {
return keywords;
}
protected forbiddenForObjectProperties(_c: ClassType, _className: Name): ForbiddenWordsInfo {
return { names: [], includeGlobalForbidden: true };
}
protected forbiddenForUnionMembers(_u: UnionType, _unionName: Name): ForbiddenWordsInfo {
return { names: [], includeGlobalForbidden: true };
}
protected forbiddenForEnumCases(_e: EnumType, _enumName: Name): ForbiddenWordsInfo {
return { names: [], includeGlobalForbidden: true };
}
protected get commentLineStart(): string {
return "# ";
}
private nullableCrystalType = (t: Type, withIssues: boolean): Sourcelike => {
return [this.crystalType(t, withIssues), "?"];
};
protected isImplicitCycleBreaker(t: Type): boolean {
const kind = t.kind;
return kind === "array" || kind === "map";
}
private crystalType = (t: Type, withIssues: boolean = false): Sourcelike => {
return matchType<Sourcelike>(
t,
_anyType => maybeAnnotated(withIssues, anyTypeIssueAnnotation, "JSON::Any"),
_nullType => maybeAnnotated(withIssues, nullTypeIssueAnnotation, "Nil"),
_boolType => "Bool",
_integerType => "Int32",
_doubleType => "Float64",
_stringType => "String",
arrayType => ["Array(", this.crystalType(arrayType.items, withIssues), ")"],
classType => this.nameForNamedType(classType),
mapType => ["Hash(String, ", this.crystalType(mapType.values, withIssues), ")"],
_enumType => "String",
unionType => {
const nullable = nullableFromUnion(unionType);
if (nullable !== null) return this.nullableCrystalType(nullable, withIssues);
const [hasNull] = removeNullFromUnion(unionType);
const name = this.nameForNamedType(unionType);
return hasNull !== null ? ([name, "?"] as Sourcelike) : name;
}
);
};
private breakCycle = (t: Type, withIssues: boolean): any => {
const crystalType = this.crystalType(t, withIssues);
return crystalType;
};
private emitRenameAttribute(propName: Name, jsonName: string) {
const escapedName = crystalStringEscape(jsonName);
const namesDiffer = this.sourcelikeToString(propName) !== escapedName;
if (namesDiffer) {
this.emitLine('@[JSON::Field(key: "', escapedName, '")]');
}
}
protected emitStructDefinition(c: ClassType, className: Name): void {
this.emitDescription(this.descriptionForType(c));
const structBody = () =>
this.forEachClassProperty(c, "none", (name, jsonName, prop) => {
this.emitDescription(this.descriptionForClassProperty(c, jsonName));
this.emitRenameAttribute(name, jsonName);
this.emitLine("property ", name, " : ", this.crystalType(prop.type, true));
});
this.emitBlock(["class ", className], structBody);
}
protected emitBlock(line: Sourcelike, f: () => void): void {
this.emitLine(line, "");
this.indent(() => {
this.emitLine("include JSON::Serializable");
});
this.ensureBlankLine();
this.indent(f);
this.emitLine("end");
}
protected emitEnum(line: Sourcelike, f: () => void): void {
this.emitLine(line);
this.indent(f);
this.emitLine("end");
}
protected emitUnion(u: UnionType, unionName: Name): void {
const isMaybeWithSingleType = nullableFromUnion(u);
if (isMaybeWithSingleType !== null) {
return;
}
this.emitDescription(this.descriptionForType(u));
const [, nonNulls] = removeNullFromUnion(u);
let count = nonNulls.size;
let types: Sourcelike[][] = [];
this.emitLine(["alias ", unionName, " = "]);
this.forEachUnionMember(u, nonNulls, "none", null, (_fieldName, t) => {
const last = --count === 0;
const blanksOrPipe = last ? "" : " |";
const crystalType = this.breakCycle(t, true);
this.emitLine([crystalType, blanksOrPipe]);
types.push(crystalType);
});
}
protected emitTopLevelAlias(t: Type, name: Name): void {
this.emitLine("alias ", name, " = ", this.crystalType(t));
}
protected emitLeadingComments(): void {
if (this.leadingComments !== undefined) {
this.emitCommentLines(this.leadingComments);
return;
}
const topLevelName = defined(mapFirst(this.topLevels));
this.emitMultiline(
`# Example code that deserializes and serializes the model.
# class ${topLevelName}
# include JSON::Serializable
#
# @[JSON::Field(key: "answer")]
# property answer : Int32
# end
#
# ${topLevelName}.from_json(%({"answer": 42}))
`
);
}
protected emitSourceStructure(): void {
this.emitLeadingComments();
this.ensureBlankLine();
this.emitLine('require "json"');
this.forEachTopLevel(
"leading",
(t, name) => this.emitTopLevelAlias(t, name),
t => this.namedTypeToNameForTopLevel(t) === undefined
);
this.forEachObject("leading-and-interposing", (c: ClassType, name: Name) => this.emitStructDefinition(c, name));
this.forEachUnion("leading-and-interposing", (u, name) => this.emitUnion(u, name));
}
}

Просмотреть файл

@ -683,6 +683,7 @@ class GraphQLFixture extends LanguageFixture {
}
export const allFixtures: Fixture[] = [
new JSONFixture(languages.CrystalLanguage),
new JSONFixture(languages.CSharpLanguage),
new JSONFixture(languages.JavaLanguage),
new JSONFixture(languages.GoLanguage),
@ -700,6 +701,7 @@ export const allFixtures: Fixture[] = [
new JSONFixture(languages.DartLanguage),
new JSONSchemaJSONFixture(languages.CSharpLanguage),
new JSONTypeScriptFixture(languages.CSharpLanguage),
new JSONSchemaFixture(languages.CrystalLanguage),
new JSONSchemaFixture(languages.CSharpLanguage),
new JSONSchemaFixture(languages.JavaLanguage),
new JSONSchemaFixture(languages.GoLanguage),

7
test/fixtures/crystal/main.cr поставляемый Normal file
Просмотреть файл

@ -0,0 +1,7 @@
require "json"
require "./TopLevel"
json = File.read(ARGV[0].not_nil!)
top = TopLevel.from_json(json)
puts top.to_json

Просмотреть файл

@ -181,6 +181,39 @@ export const RustLanguage: Language = {
sourceFiles: ["src/language/Rust.ts"]
};
export const CrystalLanguage: Language = {
name: "crystal",
base: "test/fixtures/crystal",
compileCommand: "crystal build -o quicktype main.cr",
runCommand(sample: string) {
return `./quicktype "${sample}"`;
},
diffViaSchema: false,
skipDiffViaSchema: [],
allowMissingNull: true,
features: ["enum", "union", "no-defaults"],
output: "TopLevel.cr",
topLevel: "TopLevel",
skipJSON: [
"blns-object.json",
"identifiers.json",
"simple-identifiers.json",
"bug427.json",
"nst-test-suite.json",
"34702.json",
"34702.json",
"4961a.json",
"32431.json",
"68c30.json",
"e8b04.json"
],
skipSchema: [],
skipMiscJSON: false,
rendererOptions: {},
quickTestRendererOptions: [],
sourceFiles: ["src/language/Crystal.ts"]
};
export const RubyLanguage: Language = {
name: "ruby",
base: "test/fixtures/ruby",