diff --git a/__mocks__/cosmiconfig.ts b/__mocks__/cosmiconfig.ts new file mode 100644 index 0000000..de7f103 --- /dev/null +++ b/__mocks__/cosmiconfig.ts @@ -0,0 +1,13 @@ +import { vol } from 'memfs' + +const loadConfigFile = filePath => { + const fileContent: string = vol.readFileSync(filePath, 'utf8') as string + const config = JSON.parse(fileContent) + return { config } +} + +const explorer = { loadSync: loadConfigFile } + +const cosmiconfig = jest.fn(() => explorer) + +module.exports = cosmiconfig diff --git a/__mocks__/fs.ts b/__mocks__/fs.ts new file mode 100644 index 0000000..8768c47 --- /dev/null +++ b/__mocks__/fs.ts @@ -0,0 +1,11 @@ +import { vol } from 'memfs' + +const fs = jest.requireActual('fs') + +function mockedWriteFile(path: string, content: string) { + vol.writeFileSync(path, content) +} + +jest.spyOn(fs, 'writeFile').mockImplementation(mockedWriteFile) + +module.exports = fs diff --git a/__mocks__/util.ts b/__mocks__/util.ts new file mode 100644 index 0000000..e557fa6 --- /dev/null +++ b/__mocks__/util.ts @@ -0,0 +1,13 @@ +const util = jest.requireActual('util') + +function promisify(fn) { + return (...args) => { + return new Promise(resolve => { + resolve(fn(...args)) + }) + } +} + +util.promisify = promisify + +module.exports = util diff --git a/dist/commands/generate.js b/dist/commands/generate.js index 93cad7e..fd0a18d 100644 --- a/dist/commands/generate.js +++ b/dist/commands/generate.js @@ -4,18 +4,25 @@ const tslib_1 = require('tslib') const command_1 = require('@oclif/command') const fs = require('fs') const html_ui_prototyper_1 = require('../html-ui-prototyper') +const printer_1 = require('../utils/printer') class Generate extends command_1.Command { run() { return tslib_1.__awaiter(this, void 0, void 0, function*() { - const { flags } = this.parse(Generate) - if (flags.features) { + const printer = new printer_1.default() + try { + const { flags } = this.parse(Generate) + if (!flags.features) throw new Error('Missing flag --features') const processResult = JSON.parse(flags.features) + if (processResult.features.length === 0) + throw new Error('No features found') const generator = new html_ui_prototyper_1.default( fs, flags.outputDir ) const result = yield generator.generate(processResult.features) - this.log(JSON.stringify(result)) + printer.printGeneratedFiles(result) + } catch (e) { + printer.printErrorMessage(e.message) } }) } diff --git a/dist/html-ui-prototyper.js b/dist/html-ui-prototyper.js index 1b3ad06..b59a7d2 100644 --- a/dist/html-ui-prototyper.js +++ b/dist/html-ui-prototyper.js @@ -4,8 +4,10 @@ const tslib_1 = require('tslib') const fs = require('fs') const util_1 = require('util') const path_1 = require('path') -const prettier = require('prettier') +const case_converter_1 = require('./utils/case-converter') +const format_html_1 = require('./utils/format-html') const cosmiconfig = require('cosmiconfig') +const { normalize } = require('normalize-diacritics') const widget_factory_1 = require('./widgets/widget-factory') class HtmlUIPrototyper { constructor(_fs = fs, _outputDir) { @@ -30,13 +32,13 @@ class HtmlUIPrototyper { } createHtmlFile(fileName, widgets) { return tslib_1.__awaiter(this, void 0, void 0, function*() { + fileName = yield normalize( + case_converter_1.convertCase(fileName, 'snake') + ) let content = widgets.reduce((result, widget) => { return result + widget.renderToString() }, '') - content = prettier.format(`
`, { - parser: 'html', - htmlWhitespaceSensitivity: 'ignore', - }) + content = format_html_1.formatHtml(``) const path = path_1.format({ dir: this._outputDir, name: fileName, diff --git a/dist/interfaces/app-config.d.ts b/dist/interfaces/app-config.d.ts index d56546e..9d65b8d 100644 --- a/dist/interfaces/app-config.d.ts +++ b/dist/interfaces/app-config.d.ts @@ -1,11 +1,25 @@ export interface AppConfig { widgets?: { - input?: WidgetConfig; + [key: string]: WidgetConfig; }; } export interface WidgetConfig { - opening: string; - closure?: string; - wrapperOpening?: string; - wrapperClosure?: string; + template?: string; + widget: { + opening: string; + closure?: string; + onePerValue?: boolean; + }; + valueWrapper?: { + opening: string; + closure: string; + }; + wrapper?: { + opening: string; + closure: string; + }; + label?: { + opening: string; + closure: string; + }; } diff --git a/dist/utils/case-converter.d.ts b/dist/utils/case-converter.d.ts new file mode 100644 index 0000000..fff91a0 --- /dev/null +++ b/dist/utils/case-converter.d.ts @@ -0,0 +1 @@ +export declare function convertCase(text: string, type: string): string; diff --git a/dist/utils/case-converter.js b/dist/utils/case-converter.js new file mode 100644 index 0000000..175a1a6 --- /dev/null +++ b/dist/utils/case-converter.js @@ -0,0 +1,30 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +const case_1 = require('case') +var CaseType +;(function(CaseType) { + CaseType['CAMEL'] = 'camel' + CaseType['PASCAL'] = 'pascal' + CaseType['SNAKE'] = 'snake' + CaseType['KEBAB'] = 'kebab' +})(CaseType || (CaseType = {})) +function convertCase(text, type) { + switch ( + type + .toString() + .trim() + .toLowerCase() + ) { + case CaseType.CAMEL: + return case_1.camel(text) + case CaseType.PASCAL: + return case_1.pascal(text) + case CaseType.SNAKE: + return case_1.snake(text) + case CaseType.KEBAB: + return case_1.kebab(text) + default: + return text // do nothing + } +} +exports.convertCase = convertCase diff --git a/dist/utils/format-html.d.ts b/dist/utils/format-html.d.ts new file mode 100644 index 0000000..a12da9d --- /dev/null +++ b/dist/utils/format-html.d.ts @@ -0,0 +1 @@ +export declare function formatHtml(html: string): any; diff --git a/dist/utils/format-html.js b/dist/utils/format-html.js new file mode 100644 index 0000000..eb4b05f --- /dev/null +++ b/dist/utils/format-html.js @@ -0,0 +1,10 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +const prettier = require('prettier') +function formatHtml(html) { + return prettier.format(html, { + parser: 'html', + htmlWhitespaceSensitivity: 'ignore', + }) +} +exports.formatHtml = formatHtml diff --git a/dist/utils/index.d.ts b/dist/utils/index.d.ts new file mode 100644 index 0000000..5a535f2 --- /dev/null +++ b/dist/utils/index.d.ts @@ -0,0 +1,3 @@ +export * from './case-converter'; +export * from './format-html'; +export * from './prop'; diff --git a/dist/utils/index.js b/dist/utils/index.js new file mode 100644 index 0000000..6d61023 --- /dev/null +++ b/dist/utils/index.js @@ -0,0 +1,6 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +const tslib_1 = require('tslib') +tslib_1.__exportStar(require('./case-converter'), exports) +tslib_1.__exportStar(require('./format-html'), exports) +tslib_1.__exportStar(require('./prop'), exports) diff --git a/dist/utils/printer.d.ts b/dist/utils/printer.d.ts new file mode 100644 index 0000000..43df5df --- /dev/null +++ b/dist/utils/printer.d.ts @@ -0,0 +1,4 @@ +export default class Printer { + printGeneratedFiles(files: string[]): void; + printErrorMessage(message: string): void; +} diff --git a/dist/utils/printer.js b/dist/utils/printer.js new file mode 100644 index 0000000..c45877d --- /dev/null +++ b/dist/utils/printer.js @@ -0,0 +1,21 @@ +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +const Table = require('cli-table3') +const colors = require('colors') +/* tslint:disable:no-console */ +class Printer { + printGeneratedFiles(files) { + const table = new Table({ + head: [colors.green('#'), colors.green('Generated files')], + }) + for (let i = 0; i < files.length; i++) { + const counter = i + 1 + table.push([counter, files[i]]) + } + console.log(table.toString()) + } + printErrorMessage(message) { + console.log(colors.red(message)) + } +} +exports.default = Printer diff --git a/dist/utils/prop.d.ts b/dist/utils/prop.d.ts index c409969..c34767e 100644 --- a/dist/utils/prop.d.ts +++ b/dist/utils/prop.d.ts @@ -1 +1 @@ -export declare function formatProperties(props: any, validProperties: string[]): string; +export declare function formatProperties(props: any, caseType?: string): string; diff --git a/dist/utils/prop.js b/dist/utils/prop.js index b4c1c4e..7f57fef 100644 --- a/dist/utils/prop.js +++ b/dist/utils/prop.js @@ -1,6 +1,7 @@ 'use strict' Object.defineProperty(exports, '__esModule', { value: true }) -function formatProperties(props, validProperties) { +const case_converter_1 = require('./case-converter') +function formatProperties(props, caseType = 'camel') { const translateProp = key => { switch (key) { case 'format': @@ -9,33 +10,15 @@ function formatProperties(props, validProperties) { return key } } - const getFormattedProp = key => { - let value = props[key] - const invalidIdPattern = /^\/\// - if (key === 'id') { - let newKey = key - // TODO: replace test wit str.match(pattern) - if (!invalidIdPattern.test(value)) { - const validIdPattern = /^#|~/ - const validClassPattern = /^\./ - if (validIdPattern.test(value)) { - value = value.toString().replace(validIdPattern, '') - } else if (validClassPattern.test(value)) { - newKey = 'class' - value = value.toString().replace(validClassPattern, '') - } - return `${translateProp(newKey)}="${value}"` - } - } - return `${translateProp(key)}="${value}"` - } - const formatValid = (result, prop) => { - return validProperties.includes(prop) - ? result + getFormattedProp(prop) + ' ' - : result + const getValueOf = key => + case_converter_1.convertCase(props[key].toString(), caseType) + const format = (result, key) => { + const value = getValueOf(key) + const htmlProp = translateProp(key) + return result + `${htmlProp}="${value}"` + ' ' } return Object.keys(props) - .reduce(formatValid, '') + .reduce(format, '') .trimRight() } exports.formatProperties = formatProperties diff --git a/dist/widgets/button.d.ts b/dist/widgets/button.d.ts index 906d5f2..53ef2a2 100644 --- a/dist/widgets/button.d.ts +++ b/dist/widgets/button.d.ts @@ -1,7 +1,7 @@ -import { Widget } from 'concordialang-ui-core'; -export default class Button extends Widget { - private readonly VALID_PROPERTIES; - constructor(props: any, name?: string); - renderToString(): string; +import { WidgetConfig } from '../interfaces/app-config'; +import HtmlWidget from './html-widget'; +export default class Button extends HtmlWidget { + constructor(props: any, name: string, config: WidgetConfig); + protected getFormattedProps(props: any): string; private getType; } diff --git a/dist/widgets/button.js b/dist/widgets/button.js index 3c35e32..33c5313 100644 --- a/dist/widgets/button.js +++ b/dist/widgets/button.js @@ -1,23 +1,23 @@ 'use strict' Object.defineProperty(exports, '__esModule', { value: true }) -const concordialang_ui_core_1 = require('concordialang-ui-core') +const lodash_1 = require('lodash') const prop_1 = require('../utils/prop') -class Button extends concordialang_ui_core_1.Widget { - constructor(props, name) { - super(props, name || '') - this.VALID_PROPERTIES = ['id', 'disabled', 'value'] +const html_widget_1 = require('./html-widget') +class Button extends html_widget_1.default { + constructor(props, name, config) { + super(props, name, config) + this.props.value = this.props.value || name } - renderToString() { - // const inputType = this.getType(this.props.datatype as string) - const properties = prop_1.formatProperties( - this.props, - this.VALID_PROPERTIES - ) - // return `` - return `` + getFormattedProps(props) { + // Defines the properties that will be injected in the widget and its order. + const VALID_PROPERTIES = ['id', 'type', 'disabled'] + props.type = this.getType(props.datatype) + props.value = props.value || this.name + const filteredProps = lodash_1.pick(props, VALID_PROPERTIES) + return prop_1.formatProperties(filteredProps) } getType(datatype) { - return `type="${datatype || 'button'}"` + return datatype || 'button' } } exports.default = Button diff --git a/dist/widgets/checkbox.d.ts b/dist/widgets/checkbox.d.ts index b7204c6..9b4fc7a 100644 --- a/dist/widgets/checkbox.d.ts +++ b/dist/widgets/checkbox.d.ts @@ -1,6 +1,6 @@ -import { Widget } from 'concordialang-ui-core'; -export default class Checkbox extends Widget { - private readonly VALID_PROPERTIES; - constructor(props: any, name: string); - renderToString(): string; +import { WidgetConfig } from '../interfaces/app-config'; +import HtmlWidget from './html-widget'; +export default class Checkbox extends HtmlWidget { + constructor(props: any, name: string, config: WidgetConfig); + protected getFormattedProps(props: any): string; } diff --git a/dist/widgets/checkbox.js b/dist/widgets/checkbox.js index f322947..1218466 100644 --- a/dist/widgets/checkbox.js +++ b/dist/widgets/checkbox.js @@ -1,23 +1,19 @@ 'use strict' Object.defineProperty(exports, '__esModule', { value: true }) -const concordialang_ui_core_1 = require('concordialang-ui-core') +const lodash_1 = require('lodash') const prop_1 = require('../utils/prop') -class Checkbox extends concordialang_ui_core_1.Widget { - constructor(props, name) { - super(props, name) - this.VALID_PROPERTIES = ['value', 'required'] +const html_widget_1 = require('./html-widget') +class Checkbox extends html_widget_1.default { + constructor(props, name, config) { + super(props, name, config) } - // TODO: remove \n - renderToString() { - const properties = prop_1.formatProperties( - this.props, - this.VALID_PROPERTIES - ) - if (properties) - return `