2.2.3 • Published 6 years ago

pir-model-preparer v2.2.3

Weekly downloads
-
License
ISC
Repository
-
Last release
6 years ago

Конвертация моделей данных из формата XSD ПИР в формат Sencha Ext JS Model

Инсталяция для пользователя

Установить SWI-Prolog 7.4.2 for Microsoft Windows (64 bit)
http://www.swi-prolog.org/download/stable
Добавить в PATH строку C:\Program Files\swipl\bin.
После этого в командной строке должна быть доступна команда swipl.

Установить зависимости
npm install

Создать пустой пакет (здесь он называется pir-model), где будут размещаться сгененированные модели.

В файле package.json вашего проекта прописать скрипт (здесь он называется pir-model):

"scripts": {
   "pir-model": "node_modules/.bin/pir-model-preparer -c packages/local/pir-model/preparer.config.js"
}

В корне пакета pir-model создать файл preparer.config.js со следующим содержимым:

const extend = require('./preparer/extend');
const prepareClasses = require('./preparer/prepareClasses');

module.exports = {

	dirs: {
		input: '<путь до WSDL-файлов>',
		output: {
			result: 'src'
		}
	},

	namespace: 'Pir.model',
	extend: extend,
	prepareClasses: prepareClasses

};

Здесь namespace укажите для своего проекта.

Создайте файл \packages\local\pir-model\preparer\extend.js со следующим содержимым:

/**
 * Вычисление опции extend для каждой модели.
 */
module.exports = function(model) {
	const m = model, bt = model.baseType;
	return model.baseType ? `${m.namespace}.${bt.path.join('.')}.${bt.className}` : `${m.namespace}.Base`;
};

Создайте файл \packages\local\pir-model\preparer\prepareClasses.js со следующим содержимым:

const fs = require('fs');
const path = require('path');
const _ = require('lodash');
const senchaExtjsGenerator = require('sencha-extjs-generator');

const ModelClass = senchaExtjsGenerator.ModelClass;
const Config = senchaExtjsGenerator.Config;

/**
 * Изменение классов перед их сохранением в пакете pir-model.
 */
module.exports = function(modelClassManager) {
	const common = require('./common')(modelClassManager);

	/**
	 * Создание дополнительных классов.
	 */
	const creators = fs.readdirSync(path.join(__dirname, 'create'));
	creators.forEach(creatorFilename => {
		const creatorFilepath = path.join(__dirname, 'create', creatorFilename);
		// Криэтор класса может создавать как один класс, так и массив классов.
		const cls = require(creatorFilepath)(common);
		modelClassManager.push(cls);
		// Любой класс может содержать функцию _onAfterModelClassManagerPush.
		// Если он есть, то считаем, что класс предполагает обработку после его добавления в менеджер классов.
		(_.isArray(cls) ? cls : [cls]).forEach(cls => {
			if (cls._onAfterModelClassManagerPush) cls._onAfterModelClassManagerPush();
		});
	});

	/**
	 * Переопределение имеющихся классов Override.
	 * Этим добавляется дополнительный функционал в имеющиеся модели данных.
	 */
	const overrides = fs.readdirSync(path.join(__dirname, 'override'));
	overrides.forEach(overrideFilename => {
		const overrideFilepath = path.join(__dirname, 'override', overrideFilename);
		require(overrideFilepath)(common);
	});

};

Создайте файл \packages\local\pir-model\preparer\common.js со следующим содержимым:

const camelcase = require('camelcase');
const pascalcase = require('pascalcase');
const { Config: ExtjsConfig, ModelClass: ExtjsModelClass } = require('sencha-extjs-generator');

/**
 * Общие функции.
 */
module.exports = function(modelClassManager) {

	function getModelClassManager() {
		return modelClassManager;
	}

	/**
	 * Функция для удобной обработки одного класса.
	 * В качестве контекста this для функции prepareFn выступает сам класс.
	 */
	function prepareClass(className, prepareFn) {
		const cls = modelClassManager.find(className);
		if (!cls) console.error('[ERR] prepareClass: Не найден класс "' + className + '".');
		if (cls) prepareFn.bind(cls)(cls);
	}
	
	/**
	 * Функция для создания классов, предназначенных для создания хранилищ 
	 * под результаты разных методов АПИ сервера.
	 * Добавляет название метода сервиса и имя поля, где хранятся массив записей.
	 * @return {ExtjsModelClass}
	 */
	function createInfoClass({ baseClassName, name, serviceMethod, rootProperty }) {
		const baseClass = modelClassManager.find(baseClassName);
		const infoClass = baseClass.clone();
		infoClass.name.value = name;
		infoClass.extend.value = baseClass.name.localName;
		infoClass.proxy.value = new ExtjsConfig([{
			name: 'serviceMethod',
			value: serviceMethod
		}, {
			name: 'reader',
			value: new ExtjsConfig([{
				name: 'rootProperty',
				value: `result.${rootProperty}`
			}])
		}]);
		// Внимание, после добавления класса в менеджер классов добавьте очистку полей:
		// infoClass.fields.clear();
		// Очистку полей можно делать ТОЛЬКО после добавления класса в менеджер классов.
		return infoClass;
	}

	/**
	 * Вычисление baseClassName и rootProperty для классов информационных моделей.
	 * На выходе готовый конфиг для метода createInfoClass.
	 * @param {Object} params
	 * @param {String} params.name Имя класса без неймспейса, например info.DebtorInfoSearchTemplate.
	 * @param {String} params.serviceMethod Имя метода в формате camelCase, например, getManagementCompanySearchTemplates.
	 * @param {String} params.serviceName Имя сервиса = administration | operations | information.
	 * @return {Object}
	 */
	function createInfoClassConfig({ name, serviceMethod, serviceName }) {
		let rootProperty, baseClassName;

		// Имя сервиса в WSDL (точнее имена файлов *.wsdl) отличаются от именования сервисов в клиенте.
		const serviceFolderName = {
			information: 'debtorInformation',
			operations: 'debtorOperations',
			administration: 'debtorAdministration'
		}[serviceName];
		if (!serviceFolderName) throw new Error(`Неизвестный сервис '${serviceName}'.`);
		
		// Ищем класс ответа для данного метода сервера.
		const responseClassName = `${serviceFolderName}.${pascalcase(serviceMethod)}Response`;
		const responseClass = modelClassManager.find(responseClassName);
		if (!responseClass) throw new Error(`Не найден класс '${responseClassName}'.`);
		if (!responseClass.has('hasOne')) throw new Error(`В классе ответа сервера '${responseClassName}' нет hasOne с role==result.`);

		// В классе ответа ищем поле result в разделе hasOne.
		const resultAssociation = responseClass.hasOne.value.find(item => item.find('role').value == 'result');
		// В поле result ищем type. Это будет класс результатов в ответе сервера.
		const resultClassName = resultAssociation.find('type').value;
		const resultClass = modelClassManager.find(resultClassName);
		if (!resultClass) throw new Error(`Не найден класс '${resultClassName}'.`);
		// В классе результата ищем hasMany.
		// Для проверки надо убедится что он есть и в нем только одна запись, иначе ошибка.
		if (!resultClass.has('hasMany')) throw new Error(`В классе результата '${resultClassName}' нет hasMany.`);
		if (resultClass.hasMany.value.length > 1) throw new Error(`В hasMany класса результата '${resultClassName}' больше одной связи.`);
		
		// Также можно проверить наличие totalRecordCount в fields, иначе ошибка.
		if (!resultClass.has('fields')) throw new Error(`В классе '${resultClassName}' нет полей модели.`);
		if (!resultClass.fields.value.find(field => field.find('name').value == 'totalRecordCount')) {
			throw new Error(`В классе '${resultClassName}' не найдено поле totalRecordCount.`);
		}

		// Из hasMany следует вытащить role и сделать его PascalCase.
		rootProperty = pascalcase(resultClass.hasMany.value[0].find('role').value);
		baseClassName = resultClass.hasMany.value[0].find('type').value;

		return { baseClassName, name, serviceMethod, rootProperty, serviceName };
	}

	/**
	 * Создание класса модели для словарей.
	 * Все классы наследуются от класса результата ответа сервера на метод getDictionaryDetailsBySearchParams
	 * На данный момент этим классом является Pir.model.baseModel.type.TBaseDictionary.
	 * Также в extraParams.DictionaryCode включен код словаря dictionaryCode.
	 */
	function createDictionaryDetailClass(dictionaryCode) {

		const serviceMethod = 'getDictionaryDetailsBySearchParams';

		const { baseClassName, rootProperty } = createInfoClassConfig({
			serviceName: 'information', serviceMethod
		});

		const dictionaryDetailClass = new ExtjsModelClass(`dictionary.${dictionaryCode}`, [{
			name: 'proxy',
			value: new ExtjsConfig([{
				name: 'serviceMethod',
				value: serviceMethod
			}, {
				name: 'reader',
				value: {
					name: 'rootProperty',
					value: rootProperty
				}
			}, {
				name: 'extraParams',
				value: {
					name: 'DictionaryCode',
					value: dictionaryCode
				}
			}])
		}]);

		dictionaryDetailClass.extend.value = baseClassName;

		return dictionaryDetailClass;
	}

	return {
		prepareClass,
		createInfoClass,
		createInfoClassConfig,
		createDictionaryDetailClass
	};

}

Создайте каталоги:
\packages\local\pir-model\preparer\create
\packages\local\pir-model\preparer\override
с содержимым аналогичным как в проекте w_pir_client_v2.

Собственно кодогенерацию следует настраивать путем создания скриптов в этих двух директориях.
В директории create создаются скрипты для создания новых моделей.
В директории override создаются оверрайды для существующих.

Запуск кодогенерации

npm run pir-model

Если все было сделано верно, сгенерированные модели будут размещены в директории \packages\local\pir-model\src.

2.2.3

6 years ago

2.2.2

6 years ago

2.2.1

6 years ago

2.2.0

7 years ago

2.1.7

7 years ago

2.1.6

7 years ago

2.1.5

7 years ago

2.1.4

7 years ago

2.1.3

7 years ago

2.1.2

7 years ago

2.1.1

7 years ago

2.1.0

7 years ago

2.0.9

7 years ago

2.0.8

7 years ago

2.0.7

7 years ago

2.0.6

7 years ago

2.0.5

7 years ago

2.0.4

7 years ago

2.0.3

7 years ago

2.0.2

7 years ago

2.0.1

7 years ago

2.0.0

7 years ago