This commit is contained in:
David Siegel 2018-03-08 18:20:54 -08:00 коммит произвёл David Siegel
Родитель 4e5c67923c
Коммит 42f4be58c2
10 изменённых файлов: 1059 добавлений и 24 удалений

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

@ -14,6 +14,7 @@ import SwiftTargetLanguage from "./Swift";
import ElmTargetLanguage from "./Elm";
import JSONSchemaTargetLanguage from "./JSONSchema";
import RustTargetLanguage from "./Rust";
import RubyTargetLanguage from "./Ruby";
export const all: TargetLanguage[] = [
new CSharpTargetLanguage(),
@ -28,6 +29,7 @@ export const all: TargetLanguage[] = [
new SwiftTargetLanguage(),
new ElmTargetLanguage(),
new JSONSchemaTargetLanguage(),
new RubyTargetLanguage(),
new SimpleTypesTargetLanguage()
];

612
src/Language/Ruby/index.ts Normal file
Просмотреть файл

@ -0,0 +1,612 @@
"use strict";
import { snakeCase, includes } from "lodash";
const unicode = require("unicode-properties");
import { TypeGraph } from "../../TypeGraph";
import { Sourcelike, modifySource } from "../../Source";
import { Namer, Name } from "../../Naming";
import { ConvenienceRenderer, ForbiddenWordsInfo } from "../../ConvenienceRenderer";
import { TargetLanguage } from "../../TargetLanguage";
import { Option, BooleanOption } from "../../RendererOptions";
import * as keywords from "./keywords";
import {
Type,
EnumType,
ClassType,
nullableFromUnion,
matchType,
UnionType,
ArrayType,
MapType,
ClassProperty,
removeNullFromUnion
} from "../../Type";
import {
legalizeCharacters,
splitIntoWords,
combineWords,
firstUpperWordStyle,
allUpperWordStyle,
allLowerWordStyle,
utf32ConcatMap,
isPrintable,
escapeNonPrintableMapper,
intToHex
} from "../../Strings";
function unicodeEscape(codePoint: number): string {
return "\\u{" + intToHex(codePoint, 0) + "}";
}
const stringEscape = utf32ConcatMap(escapeNonPrintableMapper(isPrintable, unicodeEscape));
export default class RubyTargetLanguage extends TargetLanguage {
private readonly _justTypesOption = new BooleanOption("just-types", "Plain types only", false);
constructor() {
super("Ruby", ["ruby"], "rb");
}
protected getOptions(): Option<any>[] {
return [this._justTypesOption];
}
get supportsOptionalClassProperties(): boolean {
return true;
}
protected get defaultIndentation(): string {
return " ";
}
protected get rendererClass(): new (
graph: TypeGraph,
leadingComments: string[] | undefined,
...optionValues: any[]
) => ConvenienceRenderer {
return RubyRenderer;
}
}
function isStartCharacter(utf16Unit: number): boolean {
return unicode.isAlphabetic(utf16Unit) || utf16Unit === 0x5f; // underscore
}
function isPartCharacter(utf16Unit: number): boolean {
const category: string = unicode.getCategory(utf16Unit);
return includes(["Nd", "Pc", "Mn", "Mc"], category) || isStartCharacter(utf16Unit);
}
const legalizeName = legalizeCharacters(isPartCharacter);
function simpleNameStyle(original: string, uppercase: boolean): string {
const words = splitIntoWords(original);
return combineWords(
words,
legalizeName,
uppercase ? firstUpperWordStyle : allLowerWordStyle,
uppercase ? firstUpperWordStyle : allLowerWordStyle,
allUpperWordStyle,
allUpperWordStyle,
"",
isStartCharacter
);
}
function memberNameStyle(original: string): string {
const words = splitIntoWords(original);
return combineWords(
words,
legalizeName,
allLowerWordStyle,
allLowerWordStyle,
allLowerWordStyle,
allLowerWordStyle,
"_",
isStartCharacter
);
}
class RubyRenderer extends ConvenienceRenderer {
constructor(graph: TypeGraph, leadingComments: string[] | undefined, private readonly _justTypes: boolean) {
super(graph, leadingComments);
}
protected get commentLineStart(): string {
return "# ";
}
protected get needsTypeDeclarationBeforeUse(): boolean {
return true;
}
protected canBeForwardDeclared(t: Type): boolean {
return "class" === t.kind;
}
protected topLevelNameStyle(rawName: string): string {
return simpleNameStyle(rawName, true);
}
protected forbiddenNamesForGlobalNamespace(): string[] {
return keywords.globals.concat(["Types", "JSON", "Dry", "Constructor"]);
}
protected forbiddenForClassProperties(_c: ClassType, _classNamed: Name): ForbiddenWordsInfo {
return { names: keywords.reservedProperties, includeGlobalForbidden: true };
}
protected makeNamedTypeNamer(): Namer {
return new Namer("types", n => simpleNameStyle(n, true), []);
}
protected namerForClassProperty(): Namer {
return new Namer("properties", memberNameStyle, []);
}
protected makeUnionMemberNamer(): Namer {
return new Namer("properties", memberNameStyle, []);
}
protected makeEnumCaseNamer(): Namer {
return new Namer("enum-cases", n => simpleNameStyle(n, true), []);
}
private dryType(t: Type, isOptional: boolean = false): Sourcelike {
const optional = isOptional ? ".optional" : "";
return matchType<Sourcelike>(
t,
_anyType => ["Types::Any", optional],
_nullType => ["Types::Strict::Nil", optional],
_boolType => ["Types::Strict::Bool", optional],
_integerType => ["Types::Strict::Int", optional],
_doubleType => ["Types::StrictDouble", optional],
_stringType => ["Types::Strict::String", optional],
arrayType => ["Types.Array(", this.dryType(arrayType.items), ")", optional],
classType => ["Types.Instance(", this.nameForNamedType(classType), ")", optional],
mapType => ["Types::Strict::Hash.meta(of: ", this.dryType(mapType.values), ")", optional],
enumType => ["Types::", this.nameForNamedType(enumType), optional],
unionType => {
const nullable = nullableFromUnion(unionType);
if (nullable !== null) {
return [this.dryType(nullable), ".optional"];
}
return ["Types.Instance(", this.nameForNamedType(unionType), ")", optional];
}
);
}
private exampleUse(t: Type, exp: Sourcelike, depth: number = 6, optional: boolean = false): Sourcelike {
if (depth-- <= 0) {
return exp;
}
const safeNav = optional ? "&" : "";
return matchType<Sourcelike>(
t,
_anyType => exp,
_nullType => [exp, ".nil?"],
_boolType => exp,
_integerType => [exp, ".even?"],
_doubleType => exp,
_stringType => exp,
arrayType => this.exampleUse(arrayType.items, [exp, safeNav, ".first"], depth),
classType => {
let info: { name: Name; prop: ClassProperty } | undefined;
this.forEachClassProperty(classType, "none", (name, _json, prop) => {
if (includes(["class", "map", "array"], prop.type.kind)) {
info = { name, prop };
} else {
info = info || { name, prop };
}
});
if (info !== undefined) {
return this.exampleUse(info.prop.type, [exp, safeNav, ".", info.name], depth, info.prop.isOptional);
}
return exp;
},
mapType => this.exampleUse(mapType.values, [exp, safeNav, `["…"]`], depth),
enumType => {
let name: Name | undefined;
this.forEachEnumCase(enumType, "none", theName => {
name = name || theName;
});
if (name !== undefined) {
return [exp, " == ", this.nameForNamedType(enumType), "::", name];
}
return exp;
},
unionType => {
const nullable = nullableFromUnion(unionType);
if (nullable !== null) {
if (["class", "map", "array"].indexOf(nullable.kind) >= 0) {
return this.exampleUse(nullable, exp, depth, true);
}
return [exp, ".nil?"];
}
return exp;
}
);
}
private jsonSample(t: Type): Sourcelike {
function inner() {
if (t instanceof ArrayType) {
return "[…]";
} else if (t instanceof MapType) {
return "{…}";
} else if (t instanceof ClassType) {
return "{…}";
} else {
return "…";
}
}
return `"${inner()}"`;
}
private fromDynamic(
t: Type,
e: Sourcelike,
optional: boolean = false,
castPrimitives: boolean = false
): Sourcelike {
const primitiveCast = [this.dryType(t, optional), "[", e, "]"];
const primitive = castPrimitives ? primitiveCast : e;
return matchType<Sourcelike>(
t,
_anyType => primitive,
_nullType => primitive,
_boolType => primitive,
_integerType => primitive,
_doubleType => primitive,
_stringType => primitive,
arrayType => [
e,
optional ? "&" : "",
".map { |x| ",
this.fromDynamic(arrayType.items, "x", false, true),
" }"
],
classType => {
const expression = [this.nameForNamedType(classType), ".from_dynamic!(", e, ")"];
return optional ? [e, " ? ", expression, " : nil"] : expression;
},
mapType => [
e,
optional ? "&" : "",
".map { |k, v| [k, ",
this.fromDynamic(mapType.values, "v", false, true),
"] }.to_h"
],
enumType => {
const expression = ["Types::", this.nameForNamedType(enumType), "[", e, "]"];
return optional ? [e, ".nil? ? nil : ", expression] : expression;
},
unionType => {
const nullable = nullableFromUnion(unionType);
if (nullable !== null) {
return [e, ".nil? ? nil : ", this.fromDynamic(nullable, e)];
}
const expression = [this.nameForNamedType(unionType), ".from_dynamic!(", e, ")"];
return optional ? [e, " ? ", expression, " : nil"] : expression;
}
);
}
private toDynamic(t: Type, e: Sourcelike, optional: boolean = false): Sourcelike {
if (this.marshalsImplicitlyToDynamic(t)) {
return e;
}
return matchType<Sourcelike>(
t,
_anyType => e,
_nullType => e,
_boolType => e,
_integerType => e,
_doubleType => e,
_stringType => e,
arrayType => [e, optional ? "&" : "", ".map { |x| ", this.toDynamic(arrayType.items, "x"), " }"],
_classType => [e, optional ? "&" : "", ".to_dynamic"],
mapType => [e, optional ? "&" : "", ".map { |k, v| [k, ", this.toDynamic(mapType.values, "v"), "] }.to_h"],
_enumType => e,
unionType => {
const nullable = nullableFromUnion(unionType);
if (nullable !== null) {
return this.toDynamic(nullable, e, true);
}
if (this.marshalsImplicitlyToDynamic(unionType)) {
return e;
}
return [e, optional ? "&" : "", ".to_dynamic"];
}
);
}
private marshalsImplicitlyToDynamic(t: Type): boolean {
return matchType<boolean>(
t,
_anyType => true,
_nullType => true,
_boolType => true,
_integerType => true,
_doubleType => true,
_stringType => true,
arrayType => this.marshalsImplicitlyToDynamic(arrayType.items),
_classType => false,
mapType => this.marshalsImplicitlyToDynamic(mapType.values),
_enumType => true,
unionType => {
const nullable = nullableFromUnion(unionType);
if (nullable !== null) {
return this.marshalsImplicitlyToDynamic(nullable);
}
return false;
}
);
}
private emitBlock(source: Sourcelike, emit: () => void) {
this.emitLine(source);
this.indent(emit);
this.emitLine("end");
}
private emitClass(c: ClassType, className: Name) {
this.emitDescription(this.descriptionForType(c));
this.emitBlock(["class ", className, " < Dry::Struct"], () => {
let table: Sourcelike[][] = [];
let count = c.properties.count();
this.forEachClassProperty(c, "none", (name, jsonName, p) => {
const last = --count === 0;
const description = this.descriptionForClassProperty(c, jsonName);
const attribute = [
["attribute :", name, ","],
[" ", this.dryType(p.type), p.isOptional ? ".optional" : ""]
];
if (description !== undefined) {
if (table.length > 0) {
this.emitTable(table);
table = [];
}
this.ensureBlankLine();
this.emitDescriptionBlock(description);
this.emitLine(attribute);
if (!last) {
this.ensureBlankLine();
}
} else {
table.push(attribute);
}
});
if (table.length > 0) {
this.emitTable(table);
}
if (this._justTypes) {
return;
}
this.ensureBlankLine();
this.emitBlock(["def self.from_dynamic!(d)"], () => {
this.emitLine(`raise ArgumentError, "Argument is not a hash" unless d.is_a? Hash`);
this.emitLine("new(");
this.indent(() => {
const inits: Sourcelike[][] = [];
this.forEachClassProperty(c, "none", (name, jsonName, p) => {
const dynamic = p.isOptional
? // If key is not found in hash, this will be nil
`d["${stringEscape(jsonName)}"]`
: // This will raise a runtime error if the key is not found in the hash
`d.fetch("${stringEscape(jsonName)}")`;
const expression = this.fromDynamic(p.type, dynamic, p.isOptional);
inits.push([[name, ": "], [expression, ","]]);
});
this.emitTable(inits);
});
this.emitLine(")");
});
this.ensureBlankLine();
this.emitBlock("def self.from_json!(json)", () => {
this.emitLine("from_dynamic!(JSON.parse(json))");
});
this.ensureBlankLine();
this.emitBlock(["def to_dynamic"], () => {
this.emitLine("{");
this.indent(() => {
const inits: Sourcelike[][] = [];
this.forEachClassProperty(c, "none", (name, jsonName, p) => {
const expression = this.toDynamic(p.type, ["@", name], p.isOptional);
inits.push([[`"${stringEscape(jsonName)}"`], [" => ", expression, ","]]);
});
this.emitTable(inits);
});
this.emitLine("}");
});
this.ensureBlankLine();
this.emitBlock("def to_json(options = nil)", () => {
this.emitLine("JSON.generate(to_dynamic, options)");
});
});
}
private emitEnum(e: EnumType, enumName: Name) {
this.emitDescription(this.descriptionForType(e));
this.emitBlock(["module ", enumName], () => {
const table: Sourcelike[][] = [];
this.forEachEnumCase(e, "none", (name, json) => {
table.push([[name], [` = "${stringEscape(json)}"`]]);
});
this.emitTable(table);
});
}
private emitUnion(u: UnionType, unionName: Name) {
this.emitDescription(this.descriptionForType(u));
this.emitBlock(["class ", unionName, " < Dry::Struct"], () => {
const table: Sourcelike[][] = [];
this.forEachUnionMember(u, u.children, "none", null, (name, t) => {
table.push([["attribute :", name, ", "], [this.dryType(t, true)]]);
});
this.emitTable(table);
if (this._justTypes) {
return;
}
this.ensureBlankLine();
const [maybeNull, nonNulls] = removeNullFromUnion(u, false);
this.emitBlock("def self.from_dynamic!(d)", () => {
const memberNames = u.children.map((member: Type) => this.nameForUnionMember(u, member));
this.forEachUnionMember(u, u.children, "none", null, (name, t) => {
const nilMembers = memberNames
.remove(name)
.toArray()
.map((memberName: Name) => [", ", memberName, ": nil"]);
if (this.marshalsImplicitlyToDynamic(t)) {
this.emitBlock(["if schema[:", name, "].right.valid? d"], () => {
this.emitLine("return new(", name, ": d", nilMembers, ")");
});
} else {
this.emitLine("begin");
this.indent(() => {
this.emitLine("value = ", this.fromDynamic(t, "d"));
this.emitBlock(["if schema[:", name, "].right.valid? value"], () => {
this.emitLine("return new(", name, ": value", nilMembers, ")");
});
});
this.emitLine("rescue");
this.emitLine("end");
}
});
this.emitLine(`raise "Invalid union"`);
});
this.ensureBlankLine();
this.emitBlock("def self.from_json!(json)", () => {
this.emitLine("from_dynamic!(JSON.parse(json))");
});
this.ensureBlankLine();
this.emitBlock("def to_dynamic", () => {
let first = true;
this.forEachUnionMember(u, nonNulls, "none", null, (name, t) => {
this.emitLine(first ? "if" : "elsif", " @", name, " != nil");
this.indent(() => {
this.emitLine(this.toDynamic(t, ["@", name]));
});
first = false;
});
if (maybeNull !== null) {
this.emitLine("else");
this.indent(() => {
this.emitLine("nil");
});
}
this.emitLine("end");
});
this.ensureBlankLine();
this.emitBlock("def to_json(options = nil)", () => {
this.emitLine("JSON.generate(to_dynamic, options)");
});
});
}
private emitEnumDeclaration(e: EnumType, name: Name) {
const cases: Sourcelike[][] = [];
this.forEachEnumCase(e, "none", (_name, json) => {
cases.push([cases.length === 0 ? "" : ", ", `"${stringEscape(json)}"`]);
});
this.emitLine(name, " = Types::Strict::String.enum(", ...cases, ")");
}
protected emitSourceStructure() {
if (this.leadingComments !== undefined) {
this.emitCommentLines(this.leadingComments);
} else if (!this._justTypes) {
this.emitLine("# This code may look unusually verbose for Ruby (and it is), but");
this.emitLine("# it performs some subtle and complex validation of JSON data.");
this.emitLine("#");
this.emitLine("# To parse this JSON, add 'dry-struct' and 'dry-types' gems, then do:");
this.emitLine("#");
this.forEachTopLevel("none", (topLevel, name) => {
const variable = modifySource(snakeCase, name);
this.emitLine("# ", variable, " = ", name, ".from_json! ", this.jsonSample(topLevel));
this.emitLine("# puts ", this.exampleUse(topLevel, variable));
this.emitLine("#");
});
this.emitLine("# If from_json! succeeds, the value returned matches the schema.");
}
this.ensureBlankLine();
this.emitLine("require 'json'");
this.emitLine("require 'dry-types'");
this.emitLine("require 'dry-struct'");
this.ensureBlankLine();
this.emitBlock(["module Types"], () => {
this.emitLine("include Dry::Types.module");
this.emitLine("StrictDouble = Strict::Int | Strict::Float");
this.forEachNamedType(
"none",
(_c, _n) => undefined,
(e, n) => this.emitEnumDeclaration(e, n),
(_u, _n) => undefined
);
});
this.forEachDeclaration("leading-and-interposing", decl => {
if (decl.kind === "forward") {
this.emitCommentLines(["(forward declaration)"]);
this.emitLine("class ", this.nameForNamedType(decl.type), " < Dry::Struct; end");
}
});
this.forEachNamedType(
"leading-and-interposing",
(c, n) => this.emitClass(c, n),
(e, n) => this.emitEnum(e, n),
(u, n) => this.emitUnion(u, n)
);
if (!this._justTypes) {
this.forEachTopLevel(
"leading-and-interposing",
(topLevel, name) => {
const self = modifySource(snakeCase, name);
// The json gem defines to_json on maps and primitives, so we only need to supply
// it for arrays.
const needsToJsonDefined = "array" === topLevel.kind;
this.emitBlock(["class ", name], () => {
this.emitBlock(["def self.from_json!(json)"], () => {
if (needsToJsonDefined) {
this.emitLine(
self,
" = ",
this.fromDynamic(topLevel, "JSON.parse(json, quirks_mode: true)")
);
this.emitBlock([self, ".define_singleton_method(:to_json) do"], () => {
this.emitLine("JSON.generate(", this.toDynamic(topLevel, "self"), ")");
});
this.emitLine(self);
} else {
this.emitLine(this.fromDynamic(topLevel, "JSON.parse(json, quirks_mode: true)"));
}
});
});
},
t => this.namedTypeToNameForTopLevel(t) === undefined
);
}
}
}

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

@ -0,0 +1,359 @@
export const keywords = [
"__ENCODING__",
"__FILE__",
"__LINE__",
"alias",
"and",
"begin",
"BEGIN",
"break",
"case",
"class",
"def",
"defined?",
"do",
"else",
"elsif",
"end",
"END",
"ensure",
"false",
"for",
"if",
"in",
"module",
"next",
"nil",
"not",
"or",
"redo",
"rescue",
"retry",
"return",
"self",
"super",
"then",
"true",
"undef",
"unless",
"until",
"when",
"while",
"yield"
];
const globalClasses = [
"ArgumentError",
"Array",
"BasicObject",
"Class",
"ClosedQueueError",
"Comparable",
"Complex",
"ConditionVariable",
"Continuation",
"Data",
"Date",
"Dir",
"ENV",
"EOFError",
"Encoding",
"EncodingError",
"Enumerable",
"Enumerator",
"Errno",
"Exception",
"FalseClass",
"Fiber",
"FiberError",
"File",
"FileTest",
"Float",
"FloatDomainError",
"FrozenError",
"GC",
"Hash",
"IO",
"IOError",
"IndexError",
"Integer",
"Interrupt",
"KeyError",
"LoadError",
"LocalJumpError",
"Marshal",
"MatchData",
"Math",
"Method",
"Module",
"Mutex",
"NameError",
"NilClass",
"NoMemoryError",
"NoMethodError",
"NotImplementedError",
"Numeric",
"Object",
"ObjectSpace",
"Proc",
"Process",
"Queue",
"Random",
"Range",
"RangeError",
"Rational",
"Regexp",
"RegexpError",
"RubyVM",
"RuntimeError",
"ScriptError",
"SecurityError",
"Set",
"Signal",
"SignalException",
"SizedQueue",
"StandardError",
"StopIteration",
"String",
"Struct",
"Symbol",
"SyntaxError",
"SystemCallError",
"SystemExit",
"SystemStackError",
"Thread",
"ThreadError",
"ThreadGroup",
"Time",
"TracePoint",
"TrueClass",
"TypeError",
"UnboundMethod",
"UncaughtThrowError",
"Undefined",
"UnicodeNormalize",
"Warning",
"ZeroDivisionError"
];
const kernel = [
"__callee__",
"__dir__",
"__id__",
"__method__",
"__send__",
"!",
"!=",
"!~",
"<",
"<=",
"<=>",
"==",
"===",
"=~",
">",
">=",
"abort",
"ancestors",
"Array",
"at_exit",
"autoload",
"autoload?",
"binding",
"block_given?",
"caller",
"caller_locations",
"catch",
"class",
"class_eval",
"class_exec",
"class_variable_defined?",
"class_variable_get",
"class_variable_set",
"class_variables",
"clone",
"Complex",
"const_defined?",
"const_get",
"const_missing",
"const_set",
"constants",
"define_singleton_method",
"deprecate_constant",
"display",
"dup",
"enum_for",
"eql?",
"equal?",
"eval",
"exec",
"exit",
"exit!",
"extend",
"fail",
"Float",
"fork",
"format",
"freeze",
"frozen?",
"gets",
"global_variables",
"hash",
"Hash",
"include",
"include?",
"included_modules",
"inspect",
"instance_eval",
"instance_exec",
"instance_method",
"instance_methods",
"instance_of?",
"instance_variable_defined?",
"instance_variable_get",
"instance_variable_set",
"instance_variables",
"Integer",
"is_a?",
"iterator?",
"itself",
"kind_of?",
"lambda",
"load",
"local_variables",
"loop",
"method",
"method_defined?",
"methods",
"module_eval",
"module_exec",
"name",
"nil?",
"object_id",
"open",
"p",
"prepend",
"print",
"printf",
"private_class_method",
"private_constant",
"private_instance_methods",
"private_method_defined?",
"private_methods",
"proc",
"protected_instance_methods",
"protected_method_defined?",
"protected_methods",
"public_class_method",
"public_constant",
"public_instance_method",
"public_instance_methods",
"public_method",
"public_method_defined?",
"public_methods",
"public_send",
"putc",
"puts",
"raise",
"rand",
"Rational",
"readline",
"readlines",
"remove_class_variable",
"remove_instance_variable",
"require",
"require_relative",
"respond_to?",
"select",
"send",
"set_trace_func",
"singleton_class",
"singleton_class?",
"singleton_method",
"singleton_methods",
"sleep",
"spawn",
"sprintf",
"srand",
"String",
"syscall",
"system",
"taint",
"tainted?",
"tap",
"test",
"throw",
"to_enum",
"to_s",
"trace_var",
"trap",
"trust",
"untaint",
"untrace_var",
"untrust",
"untrusted?",
"warn"
];
export const globals = kernel.concat(globalClasses);
export const reservedProperties = [
"__id__",
"__send__",
"call",
"class",
"clone",
"constrained_type",
"constrained?",
"constrained",
"constructor",
"default",
"define_singleton_method",
"display",
"dup",
"enum_for",
"enum",
"extend",
"freeze",
"gem",
"hash",
"inspect",
"instance_eval",
"instance_exec",
"instance_variable_defined?",
"instance_variable_get",
"instance_variable_set",
"instance_variables",
"itself",
"meta",
"method",
"methods",
"object_id",
"optional",
"options",
"pristine",
"private_methods",
"protected_methods",
"public_method",
"public_methods",
"public_send",
"remove_instance_variable",
"rule",
"safe",
"send",
"singleton_class",
"singleton_method",
"singleton_methods",
"taint",
"tap",
"to_ast",
"to_enum",
"to_json",
"to_s",
"trust",
"try",
"type",
"untaint",
"untrust",
"with"
];

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

@ -473,6 +473,7 @@ export const allFixtures: Fixture[] = [
new JSONFixture(languages.GoLanguage),
new JSONFixture(languages.CPlusPlusLanguage),
new JSONFixture(languages.RustLanguage),
new JSONFixture(languages.RubyLanguage),
new JSONFixture(languages.ElmLanguage),
new JSONFixture(languages.SwiftLanguage),
new JSONFixture(languages.ObjectiveCLanguage),

2
test/fixtures/ruby/.bundle/config поставляемый Normal file
Просмотреть файл

@ -0,0 +1,2 @@
---
BUNDLE_PATH: "vendor/bundle"

1
test/fixtures/ruby/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
vendor

4
test/fixtures/ruby/Gemfile поставляемый Normal file
Просмотреть файл

@ -0,0 +1,4 @@
source 'https://rubygems.org'
gem 'dry-types'
gem 'dry-struct'

41
test/fixtures/ruby/Gemfile.lock поставляемый Normal file
Просмотреть файл

@ -0,0 +1,41 @@
GEM
remote: https://rubygems.org/
specs:
concurrent-ruby (1.0.5)
dry-configurable (0.7.0)
concurrent-ruby (~> 1.0)
dry-container (0.6.0)
concurrent-ruby (~> 1.0)
dry-configurable (~> 0.1, >= 0.1.3)
dry-core (0.4.4)
concurrent-ruby (~> 1.0)
dry-equalizer (0.2.0)
dry-logic (0.4.2)
dry-container (~> 0.2, >= 0.2.6)
dry-core (~> 0.2)
dry-equalizer (~> 0.2)
dry-struct (0.4.0)
dry-core (~> 0.4, >= 0.4.1)
dry-equalizer (~> 0.2)
dry-types (~> 0.12, >= 0.12.2)
ice_nine (~> 0.11)
dry-types (0.12.2)
concurrent-ruby (~> 1.0)
dry-configurable (~> 0.1)
dry-container (~> 0.3)
dry-core (~> 0.2, >= 0.2.1)
dry-equalizer (~> 0.2)
dry-logic (~> 0.4, >= 0.4.2)
inflecto (~> 0.0.0, >= 0.0.2)
ice_nine (0.11.2)
inflecto (0.0.2)
PLATFORMS
ruby
DEPENDENCIES
dry-struct
dry-types
BUNDLED WITH
1.16.1

9
test/fixtures/ruby/main.rb поставляемый Executable file
Просмотреть файл

@ -0,0 +1,9 @@
#!/usr/bin/env ruby
require 'json'
require './TopLevel.rb'
json = File.read(ARGV[0])
top = TopLevel.from_json! json
puts top.to_json

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

@ -39,11 +39,7 @@ export const CSharpLanguage: Language = {
skipMiscJSON: false,
skipSchema: [],
rendererOptions: {},
quickTestRendererOptions: [
{ "array-type": "list" },
{ "csharp-version": "5" },
{ density: "dense" }
],
quickTestRendererOptions: [{ "array-type": "list" }, { "csharp-version": "5" }, { density: "dense" }],
sourceFiles: ["src/Language/CSharp.ts"]
};
@ -60,11 +56,7 @@ export const JavaLanguage: Language = {
allowMissingNull: false,
output: "src/main/java/io/quicktype/TopLevel.java",
topLevel: "TopLevel",
skipJSON: [
"identifiers.json",
"simple-identifiers.json",
"nst-test-suite.json"
],
skipJSON: ["identifiers.json", "simple-identifiers.json", "nst-test-suite.json"],
skipMiscJSON: false,
skipSchema: ["keyword-unions.schema"], // generates classes with names that are case-insensitively equal
rendererOptions: {},
@ -98,6 +90,26 @@ export const RustLanguage: Language = {
sourceFiles: ["src/Language/Rust.ts"]
};
export const RubyLanguage: Language = {
name: "ruby",
base: "test/fixtures/ruby",
setupCommand: "bundle install --path vendor/bundle",
compileCommand: "true",
runCommand(sample: string) {
return `bundle exec main.rb "${sample}"`;
},
diffViaSchema: true,
allowMissingNull: true,
output: "TopLevel.rb",
topLevel: "TopLevel",
skipJSON: [],
skipSchema: [],
skipMiscJSON: false,
rendererOptions: {},
quickTestRendererOptions: [],
sourceFiles: ["src/Language/Ruby/index.ts"]
};
export const GoLanguage: Language = {
name: "golang",
base: "test/fixtures/golang",
@ -108,12 +120,7 @@ export const GoLanguage: Language = {
allowMissingNull: false,
output: "quicktype.go",
topLevel: "TopLevel",
skipJSON: [
"identifiers.json",
"simple-identifiers.json",
"blns-object.json",
"nst-test-suite.json"
],
skipJSON: ["identifiers.json", "simple-identifiers.json", "blns-object.json", "nst-test-suite.json"],
skipMiscJSON: false,
skipSchema: [],
rendererOptions: {},
@ -149,9 +156,10 @@ export const ElmLanguage: Language = {
name: "elm",
base: "test/fixtures/elm",
setupCommand: "rm -rf elm-stuff/build-artifacts && elm-make --yes",
compileCommand: process.env.CI === "true"
? "sysconfcpus -n 1 elm-make Main.elm QuickType.elm --output elm.js"
: "elm-make Main.elm QuickType.elm --output elm.js",
compileCommand:
process.env.CI === "true"
? "sysconfcpus -n 1 elm-make Main.elm QuickType.elm --output elm.js"
: "elm-make Main.elm QuickType.elm --output elm.js",
runCommand(sample: string) {
return `node ./runner.js "${sample}"`;
},
@ -206,11 +214,7 @@ export const SwiftLanguage: Language = {
skipMiscJSON: false,
skipSchema: [],
rendererOptions: {},
quickTestRendererOptions: [
{ "struct-or-class": "class" },
{ density: "dense" },
{ density: "normal" }
],
quickTestRendererOptions: [{ "struct-or-class": "class" }, { density: "dense" }, { density: "normal" }],
sourceFiles: ["src/Language/Swift.ts"]
};