From 946479e4449d9d77da921252ae315801689f8bf3 Mon Sep 17 00:00:00 2001 From: Thanh Nguyen Date: Thu, 23 Mar 2017 09:43:19 +0700 Subject: [PATCH 1/4] Update format js of ES6 --- bin/lb-ios | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/bin/lb-ios b/bin/lb-ios index ccdf4f9..35741d0 100755 --- a/bin/lb-ios +++ b/bin/lb-ios @@ -1,15 +1,15 @@ #!/usr/bin/env node -var fs = require('fs'); -var path = require('path'); -var semver = require('semver'); -var mkdirp = require('mkdirp'); -var optimist = require('optimist'); +let fs = require('fs'); +let path = require('path'); +let semver = require('semver'); +let mkdirp = require('mkdirp'); +let optimist = require('optimist'); -// var generator = require(loopback-sdk-ios-codegen); -var generator = require('../lib/objc-codegen'); +// let generator = require(loopback-sdk-ios-codegen); +let generator = require('../lib/objc-codegen'); -var argv = optimist +let argv = optimist .usage('Generate iOS Objective-C client code for your LoopBack application.' + '\nUsage:' + '\n $0 [options] path-to-app ios-client-dir-path' + @@ -25,27 +25,27 @@ var argv = optimist .argv; try { - var appFile = path.resolve(argv._[0]); - var clientDir = path.resolve(argv._[1]); - var modelPrefix = argv['model-name-prefix']; - var verbose = argv['verbose']; + let appFile = path.resolve(argv._[0]); + let clientDir = path.resolve(argv._[1]); + let modelPrefix = argv['model-name-prefix']; + let verbose = argv['verbose']; console.error('Loading LoopBack app %j', appFile); - var app = require(appFile); + let app = require(appFile); assertLoopBackVersion(app); if (verbose) { console.error('Loopback version: ' + app.loopback.version); } console.error('Generating iOS models with prefix %j', modelPrefix); - var files = generator.objcModels(app, modelPrefix, verbose); + let files = generator.objcModels(app, modelPrefix, verbose); if (!fs.existsSync(clientDir)){ mkdirp.mkdirP.sync(clientDir); } - for (var objcFileName in files) { - var filepath = path.resolve(clientDir, objcFileName); + for (let objcFileName in files) { + let filepath = path.resolve(clientDir, objcFileName); console.log('Writing: ' + filepath); if (verbose) { console.error('--'); From 63e329b6ef49d1e5e26cb7357c3f9f0630c1accc Mon Sep 17 00:00:00 2001 From: Thanh Nguyen Date: Thu, 23 Mar 2017 09:46:55 +0700 Subject: [PATCH 2/4] - Add support include all models have relation with main models. - Support generate all relation models - Support all models include with one file it support on Xcode easy for include to build - Change to ES6 code. - Add support type object --> will change to NSDictionary. + Hard for get from iOS client but can be get. Must find better solution here. - Test using in Xcode 8 with swift3 ok. --- lib/objc-codegen.js | 259 ++++++++++++++++++++++++++++++++------------ 1 file changed, 187 insertions(+), 72 deletions(-) diff --git a/lib/objc-codegen.js b/lib/objc-codegen.js index d7eab6e..68edb91 100644 --- a/lib/objc-codegen.js +++ b/lib/objc-codegen.js @@ -3,9 +3,10 @@ // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT -var fs = require('fs'); -var ejs = require('ejs'); -var pascalCase = require('pascal-case'); +let fs = require('fs'); +let ejs = require('ejs'); +let pascalCase = require('pascal-case'); +let modelsName = Array(); /** * Generate iOS Client-side Objective-C representation of the models. @@ -15,45 +16,85 @@ var pascalCase = require('pascal-case'); */ exports.objcModels = function generateServices(app, modelPrefix, verbose) { - var models = describeModels(app); + let models = describeModels(app); + + /* Store list models name */ + for (let modelName in models) { + + if (models.hasOwnProperty(modelName)) { + modelsName[modelsName.length] = models[modelName].name; + + /* Update list include data from other model */ + models[modelName].moreInclude = []; + models[modelName].moreRepo = []; + + for (let relation in models[modelName].relations) { + + if (models[modelName].relations.hasOwnProperty(relation)) { + let relationModel = models[modelName].relations[relation].model; + + if(!((typeof relationModel) === 'undefined')) { + if (relationModel != models[modelName].name && models[modelName].moreInclude.indexOf(relationModel) == -1) { + models[modelName].moreInclude[models[modelName].moreInclude.length] = modelPrefix + pascalCase(relationModel); + } + } + + relationModel = models[modelName].relations[relation].through; + + if(!((typeof relationModel) === 'undefined')) { + if (relationModel != models[modelName].name && models[modelName].moreInclude.indexOf(relationModel) == -1) { + models[modelName].moreInclude[models[modelName].moreInclude.length] = modelPrefix + pascalCase(relationModel); + } + } + + } + } + } + + } addObjCNames(models, modelPrefix, verbose); - var objcModelHTemplate = readTemplate('./objc-model-h.ejs'); - var objcModelMTemplate = readTemplate('./objc-model-m.ejs'); - var objcRepoHTemplate = readTemplate('./objc-repo-h.ejs'); - var objcRepoMTemplate = readTemplate('./objc-repo-m.ejs'); + let objcModelHTemplate = readTemplate('./objc-model-h.ejs'); + let objcModelMTemplate = readTemplate('./objc-model-m.ejs'); + let objcRepoHTemplate = readTemplate('./objc-repo-h.ejs'); + let objcRepoMTemplate = readTemplate('./objc-repo-m.ejs'); + let objcAllMTemplate = readTemplate('./objc-all-h.ejs'); - var ret = {}; + let ret = {}; - for (var modelName in models) { - var modelDesc = models[modelName]; - var objcModelName = models[modelName].objcModelName; + for (let modelName in models) { + let modelDesc = models[modelName]; + let objcModelName = models[modelName].objcModelName; - var script = renderContent(objcModelHTemplate, modelDesc); + let script = renderContent(objcModelHTemplate, modelDesc); ret[objcModelName + '.h'] = script; - var script = renderContent(objcModelMTemplate, modelDesc); + script = renderContent(objcModelMTemplate, modelDesc); ret[objcModelName + '.m'] = script; - var script = renderContent(objcRepoHTemplate, modelDesc); + script = renderContent(objcRepoHTemplate, modelDesc); ret[objcModelName + 'Repository.h'] = script; - var script = renderContent(objcRepoMTemplate, modelDesc); + script = renderContent(objcRepoMTemplate, modelDesc); ret[objcModelName + 'Repository.m'] = script; } + /* Create include all for easy use */ + let allScript = renderContent(objcAllMTemplate, models); + ret["LoopbackModelImport.h"] = allScript; + return ret; }; function describeModels(app) { - var result = {}; - for(var model in app.models) { + let result = {}; + for(let model in app.models) { model.get; } app.handler('rest').adapter.getClasses().forEach(function(c) { - var name = c.name; - var modelDefinition = app.models[name].definition; + let name = c.name; + let modelDefinition = app.models[name].definition; if (!c.ctor) { // Skip classes that don't have a shared ctor @@ -63,7 +104,7 @@ function describeModels(app) { } // Skip the User class as its Obj-C implementation is provided as a part of the SDK framework. - var isUser = c.sharedClass.ctor.prototype instanceof app.loopback.User || + let isUser = c.sharedClass.ctor.prototype instanceof app.loopback.User || c.sharedClass.ctor.prototype === app.loopback.User.prototype; if (isUser) { return; @@ -85,7 +126,7 @@ function describeModels(app) { c.validations = modelDefinition.settings.validations; c.methods.forEach(function fixArgsOfPrototypeMethods(method) { - var ctor = method.restClass.ctor; + let ctor = method.restClass.ctor; if (!ctor || method.sharedMethod.isStatic) return; method.accepts = ctor.accepts.concat(method.accepts); }); @@ -98,16 +139,16 @@ function describeModels(app) { return result; } -var SCOPE_METHOD_REGEX = /^prototype.__([^_]+)__(.+)$/; +let SCOPE_METHOD_REGEX = /^prototype.__([^_]+)__(.+)$/; function buildScopes(models) { - for (var modelName in models) { + for (let modelName in models) { buildScopesOfModel(models, modelName); } } function buildScopesOfModel(models, modelName) { - var modelClass = models[modelName]; + let modelClass = models[modelName]; modelClass.scopes = {}; modelClass.methods.forEach(function(method) { @@ -118,16 +159,21 @@ function buildScopesOfModel(models, modelName) { } // reverse-engineer scope method -// defined by loopback-datasource-juggler/lib/scope.js +// defined by loopback-datasource-juggler/utility/scope.js function buildScopeMethod(models, modelName, method) { - var modelClass = models[modelName]; - var match = method.name.match(SCOPE_METHOD_REGEX); + let modelClass = models[modelName]; + let match = method.name.match(SCOPE_METHOD_REGEX); if (!match) return; - var op = match[1]; - var scopeName = match[2]; - var modelPrototype = modelClass.sharedClass.ctor.prototype; - var targetClass = modelPrototype[scopeName]._targetClass; + let op = match[1]; + let scopeName = match[2]; + let modelPrototype = modelClass.sharedClass.ctor.prototype; + let targetClass = modelPrototype[scopeName]._targetClass; + + // Check rename method in special case + if (match[0].indexOf("prototype.") != -1) { + method.name = op + "__" + scopeName + } if (modelClass.scopes[scopeName] === undefined) { if (!targetClass) { @@ -159,7 +205,7 @@ function buildScopeMethod(models, modelName, method) { return; } - var apiName = scopeName; + let apiName = scopeName; if (op == 'get') { // no-op, create the scope accessor } else if (op == 'delete') { @@ -168,14 +214,16 @@ function buildScopeMethod(models, modelName, method) { apiName += '.' + op; } - var scopeMethod = Object.create(method); - scopeMethod.name = reverseName; + let scopeMethod = Object.create(method); + /* Remove reverseName, it's always undefined and somethings error */ + scopeMethod.name = scopeName; + //scopeMethod.name = reverseName; // override possibly inherited values scopeMethod.deprecated = false; scopeMethod.internal = false; modelClass.scopes[scopeName].methods[apiName] = scopeMethod; if(scopeMethod.name.match(/create/)){ - var scopeCreateMany = Object.create(scopeMethod); + let scopeCreateMany = Object.create(scopeMethod); scopeCreateMany.name = scopeCreateMany.name.replace( /create/, 'createMany' @@ -187,30 +235,31 @@ function buildScopeMethod(models, modelName, method) { } function findModelByName(models, name) { - for (var n in models) { + for (let n in models) { if (n.toLowerCase() == name.toLowerCase()) return models[n]; } } -var methodNamesToSkip = [ +let methodNamesToSkip = [ // The followings are pre-implemented in LBPersistedModel. 'create', 'upsert', 'deleteById', // The followings are to be supported. 'createChangeStream', - 'prototype.updateAttributes' + 'prototype.updateAttributes', + 'prototype.patchAttributes' ]; -var objcMethodNamesToSkip = [ +let objcMethodNamesToSkip = [ // The following is skipped since `updateAll` invocation fails with an empty `where` argument // and there is no way to provide a working implementation for it. 'updateAllWithData' ]; // Amend auto-generated method names which don't sound right. -var methodNameReplacementTable = { +let methodNameReplacementTable = { 'findByIdWithId': 'findById', 'findWithSuccess': 'allWithSuccess', 'updateAllWithWhere': 'updateAllWithWhereFilter', @@ -219,23 +268,63 @@ var methodNameReplacementTable = { // Type declaration conversion table for properties. // To list all the conversion rules in a uniform manner, `<...>` notation is introduced. -var propTypeConversionTable = { +let propTypeConversionTable = { + 'String': '(nonatomic, copy) NSString *', + 'Number': 'NSNumber *', + 'Boolean': 'BOOL ', + '': '(nonatomic) NSArray *', + 'ObjectID': '(nonatomic, copy) NSString *', + 'Date': 'NSDate *', + 'object': 'NSDictionary *', + 'Object': 'NSDictionary *' +}; + +// Swift to list all the conversion rules in a uniform manner, `<...>` notation is introduced. +let swiftPropTypeConversionTable = { 'String': '(nonatomic, copy) NSString *', 'Number': 'NSNumber *', 'Boolean': 'BOOL ', - '': '(nonatomic) NSArray *' + '': '(nonatomic) NSArray *', + 'ObjectID': '(nonatomic, copy) NSString *' }; // Type conversion table for arguments. // To list all the conversion rules in a uniform manner, `<...>` notation is introduced. -var argTypeConversionTable = { +let argTypeConversionTable = { 'object data': '', // Special case: the argument whose type is `object` and name is `data`. 'object': 'NSDictionary *', - 'any': 'id' -} + 'any': 'id', + 'boolean': 'NSNumber *', + 'Boolean': 'NSNumber *', + 'string': 'NSString *', + 'String': 'NSString *', + 'number': 'NSNumber *' +}; + +// Swift To list all the conversion rules in a uniform manner, `<...>` notation is introduced. +let swiftArgTypeConversionTable = { + 'object data': '', // Special case: the argument whose type is `object` and name is `data`. + 'object': 'NSDictionary?', + 'any': 'id', + 'boolean': 'NSNumber?', + 'Boolean': 'NSNumber?', + 'string': 'NSString?', + 'String': 'NSString?', + 'number': 'NSNumber?' +}; + // Return type to Obj-C return type conversion table. -var returnTypeConversionTable = { +let returnTypeConversionTable = { + 'object': 'NSDictionary', + 'number': 'NSNumber', + 'boolean': 'BOOL', + '': 'NSArray', + '': 'void' +}; + +// Return type to Swift return type conversion table. +let swiftReturnTypeConversionTable = { 'object': 'NSDictionary', 'number': 'NSNumber', 'boolean': 'BOOL', @@ -244,11 +333,11 @@ var returnTypeConversionTable = { }; function addObjCNames(models, modelPrefix, verbose) { - for (var modelName in models) { + for (let modelName in models) { if (verbose) { console.error('\nProcessing model: "' + modelName + '"...'); } - var meta = models[modelName]; + let meta = models[modelName]; meta.objcModelName = modelPrefix + pascalCase(modelName); meta.objcRepoName = meta.objcModelName + 'Repository'; if (meta.baseModel === 'Model' || meta.baseModel === 'PersistedModel') { @@ -257,16 +346,16 @@ function addObjCNames(models, modelPrefix, verbose) { throw new Error('Unknown base model: "' + meta.baseModel + '" for model: "' + modelName + '"'); } meta.objcProps = []; - for (var propName in meta.props) { + for (let propName in meta.props) { if (propName === 'id') { // `_id` is already defined in LBPersistedModel continue; } - var prop = meta.props[propName]; + let prop = meta.props[propName]; if (verbose) { console.error(' Property: "' + propName + '"', prop); } - var objcProp = convertToObjCPropType(prop.type); + let objcProp = convertToObjCPropType(prop.type); if (typeof objcProp === 'undefined') { throw new Error( 'Unsupported property type: "' + prop.type.name + '" in model: "' + modelName + '"'); @@ -291,23 +380,29 @@ function addObjCMethodInfo(meta, method, modelName, skipOptionalArguments) { if (methodNamesToSkip.indexOf(method.name) >= 0) { return; } - var methodPrototype = ''; - var methodName = method.name; - var paramAssignments; - var bodyParamAssignments; + let methodPrototype = ''; + let methodName = method.name; + methodName = methodName.replace("prototype.", ""); + let paramAssignments; + let bodyParamAssignments; method.accepts.forEach(function (param) { - var paramRequired = param.required || (param.http && param.http.source === 'body'); + let paramRequired = param.required || (param.http && param.http.source === 'body'); if (!paramRequired && skipOptionalArguments) { return; } - var objcModelType = meta.objcModelName + ' *'; - var argType = convertToObjCArgType(param.type, param.arg, objcModelType); + + let objcModelType = meta.objcModelName + ' *'; + if(typeof(param.model) == "string") { + objcModelType = param.model + ' *'; + } + + let argType = convertToObjCArgType(param.type, param.arg, objcModelType); if (typeof argType === 'undefined') { throw new Error( 'Unsupported argument type: "' + param.type + '" in model: "' + modelName + '"'); } - var argName = (param.arg === 'id') ? 'id_' : param.arg; - var argRightValue = argName; + let argName = (param.arg === 'id') ? 'id_' : param.arg; + let argRightValue = argName; if (argType === objcModelType) { argRightValue = '[' + param.arg + ' toDictionary]'; } else if (argType === 'NSDictionary *') { @@ -336,15 +431,15 @@ function addObjCMethodInfo(meta, method, modelName, skipOptionalArguments) { methodPrototype += ':(' + argType + ')' + argName; }); - var returnArg = method.returns[0] && method.returns[0].arg; - var returnType = method.returns[0] && method.returns[0].type; - var objcReturnType = convertToObjCReturnType(returnType, modelName, meta.objcModelName); + let returnArg = method.returns[0] && method.returns[0].arg; + let returnType = method.returns[0] && method.returns[0].type; + let objcReturnType = convertToObjCReturnType(returnType, modelName, meta.objcModelName); if (typeof objcReturnType === 'undefined') { throw new Error( 'Unsupported return type: "' + returnType + '" in method: "' + method.name + '" of model: "' + modelName + '"'); } - var successBlockType = convertToObjCSuccessBlockType(objcReturnType); + let successBlockType = convertToObjCSuccessBlockType(objcReturnType); if (methodName === method.name) { methodName += 'WithSuccess'; @@ -361,12 +456,17 @@ function addObjCMethodInfo(meta, method, modelName, skipOptionalArguments) { methodName = methodNameReplacementTable[methodName]; } methodPrototype = '(void)' + methodName + methodPrototype; + let newTypeReturn = ""; + if(Array.isArray(returnType)) { + newTypeReturn = returnType[0]; + } meta.objcMethods.push({ rawName: method.name, prototype: methodPrototype, returnArg: returnArg, objcReturnType: objcReturnType, + originObjcReturnType: newTypeReturn, paramAssignments: paramAssignments, bodyParamAssignments: bodyParamAssignments }); @@ -374,9 +474,9 @@ function addObjCMethodInfo(meta, method, modelName, skipOptionalArguments) { } function hasOptionalArguments(method) { - for (var idx in method.accepts) { - var param = method.accepts[idx]; - var paramRequired = param.required || (param.http && param.http.source === 'body'); + for (let idx in method.accepts) { + let param = method.accepts[idx]; + let paramRequired = param.required || (param.http && param.http.source === 'body'); if (!paramRequired) { return true; } @@ -392,7 +492,7 @@ function convertToObjCPropType(type) { } function convertToObjCArgType(type, name, objcModelType) { - var objcType = argTypeConversionTable[type + ' ' + name]; + let objcType = argTypeConversionTable[type + ' ' + name]; objcType = objcType || argTypeConversionTable[type]; if (objcType) { objcType = objcType.replace('', objcModelType); @@ -404,17 +504,32 @@ function convertToObjCReturnType(type, modelName, objcModelName) { if (type === modelName) { return objcModelName; } + + /* In case not found any type, + * it maybe is object type from other model, must return type name + */ + if (modelsName.indexOf(type) != -1) { + return pascalCase(type); + } + if (typeof type === 'undefined') { type = ''; } if (Array.isArray(type)) { type = ''; } + +// Workaround for run ok + if (typeof type === 'object') { + type = "number" + } + + return returnTypeConversionTable[type]; } function convertToObjCSuccessBlockType(objcType) { - var returnArgType; + let returnArgType; if (objcType === 'void') { returnArgType = ''; } else if (objcType === 'BOOL') { // primitive type @@ -426,7 +541,7 @@ function convertToObjCSuccessBlockType(objcType) { } function readTemplate(filename) { - var ret = fs.readFileSync( + let ret = fs.readFileSync( require.resolve(filename), { encoding: 'utf-8' } ); @@ -434,7 +549,7 @@ function readTemplate(filename) { } function renderContent(template, modelMetaInfo) { - var script = ejs.render( + let script = ejs.render( template, { meta: modelMetaInfo } ); From cc4235044e55e695277946c2eccbc00ec21e3ad5 Mon Sep 17 00:00:00 2001 From: Thanh Nguyen Date: Thu, 23 Mar 2017 09:47:18 +0700 Subject: [PATCH 3/4] Add import all models need for relations of this model --- lib/objc-repo-h.ejs | 6 ++++++ lib/objc-repo-m.ejs | 25 ++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/objc-repo-h.ejs b/lib/objc-repo-h.ejs index cc17beb..4e8cdc5 100644 --- a/lib/objc-repo-h.ejs +++ b/lib/objc-repo-h.ejs @@ -3,6 +3,12 @@ // #import "<%- meta.objcModelName %>.h" +<% meta.moreInclude.forEach(function(model) { +-%> +#import "<%- model %>.h" +<% + }); +-%> @interface <%- meta.objcRepoName %> : <%- meta.objcBaseModel %>Repository diff --git a/lib/objc-repo-m.ejs b/lib/objc-repo-m.ejs index c872676..317bb4d 100644 --- a/lib/objc-repo-m.ejs +++ b/lib/objc-repo-m.ejs @@ -4,6 +4,13 @@ #import "<%- meta.objcRepoName %>.h" +<% meta.moreInclude.forEach(function(model) { +-%> +#import "<%- model %>Repository.h" +<% + }); +-%> + @implementation <%- meta.objcRepoName %> + (instancetype)repository { @@ -14,6 +21,16 @@ return (<%- meta.objcModelName %> *)[super modelWithDictionary:dictionary]; } +<% meta.moreInclude.forEach(function(model) { +%> +- (<%- model %> *)model<%- model %>WithDictionary:(NSDictionary *)dictionary { +<%- model %>Repository * repo = [<%- model %>Repository repositoryWithClassName:@"<%- model %>"]; + return (<%- model %> *)[repo modelWithDictionary:dictionary]; +} +<% + }); +-%> + - (SLRESTContract *)contract { SLRESTContract *contract = [super contract]; @@ -46,10 +63,16 @@ NSAssert([value isKindOfClass:[NSDictionary class]], @"Received non-Dictionary: %@", value); success((NSDictionary *)value); <% } else if (method.objcReturnType === 'NSArray') { -%> +<% + var moreName = "" + if (meta.moreInclude.indexOf(method.originObjcReturnType) != -1) { + moreName = method.originObjcReturnType + } +-%> NSAssert([value isKindOfClass:[NSArray class]], @"Received non-Array: %@", value); NSMutableArray *models = [NSMutableArray array]; [value enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - [models addObject:[self modelWithDictionary:obj]]; + [models addObject:[self model<%- moreName %>WithDictionary:obj]]; }]; success(models); <% } else if (method.objcReturnType === 'BOOL') { -%> From aff5b142c1e7da2892675d742c37c1f9d65392f5 Mon Sep 17 00:00:00 2001 From: Thanh Nguyen Date: Thu, 23 Mar 2017 09:47:37 +0700 Subject: [PATCH 4/4] Add LoopbackModelImport all for easy import in Xcode --- lib/objc-all-h.ejs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 lib/objc-all-h.ejs diff --git a/lib/objc-all-h.ejs b/lib/objc-all-h.ejs new file mode 100644 index 0000000..309f778 --- /dev/null +++ b/lib/objc-all-h.ejs @@ -0,0 +1,13 @@ +// +// LoopbackModelImport.h +// + +<% for (var model in meta) { + if (meta.hasOwnProperty(model)) { +-%> +#import "<%- meta[model].objcModelName %>.h" +#import "<%- meta[model].objcModelName %>Repository.h" +<% + } + } +-%>